Files
ClaudeMailbox/CLAUDE.md
2026-05-27 13:16:19 +02:00

7.3 KiB

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/:

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:

  • SessionStartclaude-mailbox session-announce (prints identity + active peers into context, registers session with daemon)
  • UserPromptSubmitclaude-mailbox check --hook (drains inbox, injects messages)
  • SubagentStopclaude-mailbox check --hook (same, when a subagent finishes)
  • TaskCompletedclaude-mailbox check --hook (same, when Claude marks a TaskCreate task completed — gives mid-run sync points between todo items)
  • SessionEndclaude-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.