claudemesh-cli 0.1.3 → 0.1.5

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 +186 -43
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -54990,6 +54990,7 @@ class BrokerClient {
54990
54990
  outbound = [];
54991
54991
  pushHandlers = new Set;
54992
54992
  pushBuffer = [];
54993
+ listPeersResolvers = [];
54993
54994
  closed = false;
54994
54995
  reconnectAttempt = 0;
54995
54996
  helloTimer = null;
@@ -55013,7 +55014,7 @@ class BrokerClient {
55013
55014
  async connect() {
55014
55015
  if (this.closed)
55015
55016
  throw new Error("client is closed");
55016
- this.setStatus("connecting");
55017
+ this.setConnStatus("connecting");
55017
55018
  const ws = new wrapper_default(this.mesh.brokerUrl);
55018
55019
  this.ws = ws;
55019
55020
  return new Promise((resolve, reject) => {
@@ -55053,7 +55054,7 @@ class BrokerClient {
55053
55054
  if (this.helloTimer)
55054
55055
  clearTimeout(this.helloTimer);
55055
55056
  this.helloTimer = null;
55056
- this.setStatus("open");
55057
+ this.setConnStatus("open");
55057
55058
  this.reconnectAttempt = 0;
55058
55059
  this.flushOutbound();
55059
55060
  resolve();
@@ -55072,7 +55073,7 @@ class BrokerClient {
55072
55073
  if (!this.closed)
55073
55074
  this.scheduleReconnect();
55074
55075
  else
55075
- this.setStatus("closed");
55076
+ this.setConnStatus("closed");
55076
55077
  };
55077
55078
  const onError = (err) => {
55078
55079
  this.debug(`ws error: ${err.message}`);
@@ -55152,6 +55153,26 @@ class BrokerClient {
55152
55153
  return;
55153
55154
  this.ws.send(JSON.stringify({ type: "set_status", status }));
55154
55155
  }
55156
+ async listPeers() {
55157
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
55158
+ return [];
55159
+ return new Promise((resolve) => {
55160
+ this.listPeersResolvers.push(resolve);
55161
+ this.ws.send(JSON.stringify({ type: "list_peers" }));
55162
+ setTimeout(() => {
55163
+ const idx = this.listPeersResolvers.indexOf(resolve);
55164
+ if (idx !== -1) {
55165
+ this.listPeersResolvers.splice(idx, 1);
55166
+ resolve([]);
55167
+ }
55168
+ }, 5000);
55169
+ });
55170
+ }
55171
+ async setSummary(summary) {
55172
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN)
55173
+ return;
55174
+ this.ws.send(JSON.stringify({ type: "set_summary", summary }));
55175
+ }
55155
55176
  close() {
55156
55177
  this.closed = true;
55157
55178
  if (this.helloTimer)
@@ -55163,7 +55184,7 @@ class BrokerClient {
55163
55184
  this.ws.close();
55164
55185
  } catch {}
55165
55186
  }
55166
- this.setStatus("closed");
55187
+ this.setConnStatus("closed");
55167
55188
  }
55168
55189
  handleServerMessage(msg) {
55169
55190
  if (msg.type === "ack") {
@@ -55177,6 +55198,13 @@ class BrokerClient {
55177
55198
  }
55178
55199
  return;
55179
55200
  }
55201
+ if (msg.type === "peers_list") {
55202
+ const peers = msg.peers ?? [];
55203
+ const resolver = this.listPeersResolvers.shift();
55204
+ if (resolver)
55205
+ resolver(peers);
55206
+ return;
55207
+ }
55180
55208
  if (msg.type === "push") {
55181
55209
  const nonce = String(msg.nonce ?? "");
55182
55210
  const ciphertext = String(msg.ciphertext ?? "");
@@ -55239,7 +55267,7 @@ class BrokerClient {
55239
55267
  send();
55240
55268
  }
55241
55269
  scheduleReconnect() {
55242
- this.setStatus("reconnecting");
55270
+ this.setConnStatus("reconnecting");
55243
55271
  const delay = BACKOFF_CAPS[Math.min(this.reconnectAttempt, BACKOFF_CAPS.length - 1)];
55244
55272
  this.reconnectAttempt += 1;
55245
55273
  this.debug(`reconnect in ${delay}ms (attempt ${this.reconnectAttempt})`);
@@ -55251,7 +55279,7 @@ class BrokerClient {
55251
55279
  });
55252
55280
  }, delay);
55253
55281
  }
55254
- setStatus(s) {
55282
+ setConnStatus(s) {
55255
55283
  if (this._status === s)
55256
55284
  return;
55257
55285
  this._status = s;
@@ -55344,7 +55372,7 @@ ${body}`;
55344
55372
  }
55345
55373
  async function startMcpServer() {
55346
55374
  const config2 = loadConfig();
55347
- const server = new Server({ name: "claudemesh", version: "0.1.3" }, {
55375
+ const server = new Server({ name: "claudemesh", version: "0.1.4" }, {
55348
55376
  capabilities: {
55349
55377
  experimental: { "claude/channel": {} },
55350
55378
  tools: {}
@@ -55395,12 +55423,26 @@ If you have multiple joined meshes, prefix the \`to\` argument of send_message w
55395
55423
  const clients2 = mesh_slug ? [findClient(mesh_slug)].filter(Boolean) : allClients();
55396
55424
  if (clients2.length === 0)
55397
55425
  return text(mesh_slug ? `list_peers: no joined mesh "${mesh_slug}"` : "list_peers: no joined meshes", true);
55398
- const lines = clients2.map((c) => `- ${c.meshSlug} (${c.status}, mesh ${c.meshId.slice(0, 8)}…)`);
55399
- return text(`Connected meshes:
55400
- ${lines.join(`
55401
- `)}
55426
+ const sections = [];
55427
+ for (const c of clients2) {
55428
+ const peers = await c.listPeers();
55429
+ const header = `## ${c.meshSlug} (${c.status}, mesh ${c.meshId.slice(0, 8)}…)`;
55430
+ if (peers.length === 0) {
55431
+ sections.push(`${header}
55432
+ No peers connected.`);
55433
+ } else {
55434
+ const peerLines = peers.map((p) => {
55435
+ const summary = p.summary ? ` — "${p.summary}"` : "";
55436
+ return `- **${p.displayName}** [${p.status}] (${p.pubkey.slice(0, 12)}…)${summary}`;
55437
+ });
55438
+ sections.push(`${header}
55439
+ ${peerLines.join(`
55440
+ `)}`);
55441
+ }
55442
+ }
55443
+ return text(sections.join(`
55402
55444
 
55403
- (list_peers WS protocol lands in Step 16; only mesh status is shown for now.)`);
55445
+ `));
55404
55446
  }
55405
55447
  case "check_messages": {
55406
55448
  const drained = [];
@@ -55423,7 +55465,9 @@ ${drained.join(`
55423
55465
  const { summary } = args ?? {};
55424
55466
  if (!summary)
55425
55467
  return text("set_summary: `summary` required", true);
55426
- return text(`set_summary: summary recorded locally ("${summary}"). (Broker WS protocol for summaries lands in Step 16.)`);
55468
+ for (const c of allClients())
55469
+ await c.setSummary(summary);
55470
+ return text(`Summary set: "${summary}" (visible to ${allClients().length} mesh(es)).`);
55427
55471
  }
55428
55472
  case "set_status": {
55429
55473
  const { status } = args ?? {};
@@ -55477,6 +55521,7 @@ ${drained.join(`
55477
55521
  // src/commands/install.ts
55478
55522
  import {
55479
55523
  chmodSync as chmodSync2,
55524
+ copyFileSync,
55480
55525
  existsSync as existsSync2,
55481
55526
  mkdirSync as mkdirSync2,
55482
55527
  readFileSync as readFileSync2,
@@ -55504,7 +55549,49 @@ function readClaudeConfig() {
55504
55549
  throw new Error(`failed to parse ${CLAUDE_CONFIG}: ${e instanceof Error ? e.message : String(e)}`);
55505
55550
  }
55506
55551
  }
55507
- function writeClaudeConfig(obj) {
55552
+ function backupClaudeConfig() {
55553
+ if (!existsSync2(CLAUDE_CONFIG))
55554
+ return;
55555
+ const backupDir = join2(dirname2(CLAUDE_CONFIG), ".claude", "backups");
55556
+ mkdirSync2(backupDir, { recursive: true });
55557
+ const ts = Date.now();
55558
+ const dest = join2(backupDir, `.claude.json.pre-claudemesh.${ts}`);
55559
+ copyFileSync(CLAUDE_CONFIG, dest);
55560
+ }
55561
+ function patchMcpServer(entry) {
55562
+ backupClaudeConfig();
55563
+ const cfg = readClaudeConfig();
55564
+ const servers = cfg.mcpServers ?? {};
55565
+ if (!cfg.mcpServers)
55566
+ cfg.mcpServers = servers;
55567
+ const existing = servers[MCP_NAME];
55568
+ let action;
55569
+ if (!existing) {
55570
+ servers[MCP_NAME] = entry;
55571
+ action = "added";
55572
+ } else if (entriesEqual(existing, entry)) {
55573
+ return "unchanged";
55574
+ } else {
55575
+ servers[MCP_NAME] = entry;
55576
+ action = "updated";
55577
+ }
55578
+ flushClaudeConfig(cfg);
55579
+ return action;
55580
+ }
55581
+ function removeMcpServer() {
55582
+ if (!existsSync2(CLAUDE_CONFIG))
55583
+ return false;
55584
+ backupClaudeConfig();
55585
+ const cfg = readClaudeConfig();
55586
+ const servers = cfg.mcpServers;
55587
+ if (!servers || !(MCP_NAME in servers))
55588
+ return false;
55589
+ delete servers[MCP_NAME];
55590
+ cfg.mcpServers = servers;
55591
+ flushClaudeConfig(cfg);
55592
+ return true;
55593
+ }
55594
+ function flushClaudeConfig(obj) {
55508
55595
  mkdirSync2(dirname2(CLAUDE_CONFIG), { recursive: true });
55509
55596
  writeFileSync2(CLAUDE_CONFIG, JSON.stringify(obj, null, 2) + `
55510
55597
  `, "utf-8");
@@ -55616,22 +55703,8 @@ function runInstall(args = []) {
55616
55703
  console.error(`✗ MCP entry not found at ${entry}`);
55617
55704
  process.exit(1);
55618
55705
  }
55619
- const cfg = readClaudeConfig();
55620
- const servers = (cfg.mcpServers ??= {}) ?? {};
55621
55706
  const desired = buildMcpEntry(entry);
55622
- const existing = servers[MCP_NAME];
55623
- let action;
55624
- if (!existing) {
55625
- servers[MCP_NAME] = desired;
55626
- action = "added";
55627
- } else if (entriesEqual(existing, desired)) {
55628
- action = "unchanged";
55629
- } else {
55630
- servers[MCP_NAME] = desired;
55631
- action = "updated";
55632
- }
55633
- cfg.mcpServers = servers;
55634
- writeClaudeConfig(cfg);
55707
+ const action = patchMcpServer(desired);
55635
55708
  const verify = readClaudeConfig();
55636
55709
  const verifyServers = verify.mcpServers ?? {};
55637
55710
  const stored = verifyServers[MCP_NAME];
@@ -55674,19 +55747,10 @@ function runInstall(args = []) {
55674
55747
  function runUninstall() {
55675
55748
  console.log("claudemesh uninstall");
55676
55749
  console.log("--------------------");
55677
- if (existsSync2(CLAUDE_CONFIG)) {
55678
- const cfg = readClaudeConfig();
55679
- const servers = cfg.mcpServers;
55680
- if (servers && MCP_NAME in servers) {
55681
- delete servers[MCP_NAME];
55682
- cfg.mcpServers = servers;
55683
- writeClaudeConfig(cfg);
55684
- console.log(`✓ MCP server "${MCP_NAME}" removed`);
55685
- } else {
55686
- console.log(`· MCP server "${MCP_NAME}" not present`);
55687
- }
55750
+ if (removeMcpServer()) {
55751
+ console.log(`✓ MCP server "${MCP_NAME}" removed`);
55688
55752
  } else {
55689
- console.log(`· no ${CLAUDE_CONFIG} MCP entry skipped`);
55753
+ console.log(`· MCP server "${MCP_NAME}" not present`);
55690
55754
  }
55691
55755
  try {
55692
55756
  const removed = uninstallHooks();
@@ -56086,7 +56150,7 @@ import { statSync, existsSync as existsSync3 } from "node:fs";
56086
56150
  // package.json
56087
56151
  var package_default = {
56088
56152
  name: "claudemesh-cli",
56089
- version: "0.1.3",
56153
+ version: "0.1.5",
56090
56154
  description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
56091
56155
  keywords: [
56092
56156
  "claude-code",
@@ -56414,6 +56478,83 @@ async function runDoctor() {
56414
56478
  }
56415
56479
  }
56416
56480
 
56481
+ // src/commands/welcome.ts
56482
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
56483
+ import { homedir as homedir4 } from "node:os";
56484
+ import { join as join4 } from "node:path";
56485
+ function detectState() {
56486
+ const claudeConfig = join4(homedir4(), ".claude.json");
56487
+ let mcpRegistered = false;
56488
+ if (existsSync5(claudeConfig)) {
56489
+ try {
56490
+ const cfg = JSON.parse(readFileSync4(claudeConfig, "utf-8"));
56491
+ mcpRegistered = Boolean(cfg.mcpServers?.["claudemesh"]);
56492
+ } catch {}
56493
+ }
56494
+ if (!mcpRegistered)
56495
+ return "no-install";
56496
+ try {
56497
+ const cfg = loadConfig();
56498
+ return cfg.meshes.length === 0 ? "no-meshes" : "ready";
56499
+ } catch {
56500
+ return "broken-config";
56501
+ }
56502
+ }
56503
+ function runWelcome() {
56504
+ const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
56505
+ const bold = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
56506
+ const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
56507
+ const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
56508
+ const yellow = (s) => useColor ? `\x1B[33m${s}\x1B[39m` : s;
56509
+ console.log(bold(`claudemesh v${VERSION}`) + dim(" — peer mesh for Claude Code"));
56510
+ console.log("─".repeat(60));
56511
+ const state = detectState();
56512
+ switch (state) {
56513
+ case "no-install":
56514
+ console.log("Welcome. Let's get you set up.");
56515
+ console.log("");
56516
+ console.log(bold("Step 1:") + " register the MCP server + status hooks");
56517
+ console.log(` ${green("$")} claudemesh install`);
56518
+ console.log("");
56519
+ console.log(dim("Step 2 (after restart): claudemesh join <invite-url>"));
56520
+ console.log(dim("Step 3: claudemesh launch"));
56521
+ break;
56522
+ case "no-meshes":
56523
+ console.log(green("✓") + " MCP registered. Now join a mesh.");
56524
+ console.log("");
56525
+ console.log(bold("Step 2:") + " join a mesh");
56526
+ console.log(` ${green("$")} claudemesh join https://claudemesh.com/join/<token>`);
56527
+ console.log("");
56528
+ console.log(dim(" Don't have an invite? Create one at ") + bold("https://claudemesh.com") + dim(" or ask a mesh owner."));
56529
+ console.log("");
56530
+ console.log(dim("Step 3 (after joining): claudemesh launch"));
56531
+ break;
56532
+ case "ready": {
56533
+ const cfg = loadConfig();
56534
+ const meshNames = cfg.meshes.map((m) => m.slug).join(", ");
56535
+ console.log(green("✓") + " MCP registered.");
56536
+ console.log(green("✓") + ` ${cfg.meshes.length} mesh(es) joined: ${meshNames}`);
56537
+ console.log("");
56538
+ console.log(bold("You're ready.") + " Launch Claude Code with real-time peer messages:");
56539
+ console.log(` ${green("$")} claudemesh launch`);
56540
+ console.log("");
56541
+ console.log(dim(" (Plain `claude` works too — messages pull-only via check_messages.)"));
56542
+ console.log("");
56543
+ console.log(dim("Health check: claudemesh status"));
56544
+ console.log(dim("Diagnostics: claudemesh doctor"));
56545
+ console.log(dim("All commands: claudemesh --help"));
56546
+ break;
56547
+ }
56548
+ case "broken-config":
56549
+ console.log(yellow("⚠") + " Your ~/.claudemesh/config.json is unreadable.");
56550
+ console.log("");
56551
+ console.log("Run diagnostics to see what's wrong:");
56552
+ console.log(` ${green("$")} claudemesh doctor`);
56553
+ break;
56554
+ }
56555
+ console.log("");
56556
+ }
56557
+
56417
56558
  // src/index.ts
56418
56559
  var HELP = `claudemesh v${VERSION} — peer mesh for Claude Code sessions
56419
56560
 
@@ -56487,9 +56628,11 @@ async function main() {
56487
56628
  case "--help":
56488
56629
  case "-h":
56489
56630
  case "help":
56490
- case undefined:
56491
56631
  console.log(HELP);
56492
56632
  return;
56633
+ case undefined:
56634
+ runWelcome();
56635
+ return;
56493
56636
  default:
56494
56637
  console.error(`Unknown command: ${cmd}`);
56495
56638
  console.error("Run `claudemesh --help` for usage.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemesh-cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
5
5
  "keywords": [
6
6
  "claude-code",