claudemesh-cli 0.7.0 → 0.7.1

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 +81 -12
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -47478,6 +47478,10 @@ var TOOLS = [
47478
47478
  required: ["name", "description", "inputSchema"]
47479
47479
  },
47480
47480
  description: "Tool definitions to expose"
47481
+ },
47482
+ persistent: {
47483
+ type: "boolean",
47484
+ description: "If true, registration survives peer disconnect. Other peers see it as 'offline' until you reconnect. Default: false"
47481
47485
  }
47482
47486
  },
47483
47487
  required: ["server_name", "description", "tools"]
@@ -47810,6 +47814,7 @@ class BrokerClient {
47810
47814
  sessionId: `${process.pid}-${Date.now()}`,
47811
47815
  pid: process.pid,
47812
47816
  cwd: process.cwd(),
47817
+ hostname: __require("os").hostname(),
47813
47818
  peerType: "ai",
47814
47819
  channel: "claude-code",
47815
47820
  model: process.env.CLAUDE_MODEL || undefined,
@@ -47841,6 +47846,18 @@ class BrokerClient {
47841
47846
  this.reconnectAttempt = 0;
47842
47847
  this.flushOutbound();
47843
47848
  this.startStatsReporting();
47849
+ if (msg.restored) {
47850
+ const groups = msg.restoredGroups ? msg.restoredGroups.map((g) => g.role ? `@${g.name}:${g.role}` : `@${g.name}`).join(", ") : "none";
47851
+ process.stderr.write(`[claudemesh] session restored — last seen ${msg.lastSeenAt ?? "unknown"}, groups: ${groups}
47852
+ `);
47853
+ if (msg.restoredStats) {
47854
+ const rs = msg.restoredStats;
47855
+ this._statsCounters.messagesIn = rs.messagesIn ?? 0;
47856
+ this._statsCounters.messagesOut = rs.messagesOut ?? 0;
47857
+ this._statsCounters.toolCalls = rs.toolCalls ?? 0;
47858
+ this._statsCounters.errors = rs.errors ?? 0;
47859
+ }
47860
+ }
47844
47861
  resolve();
47845
47862
  return;
47846
47863
  }
@@ -48427,7 +48444,7 @@ class BrokerClient {
48427
48444
  this.stateChangeHandlers.add(handler);
48428
48445
  return () => this.stateChangeHandlers.delete(handler);
48429
48446
  }
48430
- async mcpRegister(serverName, description, tools) {
48447
+ async mcpRegister(serverName, description, tools, persistent) {
48431
48448
  if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48432
48449
  return null;
48433
48450
  return new Promise((resolve) => {
@@ -48436,7 +48453,7 @@ class BrokerClient {
48436
48453
  if (this.mcpRegisterResolvers.delete(reqId))
48437
48454
  resolve(null);
48438
48455
  }, 5000) });
48439
- this.ws.send(JSON.stringify({ type: "mcp_register", serverName, description, tools, _reqId: reqId }));
48456
+ this.ws.send(JSON.stringify({ type: "mcp_register", serverName, description, tools, ...persistent ? { persistent: true } : {}, _reqId: reqId }));
48440
48457
  });
48441
48458
  }
