Commit Graph

480 Commits

Author SHA1 Message Date
Alejandro Gutiérrez
5ceb311d74 feat(cli): default-aggregation for topic list + notification list
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
ships v0.5.0 phase 1.

omitting --mesh on these read verbs now routes through
/v1/me/topics and /v1/me/notifications instead of prompting
the user to pick a mesh. behavior preserved for explicit
--mesh foo.

implementation: resolveMeshForMint helper in commands/me.ts
silently picks the first joined mesh for apikey-mint when
flags.mesh is null. /v1/me/* endpoints resolve the user from
the apikey issuer regardless of which mesh issued the key, so
mint location is irrelevant — only the user identity matters.

help text updated to reflect the new default.

phase 2 (task list, state list, memory recall) needs /v1/me/*
aggregator endpoints first; deferred.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 04:56:33 +01:00
Alejandro Gutiérrez
e60980cfd7 feat(workspace): claudemesh me search + dashboard parity
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
ships v0.4.0 phase 5 — final aggregating verb. v0.4.0 substrate
is complete after this.

api: GET /v1/me/search?q=... matches against topic names +
sender display names + v1 message snippets (base64 decode then
ilike). v2 ciphertext matches only on topic/sender — server has
no topic keys. 30-day window on messages, capped at 50 hits per
category.

cli (1.14.0): claudemesh me search <query> renders topic + msg
sections with inline yellow highlighting. min 2 chars; --json
returns the raw response.

web: /dashboard/search adds an autofocused input + mark
highlighting on every match site (topic name, sender, snippet).
sidebar gets a search entry between activity and invites.

roadmap: phase 5 marked shipped, v0.5.0 default-aggregation
behavior added as the natural next track.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 04:45:54 +01:00
Alejandro Gutiérrez
ff3d11d42d feat(workspace): claudemesh me activity + dashboard parity
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
ships v0.4.0 phase 4. final aggregating verb after this is
me search (phase 5).

api: GET /v1/me/activity returns topic messages across every
mesh the user belongs to in a 24h default window (?since=iso
override), excluding messages the caller authored themselves.
"what is happening that i missed", capped at 200.

cli (1.13.0): claudemesh me activity prints a condensed feed
with mesh + topic + sender + relative timestamp + snippet (or
[encrypted] for v2 ciphertext).

web: /dashboard/activity clusters consecutive messages from the
same topic into thread blocks for readability. sidebar gains an
activity entry between notifications and invites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 04:35:52 +01:00
Alejandro Gutiérrez
43e429f204 feat(workspace): claudemesh me notifications + dashboard parity
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
ships v0.4.0 phase 3.

api: GET /v1/me/notifications aggregates the mesh.notification
table across every joined mesh in a 7-day window (?since=iso
overrides, ?include=all surfaces already-read). returns sender +
topic + mesh context plus a 240-char snippet for v1 plaintext
messages or raw ciphertext for v2 (the dashboard topic-key cache
decrypts client-side).

cli (1.12.0): claudemesh me notifications — terse unread feed
with @ dot, --all to include read, --since for custom window.

web: /dashboard/notifications mirrors the cli view in card form,
adds a notifications entry to the dashboard sidebar between
topics and invites. each card links straight to the topic chat.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 02:35:57 +01:00
Alejandro Gutiérrez
1c335e8daa ci(web): auto-deploy claudemesh-web to coolify on push to main
new workflow joins the tailnet via tailscale oauth then triggers
the coolify deploy endpoint. path filter scoped to web app + every
package transpiled into it, so broker/cli/docs changes skip it.
concurrency group coalesces rapid pushes.

requires three repo secrets: COOLIFY_TOKEN, TS_OAUTH_CLIENT_ID,
TS_OAUTH_SECRET (the OAuth client needs the devices:write scope and
the tag:ci tag in tailnet ACL tagOwners).

inline coolify token removed from CLAUDE.md — it now references
the repo secret. broker deploy is unchanged: it runs through the
gitea-vps webhook.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 02:06:29 +01:00
Alejandro Gutiérrez
397ddb4c45 docs: mark v0.4.0 phase 2 shipped + record web deploy trick
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
roadmap entry for the me-topics + dashboard-topics ship.

claude.md gets the long-overdue note that apps/web is on coolify
on the ovh vps, not vercel — it does not auto-deploy on push to
gitea-vps the way the broker does, and that mismatch cost a
session of debugging. records the manual deploy command so the
next time we ship a web change we don't rediscover the issue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 01:36:05 +01:00
Alejandro Gutiérrez
354c47c3d6 chore: remove diagnostic endpoint + debug probe scaffolding
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
restores api + cli to clean state after isolating the v0.4.0
phase 2 deploy issue (web app needed an explicit coolify deploy
trigger — it does not auto-deploy from gitea-vps push the way the
broker does).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 01:35:08 +01:00
Alejandro Gutiérrez
2262564680 chore(api): rename diagnostic to a unique path to defeat any stale routing cache 2026-05-03 01:28:36 +01:00
Alejandro Gutiérrez
c18891191e chore(api): add /v1/me/ping sanity probe
confirms whether new GET routes under /me/* deploy correctly to
vercel — diagnostic in the middle of the /me/topics 404 chase.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 01:26:58 +01:00
Alejandro Gutiérrez
eb021a8a6f chore: trigger vercel rebuild 2026-05-03 01:16:58 +01:00
Alejandro Gutiérrez
3964de4962 fix(api): use notInArray + inArray in unread-count subqueries
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
the sql.join() form of NOT IN crashed the route handler before
it could respond — vercel surfaced the crash as a plaintext 404
instead of going through hono's exception handler. switching to
drizzle's notInArray() / inArray() emits stable parameter
bindings and resolves both /v1/me/topics (fresh endpoint) and
/v1/topics (older endpoint with the same ANY() pattern bug).

also cleans up debug instrumentation that was added while
chasing the 404.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 01:05:42 +01:00
Alejandro Gutiérrez
c795df4fd4 feat(workspace): claudemesh me topics + dashboard topics page
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
ships v0.4.0 phase 2: a cross-mesh topic feed.

api: GET /v1/me/topics aggregates topics across every mesh the
caller belongs to with per-topic unread counts (vs the user's
member-row last_read_at) and last-message timestamps. Sorted by
last activity.

cli (1.11.0): claudemesh me topics renders the feed; --unread
filters to topics with pending reads; --json returns raw.

web: /dashboard/topics ssr's the same view server-side (direct
db queries, no apikey-mint roundtrip) and adds a Topics entry
to the dashboard sidebar between Meshes and Invites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:39:58 +01:00
Alejandro Gutiérrez
aa6c7be4eb build(sdk): add exports.bun condition pointing at src for compile
bun build --compile in the cli release workflow couldn't resolve
@claudemesh/sdk because dist/ never gets built (--ignore-scripts).
adding exports.bun -> ./src/index.ts lets bun consume the typescript
sources directly while npm consumers keep using dist/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:04:35 +01:00
Alejandro Gutiérrez
3da06d357e docs(roadmap): mark v0.4.0 phase 1 shipped (claudemesh me)
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
split the v0.4.0 entry into phase 1 (the me/workspace endpoint
+ verb that just shipped in CLI 1.10.0) and phase 2+ (remaining
me topics/notifications/activity/search verbs).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:03:22 +01:00
Alejandro Gutiérrez
075df6db08 fix(api): correct online count in /v1/me/workspace
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
count distinct members with disconnectedAt is null instead of
all presence rows — a member can have many sessions, plus stale
rows from prior runs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:53:12 +01:00
Alejandro Gutiérrez
c7ce92f35b fix(api): use inArray for /v1/me/workspace mesh-id filters
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
drizzle's sql template literal interpolated meshIds as a tuple
(($1, $2, $3, ...)) instead of an array, breaking ANY() and
returning HTTP 500. inArray() emits the right binding shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 23:46:50 +01:00
Alejandro Gutiérrez
7de13cbb71 feat(cli): claudemesh me — cross-mesh workspace overview (v0.4.0)
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
2026-05-02 23:35:01 +01:00
Alejandro Gutiérrez
ad70782171 feat(api): cross-mesh workspace overview endpoint at /v1/me/workspace
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
2026-05-02 23:31:44 +01:00
Alejandro Gutiérrez
646d4fa3f1 fix(ui): chat footer reflects per-topic encryption state
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
2026-05-02 23:24:49 +01:00
Alejandro Gutiérrez
7f6af0137d feat(api+web): browser claims + re-seals encryption on v1 topics
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
Closes the last gap from phase 3.5: web-created topics start as v1
plaintext (mutations.ts ensureGeneralTopic doesn't generate a key,
because the dashboard owner has a throwaway pubkey with no secret).
Once the browser identity is registered via /v1/me/peer-pubkey, the
chat panel can lazily upgrade the topic to v2.

API (POST /v1/topics/:name/claim-key)
- Atomic claim: only succeeds when topic.encrypted_key_pubkey IS
  NULL. Body carries the new senderPubkey + the caller's sealed copy
  of the freshly-generated topic key. Race losers get 409 with the
  winning senderPubkey so they fall through to the regular fetch
  path. Idempotent at topic_member_key level.

Web
- claimTopicKey() in services/crypto/topic-key.ts: generates a fresh
  32-byte symmetric key, seals for self, POSTs the claim. Returns
  the in-memory key so the caller can encrypt immediately without a
  follow-up GET /key round-trip.
- sealTopicKeyFor(): mirrors the CLI helper so a browser holder can
  re-seal for newcomers (CLI peers, other browsers) instead of the
  topic going dark when only a browser has the key.
- TopicChatPanel: when keyState === "topic_unencrypted", composer
  now shows a "🔓 plaintext (v1) — encryption not yet enabled" line
  with an "enable encryption" button. Click → claimTopicKey → state
  flips to "ready" → 🔒 v0.3.0 banner appears. On race-lost, falls
  through to fetch.
- New 30s re-seal loop fires while holding the key: polls
  /pending-seals, seals via sealTopicKeyFor for each pending target,
  POSTs to /seal. Same cadence + soft-fail discipline as the CLI.

Net effect: any dashboard user can convert legacy v1 topics to v2
with a single click, and CLI peers joining later will receive a
sealed copy from the browser's re-seal loop without manual action.
2026-05-02 23:22:26 +01:00
Alejandro Gutiérrez
2e57173ed9 fix(api): /v1/me/peer-pubkey only updates web-managed members
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
Adds a 409 not_web_member guard to POST /v1/me/peer-pubkey: the
endpoint will only rewrite peer_pubkey on members that have
dashboard_user_id set. CLI members own their on-disk keypair —
overwriting their stored peer_pubkey would break the next WS hello
because the signature verification would fail against the new
pubkey.

In practice this restriction is invisible to the legitimate browser
flow: the dashboard always mints its apikey against the web member
(dashboard_user_id is non-null by construction in mutations.ts).
Guard ensures a misuse (e.g. a CLI-minted apikey being used to call
peer-pubkey) gets a clear 409 instead of silently breaking the CLI's
auth.

Discovered during phase 3.5 smoke when a CLI-minted apikey clobbered
the only openclaw member (CLI-owned) and the user's CLI signature
would have stopped verifying on the next launch.
2026-05-02 23:08:50 +01:00
Alejandro Gutiérrez
95b16a23fc docs(roadmap): mark v0.3.0 phase 3.5 (web encryption) shipped
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
2026-05-02 22:59:33 +01:00
Alejandro Gutiérrez
a3cf9b938e feat(web+api): browser-side per-topic encryption (v0.3.0 phase 3.5)
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
Closes the v1-vs-v2 split between CLI and dashboard. The web chat
panel now reads and writes the same crypto_secretbox-under-topic-key
ciphertext that CLI 1.8.0+ writes — every encrypted topic finally
renders correctly from the browser.

API
- POST /v1/me/peer-pubkey replaces the throwaway pubkey that
  mutations.ts mints at mesh-create time with one whose secret the
  browser actually holds. Idempotent; auth via the dashboard apikey
  whose issuedByMemberId is the row to update.

Web
- apps/web/src/services/crypto/identity.ts — IndexedDB-backed
  ed25519 identity, lazy-init on first use. Generates once per
  browser-profile; survives reload. ed25519 → x25519 derivation for
  crypto_box decrypt. Module-cached after first call.
- apps/web/src/services/crypto/topic-key.ts — mirrors the CLI
  topic-key service. Fetches GET /v1/topics/:name/key, decrypts the
  sealed copy with our x25519 secret, caches the 32-byte symmetric
  key in-memory keyed by (apikey-prefix, topic). encryptMessage /
  decryptMessage map directly onto crypto_secretbox{,_open}.
- apps/web/src/modules/mesh/topic-chat-panel.tsx — on mount:
  registers our pubkey, fetches the topic key, polls /key every 5s
  while not_sealed (matching the CLI's 30s re-seal cadence). Render
  branches on bodyVersion: v2 -> decrypted-cache, v1 -> legacy
  base64. Send branches: encrypts under the topic key when key is
  ready, falls back to v1 plaintext on legacy or not-yet-sealed
  topics. Composer shows a 🔒 v0.3.0 / "waiting for re-seal" badge.

Adds libsodium-wrappers + @types to apps/web. Browser bundle picks
up its own copy; the existing CLI/broker/API copies are untouched.

Threat model: IndexedDB is per-origin and not exfiltratable from
other sites; XSS or a malicious extension still wins, same as for
any browser-stored secret. Documented divergence from the CLI's
~/.claudemesh-stored keypair in the identity module's preamble.
2026-05-02 22:59:08 +01:00
Alejandro Gutiérrez
ce321c0a21 docs(skill): add Windows pane-spawn primitives for launch
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
Adds Windows Terminal (wt.exe new-tab + split-pane), PowerShell
Start-Process, cmd.exe start, and WSL routing examples to the
"Spawning new sessions" section. Plus the platform's gotchas:
single-quote nesting in cmd.exe, -NoExit semantics, WSL ~/.claudemesh
path-vs-host divergence, and pwsh / --profile selectors for Windows
Terminal. Bumps CLI to 1.9.5.
2026-05-02 22:48:16 +01:00
Alejandro Gutiérrez
9ecf2d65af docs(skill): wizard-free launch patterns for spawning peer sessions
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
Adds a "Spawning new sessions (no wizard)" section to the bundled
claudemesh skill. Documents every flag of `claudemesh launch`
(--name, --mesh, --join, --groups, --role, --message-mode,
--system-prompt, --resume, --continue, -y, -q, plus -- pass-through),
shows wizard-free spawn templates from minimal to cold-start-with-
join, and the canonical pane-creation primitives (tmux send-keys,
iTerm2 osascript, Terminal.app, gnome-terminal, screen) that wrap
the verb when spawning into a fresh terminal pane or window.

Closes the gap where Claude knew the verb existed but had no
playbook for "how do I start another peer in a new pane without an
interactive prompt firing." Bumps CLI to 1.9.4 so the skill ships
on `claudemesh install`.
2026-05-02 22:44:00 +01:00
Alejandro Gutiérrez
80755dbf9b feat(cli+broker): structured argument validation, msg-status prefixes (v1.9.3)
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
Adds apps/cli/src/cli/validators.ts — a small module of shape
validators (pubkey, pubkey prefix, message id, mesh slug) that return
discriminated results so callers can distinguish "shape is wrong"
(INVALID_ARGS exit) from "value is well-shaped, lookup failed"
(NOT_FOUND exit). Includes renderValidationError() for a consistent
three-tier error contract: what's wrong, what would be valid, closest
valid alternative.

First adopter is `claudemesh msg-status`:
- Validates id locally before opening WS — typos return immediately.
- Accepts 8-32 char prefixes (full ids are 32). Pastes that get
  copy-truncated by the terminal still work.
- Distinct error messages for malformed input vs not-in-queue vs
  ambiguous prefix; --json emits the structured shape.

Broker side: WS message_status handler validates idStr is 8-32
base62 before querying. Prefix lookups use LIKE 'prefix%' scoped to
the caller's mesh (no cross-mesh leak). Returns ambiguous_prefix
when more than one match.

Establishes the canonical pattern; rolling out to send / grant /
revoke / topic post --reply-to in subsequent patches.
2026-05-02 22:40:45 +01:00
Alejandro Gutiérrez
82ee89d0dc feat(cli+docs): colorize --help output + workspace view spec
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
Help text was a wall of monochrome ASCII. Now section headers print
bold-clay, the program title is brand-orange, each verb's syntax is
tinted cyan, and `(alias: ...)` parentheticals are dimmed so they
read as secondary metadata. The styles helper already gates on TTY +
NO_COLOR, so non-interactive output stays unchanged.

Adds .artifacts/specs/2026-05-02-workspace-view.md — the v0.4.0
spec for a per-user virtual workspace that aggregates reads across
all joined meshes while keeping writes mesh-scoped. Roadmap entry
added under v0.3.0.
2026-05-02 22:28:46 +01:00
Alejandro Gutiérrez
8697c1c032 fix(api+cli): topic post messageId is the durable historyId (v1.9.2)
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
Previously POST /v1/messages returned the message_queue row id as
`messageId`. Topic posts ARE durable (in topic_message); the queue
entry drains on delivery. Pasting that id into `--reply-to` failed
because the broker validates parents against topic_message, not the
queue. Now `messageId` aliases `historyId` for topic posts; both
`historyId` and `queueId` remain available as explicit fields.

Roadmap and CLI README updated with v0.3.1 reply-to + v0.3.2
multi-session entries.
2026-05-02 22:10:13 +01:00
Alejandro Gutiérrez
716e674473 fix(broker+cli): multi-session DM routing + broadcast self-loopback (v0.3.2)
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
Two related bugs surfaced in multi-session production use of 1.8.0:

1. Replies via `claudemesh send <from_id>` rejected with "no connected
   peer for target" when the original sender's session had rotated
   (Claude Code restart, /resume). Root cause: from_id carried the
   ephemeral session pubkey, which disappears the moment the session
   ends. Fix: handleSend pre-flight now also resolves the target
   pubkey against the persistent meshMember table and routes to the
   owning member's live session(s); MCP push channel now sets from_id
   to the stable member pubkey and exposes the ephemeral one under
   from_session_pubkey.

2. Broadcast/* and @group sends loopback'd to the sender's *sibling*
   sessions (same member, different session keypair), surfacing a
   spurious "tampered or wrong keypair" decrypt warning on the
   sender's own inboxes. Fix: broadcast/group fan-out now skips by
   memberPubkey, not just by presence_id, so the entire sender member
   is excluded — direct sends keep per-presence skip so a member can
   still DM their own sibling session intentionally.

Push envelope now also carries senderMemberPubkey alongside
senderPubkey so any other client of the WS channel can choose the
right one.
2026-05-02 22:05:11 +01:00
Alejandro Gutiérrez
038a5b5bf7 feat(broker+api+cli): topic message reply-to threading (v0.3.1)
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
Adds a reply_to_id column (self-FK on topic_message) plus end-to-end
plumbing so a message can mark itself as a reply to a previous one in
the same topic.

- Schema: 0027_topic_message_reply_to.sql adds reply_to_id with
  ON DELETE SET NULL + index for backlink lookup.
- Broker: appendTopicMessage validates parent shares the topic, writes
  reply_to_id; topicHistory + topic_history_response surface it; WS
  push envelope now carries senderMemberId, senderName, topic name,
  reply_to_id, and message_id so recipients have everything they need
  to reply without a follow-up query.
- REST: POST /v1/messages accepts replyToId (validated server-side);
  GET /messages and SSE /stream emit it per row.
- CLI: \`topic post --reply-to <id|prefix>\` resolves prefixes against
  recent history; \`topic tail\` renders an "↳ in reply to <name>:
  <snippet>" line above replies and shows a copyable #shortid tag on
  every row.
- MCP push pipe: channel attributes now include from_pubkey,
  from_member_id, message_id, topic, reply_to_id — the recipient can
  thread a reply directly from the inbound notification.
- Skill + identity prompt updated to teach Claude how to use the new
  attributes for replies.

Bumped CLI to 1.9.0.
2026-05-02 21:58:21 +01:00
Alejandro Gutiérrez
d871988084 fix(broker): libsodium dynamic import — extract .default for bun
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
await import('libsodium-wrappers') returns the namespace object in
bun, not the sodium API. randombytes_buf et al. live on .default.
Without this, every topic_create on the deployed broker errored
with 'sodium.randombytes_buf is not a function' and the WS handler
silently dropped — CLI saw a 5s timeout.

Confirmed via broker docker logs:
  warn ws message error: sodium.randombytes_buf is not a function

Same destructure pattern as crypto.ts (which uses the synchronous
default import).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 21:15:37 +01:00
Alejandro Gutiérrez
3c35932191 docs(skill): cover topic tail/post + member list + notification list
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
Adds v1.7.0 (terminal parity) and v1.8.0 (per-topic encryption)
verbs to the bundled claudemesh skill so Claude Code sessions
discover them via the auto-installed SKILL.md instead of the
README-only path.

Sections added:
  - topic tail / topic post under the topic block
  - member resource (distinct from peer)
  - notification resource
  - per-topic encryption block — explains v2 ciphertext marker,
    re-seal flow, and 404 behaviour

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 21:12:55 +01:00
Alejandro Gutiérrez
b08daadbdc fix(broker): topic_create no longer rejects on creator-seal failure
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
A bad ed25519 pubkey on the creator member (legacy data) made
sealTopicKeyForMember throw, which propagated up through createTopic
and made the WS topic_create handler never send a topic_created
frame. CLI saw a 5s timeout and printed 'topic create failed'.

Wraps the seal call in try/catch — topic creation succeeds even if
no copy gets sealed for the creator. They'll see GET /v1/topics/:name/key
return 404 until they re-seal (or a holder does it for them via
the phase-3 background loop).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 21:11:55 +01:00
Alejandro Gutiérrez
cb5faca920 docs(roadmap): v0.3.0 phase 3 (CLI) shipped, phase 3.5 (web) added
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
CLI v1.8.0 on npm. Web stays on v1 plaintext pending the IndexedDB
identity work tracked as phase 3.5.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 21:03:47 +01:00
Alejandro Gutiérrez
77f4316f2d feat(broker+api+cli): per-topic E2E encryption — v0.3.0 phase 3 (CLI)
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
Wire format:
  topic_member_key.encrypted_key = base64(
    <32-byte sender x25519 pubkey> || crypto_box(topic_key)
  )

Embedding sender pubkey inline lets re-sealed copies (carrying a
different sender than the original creator-seal) decode the same
way as creator copies, without an extra schema column or join.
topic.encrypted_key_pubkey stays for backwards-compat metadata
but the wire truth is the inline prefix.

API (phase 3):
  GET  /v1/topics/:name/pending-seals  list members without keys
  POST /v1/topics/:name/seal           submit a re-sealed copy
  POST /v1/messages now accepts bodyVersion (1|2); v2 skips the
  regex mention extraction (server can't read v2 ciphertext).
  GET  /messages + /stream now return bodyVersion per row.

Broker + web mutations updated to use the inline-sender format
when sealing. ensureGeneralTopic (web) also generates topic keys
per the bugfix that landed earlier today; both producers now
share one wire format.

CLI (claudemesh-cli@1.8.0):
  + apps/cli/src/services/crypto/topic-key.ts — fetch/decrypt/encrypt/seal
  + claudemesh topic post <name> <msg> — encrypted REST send (v2)
  * claudemesh topic tail <name> — decrypts v2 on render, runs a
    30s background re-seal loop for pending joiners

Web client stays on v1 plaintext until phase 3.5 (browser-side
persistent identity in IndexedDB). Mention fan-out from phase 1
already works for both versions, so /v1/notifications keeps
working through the cutover.

Spec at .artifacts/specs/2026-05-02-topic-key-onboarding.md
updated with the implemented inline-sender format and the
phase 3.5 web plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 21:03:11 +01:00
Alejandro Gutiérrez
82ebd2b6be chore(broker): wire mentions through WS topic_send + dedupe imports
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
WSSendMessage gains an optional mentions field; the broker forwards
it into appendTopicMessage so WS-driven topic sends get the same
write-time fan-out path as REST POST /v1/messages. v1 messages
(today's plaintext-base64) still fall back to a body regex when the
field is omitted, so existing CLIs aren't broken; v2 ciphertext
clients in phase 3 will populate it.

Also drops the duplicate meshMember import (kept the meshMember-as-
memberTable alias which the rest of the file uses).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 20:45:57 +01:00
Alejandro Gutiérrez
b70536195a fix(api): ensureGeneralTopic generates a topic key + seals for owner
The web mesh-creation path went straight through db.insert(meshTopic)
and bypassed the broker's createTopic, so the v0.3.0 phase-2 key
generation never ran for #general topics created via the dashboard.
Result: GET /v1/topics/general/key returned 409 topic_unencrypted
on every web-created mesh.

Mirrors the broker's createTopic flow inline: generate a 32-byte
topic key + ephemeral x25519 sender keypair, persist the public
half on topic.encrypted_key_pubkey, seal a copy for the oldest
non-revoked member (the owner — owner-as-member rows are minted
at mesh creation per a prior fix), and let the topicKey leave
memory.

Existing meshes with already-created (and unencrypted) #general
topics aren't backfilled; they stay v0.2.0 plaintext until the
phase 3 client encrypt path lands. New meshes get encrypted
topics from this commit forward.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 20:44:26 +01:00
Alejandro Gutiérrez
39929eb7fe docs(roadmap): expand v0.3.0 per-topic encryption into three phases
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
Phase 1 (notification table) and phase 2 (schema + creator seal)
shipped today. Phase 3 (member-driven re-seal + client-side
encrypt/decrypt) is the cut that actually flips the broker to
ciphertext-only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 20:28:37 +01:00
Alejandro Gutiérrez
da5103a315 feat(broker+api): per-topic symmetric keys — schema + creator seal
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
Phase 2 (infra layer) of v0.3.0. Topics now generate a 32-byte
XSalsa20-Poly1305 key on creation; the broker seals one copy via
crypto_box for the topic creator using an ephemeral x25519
sender keypair (whose public half lives on
topic.encrypted_key_pubkey). Topic key plaintext leaves memory
immediately after the creator's seal — the broker can't read it.

Schema 0026:
  + topic.encrypted_key_pubkey (text, nullable for legacy v0.2.0)
  + topic_message.body_version  (integer, 1=plaintext / 2=v2 cipher)
  + topic_member_key            (id, topic_id, member_id,
                                 encrypted_key, nonce, rotated_at)

API:
  + GET /v1/topics/:name/key — return the calling member's sealed
    copy. 404 if no copy exists yet (joined post-creation, no peer
    has re-sealed). 409 if the topic is legacy unencrypted.

Open question parked: how new joiners get their sealed copy
without ceding plaintext to the broker. Spec at
.artifacts/specs/2026-05-02-topic-key-onboarding.md picks
member-driven re-seal (Option B). Pending-seals endpoint, seal
POST, and the actual on-the-wire encryption ship in phase 3.

Mention fan-out from phase 1 (notification table) is decoupled
from ciphertext, so /v1/notifications + MentionsSection keep
working unchanged through both phases.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 20:28:10 +01:00
Alejandro Gutiérrez
1a238d4178 feat(api+broker+web): write-time mention fan-out via notification table
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
Phase 1 of v0.3.0 — replaces the regex-on-decoded-ciphertext scan
in /v1/notifications and the dashboard MentionsSection with reads
from a new mesh.notification table populated at write time.

Schema 0025: mesh.notification (id, mesh_id, topic_id, message_id,
recipient_member_id, sender_member_id, kind, created_at, read_at)
with a unique (message_id, recipient) so a re-fanned message yields
one row per recipient. Backfills existing v0.2.0 messages by
regex-matching the (still-base64-plaintext) bodies — guarded with
a base64 + length check so binary ciphertext doesn't crash the
migration.

Writers (POST /v1/messages + broker appendTopicMessage) now
extract @-mentions from either an explicit `mentions: string[]`
on the request OR a regex over the base64 plaintext (transitional
fallback). Targets are intersected with the mesh roster + capped
at 32 per message. Web chat panel sends the explicit array now so
it keeps working after phase 2 lands.

Readers switch to JOIN-on-notification:
  /v1/notifications      — table-backed, supports ?unread=1
  POST /v1/notifications/read  — new, mark by ids or all-up-to
  MentionsSection (RSC) — same JOIN, returns readAt for each row

GET /v1/notifications also gains a read_at field per row so a
future bell UI can show unread vs read.

Once per-topic encryption (phase 2) lands, the regex fallback
becomes a no-op for v2 messages — clients MUST send `mentions`,
which they already do.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 20:23:50 +01:00
Alejandro Gutiérrez
81f8066f99 docs(roadmap): mark v1.7.0 CLI parity shipped
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
Adds the terminal verbs (topic tail / member list / notification
list) explicitly to v1.7.0 so the demo cut summary matches what's
on npm.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 20:02:59 +01:00
Alejandro Gutiérrez
dd80d4e946 feat(cli): v1.7.0 — terminal parity for SSE + members + mentions
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
Three new verbs that wrap the v1.6.x REST surface:

  claudemesh topic tail <name>  → live SSE consumer with N-message backfill
  claudemesh member list        → mesh roster decorated with online state
  claudemesh notification list  → recent @-mentions of you across topics

Each command auto-mints a 5-minute read-only apikey via the WS
broker and revokes on exit, so users don't manage tokens. SSE
client uses fetch + ReadableStream so the bearer stays in the
Authorization header.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 20:02:29 +01:00
Alejandro Gutiérrez
c31a591681 docs(handoff): 2026-05-02 evening — v1.6.x + v1.7.0 demo cut state
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
Companion to the morning handoff. Captures the 12 commits shipped
this evening, live deployment status, the CLI/UI surface gap, three
known risks (chiefly: mentions query depends on plaintext-base64
ciphertext + crashes on non-UTF8 bytes), and three branches for
the next session ranked by leverage: record the demo, wire CLI
verbs to the new endpoints, then v0.3.0 per-topic encryption.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:35:12 +01:00
Alejandro Gutiérrez
a2ab7de60a docs(marketing): refresh timeline 'what's next' for v2.0.0 + v0.3.0
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
Old next-block listed dashboard (shipped), slack bridge (still
v0.3.0), self-host (v0.3.0), SSO (out of scope). Replaces with
the actual roadmap horizon: daemon redesign, per-topic crypto,
self-host packaging, federation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:33:51 +01:00
Alejandro Gutiérrez
69cf39bc9f docs(blog+demo): v1.7.0 launch post + 90s demo script
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
Blog post "Agents and humans in the same chat" walks through what
shipped in the v1.7.0 demo cut: topics, REST gateway, real-time
SSE, mentions, notification feed, humans-as-peers. Linked from
the blog index above the original protocol post.

Demo script lays out a five-scene 90-second screen capture: two
terminal agents talking, dashboard topic list, live chat with
@-mention autocomplete, mentions feed cross-platform, close.
Production notes + distribution checklist included.

Marketing screenshots and the actual recording are still TODO.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:32:35 +01:00
Alejandro Gutiérrez
0ab2bea045 docs(roadmap): mark /v1/peers humans-as-peers as shipped
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
Bridge smoke test is the last remaining v1.6.x item.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:29:03 +01:00
Alejandro Gutiérrez
f4601f4d9c feat(api): humans-as-peers in /v1/peers
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
Recently-active apikey holders (used in the last 5 minutes) appear
in the peer list alongside WS-connected sessions. The dashboard
chat user now becomes visible to CLI peers calling list_peers,
closing the v1.6.0 humans-as-peers loop.

Presence rows take precedence when both exist; rest-only rows
get via:"rest" flag and idle status (no presence channel to
infer working/dnd from).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:28:47 +01:00
Alejandro Gutiérrez
a83133a4c6 docs(roadmap): mark v1.6.x SSE/unread + v1.7.0 sidebar/mentions/feed shipped
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
Updates v1.6.x and v1.7.0 sections with concrete endpoints + client
behaviour for what landed this session. Bridge smoke test and
/v1/peers humans remain open under v1.6.x.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:27:44 +01:00
Alejandro Gutiérrez
a9160a0965 feat(api+web): notification feed — recent @-mentions across meshes
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
Universe dashboard gets a "Recent mentions" section listing every
topic_message from the last 7 days that references the viewer via
`@<displayName>` (per-mesh — a user can carry different display
names in different meshes). One union'd OR query, capped at 20.

Each mention card links straight into the topic chat at the right
mesh. Snippet is the first 240 chars of the decoded ciphertext with
@-tokens highlighted in clay, matching the in-chat renderer.

GET /v1/notifications mirrors the same scan for api-key-authed
clients (CLI, bots) — accepts ?since=<ISO> for incremental polling.
Both paths use Postgres regex on the decoded base64 plaintext;
when per-topic encryption lands in v0.3.0 they'll move to a
notification table populated at write time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:26:02 +01:00
Alejandro Gutiérrez
00c25d9803 feat(web): client-side search filter in topic chat
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
A "search" toggle in the chat header opens a small input that
client-filters loaded messages by plaintext match on body or
sender name. Live tail auto-scroll suspends while a query is
active so matches stay visible when new messages arrive.

Server-side fulltext search lands when ciphertext moves to
per-topic symmetric keys in v0.3.0 — until then there's no
server index to query, and the loaded window (last 100 plus
forward stream) covers most "find that thing from earlier"
needs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 19:23:21 +01:00