Files
claudemesh/.artifacts/specs/2026-04-11-cli-v2-pass1.md
Alejandro Gutiérrez ee12510ef1
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
refactor: rename cli-v2 → cli, archive legacy cli, plus broker-side grants + auto-migrate
- 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>
2026-04-15 08:44:52 +01:00

62 KiB
Raw Permalink Blame History

claudemesh-cli v2 — Pass 1

Status: spec (active implementation target) Created: 2026-04-11 Scope: immediate v2 work — refactor + CLI user flows + v1 preservation Ships as: claudemesh-cli@1.0.0

Companion documents (read these first):

  • 2026-04-11-v1-feature-inventory.md — every v1 feature that must keep working
  • 2026-04-11-v2-parity-test-plan.md — how we verify v2 preserves v1
  • 2026-04-10-cli-v2-pass2-facade-pattern.md — architectural rules (authoritative for both passes)

Pass 2 references (future work, not implementation targets):

  • 2026-04-10-cli-v2-pass2-final-vision.md — longer-term architecture vision
  • 2026-04-10-cli-v2-pass2-ux-design.md — interactive UX redesign
  • 2026-04-10-cli-v2-pass2-local-first-storage.md — local SQLite + Lamport + sync daemon
  • 2026-04-10-cli-v2-pass2-shared-infrastructure.md — broker hardening

1. Scope

1.1 What Pass 1 delivers

Three things, exactly:

  1. Folder structure refactor. apps/cli/src/ becomes apps/cli-v2/src/ with a cleaner layout: entrypoints/, cli/, commands/, services/ (feature-folders with facades), ui/, mcp/, constants/, types/, utils/, locales/, templates/. ESLint + dependency-cruiser enforce the facade pattern. The scaffold replaces v1's ad-hoc organization.
  2. CLI user flows that remove the dependency on the web dashboard for common operations:
    • claudemesh login — device-code OAuth against claudemesh.com
    • claudemesh register — account creation flow (browser handoff for email verification)
    • claudemesh logout — revoke session
    • claudemesh whoami — identity check
    • claudemesh new — create a mesh from the CLI (was dashboard-only in v1)
    • claudemesh invite — generate an invite from the CLI (was dashboard-only in v1)
  3. Every v1 feature preserved — the 79 MCP tools, the 85 broker WS message types, the welcome wizard, the launch flow, the status hooks, the crypto primitives, the invite parsing, the doctor checks. See 2026-04-11-v1-feature-inventory.md for the complete list.

1.2 What Pass 1 does NOT deliver

Everything in the Pass 2 specs. Concretely:

  • No local SQLite source of truth. v2 Pass 1 still hits the broker for every read/write (same as v1).
  • No Lamport clock, no sync daemon, no outbox/inbox, no publish transaction, no NFC normalization, no write queue with state machine.
  • No broker changes. The broker runs unchanged. Postgres, Neo4j, Qdrant, MinIO, Docker sandboxes — all stay on their v1 configurations.
  • No hardened Postgres role isolation. v1's search_path-based scoping is preserved unchanged. Improving this is Pass 2.
  • No MCP catalog tiering, no catalog audit process, no egress proxy for Docker sandboxes, no SSRF policy for URL watch. v1 behavior is preserved.
  • No Telegram bridge changes. apps/broker/src/telegram-bridge.ts stays exactly as it is. The v2 CLI preserves claudemesh connect telegram as an interactive wizard (see §5.7) — this was a regression risk in an earlier draft of this spec. The v1 disconnect telegram subcommand is dropped because teardown is handled by the bridge itself (revoke inside Telegram) or by leaving the mesh.
  • No new MCP catalog. Deployed MCPs still come from zip upload / git URL / npx package, same as v1. The bundled catalog (apps/broker/src/mcp-catalog.ts) is Pass 2 work.
  • No ICU MessageFormat. v2 Pass 1 uses simple string templates. ICU plurals + per-locale length budgets are Pass 2.
  • No session_kind enum with six output budgets. v2 Pass 1 keeps v1's imperative output patterns.
  • No accessibility token-signal matrix. v2 Pass 1 keeps v1's TUI primitives. Pass 2 introduces the new design system.
  • No new visual design system. v1's colors and icons stay. Pass 2 introduces the six-role palette.
  • No trust surfaces / delight beats budget. Pass 2 concern.
  • No new error structure with one-action recovery. v1's existing error messages stay.
  • No 14 inviolable rules from the Pass 2 UX spec. Pass 2 concern.

If it's in a Pass 2 document, it is not part of Pass 1 unless this document explicitly says so.

1.3 The two compatibility contracts

Contract A — the broker's WS protocol. v2's CLI must speak the broker's v1 WS protocol byte-for-byte. Every one of the 85 WS message types in inventory §3 keeps the same envelope shape, the same field names, the same signature format. Production brokers deployed today must continue to serve v2 CLIs without any broker-side changes. This is the only compatibility contract that matters.

Contract B — v1 feature parity (user-facing behavior). Every item in the v1 inventory §12 "must preserve" list must keep working. This includes the 79 MCP tools, the 85 WS messages, the 18 HTTP endpoints, the backend integrations, the status engine, the scheduled message delivery, the URL watch, and the crypto round-trips. The v2 parity test suite is the verification.

What is NOT a compatibility contract:

  • v1's CLI command names (launch, connect telegram, etc.) — v2 picks new names where better ones exist.
  • v1's config file shape (~/.claudemesh/config.json) — v2 picks a new shape if needed.
  • v1's JSON output shape (--json outputs) — v2 locks a new shape via schema_version: "1.0".
  • v1's env var names — v2 picks new ones.
  • v1's exit codes — v2 uses the cleaner scheme from Pass 2 §11.2.
  • v1's stdout text — v2 rewrites for consistency and future localization.

No deprecation windows, no migration runners, no aliases. There are no users to migrate.


2. The three Pass 1 intents

These are the decisions v2 Pass 1 is optimizing for. Everything else is either out of scope or inherits from v1.

2.1 Intent A — scalability through folder structure

Problem: v1's apps/cli/src/ has 22 files, with business logic scattered across commands/, mcp/, ws/, auth/, crypto/, invite/, state/, tui/, and lib/. No enforced boundaries. commands/launch.ts is 775 lines. mcp/server.ts is 2,139 lines. ws/client.ts is 2,191 lines. Growing to v1.5 and beyond would compound the tangle.

