Wires the v2 invite protocol end-to-end from a CLI user's perspective.
Broker foundation landed in c1fa3bc; this commit is the glue between
it and the human.
API (packages/api)
- createMyInvite now mints BOTH v1 token (legacy) AND v2 capability.
Two-phase insert: row first (to get invite.id), then UPDATE with
signed canonical bytes stored as JSON {canonical, signature} in the
capabilityV2 column. Broker's claim handler parses the same shape.
- canonicalInviteV2 locked to `v=2|mesh_id|invite_id|expires_at|role|
owner_pubkey_hex` — byte-identical to apps/broker/src/crypto.ts.
- brokerHttpBase() helper rewrites wss://host/ws → https://host for
server-to-server calls.
- POST /api/public/invites/:code/claim — thin proxy to broker;
passes status + body through, 502 broker_unreachable on fetch fail,
cache-control: no-store.
- POST /api/my/meshes/:id/invites/email — mints a normal v2 invite
via createMyInvite, records a pending_invite row, calls stubbed
sendEmailInvite (logs TODO for Postmark wiring in a later PR).
- New schemas: claimInviteInput/ResponseSchema,
createEmailInviteInput/ResponseSchema, v2 fields on
createMyInviteResponseSchema.
- v1 paths untouched — legacy /join/[token] and /api/public/invite/:token
continue to work throughout v0.1.x.
CLI (apps/cli)
- New `claudemesh join <code-or-url>` subcommand.
- Accepts bare code (abc12345), short URL (claudemesh.com/i/abc12345),
or legacy ic://join/<token>. Detects v2 vs v1 and dispatches.
- v2 path: generates fresh ephemeral x25519 keypair (separate from
the ed25519 identity) → POST /api/public/invites/:code/claim →
unseals sealed_root_key via crypto_box_seal_open → persists mesh
with inviteVersion: 2 and base64url rootKey to local config.
- Signature verification skipped with TODO — v0.1.x trusts broker;
seal-open is already authenticated.
- apps/cli/src/lib/invite-v2.ts: generateX25519Keypair, claimInviteV2,
parseV2InviteInput.
- state/config.ts: additive rootKey?/inviteVersion? fields.
CLI friction reducer
- apps/cli/src/index.ts: flag-first invocations
(`claudemesh --resume xxx`, `claudemesh -c`, `claudemesh -- --model
opus`) now route through `launch` automatically. Bare `claudemesh`
still shows welcome; known subcommands dispatch normally.
- Removes one word of cognitive load: users never type `launch`.
No schema changes. No new deps. v1 fully backward compatible.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
broker: expand member groups to ancestor paths at drain time (pull model)
- @flexicar message reaches peers in @flexicar/core, @flexicar/output, etc.
- Resolved at drainForMember — no DB changes, fully backward-compatible
- Any depth: flexicar/team/backend also matches @flexicar and @flexicar/team
cli: wire --role all the way through to session config + env
- Config.role field added
- launch.ts stores role in sessionConfig, passes CLAUDEMESH_ROLE env var
- mcp/server.ts includes role in identity string
- manager.ts auto-joins groups from config on WS connect (--groups flag now works)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
--inbox: count-only notifications, no content in context
--no-messages: tools only, zero prompt injection risk
Default: push (real-time, current behavior)
Wizard shows mode picker when no flag provided.
MCP instructions tell Claude its current mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase A of the claudemesh spec. Peers can now join named groups
with roles, and messages route to @group targets.
Broker:
- @group routing in fan-out (matches peer group membership)
- @all alias for broadcast
- join_group/leave_group WS messages + DB persistence
- list_peers returns group metadata
- drainForMember matches @group targetSpecs in SQL
CLI:
- join_group/leave_group MCP tools
- send_message supports @group targets
- list_peers shows group membership
- PeerInfo includes groups array
- Peer name cache for push notifications
Launch:
- --role flag (optional peer role)
- --groups flag (comma-separated, e.g. "frontend:lead,reviewers")
- Interactive wizard for role + groups when flags omitted
- Groups written to session config for broker hello
Spec: SPEC.md added with full v0.2 vision (groups, state, memory)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Write displayName into tmpdir config.json so the MCP server reads
it directly. Env vars from claudemesh launch may not propagate to
MCP child processes spawned by Claude Code. Config file is reliable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Zod schemas with plain TypeScript validation in env.ts,
config.ts, and invite/parse.ts. Zod 4 classes break under bun
build --target=node (Class2 is not a constructor).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
End-to-end join: user runs `claudemesh join ic://join/<base64>` and
walks away with a signed member record + persistent keypair.
new modules:
- src/crypto/keypair.ts: libsodium ed25519 keypair generation. Format
is crypto_sign_keypair raw bytes, hex-encoded (32-byte pub, 64-byte
secret = seed || pub). Same format libsodium will need in Step 18
for sign/verify.
- src/invite/parse.ts: ic://join/<base64url(JSON)> parser with Zod
shape validation + expiry check. encodeInviteLink helper for tests.
- src/invite/enroll.ts: POST /join to broker, converts ws:// to http://
transparently.
rewritten join command wires them together:
1. parse invite → 2. generate keypair → 3. POST /join → 4. persist
config → 5. print success.
state/config.ts: saveConfig now chmods the file to 0600 after write,
since it holds ed25519 secret keys. No-op on Windows.
signature verification (step 18) + invite-token one-time-use tracking
are deferred. For now the invite link is a plain bearer token; any
client with the link can join.
verified end-to-end via apps/cli/scripts/join-roundtrip.ts:
build invite → run join subprocess → load new config → connect as
new member → send A→B → receive push. Flow passes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>