feat: wire setup-gbrain + brain-restore + brain-uninstall to use the helper

setup-gbrain Step 7 now invokes gstack-gbrain-source-wireup --strict after
gstack-brain-init + gbrain_sync_mode is set. Strict mode means the user sees
the failure rather than silently ending up with an unwired brain.

bin/gstack-brain-init drops 60 lines of dead code: the HTTP POST to
${GBRAIN_URL}/ingest-repo, the GBRAIN_URL_VAL/GBRAIN_TOKEN_VAL probes, the
consumers.json writer, and the chore commit step. CONSUMERS_FILE variable
declaration removed. The closing message no longer points at the dead
gstack-brain-consumer add path.

bin/gstack-brain-restore drops the 18-line consumers.json token-rehydration
block (was a no-op for the only consumer that ever existed). Adds a
best-effort wireup invocation after the brain-repo clone so 2nd-Mac restore
gets gbrain federation automatically. Failure prints a stderr WARNING but
does not abort the restore — restore's primary job is the git clone.

bin/gstack-brain-uninstall calls the helper's --uninstall mode (which
removes the gbrain source registration, the git worktree, and the
future-launchd-plist stub) before the existing legacy consumers.json
removal. Ordering is fragile-by-design: helper derives source-id via
multi-fallback so it works even after .git is destroyed.

bin/gstack-brain-consumer gets a DEPRECATED header note. Stays in the tree
for one cycle of grace; removal in v1.13.0.0.

setup-gbrain/SKILL.md is regenerated from the .tmpl via gen:skill-docs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-04-25 22:07:32 -07:00
parent b21bf34893
commit fbb1a82d97
6 changed files with 65 additions and 91 deletions

View File

@@ -1,6 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# gstack-brain-consumer — manage the consumer (reader) registry. # gstack-brain-consumer — manage the consumer (reader) registry.
# #
# DEPRECATED in v1.12.3.0. This binary targets a gbrain HTTP /ingest-repo
# endpoint that never shipped on the gbrain side. Live federation now uses
# `gbrain sources` directly via bin/gstack-gbrain-source-wireup. This file
# stays for one cycle to avoid breaking external scripts; removal in v1.13.0.0.
#
# Consumer = a reader that ingests the gstack-brain git repo as a source of # Consumer = a reader that ingests the gstack-brain git repo as a source of
# session memory. v1 primary consumer is GBrain; later versions can register # session memory. v1 primary consumer is GBrain; later versions can register
# Codex, OpenClaw, or third-party readers. # Codex, OpenClaw, or third-party readers.

View File

