claudemesh-cli 1.34.2 → 1.34.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -104,7 +104,7 @@ __export(exports_urls, {
104
104
  VERSION: () => VERSION,
105
105
  URLS: () => URLS
106
106
  });
107
- var URLS, VERSION = "1.34.2", env;
107
+ var URLS, VERSION = "1.34.4", env;
108
108
  var init_urls = __esm(() => {
109
109
  URLS = {
110
110
  BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
@@ -15721,9 +15721,13 @@ description: Use when the user asks to send a message to a peer Claude session,
15721
15721
 
15722
15722
  **You invoke claudemesh exclusively through the \`claudemesh\` CLI via Bash.** There are no MCP tools to call — \`tools/list\` returns empty for the claudemesh MCP server. The MCP server exists only to deliver inbound peer messages as \`<channel source="claudemesh">\` interrupts mid-turn. Everything else is CLI.
15723
15723
 
15724
+ ## Launch welcome (\`kind: "welcome"\`) — 1.34.2+
15725
+
15726
+ 5 seconds after Claude Code attaches to claudemesh via \`claudemesh launch\`, the MCP server emits ONE \`<channel source="claudemesh">\` push with \`meta.kind: "welcome"\`. It carries identity (\`self_display_name\`, \`self_session_pubkey\`, \`self_role\`), the active \`mesh_slug\`, live \`peer_count\` + \`peer_names\`, recent \`unread_count\` + \`latest_message_ids\`, and a CLI hint line. Treat it as the "mesh is connected" handshake — read it once, internalize identity + peers + inbox state, and use it to decide whether to act on unread items right away. Do NOT reply to a welcome push the way you reply to a DM; it has no sender.
15727
+
15724
15728
  ## When you receive a \`<channel source="claudemesh">\` message
15725
15729
 
15726
- Respond IMMEDIATELY. Pause your current task, reply via \`claudemesh send\`, then resume. Read \`from_name\`, \`mesh_slug\`, and \`priority\` from the channel attributes. Reply by setting \`<to>\` to the sender's \`from_name\`. Do not ignore low-priority messages — acknowledge them briefly even if you defer action. If the channel meta contains \`subtype: reminder\`, this is a scheduled reminder you set yourself — act on it.
15730
+ Respond IMMEDIATELY (unless \`meta.kind\` is \`"welcome"\` or \`"system"\` — those are informational, no reply needed). Pause your current task, reply via \`claudemesh send\`, then resume. Read \`from_name\`, \`mesh_slug\`, and \`priority\` from the channel attributes. Reply by setting \`<to>\` to the sender's \`from_name\`. Do not ignore low-priority messages — acknowledge them briefly even if you defer action. If the channel meta contains \`subtype: reminder\`, this is a scheduled reminder you set yourself — act on it.
15727
15731
 
15728
15732
  ### Channel attributes (everything you need to reply is in the push)
15729
15733
 
@@ -15731,14 +15735,17 @@ The \`<channel>\` interrupt carries these attributes — no lookup needed:
15731
15735
 
15732
15736
  | Attribute | What it is |
15733
15737
  |---|---|
15734
- | \`from_name\` | Sender's display name. **Use as \`to\` in your reply** for DMs. |
15735
- | \`from_pubkey\` | Sender's session pubkey (hex). Stable per-session. |
15736
- | \`from_member_id\` | Sender's stable mesh.member id. Survives display-name changes the canonical id. |
15738
+ | \`from_name\` | Sender's display name. **Use as \`to\` in your reply** for DMs. Empty/absent on \`kind: "welcome"\` and \`kind: "system"\`. |
15739
+ | \`from_pubkey\` | Sender's **session pubkey** (hex, ephemeral per-launch). Since 1.34.0 this is the session pubkey of the launched session that originated the send, NOT the daemon's stable member pubkey — sibling sessions of the same human are correctly disambiguated. |
15740
+ | \`from_session_pubkey\` | Same as \`from_pubkey\` for session-originated DMs. Kept as a separate key so the model never confuses session vs member identity when a control-plane source is involved. |
15741
+ | \`from_member_id\` / \`from_member_pubkey\` | Sender's stable mesh.member id / pubkey. Survives display-name and session rotation. Use to recognize "the same human across multiple Claude Code windows". |
15737
15742
  | \`mesh_slug\` | Mesh the message arrived on. Pass via \`--mesh <slug>\` if the parent isn't on the same mesh. |
15738
15743
  | \`priority\` | \`now\` / \`next\` / \`low\`. |
15739
15744
  | \`message_id\` | Server-side id of THIS message. **Pass to \`--reply-to <id>\` to thread your reply** in topic posts. |
15745
+ | \`client_message_id\` | Sender-stable idempotency id (UUID). Survives broker restarts; safe to log. |
15740
15746
  | \`topic\` | Set when the source is a topic post. Reply via \`topic post <topic> --reply-to <message_id>\`. |
15741
15747
  | \`reply_to_id\` | Set when the message itself is a reply to a previous one — render thread context. |
15748
+ | \`kind\` (welcome/system meta only) | \`"welcome"\` for the launch handshake, \`"system"\` for peer_join/peer_leave/etc. — neither needs a reply. |
15742
15749
 
15743
15750
  **Reply patterns:**
15744
15751
 
@@ -16082,15 +16089,19 @@ claudemesh message send <p> "..." --priority now # bypass busy gates
16082
16089
  claudemesh message send <p> "..." --priority next # default
16083
16090
  claudemesh message send <p> "..." --priority low # pull-only
16084
16091
 
16085
- # inbox (alias: claudemesh inbox)
16086
- claudemesh message inbox
16087
- claudemesh message inbox --json
16092
+ # inbox (alias: claudemesh inbox) — 1.34.0+ reads from inbox.db via daemon IPC
16093
+ claudemesh inbox # all attached meshes, last 100
16094
+ claudemesh inbox --mesh <slug> # scoped to one mesh
16095
+ claudemesh inbox --mesh <slug> --limit 20 # custom cap
16096
+ claudemesh inbox --json # full row (sender_pubkey, mesh, body, received_at, …)
16088
16097
 
16089
16098
  # delivery status (alias: claudemesh msg-status <id>)
16090
16099
  claudemesh message status <message-id>
16091
16100
  claudemesh message status <message-id> --json
16092
16101
  \`\`\`
16093
16102
 
16103
+ **Inbox source (1.34.0+):** \`claudemesh inbox\` queries the daemon's persistent \`~/.claudemesh/daemon/inbox.db\` over IPC — it is NOT a fresh broker-WS buffer drain. Rows survive daemon restarts. Sender attribution is the actual session pubkey of the launched session that originated the send (NOT the stable member pubkey of the sender's daemon), so two sibling sessions of the same human appear as distinct rows.
16104
+
16094
16105
  \`send\` JSON output: \`{"ok": true, "messageId": "...", "target": "..."}\`. Errors: \`{"ok": false, "error": "..."}\`.
16095
16106
 
16096
16107
  ### \`state\` — shared per-mesh key-value store
@@ -18170,9 +18181,12 @@ function bailNoDaemon() {
18170
18181
  `);
18171
18182
  process.exit(1);
18172
18183
  }
18173
- function daemonGet(path2) {
18184
+ function daemonGet(path2, opts = {}) {
18174
18185
  return new Promise((resolve3, reject) => {
18175
- const req = httpRequest2({ socketPath: DAEMON_PATHS.SOCK_FILE, path: path2, method: "GET", timeout: 5000 }, (res) => {
18186
+ const headers = {};
18187
+ if (opts.sessionToken)
18188
+ headers.Authorization = `ClaudeMesh-Session ${opts.sessionToken}`;
18189
+ const req = httpRequest2({ socketPath: DAEMON_PATHS.SOCK_FILE, path: path2, method: "GET", timeout: 5000, headers }, (res) => {
18176
18190
  const chunks = [];
18177
18191
  res.on("data", (c) => chunks.push(c));
18178
18192
  res.on("end", () => {
@@ -18466,13 +18480,19 @@ ${mf.allowed_tools.map((t) => ` - ${t}`).join(`
18466
18480
  } catch {}
18467
18481
  }
18468
18482
  });
18483
+ let welcomeSent = false;
18484
+ const WELCOME_GRACE_MS = 2000;
18485
+ server.oninitialized = () => {
18486
+ mcpLog("server_initialized");
18487
+ if (welcomeSent)
18488
+ return;
18489
+ welcomeSent = true;
18490
+ setTimeout(() => {
18491
+ emitMeshWelcome(server, mcpLog);
18492
+ }, WELCOME_GRACE_MS);
18493
+ };
18469
18494
  const transport = new StdioServerTransport;
18470
18495
  await server.connect(transport);
18471
- const WELCOME_DELAY_MS = 5000;
18472
- const WELCOME_LOOKBACK_MS = 24 * 60 * 60 * 1000;
18473
- setTimeout(() => {
18474
- emitInboxWelcome(server, mcpLog, WELCOME_LOOKBACK_MS);
18475
- }, WELCOME_DELAY_MS);
18476
18496
  const keepalive = setInterval(() => {}, 1000);
18477
18497
  const shutdown = () => {
18478
18498
  clearInterval(keepalive);
@@ -18482,52 +18502,95 @@ ${mf.allowed_tools.map((t) => ` - ${t}`).join(`
18482
18502
  process.on("SIGTERM", shutdown);
18483
18503
  process.on("SIGINT", shutdown);
18484
18504
  }
18485
- async function emitInboxWelcome(server, mcpLog, lookbackMs) {
18486
- const sinceIso = new Date(Date.now() - lookbackMs).toISOString();
18487
- const path2 = `/v1/inbox?since=${encodeURIComponent(sinceIso)}&limit=20`;
18488
- let body;
18505
+ async function emitMeshWelcome(server, mcpLog) {
18506
+ const { readSessionTokenFromEnv: readSessionTokenFromEnv2 } = await Promise.resolve().then(() => (init_token(), exports_token));
18507
+ const sessionToken = readSessionTokenFromEnv2();
18508
+ let selfDisplayName;
18509
+ let selfSessionPubkey;
18510
+ let selfMeshSlug;
18511
+ let selfRole;
18512
+ if (sessionToken) {
18513
+ try {
18514
+ const { status, body } = await daemonGet("/v1/sessions/me", { sessionToken });
18515
+ if (status === 200 && body?.session) {
18516
+ selfDisplayName = body.session.displayName;
18517
+ selfMeshSlug = body.session.mesh;
18518
+ selfRole = body.session.role;
18519
+ selfSessionPubkey = body.session.presence?.sessionPubkey;
18520
+ }
18521
+ } catch (e) {
18522
+ mcpLog("welcome_self_lookup_failed", { err: String(e) });
18523
+ }
18524
+ }
18525
+ let peerCount = -1;
18526
+ let peerNames = [];
18489
18527
  try {
18490
- const res = await daemonGet(path2);
18491
- if (res.status !== 200) {
18492
- mcpLog("welcome_skip", { reason: "ipc_status", status: res.status });
18493
- return;
18528
+ const path2 = selfMeshSlug ? `/v1/peers?mesh=${encodeURIComponent(selfMeshSlug)}` : "/v1/peers";
18529
+ const { status, body } = await daemonGet(path2, { sessionToken });
18530
+ if (status === 200 && Array.isArray(body?.peers)) {
18531
+ const peers = body.peers;
18532
+ const real = peers.filter((p) => {
18533
+ const channel = String(p.channel ?? "");
18534
+ const peerRole = String(p.peerRole ?? "");
18535
+ const isInfra = channel === "claudemesh-daemon" || peerRole === "control-plane";
18536
+ if (isInfra)
18537
+ return false;
18538
+ if (selfSessionPubkey && p.pubkey === selfSessionPubkey)
18539
+ return false;
18540
+ return true;
18541
+ });
18542
+ peerCount = real.length;
18543
+ peerNames = real.map((p) => String(p.displayName ?? "unknown")).filter((n, i, arr) => arr.indexOf(n) === i).slice(0, 5);
18544
+ mcpLog("welcome_peers_resolved", { total: peers.length, real: real.length });
18545
+ } else {
18546
+ mcpLog("welcome_peers_status", { status });
18494
18547
  }
18495
- body = res.body;
18496
18548
  } catch (e) {
18497
- mcpLog("welcome_skip", { reason: "ipc_threw", err: String(e) });
18498
- return;
18549
+ mcpLog("welcome_peers_lookup_failed", { err: String(e) });
18499
18550
  }
18500
- const items = Array.isArray(body.items) ? body.items : [];
18501
- if (items.length === 0) {
18502
- mcpLog("welcome_skip", { reason: "empty" });
18503
- return;
18551
+ const sinceIso = new Date(Date.now() - 86400000).toISOString();
18552
+ const inboxPath = selfMeshSlug ? `/v1/inbox?mesh=${encodeURIComponent(selfMeshSlug)}&since=${encodeURIComponent(sinceIso)}&limit=20` : `/v1/inbox?since=${encodeURIComponent(sinceIso)}&limit=20`;
18553
+ let inboxItems = [];
18554
+ try {
18555
+ const { status, body } = await daemonGet(inboxPath, { sessionToken });
18556
+ if (status === 200 && Array.isArray(body?.items)) {
18557
+ inboxItems = body.items;
18558
+ }
18559
+ } catch (e) {
18560
+ mcpLog("welcome_inbox_lookup_failed", { err: String(e) });
18561
+ }
18562
+ const lines = [];
18563
+ const idTag = selfDisplayName ? `${selfDisplayName}${selfSessionPubkey ? ` (${selfSessionPubkey.slice(0, 8)})` : ""}${selfRole ? ` [${selfRole}]` : ""}` : "session";
18564
+ const meshTag = selfMeshSlug ? ` on mesh \`${selfMeshSlug}\`` : "";
18565
+ lines.push(`\uD83C\uDF10 [welcome] claudemesh connected — you are **${idTag}**${meshTag}.`);
18566
+ if (peerCount === 0) {
18567
+ lines.push(`\uD83D\uDC65 No other peers online right now.`);
18568
+ } else if (peerCount > 0) {
18569
+ const namesPreview = peerNames.join(", ");
18570
+ const more = peerCount > peerNames.length ? ` …and ${peerCount - peerNames.length} more` : "";
18571
+ lines.push(`\uD83D\uDC65 ${peerCount} peer${peerCount === 1 ? "" : "s"} online: ${namesPreview}${more}`);
18572
+ } else {
18573
+ lines.push(`\uD83D\uDC65 Peer list unavailable (daemon query failed).`);
18504
18574
  }
18505
- const byMesh = new Map;
18506
- for (const it of items) {
18507
- const meshSlug = String(it.mesh ?? "");
18508
- const arr = byMesh.get(meshSlug) ?? [];
18509
- arr.push(it);
18510
- byMesh.set(meshSlug, arr);
18511
- }
18512
- const preview = items.slice(0, 3).map((it) => {
18513
- const sender = String(it.sender_name ?? "unknown");
18514
- const senderPub = String(it.sender_pubkey ?? "").slice(0, 8);
18515
- const meshSlug = String(it.mesh ?? "");
18516
- const bodyText = (typeof it.body === "string" ? it.body : "(encrypted)").slice(0, 60);
18517
- const ts = String(it.received_at ?? "");
18518
- const time = ts ? new Date(ts).toLocaleTimeString() : "";
18519
- const tag = sender !== senderPub ? `${sender} (${senderPub})` : senderPub;
18520
- return ` ${tag} [${meshSlug}] ${time}: ${bodyText}`;
18521
- }).join(`
18575
+ if (inboxItems.length === 0) {
18576
+ lines.push(`\uD83D\uDCE5 Inbox is empty (last 24h).`);
18577
+ } else {
18578
+ lines.push(`\uD83D\uDCE5 ${inboxItems.length} message${inboxItems.length === 1 ? "" : "s"} in inbox (last 24h):`);
18579
+ for (const it of inboxItems.slice(0, 3)) {
18580
+ const sender = String(it.sender_name ?? "unknown");
18581
+ const senderPub = String(it.sender_pubkey ?? "").slice(0, 8);
18582
+ const tag = sender !== senderPub ? `${sender} (${senderPub})` : senderPub;
18583
+ const bodyText = (typeof it.body === "string" ? it.body : "(encrypted)").slice(0, 60);
18584
+ const time = it.received_at ? new Date(String(it.received_at)).toLocaleTimeString() : "";
18585
+ lines.push(` ${tag} ${time}: ${bodyText}`);
18586
+ }
18587
+ if (inboxItems.length > 3)
18588
+ lines.push(` …and ${inboxItems.length - 3} more`);
18589
+ }
18590
+ lines.push(`\uD83D\uDCA1 Use: \`claudemesh peer list\` · \`claudemesh send <peer> <msg>\` · \`claudemesh inbox\``);
18591
+ lines.push(`\uD83D\uDCDA Read the \`claudemesh\` skill (SKILL.md) for full CLI / channel / inbox reference if not yet in context.`);
18592
+ const content = lines.join(`
18522
18593
  `);
18523
- const remainder = items.length > 3 ? `
18524
- …and ${items.length - 3} more` : "";
18525
- const meshList = [...byMesh.keys()].filter(Boolean).join(", ");
18526
- const header = `\uD83D\uDCE5 [welcome] ${items.length} message${items.length === 1 ? "" : "s"} in inbox from the last 24h${meshList ? ` (${meshList})` : ""}`;
18527
- const footer = `
18528
- Run \`claudemesh inbox\` for full content.`;
18529
- const content = `${header}
18530
- ${preview}${remainder}${footer}`;
18531
18594
  try {
18532
18595
  await server.notification({
18533
18596
  method: "notifications/claude/channel",
@@ -18535,13 +18598,22 @@ ${preview}${remainder}${footer}`;
18535
18598
  content,
18536
18599
  meta: {
18537
18600
  kind: "welcome",
18538
- unread_count: items.length,
18539
- meshes: [...byMesh.keys()],
18540
- latest_message_ids: items.slice(0, 10).map((it) => String(it.id ?? ""))
18601
+ self_display_name: selfDisplayName ?? "",
18602
+ self_session_pubkey: selfSessionPubkey ?? "",
18603
+ self_role: selfRole ?? "",
18604
+ mesh_slug: selfMeshSlug ?? "",
18605
+ peer_count: peerCount >= 0 ? peerCount : null,
18606
+ peer_names: peerNames,
18607
+ unread_count: inboxItems.length,
18608
+ latest_message_ids: inboxItems.slice(0, 10).map((it) => String(it.id ?? ""))
18541
18609
  }
18542
18610
  }
18543
18611
  });
18544
- mcpLog("welcome_emitted", { count: items.length, meshes: [...byMesh.keys()] });
18612
+ mcpLog("welcome_emitted", {
18613
+ mesh: selfMeshSlug ?? "",
18614
+ peer_count: peerCount,
18615
+ unread_count: inboxItems.length
18616
+ });
18545
18617
  } catch (err) {
18546
18618
  mcpLog("welcome_emit_failed", { err: String(err) });
18547
18619
  }
@@ -19416,7 +19488,9 @@ Message (resource form)
19416
19488
  [--self] (allow targeting your own member/session pubkey;
19417
19489
  fans out to every sibling session of your member)
19418
19490
  [--json] (machine-readable result)
19419
- claudemesh message inbox drain pending (alias: inbox)
19491
+ claudemesh message inbox read persisted inbox (alias: inbox)
19492
+ flags: [--mesh <slug>] [--limit N] [--json]
19493
+ reads ~/.claudemesh/daemon/inbox.db via daemon
19420
19494
  claudemesh message status <id> delivery status (alias: msg-status)
19421
19495
 
19422
19496
  Memory (resource form)
@@ -20582,4 +20656,4 @@ main().catch((err) => {
20582
20656
  process.exit(EXIT.INTERNAL_ERROR);
20583
20657
  });
20584
20658
 
20585
- //# debugId=FD137608913AB26764756E2164756E21
20659
+ //# debugId=A642AC8B680B8F3E64756E2164756E21