Pass 1 fix: feature-folder services with facade pattern. Every service (auth, mesh, invite, broker, api, crypto, config, state, device, clipboard, spawn, telemetry, health, update, i18n, lifecycle, logger) lives in services/<name>/ with facade.ts as the single public entry point. ESLint boundaries plugin + dependency-cruiser enforce that ui/, commands/, cli/, and mcp/ import only from services/*/facade.ts.

The facade pattern is described in full in 2026-04-10-cli-v2-pass2-facade-pattern.md (which applies to both passes). Pass 1 implements it completely.

What this buys us:

  • Growing a service is local — add files inside its folder, extend its facade
  • Cross-service calls are explicit — facades import other facades, never internal files
  • Testing is trivial — mock the facade
  • Code review is bounded — PRs touch single service folders
  • Scaling contributors is possible — ownership can split along service lines

2.2 Intent B — CLI user flows (remove dashboard dependency)

Problem: v1 users must open the claudemesh.com dashboard to:

  • Create an account
  • Log in to the dashboard (OAuth flow)
  • Create a mesh
  • Generate an invite link
  • Manage their profile

Only after those web steps can they use the CLI to join (claudemesh join <url>). This means a terminal-first developer always has to context-switch to a browser for anything beyond joining.

Pass 1 fix: add CLI commands that drive all these flows end-to-end.

v2 Pass 1 command Replaces
claudemesh register Web dashboard signup page
claudemesh login Web dashboard login + dashboard-to-CLI JWT sync
claudemesh logout Web dashboard logout
claudemesh whoami Web dashboard "who am I" profile view
claudemesh new <name> Web dashboard "create mesh" form
claudemesh invite [email] Web dashboard "generate invite" button

These are the only net-new CLI commands in Pass 1. Everything else inherits from v1.

The flows use device-code OAuth against claudemesh.com's existing Better Auth backend. No new backend work is required beyond confirming the device-code endpoint exists (it may or may not — the inventory §4 confirms POST /cli-sync exists, which is the existing dashboard-sync flow; v2 extends this to a full login/register flow).

2.3 Intent C — preserve every v1 feature

Problem: the scope creep risk. Every review round has added "while we're refactoring, let's also fix X."

Pass 1 fix: the v1 feature inventory is the regression contract. The parity test suite is the verification. A PR cannot merge if it breaks any item in inventory §12.

Pass 1 is a refactor, not a rewrite. v1's code gets moved into new files, its imports get updated to use facades, its business logic stays intact. Where v1's implementation is long or messy, v2 Pass 1 may split it into multiple files — but the observable behavior stays identical.


3. Target source tree

apps/cli-v2/
├── package.json                        # name: claudemesh-cli, version: 0.11.0-alpha.0 → 1.0.0
├── tsconfig.json
├── bunfig.toml
├── build.ts                            # Bun bundler driver, enforces 1.2 MB gzipped ceiling
├── .eslintrc.cjs                       # boundaries plugin + 3 custom rules
├── dependency-cruiser.config.js        # folder-level boundary enforcement
├── biome.json
├── CHANGELOG.md
├── README.md
├── bin/
│   └── claudemesh                      # shebang → dist/entrypoints/cli.js
│
├── src/
│   ├── entrypoints/
│   │   ├── cli.ts                      # interactive CLI entry — parses argv, dispatches
│   │   └── mcp.ts                      # `claudemesh mcp` → MCP stdio server
│   │
│   ├── cli/                            # non-Ink I/O plumbing
│   │   ├── argv.ts                     # parse process.argv, detect positional invite URLs
│   │   ├── print.ts                    # stdout helpers (respects NO_COLOR / FORCE_COLOR)
│   │   ├── structured-io.ts            # --json output with schema_version
│   │   ├── exit.ts                     # exit codes + cleanup hooks
│   │   ├── update-notice.ts            # "new version available" banner (npm registry poll)
│   │   ├── handlers/
│   │   │   ├── signal.ts               # SIGINT/SIGTERM graceful shutdown
│   │   │   └── error.ts                # top-level error → user message
│   │   └── output/                     # plain-text renderers for non-interactive commands
│   │       ├── list.ts
│   │       ├── peers.ts
│   │       ├── whoami.ts
│   │       └── version.ts
│   │
│   ├── commands/                       # one verb per file; no "advanced" prefix
│   │   ├── index.ts                    # command registry
│   │   ├── join.ts                     # join a mesh from invite URL (v1 + v2 formats)
│   │   ├── new.ts                      # NEW — create a mesh via API
│   │   ├── invite.ts                   # NEW — generate invite via API
│   │   ├── list.ts                     # list joined meshes
│   │   ├── rename.ts                   # rename current mesh
│   │   ├── leave.ts                    # leave a mesh
│   │   ├── peers.ts                    # list peers
│   │   ├── send.ts                     # send a message
│   │   ├── inbox.ts                    # drain inbox
│   │   ├── state.ts                    # get / set / list state
│   │   ├── info.ts                     # mesh overview
│   │   ├── remember.ts                 # store memory
│   │   ├── recall.ts                   # search memories
│   │   ├── remind.ts                   # schedule reminder
│   │   ├── profile.ts                  # view / edit profile
│   │   ├── status.ts                   # broker connectivity check
│   │   ├── doctor.ts                   # diagnostic checks (port from v1)
│   │   ├── register.ts                 # NEW — account creation
│   │   ├── login.ts                    # NEW — device-code OAuth
│   │   ├── logout.ts                   # NEW — revoke session
│   │   ├── whoami.ts                   # NEW — identity check
│   │   ├── install.ts                  # register MCP + hooks with Claude Code
│   │   ├── uninstall.ts                # remove MCP + hooks
│   │   ├── sync.ts                     # sync meshes from dashboard (existing /cli-sync)
│   │   ├── welcome.ts                  # welcome wizard (bare command, first-run only)
│   │   ├── hook.ts                     # internal Claude Code hook handler
│   │   ├── mcp.ts                      # internal `claudemesh mcp` dispatcher
│   │   └── seed-test-mesh.ts           # dev-only
│   │
│   ├── services/                       # feature-folders with facades
│   │   ├── auth/                       # NEW CLI user flows + existing JWT sync
│   │   │   ├── client.ts               # HTTP calls to /api/auth/cli/* endpoints
│   │   │   ├── device-code.ts          # device-code flow orchestration
│   │   │   ├── token-store.ts          # ~/.claudemesh/auth.json R/W with 0600
│   │   │   ├── dashboard-sync.ts       # existing /cli-sync flow (port from v1 auth/sync-with-broker.ts)
│   │   │   ├── callback-listener.ts    # local HTTP listener for OAuth callback (port from v1)
│   │   │   ├── open-browser.ts         # cross-platform browser launcher (port from v1)
│   │   │   ├── implementation.ts
│   │   │   ├── schemas.ts
│   │   │   ├── errors.ts
│   │   │   ├── index.ts                # getAuthService() factory
│   │   │   ├── facade.ts               # loginWithDeviceCode, logout, whoAmI, register
│   │   │   └── auth.test.ts
│   │   │
│   │   ├── mesh/                       # mesh CRUD + list + resolve-target
│   │   │   ├── client.ts               # HTTP to /api/my/meshes
│   │   │   ├── list.ts                 # port from v1 commands/list.ts logic
│   │   │   ├── create.ts               # NEW — POST /api/my/meshes
│   │   │   ├── rename.ts               # PATCH /api/my/meshes/:slug
│   │   │   ├── leave.ts                # port from v1 commands/leave.ts
│   │   │   ├── join.ts                 # port from v1 commands/join.ts
│   │   │   ├── resolve-target.ts       # port from v1 launch.ts mesh picker logic
│   │   │   ├── implementation.ts
│   │   │   ├── schemas.ts
│   │   │   ├── errors.ts
│   │   │   ├── index.ts
│   │   │   └── facade.ts
│   │   │
│   │   ├── invite/                     # invite generation + parsing + claiming
│   │   │   ├── generate.ts             # NEW — POST /api/my/meshes/:slug/invites
│   │   │   ├── parse-url.ts            # port from v1 invite/parse.ts + lib/invite-v2.ts
│   │   │   ├── claim.ts                # port from v1 invite/enroll.ts
│   │   │   ├── send-email.ts           # NEW — email delivery (if backend supports)
│   │   │   ├── implementation.ts
│   │   │   ├── schemas.ts
│   │   │   ├── errors.ts
│   │   │   ├── index.ts
│   │   │   └── facade.ts
│   │   │
│   │   ├── broker/                     # WS client + peer crypto (port from v1 ws/)
│   │   │   ├── ws-client.ts            # port from v1 ws/client.ts (2191 lines)
│   │   │   ├── manager.ts              # port from v1 ws/manager.ts
│   │   │   ├── envelope.ts             # port from v1 crypto/envelope.ts
│   │   │   ├── hello-sig.ts            # port from v1 crypto/hello-sig.ts
│   │   │   ├── implementation.ts
│   │   │   ├── schemas.ts              # WS message type definitions (all 85)
│   │   │   ├── errors.ts
│   │   │   ├── index.ts
│   │   │   ├── facade.ts               # typed methods for each WS op
│   │   │   └── broker.test.ts
│   │   │
│   │   ├── api/                        # base HTTP client
│   │   │   ├── client.ts               # typed fetch wrapper
│   │   │   ├── my.ts                   # /api/my/* endpoint helpers
│   │   │   ├── public.ts               # /api/public/* (invite claim)
│   │   │   ├── errors.ts
│   │   │   ├── index.ts
│   │   │   └── facade.ts
│   │   │
│   │   ├── crypto/                     # Ed25519, NaCl, AES-GCM
│   │   │   ├── keypair.ts              # port from v1 crypto/keypair.ts
│   │   │   ├── file-crypto.ts          # port from v1 crypto/file-crypto.ts
│   │   │   ├── box.ts                  # NaCl crypto_box wrappers
│   │   │   ├── random.ts               # secure random helpers
│   │   │   ├── index.ts
│   │   │   ├── facade.ts
│   │   │   └── crypto.test.ts
│   │   │
│   │   ├── config/                     # ~/.claudemesh/config.json
│   │   │   ├── read.ts                 # port from v1 state/config.ts
│   │   │   ├── write.ts
│   │   │   ├── schemas.ts
│   │   │   ├── index.ts
│   │   │   └── facade.ts
│   │   │
│   │   ├── state/                      # last-used cache (not mesh state — that's via broker)
│   │   │   ├── last-used.ts            # remember last mesh, name, role for faster launch
│   │   │   ├── schemas.ts
│   │   │   ├── index.ts
│   │   │   └── facade.ts
│   │   │
│   │   ├── device/                     # hostname, os, arch
│   │   │   ├── info.ts
│   │   │   ├── index.ts
│   │   │   └── facade.ts
│   │   │
│   │   ├── clipboard/                  # pbpaste / xclip / wl-paste (optional)
│   │   │   ├── read.ts
│   │   │   ├── index.ts
│   │   │   └── facade.ts
│   │   │
│   │   ├── spawn/                      # exec choke points
│   │   │   ├── claude.ts               # exec claude binary (port from v1 launch.ts)
│   │   │   ├── browser.ts              # open browser (port from v1 auth/open-browser.ts)
│   │   │   ├── index.ts
│   │   │   └── facade.ts
│   │   │
│   │   ├── telemetry/                  # opt-out usage events
│   │   │   ├── emit.ts
│   │   │   ├── opt-out.ts
│   │   │   ├── index.ts
│   │   │   └── facade.ts
│   │   │
│   │   ├── health/                     # doctor check implementations
│   │   │   ├── check-node-version.ts
│   │   │   ├── check-claude-binary.ts
│   │   │   ├── check-mcp-registered.ts
│   │   │   ├── check-hooks-registered.ts
│   │   │   ├── check-config-perms.ts
│   │   │   ├── check-keypairs-valid.ts
│   │   │   ├── index.ts
│   │   │   └── facade.ts               # runAllChecks, runCheck
│   │   │
│   │   ├── update/                     # npm registry version check
│   │   │   ├── check.ts
│   │   │   ├── index.ts
│   │   │   └── facade.ts
│   │   │
│   │   ├── i18n/                       # simple string templates (no ICU in Pass 1)
│   │   │   ├── resolve.ts              # locale detection
│   │   │   ├── format.ts               # {placeholder} substitution
│   │   │   ├── index.ts
│   │   │   └── facade.ts
│   │   │
│   │   ├── lifecycle/                  # start/stop long-running services
│   │   │   ├── service-manager.ts
│   │   │   ├── index.ts
│   │   │   └── facade.ts
│   │   │
│   │   └── logger/                     # structured logger
│   │       ├── logger.ts
│   │       ├── index.ts
│   │       └── facade.ts
│   │
│   ├── ui/                             # Ink-based TUI (port from v1 tui/)
│   │   ├── styles.ts                   # colors + icons (port from v1 tui/colors.ts — v1 values, not Pass 2 redesign)
│   │   ├── screen.ts                   # port from v1 tui/screen.ts
│   │   ├── spinner.ts                  # port from v1 tui/spinner.ts
│   │   ├── welcome/                    # welcome wizard screens (port from v1 commands/welcome.ts)
│   │   │   ├── WelcomeScreen.tsx
│   │   │   ├── RegisterStep.tsx        # NEW for Pass 1
│   │   │   ├── LoginStep.tsx           # NEW for Pass 1
│   │   │   ├── MeshPickerStep.tsx
│   │   │   └── index.ts
│   │   └── launch/                     # launch flow (port from v1 commands/launch.ts interactive bits)
│   │       ├── LaunchFlow.tsx
│   │       └── index.ts
│   │
│   ├── mcp/                            # stdio MCP server (port from v1 mcp/server.ts)
│   │   ├── server.ts                   # entry from entrypoints/mcp.ts
│   │   ├── router.ts                   # tool dispatch
│   │   ├── tools/                      # one file per tool family (79 tools total)
│   │   │   ├── memory.ts               # remember, recall, forget
│   │   │   ├── state.ts                # set_state, get_state, list_state
│   │   │   ├── messaging.ts            # send_message, list_peers, check_messages, message_status
│   │   │   ├── profile.ts              # set_profile, set_status, set_summary, set_visible
│   │   │   ├── groups.ts               # join_group, leave_group
│   │   │   ├── files.ts                # share_file, get_file, list_files, file_status, delete_file, grant_file_access, read_peer_file, list_peer_files
│   │   │   ├── vectors.ts              # vector_store, vector_search, vector_delete, list_collections
│   │   │   ├── graph.ts                # graph_query, graph_execute
│   │   │   ├── sql.ts                  # mesh_query, mesh_execute, mesh_schema
│   │   │   ├── streams.ts              # create_stream, publish, subscribe, list_streams
│   │   │   ├── contexts.ts             # share_context, get_context, list_contexts
│   │   │   ├── tasks.ts                # create_task, claim_task, complete_task, list_tasks
│   │   │   ├── scheduling.ts           # schedule_reminder, list_scheduled, cancel_scheduled
│   │   │   ├── mesh-meta.ts            # mesh_info, mesh_stats, mesh_clock, ping_mesh
│   │   │   ├── clock-write.ts          # mesh_set_clock, mesh_pause_clock, mesh_resume_clock
│   │   │   ├── skills.ts               # share_skill, get_skill, list_skills, remove_skill, mesh_skill_deploy
│   │   │   ├── mcp-registry-peer.ts    # mesh_mcp_register, mesh_mcp_list, mesh_tool_call, mesh_mcp_remove
│   │   │   ├── mcp-registry-broker.ts  # mesh_mcp_deploy, undeploy, update, logs, scope, schema, catalog
│   │   │   ├── vault.ts                # vault_set, vault_list, vault_delete
│   │   │   ├── url-watch.ts            # mesh_watch, mesh_unwatch, mesh_watches
│   │   │   ├── webhooks.ts             # create_webhook, list_webhooks, delete_webhook
│   │   │   └── index.ts
│   │   ├── middleware/
│   │   │   ├── logging.ts
│   │   │   └── error-handler.ts
│   │   └── handlers/
│   │       ├── stdio.ts
│   │       └── jsonrpc.ts
│   │
│   ├── constants/
│   │   ├── paths.ts                    # XDG paths for ~/.claudemesh/
│   │   ├── urls.ts                     # default API / broker URLs
│   │   ├── timings.ts                  # polling intervals, timeouts
│   │   ├── exit-codes.ts               # numeric exit code constants
│   │   └── index.ts
│   │
│   ├── types/
│   │   ├── api.ts                      # request/response types
│   │   ├── mesh.ts                     # mesh, member, invite types
│   │   ├── peer.ts
│   │   └── index.ts
│   │
│   ├── utils/
│   │   ├── levenshtein.ts              # typo recovery
│   │   ├── slug.ts                     # hostname → slug
│   │   ├── url.ts                      # URL parsing
│   │   ├── format.ts                   # bytes, durations
│   │   ├── semver.ts                   # version comparison
│   │   ├── retry.ts                    # backoff helpers
│   │   └── index.ts
│   │
│   ├── locales/
│   │   ├── en.ts                       # simple string templates (no ICU in Pass 1)
│   │   └── index.ts
│   │
│   └── templates/                      # mesh templates (port from v1 templates/index.ts)
│       ├── dev-team.ts
│       ├── research.ts
│       ├── ops-incident.ts
│       ├── simulation.ts
│       ├── personal.ts
│       └── index.ts
│
└── tests/                              # See parity test plan for full layout
    ├── unit/                           # colocated tests under src/services/*/
    ├── parity/                         # v1/v2 behavioral equivalence (~70 files)
    ├── contract/                       # WS protocol compatibility (~85 files)
    ├── mcp-tools/                      # per-tool handler parity (79 files)
    ├── e2e/                            # full journeys against real broker (~28 files)
    ├── golden/                         # --json output shape (~12 files)
    ├── fixtures/
    └── helpers/

