refactor: rename cli-v2 → cli, archive legacy cli, plus broker-side grants + auto-migrate
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

- apps/cli/ is now the canonical CLI (was apps/cli-v2/).
- apps/cli/ legacy v0 archived as branch 'legacy-cli-archive' and tag
  'cli-v0-legacy-final' before deletion; git history preserves it too.
- .github/workflows/release-cli.yml paths updated.
- pnpm-lock.yaml regenerated.

Broker-side peer-grant enforcement (spec: 2026-04-15-per-peer-capabilities):
- 0020_peer-grants.sql adds peer_grants jsonb + GIN index on mesh.member.
- handleSend in broker fetches recipient grant maps once per send, drops
  messages silently when sender lacks the required capability.
- POST /cli/mesh/:slug/grants to update from CLI; broker_messages_dropped_by_grant_total metric.
- CLI grant/revoke/block now mirror to broker via syncToBroker.

Auto-migrate on broker startup:
- apps/broker/src/migrate.ts runs drizzle migrate with pg_advisory_lock
  before the HTTP server binds. Exits non-zero on failure so Coolify
  healthcheck fails closed.
- Dockerfile copies packages/db/migrations into /app/migrations.
- postgres 3.4.5 added as direct broker dep.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-15 08:44:52 +01:00
parent c9ede3d469
commit ee12510ef1
374 changed files with 14706 additions and 11307 deletions

View File

@@ -0,0 +1,243 @@
# CLI Wizard Architecture Refactor
**Status:** backlog
**Created:** 2026-04-10
**Source:** Reverse-engineered from `@posthog/wizard` (npm cache), applied to `apps/cli/src/commands/launch.ts`
## Why
Launch wizard has three compounding problems:
1. **Imperative branching**`launch.ts` checks account → mesh → name → role → exec in hardcoded order. Adding a screen requires touching existing code. Hard to reason about `--resume`, `--non-interactive`, and skip conditions.
2. **Terminal bleed-through on handoff** — wizard→`claude` exec corrupts Ink's TUI state (garbled word wraps, tool labels overwritten, spinner fragments fused to paths). Root cause is spread across multiple exit paths instead of one choke point.
3. **Inconsistent visual design** — ad-hoc colors per file, no central palette, no shared icon set, no shared layout primitives. Every screen reinvents status rows, centering, and spacing.
PostHog's wizard solves all three with one architectural pattern: **declarative flow pipelines + session-as-store + shared visual primitives**. This artifact captures the plan to port that pattern.
## What PostHog does (the reference)
### Flow pipeline (`flows.ts` + `router.ts`)
Each wizard flow is an array of screen entries:
```ts
export const FLOWS = {
[Flow.Wizard]: [
{ screen: Screen.Intro, isComplete: s => s.setupConfirmed },
{ screen: Screen.HealthCheck, isComplete: s => s.readinessResult !== null },
{ screen: Screen.Setup, show: needsSetup, isComplete: s => !needsSetup(s) },
{ screen: Screen.Auth, isComplete: s => s.credentials !== null },
{ screen: Screen.Run, isComplete: s => s.runPhase === RunPhase.Completed },
{ screen: Screen.Outro, isComplete: s => s.outroDismissed },
],
};
```
The router walks the array, skips entries where `show(s) === false` or `isComplete(s) === true`, and returns the first remaining entry. Zero switch statements. Zero hardcoded transitions. Adding a screen = appending an object.
### Overlay stack
Separate from the linear flow cursor. Interrupts (port conflict, auth expired, managed settings) are pushed onto `overlays[]` from anywhere and popped when dismissed. Active screen = top of overlay stack OR flow cursor. Flows never need to know about interrupts.
### Session as single source of truth
One `WizardStore` holds all session state. Screens subscribe via React 18 `useSyncExternalStore`. Completion predicates read session; imperative code writes session; the router re-resolves on every change.
### Visual primitives
- `styles.ts` — 6-color palette (`Colors`), 9-icon set (`Icons`), alignment enums (`HAlign`, `VAlign`)
- `CardLayout` — semantic centering wrapper used by every screen
- `PickerMenu` — the only selection primitive, used for every choice
- `screen-registry.ts` — maps `Screen` enum → React component
- Brand mark: three colored `█` blocks next to the wizard name on every screen header
## What claudemesh should do
### Target file layout
```
apps/cli/src/
├── commands/
│ └── launch.ts # thin entrypoint: parse flags → start TUI
└── ui/
├── styles.ts # palette, icons, alignment enums
├── store.ts # LaunchStore (session + subscribe)
├── router.ts # flow cursor + overlay stack
├── flows.ts # FLOWS = { Launch: [...], Join: [...] }
├── screen-registry.ts # Screen enum → component
├── primitives/
│ ├── CardLayout.tsx
│ ├── PickerMenu.tsx
│ ├── StatusRows.tsx # new: "Directory ✓ /claudemesh" pattern
│ ├── BrandMark.tsx # new: 3 colored squares + label
│ └── LoadingBox.tsx
└── screens/
├── WelcomeScreen.tsx
├── AccountScreen.tsx
├── MeshPickerScreen.tsx
├── NameRoleScreen.tsx
├── ConfirmScreen.tsx
└── HandoffScreen.tsx # last screen; its unmount triggers exec claude
```
### Flow definition
```ts
export const FLOWS = {
[Flow.Launch]: [
{ screen: Screen.Welcome, isComplete: s => s.welcomed },
{ screen: Screen.Account, show: s => !s.hasAccount, isComplete: s => s.hasAccount },
{ screen: Screen.MeshPicker, show: s => s.meshes.length > 1, isComplete: s => s.meshSlug !== null },
{ screen: Screen.NameRole, isComplete: s => s.displayName !== null && s.role !== null },
{ screen: Screen.Confirm, isComplete: s => s.confirmed },
{ screen: Screen.Handoff, isComplete: () => false }, // terminal screen
],
};
```
### `--resume` works for free
`--resume <id>` populates the session from saved state; every satisfied predicate auto-skips. The wizard renders only the screens that still need input. No special `--resume` branches in screen code.
### `--non-interactive` works for free
Non-interactive mode: walk the flow, for each incomplete entry check if its required session fields can be sourced from CLI flags. If yes, populate and continue. If no, **fail fast with a clear message** naming the missing flag. Never silently guess defaults.
```
$ claudemesh launch --non-interactive --name Alexis
✗ Missing --mesh (required in non-interactive mode when >1 mesh joined)
Available meshes: alexis-mou, dev, staging
```
### Overlay interrupts claudemesh needs
- `BrokerDisconnect` — WS dropped mid-wizard, retry countdown
- `InviteInvalid` — paste invite screen rejected token
- `MeshNotFound``--mesh foo` passed but not joined
- `RateLimit` — broker rate limited the CLI, backoff timer
- `UpdateAvailable` — newer CLI version on npm, non-blocking banner
### Terminal handoff choke point
The last flow entry (`Screen.Handoff`) renders a brief "Launching Claude Code…" card, then:
```ts
// apps/cli/src/ui/screens/HandoffScreen.tsx (on mount)
useEffect(() => {
(async () => {
await inkApp.unmount();
await inkApp.waitUntilExit();
resetTerminal(); // single choke point for ANSI teardown
await flushStdout();
execa('claude', claudeArgs, { stdio: 'inherit' });
})();
}, []);
```
`resetTerminal()` lives in `apps/cli/src/ui/terminal.ts`:
```ts
export function resetTerminal() {
process.stdout.write(
'\x1b[0m' + // reset SGR
'\x1b[?25h' + // show cursor
'\x1b[?1049l' + // exit alt-screen
'\x1b[?1000l' + // disable mouse tracking
'\x1b[?1002l' +
'\x1b[?1003l' +
'\x1b[?1006l' +
'\x1b[?2004l' + // disable bracketed paste
'\x1b[2J' + // clear screen
'\x1b[H' // cursor home
);
if (process.stdin.isTTY) process.stdin.setRawMode(false);
}
```
PostHog only does SGR reset + clear + home on unmount — they don't hand off to another full-screen app, so that's enough for them. Claudemesh needs the full mode-reset because Claude Code takes over the TTY.
### Visual design system
`apps/cli/src/ui/styles.ts`:
```ts
export const Colors = {
primary: 'cyan',
accent: '#7C3AED', // claudemesh purple
title: '#4C1D95',
success: 'green',
error: 'red',
warning: 'yellow',
muted: 'gray',
} as const;
export const Icons = {
check: '✔',
cross: '✘',
warning: '⚠',
arrow: '▶',
smallArrow: '▸',
bullet: '•',
diamond: '◆',
square: '█',
} as const;
export enum HAlign { Left = 'flex-start', Center = 'center', Right = 'flex-end' }
export enum VAlign { Top = 'flex-start', Center = 'center', Bottom = 'flex-end' }
```
Every screen imports from here. No inline color strings allowed.
### Status rows pattern
Replaces the current plain-text banner:
```
██ claudemesh launch
Directory ✔ /claudemesh
Account ✔ agutierrez@mineryreport.com
Mesh ✔ alexis-mou (9 peers online)
Name ✔ Alexis
Role ▸ (pick one)
▸ Continue
Change mesh
Cancel
```
## Implementation order
| # | Impact | Effort | Scope |
|---|---|---|---|
| 1 | High | S | `ui/styles.ts` — palette + icons + alignment enums; migrate existing screens |
| 2 | High | S | `ui/primitives/StatusRows.tsx` + `BrandMark.tsx` |
| 3 | High | M | `ui/store.ts` + `ui/router.ts` + `ui/flows.ts` (flow pipeline core) |
| 4 | High | M | Refactor `launch.ts` to render through router; port existing screens |
| 5 | High | S | `HandoffScreen` + `resetTerminal()` choke point — fixes TUI bleed bug |
| 6 | High | S | Preselect "Continue" on every confirmation screen (one-keypress happy path) |
| 7 | Med | M | Overlay stack + first two overlays (`BrokerDisconnect`, `InviteInvalid`) |
| 8 | Med | M | `--non-interactive` mode using flow walker + fail-fast flag check |
| 9 | Med | S | Per-mesh/per-role `preRunNotice` extension point |
| 10| Low | L | `DissolveTransition` / `ContentSequencer` polish primitives |
Steps 15 are the atomic unit of value: they fix the bleed-through bug, establish the visual system, and unblock everything else. Should ship as one PR.
Steps 69 can each ship independently.
Step 10 is polish — defer until after v0.2.
## Open questions
- **Ink version**: current CLI uses Ink 4.x? PostHog is on Ink 5 with `useSyncExternalStore`. Check `apps/cli/package.json` before porting the store pattern — Ink 4 needs a different subscription approach.
- **React version**: `useSyncExternalStore` is React 18+. Confirm.
- **Flow granularity**: should `Join` (paste invite) be a separate flow from `Launch`, or an overlay inside `Launch`? PostHog-style: separate flow triggered from the welcome screen. Simpler.
- **Resume semantics**: does `--resume <id>` resume the *Claude* session only, or also restore the wizard's last mesh/name/role choice? If the latter, need a `~/.claudemesh/sessions/<id>.json` alongside Claude's own session file.
## References
- PostHog wizard source: `~/.npm/_npx/b48b11b34a0cada0/node_modules/@posthog/wizard/dist/src/ui/tui/`
- `start-tui.js` — Ink bootstrap + cleanup
- `router.js` — flow cursor + overlay stack
- `flows.js` — declarative pipeline definition
- `styles.js` — palette + icons
- `screens/IntroScreen.js` — reference for status rows + picker
- `primitives/CardLayout.js` — semantic centering

