chore: wrap up the gap-closing session
- 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>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# Feature request draft: rich `<channel>` notification UI
|
||||
|
||||
**Target:** `anthropics/claude-code` GitHub issues / feedback channel.
|
||||
**Drafted:** 2026-04-15.
|
||||
|
||||
Paste the section below once the issue template is ready. Adjust tone
|
||||
to match Claude Code's issue style.
|
||||
|
||||
---
|
||||
|
||||
### Title
|
||||
|
||||
Rich UI for `notifications/claude/channel` messages (first-class chat, not just reminders)
|
||||
|
||||
### Body
|
||||
|
||||
**Summary**
|
||||
|
||||
MCP servers can emit `notifications/claude/channel` notifications which
|
||||
Claude Code renders inside the current turn as a `<channel>` reminder.
|
||||
For MCP servers that are conversational in nature (peer messaging,
|
||||
collaborative sessions, delegated agents), rendering these inline as
|
||||
plain-text reminders misses the UX affordances users expect from chat:
|
||||
|
||||
- sender avatar / identity
|
||||
- timestamp
|
||||
- priority badge (urgent / normal / low)
|
||||
- expandable quote from the original thread
|
||||
- optional inline reply action that calls a specific MCP tool
|
||||
|
||||
**Concrete use case**
|
||||
|
||||
[claudemesh](https://claudemesh.com) is a peer mesh for Claude Code
|
||||
sessions. When a peer sends a message it arrives as
|
||||
`notifications/claude/channel` with structured metadata in `meta`:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "notifications/claude/channel",
|
||||
"params": {
|
||||
"content": "alice: can you rebase main before deploy?",
|
||||
"meta": {
|
||||
"from_id": "<ed25519 hex>",
|
||||
"from_name": "alice",
|
||||
"priority": "now",
|
||||
"sent_at": "2026-04-15T00:00:00Z",
|
||||
"mesh_slug": "team-platform",
|
||||
"kind": "direct"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Today this renders as a `<channel>` text block — useful, but the user
|
||||
can't tell at a glance that it's from another human.
|
||||
|
||||
**What we'd like**
|
||||
|
||||
A hint on the notification (e.g. `meta.display: "chat"`) that lets
|
||||
Claude Code render it as a chat bubble with the `from_name` as the
|
||||
speaker, priority visualised, and an optional "Reply" action bound to
|
||||
a declared MCP tool (`reply_tool_name`).
|
||||
|
||||
**Why users would benefit beyond claudemesh**
|
||||
|
||||
- Delegated agent frameworks can render sub-agent responses as chat
|
||||
- Live-pairing MCP servers get a proper UI without inventing their own
|
||||
- The existing `<channel>` fallback means older clients still see
|
||||
the same text — additive, not breaking
|
||||
|
||||
**Willing to contribute a PR** if the feature is on-roadmap.
|
||||
84
.artifacts/specs/2026-04-15-invite-v2-cli-migration.md
Normal file
84
.artifacts/specs/2026-04-15-invite-v2-cli-migration.md
Normal file
@@ -0,0 +1,84 @@
|
||||
# Invite v2 — CLI migration (server-side already shipped)
|
||||
|
||||
## Current state
|
||||
|
||||
**Server-side (broker) — DEPLOYED**
|
||||
- `canonicalInviteV2` bytes format (crypto.ts)
|
||||
- `verifyInviteV2` signature check
|
||||
- `claimInviteV2Core` at `POST /invites/:code/claim`
|
||||
- `sealRootKeyToRecipient` using crypto_box_seal
|
||||
- Every v1 invite also stores `capability_v2` for cross-compat
|
||||
- Web route `/api/public/invites/:code/claim` proxies 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
|
||||
|
||||
1. **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
|
||||
|
||||
2. **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
|
||||
|
||||
3. **Config schema** already has `rootKey` per mesh — just write from
|
||||
unsealed bytes instead of from the token payload.
|
||||
|
||||
4. **Spec test** `tests/golden/invite-v2.test.ts`
|
||||
- Broker already has `claimInviteV2Core` tests; add a CLI-side
|
||||
end-to-end that hits a local broker and verifies the sealed key
|
||||
round-trips.
|
||||
|
||||
## 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
|
||||
|
||||
1. Ship CLI v2 path behind `CLAUDEMESH_INVITE_V2=1` env.
|
||||
2. Dogfood: new invites generated by `claudemesh share` use `/api/public/invite-code/:code` with v2-shape response that omits token; CLI resolves via claim.
|
||||
3. Verify with `claudemesh verify` safety numbers cross-check.
|
||||
4. After 2 weeks uneventful, flip default to v2.
|
||||
5. After 60 days, stop embedding root_key in long URLs entirely.
|
||||
6. 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.
|
||||
Reference in New Issue
Block a user