What's NOT in this tree (versus Pass 2):

  • No src/services/store/ — no local SQLite source of truth
  • No src/services/broker/sync-daemon.ts — no outbox/inbox sync
  • No src/services/broker/peer-crypto.ts (beyond hello-sig + envelope that v1 already has)
  • No src/migrations/ — no config migrations because there's no v1 user base
  • No src/ui/accessibility/ — no announcement shim
  • No src/ui/session-kind.ts — no output budget enum
  • No src/locales/es.ts — Pass 1 is English-only (Spanish is Pass 2)
  • No src/services/broker/mcp-catalog.ts — no CLI-side catalog mirror

The scaffold for Pass 1 creates ~200 files (similar to Pass 2 scaffold but leaner on the services/store/, services/broker/sync-*, ui/accessibility/, and Pass 2-specific areas).


4. CLI command surface

4.1 Main --help output

$ claudemesh --help

claudemesh — peer mesh for Claude Code sessions
v1.0.0

USAGE
  claudemesh                 start a session in your mesh (creates one if needed)
  claudemesh <url>           join a mesh from an invite link
  claudemesh new             create a new mesh
  claudemesh invite [email]  generate an invite (copies to clipboard)
  claudemesh list            see your meshes
  claudemesh rename <name>   rename the current mesh
  claudemesh leave [mesh]    leave a mesh
  claudemesh peers           see who's in the current mesh

  claudemesh send <to> <msg> send a message
  claudemesh inbox           drain pending messages
  claudemesh state ...       get, set, or list shared state
  claudemesh remember <text> store a memory
  claudemesh recall <query>  search memories
  claudemesh remind ...      schedule a reminder
  claudemesh profile         view or edit your profile

