claudemesh-cli 0.2.0 → 0.3.0

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.
Files changed (2) hide show
  1. package/dist/index.js +371 -18
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -46276,6 +46276,20 @@ var TOOLS = [
46276
46276
  }
46277
46277
  }
46278
46278
  },
46279
+ {
46280
+ name: "message_status",
46281
+ description: "Check the delivery status of a sent message. Shows whether each recipient received it.",
46282
+ inputSchema: {
46283
+ type: "object",
46284
+ properties: {
46285
+ id: {
46286
+ type: "string",
46287
+ description: "Message ID (returned by send_message)"
46288
+ }
46289
+ },
46290
+ required: ["id"]
46291
+ }
46292
+ },
46279
46293
  {
46280
46294
  name: "check_messages",
46281
46295
  description: "Pull any undelivered messages from the broker. Normally messages arrive via push; use this to drain the queue after being offline.",
@@ -46332,6 +46346,75 @@ var TOOLS = [
46332
46346
  },
46333
46347
  required: ["name"]
46334
46348
  }
46349
+ },
46350
+ {
46351
+ name: "set_state",
46352
+ description: "Set a shared state value visible to all peers in the mesh. Pushes a change notification.",
46353
+ inputSchema: {
46354
+ type: "object",
46355
+ properties: {
46356
+ key: { type: "string" },
46357
+ value: { description: "Any JSON value" }
46358
+ },
46359
+ required: ["key", "value"]
46360
+ }
46361
+ },
46362
+ {
46363
+ name: "get_state",
46364
+ description: "Read a shared state value.",
46365
+ inputSchema: {
46366
+ type: "object",
46367
+ properties: {
46368
+ key: { type: "string" }
46369
+ },
46370
+ required: ["key"]
46371
+ }
46372
+ },
46373
+ {
46374
+ name: "list_state",
46375
+ description: "List all shared state keys and values in the mesh.",
46376
+ inputSchema: { type: "object", properties: {} }
46377
+ },
46378
+ {
46379
+ name: "remember",
46380
+ description: "Store persistent knowledge in the mesh's shared memory. Survives across sessions.",
46381
+ inputSchema: {
46382
+ type: "object",
46383
+ properties: {
46384
+ content: {
46385
+ type: "string",
46386
+ description: "The knowledge to remember"
46387
+ },
46388
+ tags: {
46389
+ type: "array",
46390
+ items: { type: "string" },
46391
+ description: "Optional categorization tags"
46392
+ }
46393
+ },
46394
+ required: ["content"]
46395
+ }
46396
+ },
46397
+ {
46398
+ name: "recall",
46399
+ description: "Search the mesh's shared memory by relevance.",
46400
+ inputSchema: {
46401
+ type: "object",
46402
+ properties: {
46403
+ query: { type: "string", description: "Search query" }
46404
+ },
46405
+ required: ["query"]
46406
+ }
46407
+ },
46408
+ {
46409
+ name: "forget",
46410
+ description: "Remove a memory from the mesh's shared knowledge.",
46411
+ inputSchema: {
46412
+ type: "object",
46413
+ properties: {
46414
+ id: { type: "string", description: "Memory ID to forget" }
46415
+ },
46416
+ required: ["id"]
46417
+ }
46335
46418
  }
46336
46419
  ];
46337
46420
 
