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,8 +1,8 @@
# claude-mailbox plugin
Lets Claude Code pull unread messages from a local `claude-mailbox` daemon before every prompt and inject them into the conversation context.
Lets Claude Code pull unread messages from a local `claude-mailbox` daemon before every prompt and inject them into the conversation context. Each Claude session gets a **unique mailbox identity** auto-derived from its session id, so two sessions in the same project never collide.
## Setup (two steps, all inside Claude Code)
## Setup (three prompts, all inside Claude Code)
```
/plugin marketplace add https://git.kuns.dev/releases/ClaudeMailbox
@@ -10,50 +10,59 @@ Lets Claude Code pull unread messages from a local `claude-mailbox` daemon befor
/claude-mailbox:mailbox-doctor
```
The doctor command walks the rest:
The doctor walks the rest:
1. checks whether the `claude-mailbox` binary is on `PATH` — installs it (`npm install -g @kuns/claude-mailbox`) if missing, asks before doing anything that might need elevation
2. checks the daemon status — runs `install-autostart` and/or `start` until it reports `Running`
3. ensures `CLAUDE_MAILBOX_NAME` is set in `.claude/settings.json` env — prompts for a name if not, writes it idempotently
4. runs a self → self smoke test to verify the round-trip works
1. installs the `claude-mailbox` binary via `npm install -g @kuns/claude-mailbox` if missing (asks first)
2. registers the daemon for autostart and starts it if needed
3. health-probes `http://127.0.0.1:47822/health`
4. optionally lets you set a **base prefix** (e.g., `backend`) — without one, mailbox names are anonymous (`claude-XXXXXXXX`)
5. runs a self → self smoke test
Restart Claude Code after the doctor finishes (only needed if the mailbox name was newly written). Unread messages will then appear in context before every prompt.
Restart Claude Code only if step 4 wrote a new prefix. After that, every prompt auto-pulls unread messages.
## Why a mailbox name?
## Mailbox identity (the important bit)
Each Claude session has an identity used to address peer sessions — like an email address. If you run a `backend` session and a `frontend` session in parallel, they need different names so they can send messages to each other.
Each Claude Code session gets its own mailbox name, derived from the session's UUID:
For a single Claude Code instance just wanting notifications, any stable kebab-case name works. The name lives in **per-project** `.claude/settings.json` env, so different worktrees / projects automatically get different mailboxes.
| Configuration | Resulting mailbox name |
|---|---|
| No `CLAUDE_MAILBOX_NAME` set | `claude-a8b3c1d2` (first 8 hex chars of session_id) |
| `CLAUDE_MAILBOX_NAME=backend` in `.claude/settings.json` env | `backend-a8b3c1d2` |
## What the hook actually does
So if you open two Claude Code sessions in the same project, they'll be e.g. `backend-a8b3c1d2` and `backend-d4e5f6a7` — distinct, addressable, no manual setup.
Before every prompt the plugin runs `claude-mailbox check --hook`, which:
The `SessionStart` hook announces the current session's mailbox name in the conversation context on startup. Peers discover each other via `claude-mailbox list` or the `mcp__mailbox__list_mailboxes` MCP tool.
- prints unread mailbox messages in a Claude-friendly format and marks them delivered,
- stays **silent** when the inbox is empty or `CLAUDE_MAILBOX_NAME` is not set,
- emits a one-line setup hint when the daemon is unreachable, so a missing daemon is loud, not invisible.
## What the hooks do
Cost: one local HTTP round-trip plus Node coldstart per prompt (~100ms on Windows).
| Hook | Command | Effect |
|---|---|---|
| `SessionStart` | `claude-mailbox session-announce` | Prints `"Claude-Mailbox: this session is mailbox \`X\`"` so Claude knows its own identity. |
| `UserPromptSubmit` | `claude-mailbox check --hook` | Pulls unread messages for the session's mailbox and injects them as context. Silent on empty inbox; emits a one-line setup hint when the daemon is unreachable. |
## Commands
Cost: one local HTTP round-trip per prompt + Node coldstart (~100ms on Windows).
## Slash commands
| Command | What it does |
|---|---|
| `/claude-mailbox:mailbox-doctor` | Diagnose + auto-fix the full setup. |
| `/claude-mailbox:mailbox-status` | Read-only health check. No changes. |
| `/claude-mailbox:mailbox-update` | Update the daemon to the latest npm version and restart it. |
## Smoke test (manually, after doctor finishes)
## Sending a message to a peer session
From inside Claude Code, use the MCP tool (the daemon already exposes `mcp__mailbox__*`). From any shell:
```sh
claude-mailbox send --from probe --to <your-mailbox-name> --body "hello"
claude-mailbox list # find the recipient's mailbox name
claude-mailbox send --from probe --to backend-a8b3c1d2 --body "hi"
```
Then start a new Claude Code prompt — the message should appear in context before Claude's first reply.
## Uninstall
```
/plugin uninstall claude-mailbox@claude-mailbox
npm uninstall -g @kuns/claude-mailbox
claude-mailbox uninstall-autostart # if you used it
claude-mailbox uninstall-autostart # if you registered it
```