When something's wrong
  claudemesh doctor          diagnose install/config/connection issues
  claudemesh whoami          show current identity
  claudemesh status          check broker connectivity

Authentication
  claudemesh register        create a claudemesh.com account from the CLI
  claudemesh login           sign in via browser
  claudemesh logout          sign out and clear credentials

Setup
  claudemesh install           register MCP server + status hooks with Claude Code
  claudemesh uninstall         remove MCP server + hooks
  claudemesh sync              refresh mesh list from your dashboard
  claudemesh connect telegram  link a Telegram bot to a mesh (interactive wizard)

Internal (for Claude Code and scripts)
  claudemesh mcp             stdio MCP server
  claudemesh hook            handle Claude Code hook events
  claudemesh seed-test-mesh  dev-only helper

4.2 Flat command namespace

There is no advanced prefix. There is no launch subcommand. connect telegram is preserved as an interactive wizard (see §5.7); disconnect telegram is dropped. Every command above is callable directly.

The main --help groups commands visually (USAGE block + "When something's wrong" + "Authentication" + "Setup" + "Internal") but this is display formatting, not namespacing. claudemesh register is a direct invocation, not claudemesh advanced register.

4.3 Bare command behavior

$ claudemesh

Depends on machine state:

  • First run, no config: runs the welcome wizard (commands/welcome.ts) which detects first-time use and prompts for register/login/join/exit.
  • Returning user, config present: launches a session directly. Picks the mesh from (in priority order): --mesh flag → last-used cache (services/state/last-used.ts) → interactive picker if ambiguous → the only joined mesh if there's just one.
  • Returning user, flag-first invocation (claudemesh --resume abc, claudemesh --name X, claudemesh -y): same as returning user launch, with the flags passed to the launch handler.
  • Returning user, mesh already determined, clipboard has invite URL: offers the invite as an alternative option before launching.

This is the v1 behavior preserved verbatim, minus the launch word.

4.4 Exit codes

0   success
1   user cancelled (Ctrl-C, declined prompt)
2   authentication failed
3   invalid arguments / unknown command
4   network error (broker or API unreachable — only when network was explicitly required)
5   not found (mesh, invite, peer)
6   already exists (slug collision, duplicate command)
7   permission denied (role, scope)
8   internal error (bug)
9   claude binary missing

These are defined in src/constants/exit-codes.ts and imported by every command handler.

4.5 Flag conventions

  • -y / --yes — skip interactive confirmations
  • -q / --quiet — suppress non-essential output
  • -v / --verbose — increase log detail
  • --json / --output-format json — machine-readable output with schema_version: "1.0"
  • --mesh <slug> — override mesh selection
  • --token <value> — override auth token (for scripts)
  • --help / -h — per-command help

-y is the key flag. It enables scripted/non-interactive use by skipping every prompt. Commands that require a confirmation in interactive mode just proceed when -y is passed.


5. CLI user flows (net new)

All five user flows run against claudemesh.com's existing Better Auth + dashboard infrastructure. The broker already has POST /cli-sync (inventory §4) for JWT-based sync — v2 Pass 1 extends this to a full device-code OAuth flow.

5.1 claudemesh register

Creates a new claudemesh.com account via browser handoff (email verification is a browser-side step).