@@ -46425,6 +46508,11 @@ class BrokerClient {
46425
46508
  pushHandlers = new Set;
46426
46509
  pushBuffer = [];
46427
46510
  listPeersResolvers = [];
46511
+ stateResolvers = [];
46512
+ stateListResolvers = [];
46513
+ memoryStoreResolvers = [];
46514
+ memoryRecallResolvers = [];
46515
+ stateChangeHandlers = new Set;
46428
46516
  sessionPubkey = null;
46429
46517
  sessionSecretKey = null;
46430
46518
  closed = false;
@@ -46626,6 +46714,96 @@ class BrokerClient {
46626
46714
  return;
46627
46715
  this.ws.send(JSON.stringify({ type: "leave_group", name }));
46628
46716
  }
46717
+ async setState(key, value) {
46718
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
46719
+ return;
46720
+ this.ws.send(JSON.stringify({ type: "set_state", key, value }));
46721
+ }
46722
+ async getState(key) {
46723
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
46724
+ return null;
46725
+ return new Promise((resolve) => {
46726
+ this.stateResolvers.push(resolve);
46727
+ this.ws.send(JSON.stringify({ type: "get_state", key }));
46728
+ setTimeout(() => {
46729
+ const idx = this.stateResolvers.indexOf(resolve);
46730
+ if (idx !== -1) {
46731
+ this.stateResolvers.splice(idx, 1);
46732
+ resolve(null);
46733
+ }
46734
+ }, 5000);
46735
+ });
46736
+ }
46737
+ async listState() {
46738
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
46739
+ return [];
46740
+ return new Promise((resolve) => {
46741
+ this.stateListResolvers.push(resolve);
46742
+ this.ws.send(JSON.stringify({ type: "list_state" }));
46743
+ setTimeout(() => {
46744
+ const idx = this.stateListResolvers.indexOf(resolve);
46745
+ if (idx !== -1) {
46746
+ this.stateListResolvers.splice(idx, 1);
46747
+ resolve([]);
46748
+ }
46749
+ }, 5000);
46750
+ });
46751
+ }
46752
+ async remember(content, tags) {
46753
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
46754
+ return null;
46755
+ return new Promise((resolve) => {
46756
+ this.memoryStoreResolvers.push(resolve);
46757
+ this.ws.send(JSON.stringify({ type: "remember", content, tags }));
46758
+ setTimeout(() => {
46759
+ const idx = this.memoryStoreResolvers.indexOf(resolve);
46760
+ if (idx !== -1) {
46761
+ this.memoryStoreResolvers.splice(idx, 1);
46762
+ resolve(null);
46763
+ }
46764
+ }, 5000);
46765
+ });
46766
+ }
46767
+ async recall(query) {
46768
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
46769
+ return [];
46770
+ return new Promise((resolve) => {
46771
+ this.memoryRecallResolvers.push(resolve);
46772
+ this.ws.send(JSON.stringify({ type: "recall", query }));
46773
+ setTimeout(() => {
46774
+ const idx = this.memoryRecallResolvers.indexOf(resolve);
46775
+ if (idx !== -1) {
46776
+ this.memoryRecallResolvers.splice(idx, 1);
46777
+ resolve([]);
46778
+ }
46779
+ }, 5000);
46780
+ });
46781
+ }
46782
+ async forget(memoryId) {
46783
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
46784
+ return;
46785
+ this.ws.send(JSON.stringify({ type: "forget", memoryId }));
46786
+ }
46787
+ messageStatusResolvers = [];
46788
+ async messageStatus(messageId) {
46789
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
46790
+ return null;
46791
+ return new Promise((resolve) => {
46792
+ this.messageStatusResolvers.push(resolve);
46793
+ this.ws.send(JSON.stringify({ type: "message_status", messageId }));
46794
+ setTimeout(() => {
46795
+ const idx = this.messageStatusResolvers.indexOf(resolve);
46796
+ if (idx !== -1) {
46797
+ this.messageStatusResolvers.splice(idx, 1);
46798
+ resolve(null);
46799
+ }
46800
+ }, 5000);
46801
+ });
46802
+ }
46803
+ onStateChange(handler) {
46804
+ this.stateChangeHandlers.add(handler);
46805
+ return () => this.stateChangeHandlers.delete(handler);
46806
+ }
46629
46807
  close() {
46630
46808
  this.closed = true;
46631
46809
  if (this.helloTimer)
@@ -46708,6 +46886,61 @@ class BrokerClient {
46708
46886
  })();
46709
46887
  return;
46710
46888
  }
