110 lines
7.3 KiB
Markdown
110 lines
7.3 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Repo layout (important)
|
|
|
|
The active codebase lives entirely in **`node/`** (TypeScript, Node 24+). The top-level `src/ClaudeMailbox/` and `tests/ClaudeMailbox.Tests/` directories are stale .NET build artifacts (`bin/`, `obj/` only) left over from an abandoned C# prototype — ignore them, don't build them, don't grep them.
|
|
|
|
Other top-level dirs:
|
|
|
|
- `plugin/` — the Claude Code plugin (`hooks/hooks.json`, slash `commands/`, the `mailbox-collaborate` skill). Loaded via `.claude-plugin/marketplace.json`.
|
|
- `homebrew/`, `install.ps1`, `install.sh` — distribution shims for the published npm package `@kuns/claude-mailbox`.
|
|
- `docs/superpowers/` — design specs and plans (history, not runtime).
|
|
|
|
## Development commands
|
|
|
|
All commands run from `node/`:
|
|
|
|
```sh
|
|
cd node
|
|
npm install
|
|
npm run build # tsc → dist/
|
|
npm test # pretest builds, then vitest run
|
|
npm run test:watch # vitest in watch mode
|
|
npx vitest run tests/cli-watch.test.ts # single test file
|
|
npx vitest run -t "rename" # by test name pattern
|
|
npm start # node dist/cli.js serve (run daemon in foreground)
|
|
```
|
|
|
|
`npm test` runs `pretest` (build) first — vitest executes against `dist/`, not via a TS transformer, so always rebuild after editing `src/` if running vitest directly.
|
|
|
|
Vitest config: `tests/**/*.test.ts`, `pool: "forks"`, 15 s timeout. Tests spin up real Fastify servers and real SQLite files in temp dirs — they are integration tests, not unit tests with mocks.
|
|
|
|
## Runtime CLI surface (after `npm run build`)
|
|
|
|
`dist/cli.js` is the single entry point (bin name `claude-mailbox`):
|
|
|
|
```
|
|
serve | send | peek | check [--hook] | watch --block | list | status
|
|
session-announce # SessionStart hook helper, reads stdin JSON
|
|
session-end # SessionEnd hook helper, deletes mailbox if empty
|
|
install-hook / uninstall-hook # patch settings.json
|
|
install-autostart / uninstall-autostart [--service] # OS autostart registration
|
|
mcp-stdio # stdio MCP wrapper that proxies to the HTTP daemon
|
|
```
|
|
|
|
All subcommands accept `--url <url>`; `CLAUDE_MAILBOX_URL` env overrides the default `http://127.0.0.1:37849`.
|
|
|
|
## Architecture
|
|
|
|
One long-running daemon, single SQLite file, multiple thin clients.
|
|
|
|
```
|
|
clients (Claude sessions, CLI, scripts)
|
|
│ HTTP loopback
|
|
▼
|
|
claude-mailbox serve (Fastify, port 37849)
|
|
├─ /mcp MCP Streamable HTTP transport (registerMcp)
|
|
├─ /v1/send /peek /check-inbox /watch /list REST
|
|
└─ /health
|
|
│
|
|
▼
|
|
~/.claude-mailbox/mailbox.db (SQLite WAL, two tables: mailboxes, messages)
|
|
```
|
|
|
|
Module map under `node/src/`:
|
|
|
|
| File | Responsibility |
|
|
|---|---|
|
|
| `cli.ts` | Commander CLI; dispatches to every other module. Single entry. |
|
|
| `server.ts` | Fastify app, request hook that enforces `X-Mailbox` header on non-anonymous paths, REST routes. |
|
|
| `mcp.ts` | Registers MCP tools (`send`, `check_inbox`, `peek_inbox`, `list_mailboxes`, `rename`) on the same Fastify app at `/mcp`. |
|
|
| `mcp-stdio.ts` | Stdio MCP wrapper used by the plugin's `.mcp.json` — proxies tool calls to the HTTP daemon (workaround for Claude Code not yet supporting env-var substitution in HTTP MCP URLs). |
|
|
| `db.ts` | `MailboxStore` — all SQL via `node:sqlite` (no ORM). Owns DDL idempotency, atomic `check_inbox`, rename-with-message-transfer, long-poll `wait` for push delivery. |
|
|
| `hook.ts` | Helpers shared by `session-announce` / `check --hook` / `install-hook`: stdin parsing, identity derivation (`<project>-<8hex>` from `session_id`), settings.json patching, peer formatting. |
|
|
| `config.ts` | Config precedence: CLI flag > `mailbox.json` > defaults. Looks in `~/.claude-mailbox/mailbox.json` and on Windows also `%ProgramData%\ClaudeMailbox\mailbox.json`. |
|
|
| `autostart/{windows,darwin,linux}.ts` | Per-OS autostart: Scheduled Task / HKCU Run / `node-windows` service / launchd LaunchAgent / systemd `--user`. Selected via `autostart/index.ts`. |
|
|
|
|
### Identity derivation
|
|
|
|
`hook.ts::deriveSessionName` builds the mailbox name from the SessionStart hook's stdin JSON (`session_id`, `cwd`): `<sanitized-project>-<first-8-hex-of-session-id>`. Project name is the git repo basename if inside a repo, else the cwd basename, sanitized (lowercased, non-alphanumerics → `-`, capped at 40 chars). Without a cwd it degrades to `claude-<8hex>`. This is how two parallel sessions in the same project stay distinct.
|
|
|
|
### Push delivery (`watch --block`)
|
|
|
|
Long-poll on `GET /v1/watch?name=…&timeout=…`. The daemon parks the request on an in-memory waiter list keyed by mailbox; `send` wakes the first waiter atomically (FIFO winner across concurrent watchers). Exit codes: `0` delivered (or `Mailbox renamed to '<new>'` on stdout), `2` daemon unreachable, `3` timeout. Push is **opt-in** — the plugin's `SessionStart` hook does *not* launch the watcher automatically; users invoke the `mailbox-collaborate` skill (or `/collaborate`) to enter collaboration mode. The pull path via `UserPromptSubmit` / `SubagentStop` hooks remains the always-on fallback.
|
|
|
|
### Plugin hooks (what runs without you doing anything)
|
|
|
|
`plugin/hooks/hooks.json` wires five hooks to the installed `claude-mailbox` binary:
|
|
|
|
- `SessionStart` → `claude-mailbox session-announce` (prints identity + active peers into context, registers session with daemon)
|
|
- `UserPromptSubmit` → `claude-mailbox check --hook` (drains inbox, injects messages)
|
|
- `SubagentStop` → `claude-mailbox check --hook` (same, when a subagent finishes)
|
|
- `TaskCompleted` → `claude-mailbox check --hook` (same, when Claude marks a TaskCreate task completed — gives mid-run sync points between todo items)
|
|
- `SessionEnd` → `claude-mailbox session-end` (deletes the session's mailbox if no pending messages — same semantics as the sweeper, just immediate; renamed mailboxes are preserved because the auto-derived name no longer exists)
|
|
|
|
`install-hook` / `uninstall-hook` patch the same five events into `settings.json` for users not using the plugin. The manual installer is multi-event aware — adding/removing a hook in `plugin/hooks/hooks.json` should be mirrored by `MANAGED_HOOK_EVENTS` and `buildPluginHookCommands` in `hook.ts`.
|
|
|
|
## Conventions worth knowing
|
|
|
|
- ES modules with `NodeNext` resolution — relative imports must use the `.js` extension even in `.ts` source (e.g. `import { … } from "./db.js"`).
|
|
- `tsconfig.json` is strict including `noUnusedLocals` / `noUnusedParameters` — dead identifiers fail the build.
|
|
- `node:sqlite` is used directly (no `better-sqlite3`) — Node 24+ requirement comes from this.
|
|
- The daemon **never authenticates**: loopback bind + filesystem perms are the trust boundary. `X-Mailbox` header is identity, not auth — anything on loopback can claim any name. Don't add code that assumes otherwise.
|
|
- `*.db`, `*.db-shm`, `*.db-wal` are gitignored — never commit a real mailbox DB.
|
|
|
|
## Release
|
|
|
|
`node/package.json` is the published artifact (`@kuns/claude-mailbox`, registry `https://git.kuns.dev/api/packages/releases/npm/`). Version bumps land as `chore(release): X.Y.Z` commits (see git log). The plugin and the npm package version-lockstep — `mailbox-doctor` and `mailbox-update` slash commands run `npm install -g @kuns/claude-mailbox` against that registry.
|