@@ -22,11 +22,9 @@
# 8. Prompt for remote (default: gh repo create --private gstack-brain-$USER) # 8. Prompt for remote (default: gh repo create --private gstack-brain-$USER)
# 9. Initial commit + push # 9. Initial commit + push
# 10. Write ~/.gstack-brain-remote.txt (URL-only, safe to share) # 10. Write ~/.gstack-brain-remote.txt (URL-only, safe to share)
# 11. Register GBrain consumer (HTTP POST if GBRAIN_URL set; else defer)
# #
# Env: # Env:
# GSTACK_HOME — override ~/.gstack # GSTACK_HOME — override ~/.gstack
# GBRAIN_URL — GBrain ingest endpoint base URL (for consumer registration)
set -euo pipefail set -euo pipefail
@@ -34,7 +32,6 @@ GSTACK_HOME="${GSTACK_HOME:-$HOME/.gstack}"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
CONFIG_BIN="$SCRIPT_DIR/gstack-config" CONFIG_BIN="$SCRIPT_DIR/gstack-config"
REMOTE_FILE="$HOME/.gstack-brain-remote.txt" REMOTE_FILE="$HOME/.gstack-brain-remote.txt"
CONSUMERS_FILE="$GSTACK_HOME/consumers.json"
REMOTE_URL="" REMOTE_URL=""
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
@@ -280,68 +277,6 @@ fi
echo "$REMOTE_URL" > "$REMOTE_FILE" echo "$REMOTE_URL" > "$REMOTE_FILE"
chmod 600 "$REMOTE_FILE" chmod 600 "$REMOTE_FILE"
# ---- register GBrain consumer ----
mkdir -p "$GSTACK_HOME"
CONSUMER_STATUS="pending"
GBRAIN_URL_VAL="${GBRAIN_URL:-$("$CONFIG_BIN" get gbrain_url 2>/dev/null || echo "")}"
GBRAIN_TOKEN_VAL="${GBRAIN_TOKEN:-$("$CONFIG_BIN" get gbrain_token 2>/dev/null || echo "")}"
if [ -n "$GBRAIN_URL_VAL" ] && [ -n "$GBRAIN_TOKEN_VAL" ]; then
# Try the HTTP handoff.
HTTP_RESP=$(curl -sS -X POST "${GBRAIN_URL_VAL%/}/ingest-repo" \
-H "Authorization: Bearer $GBRAIN_TOKEN_VAL" \
-H "Content-Type: application/json" \
--data "{\"repo_url\":\"$REMOTE_URL\"}" \
-w "\n%{http_code}" 2>&1 || echo -e "\ncurl-error")
HTTP_CODE=$(echo "$HTTP_RESP" | tail -1)
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "204" ]; then
CONSUMER_STATUS="ok"
echo "GBrain consumer registered: $GBRAIN_URL_VAL"
else
echo "GBrain ingest endpoint returned HTTP $HTTP_CODE; will retry on next skill run."
fi
elif [ -z "$GBRAIN_URL_VAL" ]; then
echo "(GBRAIN_URL not configured; skipping consumer registration. Set it with:"
echo " gstack-config set gbrain_url <url>"
echo " gstack-config set gbrain_token <token>"
echo " then run: gstack-brain-consumer add gbrain --ingest-url <url> --token <token>)"
fi
# Write consumers.json — the canonical registry. Tokens are NOT stored here;
# they stay in gstack-config (machine-local). This file IS synced so a new
# machine knows which consumers exist and can prompt for tokens.
python3 - "$CONSUMERS_FILE" "$GBRAIN_URL_VAL" "$CONSUMER_STATUS" <<'PYEOF'
import sys, json, os
path, url, status = sys.argv[1:4]
try:
with open(path) as f:
data = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
data = {"consumers": []}
# Upsert GBrain entry.
entry = {"name": "gbrain", "ingest_url": url, "status": status, "token_ref": "gbrain_token"}
updated = False
for i, c in enumerate(data.get("consumers", [])):
if c.get("name") == "gbrain":
data["consumers"][i] = entry
updated = True
break
if not updated:
data.setdefault("consumers", []).append(entry)
with open(path, "w") as f:
json.dump(data, f, indent=2)
f.write("\n")
PYEOF
# Stage and commit consumers.json in the same session.
cd "$GSTACK_HOME"
git add -f consumers.json 2>/dev/null || true
if ! git diff --cached --quiet 2>/dev/null; then
git -c user.email="gstack@localhost" -c user.name="gstack-brain-init" \
commit -q -m "chore: register GBrain consumer"
git push -q origin HEAD 2>/dev/null || true
fi
# ---- done ---- # ---- done ----
cat <<EOF cat <<EOF
@@ -350,12 +285,14 @@ Repo: $GSTACK_HOME (git)
Remote: $REMOTE_URL Remote: $REMOTE_URL
Remote URL also saved at: $REMOTE_FILE Remote URL also saved at: $REMOTE_FILE
Sync happens automatically at the start and end of each skill (no daemon). Sync to GitHub happens automatically at the start and end of each skill
Check status anytime with: (no daemon). Check status anytime with:
gstack-brain-sync --status gstack-brain-sync --status
To activate sync, the next skill you run will ask you one question about The next skill run will ask you one question about privacy mode (full /
privacy mode (sync everything / artifacts only / off). artifacts-only / off). After that, /setup-gbrain Step 7 (or the
gstack-gbrain-source-wireup helper) registers this repo as a federated
source on gbrain so its content is searchable via 'gbrain search'.
New machine? On the other laptop, put a copy of: New machine? On the other laptop, put a copy of:
$REMOTE_FILE $REMOTE_FILE

