feat(plugin): per-session mailbox identity + mailbox-update command

The hook now derives a unique mailbox name from the session_id supplied
on hook stdin, so two parallel Claude Code sessions in the same project
get distinct mailboxes (e.g. `claude-a8b3c1d2`, `claude-d4e5f6a7`)
instead of colliding on a shared env value. An optional
CLAUDE_MAILBOX_NAME base prefix flavors the names as `<base>-<sid>`.

Adds:
- `claude-mailbox session-announce` subcommand for the new SessionStart
  hook, which prints the current session's mailbox name to context
- `/claude-mailbox:mailbox-update` slash command for `npm update` +
  daemon restart
- stdin parsing helpers (parseHookStdin, deriveSessionName) with unit
  tests; the doctor no longer needs a mandatory name prompt
This commit is contained in:
Mika Kuns
2026-05-19 11:39:14 +02:00
parent c231f8c18c
commit 462d6561e1
9 changed files with 385 additions and 94 deletions

View File

@@ -1,85 +1,92 @@
---
description: Diagnose and auto-fix the Claude-Mailbox setup (binary install, daemon autostart, mailbox name, smoke test).
description: Diagnose and auto-fix the Claude-Mailbox setup (binary install, daemon autostart, smoke test, optional base-prefix).
allowed-tools: Bash, Read, Edit, Write
---
You are running the **Claude-Mailbox doctor**. Walk through these checks in order. After each check, print a one-line status (✓ / ✗) and the action you took. At the very end, print a final summary block.
You are running the **Claude-Mailbox doctor**. Walk through these checks in order. After each step, print a one-line `✓` / `✗` with the action you took. End with a summary block.
Throughout, prefer the dedicated tools (`Read`, `Edit`, `Write`) for files. Use `Bash` only for `claude-mailbox` subcommands, `npm`, `where`/`which`, and `cat /etc/os-release` style lookups. Never run `sudo` automatically — if elevation is needed, stop and ask the user how to proceed.
---
Use `Bash` only for `claude-mailbox` subcommands, `npm`, `where`/`which`, and HTTP probes. Use `Read`/`Edit`/`Write` for `.claude/settings.json`. Never run `sudo` automatically — if elevation is needed, stop and ask.
## Step 1 — daemon binary on PATH
Run: `claude-mailbox --version`
- **Exit 0** → binary present. Record the version string. ✓ continue.
- **Command not found / non-zero exit** → binary missing. Tell the user the install command for their platform and ask before running it:
- **Exit 0** → ✓ record the version. Continue.
- **Command not found** → binary missing. Install path:
| Platform | Install command |
| Platform | Command |
|---|---|
| Windows | `npm install -g @kuns/claude-mailbox` (no admin) |
| macOS / Linux | `npm install -g @kuns/claude-mailbox` (may need `sudo` depending on Node setup; ask the user if `npm install` fails with EACCES) |
| macOS / Linux | `npm install -g @kuns/claude-mailbox` (may fail with EACCES — never run sudo automatically; ask the user) |
Prerequisite: `npm config get @kuns:registry` should return `https://git.kuns.dev/api/packages/releases/npm/`. If it doesn't, set it first:
Prerequisite: `npm config get @kuns:registry` must point at `https://git.kuns.dev/api/packages/releases/npm/`. If not:
```
npm config set @kuns:registry=https://git.kuns.dev/api/packages/releases/npm/
```
After install, re-run `claude-mailbox --version`. If it still fails, stop and report the error.
After install, re-run `claude-mailbox --version`. If it still fails, stop and report.
## Step 2 — daemon autostart and running state
Run: `claude-mailbox status`
- **`Running`** → ✓ continue.
- **`Stopped`** → run `claude-mailbox start`. Re-check status.
- **`NotInstalled`** → run `claude-mailbox install-autostart`, then `claude-mailbox start`. Re-check status.
- `Running` → ✓ continue.
- `Stopped` `claude-mailbox start`, re-check.
- `NotInstalled` `claude-mailbox install-autostart`, then `claude-mailbox start`, re-check.
If status doesn't become `Running` after the fix, stop and report what `status` and `start` printed.
If status doesn't reach `Running`, stop and report.
Sanity check: `curl -sf http://127.0.0.1:47822/health` (or use Bash to fetch it). Expect a JSON body with `"status":"ok"`.
## Step 3 — health probe
## Step 3 — mailbox name in project settings
Hit `http://127.0.0.1:47822/health`. Expect a JSON body with `"status":"ok"`. If unreachable, stop and report — the daemon claims it's running but isn't accepting connections.
The hook reads the mailbox name from `$CLAUDE_MAILBOX_NAME`. Claude Code injects env vars from `.claude/settings.json` into hook commands, so the cleanest place to set it is per-project.
## Step 4 — mailbox identity
1. Read `.claude/settings.json` in the current working directory (it may not exist yet — that's fine).
2. Check if `env.CLAUDE_MAILBOX_NAME` is set.
3. If **set**: ✓ continue, record the name.
4. If **not set**:
- Ask the user for a mailbox name. Suggest a default based on the cwd basename (e.g., for `C:\Private\Claude-Mailbox` suggest `claude-mailbox`). Names should be short, kebab-case-ish, unique among parallel Claude sessions.
- Read existing `.claude/settings.json` if present, otherwise start with `{}`.
- Set/merge `env.CLAUDE_MAILBOX_NAME` to the chosen name. Preserve any other existing settings.
- Write back with 2-space indentation.
- Tell the user they need to **restart this Claude Code session** for the env to take effect in the hook — but the smoke test below can still run because we'll pass `--name` explicitly.
**No prompt by default.** Each Claude Code session now gets a unique mailbox name auto-derived from its `session_id` (e.g., `claude-a8b3c1d2`), so two parallel sessions can never collide.
## Step 4 — smoke test
Read `.claude/settings.json` in the current working directory and look for `env.CLAUDE_MAILBOX_NAME`.
Use the resolved name from step 3 (either pre-existing or just chosen). Run:
- If set → ✓ this is a **base prefix**. The real name will be `<base>-<short_session_id>`. Tell the user "Mailbox prefix is set to `X`."
- If unset → ✓ tell the user "Mailbox name will be auto-derived (`claude-<short_session_id>`)."
Then **ask** the user (one question, not a deep prompt):
> "Want to flavor your mailbox names with a memorable prefix like `backend` or `frontend`? (yes / no / change to `<x>`)"
If they say yes or give a value:
1. Read `.claude/settings.json` (empty `{}` if missing).
2. Merge `env.CLAUDE_MAILBOX_NAME` = chosen value, preserving anything else.
3. Write back with 2-space indentation.
4. Mark this as `restart_needed = true`.
If they say no or skip → leave as-is.
## Step 5 — smoke test
Use two ephemeral names (`doctor-probe-a` / `doctor-probe-b`) — we don't need the real session name here, we just need to prove the daemon round-trips:
```
claude-mailbox send --from doctor-probe --to <name> --body "ping from /claude-mailbox:mailbox-doctor"
claude-mailbox check --name <name>
claude-mailbox send --from doctor-probe-a --to doctor-probe-b --body "ping from doctor"
claude-mailbox check --name doctor-probe-b
```
- The `check` output should be a JSON array containing exactly one message with `"from": "doctor-probe"` and that body.
- If yes: ✓ smoke test passed.
- If no (empty array, error, or wrong message): ✗ report what was returned.
The `check` output must be a JSON array with one message: `from: doctor-probe-a`, body matches. If yes ✓. If no ✗ and report what came back.
## Step 5 final summary
Print a compact block with these fields, one per line:
## Step 6 — summary
```
Claude-Mailbox doctor
binary: <version>
daemon: Running | Stopped | NotInstalled (and what you did)
health: ok | unreachable
mailbox name: <name> (source: existing | newly written to .claude/settings.json)
smoke test: passed | failed
restart hint: <yes if name was newly written, otherwise no>
binary: <version>
daemon: Running (and what you did, if anything)
health: ok
base prefix: <name from settings, or "auto-derived (anonymous)">
smoke test: passed | failed
restart hint: yes if restart_needed, otherwise no
```
If everything is ✓ and `restart hint: yes`, end with: "Restart Claude Code (or open a new session) so the UserPromptSubmit hook picks up `CLAUDE_MAILBOX_NAME`." If `restart hint: no`, end with: "You're good to go — unread messages will appear before your next prompt."
End with one of:
- All ✓ and no restart needed → "Setup is healthy. Your mailbox name this session will be revealed by the SessionStart hook on next session start (or run `claude-mailbox list` to see active mailboxes)."
- All ✓ and restart needed → "Restart Claude Code (or open a new session) so the SessionStart hook picks up the new base prefix."
- Anything ✗ → "Setup incomplete: <first failure>."

View File

@@ -0,0 +1,56 @@
---
description: Update the Claude-Mailbox daemon to the latest published npm version and restart it.
allowed-tools: Bash
---
You are running the **Claude-Mailbox update** command. Update the `@kuns/claude-mailbox` npm package and restart the daemon. Never run `sudo` automatically — if elevation is needed, stop and ask.
## Step 1 — current version
Run: `claude-mailbox --version`
- Exit 0 → record the version string as `CURRENT`.
- Non-zero → tell the user the daemon binary is not installed yet. Suggest `/claude-mailbox:mailbox-doctor` to do a full setup and stop.
## Step 2 — latest published version
Run: `npm view @kuns/claude-mailbox version`
If the npm registry config is missing, the call may fail with a 404. Fall back to:
```
npm view --registry=https://git.kuns.dev/api/packages/releases/npm/ @kuns/claude-mailbox version
```
Record the result as `LATEST`.
## Step 3 — compare
- If `CURRENT === LATEST`: print "Already up to date (vX.Y.Z)." and stop. Do not run any further steps.
- Otherwise: tell the user `CURRENT``LATEST` and ask for confirmation before proceeding.
## Step 4 — perform the update
On user confirmation, run these in order. Stop on the first failure and report it:
1. `claude-mailbox stop`
2. `npm install -g @kuns/claude-mailbox@latest`
- On Linux/macOS this may fail with EACCES. **Do not run sudo automatically.** Ask the user how they want to proceed (e.g., `sudo npm install -g …`, or switch to a user-scoped Node setup with nvm/fnm).
3. `claude-mailbox start`
4. `claude-mailbox --version` to verify the upgrade landed.
5. `claude-mailbox status` to verify the daemon is `Running`.
## Step 5 — summary
Print exactly this block:
```
Claude-Mailbox update
previous version: <CURRENT>
new version: <whatever --version now reports>
daemon: Running | Stopped | NotInstalled
pending messages survived: <count of messages still in inbox via `claude-mailbox list`, if applicable>
```
If the new version matches `LATEST` and daemon is `Running`, end with: "Update complete."
Otherwise, end with the first thing that went wrong.