- info/inbox commands → unified render.ts - install route: drop in-memory counter, rely on PostHog + structured logs - docs: roadmap, CLAUDE.md reflect alpha.31 state - tests workflow now also builds + smoke-tests the CLI bundle - homebrew tap bootstrap kit in packaging/homebrew-tap-bootstrap/ (README + copy of the formula template for dropping into the tap repo) - upstream Claude Code issue draft for rich <channel> UI Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3.6 KiB
Invite v2 — CLI migration (server-side already shipped)
Current state
Server-side (broker) — DEPLOYED
canonicalInviteV2bytes format (crypto.ts)verifyInviteV2signature checkclaimInviteV2CoreatPOST /invites/:code/claimsealRootKeyToRecipientusing crypto_box_seal- Every v1 invite also stores
capability_v2for cross-compat - Web route
/api/public/invites/:code/claimproxies to broker
Client-side (CLI) — NOT MIGRATED
The CLI still uses the v1 flow (enrollWithBroker) which reads
mesh_root_key from the invite token's base64 payload. This means:
- Long URL
/join/<token>contains the root key - Short URL
/i/<code>resolves to the long URL → still contains root key - Anyone who can read the URL (history, screenshot, mail archive) has the key
The v2 CLI flow
parseInviteLinkV2(url)
→ short URL /i/<code>? GET /api/public/invite-code/:code
→ returns `{ found, code, mesh_slug, broker_url, owner_pubkey,
canonical_v2, expires_at, role }` (NO root_key)
→ generate local x25519 keypair (curve25519)
→ POST /invites/<code>/claim { recipient_x25519_pubkey, display_name }
→ broker verifies capability_v2 signature
→ broker seals mesh.root_key with crypto_box_seal(root_key, our_pubkey)
→ returns { sealed_root_key, mesh_id, member_id, owner_pubkey, canonical_v2 }
→ open sealed_root_key with our x25519 secret key
→ store root_key in ~/.claudemesh/config.json.meshes[].rootKey
(NOT in the invite link — it was never transmitted unsealed)
→ upgrade enroll to use claim response instead of the /join endpoint
What needs to change in the CLI
-
New file
apps/cli/src/services/invite/parse-v2.ts- Detect short URL, resolve via
/api/public/invite-code/:code - Expect the API returns v2 shape (server already has this route; verify field names)
- Generate x25519 keypair via libsodium
- POST to claim endpoint
- Unseal root_key
- Detect short URL, resolve via
-
Conditional in
parseInviteLink- If URL is short-form and broker supports v2, use the new path
- Fall back to v1 for legacy long-form URLs in transit
-
Config schema already has
rootKeyper mesh — just write from unsealed bytes instead of from the token payload. -
Spec test
tests/golden/invite-v2.test.ts- Broker already has
claimInviteV2Coretests; add a CLI-side end-to-end that hits a local broker and verifies the sealed key round-trips.
- Broker already has
Why it wasn't rushed in this session
Crypto code deserves review. The server-side v2 shipped weeks ago
with its own testing and audit; the CLI migration needs the same
rigor — at minimum, a test that proves the sealed key we unseal
matches the root_key the broker had in its DB, verified against
canonical_v2 signature.
The current v1 flow is a known quantity (the root_key-in-URL risk is documented in the spec). Broker is already v2-ready so when the CLI migration lands, emails / links can immediately start using the claim-only short URL without a server deploy.
Rollout plan
- Ship CLI v2 path behind
CLAUDEMESH_INVITE_V2=1env. - Dogfood: new invites generated by
claudemesh shareuse/api/public/invite-code/:codewith v2-shape response that omits token; CLI resolves via claim. - Verify with
claudemesh verifysafety numbers cross-check. - After 2 weeks uneventful, flip default to v2.
- After 60 days, stop embedding root_key in long URLs entirely.
- v3 (future): short URL becomes the only form.
Effort
~1 day of focused crypto + testing. Broker work is done; API work is done; CLI work is a new parse path + a new enroll path + a few tests.