View File

@@ -19,7 +19,8 @@
# 3. rsync-copy tracked files into ~/.gstack/ with skip-if-same-hash # 3. rsync-copy tracked files into ~/.gstack/ with skip-if-same-hash
# 4. Move staging's .git into ~/.gstack/.git # 4. Move staging's .git into ~/.gstack/.git
# 5. Register local git config merge drivers (they don't clone from remote) # 5. Register local git config merge drivers (they don't clone from remote)
# 6. Rehydrate consumers.json endpoints; prompt for tokens # 6. Wire the cloned brain into gbrain via gstack-gbrain-source-wireup
# (best-effort; restore continues even if gbrain wireup fails)
# #
# Env: # Env:
# GSTACK_HOME — override ~/.gstack # GSTACK_HOME — override ~/.gstack
@@ -195,25 +196,6 @@ sys.exit(0)
HOOK_EOF HOOK_EOF
chmod +x "$HOOK" chmod +x "$HOOK"
# ---- rehydrate consumers, prompt for tokens ----
if [ -f "$GSTACK_HOME/consumers.json" ]; then
echo ""
echo "Consumer registry restored. Tokens are machine-local and NOT synced."
echo "Run these for each consumer to re-enter tokens:"
python3 - "$GSTACK_HOME/consumers.json" <<'PYEOF'
import sys, json
try:
with open(sys.argv[1]) as f:
data = json.load(f)
except Exception:
sys.exit(0)
for c in data.get("consumers", []):
name = c.get("name", "")
token_ref = c.get("token_ref", f"{name}_token")
print(f" gstack-config set {token_ref} <your-token>")
PYEOF
fi
# ---- write remote helper file if missing ---- # ---- write remote helper file if missing ----
if [ ! -f "$REMOTE_FILE" ]; then if [ ! -f "$REMOTE_FILE" ]; then
echo "$REMOTE_URL" > "$REMOTE_FILE" echo "$REMOTE_URL" > "$REMOTE_FILE"
@@ -222,6 +204,12 @@ if [ ! -f "$REMOTE_FILE" ]; then
echo "Wrote $REMOTE_FILE for future skill-run auto-detection." echo "Wrote $REMOTE_FILE for future skill-run auto-detection."
fi fi
# ---- wire the cloned brain into gbrain (best-effort) ----
WIREUP_BIN="$SCRIPT_DIR/gstack-gbrain-source-wireup"
if [ -x "$WIREUP_BIN" ]; then
"$WIREUP_BIN" || >&2 echo "WARNING: gbrain wireup failed; run $WIREUP_BIN manually after fixing prereqs"
fi
cat <<EOF cat <<EOF
gstack-brain-restore complete. gstack-brain-restore complete.

View File

@@ -120,6 +120,16 @@ rm -f "$GSTACK_HOME/.brain-last-pull" 2>/dev/null || true
rm -f "$GSTACK_HOME/.brain-skip.txt" 2>/dev/null || true rm -f "$GSTACK_HOME/.brain-skip.txt" 2>/dev/null || true
rm -f "$GSTACK_HOME/.brain-sync-status.json" 2>/dev/null || true rm -f "$GSTACK_HOME/.brain-sync-status.json" 2>/dev/null || true
rm -rf "$GSTACK_HOME/.brain-sync.lock.d" 2>/dev/null || true rm -rf "$GSTACK_HOME/.brain-sync.lock.d" 2>/dev/null || true
# ---- unregister gbrain federated source + remove worktree (best-effort) ----
# The wireup helper handles: gbrain sources remove, git worktree remove,
# launchd plist (future). All best-effort; uninstall continues on failure.
WIREUP_BIN="$SCRIPT_DIR/gstack-gbrain-source-wireup"
if [ -x "$WIREUP_BIN" ]; then
"$WIREUP_BIN" --uninstall 2>/dev/null || true
fi
# ---- legacy consumers.json (no longer written by gstack-brain-init since v1.12.3.0) ----
rm -f "$GSTACK_HOME/consumers.json" 2>/dev/null || true rm -f "$GSTACK_HOME/consumers.json" 2>/dev/null || true
# ---- clear config keys ---- # ---- clear config keys ----