$ claudemesh register

  Opening browser for account signup…

  [browser opens to claudemesh.com/register?source=cli]
  [user fills in email, password, verifies email]
  [browser redirects to claudemesh.com/cli-auth with success token]

  ✔ Account created: alejandro@example.com
  ✔ Signed in.

Implementation:

  1. CLI generates a one-time callback URL with a local port listener (services/auth/callback-listener.ts, ported from v1)
  2. Opens browser to claudemesh.com/register?source=cli&callback=http://localhost:<port>
  3. User completes signup + email verification on the web
  4. claudemesh.com posts a session token back to the callback
  5. CLI stores token in ~/.claudemesh/auth.json via services/auth/token-store.ts
  6. CLI calls POST /cli-sync to fetch any existing meshes (usually none for a new account)

Backend requirement: claudemesh.com/register?source=cli&callback=<url> must exist. If it doesn't, Pass 1 scope expands to add it to the web app (but this is a single Next.js page, not a backend architectural change).

5.2 claudemesh login

Device-code OAuth against claudemesh.com for existing accounts.

$ claudemesh login

  Opening browser for sign-in…

  If your browser didn't open, visit:
    https://claudemesh.com/cli-auth?code=ABCD-EFGH

  Waiting for confirmation…

  ✔ Signed in as Alejandro.
  ✔ Synced 3 meshes: platform-team, alejandro-mbp, claudefarm

Implementation:

  1. CLI POSTs /api/auth/cli/device-code/new with device info (hostname, os, arch)
  2. Receives { device_code, user_code, expires_at, verification_url }
  3. Opens browser to verification_url?code=<user_code>
  4. Prints the user code to terminal as fallback
  5. Polls /api/auth/cli/device-code/<device_code> every 1.5 seconds
  6. On approval, receives { session_token, user } + stores in ~/.claudemesh/auth.json
  7. Calls POST /cli-sync to fetch meshes