48442
48459
  async mcpUnregister(serverName) {
@@ -49312,6 +49329,25 @@ function stopAll() {
49312
49329
  }
49313
49330
 
49314
49331
  // src/mcp/server.ts
49332
+ function relativeTime(isoStr) {
49333
+ const then = new Date(isoStr).getTime();
49334
+ if (isNaN(then))
49335
+ return "unknown";
49336
+ const diffMs = Date.now() - then;
49337
+ if (diffMs < 0)
49338
+ return "just now";
49339
+ const seconds = Math.floor(diffMs / 1000);
49340
+ if (seconds < 60)
49341
+ return `${seconds}s ago`;
49342
+ const minutes = Math.floor(seconds / 60);
49343
+ if (minutes < 60)
49344
+ return `${minutes}m ago`;
49345
+ const hours = Math.floor(minutes / 60);
49346
+ if (hours < 24)
49347
+ return `${hours}h ago`;
49348
+ const days = Math.floor(hours / 24);
49349
+ return `${days} day${days !== 1 ? "s" : ""} ago`;
49350
+ }
49315
49351
  function text(msg, isError = false) {
49316
49352
  return {
49317
49353
  content: [{ type: "text", text: msg }],
@@ -49484,9 +49520,14 @@ Shared key-value store scoped to the mesh. Use get_state/set_state for live coor
49484
49520
  ## Memory
49485
49521
  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.
49486
49522
 
49487
- ## Files
49488
- share_file for persistent references, send_message(file:) for ephemeral attachments.
49489
- Tags on shared files make them searchable. Use list_files to find what peers shared.
49523
+ ## File access — decision guide
49524
+ Three ways to access files. Pick the right one:
49525
+
49526
+ 1. **Local peer (same machine, [local] tag):** Read files directly via filesystem using their \`cwd\` path from list_peers. No limit, instant. This is the default for local peers.
49527
+ 2. **Remote peer (different machine, [remote] tag):** Use \`read_peer_file(peer, path)\` — relays through the mesh. **1 MB limit**, base64 encoded. Use \`list_peer_files\` to browse first.
49528
+ 3. **Persistent sharing (any peer):** Use \`share_file(path)\` — uploads to mesh storage (MinIO). **No size limit**. All peers can download anytime via \`get_file\`. Use for files that need to persist or be shared with multiple peers.
49529
+
49530
+ **Rule of thumb:** local peer → filesystem. Remote peer, small file → read_peer_file. Large file or needs to persist → share_file.
49490
49531
 
49491
49532
  ## Vectors
49492
49533
  Store and search semantic embeddings. Use vector_store to index content, vector_search to find similar content.
@@ -49583,10 +49624,12 @@ No peers connected.`);
49583
49624
  meta2.push(`model:${p.model}`);
49584
49625
  const metaStr = meta2.length ? ` {${meta2.join(", ")}}` : "";
49585
49626
  const cwdStr = p.cwd ? ` cwd:${p.cwd}` : "";
49627
+ const locality = p.hostname && p.hostname === __require("os").hostname() ? "local" : "remote";
49628
+ const localityTag = ` [${locality}]`;
49586
49629
  const profileAvatar = p.profile?.avatar ? `${p.profile.avatar} ` : "";
49587
49630
  const profileTitle = p.profile?.title ? ` (${p.profile.title})` : "";
49588
49631
  const hiddenTag = p.visible === false ? " [hidden]" : "";
49589
- return `- ${profileAvatar}**${p.displayName}**${profileTitle} [${p.status}]${hiddenTag}${groupsStr}${metaStr} (${p.pubkey.slice(0, 12)}…)${cwdStr}${summary}`;
49632
+ return `- ${profileAvatar}**${p.displayName}**${profileTitle} [${p.status}]${localityTag}${hiddenTag}${groupsStr}${metaStr} (${p.pubkey.slice(0, 12)}…)${cwdStr}${summary}`;
49590
49633
  });
49591
49634
  sections.push(`${header}
49592
49635
  ${peerLines.join(`
@@ -50423,16 +50466,17 @@ ${lines.join(`
50423
50466
  `));
50424
50467
  }
50425
50468
  case "mesh_mcp_register": {
50426
- const { server_name, description, tools: regTools } = args ?? {};
50469
+ const { server_name, description, tools: regTools, persistent: regPersistent } = args ?? {};
50427
50470
  if (!server_name || !description || !regTools?.length)
50428
50471
  return text("mesh_mcp_register: `server_name`, `description`, and `tools` required", true);
50429
50472
  const client2 = allClients()[0];
50430
50473
  if (!client2)
50431
50474
  return text("mesh_mcp_register: not connected", true);
50432
- const result = await client2.mcpRegister(server_name, description, regTools);
50475
+ const result = await client2.mcpRegister(server_name, description, regTools, regPersistent);
50433
50476
  if (!result)
50434
50477
  return text("mesh_mcp_register: broker did not acknowledge", true);
50435
- return text(`Registered MCP server "${result.serverName}" with ${result.toolCount} tool(s). Other peers can now call its tools via mesh_tool_call.`);
50478
+ const persistLabel = regPersistent ? " (persistent survives disconnect)" : "";
50479
+ return text(`Registered MCP server "${result.serverName}" with ${result.toolCount} tool(s)${persistLabel}. Other peers can now call its tools via mesh_tool_call.`);
50436
50480
  }
50437
50481
  case "mesh_mcp_list": {
50438
50482
  const client2 = allClients()[0];
@@ -50444,7 +50488,8 @@ ${lines.join(`
50444
50488
  const lines = servers.map((s) => {
50445
50489
  const toolList = s.tools.map((t) => ` - **${t.name}**: ${t.description}`).join(`
50446
50490
  `);
50447
- return `- **${s.name}** (hosted by ${s.hostedBy}): ${s.description}
50491
+ const status = s.online === false ? ` [OFFLINE${s.offlineSince ? ` since ${s.offlineSince}` : ""}]` : "";
50492
+ return `- **${s.name}** (hosted by ${s.hostedBy})${status}: ${s.description}
50448
50493
  ${toolList}`;
50449
50494
  });
50450
50495
  return text(`${servers.length} MCP server(s) in mesh:
@@ -50531,6 +50576,17 @@ ${lines.join(`
50531
50576
  targetPubkey = match.pubkey;
50532
50577
  }
50533
50578
  }