View File

@@ -1345,7 +1345,7 @@ For `/setup-gbrain --repo` invocations, execute ONLY Step 6 and exit.
--- ---
## Step 7: Offer gstack-brain-sync ## Step 7: Offer gstack-brain-sync + wire it into gbrain
Separate AskUserQuestion: "Also sync your gstack session memory (learnings, Separate AskUserQuestion: "Also sync your gstack session memory (learnings,
plans, retros) to a private git repo that gbrain can index across machines?" plans, retros) to a private git repo that gbrain can index across machines?"
@@ -1363,6 +1363,23 @@ If yes:
# or "full" if user picked yes-full # or "full" if user picked yes-full
``` ```
Then wire the brain repo into gbrain so its content is searchable from any
gbrain client (this Claude Code session, future Macs, optional cloud agents).
The helper creates a `git worktree` of `~/.gstack/`, registers it as a
federated source on the user's gbrain (Supabase or PGLite), and runs an
initial `gbrain sync`. Local-Mac only. No cloud agent required. Subsequent
skill runs trigger incremental sync via the existing skill-end push hook.
```bash
~/.claude/skills/gstack/bin/gstack-gbrain-source-wireup --strict
```
`--strict` exits non-zero on missing prereqs (gbrain not installed, < 0.18.0,
or no `~/.gstack/.git` yet) so the user sees the failure rather than silently
ending up with an unwired brain. On non-zero exit, surface the helper's
output and STOP per skill rules — search-across-machines won't work until
the prereq is fixed.
--- ---
## Step 8: Persist `## GBrain Configuration` in CLAUDE.md ## Step 8: Persist `## GBrain Configuration` in CLAUDE.md

View File

@@ -347,7 +347,7 @@ For `/setup-gbrain --repo` invocations, execute ONLY Step 6 and exit.
--- ---
## Step 7: Offer gstack-brain-sync ## Step 7: Offer gstack-brain-sync + wire it into gbrain
Separate AskUserQuestion: "Also sync your gstack session memory (learnings, Separate AskUserQuestion: "Also sync your gstack session memory (learnings,
plans, retros) to a private git repo that gbrain can index across machines?" plans, retros) to a private git repo that gbrain can index across machines?"
@@ -365,6 +365,23 @@ If yes:
# or "full" if user picked yes-full # or "full" if user picked yes-full
``` ```
Then wire the brain repo into gbrain so its content is searchable from any
gbrain client (this Claude Code session, future Macs, optional cloud agents).
The helper creates a `git worktree` of `~/.gstack/`, registers it as a
federated source on the user's gbrain (Supabase or PGLite), and runs an
initial `gbrain sync`. Local-Mac only. No cloud agent required. Subsequent
skill runs trigger incremental sync via the existing skill-end push hook.
```bash
~/.claude/skills/gstack/bin/gstack-gbrain-source-wireup --strict
```
`--strict` exits non-zero on missing prereqs (gbrain not installed, < 0.18.0,
or no `~/.gstack/.git` yet) so the user sees the failure rather than silently
ending up with an unwired brain. On non-zero exit, surface the helper's
output and STOP per skill rules — search-across-machines won't work until
the prereq is fixed.
--- ---
## Step 8: Persist `## GBrain Configuration` in CLAUDE.md ## Step 8: Persist `## GBrain Configuration` in CLAUDE.md