Backend requirement: /api/auth/cli/device-code/* endpoints must exist. These are net new. Pass 1 scope includes adding them to apps/web/src/app/api/auth/cli/ as a thin wrapper around Better Auth's session creation. This is ~100 lines of backend TypeScript.

5.3 claudemesh logout

$ claudemesh logout

  ✔ Revoked session on claudemesh.com
  ✔ Removed ~/.claudemesh/auth.json

Implementation:

  1. Calls DELETE /api/my/sessions/current (or equivalent Better Auth revoke endpoint)
  2. Deletes ~/.claudemesh/auth.json
  3. On server revocation failure, still removes local file and warns:
    ✔ Removed local credentials.
    ⚠ Could not revoke session on claudemesh.com. Revoke manually at
      https://claudemesh.com/dashboard/settings/sessions
    

5.4 claudemesh whoami

$ claudemesh whoami

  Signed in as Alejandro (alejandro@example.com)
  Token source: device-code (~/.claudemesh/auth.json)
  Meshes: 3 owned, 1 guest

With --json:

{
  "schema_version": "1.0",
  "signed_in": true,
  "user": {
    "id": "usr_abc123",
    "display_name": "Alejandro",
    "email": "alejandro@example.com"
  },
  "token_source": "device-code",
  "meshes": { "owned": 3, "guest": 1 }
}

Implementation: reads ~/.claudemesh/auth.json, calls GET /api/my/profile to verify + refresh, prints formatted output.

5.5 claudemesh new <name>

Creates a mesh on claudemesh.com from the CLI.

$ claudemesh new "Platform team"

  ✔ Created "platform-team" (id: msh_abc123)
  ✔ You're the owner
  ✔ Joined locally

  Invite teammates with: claudemesh invite

Implementation:

  1. Requires authenticated session (if not, triggers login flow first)
  2. POSTs /api/my/meshes with { name, slug?: "platform-team" } (slug auto-derived from name if not provided)
  3. On slug collision, suggests alternative: "A mesh called 'platform-team' already exists. Try 'platform-team-2'?"
  4. On success, stores the mesh in local config via services/config/facade.ts
  5. Joins locally (generates Ed25519 keypair, stores in ~/.claudemesh/keys/<slug>.key)

Flags:

  • --template <name> — use a mesh template (reuses templates/ directory ported from v1)
  • --description <text> — optional description

Backend requirement: POST /api/my/meshes must exist. It may already exist (v1 backend has mesh creation endpoints per member-api.ts). If not, adding it is ~50 lines on top of existing schema patterns.

5.6 claudemesh invite [email]

Generates an invite URL for the current mesh, optionally sending email.

$ claudemesh invite

  ✔ Invite URL copied to clipboard:
    https://claudemesh.com/i/AB12CD34

  Expires in 7 days. Anyone with this link can join "platform-team".

With email:

$ claudemesh invite alice@example.com

  ✔ Sent to alice@example.com
  ✔ Also copied to clipboard

Implementation:

  1. Resolves current mesh (via services/mesh/resolve-target.ts)
  2. POSTs /api/my/meshes/:slug/invites with { email?, expires_in?: "7d", role?: "member" }
  3. Receives { url, code, expires_at }
  4. Copies to clipboard via services/clipboard/facade.ts
  5. If email provided, backend sends an invitation email (reuses existing transactional email path in the web app)

Flags:

  • --mesh <slug> — target mesh (default: current)
  • --expires <dur> — expiry (default: 7d)
  • --uses <n> — max uses (default: unlimited)
  • --role <role> — role for the invitee (default: member)

Backend requirement: POST /api/my/meshes/:slug/invites must exist. v1 has the inverse flow (POST /api/public/invites/:code/claim). Generating an invite is presumably already there since the dashboard has an "Invite" button — this just exposes it over the CLI.

5.7 claudemesh connect telegram

Interactive wizard that picks a mesh, requests a connect token from the broker, and shows a QR code + t.me deep link for the user to scan. Replaces v1's silent auto-pick of config.meshes[0] (which was a multi-mesh footgun).

$ claudemesh connect telegram

  Connect Telegram to a mesh
  ──────────────────────────

  Select mesh:
   1) platform-team      (owner, 4 members)
    2) alejandro-mbp      (owner, 1 member)
    3) claudefarm         (guest, 12 members)

  [enter to confirm, ↑↓ to change, q to cancel]

  ✔ Token generated for "platform-team"

  Scan with your phone camera, or tap the link:

    █▀▀▀▀▀█ ▀▀ █▄▄▀ █▀▀▀▀▀█
    █ ███ █ ██▄▄▄▄▀ █ ███ █
    ...

    https://t.me/claudemeshbot?start=abc123…

  Waiting for confirmation from Telegram… (Ctrl-C to skip)

  ✔ Connected as @alejandro_m
    Messages to tg:alejandro_m now route to Telegram.

Implementation:

  1. Loads joined meshes from services/config/facade.ts. If zero → error with claudemesh join <url> hint. If one → skip the picker. If multiple → render Ink mesh picker in ui/telegram/ConnectWizard.tsx.
  2. Converts the chosen mesh's brokerUrl from wss:// to https://, POSTs {meshId, memberId, pubkey, secretKey} to <broker>/tg/token.
  3. Broker returns {token, deepLink}. Wizard renders qrcode-terminal ASCII block + the raw link so users on desktop can copy-paste.
  4. Opens a short-lived WS subscription on the chosen mesh and waits for a telegram_bridge_connected push (new broker event — see §5.7.1 below). If received within 5 minutes, prints the success line with the resolved tg:<username>.
  5. On Ctrl-C, prints The link stays valid. Run 'claudemesh connect telegram --status' later to check. and exits 0.

Flags:

  • --mesh <slug> — skip the picker, target a specific mesh
  • --link — print only the deep link, no QR, no wait (for scripting)
  • --status — check whether the current mesh already has a Telegram bridge registered, without generating a new token

Broker dependencies to verify before Phase 5:

  1. Token TTL/tg/token currently returns {token, deepLink}. If the broker knows the TTL (it should — generateTelegramConnectToken encodes an expiry), extend the response to {token, deepLink, expiresAt}. This is a 3-line broker change and is the only broker touch permitted in Pass 1. Alternatively, the wizard just prints "link stays valid for a while" without a specific duration.
  2. Bridge-connected push event — if the broker doesn't already emit a push when a Telegram bridge claims a token, the wizard falls back to polling GET /mesh/:id/members every 2s for a new tg:* member, with a 5-minute timeout. Polling is acceptable for Pass 1 — no broker change needed.
  3. Rate limit/tg/token is capped at 10 requests/hour/IP. Wizard must catch the 429 and print You've requested too many Telegram tokens in the last hour. Try again in a few minutes. instead of raw HTTP errors.

5.7.1 Optional broker edit (small, allowed)

The rule "broker unchanged in Pass 1" has one permitted exception: extending the /tg/token response with expiresAt. This is additive, unversioned, and costs three lines. If the v2 implementer judges even this too risky, the wizard simply omits the TTL display.

No other broker change is in scope.


6. Broker compatibility

6.1 The broker is unchanged

apps/broker/src/ stays exactly as it is in v1. No files are added, modified, or removed. The broker:

  • Listens on the same WS endpoint
  • Handles the same 85 WS message types
  • Serves the same 18 HTTP endpoints
  • Uses the same Postgres schema
  • Runs the same Neo4j, Qdrant, MinIO, Docker backends
  • Executes the same Telegram bridge (telegram-bridge.ts, 1711 lines, unchanged)
  • Applies the same rate limits, audit logging, status engine, priority delivery

Why: because there are no broker-side changes in scope, and touching the broker introduces risk without reward for Pass 1. Every Pass 2 improvement to the broker (role-per-mesh isolation, egress proxy, catalog audit, etc.) is deferred until its own decision point.

6.2 The CLI speaks v1's WS protocol

v2's services/broker/ws-client.ts is a port of v1's ws/client.ts. The port preserves:

  • Every WS message type's envelope shape
  • Every field name and type
  • The Ed25519 hello signature format
  • NaCl crypto_box envelope wrapping for send_message
  • Reconnect logic, message queue, request/response correlation

The parity test layer 2 (WS contract tests) verifies this empirically by capturing v1's envelope for each message type and asserting v2's envelope matches byte-for-byte (modulo nonces and timestamps).

6.3 HTTP API compatibility

v2's services/api/my.ts and services/api/public.ts call the same endpoints v1 uses. POST /cli-sync, POST /invites/:code/claim, POST /hook/set-status, POST /upload, GET /download/:id, etc. — all unchanged.

The new user flow endpoints (/api/auth/cli/device-code/*, POST /api/my/meshes, POST /api/my/meshes/:slug/invites, DELETE /api/my/sessions/current, GET /api/my/profile) are net new but live in the claudemesh.com web app (apps/web/), not the broker. They're ~200 lines of Next.js API routes total.

6.4 Backend services unchanged

Postgres, Neo4j, Qdrant, MinIO, Docker — all on their v1 configurations. No role changes, no schema changes, no hardening, no egress controls. These all stay as Pass 2 work.


7. Implementation phases

Phases are organized by what gets built in each step. No time estimates — each phase ships when its acceptance criteria are met.

Phase 0 — Scaffold

  • Create apps/cli-v2/ with the full source tree
  • All files exist as stubs throwing NotImplementedError with the relevant spec reference in the header
  • package.json, tsconfig.json, bunfig.toml, build.ts, .eslintrc.cjs, dependency-cruiser.config.js, biome.json all configured
  • Custom ESLint plugin tools/eslint-plugin-claudemesh/ scaffolded (3 rules)
  • Test files for every layer of the parity plan scaffolded (~300 files)
  • CI runs: lint green, type-check green, zero violations in dependency-cruiser + boundaries
  • Done when: scaffold compiles and tests run (even if most tests are NotImplementedError)

Phase 1 — Foundation layers

  • types/, constants/, utils/, locales/ fully implemented
  • Pure services (services/device/, services/clipboard/, services/spawn/, services/crypto/, services/i18n/, services/logger/, services/lifecycle/)
  • Unit tests for each (colocated)
  • No user-visible behavior yet
  • Done when: pure-layer unit tests all pass, 80%+ branch coverage on foundation services

Phase 2 — Config + state + API client

  • services/config/ reads + writes ~/.claudemesh/config.json with 0600 enforcement
  • services/state/last-used.ts persists mesh picker cache
  • services/api/client.ts typed fetch wrapper with retry
  • services/api/my.ts + services/api/public.ts endpoint helpers
  • Unit tests for each
  • Done when: config round-trips, API client calls a mock server

Phase 3 — Broker client (port from v1 ws/)

  • services/broker/ws-client.ts ports v1's ws/client.ts into the facade pattern
  • services/broker/envelope.ts and hello-sig.ts port v1's crypto primitives
  • All 85 WS message types have typed facade methods
  • Contract test layer (parity plan layer 2) goes green
  • Done when: every WS contract test passes against captured v1 envelopes

Phase 4 — MCP server (port from v1 mcp/server.ts)

  • mcp/server.ts + router.ts + 21 tool family files
  • Each tool family ports v1's handler logic unchanged, calling the new broker facade instead of v1's direct WS client
  • MCP tool parity test layer (parity plan layer 3) goes green
  • Done when: all 79 MCP tool parity tests pass

Phase 5 — Auth + CLI user flows

  • services/auth/ full implementation: device-code flow, token store, dashboard sync, callback listener, browser opener
  • commands/register.ts, commands/login.ts, commands/logout.ts, commands/whoami.ts
  • Backend work (if needed): add /api/auth/cli/device-code/* endpoints to apps/web/
  • commands/new.ts, commands/invite.ts — call the new backend endpoints
  • End-to-end test of full flow: register → login → new mesh → invite → logout
  • Done when: fresh user can register, create a mesh, invite a teammate, all from the CLI

Phase 6 — Mesh + invite + remaining commands

  • services/mesh/ — list, rename, leave, join, resolve-target (port from v1)
  • services/invite/ — generate, parse-url, claim (port from v1)
  • commands/join.ts, commands/list.ts, commands/rename.ts, commands/leave.ts, commands/peers.ts, commands/send.ts, commands/inbox.ts, commands/state.ts, commands/info.ts, commands/remember.ts, commands/recall.ts, commands/remind.ts, commands/profile.ts, commands/status.ts, commands/sync.ts
  • commands/install.ts, commands/uninstall.ts — port from v1 (writes to ~/.claude.json + ~/.claude/settings.json)
  • commands/doctor.ts — port from v1's 7 checks
  • Parity test layer (parity plan layer 1) goes green for all non-launch commands
  • Done when: every command in §4.1 main help works identically to v1

Phase 7 — UI flows (welcome wizard + launch)

  • ui/welcome/ — welcome wizard screens (port from v1 commands/welcome.ts)
  • ui/launch/ — launch flow screens (port from v1 commands/launch.ts interactive bits)
  • commands/welcome.ts → renders ui/welcome/WelcomeScreen
  • Bare claudemesh dispatches to welcome (first run) or launch (returning)
  • Parity test layer for first-run + launch journeys goes green
  • Done when: welcome wizard and launch flow work identically to v1

Phase 8 — Golden JSON + E2E tests

  • tests/golden/ — lock JSON output shape for every command that supports --json
  • tests/e2e/ — full journey tests against a real broker in testcontainers
  • E2E tests cover the 28 flows from parity plan §6
  • Done when: all e2e tests green on main, all golden tests green on PR

Phase 9 — Docs + ship

  • apps/cli-v2/README.md — usage, install, architecture overview
  • apps/cli-v2/CHANGELOG.md — v1.0.0 entry listing changes from v0.10.5
  • Root README.md updated to reference v2
  • docs/quickstart.md rewritten for v2 command surface
  • Migration notes: none (no users to migrate)
  • Done when: docs complete, all layers green, ready to ship

Phase 10 — Atomic swap + v1.0.0 publish

Single atomic commit:

rm -rf apps/cli && mv apps/cli-v2 apps/cli
git commit -m "v2: replace v1 CLI with the refactored v2 implementation"

Followed by:

cd apps/cli && pnpm publish --access public --no-git-checks
git tag v1.0.0
git push origin main --tags

No legacy preservation, no rollback window, no deprecation period. Clean break. (Feature is at apps/cli/ after the swap, not apps/cli-v2/.)


8. Acceptance criteria

v2 Pass 1 ships when all of these are true:

Test gates

  • Every test file in 2026-04-11-v2-parity-test-plan.md has a passing assertion (no NotImplementedError remaining)
  • Layer 1 parity: ~70 test files covering v1 inventory §12 regression checks — all green
  • Layer 2 contract: ~85 test files covering WS message types — all green against captured v1 envelopes
  • Layer 3 MCP tool parity: 79 test files covering every MCP tool — all green
  • Layer 4 e2e: 28 journey tests — all green against real broker in testcontainers
  • Layer 5 golden JSON: 12 test files locking --json output shapes — all green
  • Layer 6 facade units: colocated tests + boundary scanner — all green
  • Layer 7 port-forwarded v1 tests: crypto round-trip + invite parse — all green

Lint / type / structure gates

  • biome check — zero violations
  • tsc --noEmit — zero errors, strict mode
  • ESLint boundaries plugin — zero violations (facade pattern holds)
  • ESLint custom rules (no-index-reexport-internal, type-imports-count-as-edges, no-dynamic-service-imports) — zero violations
  • dependency-cruiser — zero circular imports, zero layer violations, zero v1 imports (no-v1-dependencies rule)
  • no-restricted-imports — zero violations

Coverage gates

  • services/*/*.test.ts — 80%+ branch coverage on non-broker services
  • services/broker/* — 70%+ branch coverage via integration tests (WS client is the bulk of this)
  • No file below 60% branch coverage without a documented reason in a PR comment

Build / ship gates

  • bun build.ts produces dist/entrypoints/cli.js and dist/entrypoints/mcp.js
  • Gzipped JS bundle < 1.2 MB (enforced in build.ts)
  • bin/claudemesh shebang works on macOS + Linux
  • pnpm publish dry-run passes
  • Published package installable via npm i -g claudemesh-cli@1.0.0-rc.1
  • Cold start < 400 ms on Apple M2 Pro (benchmarked in tests/bench/cold-start.bench.ts)

Behavioral gates (against v1 feature inventory §12)

  • First-run install works on a fresh machine
  • Welcome wizard shows on bare command with no config
  • Launch works on returning machine (bare + flag-first)
  • All 79 MCP tools dispatch correctly through the v2 MCP server
  • Status priority engine (hook > manual > jsonl) unchanged
  • Message queue priority delivery (now / next / low) unchanged
  • Cryptographic integrity (Ed25519 signatures, NaCl envelopes, AES-GCM files) unchanged
  • Scheduled reminders survive broker restart
  • URL watch detects changes + survives broker restart
  • Telegram bridge continues to work on the broker side (verified via e2e against broker directly, not via v2 CLI subcommand which is gone)
  • Dashboard sync (POST /cli-sync) works
  • Webhooks (POST /hook/:meshId/:webhookId) route external requests to mesh messages
  • Doctor's 7 checks all pass on a clean install

New behavior gates (net new in Pass 1)

  • claudemesh register creates a new claudemesh.com account from the CLI
  • claudemesh login completes the device-code flow
  • claudemesh logout revokes the session + clears local credentials
  • claudemesh whoami prints current identity
  • claudemesh new <name> creates a mesh via POST /api/my/meshes
  • claudemesh invite generates an invite via POST /api/my/meshes/:slug/invites
  • claudemesh invite <email> sends a transactional email (if backend supports)
  • Complete first-time user journey: register → new → invite → send → logout works end-to-end without opening the browser except for account signup email verification

Regression gates (nothing should break)

  • Zero previously-passing tests become failing
  • No --json output shape change for any command that supported --json in v1
  • Broker test suite (unchanged in Pass 1) stays green

When all gates are true, Pass 1 ships.


9. What gets renamed vs what gets rewritten

Files that are ports (move + update imports, preserve logic)

These files' business logic is preserved verbatim from v1. The only changes are folder location, import paths, and the addition of a facade wrapper.

v1 file v2 location Change
cli/src/ws/client.ts services/broker/ws-client.ts Import paths, facade wrap
cli/src/ws/manager.ts services/broker/manager.ts Import paths
cli/src/crypto/envelope.ts services/broker/envelope.ts Folder move only
cli/src/crypto/hello-sig.ts services/broker/hello-sig.ts Folder move only
cli/src/crypto/keypair.ts services/crypto/keypair.ts Facade wrap
cli/src/crypto/file-crypto.ts services/crypto/file-crypto.ts Facade wrap
cli/src/auth/callback-listener.ts services/auth/callback-listener.ts Folder move
cli/src/auth/open-browser.ts services/spawn/browser.ts Consolidated with launch spawn
cli/src/auth/pairing-code.ts services/auth/pairing-code.ts Folder move
cli/src/auth/sync-with-broker.ts services/auth/dashboard-sync.ts Folder move + facade wrap
cli/src/invite/parse.ts services/invite/parse-url.ts Folder move
cli/src/invite/enroll.ts services/invite/claim.ts Folder move
cli/src/lib/invite-v2.ts services/invite/parse-url.ts (merged) Consolidated
cli/src/state/config.ts services/config/ (split into read + write) Split + facade wrap
cli/src/mcp/server.ts (2139 lines) mcp/server.ts + mcp/router.ts + 21 tool family files Split by family
cli/src/mcp/tools.ts mcp/tools/*.ts — 21 family files Split by family
cli/src/templates/index.ts templates/ — 5 individual files Split by template name
cli/src/commands/launch.ts (775 lines) commands/welcome.ts + ui/launch/ + launch handler dispatched from bare command Split
cli/src/commands/welcome.ts commands/welcome.ts + ui/welcome/ Split
cli/src/commands/install.ts commands/install.ts Folder move, port ~538 lines
cli/src/commands/doctor.ts commands/doctor.ts + services/health/check-*.ts Split into health checks
cli/src/commands/join.ts commands/join.ts + services/mesh/join.ts Split into command + service
cli/src/commands/list.ts commands/list.ts + services/mesh/list.ts Split
cli/src/commands/leave.ts commands/leave.ts + services/mesh/leave.ts Split
cli/src/commands/peers.ts commands/peers.ts Folder move
cli/src/commands/send.ts commands/send.ts Folder move
cli/src/commands/inbox.ts commands/inbox.ts Folder move
cli/src/commands/state.ts commands/state.ts Folder move
cli/src/commands/info.ts commands/info.ts Folder move
cli/src/commands/memory.ts commands/remember.ts + commands/recall.ts Split into two commands
cli/src/commands/remind.ts commands/remind.ts Folder move
cli/src/commands/profile.ts commands/profile.ts Folder move
cli/src/commands/status.ts commands/status.ts Folder move
cli/src/commands/sync.ts commands/sync.ts Folder move
cli/src/commands/hook.ts commands/hook.ts Folder move
cli/src/commands/create.ts commands/new.ts Renamed (from create to new, matches spec)
cli/src/commands/seed-test-mesh.ts commands/seed-test-mesh.ts Folder move
cli/src/commands/connect-telegram.ts commands/connect-telegram.ts + ui/telegram/ConnectWizard.tsx Port + split: command stays thin, mesh picker + QR render live in Ink wizard (§5.7)
cli/src/tui/colors.ts ui/styles.ts Folder move, keep v1 values (Pass 2 redesigns)
cli/src/tui/index.ts ui/index.ts Folder move
cli/src/tui/screen.ts ui/screen.ts Folder move
cli/src/tui/spinner.ts ui/spinner.ts Folder move
cli/src/env.ts constants/urls.ts + constants/paths.ts (split) Consolidated into constants
cli/src/version.ts constants/version.ts Folder move
cli/src/logo-spinner.ts ui/logo-spinner.ts Folder move
cli/src/index.ts entrypoints/cli.ts + commands/index.ts Split entry point from command registry
cli/src/__tests__/crypto-roundtrip.test.ts tests/unit/crypto-roundtrip.test.ts Folder move
cli/src/__tests__/invite-parse.test.ts tests/unit/invite-parse.test.ts Folder move

Files that are NET NEW in Pass 1

v2 file Purpose
commands/register.ts Account creation via browser handoff
commands/login.ts Device-code OAuth
commands/logout.ts Revoke session
commands/whoami.ts Identity check
commands/invite.ts Generate invite (was dashboard-only in v1)
commands/rename.ts Rename a mesh (was dashboard-only in v1)
services/auth/device-code.ts Device-code flow orchestration
services/auth/token-store.ts ~/.claudemesh/auth.json R/W
services/auth/client.ts HTTP calls to new /api/auth/cli/* endpoints
services/auth/facade.ts Public auth API
services/mesh/create.ts POST /api/my/meshes
services/mesh/facade.ts Public mesh API
services/invite/generate.ts POST /api/my/meshes/:slug/invites
services/invite/send-email.ts Email delivery through backend
services/invite/facade.ts Public invite API
services/api/facade.ts Public HTTP API
services/config/facade.ts Public config API
services/state/last-used.ts Last-used mesh/name/role cache
services/state/facade.ts Public state API
services/device/facade.ts Device info for device-code registration
services/clipboard/facade.ts Clipboard read
services/spawn/facade.ts Claude + browser spawning
services/telemetry/facade.ts Usage event emission (opt-out)
services/health/facade.ts Doctor checks
services/update/facade.ts npm registry version poll
services/i18n/facade.ts String templates (no ICU in Pass 1)
services/lifecycle/facade.ts Service lifecycle manager
services/logger/facade.ts Structured logger
cli/argv.ts argv parsing
cli/print.ts stdout helpers
cli/structured-io.ts JSON output
cli/exit.ts Exit code + cleanup hooks
cli/update-notice.ts Update banner
cli/handlers/signal.ts SIGINT/SIGTERM
cli/handlers/error.ts Top-level error handler
cli/output/list.ts Plain-text list renderer
cli/output/peers.ts Plain-text peers renderer
cli/output/whoami.ts Plain-text whoami renderer
cli/output/version.ts Version output
tools/eslint-plugin-claudemesh/ Custom ESLint plugin (3 rules)
tests/helpers/v1-runner.ts Spawns v1 CLI for parity comparisons
tests/helpers/v2-runner.ts Spawns v2 CLI for parity comparisons
tests/helpers/wire-capture.ts Records WS envelopes
tests/helpers/mock-broker.ts In-memory broker
tests/helpers/real-broker.ts Testcontainers broker harness

Files that are DELETED (no v2 replacement)

v1 file Why deleted
cli/src/commands/disconnect-telegram.ts Bridge teardown is handled inside Telegram (/revoke in the bot) or by leaving the mesh; a CLI wrapper is purely cosmetic

Files that are NEW in the backend (apps/web/)

File Purpose
apps/web/src/app/api/auth/cli/device-code/new/route.ts Issue device code
apps/web/src/app/api/auth/cli/device-code/[code]/route.ts Poll device code status
apps/web/src/app/api/auth/cli/device-code/[code]/approve/route.ts Approve from browser
apps/web/src/app/[locale]/cli-auth/page.tsx Browser-side approval UI
apps/web/src/app/api/my/meshes/route.ts POST = create mesh (may already exist)
apps/web/src/app/api/my/meshes/[slug]/invites/route.ts POST = generate invite (may already exist)
apps/web/src/app/api/my/profile/route.ts GET = whoami (may already exist)
apps/web/src/app/api/my/sessions/current/route.ts DELETE = logout

These are small Next.js API routes (~50 lines each) that wrap Better Auth's session management. The exact set depends on what already exists in the web app — an audit is part of Phase 5.


10. Decisions locked in by this spec

If you want to change any of these, update this spec and run the parity tests again.

  1. CLI command surface is flat. No advanced prefix. No launch subcommand. No connect telegram / disconnect telegram. The main --help groups commands visually but the namespace is flat.

  2. Pass 1 preserves every v1 feature in the inventory. No feature cuts, no "while we're at it, let's also change X." The only removals are CLI subcommands that don't add value (launch, connect-telegram, disconnect-telegram).

  3. The broker is unchanged in Pass 1. No files added, modified, or removed. All broker improvements are deferred to Pass 2.

  4. Facade pattern is mandatory. Every service has a facade.ts as the single public entry point. ESLint boundaries enforce this.

  5. No Pass 2 features sneak in. No local SQLite, no Lamport, no sync daemon, no hardened Postgres, no MCP catalog tiering, no ICU, no accessibility matrix, no session_kind enum, no six-color palette redesign, no facade bypass path closures beyond what exists in the Pass 2 facade spec.

  6. No backwards compatibility hedges. There are no users to migrate. v2 picks the best names, shapes, and conventions without alias support or deprecation windows.

  7. The parity test suite is the acceptance criteria. Not vibes, not "looks good to me," not code review alone. A red test means not-done.

  8. The broker's WS protocol is the only compatibility contract that matters. Everything above that layer (command names, config shape, JSON output, env vars, exit codes, stdout text) can change freely.

  9. Atomic swap, clean break. When Pass 1 is ready, a single commit replaces apps/cli with apps/cli-v2. No legacy preservation.

  10. Pass 1 is done when the acceptance criteria in §8 are all green. Nothing more, nothing less.


End of Pass 1 spec.