- 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 -q` before 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:
1. **Symlink into the project's `.claude/skills/` directory**, then restart the Claude Code session:
```bash
mkdir -p <cwd>/.claude/skills/sillytavern-honcho-setup
ln -sf <repo>/skills/setup/SKILL.md \
<cwd>/.claude/skills/sillytavern-honcho-setup/SKILL.md
```
2. **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.
```bash
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-honcho` checkout. 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.json` with 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
```bash
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.
```bash
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).
```bash
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:
```bash
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`
```bash
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.
```bash
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
```bash
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
```bash
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
```bash
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.
```bash
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
```bash
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.
```bash
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:
```bash
# 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.
View on GitHub