50579
+ const resolvedPeer = peers.find((p) => p.pubkey === targetPubkey);
50580
+ const isLocal = resolvedPeer?.hostname && resolvedPeer.hostname === __require("os").hostname();
50581
+ let localHint = "";
50582
+ if (isLocal && resolvedPeer?.cwd) {
50583
+ const directPath = __require("path").resolve(resolvedPeer.cwd, filePath);
50584
+ localHint = `
50585
+
50586
+ > **Hint:** This peer is LOCAL (same machine). Next time, read directly: \`${directPath}\` — faster, no size limit.
50587
+
50588
+ `;
50589
+ }
50534
50590
  const result = await client2.requestFile(targetPubkey, filePath);
50535
50591
  if (result.error)
50536
50592
  return text(`read_peer_file: ${result.error}`, true);
@@ -50538,7 +50594,7 @@ ${lines.join(`
50538
50594
  return text("read_peer_file: empty response from peer", true);
50539
50595
  try {
50540
50596
  const decoded = Buffer.from(result.content, "base64").toString("utf-8");
50541
- return text(decoded);
50597
+ return text(localHint + decoded);
50542
50598
  } catch {
50543
50599
  return text("read_peer_file: failed to decode file content (binary file?)", true);
50544
50600
  }
@@ -50638,8 +50694,21 @@ ${lines.join(`
50638
50694
  content2 = `[heartbeat] tick ${tick} | sim time: ${simTime} | speed: x${speed}`;
50639
50695
  } else if (eventName === "peer_joined") {
50640
50696
  content2 = `[system] Peer "${data.name ?? "unknown"}" joined the mesh`;
50697
+ } else if (eventName === "peer_returned") {
50698
+ const peerName = String(data.name ?? "unknown");
50699
+ const lastSeenAt = data.lastSeenAt ? relativeTime(String(data.lastSeenAt)) : "unknown";
50700
+ const groups = Array.isArray(data.groups) ? data.groups.map((g) => g.role ? `@${g.name}:${g.role}` : `@${g.name}`).join(", ") : "";
50701
+ const summary = data.summary ? ` Summary: "${data.summary}"` : "";
50702
+ content2 = `[system] Welcome back, "${peerName}"! Last seen ${lastSeenAt}.${groups ? ` Restored: ${groups}` : ""}${summary}`;
50641
50703
  } else if (eventName === "peer_left") {
50642
50704
  content2 = `[system] Peer "${data.name ?? "unknown"}" left the mesh`;
50705
+ } else if (eventName === "mcp_registered") {
50706
+ const tools = Array.isArray(data.tools) ? data.tools.join(", ") : "";
50707
+ content2 = `[system] New MCP server available: "${data.serverName}" (hosted by ${data.hostedBy}). Tools: ${tools}. Use mesh_tool_call to invoke.`;
50708
+ } else if (eventName === "mcp_unregistered") {
50709
+ content2 = `[system] MCP server "${data.serverName}" removed (was hosted by ${data.hostedBy})`;
50710
+ } else if (eventName === "mcp_restored") {
50711
+ content2 = `[system] MCP server "${data.serverName}" is back online (hosted by ${data.hostedBy})`;
50643
50712
  } else {
50644
50713
  content2 = `[system] ${eventName}: ${JSON.stringify(data)}`;
50645
50714
  }
@@ -51739,7 +51808,7 @@ init_config();
51739
51808
  // package.json
51740
51809
  var package_default = {
51741
51810
  name: "claudemesh-cli",
51742
- version: "0.7.0",
51811
+ version: "0.7.1",
51743
51812
  description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
51744
51813
  keywords: [
51745
51814
  "claude-code",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemesh-cli",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -48,10 +48,10 @@
48
48
  "prettier": "3.6.2",
49
49
  "typescript": "5.9.3",
50
50
  "vitest": "4.0.14",
51
+ "@turbostarter/eslint-config": "0.1.0",
51
52
  "@turbostarter/tsconfig": "0.1.0",
52
53
  "@turbostarter/prettier-config": "0.1.0",
53
- "@turbostarter/vitest-config": "0.1.0",
54
- "@turbostarter/eslint-config": "0.1.0"
54
+ "@turbostarter/vitest-config": "0.1.0"
55
55
  },
56
56
  "scripts": {
57
57
  "build": "bun build src/index.ts --target=node --outfile dist/index.js --banner \"#!/usr/bin/env node\" && chmod +x dist/index.js",