46889
+ if (msg.type === "state_result") {
46890
+ const resolver = this.stateResolvers.shift();
46891
+ if (resolver) {
46892
+ if (msg.key) {
46893
+ resolver({
46894
+ key: String(msg.key),
46895
+ value: msg.value,
46896
+ updatedBy: String(msg.updatedBy ?? ""),
46897
+ updatedAt: String(msg.updatedAt ?? "")
46898
+ });
46899
+ } else {
46900
+ resolver(null);
46901
+ }
46902
+ }
46903
+ return;
46904
+ }
46905
+ if (msg.type === "state_list") {
46906
+ const entries = msg.entries ?? [];
46907
+ const resolver = this.stateListResolvers.shift();
46908
+ if (resolver)
46909
+ resolver(entries);
46910
+ return;
46911
+ }
46912
+ if (msg.type === "state_change") {
46913
+ const change = {
46914
+ key: String(msg.key ?? ""),
46915
+ value: msg.value,
46916
+ updatedBy: String(msg.updatedBy ?? "")
46917
+ };
46918
+ for (const h of this.stateChangeHandlers) {
46919
+ try {
46920
+ h(change);
46921
+ } catch {}
46922
+ }
46923
+ return;
46924
+ }
46925
+ if (msg.type === "memory_stored") {
46926
+ const resolver = this.memoryStoreResolvers.shift();
46927
+ if (resolver)
46928
+ resolver(msg.id ? String(msg.id) : null);
46929
+ return;
46930
+ }
46931
+ if (msg.type === "memory_results") {
46932
+ const memories = msg.memories ?? [];
46933
+ const resolver = this.memoryRecallResolvers.shift();
46934
+ if (resolver)
46935
+ resolver(memories);
46936
+ return;
46937
+ }
46938
+ if (msg.type === "message_status_result") {
46939
+ const resolver = this.messageStatusResolvers.shift();
46940
+ if (resolver)
46941
+ resolver(msg);
46942
+ return;
46943
+ }
46711
46944
  if (msg.type === "error") {
46712
46945
  this.debug(`broker error: ${msg.code} ${msg.message}`);
46713
46946
  const id = msg.id ? String(msg.id) : null;
@@ -46879,32 +47112,54 @@ ${body}`;
46879
47112
  }
46880
47113
  async function startMcpServer() {
46881
47114
  const config2 = loadConfig();
46882
- const server = new Server({ name: "claudemesh", version: "0.1.4" }, {
47115
+ const myName = config2.displayName ?? "unnamed";
47116
+ const myGroups = (config2.groups ?? []).map((g) => `@${g.name}${g.role ? ":" + g.role : ""}`).join(", ") || "none";
47117
+ const server = new Server({ name: "claudemesh", version: "0.3.0" }, {
46883
47118
  capabilities: {
46884
47119
  experimental: { "claude/channel": {} },
46885
47120
  tools: {}
46886
47121
  },
46887
- instructions: `You are connected to claudemesh — a peer mesh for Claude Code sessions on this machine and elsewhere.
47122
+ instructions: `## Identity
47123
+ You are "${myName}" — a peer in the claudemesh network. Your groups: ${myGroups}. You are one of several Claude Code sessions connected to the same mesh. No orchestrator exists — peers are equals. Your identity comes from your name and group roles, not from a central authority.
47124
+
47125
+ ## Responding to messages
47126
+ When you receive a <channel source="claudemesh" ...> message, RESPOND IMMEDIATELY. Pause your current task, reply via send_message, then resume. Read from_name, mesh_slug, and priority from the channel attributes. Reply by setting \`to\` to the sender's from_name (display name). Stay in character per your system prompt. Do not ignore low-priority messages — acknowledge them briefly even if you defer action.
47127
+
47128
+ ## Tools
47129
+ | Tool | Description |
47130
+ |------|-------------|
47131
+ | send_message(to, message, priority?) | Send to peer name, @group, or * broadcast. \`to\` accepts display name, pubkey hex, @groupname, or *. |
47132
+ | list_peers(mesh_slug?) | List connected peers with status, summary, groups, and roles. |
47133
+ | check_messages() | Drain buffered inbound messages (auto-pushed in most cases, use as fallback). |
47134
+ | set_summary(summary) | Set 1-2 sentence description of your current work, visible to all peers. |
47135
+ | set_status(status) | Override status: idle, working, or dnd. |
47136
+ | join_group(name, role?) | Join a @group with optional role (lead, member, observer, or any string). |
47137
+ | leave_group(name) | Leave a @group. |
47138
+ | set_state(key, value) | Write shared state; pushes change to all peers. |
47139
+ | get_state(key) | Read a shared state value. |
47140
+ | list_state() | List all state keys with values, authors, and timestamps. |
47141
+ | remember(content, tags?) | Store persistent knowledge with optional tags. |
47142
+ | recall(query) | Full-text search over mesh memory. |
47143
+ | forget(id) | Soft-delete a memory entry. |
46888
47144
 
46889
- IMPORTANT: When you receive a <channel source="claudemesh" ...> message, RESPOND IMMEDIATELY. Do not wait until your current task is finished. Pause what you are doing, reply to the message using send_message, then resume your work. Treat incoming peer messages like a coworker tapping you on the shoulder — answer right away, even if you're in the middle of something.
47145
+ If multiple meshes are joined, prefix \`to\` with \`<mesh-slug>:\` to disambiguate (e.g. \`dev-team:Alice\`).
46890
47146
 
46891
- Read the from_id, from_name, mesh_slug, and priority attributes to understand context. Reply by calling send_message with to set to the from_name (display name) of the sender. The \`to\` field can be a peer name, pubkey, @group, or * for broadcast.
47147
+ ## Groups
47148
+ Groups are routing labels. Send to @groupname to multicast to all members. Roles are metadata that peers interpret: a "lead" gathers input before synthesizing a response, a "member" contributes when asked, an "observer" watches silently. Join and leave groups dynamically with join_group/leave_group. Check list_peers to see who belongs to which groups and their roles.
46892
47149
 
46893
- Available tools:
46894
- - list_peers: see joined meshes + their connection status
46895
- - send_message: send to a peer by display name, pubkey, @group, #channel, or * broadcast (priority: now/next/low)
46896
- - check_messages: drain buffered inbound messages (usually auto-pushed)
46897
- - set_summary: 1-2 sentence summary of what you're working on
46898
- - set_status: manually override your status (idle/working/dnd)
46899
- - join_group: join a @group with optional role
46900
- - leave_group: leave a @group
47150
+ ## State
47151
+ Shared key-value store scoped to the mesh. Use get_state/set_state for live coordination facts (deploy frozen? current sprint? PR queue). set_state pushes the change to all connected peers. Read state before asking peers questions — the answer may already be there. State is operational, not archival.
46901
47152
 
46902
- Message priority:
46903
- - "now": delivered immediately regardless of recipient status (use sparingly)
46904
- - "next" (default): delivered when recipient is idle
46905
- - "low": pull-only (check_messages)
47153
+ ## Memory
47154
+ Persistent knowledge that survives across sessions. Use remember(content, tags?) to store lessons, decisions, and incidents. Use recall(query) to search before asking peers. New peers should recall at session start to load institutional knowledge.
46906
47155
 
46907
- If you have multiple joined meshes, prefix the \`to\` argument of send_message with \`<mesh-slug>:\` to disambiguate. Otherwise claudemesh picks the single joined mesh.`
47156
+ ## Priority
47157
+ - "now": interrupt immediately, even if recipient is in DND (use for urgent: broken deploy, blocking issue)
47158
+ - "next" (default): deliver when recipient goes idle (normal coordination)
47159
+ - "low": pull-only via check_messages (FYI, non-blocking context)
47160
+
47161
+ ## Coordination
47162
+ Call list_peers at session start to understand who is online, their roles, and what they are working on. If you are a group lead, gather input from members before responding to external requests — do not answer alone. If you are a member, contribute to your lead when asked. Use @group messages for team-wide questions, direct messages for 1:1 coordination. Set a meaningful summary so peers know your current focus.`
46908
47163
  });
46909
47164
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
46910
47165
  tools: TOOLS
@@ -46953,6 +47208,23 @@ ${peerLines.join(`
46953
47208
  return text(sections.join(`
46954
47209
 
46955
47210
  `));
47211
+ }
47212
+ case "message_status": {
47213
+ const { id } = args ?? {};
47214
+ if (!id)
47215
+ return text("message_status: `id` required", true);
47216
+ const client = allClients()[0];
47217
+ if (!client)
47218
+ return text("message_status: not connected", true);
47219
+ const result = await client.messageStatus(id);
47220
+ if (!result)
47221
+ return text(`Message ${id} not found or timed out.`);
47222
+ const recipientLines = result.recipients.map((r) => ` - ${r.name} (${r.pubkey.slice(0, 12)}…): ${r.status}`);
47223
+ return text(`Message ${id.slice(0, 12)}… → ${result.targetSpec}
47224
+ ` + `Delivered: ${result.delivered}${result.deliveredAt ? ` at ${result.deliveredAt}` : ""}
47225
+ ` + `Recipients:
47226
+ ${recipientLines.join(`
47227
+ `)}`);
46956
47228
  }
