name: sillytavern-honcho-setup description: Install the Honcho Memory extension + plugin into a SillyTavern checkout. Use when installing, re-installing, or troubleshooting the integration — including on a fresh SillyTavern checkout. Safe to re-run; every patch step is idempotent. allowed-tools: Read, Glob, Grep, Bash(npm:), Bash(node:), Bash(ln:), Bash(ls:), Bash(cd:), Bash(cat:), Bash(mkdir:), Bash(cp:), Bash(rm:), Bash(git:), Bash(curl:), Bash(lsof:), Bash(pgrep:), Bash(kill:), Bash(sleep:*), Edit, Write, AskUserQuestion user-invocable: true
Install the Honcho Memory extension into SillyTavern
- Follow each phase in order. Do not skip preflight.
- Verify every patch is idempotent with
grep -qbefore running the Edit. - If any phase fails, stop and surface the failure — do not route around it.
When to use / can be skipped
Use when: setting up the integration on a new SillyTavern checkout, re-installing after a failed attempt, or diagnosing a broken install.
Skip when: the install is already verified working (Phase 4 smoke test passes) and you don't need to change configuration.
Skill discoverability (read this before invoking)
This skill lives inside the sillytavern-honcho repo at skills/setup/SKILL.md. Claude Code does not auto-discover it. To invoke as /sillytavern-honcho-setup in a new session, either:
- Symlink into the project's
.claude/skills/directory, then restart the Claude Code session:mkdir -p <cwd>/.claude/skills/sillytavern-honcho-setup ln -sf <repo>/skills/setup/SKILL.md \ <cwd>/.claude/skills/sillytavern-honcho-setup/SKILL.md - Or read the file directly and execute each phase as a checklist (what Path B's executor fell back to).
The repo's README should document option 1 as the supported path.
Phase 0 — Preflight
Run these checks in parallel. If any fails, stop and report — do not proceed.
PORT="${ST_PORT:-8000}"
node --version # require >= 18
git --version # required
[[ -d "$ST_DIR" ]] && git -C "$ST_DIR" status --short # expect clean tree
[[ -d "$SH_DIR" ]] && git -C "$SH_DIR" branch --show-current # capture branch for logs
lsof -i ":$PORT" -P 2>/dev/null | grep LISTEN # detect pre-existing SillyTavern on target port
[[ -f ~/.honcho/config.json ]] && echo "global config present"
Where:
$ST_DIR= SillyTavern checkout. Ask the user if not set.$SH_DIR=sillytavern-honchocheckout. Ask the user if not set.$ST_PORT= SillyTavern's listen port. Defaults to 8000. Override (e.g.ST_PORT=8099) to run alongside an existing SillyTavern on 8000 without collision.
Gate: pre-existing SillyTavern on the target port
If $PORT is already listening, ask the user before proceeding. Running this skill will start a new server on the same port and can clobber their live session.
Use AskUserQuestion: "A SillyTavern instance is already running on port $PORT (PID <pid>). I'll need to stop it to complete this install. Stop it? [yes / no — I'll pick a different port]".
If the user picks a different port, re-run with ST_PORT=<new-port> exported — every npm start / node server.js / curl call below already honors $ST_PORT.
Gate: existing vs cold-start user
Use AskUserQuestion with two options:
- Existing Honcho user — "I already use Honcho in another tool (Claude Code, Cursor, Hermes). I have
~/.honcho/config.jsonwith a real API key and workspace." → Phase 5 Branch A. - Cold-start user — "First time using Honcho. I'll need to enter an API key and workspace ID by hand." → Phase 5 Branch B.
If ~/.honcho/config.json is absent OR present but hosts.sillytavern = {} AND no root-level apiKey, treat the user as cold-start regardless of answer — the file won't help the plugin resolve a key.
Phase 1 — SillyTavern baseline and config.yaml
1.1 Install dependencies
cd "$ST_DIR" && npm install
Expect npm warn lines and a vulnerability count. Skill does not fix these; they're upstream. Note: v1 claimed a postinstall script copies default/config.yaml → config.yaml. That is not true on current SillyTavern. See 1.2.
1.2 Generate config.yaml
config.yaml is created by SillyTavern's server on first launch, not by npm install. Start the server once to generate it, then stop.
PORT="${ST_PORT:-8000}"
BOOT_LOG=$(mktemp -t silly-first-launch.XXXXXX.log)
# exec npm so $! captures the node process directly (not the subshell),
# making `kill "$ST_PID"` reach node without the broad `pkill -f
# "node.*server.js"` footgun that would nuke unrelated ST instances.
( cd "$ST_DIR" && exec npm start -- --port "$PORT" > "$BOOT_LOG" 2>&1 ) &
ST_PID=$!
# Wait for config.yaml to appear, up to 90s (tight on slow hardware)
for _ in $(seq 1 90); do [[ -f "$ST_DIR/config.yaml" ]] && break; sleep 1; done
{ kill "$ST_PID" 2>/dev/null; wait "$ST_PID" 2>/dev/null; } || true
[[ -f "$ST_DIR/config.yaml" ]] || { echo "config.yaml did not appear — inspect $BOOT_LOG"; exit 1; }
1.3 Enable server plugins; disable auto-update
Two edits in config.yaml. Both are idempotent (precheck before editing).
cd "$ST_DIR"
# sed -i.bak + rm is the portable form; `sed -i ''` is BSD-only and
# breaks on GNU sed (Linux). The .bak suffix is accepted by both.
grep -q '^enableServerPlugins: true' config.yaml || {
sed -i.bak 's/^enableServerPlugins: false/enableServerPlugins: true/' config.yaml
rm -f config.yaml.bak
}
grep -q '^enableServerPluginsAutoUpdate: false' config.yaml || {
sed -i.bak 's/^enableServerPluginsAutoUpdate: true/enableServerPluginsAutoUpdate: false/' config.yaml
rm -f config.yaml.bak
}
Why disable auto-update: this install symlinks a local dev checkout of sillytavern-honcho. If auto-update is on, SillyTavern tries to git pull that checkout on every boot, which surprises anyone actively editing the repo. Re-enable after shipping.
Verify:
grep -E '^enableServerPlugins(AutoUpdate)?:' config.yaml
# expect: enableServerPlugins: true
# enableServerPluginsAutoUpdate: false
# Note: empty alternation like ^foo(|Bar): fails on BSD grep (macOS default).
# The (AutoUpdate)? form is portable across both GNU and BSD grep.
Phase 2 — Apply core patches (idempotent)
These add a HONCHO entry to SillyTavern's SECRET_KEYS object in two files, plus a FRIENDLY_NAMES entry on the client side. They're required for the Extensions-panel API-key input (SillyTavern secret manager) to persist a key. Not required for the plugin to load, and not required for the ~/.honcho/config.json config path.
2.1 Server-side SECRET_KEYS
cd "$ST_DIR"
if ! grep -q "HONCHO: 'api_key_honcho'" src/endpoints/secrets.js; then
# Insert before the closing `};` of the SECRET_KEYS object (the first `};` in the file)
# v1 pinned this to a specific neighbor key (VOLCENGINE_ACCESS_KEY). Current staging
# has WORKERS_AI last — the neighbor drifts. Patch by structure, not neighbor name.
awk '
/^export const SECRET_KEYS = \{/ { in_sk=1 }
in_sk && /^\};/ && !done { print " HONCHO: '\''api_key_honcho'\'',"; in_sk=0; done=1 }
{ print }
' src/endpoints/secrets.js > src/endpoints/secrets.js.new && mv src/endpoints/secrets.js.new src/endpoints/secrets.js
fi
grep -q "HONCHO: 'api_key_honcho'" src/endpoints/secrets.js # verify
2.2 Client-side SECRET_KEYS and FRIENDLY_NAMES
Two inserts in public/scripts/secrets.js. Same idempotency pattern.
cd "$ST_DIR"
# SECRET_KEYS insert — first `};` after the SECRET_KEYS open
if ! grep -q "HONCHO: 'api_key_honcho'" public/scripts/secrets.js; then
awk '
/^export const SECRET_KEYS = \{/ { in_sk=1 }
in_sk && /^\};/ && !sk_done { print " HONCHO: '\''api_key_honcho'\'',"; in_sk=0; sk_done=1 }
{ print }
' public/scripts/secrets.js > public/scripts/secrets.js.new && mv public/scripts/secrets.js.new public/scripts/secrets.js
fi
# FRIENDLY_NAMES insert — second `};`, scoped to the FRIENDLY_NAMES block
if ! grep -q "\[SECRET_KEYS.HONCHO\]: 'Honcho AI'" public/scripts/secrets.js; then
awk '
/^const FRIENDLY_NAMES = \{/ { in_fn=1 }
in_fn && /^\};/ && !fn_done { print " [SECRET_KEYS.HONCHO]: '\''Honcho AI'\'',"; in_fn=0; fn_done=1 }
{ print }
' public/scripts/secrets.js > public/scripts/secrets.js.new && mv public/scripts/secrets.js.new public/scripts/secrets.js
fi
grep -c 'HONCHO' public/scripts/secrets.js # expect >= 2
Phase 3 — Install extension and plugin
3.1 Symlink extension
cd "$ST_DIR"
EXT_DIR="public/scripts/extensions/third-party/sillytavern-honcho"
if [[ ! -e "$EXT_DIR" ]]; then
mkdir -p "$(dirname "$EXT_DIR")"
ln -s "$SH_DIR" "$EXT_DIR"
fi
[[ -f "$EXT_DIR/manifest.json" ]] || { echo "manifest.json unreachable — abort"; exit 1; }
3.2 Symlink plugin + install SDK
cd "$ST_DIR"
if [[ ! -e "plugins/honcho-proxy" ]]; then
ln -s "$SH_DIR/plugin" "plugins/honcho-proxy"
fi
[[ -f "plugins/honcho-proxy/index.js" ]] || { echo "plugin index.js unreachable — abort"; exit 1; }
# Install once; npm install is idempotent
( cd "plugins/honcho-proxy" && npm install )
[[ -f "plugins/honcho-proxy/node_modules/@honcho-ai/sdk/package.json" ]] || { echo "SDK install failed"; exit 1; }
Phase 4 — Start and verify (programmatic, no browser)
4.1 Start the server
PORT="${ST_PORT:-8000}"
cd "$ST_DIR"
rm -f /tmp/silly-server.log
npm start -- --port "$PORT" > /tmp/silly-server.log 2>&1 &
ST_PID=$!
# Wait for "listening" line, up to 30s
for _ in $(seq 1 30); do grep -q "is listening on" /tmp/silly-server.log && break; sleep 1; done
4.2 Verify plugin loaded (substring match only)
v1 grepped for "Honcho SDK loaded successfully" and "Plugin initialized with 5 routes". Both are brittle. Check substrings instead.
grep -q "\[honcho-proxy\] Honcho SDK loaded" /tmp/silly-server.log || { echo "plugin did not load"; exit 1; }
grep -q "\[honcho-proxy\] Plugin initialized" /tmp/silly-server.log || { echo "plugin failed to init"; exit 1; }
grep "\[honcho-proxy\]" /tmp/silly-server.log # surface any other messages for the user
4.3 Verify extension served
PORT="${ST_PORT:-8000}"
for f in manifest.json settings.html index.js style.css; do
code=$(curl -s -o /dev/null -w "%{http_code}" \
"http://127.0.0.1:$PORT/scripts/extensions/third-party/sillytavern-honcho/$f")
[[ "$code" == "200" ]] || { echo "$f returned $code — abort"; exit 1; }
done
4.4 Verify plugin route is mounted
A POST with a bogus workspaceId should return 403/400/500, not 404. A 404 means the route isn't mounted.
code=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
-H "Content-Type: application/json" \
-d '{"workspaceId":"smoke","peerId":"smoke"}' \
"http://127.0.0.1:$PORT/api/plugins/honcho-proxy/peer")
[[ "$code" != "404" ]] || { echo "plugin route not mounted — abort"; exit 1; }
echo "plugin route mounted (HTTP $code — expected 400/403/500 without CSRF+key)"
Phase 5 — Configuration (branch on user type)
Branch A — Existing Honcho user
The plugin reads ~/.honcho/config.json at startup. Confirm the keys it actually uses are resolvable:
# python3 is preferred over jq because it's a hard dep on macOS + most
# Linux dev envs. If it's still missing (minimal Alpine / Debian-slim),
# skip the probe and fall through to Branch B rather than crashing.
if ! command -v python3 >/dev/null 2>&1; then
echo "python3 not available — skipping apiKey probe; treat as cold-start" >&2
# Branch B
else
# Config path passed via argv (not heredoc interpolation) so a
# pathological path can't inject Python code.
python3 - "$HOME/.honcho/config.json" <<'PY' 2>/dev/null
import json, sys
try:
d = json.load(open(sys.argv[1]))
h = d.get("hosts", {}).get("sillytavern", {})
ok = bool(h.get("apiKey") or d.get("apiKey"))
print("resolvable" if ok else "EMPTY — treat as cold-start", file=sys.stderr)
sys.exit(0 if ok else 1)
except Exception:
print("EMPTY — treat as cold-start", file=sys.stderr)
sys.exit(1)
PY
fi
If the check prints EMPTY — treat as cold-start (or python3 not available), jump to Branch B. "Auto-populated" is a false promise when hosts.sillytavern = {} with no root-level fallback.
If resolvable, no further config action — the plugin will read the keys on startup. Verify with a real API call in Phase 6.
Branch B — Cold-start user
Use AskUserQuestion twice:
- "Paste your Honcho API key from https://app.honcho.dev (stored only in SillyTavern's secret manager, never logged)." → save as
HONCHO_API_KEY. - "Enter your Honcho workspace ID (e.g.
sillytavern, or copy from your Honcho dashboard)." → save asHONCHO_WORKSPACE_ID.
Entry happens in the SillyTavern UI (Phase 6, browser step) — this skill does not POST the key to the plugin directly because the SillyTavern secret manager is the intended persistence surface and hitting it from the CLI requires an authenticated session cookie. Surface both values to the user when they open the browser.
Phase 6 — End-to-end verification
6.1 Agent-path verification (required — no browser)
Round-trip a real request through the plugin. Requires the user's Honcho API key.
PORT="${ST_PORT:-8000}"
JAR=$(mktemp)
# Capture the CSRF token. SillyTavern exposes it at GET /csrf-token as JSON
# ({"token": "..."}), and binds that token to a `session-*` cookie. The POST
# below MUST reuse the same cookie jar or CSRF middleware returns an HTML
# ForbiddenError before the honcho-proxy handler runs.
# Token extraction tolerates python3 absence by falling back to node, which
# is already a hard dep for this skill (SillyTavern requires it).
if command -v python3 >/dev/null 2>&1; then
CSRF_TOKEN=$(curl -s -c "$JAR" "http://127.0.0.1:$PORT/csrf-token" \
| python3 -c 'import sys, json; print(json.load(sys.stdin)["token"])')
else
CSRF_TOKEN=$(curl -s -c "$JAR" "http://127.0.0.1:$PORT/csrf-token" \
| node -e 'let b=""; process.stdin.on("data",d=>b+=d).on("end",()=>process.stdout.write(JSON.parse(b).token))')
fi
[[ -n "$CSRF_TOKEN" ]] || { echo "CSRF token capture failed — abort"; rm -f "$JAR"; exit 1; }
# Minimal probe: hit /chat with a known-bad workspace to confirm the SDK
# initializes without throwing and returns a structured Honcho error (not a
# crash). -b reuses the session cookie from the GET.
curl -s -b "$JAR" -X POST \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: $CSRF_TOKEN" \
-d "{\"workspaceId\":\"$HONCHO_WORKSPACE_ID\",\"peerId\":\"smoke-test-peer\",\"query\":\"hello\"}" \
"http://127.0.0.1:$PORT/api/plugins/honcho-proxy/chat" | tee /tmp/honcho-probe.json
rm -f "$JAR"
Success criteria:
- HTTP 200 with a non-empty JSON body, OR
- HTTP 4xx/5xx with a structured Honcho error message (not a stack trace, not "ECONNREFUSED").
If the probe returns a stack trace or connection refused, Honcho is unreachable or the SDK failed to initialize — stop and report.
6.2 Browser-path verification (recommended)
Some of the skill's guarantees (drawer render, status indicator, UI-entered key persistence) can only be verified in a browser. Surface this to the user:
Open http://127.0.0.1:$PORT in your browser. Click the puzzle-piece icon (top-right) → Extensions → expand "Honcho Memory". Check Enable. Click the API Key field and paste
$HONCHO_API_KEY. Enter$HONCHO_WORKSPACE_IDin the Workspace ID field. Status should change from "Not ready" to "Ready". Open any character chat, send "hello", and watch the browser DevTools console for[Honcho] Session ready for chat: ....
The skill's agent-path stop condition is Phase 6.1 passing. Browser-path is user-driven and optional from the agent's perspective.
Troubleshooting
| Symptom | Most likely cause | Fix |
|---|---|---|
config.yaml did not appear — abort in 1.2 |
SillyTavern server failed to start | Read /tmp/silly-first-launch.log — usually missing Node modules (rerun 1.1) |
sed: config.yaml: No such file |
1.2 skipped | Run 1.2 before 1.3 |
| Phase 2 patch inserted twice | grep -q precheck failed to match due to whitespace |
Open the file, remove the duplicate line, rerun from Phase 2 |
plugin did not load in 4.2 |
enableServerPlugins: false OR plugin symlink broken |
Re-verify 1.3 and 3.2; check /tmp/silly-server.log for symlink errors |
| 4.3 returns 404 for manifest | Extension symlink target doesn't resolve | ls -la "$EXT_DIR" to confirm target; readlink the path |
| 4.4 returns 404 | Plugin not mounted by SillyTavern | Confirm plugins/honcho-proxy/info.id === "honcho-proxy" and server was restarted after Phase 3 |
| 6.1 ECONNREFUSED to Honcho | Honcho unreachable or wrong base URL | Verify Honcho dashboard is up; check API key; check HONCHO_BASE_URL env if set |
| "Auto-populated" message misleading | hosts.sillytavern = {} with no root fallback |
Treat as cold-start; enter key via UI |
Uninstall (appendix)
cd "$ST_DIR"
rm -f plugins/honcho-proxy
rm -f public/scripts/extensions/third-party/sillytavern-honcho
# Revert the two edits (idempotent — grep-guarded). Uses sed -i.bak + rm
# for BSD/GNU portability (plain `sed -i ''` is BSD-only).
sed -i.bak "/HONCHO: 'api_key_honcho',/d" src/endpoints/secrets.js && rm -f src/endpoints/secrets.js.bak
sed -i.bak "/HONCHO: 'api_key_honcho',/d" public/scripts/secrets.js && rm -f public/scripts/secrets.js.bak
sed -i.bak "/\[SECRET_KEYS.HONCHO\]: 'Honcho AI',/d" public/scripts/secrets.js && rm -f public/scripts/secrets.js.bak
# Optional: revert config.yaml changes
grep -q '^enableServerPlugins: false' config.yaml || {
sed -i.bak 's/^enableServerPlugins: true/enableServerPlugins: false/' config.yaml
rm -f config.yaml.bak
}
Anti-patterns (things this skill tends to get wrong)
| Anti-pattern | Correction |
|---|---|
Assume npm install creates config.yaml |
It doesn't. Phase 1.2 runs npm start briefly to generate it. |
| Patch by line number or neighbor key name | Anchors drift as SillyTavern adds keys. Patch by structure (first }; inside the named object) and always grep -q before editing. |
| Grep for exact log strings like "loaded successfully" | The plugin's log wording changes. Use stable substrings (\[honcho-proxy\] Honcho SDK loaded). |
| Assert "5 routes" in the verification | The route count evolves. Don't assert it. |
| Kill SillyTavern on port 8000 without asking | The user may have a live chat session. Phase 0 asks first. |
| Skip Phase 6.1 because "the plugin loaded" | A loaded plugin can still fail to reach Honcho. The round-trip is the real stop condition. |
Treat ~/.honcho/config.json existence as "configured" |
The plugin needs resolvable keys. Probe them; don't just stat the file. |
Leave enableServerPluginsAutoUpdate: true on a dev install |
Surprises anyone actively editing the symlinked repo. Set false during dev; flip back for release. |
Use sed -i '' for in-place edits |
BSD-only. Breaks on GNU sed (Linux). Use sed -i.bak '...' FILE && rm -f FILE.bak — portable across both. |
pkill -f "node.*server.js" as a cleanup fallback |
Nukes any unrelated Node server matching the pattern. Use exec npm start in the background, track $ST_PID, and { kill "$PID"; wait "$PID"; } || true for a clean reap. |
Call python3 without preflight |
Crashes under set -e on minimal Alpine / distroless. Gate with command -v python3 and fall through (to node, or to a honest "skipping probe" message). |
Handoff
After Phase 6 passes, the install is complete. For per-feature follow-ups (peer modes, context modes, chat-time event flow), refer to the extension's settings reference at <repo>/README.md or hand off to a future sillytavern-honcho-tune skill (not yet built).