View File

@@ -0,0 +1,820 @@
# claudemesh v1 — Feature Inventory
**Status:** backlog reference
**Created:** 2026-04-11
**Purpose:** Exhaustive audit of what v1 ships today. **Every row in this document must still work after v2 lands.** v2 is a refactor + CLI user flows, NOT a functional rewrite; this inventory is the regression checklist.
**Source of truth**:
- `apps/cli/src/` — 22 files, ~12 k LOC (v0.10.5)
- `apps/broker/src/` — 23 files, ~11 k LOC
- `packages/db/src/schema/mesh.ts` — 1,019 lines, 23 tables
---
## 0. Summary counts
| Surface | v1 count |
|---|---|
| CLI commands (subcommands in `index.ts`) | 23 |
| MCP tools (handlers in `mcp/server.ts`) | 79 |
| Broker WS message types (dispatched in `index.ts`) | 85 |
| Broker HTTP endpoints | 18 |
| Postgres tables in `mesh` schema | 23 |
| External backend services the broker manages | 5 (Postgres, Neo4j, Qdrant, MinIO, Docker) |
| Lines of source (CLI + broker, excluding tests) | ~23,450 |
---
## 1. CLI commands
All dispatched from `apps/cli/src/index.ts`. v1 ships 23 public subcommands plus the bare-command welcome wizard.
| Command | File | Purpose | Flags / args |
|---|---|---|---|
| `claudemesh` (bare) | `commands/welcome.ts` | Interactive welcome wizard. Entry point for new users. | (none) |
| `launch` | `commands/launch.ts` (775 lines, biggest) | Spawn a Claude Code session with mesh connectivity + MCP tools | `--name`, `--role`, `--groups`, `--mesh`, `--join`, `--message-mode`, `--system-prompt`, `-y/--yes`, `-r/--resume`, `-c/--continue`, `--quiet`, + passthrough to `claude` after `--` |
| `create` | `commands/create.ts` | Create a new mesh from a template | `--template`, `--list-templates` |
| `install` | `commands/install.ts` (538 lines) | Register MCP server + status hooks with Claude Code (`~/.claude.json`, `~/.claude/settings.json`) | `--no-hooks` |
| `uninstall` | `commands/install.ts` | Remove MCP server + hooks from Claude Code config | (none) |
| `join` | `commands/join.ts` (193 lines) | Join a mesh via invite URL or token | positional `<url>` |
| `list` | `commands/list.ts` | Show joined meshes, slugs, local identities | (none) |
| `leave` | `commands/leave.ts` | Leave a joined mesh + remove its local keypair | positional `<slug>` |
| `peers` | `commands/peers.ts` | List online peers with status, summary, groups | `--mesh`, `--json` |
| `send` | `commands/send.ts` | Send a message to a peer, group, or all peers | positional `<to> <message>`, `--mesh`, `--priority` |
| `inbox` | `commands/inbox.ts` | Drain pending inbound messages | `--mesh`, `--json`, `--wait` |
| `state` | `commands/state.ts` | Get / set / list shared KV state in the mesh | positional `<action> <key> [value]`, `--mesh`, `--json` |
| `info` | `commands/info.ts` | Mesh overview: slug, broker, peer count, state keys | `--mesh`, `--json` |
| `remember` | `commands/memory.ts` | Store a persistent memory visible to all peers | positional `<content>`, `--mesh`, `--tags`, `--json` |
| `recall` | `commands/memory.ts` | Full-text search of mesh memories | positional `<query>`, `--mesh`, `--json` |
| `remind` | `commands/remind.ts` (142 lines) | Schedule a delayed message. Also: `remind list`, `remind cancel <id>` | positional `<message>`, `--in`, `--at`, `--cron`, `--to`, `--mesh`, `--json` |
| `sync` | `commands/sync.ts` | Sync meshes from the user's claudemesh.com dashboard account | `--force` |
| `profile` | `commands/profile.ts` | View or edit member profile (self or another member if admin) | `--mesh`, `--role-tag`, `--groups`, `--message-mode`, `--name`, `--member`, `--json` |
| `status` | `commands/status.ts` | Check broker connectivity for each joined mesh | (none) |
| `doctor` | `commands/doctor.ts` (212 lines) | Diagnose install, config, keypairs, PATH | 7 checks: Node >= 20, claude binary, MCP registered, hooks registered, config parses, file perms, keypairs valid |
| `mcp` | `mcp/server.ts` (2139 lines) | Start MCP server on stdio (internal — invoked by Claude Code) | (none) |
| `seed-test-mesh` | `commands/seed-test-mesh.ts` | Dev-only: inject a mesh into local config without invite flow | `<slug>`, `<broker_url>` |
| `hook` | `commands/hook.ts` | Internal: handle Claude Code hook events (status updates from session lifecycle) | stdin JSON from Claude Code |
| `connect telegram` | `commands/connect-telegram.ts` | Link a Telegram bot to a mesh | inline token prompts, calls broker `/tg/token` |
| `disconnect telegram` | `commands/disconnect-telegram.ts` | Unlink Telegram bot | (none) |
### Flag-first invocation rewrite
`apps/cli/src/index.ts` lines 339355 implement a **friction reducer**: if the user types `claudemesh --resume xxx` or any flag-first invocation, the argv is rewritten to `claudemesh launch --resume xxx` before citty parses it. This lets users skip typing `launch` for common flag-only forms.
**Must preserve in v2.** Users may depend on this. Applies to `--resume`, `--continue`, `-y`, `--mesh`, `--name`, etc.
---
## 2. MCP tools (79 total)
Defined in `apps/cli/src/mcp/tools.ts` with schemas, implemented in `apps/cli/src/mcp/server.ts` with per-tool case handlers. Each MCP tool is a RPC that the CLI's MCP server handles locally or forwards to the broker via WS.
Grouped by domain family. Every tool listed here has a working handler in v1.
### 2.1 Messaging (4)
| Tool | v1 behavior |
|---|---|
| `send_message` | Send encrypted message to peer, group, or broadcast. Supports priorities: `now` (immediate), `next` (default), `low`. Broker queues if recipient offline. |
| `list_peers` | List connected peers in the mesh with `presenceId`, `displayName`, `status`, `summary`, `groups`, `roleTag`. |
| `message_status` | Query delivery state of a sent message by `messageId`. |
| `check_messages` | Drain pending inbox messages (push mode). |
### 2.2 Profile + identity (4)
| Tool | v1 behavior |
|---|---|
| `set_summary` | Set the current peer's work summary (visible to others). |
| `set_status` | Set status: `idle`, `working`, `dnd`. Priority-ranked by source (`hook` > `manual` > `jsonl`). |
| `set_visible` | Toggle visibility. Hidden peers skip `list_peers` and broadcasts but still receive direct messages. |
| `set_profile` | Update display name, role tag, groups, avatar, title, bio, capabilities. |
### 2.3 Groups (2)
| Tool | v1 behavior |
|---|---|
| `join_group` | Join a `@group` with optional role (`lead`, `member`, or free-form). |
| `leave_group` | Leave a `@group`. |
### 2.4 State KV (3)
| Tool | v1 behavior |
|---|---|
| `set_state` | Set a key-value pair in the mesh's shared state. Broadcasts `state_change` push to all peers. |
| `get_state` | Read a value by key. |
| `list_state` | List all state keys with values, authors, timestamps. |
### 2.5 Memory (3)
| Tool | v1 behavior |
|---|---|
| `remember` | Store a text memory with optional tags. Persists across sessions. |
| `recall` | Full-text search memories by query, ranked results. |
| `forget` | Delete a memory by ID. |
### 2.6 Files (8)
| Tool | v1 behavior |
|---|---|
| `share_file` | Upload a file to MinIO. Supports `to: <peer>` for E2E encryption (symmetric key wrapped with peer pubkey), or mesh-wide sharing. Supports `persistent` vs `ephemeral` storage. |
| `get_file` | Download a file by `fileId`. Returns a presigned MinIO URL. |
| `list_files` | List files in the mesh by `scope`, `tags`, author. |
| `file_status` | Query status of a file: who downloaded, when. |
| `delete_file` | Delete a file (owner only). |
| `grant_file_access` | Add another peer as a recipient of an already-encrypted file (re-wraps symmetric key). |
| `read_peer_file` | Read a file from another peer's working directory (requires peer online + sharing). |
| `list_peer_files` | List files in a peer's shared directory (tree of names, not contents). |
### 2.7 Vectors (Qdrant) (4)
| Tool | v1 behavior |
|---|---|
| `vector_store` | Store embedding with metadata in a named collection. |
| `vector_search` | Nearest-neighbor search in a collection with `limit`. |
| `vector_delete` | Delete a vector by ID. |
| `list_collections` | List collections in the mesh's Qdrant namespace. |
### 2.8 Graph (Neo4j) (2)
| Tool | v1 behavior |
|---|---|
| `graph_query` | Read-only Cypher MATCH query on the per-mesh Neo4j database. |
| `graph_execute` | Write Cypher (CREATE/MERGE/DELETE). |
### 2.9 Shared SQL (Postgres) (3)
| Tool | v1 behavior |
|---|---|
| `mesh_query` | SELECT-only query on the per-mesh Postgres schema. |
| `mesh_execute` | DDL + DML (CREATE TABLE, INSERT, UPDATE, DELETE). |
| `mesh_schema` | List tables + columns in the mesh's schema. |
### 2.10 Streams (4)
| Tool | v1 behavior |
|---|---|
| `create_stream` | Create a named stream for live data pub-sub. |
| `publish` | Push data to a stream. Subscribers receive in real-time. |
| `subscribe` | Subscribe to a stream. Events arrive as channel notifications. |
| `list_streams` | List active streams. |
### 2.11 Contexts (3)
| Tool | v1 behavior |
|---|---|
| `share_context` | Share session understanding with the mesh (summary + files_read + key_findings + tags). |
| `get_context` | Search contexts by query (file path, topic, etc.). |
| `list_contexts` | Show what peers currently know about the codebase. |
### 2.12 Tasks (4)
| Tool | v1 behavior |
|---|---|
| `create_task` | Create a work item (title, assignee, priority, tags). |
| `claim_task` | Claim an unclaimed task. |
| `complete_task` | Mark done with optional result summary. |
| `list_tasks` | Filter by status and/or assignee. |
### 2.13 Scheduling (3)
| Tool | v1 behavior |
|---|---|
| `schedule_reminder` | One-shot (`deliver_at`, `in_seconds`) or recurring (`cron`). Delivered to self or `to`. Persists across broker restarts. |
| `list_scheduled` | List pending scheduled messages. |
| `cancel_scheduled` | Cancel by ID. |
### 2.14 Mesh metadata — read (4)
| Tool | v1 behavior |
|---|---|
| `mesh_info` | Overview: peers, groups, state, memory, files, tasks, streams, tables. |
| `mesh_stats` | Resource usage per peer: messages in/out, tool calls, uptime, errors. |
| `mesh_clock` | Simulation clock status: speed, tick count, simulated time. |
| `ping_mesh` | Test messages through the full pipeline, measure round-trip per priority. Diagnoses push delivery issues. |
### 2.15 Mesh clock — write (3)
| Tool | v1 behavior |
|---|---|
| `mesh_set_clock` | Set simulation clock speed (1100x). Peers receive heartbeat ticks at the simulated rate. |
| `mesh_pause_clock` | Pause simulation clock. |
| `mesh_resume_clock` | Resume paused clock. |
### 2.16 Skills (5)
| Tool | v1 behavior |
|---|---|
| `share_skill` | Publish a reusable skill (name + description + instructions + tags + when_to_use + allowed_tools + model + context + agent + user_invocable + argument_hint). Exposed as MCP prompts and `skill://` resources. |
| `get_skill` | Load a skill's full instructions by name. |
| `list_skills` | Browse available skills, optionally filter by keyword. |
| `remove_skill` | Remove a shared skill. |
| `mesh_skill_deploy` | Deploy a multi-file skill bundle from zip or git repo. |
### 2.17 MCP registry tier 1 — peer-hosted (4)
| Tool | v1 behavior |
|---|---|
| `mesh_mcp_register` | Register a peer's local MCP server with the mesh (server_name, description, tools schema, persistent flag). Other peers can invoke via `mesh_tool_call`. |
| `mesh_mcp_list` | List MCP servers in the mesh with their tools + hosting peer. |
| `mesh_tool_call` | Call a tool on a mesh-registered MCP server. Routes: caller → broker → hosting peer → execute → result back. 30s timeout. |
| `mesh_mcp_remove` | Unregister a peer-hosted MCP server. |
### 2.18 MCP registry tier 2 — broker-deployed (7)
| Tool | v1 behavior |
|---|---|
| `mesh_mcp_deploy` | Deploy an MCP server from zip (via `file_id`), git URL, or npx package. Runs on broker VPS in Docker sandbox. Scope: `peer` (default), `mesh`, or `{group/groups/role/peers}`. Runtime: node / python / bun. Memory, network_allow, env with `$vault:` references. |
| `mesh_mcp_undeploy` | Stop and remove a managed MCP server. |
| `mesh_mcp_update` | Pull latest + restart a git-sourced server. |
| `mesh_mcp_logs` | Tail recent logs from a managed server. |
| `mesh_mcp_scope` | Get or set visibility scope. |
| `mesh_mcp_schema` | Inspect tool schemas for a deployed server. |
| `mesh_mcp_catalog` | List all deployed services with status, scope, tool count. |
### 2.19 Vault (3)
| Tool | v1 behavior |
|---|---|
| `vault_set` | Store encrypted credential. `type: env` (string, injected as env var via `$vault:<key>`) or `type: file` (file written to `mount_path` in container). |
| `vault_list` | List vault entries (keys + metadata only, no values). |
| `vault_delete` | Remove a credential. |
### 2.20 URL watch (3)
| Tool | v1 behavior |
|---|---|
| `mesh_watch` | Watch a URL for changes. Modes: `hash` (SHA-256 body), `json` (jsonpath extract), `status` (HTTP code). Polling `interval` (min 5s). `notify_on: change \| match:<val> \| not_match:<val>`. Custom headers. |
| `mesh_unwatch` | Stop watching by `watch_id`. |
| `mesh_watches` | List active watches. |
### 2.21 Webhooks (3)
| Tool | v1 behavior |
|---|---|
| `create_webhook` | Create an inbound webhook. Returns a URL external services (GitHub, CI/CD, monitoring) can POST to. Payload becomes a mesh message to all peers. |
| `list_webhooks` | List active webhooks. |
| `delete_webhook` | Deactivate by name. |
---
## 3. Broker WS protocol
`apps/broker/src/index.ts` dispatches 85 message types over a single WebSocket endpoint (`WS_PATH`). Each WS message is a client-initiated RPC; most of the 79 MCP tools above map 1:1 to a WS message. Some additional WS messages exist for connection lifecycle + internal routing.
### 3.1 Connection lifecycle (3)
- `hello` — client authentication. Ed25519 signature over `{meshId, memberId, pubkey, timestamp}`. Broker verifies, creates presence row, replies with `hello_ack`.
- `hello_ack` — server → client, confirms authentication + sends restored peer state.
- `get_clock` — get current simulation clock state.
### 3.2 Messaging (4 WS ops)
- `send` — send a message. Envelope contains sender, recipient (peer/group/*), priority, nonce, ciphertext.
- `peer_dir_request` / `peer_dir_response` — peer-to-peer directory request (read_peer_file under the hood).
- `peer_file_request` / `peer_file_response` — peer-to-peer file read.
### 3.3 Profile + presence (5)
- `set_status`, `set_summary`, `set_visible`, `set_profile`, `set_stats`
### 3.4 Groups (2)
- `join_group`, `leave_group`
### 3.5 State KV (3)
- `set_state`, `get_state`, `list_state`
### 3.6 Memory (3)
- `remember`, `recall`, `forget`
### 3.7 Files (5)
- `get_file`, `list_files`, `file_status`, `grant_file_access`, `delete_file`
### 3.8 Vectors (3)
- `vector_store`, `vector_search`, `vector_delete`, `list_collections`
### 3.9 Graph (2)
- `graph_query`, `graph_execute`
### 3.10 Shared SQL (3)
- `mesh_query`, `mesh_execute`, `mesh_schema`
### 3.11 Streams (4)
- `create_stream`, `publish`, `subscribe`, `unsubscribe`, `list_streams`
### 3.12 Contexts (3)
- `share_context`, `get_context`, `list_contexts`
### 3.13 Tasks (4)
- `create_task`, `claim_task`, `complete_task`, `list_tasks`
### 3.14 Scheduling (3)
- `schedule`, `list_scheduled`, `cancel_scheduled`
### 3.15 Mesh metadata (3)
- `mesh_info`, `peers_list` (from `list_peers`), `message_status`
### 3.16 Simulation clock (4)
- `set_clock`, `pause_clock`, `resume_clock`, `get_clock`
### 3.17 Skills (4)
- `share_skill`, `get_skill`, `list_skills`, `remove_skill`, `skill_deploy`
### 3.18 MCP registry (11)
- `mcp_register`, `mcp_unregister`, `mcp_list`, `mcp_call`, `mcp_call_response` (peer → peer relay)
- `mcp_deploy`, `mcp_undeploy`, `mcp_update`, `mcp_logs`, `mcp_scope`, `mcp_schema`, `mcp_catalog`
### 3.19 Vault (4)
- `vault_set`, `vault_get`, `vault_list`, `vault_delete`
### 3.20 URL watch (3)
- `watch`, `unwatch`, `watch_list`
### 3.21 Webhooks (3)
- `create_webhook`, `list_webhooks`, `delete_webhook`
### 3.22 Audit (2)
- `audit_query`, `audit_verify`
---
## 4. Broker HTTP endpoints
The broker serves both WS (`/ws`) and HTTP on the same port. HTTP endpoints are listed here by (method, path) with purpose.
| Method | Path | Purpose |
|---|---|---|
| `GET` | `/health` | Health check: liveness probe |
| `GET` | `/metrics` | Prometheus metrics endpoint |
| `POST` | `/hook/set-status` | Receive hook status updates from CLI `hook` command (Claude Code session lifecycle) |
| `POST` | `/join` | Accept v1 invite join (legacy) |
| `POST` | `/invites/:code/claim` | v2 invite claim (public, unauthenticated) |
| `POST` | `/upload` | Upload a file (returns fileId, used by `share_file`) |
| `GET` | `/download/:id` | Download a file (returns content or presigned URL) |
| `POST` | `/cli-sync` | CLI sync endpoint — fetches user's meshes from `claudemesh.com` dashboard via JWT, returns mesh list |
| `POST` | `/tg/token` | Register a Telegram bot token for a mesh (connects via `connect telegram` CLI command) |
| `PATCH` | `/mesh/:id/member/:memberId` | Update a member's profile (admin or self) |
| `GET` | `/mesh/:id/members` | List mesh members |
| `PATCH` | `/mesh/:id/settings` | Update mesh-level settings (owner/admin) |
| `POST` | `/hook/:meshId/:webhookId` | Inbound webhook — external systems POST here to publish a mesh message |
| `GET` | `/test/clock` | Dev-only: simulation clock state |
| `GET` | `/test/flip` | Dev-only: test flip endpoint |
| `GET` | `/test/html` | Dev-only: test HTML endpoint |
| `WS` | `/ws` | WebSocket connection for mesh peers (all WS ops above) |
---
## 5. Database schema — `mesh` Postgres schema
23 tables in the `mesh` schema (managed via Drizzle). Defined in `packages/db/src/schema/mesh.ts`.
| Table | Purpose |
|---|---|
| `mesh.mesh` | Mesh identity. slug, name, ownerId, createdAt, settings. |
| `mesh.member` | Per-mesh member record. Stable, durable. pubkey, displayName, role, groups, joinedAt. |
| `mesh.invite` | Invite codes + metadata. |
| `mesh.pending_invite` | v2 invite handshake state (pending claim). |
| `mesh.audit_log` | Audit events per mesh. |
| `mesh.presence` | Ephemeral WS session — one row per active connection. Status, statusSource, statusUpdatedAt. |
| `mesh.message_queue` | Queued messages pending push delivery (priority ordered). |
| `mesh.pending_status` | In-flight status updates (10s TTL). |
| `mesh.state` (meshState) | Shared KV state per mesh. |
| `mesh.memory` (meshMemory) | Shared memories with full-text search. |
| `mesh.file` (meshFile) | File metadata (uploader, size, sha256, persistence, storage location). |
| `mesh.file_access` (meshFileAccess) | Per-recipient ACL on files. |
| `mesh.file_key` (meshFileKey) | Per-recipient wrapped symmetric keys for E2E encryption. |
| `mesh.context` (meshContext) | Shared context entries. |
| `mesh.task` (meshTask) | Tasks with lifecycle (open, claimed, completed, cancelled). |
| `mesh.stream` (meshStream) | Stream metadata. |
| `mesh.skill` (meshSkill) | Skill registrations (name, content, frontmatter, tags). |
| `mesh.webhook` (meshWebhook) | Inbound webhook registrations. |
| `mesh.service` (meshService) | Deployed MCP server state (container ID, scope, env, runtime, memory, logs). |
| `mesh.vault_entry` (meshVaultEntry) | Encrypted vault entries per (mesh, peer, key). |
| `mesh.scheduled_message` | Scheduled / recurring reminders (cron + one-shot). |
| `mesh.peer_state` | Per-peer state (groups, role, profile, message mode preference). |
| `mesh.telegram_bridge` | Telegram bot registration per mesh. |
---
## 6. Broker backend services
Five external services the broker manages at runtime. All currently work in v1 and ship in the default Docker Compose deployment.
| Service | Purpose | File | Per-mesh model |
|---|---|---|---|
| **Postgres** (Drizzle) | Primary data store for mesh schema. Also used for `mesh_execute` / `mesh_query` / `mesh_schema` shared-SQL tools via per-mesh schemas. | `db.ts` | Schema-per-mesh for shared SQL tools |
| **Neo4j** | Graph queries (`graph_query`, `graph_execute`). | `neo4j-client.ts` | Database-per-mesh (Enterprise) or labeled-node fallback (Community) |
| **Qdrant** | Vector embeddings + nearest-neighbor search. | `qdrant.ts` | Collection naming: `mesh_<meshId>_<collection>`, 1536-dim default, cosine distance |
| **MinIO** | File storage for `share_file` / `get_file`. | `minio.ts` | Bucket-per-mesh: `mesh-<meshId>`. Persistent + ephemeral key paths. |
| **Docker** | Runs deployed MCP servers in sandboxed containers. | `index.ts` (deploy handler) | Container-per-deployment. Read-only root, dropped caps, memory limits, network_allow. |
---
## 7. Broker core subsystems
### 7.1 Status engine (`broker.ts`, 2066 lines)
**Battle-tested status model** ported from `claude-intercom`. Rules:
- Status sources are ranked: `hook` (3) > `manual` (2) > `jsonl` (1)
- On status update:
- If status **changed** → bump everything, record new source
- If status **unchanged**, incoming source ≥ recorded → upgrade
- If status **unchanged**, incoming source < recorded:
- Recorded source still fresh → keep it (bump timestamp only)
- Recorded source stale → downgrade to honest attribution
- `HOOK_FRESHNESS_MS` window (default 60s) for "fresh" classification
- `WORKING_TTL_MS` after which `working` status reverts to `idle`
- `PENDING_TTL_MS = 10_000` for pending status cleanup
- `TTL_SWEEP_INTERVAL_MS = 15_000` for periodic cleanup
**Must preserve** — this is the correctness engine for `set_status`, `list_peers`, and Claude Code's status line.
### 7.2 Message queue + priority delivery
- Messages are stored in `mesh.message_queue` with priority (`now`, `next`, `low`)
- `now` messages bypass busy-gate and are pushed immediately
- `next` messages wait for idle peer
- `low` messages are pull-only (delivered when peer explicitly drains via `check_messages`)
- Queue is drained via `drainForMember(meshId, memberId)` on WS message arrival or manual `check_messages`
- Duplicate delivery prevention via `messageId` UUID tracking
### 7.3 Scheduled message delivery (`index.ts` in-memory + DB persistence)
- One-shot: `deliver_at` (timestamp) or `in_seconds`
- Recurring: standard 5-field cron expression
- Persists to `mesh.scheduled_message` table — survives broker restart
- On broker start, pending schedules are re-registered
- Delivery is via the normal `send_message` pipeline with `subtype: reminder`
### 7.4 URL watch subsystem (`index.ts`)
- Poller runs in-process (worker per watch)
- Modes: `hash` (SHA-256 of body), `json` (extract jsonpath value), `status` (HTTP status)
- `notify_on: change | match:<val> | not_match:<val>`
- Persists to DB so watches survive broker restart
- Min interval 5s, max 24h
### 7.5 Telegram bridge (`telegram-bridge.ts`, 1711 lines)
**Substantial subsystem.** Provides Telegram Bot API integration:
- Bot token registration per mesh via `POST /tg/token`
- Long-polling or webhook mode
- `tg:<username>` peer identity registration in the mesh's member table
- Inbound Telegram messages → mesh `send_message` events with `subtype: telegram`
- Outbound `send_message(to: "tg:<name>")` → Telegram Bot API call
- Chat-to-mesh mapping (Telegram chat_id ↔ mesh peer)
- User discovery (`connectChat`)
- Bridge row persistence in `mesh.telegram_bridge`
**This is ~18% of the broker's total source**. v2 must either:
1. Port the logic into a standalone MCP connector (`apps/mcp-telegram/`), or
2. Keep this file in the broker and wire it into the v2 architecture unchanged (my recommendation per the previous conversation — bundled into the broker image)
Either way, **every behavior documented here must still work after v2 lands**.
### 7.6 Auth + crypto (`crypto.ts`, `broker-crypto.ts`, `jwt.ts`)
- **Hello signatures**: Ed25519 signed tuple of `(meshId, memberId, pubkey, timestamp)`. Verified on every WS connection. Replay protection via timestamp window.
- **Invite verification**: canonical invite payload (`canonicalInvite`) signed by mesh owner, Ed25519 verified on claim
- **JWT**: for `/cli-sync` endpoint — the CLI obtains a JWT from `claudemesh.com` via browser flow, passes it to the broker, broker verifies and returns the user's mesh list
- **File envelopes**: client-side AES-GCM + per-recipient key wrapping (file_key table)
### 7.7 Rate limiting (`rate-limit.ts`)
- Per-peer rate limits on expensive operations
- Currently in-process (not Redis-backed)
- Enforces limits on `send`, `vector_store`, `mesh_execute`, `mesh_mcp_deploy`, etc.
### 7.8 Metrics (`metrics.ts`)
Prometheus metrics exposed at `/metrics`:
- Request counts by op type
- Latencies p50/p99
- Connection counts per mesh
- Message delivery counts by priority
- Error rates
### 7.9 Audit log (`audit.ts`)
- Every mutation is audited to `mesh.audit_log`
- Tamper-evidence via hash chaining
- Accessible via `audit_query` and `audit_verify` WS ops
### 7.10 Member API (`member-api.ts`, 284 lines)
Exports:
- `updateMemberProfile()` — used by `PATCH /mesh/:id/member/:memberId`
- `listMeshMembers()` — used by `GET /mesh/:id/members`
- `updateMeshSettings()` — used by `PATCH /mesh/:id/settings`
### 7.11 CLI sync (`cli-sync.ts`, 133 lines)
Exports `handleCliSync()` for `POST /cli-sync`. This is **already the "CLI sync meshes from dashboard" feature** — v2 will reuse this endpoint for its mesh-list refresh logic.
### 7.12 Webhook subsystem (`webhooks.ts`, 97 lines)
Handles `POST /hook/:meshId/:webhookId` inbound. Signature verification (HMAC), payload normalization, mesh message emission.
---
## 8. CLI core subsystems
### 8.1 WS client (`ws/client.ts`, 2191 lines)
**The biggest CLI file.** Implements the full WS protocol with:
- Connection management, reconnect with exponential backoff
- Message queue for offline buffering
- Request/response correlation via `_reqId`
- Ed25519 hello signature generation
- Crypto envelope wrapping for `send_message` payloads
- Push notification delivery (messages, state changes, system events)
- Per-mesh connection pooling (one WS per mesh)
### 8.2 MCP server (`mcp/server.ts`, 2139 lines)
Second biggest CLI file. Implements:
- MCP stdio transport (registered with Claude Code via `install.ts`)
- Tool registry from `mcp/tools.ts`
- Dispatch to 79 handlers (one per tool)
- WS client pooling (one connection per mesh)
- Crypto primitives for memory/state encryption
- Inline file-read helpers for `read_peer_file`
- Channel notification forwarding from broker → Claude Code via MCP elicitation
### 8.3 Crypto (`crypto/*.ts`)
- `keypair.ts` — Ed25519 keypair generation + persistence (`~/.claudemesh/keys/<mesh>.key`)
- `envelope.ts` — NaCl `crypto_box` envelope wrapping
- `file-crypto.ts` — AES-GCM file encryption + per-recipient key wrapping
- `hello-sig.ts` — Hello signature generation/verification
### 8.4 Auth + invite (`auth/*.ts`, `invite/*.ts`, `lib/invite-v2.ts`)
- `callback-listener.ts` — local HTTP server that catches browser OAuth callback (for `sync` command)
- `open-browser.ts` — cross-platform browser launcher
- `pairing-code.ts` — pairing code display
- `sync-with-broker.ts` — JWT-based sync from dashboard
- `invite/parse.ts` — parse v1 invite URLs
- `invite/enroll.ts` — enroll into a mesh from an invite
- `lib/invite-v2.ts` — v2 invite format (short-code + signed payload)
### 8.5 State + config (`state/config.ts`)
- `~/.claudemesh/config.json` read/write (mesh list, keypairs, profile defaults)
- 0600 permission enforcement
- Schema validation
### 8.6 TUI primitives (`tui/*.ts`)
- `colors.ts` — hard-coded ANSI colors
- `index.ts` — input helpers
- `screen.ts` — raw-mode screen control
- `spinner.ts` — simple spinner
### 8.7 Templates (`templates/index.ts`)
- `dev-team`, `research`, `ops-incident`, `simulation`, `personal`
- Each template seeds initial state + preset groups
### 8.8 Tests
- `__tests__/crypto-roundtrip.test.ts` — crypto round-trip verification
- `__tests__/invite-parse.test.ts` — invite URL parsing
- No integration tests against a real broker
---
## 9. Infrastructure + deployment
### 9.1 Broker runtime (`env.ts`)
Environment variables the broker expects:
- `DATABASE_URL` — Postgres connection
- `NEO4J_URL`, `NEO4J_USER`, `NEO4J_PASSWORD`
- `QDRANT_URL`
- `MINIO_ENDPOINT`, `MINIO_ACCESS_KEY`, `MINIO_SECRET_KEY`, `MINIO_USE_SSL`
- `STATUS_TTL_SECONDS` — working status timeout
- `HOOK_FRESH_WINDOW_SECONDS` — hook source freshness window
- `TELEGRAM_BOT_TOKEN` — for bridge
- `DASHBOARD_JWT_SECRET` — for `/cli-sync` verification
- `PORT` (default 8787)
- Various feature flags
### 9.2 CLI runtime
- Node >= 20 required (checked in `doctor`)
- `claude` binary must be on PATH
- `~/.claudemesh/` directory with config + keys
- `~/.claude.json` MCP server registration
- `~/.claude/settings.json` status hooks registration
### 9.3 Deployment (Coolify/Docker Compose)
- Broker deployed via Coolify + Gitea CI on OVHcloud VPS (`ic.claudemesh.com`)
- WS endpoint: `wss://ic.claudemesh.com/ws`
- HTTP endpoint: `https://ic.claudemesh.com`
- Postgres, Neo4j, Qdrant, MinIO run as siblings in Docker Compose
- Deployed MCP sandboxes use the host Docker daemon via socket mount
---
## 10. Features not in the tool/WS surface (behavioral)
These are v1 behaviors that exist but aren't enumerated as tools. Each must still work after v2.
| Feature | Location | Notes |
|---|---|---|
| Flag-first `claudemesh --resume xxx` routing | `cli/src/index.ts` §339 | Rewrites argv to `launch --resume xxx` |
| Bare `claudemesh` → welcome wizard | `cli/src/index.ts` §334 | Runs `runWelcome()` |
| Status hook auto-registration | `commands/install.ts` | Writes to `~/.claude/settings.json` |
| Claude Code session hook handling | `commands/hook.ts` | Receives stdin JSON, posts to `/hook/set-status` |
| Per-mesh keypair directory | `crypto/keypair.ts` | `~/.claudemesh/keys/<mesh>.key` with 0600 perms |
| E2E file encryption with re-wrapping | `crypto/file-crypto.ts` + `mesh_file_key` table | `grant_file_access` re-wraps symmetric key for new recipient |
| Priority message delivery | `broker.ts` | `now` bypasses busy-gate, `next` waits for idle, `low` is pull-only |
| Hook > manual > jsonl status priority | `broker.ts` | Documented in §7.1 |
| Simulation clock for test time | `index.ts` (broker) | Peers receive heartbeat ticks at simulated rate |
| Audit log hash chaining | `audit.ts` | Tamper-evident — tools call `audit_verify` to check |
| Dashboard-CLI sync | `auth/sync-with-broker.ts` + `cli-sync.ts` | Browser JWT flow, fetches mesh list from dashboard |
| Telegram chat ↔ mesh peer mapping | `telegram-bridge.ts` | Bidirectional routing via `tg:<username>` |
| Inbound webhook payload normalization | `webhooks.ts` | External systems POST, becomes a mesh message |
| Rate limiting per peer per operation | `rate-limit.ts` | In-memory token buckets |
| Prometheus metrics | `metrics.ts` | `/metrics` endpoint |
---
## 11. Test coverage (v1)
| Test | File | Notes |
|---|---|---|
| Crypto round-trip | `apps/cli/src/__tests__/crypto-roundtrip.test.ts` | Encrypt → decrypt verification |
| Invite URL parsing | `apps/cli/src/__tests__/invite-parse.test.ts` | v1 and v2 formats |
| Broker tests | `apps/broker/tests/*.test.ts` | broker.test.ts, invite-signature.test.ts, invite-v2.test.ts, hello-signature.test.ts, rate-limit.test.ts, encoding.test.ts, dup-delivery.test.ts, metrics.test.ts, logging.test.ts, integration/health.test.ts |
**v1 test coverage is minimal for the CLI side.** 2 unit test files for 12k LOC.
Broker has ~10 test files. They cover crypto primitives, invite flow, hello signatures, rate limiting, metrics — but **not** the 85 WS message handlers comprehensively.
---
## 12. The "must preserve" list (high-priority regression checks)
If v2 breaks any of these, it's a user-facing regression:
### 12.1 First-run experience
- [ ] `claudemesh` bare command → welcome wizard
- [ ] `claudemesh install` registers MCP server + status hooks in Claude Code config
- [ ] `claudemesh join <url>` enrolls into a mesh from a v1 OR v2 invite URL
- [ ] `claudemesh launch` starts Claude Code with mesh connectivity
### 12.2 Session lifecycle
- [ ] Status hooks fire correctly on Claude Code session start/stop/pause
- [ ] `set_status` honors priority (hook > manual > jsonl)
- [ ] `list_peers` shows live status with freshness gating
- [ ] Status TTL sweeper runs every 15s
### 12.3 Messaging
- [ ] `send_message(to: peer, priority: "now")` delivers immediately
- [ ] `send_message(to: peer, priority: "next")` waits for idle
- [ ] `send_message(to: "@group")` broadcasts to group members
- [ ] `send_message(to: "*")` broadcasts to all mesh peers
- [ ] Offline recipients receive queued messages on reconnect
- [ ] Duplicate delivery is prevented by `messageId` tracking
### 12.4 Cryptographic integrity
- [ ] Ed25519 keypair generation + persistence with 0600 perms
- [ ] Hello signature verification rejects replay within timestamp window
- [ ] `send_message` envelopes are E2E encrypted (NaCl crypto_box)
- [ ] File uploads are AES-GCM encrypted with per-recipient key wrapping
- [ ] `grant_file_access` re-wraps symmetric key for a new recipient
### 12.5 All 79 MCP tools
- [ ] Every tool in §2 dispatches correctly through the CLI's MCP server
- [ ] Every tool delegates to the broker WS protocol or local handler as appropriate
- [ ] No tool returns "not implemented" or throws an unexpected error
### 12.6 Broker backends
- [ ] `mesh_query` / `mesh_execute` / `mesh_schema` work against per-mesh Postgres schema
- [ ] `graph_query` / `graph_execute` work against per-mesh Neo4j database
- [ ] `vector_store` / `vector_search` work against per-mesh Qdrant collection
- [ ] `share_file` / `get_file` work through per-mesh MinIO bucket
- [ ] `mesh_mcp_deploy` spawns a Docker container with correct scope + env + network_allow
- [ ] `vault_set` + `$vault:<key>` env injection works end-to-end for deployed MCPs
### 12.7 Scheduled + URL watch
- [ ] `schedule_reminder` with `cron` survives broker restart (persisted in DB)
- [ ] `mesh_watch` polls at the specified interval and notifies on change
- [ ] Watch state persists across broker restart
### 12.8 Telegram bridge
- [ ] `connect telegram` registers bot token via `POST /tg/token`
- [ ] Bot token is stored in `mesh.telegram_bridge`
- [ ] Inbound Telegram messages are routed as mesh messages
- [ ] `send_message(to: "tg:<username>")` routes via Telegram Bot API
- [ ] `disconnect telegram` tears down the bridge cleanly
### 12.9 Dashboard sync
- [ ] `claudemesh sync` browser flow completes and fetches mesh list
- [ ] `POST /cli-sync` with valid JWT returns user's dashboard meshes
### 12.10 Webhooks
- [ ] `create_webhook` returns a POST URL
- [ ] External POST to webhook URL becomes a mesh message
- [ ] HMAC signature validation rejects unsigned requests
- [ ] `list_webhooks` + `delete_webhook` work
### 12.11 Doctor checks
- [ ] Node >= 20 check
- [ ] `claude` binary on PATH
- [ ] MCP server registered in `~/.claude.json`
- [ ] Status hooks registered in `~/.claude/settings.json`
- [ ] `~/.claudemesh/config.json` exists + parses + 0600 perms
- [ ] Mesh keypairs valid
---
## 13. What v2 is adding (net new)
Not part of the regression list, but tracked here so we don't lose sight of the forward-looking scope.
### 13.1 New CLI features (from user's stated v2 intent)
- [ ] `claudemesh login` — device-code OAuth against claudemesh.com's Better Auth backend
- [ ] `claudemesh register` — create a new account from the CLI (via browser handoff)
- [ ] `claudemesh new` — create a mesh from the CLI against `POST /api/my/meshes` (not via templates in the CLI — via dashboard API)
- [ ] `claudemesh invite` — generate an invite from the CLI via `POST /api/my/meshes/:slug/invites`
- [ ] `claudemesh whoami` — show current identity + token source
- [ ] `claudemesh logout` — revoke server-side session + clear local credentials
### 13.2 Architecture improvements (from user's v2 intent)
- [ ] Feature-folder `services/` layer with strict facade boundaries
- [ ] ESLint + dependency-cruiser boundary enforcement
- [ ] `cli/` vs `ui/` separation (non-Ink I/O vs Ink rendering)
- [ ] `entrypoints/` folder with cli + mcp entries
- [ ] Typed error classes per service with `toDomainError` helper
- [ ] Coverage threshold enforcement in CI
### 13.3 Not in v1.0.0 scope (defer to v1.1+)
Everything from the Composer 2 review rounds that isn't Pass 1:
- Local-first SQLite source of truth (Lamport, sync daemon, publish transaction)
- Broker security hardening (role-per-mesh Postgres, Docker egress proxy, SSRF policy)
- ICU MessageFormat + per-locale budgets
- Accessibility token-signal matrix
- Tiered MCP catalog + audit process
- session_kind enum
- NFC peer_id normalization
- Write queue state machine
These stay in the `.artifacts/specs/` as reference documents. They describe a good destination. They are NOT v1.0.0 requirements.
---
## 14. Known v1 technical debt / gaps (worth noting)
These aren't features — they're places where v1 is weaker than it could be. Document here so v2 doesn't blindly port the weaknesses.
- **CLI auth is missing** — v1 has no `login` / `logout` command. All account-level operations require the web dashboard. This is what v2 is adding.
- **Imperative command branching** — `commands/launch.ts` is 775 lines with nested flag handling. Cleaner in v2's flow pipeline.
- **Minimal CLI test coverage** — 2 test files for 12k LOC. v2 should have colocated tests per service.
- **Rate limiting is in-memory only** — doesn't survive broker restart; not Redis-backed.
- **No CLI-side caching** — every `list_peers` / `mesh_info` call hits the broker. v2's local-first layer (Pass 2) addresses this.
- **Telegram bridge is a large monolithic file** (1711 lines) — legitimate complexity, but v2 may want to modularize if it touches it.
- **v1 wizard bleed-through** — `launch``claude` handoff leaves ANSI state dirty. v2's `resetTerminal()` choke point fixes this.
None of these are regressions if v2 keeps them as-is. v2 should **not** prioritize fixing them — fix them when they become a problem, not speculatively.
---
## 15. Reading this inventory
**If you're implementing v2 Phase 1** (foundation layers): every tool in §2, every WS op in §3, every HTTP endpoint in §4, every DB table in §5 must have a place in the v2 folder structure. No new semantics, no improved algorithms — just move the working code.
**If you're reviewing a v2 PR**: check it against §12 ("must preserve" list). If the PR changes the behavior of anything in that list, it's a regression and needs explicit sign-off.
**If you're writing v2 docs**: reference this document. Every feature here is user-visible and documented in v1's README / slash-command help / tool descriptions. v2 docs should mention every feature from §2 as preserved.
---
**End of inventory.**

File diff suppressed because it is too large Load Diff