46957
47229
  case "check_messages": {
46958
47230
  const drained = [];
@@ -47004,6 +47276,72 @@ ${drained.join(`
47004
47276
  await c.leaveGroup(groupName);
47005
47277
  return text(`Left @${groupName}`);
47006
47278
  }
47279
+ case "set_state": {
47280
+ const { key, value } = args ?? {};
47281
+ if (!key)
47282
+ return text("set_state: `key` required", true);
47283
+ for (const c of allClients())
47284
+ await c.setState(key, value);
47285
+ return text(`State set: ${key} = ${JSON.stringify(value)}`);
47286
+ }
47287
+ case "get_state": {
47288
+ const { key } = args ?? {};
47289
+ if (!key)
47290
+ return text("get_state: `key` required", true);
47291
+ const client = allClients()[0];
47292
+ if (!client)
47293
+ return text("get_state: not connected", true);
47294
+ const result = await client.getState(key);
47295
+ if (!result)
47296
+ return text(`State "${key}" not found.`);
47297
+ return text(`${key} = ${JSON.stringify(result.value)} (set by ${result.updatedBy} at ${result.updatedAt})`);
47298
+ }
47299
+ case "list_state": {
47300
+ const client = allClients()[0];
47301
+ if (!client)
47302
+ return text("list_state: not connected", true);
47303
+ const entries = await client.listState();
47304
+ if (entries.length === 0)
47305
+ return text("No shared state set.");
47306
+ const lines = entries.map((e) => `- **${e.key}** = ${JSON.stringify(e.value)} (by ${e.updatedBy})`);
47307
+ return text(lines.join(`
47308
+ `));
47309
+ }
47310
+ case "remember": {
47311
+ const { content, tags } = args ?? {};
47312
+ if (!content)
47313
+ return text("remember: `content` required", true);
47314
+ const client = allClients()[0];
47315
+ if (!client)
47316
+ return text("remember: not connected", true);
47317
+ const id = await client.remember(content, tags);
47318
+ return text(`Remembered${id ? ` (${id})` : ""}: "${content.slice(0, 80)}${content.length > 80 ? "..." : ""}"`);
47319
+ }
47320
+ case "recall": {
47321
+ const { query } = args ?? {};
47322
+ if (!query)
47323
+ return text("recall: `query` required", true);
47324
+ const client = allClients()[0];
47325
+ if (!client)
47326
+ return text("recall: not connected", true);
47327
+ const memories = await client.recall(query);
47328
+ if (memories.length === 0)
47329
+ return text(`No memories found for "${query}".`);
47330
+ const lines = memories.map((m) => `- [${m.id.slice(0, 8)}] ${m.content} (by ${m.rememberedBy}, ${m.rememberedAt})`);
47331
+ return text(`${memories.length} memor${memories.length === 1 ? "y" : "ies"}:
47332
+ ${lines.join(`
47333
+ `)}`);
47334
+ }
47335
+ case "forget": {
47336
+ const { id } = args ?? {};
47337
+ if (!id)
47338
+ return text("forget: `id` required", true);
47339
+ const client = allClients()[0];
47340
+ if (!client)
47341
+ return text("forget: not connected", true);
47342
+ await client.forget(id);
47343
+ return text(`Forgotten: ${id}`);
47344
+ }
47007
47345
  default:
47008
47346
  return text(`Unknown tool: ${name}`, true);
47009
47347
  }
@@ -47035,6 +47373,21 @@ ${drained.join(`
47035
47373
  });
47036
47374
  } catch {}
47037
47375
  });
47376
+ client.onStateChange(async (change) => {
47377
+ try {
47378
+ await server.notification({
47379
+ method: "notifications/claude/channel",
47380
+ params: {
47381
+ content: `[state] ${change.key} = ${JSON.stringify(change.value)} (set by ${change.updatedBy})`,
47382
+ meta: {
47383
+ kind: "state_change",
47384
+ key: change.key,
47385
+ updated_by: change.updatedBy
47386
+ }
47387
+ }
47388
+ });
47389
+ } catch {}
47390
+ });
47038
47391
  }
47039
47392
  const shutdown = () => {
47040
47393
  stopAll();
@@ -47920,7 +48273,7 @@ init_config();
47920
48273
  // package.json
47921
48274
  var package_default = {
47922
48275
  name: "claudemesh-cli",
47923
- version: "0.2.0",
48276
+ version: "0.3.0",
47924
48277
  description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
47925
48278
  keywords: [
47926
48279
  "claude-code",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemesh-cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
5
5
  "keywords": [
6
6
  "claude-code",