claudemesh-cli 0.8.9 → 0.9.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 +680 -134
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6514,7 +6514,7 @@ function loadConfig() {
6514
6514
  if (!parsed || !Array.isArray(parsed.meshes)) {
6515
6515
  return { version: 1, meshes: [] };
6516
6516
  }
6517
- return { version: 1, meshes: parsed.meshes, displayName: parsed.displayName, role: parsed.role, groups: parsed.groups, messageMode: parsed.messageMode };
6517
+ return { version: 1, meshes: parsed.meshes, displayName: parsed.displayName, role: parsed.role, groups: parsed.groups, messageMode: parsed.messageMode, accountId: parsed.accountId };
6518
6518
  } catch (e) {
6519
6519
  throw new Error(`Failed to load ${CONFIG_PATH}: ${e instanceof Error ? e.message : String(e)}`);
6520
6520
  }
@@ -40005,6 +40005,48 @@ var init_file_crypto = __esm(() => {
40005
40005
  init_keypair();
40006
40006
  });
40007
40007
 
40008
+ // src/auth/sync-with-broker.ts
40009
+ var exports_sync_with_broker = {};
40010
+ __export(exports_sync_with_broker, {
40011
+ syncWithBroker: () => syncWithBroker
40012
+ });
40013
+ async function syncWithBroker(syncToken, peerPubkey, displayName, brokerBaseUrl) {
40014
+ const base = brokerBaseUrl ?? deriveHttpUrl(process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws");
40015
+ const res = await fetch(`${base}/cli-sync`, {
40016
+ method: "POST",
40017
+ headers: { "Content-Type": "application/json" },
40018
+ body: JSON.stringify({
40019
+ sync_token: syncToken,
40020
+ peer_pubkey: peerPubkey,
40021
+ display_name: displayName
40022
+ })
40023
+ });
40024
+ if (!res.ok) {
40025
+ const body2 = await res.text();
40026
+ let msg;
40027
+ try {
40028
+ msg = JSON.parse(body2).error ?? body2;
40029
+ } catch {
40030
+ msg = body2;
40031
+ }
40032
+ throw new Error(`Broker sync failed (${res.status}): ${msg}`);
40033
+ }
40034
+ const body = await res.json();
40035
+ if (!body.ok) {
40036
+ throw new Error(`Broker sync failed: ${body.error ?? "unknown error"}`);
40037
+ }
40038
+ return {
40039
+ account_id: body.account_id,
40040
+ meshes: body.meshes
40041
+ };
40042
+ }
40043
+ function deriveHttpUrl(wssUrl) {
40044
+ const url = new URL(wssUrl);
40045
+ url.protocol = url.protocol === "wss:" ? "https:" : "http:";
40046
+ url.pathname = url.pathname.replace(/\/ws\/?$/, "");
40047
+ return url.toString().replace(/\/$/, "");
40048
+ }
40049
+
40008
40050
  // ../../node_modules/citty/dist/_chunks/libs/scule.mjs
40009
40051
  var NUMBER_CHAR_RE = /\d/;
40010
40052
  var STR_SPLITTERS = [
@@ -47547,18 +47589,29 @@ var TOOLS = [
47547
47589
  },
47548
47590
  {
47549
47591
  name: "share_skill",
47550
- description: "Publish a reusable skill to the mesh. Other peers can discover and load it. If a skill with the same name exists, it is updated.",
47592
+ description: "Publish a reusable skill to the mesh. Other peers can discover and load it as a slash command. If a skill with the same name exists, it is updated. Skills are automatically exposed as MCP prompts and skill:// resources for native Claude Code integration.",
47551
47593
  inputSchema: {
47552
47594
  type: "object",
47553
47595
  properties: {
47554
- name: { type: "string", description: "Unique skill name (e.g. 'code-review', 'deploy-checklist')" },
47596
+ name: { type: "string", description: "Unique skill name (e.g. 'code-review', 'deploy-checklist'). Becomes the slash command name." },
47555
47597
  description: { type: "string", description: "Short description of what the skill does" },
47556
- instructions: { type: "string", description: "Full instructions/prompt that a peer loads to acquire this capability" },
47598
+ instructions: { type: "string", description: "Full instructions/prompt markdown. Can include frontmatter (---) block." },
47557
47599
  tags: {
47558
47600
  type: "array",
47559
47601
  items: { type: "string" },
47560
47602
  description: "Tags for discoverability"
47561
- }
47603
+ },
47604
+ when_to_use: { type: "string", description: "Detailed description of when Claude should auto-invoke this skill" },
47605
+ allowed_tools: {
47606
+ type: "array",
47607
+ items: { type: "string" },
47608
+ description: "Tool names this skill is allowed to use (e.g. ['Bash', 'Read', 'Edit'])"
47609
+ },
47610
+ model: { type: "string", description: "Model override (e.g. 'sonnet', 'opus', 'haiku')" },
47611
+ context: { type: "string", enum: ["inline", "fork"], description: "Execution context: 'inline' (default) or 'fork' (sub-agent)" },
47612
+ agent: { type: "string", description: "Agent type when forked (e.g. 'general-purpose')" },
47613
+ user_invocable: { type: "boolean", description: "Whether users can invoke via /skill-name (default: true)" },
47614
+ argument_hint: { type: "string", description: "Hint text for arguments (e.g. '<file-path>')" }
47562
47615
  },
47563
47616
  required: ["name", "description", "instructions"]
47564
47617
  }
@@ -48702,7 +48755,7 @@ class BrokerClient {
48702
48755
  skillAckResolvers = new Map;
48703
48756
  skillDataResolvers = new Map;
48704
48757
  skillListResolvers = new Map;
48705
- async shareSkill(name, description, instructions, tags) {
48758
+ async shareSkill(name, description, instructions, tags, manifest) {
48706
48759
  if (!this.ws || this.ws.readyState !== this.ws.OPEN)
48707
48760
  return null;
48708
48761
  return new Promise((resolve) => {
@@ -48713,7 +48766,7 @@ class BrokerClient {
48713
48766
  if (this.skillAckResolvers.delete(reqId))
48714
48767
  resolve(null);
48715
48768
  }, 5000) });
48716
- this.ws.send(JSON.stringify({ type: "share_skill", name, description, instructions, tags, _reqId: reqId }));
48769
+ this.ws.send(JSON.stringify({ type: "share_skill", name, description, instructions, tags, manifest, _reqId: reqId }));
48717
48770
  });
48718
48771
  }
48719
48772
  async getSkill(name) {
@@ -49890,7 +49943,9 @@ async function startMcpServer() {
49890
49943
  const server = new Server({ name: "claudemesh", version: "0.3.0" }, {
49891
49944
  capabilities: {
49892
49945
  experimental: { "claude/channel": {} },
49893
- tools: {}
49946
+ tools: {},
49947
+ prompts: {},
49948
+ resources: {}
49894
49949
  },
49895
49950
  instructions: `## Identity
49896
49951
  You are "${myName}"${myRole ? ` (${myRole})` : ""} — 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.
@@ -50018,6 +50073,129 @@ Your message mode is "${messageMode}".
50018
50073
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
50019
50074
  tools: TOOLS
50020
50075
  }));
50076
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
50077
+ const client2 = allClients()[0];
50078
+ if (!client2)
50079
+ return { prompts: [] };
50080
+ const skills = await client2.listSkills();
50081
+ return {
50082
+ prompts: skills.map((s) => ({
50083
+ name: s.name,
50084
+ description: s.description,
50085
+ arguments: []
50086
+ }))
50087
+ };
50088
+ });
50089
+ server.setRequestHandler(GetPromptRequestSchema, async (req) => {
50090
+ const { name, arguments: promptArgs } = req.params;
50091
+ const client2 = allClients()[0];
50092
+ if (!client2)
50093
+ throw new Error("Not connected to any mesh");
50094
+ const skill = await client2.getSkill(name);
50095
+ if (!skill)
50096
+ throw new Error(`Skill "${name}" not found in the mesh`);
50097
+ let content = skill.instructions;
50098
+ const manifest = skill.manifest;
50099
+ if (manifest && typeof manifest === "object") {
50100
+ const fm = ["---"];
50101
+ if (manifest.description)
50102
+ fm.push(`description: "${manifest.description}"`);
50103
+ if (manifest.when_to_use)
50104
+ fm.push(`when_to_use: "${manifest.when_to_use}"`);
50105
+ if (manifest.allowed_tools?.length)
50106
+ fm.push(`allowed-tools:
50107
+ ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
50108
+ `)}`);
50109
+ if (manifest.model)
50110
+ fm.push(`model: ${manifest.model}`);
50111
+ if (manifest.context)
50112
+ fm.push(`context: ${manifest.context}`);
50113
+ if (manifest.agent)
50114
+ fm.push(`agent: ${manifest.agent}`);
50115
+ if (manifest.user_invocable === false)
50116
+ fm.push(`user-invocable: false`);
50117
+ if (manifest.argument_hint)
50118
+ fm.push(`argument-hint: "${manifest.argument_hint}"`);
50119
+ fm.push(`---
50120
+ `);
50121
+ if (fm.length > 3)
50122
+ content = fm.join(`
50123
+ `) + content;
50124
+ }
50125
+ return {
50126
+ description: skill.description,
50127
+ messages: [
50128
+ {
50129
+ role: "user",
50130
+ content: { type: "text", text: content }
50131
+ }
50132
+ ]
50133
+ };
50134
+ });
50135
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
50136
+ const client2 = allClients()[0];
50137
+ if (!client2)
50138
+ return { resources: [] };
50139
+ const skills = await client2.listSkills();
50140
+ return {
50141
+ resources: skills.map((s) => ({
50142
+ uri: `skill://claudemesh/${encodeURIComponent(s.name)}`,
50143
+ name: s.name,
50144
+ description: s.description,
50145
+ mimeType: "text/markdown"
50146
+ }))
50147
+ };
50148
+ });
50149
+ server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
50150
+ const { uri } = req.params;
50151
+ const match = uri.match(/^skill:\/\/claudemesh\/(.+)$/);
50152
+ if (!match)
50153
+ throw new Error(`Unknown resource URI: ${uri}`);
50154
+ const name = decodeURIComponent(match[1]);
50155
+ const client2 = allClients()[0];
50156
+ if (!client2)
50157
+ throw new Error("Not connected to any mesh");
50158
+ const skill = await client2.getSkill(name);
50159
+ if (!skill)
50160
+ throw new Error(`Skill "${name}" not found`);
50161
+ const manifest = skill.manifest;
50162
+ const fmLines = ["---"];
50163
+ fmLines.push(`name: ${skill.name}`);
50164
+ fmLines.push(`description: "${skill.description}"`);
50165
+ if (skill.tags.length)
50166
+ fmLines.push(`tags: [${skill.tags.join(", ")}]`);
50167
+ if (manifest && typeof manifest === "object") {
50168
+ if (manifest.when_to_use)
50169
+ fmLines.push(`when_to_use: "${manifest.when_to_use}"`);
50170
+ if (manifest.allowed_tools?.length)
50171
+ fmLines.push(`allowed-tools:
50172
+ ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
50173
+ `)}`);
50174
+ if (manifest.model)
50175
+ fmLines.push(`model: ${manifest.model}`);
50176
+ if (manifest.context)
50177
+ fmLines.push(`context: ${manifest.context}`);
50178
+ if (manifest.agent)
50179
+ fmLines.push(`agent: ${manifest.agent}`);
50180
+ if (manifest.user_invocable === false)
50181
+ fmLines.push(`user-invocable: false`);
50182
+ if (manifest.argument_hint)
50183
+ fmLines.push(`argument-hint: "${manifest.argument_hint}"`);
50184
+ }
50185
+ fmLines.push(`---
50186
+ `);
50187
+ const fullContent = fmLines.join(`
50188
+ `) + skill.instructions;
50189
+ return {
50190
+ contents: [
50191
+ {
50192
+ uri,
50193
+ mimeType: "text/markdown",
50194
+ text: fullContent
50195
+ }
50196
+ ]
50197
+ };
50198
+ });
50021
50199
  server.setRequestHandler(CallToolRequestSchema, async (req) => {
50022
50200
  const { name, arguments: args } = req.params;
50023
50201
  for (const c of allClients()) {
@@ -50826,16 +51004,45 @@ ${rows.join(`
50826
51004
  `));
50827
51005
  }
50828
51006
  case "share_skill": {
50829
- const { name: skillName, description: skillDesc, instructions: skillInstr, tags: skillTags } = args ?? {};
51007
+ const {
51008
+ name: skillName,
51009
+ description: skillDesc,
51010
+ instructions: skillInstr,
51011
+ tags: skillTags,
51012
+ when_to_use,
51013
+ allowed_tools,
51014
+ model,
51015
+ context: skillContext,
51016
+ agent,
51017
+ user_invocable,
51018
+ argument_hint
51019
+ } = args ?? {};
50830
51020
  if (!skillName || !skillDesc || !skillInstr)
50831
51021
  return text("share_skill: `name`, `description`, and `instructions` required", true);
50832
51022
  const client2 = allClients()[0];
50833
51023
  if (!client2)
50834
51024
  return text("share_skill: not connected", true);
50835
- const result = await client2.shareSkill(skillName, skillDesc, skillInstr, skillTags);
51025
+ const manifest = {};
51026
+ if (when_to_use)
51027
+ manifest.when_to_use = when_to_use;
51028
+ if (allowed_tools?.length)
51029
+ manifest.allowed_tools = allowed_tools;
51030
+ if (model)
51031
+ manifest.model = model;
51032
+ if (skillContext)
51033
+ manifest.context = skillContext;
51034
+ if (agent)
51035
+ manifest.agent = agent;
51036
+ if (user_invocable === false)
51037
+ manifest.user_invocable = false;
51038
+ if (argument_hint)
51039
+ manifest.argument_hint = argument_hint;
51040
+ const result = await client2.shareSkill(skillName, skillDesc, skillInstr, skillTags, Object.keys(manifest).length > 0 ? manifest : undefined);
50836
51041
  if (!result)
50837
51042
  return text("share_skill: broker did not acknowledge", true);
50838
- return text(`Skill "${skillName}" published to the mesh.`);
51043
+ server.notification({ method: "notifications/prompts/list_changed" });
51044
+ server.notification({ method: "notifications/resources/list_changed" });
51045
+ return text(`Skill "${skillName}" published to the mesh. It will appear as /claudemesh:${skillName} in Claude Code.`);
50839
51046
  }
50840
51047
  case "get_skill": {
50841
51048
  const { name: gsName } = args ?? {};
@@ -50847,13 +51054,30 @@ ${rows.join(`
50847
51054
  const skill = await client2.getSkill(gsName);
50848
51055
  if (!skill)
50849
51056
  return text(`Skill "${gsName}" not found in the mesh.`);
51057
+ const manifest = skill.manifest;
51058
+ const metaLines = [];
51059
+ if (manifest) {
51060
+ if (manifest.when_to_use)
51061
+ metaLines.push(`**When to use:** ${manifest.when_to_use}`);
51062
+ if (manifest.allowed_tools)
51063
+ metaLines.push(`**Allowed tools:** ${manifest.allowed_tools.join(", ")}`);
51064
+ if (manifest.model)
51065
+ metaLines.push(`**Model:** ${manifest.model}`);
51066
+ if (manifest.context)
51067
+ metaLines.push(`**Context:** ${manifest.context}`);
51068
+ if (manifest.agent)
51069
+ metaLines.push(`**Agent:** ${manifest.agent}`);
51070
+ }
50850
51071
  return text(`# Skill: ${skill.name}
50851
51072
 
50852
51073
  **Description:** ${skill.description}
50853
51074
  **Author:** ${skill.author}
50854
51075
  **Tags:** ${skill.tags.length ? skill.tags.join(", ") : "none"}
50855
51076
  **Created:** ${skill.createdAt}
50856
-
51077
+ **Slash command:** /claudemesh:${skill.name}
51078
+ ` + (metaLines.length ? metaLines.join(`
51079
+ `) + `
51080
+ ` : "") + `
50857
51081
  ---
50858
51082
 
50859
51083
  ## Instructions
@@ -50881,6 +51105,10 @@ ${lines.join(`
50881
51105
  if (!client2)
50882
51106
  return text("remove_skill: not connected", true);
50883
51107
  const removed = await client2.removeSkill(rsName);
51108
+ if (removed) {
51109
+ server.notification({ method: "notifications/prompts/list_changed" });
51110
+ server.notification({ method: "notifications/resources/list_changed" });
51111
+ }
50884
51112
  return text(removed ? `Skill "${rsName}" removed.` : `Skill "${rsName}" not found.`, !removed);
50885
51113
  }
50886
51114
  case "ping_mesh": {
@@ -51394,159 +51622,163 @@ ${lines.join(`
51394
51622
  return text(`Unknown tool: ${name}`, true);
51395
51623
  }
51396
51624
  });
51397
- await startClients(config2);
51398
51625
  const transport = new StdioServerTransport;
51399
51626
  await server.connect(transport);
51400
- for (const client2 of allClients()) {
51401
- client2.onPush(async (msg) => {
51402
- if (messageMode === "off")
51403
- return;
51404
- if (msg.subtype === "system" && msg.event) {
51405
- const eventName = msg.event;
51406
- const data = msg.eventData ?? {};
51407
- let content2;
51408
- if (eventName === "tick") {
51409
- const tick = data.tick ?? 0;
51410
- const simTime = String(data.simTime ?? "").replace("T", " ").replace(/\..*/, "");
51411
- const speed = data.speed ?? 1;
51412
- content2 = `[heartbeat] tick ${tick} | sim time: ${simTime} | speed: x${speed}`;
51413
- } else if (eventName === "peer_joined") {
51414
- content2 = `[system] Peer "${data.name ?? "unknown"}" joined the mesh`;
51415
- } else if (eventName === "peer_returned") {
51416
- const peerName = String(data.name ?? "unknown");
51417
- const lastSeenAt = data.lastSeenAt ? relativeTime(String(data.lastSeenAt)) : "unknown";
51418
- const groups = Array.isArray(data.groups) ? data.groups.map((g) => g.role ? `@${g.name}:${g.role}` : `@${g.name}`).join(", ") : "";
51419
- const summary = data.summary ? ` Summary: "${data.summary}"` : "";
51420
- content2 = `[system] Welcome back, "${peerName}"! Last seen ${lastSeenAt}.${groups ? ` Restored: ${groups}` : ""}${summary}`;
51421
- } else if (eventName === "peer_left") {
51422
- content2 = `[system] Peer "${data.name ?? "unknown"}" left the mesh`;
51423
- } else if (eventName === "mcp_registered") {
51424
- const tools = Array.isArray(data.tools) ? data.tools.join(", ") : "";
51425
- content2 = `[system] New MCP server available: "${data.serverName}" (hosted by ${data.hostedBy}). Tools: ${tools}. Use mesh_tool_call to invoke.`;
51426
- } else if (eventName === "mcp_unregistered") {
51427
- content2 = `[system] MCP server "${data.serverName}" removed (was hosted by ${data.hostedBy})`;
51428
- } else if (eventName === "mcp_restored") {
51429
- content2 = `[system] MCP server "${data.serverName}" is back online (hosted by ${data.hostedBy})`;
51430
- } else if (eventName === "watch_triggered") {
51431
- content2 = `[WATCH] ${data.label ?? data.url}: ${data.oldValue} ${data.newValue}`;
51432
- } else if (eventName === "mcp_deployed") {
51433
- content2 = `[SERVICE] "${data.name}" deployed (${data.tool_count} tools) by ${data.deployed_by}`;
51434
- } else if (eventName === "mcp_undeployed") {
51435
- content2 = `[SERVICE] "${data.name}" undeployed by ${data.by}`;
51436
- } else if (eventName === "mcp_scope_changed") {
51437
- content2 = `[SERVICE] "${data.name}" scope changed to ${JSON.stringify(data.scope)} by ${data.by}`;
51438
- } else {
51439
- content2 = `[system] ${eventName}: ${JSON.stringify(data)}`;
51627
+ startClients(config2).then(() => {
51628
+ wirePushHandlers();
51629
+ }).catch(() => {
51630
+ wirePushHandlers();
51631
+ });
51632
+ async function wirePushHandlers() {
51633
+ for (const client2 of allClients()) {
51634
+ client2.onPush(async (msg) => {
51635
+ if (messageMode === "off")
51636
+ return;
51637
+ if (msg.subtype === "system" && msg.event) {
51638
+ const eventName = msg.event;
51639
+ const data = msg.eventData ?? {};
51640
+ let content2;
51641
+ if (eventName === "tick") {
51642
+ const tick = data.tick ?? 0;
51643
+ const simTime = String(data.simTime ?? "").replace("T", " ").replace(/\..*/, "");
51644
+ const speed = data.speed ?? 1;
51645
+ content2 = `[heartbeat] tick ${tick} | sim time: ${simTime} | speed: x${speed}`;
51646
+ } else if (eventName === "peer_joined") {
51647
+ content2 = `[system] Peer "${data.name ?? "unknown"}" joined the mesh`;
51648
+ } else if (eventName === "peer_returned") {
51649
+ const peerName = String(data.name ?? "unknown");
51650
+ const lastSeenAt = data.lastSeenAt ? relativeTime(String(data.lastSeenAt)) : "unknown";
51651
+ const groups = Array.isArray(data.groups) ? data.groups.map((g) => g.role ? `@${g.name}:${g.role}` : `@${g.name}`).join(", ") : "";
51652
+ const summary = data.summary ? ` Summary: "${data.summary}"` : "";
51653
+ content2 = `[system] Welcome back, "${peerName}"! Last seen ${lastSeenAt}.${groups ? ` Restored: ${groups}` : ""}${summary}`;
51654
+ } else if (eventName === "peer_left") {
51655
+ content2 = `[system] Peer "${data.name ?? "unknown"}" left the mesh`;
51656
+ } else if (eventName === "mcp_registered") {
51657
+ const tools = Array.isArray(data.tools) ? data.tools.join(", ") : "";
51658
+ content2 = `[system] New MCP server available: "${data.serverName}" (hosted by ${data.hostedBy}). Tools: ${tools}. Use mesh_tool_call to invoke.`;
51659
+ } else if (eventName === "mcp_unregistered") {
51660
+ content2 = `[system] MCP server "${data.serverName}" removed (was hosted by ${data.hostedBy})`;
51661
+ } else if (eventName === "mcp_restored") {
51662
+ content2 = `[system] MCP server "${data.serverName}" is back online (hosted by ${data.hostedBy})`;
51663
+ } else if (eventName === "watch_triggered") {
51664
+ content2 = `[WATCH] ${data.label ?? data.url}: ${data.oldValue} ${data.newValue}`;
51665
+ } else if (eventName === "mcp_deployed") {
51666
+ content2 = `[SERVICE] "${data.name}" deployed (${data.tool_count} tools) by ${data.deployed_by}`;
51667
+ } else if (eventName === "mcp_undeployed") {
51668
+ content2 = `[SERVICE] "${data.name}" undeployed by ${data.by}`;
51669
+ } else if (eventName === "mcp_scope_changed") {
51670
+ content2 = `[SERVICE] "${data.name}" scope changed to ${JSON.stringify(data.scope)} by ${data.by}`;
51671
+ } else {
51672
+ content2 = `[system] ${eventName}: ${JSON.stringify(data)}`;
51673
+ }
51674
+ try {
51675
+ await server.notification({
51676
+ method: "notifications/claude/channel",
51677
+ params: {
51678
+ content: content2,
51679
+ meta: {
51680
+ kind: "system",
51681
+ event: eventName,
51682
+ mesh_slug: client2.meshSlug,
51683
+ mesh_id: client2.meshId,
51684
+ ...Object.keys(data).length > 0 ? { eventData: data } : {}
51685
+ }
51686
+ }
51687
+ });
51688
+ process.stderr.write(`[claudemesh] system: ${content2}
51689
+ `);
51690
+ } catch (pushErr) {
51691
+ process.stderr.write(`[claudemesh] system push FAILED: ${pushErr}
51692
+ `);
51693
+ }
51694
+ return;
51695
+ }
51696
+ const fromPubkey = msg.senderPubkey || "";
51697
+ const fromName = fromPubkey ? await resolvePeerName(client2, fromPubkey) : "unknown";
51698
+ if (messageMode === "inbox") {
51699
+ try {
51700
+ await server.notification({
51701
+ method: "notifications/claude/channel",
51702
+ params: {
51703
+ content: `[inbox] New message from ${fromName}. Use check_messages to read.`,
51704
+ meta: { kind: "inbox_notification", from_name: fromName }
51705
+ }
51706
+ });
51707
+ } catch {}
51708
+ return;
51440
51709
  }
51710
+ const content = msg.plaintext ?? decryptFailedWarning(fromPubkey);
51441
51711
  try {
51442
51712
  await server.notification({
51443
51713
  method: "notifications/claude/channel",
51444
51714
  params: {
51445
- content: content2,
51715
+ content,
51446
51716
  meta: {
51447
- kind: "system",
51448
- event: eventName,
51717
+ from_id: fromPubkey,
51718
+ from_name: fromName,
51449
51719
  mesh_slug: client2.meshSlug,
51450
51720
  mesh_id: client2.meshId,
51451
- ...Object.keys(data).length > 0 ? { eventData: data } : {}
51721
+ priority: msg.priority,
51722
+ sent_at: msg.createdAt,
51723
+ delivered_at: msg.receivedAt,
51724
+ kind: msg.kind,
51725
+ ...msg.subtype ? { subtype: msg.subtype } : {}
51452
51726
  }
51453
51727
  }
51454
51728
  });
51455
- process.stderr.write(`[claudemesh] system: ${content2}
51729
+ process.stderr.write(`[claudemesh] pushed: from=${fromName} content=${content.slice(0, 60)}
51456
51730
  `);
51457
51731
  } catch (pushErr) {
51458
- process.stderr.write(`[claudemesh] system push FAILED: ${pushErr}
51732
+ process.stderr.write(`[claudemesh] push FAILED: ${pushErr}
51459
51733
  `);
51460
51734
  }
51461
- return;
51462
- }
51463
- const fromPubkey = msg.senderPubkey || "";
51464
- const fromName = fromPubkey ? await resolvePeerName(client2, fromPubkey) : "unknown";
51465
- if (messageMode === "inbox") {
51735
+ });
51736
+ client2.onStreamData(async (evt) => {
51466
51737
  try {
51467
51738
  await server.notification({
51468
51739
  method: "notifications/claude/channel",
51469
51740
  params: {
51470
- content: `[inbox] New message from ${fromName}. Use check_messages to read.`,
51471
- meta: { kind: "inbox_notification", from_name: fromName }
51741
+ content: `[stream:${evt.stream}] from ${evt.publishedBy}: ${JSON.stringify(evt.data)}`,
51742
+ meta: {
51743
+ kind: "stream_data",
51744
+ stream: evt.stream,
51745
+ published_by: evt.publishedBy
51746
+ }
51472
51747
  }
51473
51748
  });
51474
51749
  } catch {}
51475
- return;
51476
- }
51477
- const content = msg.plaintext ?? decryptFailedWarning(fromPubkey);
51478
- try {
51479
- await server.notification({
51480
- method: "notifications/claude/channel",
51481
- params: {
51482
- content,
51483
- meta: {
51484
- from_id: fromPubkey,
51485
- from_name: fromName,
51486
- mesh_slug: client2.meshSlug,
51487
- mesh_id: client2.meshId,
51488
- priority: msg.priority,
51489
- sent_at: msg.createdAt,
51490
- delivered_at: msg.receivedAt,
51491
- kind: msg.kind,
51492
- ...msg.subtype ? { subtype: msg.subtype } : {}
51493
- }
51494
- }
51495
- });
51496
- process.stderr.write(`[claudemesh] pushed: from=${fromName} content=${content.slice(0, 60)}
51497
- `);
51498
- } catch (pushErr) {
51499
- process.stderr.write(`[claudemesh] push FAILED: ${pushErr}
51500
- `);
51501
- }
51502
- });
51503
- client2.onStreamData(async (evt) => {
51504
- try {
51505
- await server.notification({
51506
- method: "notifications/claude/channel",
51507
- params: {
51508
- content: `[stream:${evt.stream}] from ${evt.publishedBy}: ${JSON.stringify(evt.data)}`,
51509
- meta: {
51510
- kind: "stream_data",
51511
- stream: evt.stream,
51512
- published_by: evt.publishedBy
51750
+ });
51751
+ client2.onStateChange(async (change) => {
51752
+ try {
51753
+ await server.notification({
51754
+ method: "notifications/claude/channel",
51755
+ params: {
51756
+ content: `[state] ${change.key} = ${JSON.stringify(change.value)} (set by ${change.updatedBy})`,
51757
+ meta: {
51758
+ kind: "state_change",
51759
+ key: change.key,
51760
+ updated_by: change.updatedBy
51761
+ }
51513
51762
  }
51514
- }
51515
- });
51516
- } catch {}
51517
- });
51518
- client2.onStateChange(async (change) => {
51763
+ });
51764
+ } catch {}
51765
+ });
51766
+ }
51767
+ const welcomeClient = allClients()[0];
51768
+ if (welcomeClient && welcomeClient.status === "open") {
51519
51769
  try {
51770
+ const peers = await welcomeClient.listPeers();
51771
+ const peerNames = peers.filter((p) => p.displayName !== myName).map((p) => p.displayName).join(", ") || "none";
51520
51772
  await server.notification({
51521
51773
  method: "notifications/claude/channel",
51522
51774
  params: {
51523
- content: `[state] ${change.key} = ${JSON.stringify(change.value)} (set by ${change.updatedBy})`,
51524
- meta: {
51525
- kind: "state_change",
51526
- key: change.key,
51527
- updated_by: change.updatedBy
51528
- }
51775
+ content: `[system] Connected as ${myName} to mesh ${welcomeClient.meshSlug}. ${peers.length} peer(s) online: ${peerNames}. Call mesh_info for full details or set_summary to announce yourself.`,
51776
+ meta: { kind: "welcome", mesh_slug: welcomeClient.meshSlug }
51529
51777
  }
51530
51778
  });
51531
51779
  } catch {}
51532
- });
51780
+ }
51533
51781
  }
51534
- setTimeout(async () => {
51535
- const client2 = allClients()[0];
51536
- if (!client2 || client2.status !== "open")
51537
- return;
51538
- try {
51539
- const peers = await client2.listPeers();
51540
- const peerNames = peers.filter((p) => p.displayName !== myName).map((p) => p.displayName).join(", ") || "none";
51541
- await server.notification({
51542
- method: "notifications/claude/channel",
51543
- params: {
51544
- content: `[system] Connected as ${myName} to mesh ${client2.meshSlug}. ${peers.length} peer(s) online: ${peerNames}. Call mesh_info for full details or set_summary to announce yourself.`,
51545
- meta: { kind: "welcome", mesh_slug: client2.meshSlug }
51546
- }
51547
- });
51548
- } catch {}
51549
- }, 3000);
51550
51782
  const keepalive = setInterval(() => {}, 1000);
51551
51783
  const shutdown = () => {
51552
51784
  clearInterval(keepalive);
@@ -52371,6 +52603,84 @@ import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, stat
52371
52603
  import { tmpdir, hostname as hostname2, homedir as homedir4 } from "node:os";
52372
52604
  import { join as join4 } from "node:path";
52373
52605
  import { createInterface } from "node:readline";
52606
+
52607
+ // src/auth/callback-listener.ts
52608
+ import { createServer } from "node:http";
52609
+ function startCallbackListener() {
52610
+ return new Promise((resolveStart) => {
52611
+ let resolveToken;
52612
+ const tokenPromise = new Promise((r) => {
52613
+ resolveToken = r;
52614
+ });
52615
+ const server = createServer((req, res) => {
52616
+ const url = new URL(req.url, "http://localhost");
52617
+ if (req.method === "OPTIONS") {
52618
+ res.writeHead(204, {
52619
+ "Access-Control-Allow-Origin": "https://claudemesh.com",
52620
+ "Access-Control-Allow-Methods": "GET",
52621
+ "Access-Control-Allow-Headers": "Content-Type"
52622
+ });
52623
+ res.end();
52624
+ return;
52625
+ }
52626
+ if (url.pathname === "/ping") {
52627
+ res.writeHead(200, {
52628
+ "Content-Type": "text/plain",
52629
+ "Access-Control-Allow-Origin": "https://claudemesh.com"
52630
+ });
52631
+ res.end("ok");
52632
+ return;
52633
+ }
52634
+ if (url.pathname === "/callback") {
52635
+ const token = url.searchParams.get("token");
52636
+ if (token) {
52637
+ res.writeHead(200, {
52638
+ "Content-Type": "text/html",
52639
+ "Access-Control-Allow-Origin": "https://claudemesh.com"
52640
+ });
52641
+ res.end("<html><body><h2>Done! You can close this tab.</h2><p>Launching claudemesh...</p></body></html>");
52642
+ resolveToken(token);
52643
+ setTimeout(() => server.close(), 500);
52644
+ } else {
52645
+ res.writeHead(400, { "Content-Type": "text/plain" });
52646
+ res.end("Missing token");
52647
+ }
52648
+ return;
52649
+ }
52650
+ res.writeHead(404);
52651
+ res.end();
52652
+ });
52653
+ server.listen(0, "127.0.0.1", () => {
52654
+ const addr = server.address();
52655
+ resolveStart({
52656
+ port: addr.port,
52657
+ token: tokenPromise,
52658
+ close: () => server.close()
52659
+ });
52660
+ });
52661
+ });
52662
+ }
52663
+ // src/auth/open-browser.ts
52664
+ import { exec } from "node:child_process";
52665
+ function openBrowser(url) {
52666
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
52667
+ return Promise.resolve(false);
52668
+ }
52669
+ const quoted = JSON.stringify(url);
52670
+ const browserCmd = process.env.BROWSER;
52671
+ const cmd = browserCmd ? `${browserCmd} ${quoted}` : process.platform === "darwin" ? `open ${quoted}` : process.platform === "win32" ? `rundll32 url.dll,FileProtocolHandler ${quoted}` : `xdg-open ${quoted}`;
52672
+ return new Promise((resolve2) => {
52673
+ exec(cmd, (err) => resolve2(!err));
52674
+ });
52675
+ }
52676
+ // src/auth/pairing-code.ts
52677
+ import { randomBytes as randomBytes2 } from "node:crypto";
52678
+ var CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
52679
+ function generatePairingCode() {
52680
+ const bytes = randomBytes2(4);
52681
+ return Array.from(bytes, (b) => CHARS[b % CHARS.length]).join("");
52682
+ }
52683
+ // src/commands/launch.ts
52374
52684
  async function pickMesh(meshes) {
52375
52685
  if (meshes.length === 1)
52376
52686
  return meshes[0];
@@ -52509,6 +52819,73 @@ async function runLaunch(flags, rawArgs) {
52509
52819
  console.log(`✓ Joined "${invite.payload.mesh_slug}"${enroll.alreadyMember ? " (already member)" : ""}`);
52510
52820
  }
52511
52821
  const config2 = loadConfig();
52822
+ let justSynced = false;
52823
+ if (config2.meshes.length === 0 && !args.joinLink) {
52824
+ const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
52825
+ const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
52826
+ const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
52827
+ const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
52828
+ const code = generatePairingCode();
52829
+ const listener = await startCallbackListener();
52830
+ const url = `https://claudemesh.com/cli-auth?port=${listener.port}&code=${code}&action=sync`;
52831
+ console.log(`
52832
+ ${bold2("Welcome to claudemesh!")} No meshes found.`);
52833
+ console.log(` Opening browser to sign in...
52834
+ `);
52835
+ const opened = await openBrowser(url);
52836
+ if (!opened) {
52837
+ console.log(` Couldn't open browser automatically.`);
52838
+ }
52839
+ console.log(` ${dim(`Visit: ${url}`)}`);
52840
+ console.log(` ${dim(`Or join with invite: claudemesh launch --join <url>`)}
52841
+ `);
52842
+ const manualPromise = new Promise((resolve2) => {
52843
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
52844
+ rl.question(" Paste sync token (or wait for browser): ", (answer) => {
52845
+ rl.close();
52846
+ if (answer.trim())
52847
+ resolve2(answer.trim());
52848
+ });
52849
+ });
52850
+ const timeoutPromise = new Promise((resolve2) => {
52851
+ setTimeout(() => resolve2(null), 900000);
52852
+ });
52853
+ const syncToken = await Promise.race([
52854
+ listener.token,
52855
+ manualPromise,
52856
+ timeoutPromise
52857
+ ]);
52858
+ listener.close();
52859
+ if (!syncToken) {
52860
+ console.error(`
52861
+ Timed out waiting for sign-in.`);
52862
+ process.exit(1);
52863
+ }
52864
+ const { generateKeypair: generateKeypair3 } = await Promise.resolve().then(() => (init_keypair(), exports_keypair));
52865
+ const keypair = await generateKeypair3();
52866
+ const displayNameForSync = args.name ?? `${hostname2()}-${process.pid}`;
52867
+ const { syncWithBroker: syncWithBroker2 } = await Promise.resolve().then(() => exports_sync_with_broker);
52868
+ const result = await syncWithBroker2(syncToken, keypair.publicKey, displayNameForSync);
52869
+ const { saveConfig: saveConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
52870
+ for (const m of result.meshes) {
52871
+ config2.meshes.push({
52872
+ meshId: m.mesh_id,
52873
+ memberId: m.member_id,
52874
+ slug: m.slug,
52875
+ name: m.slug,
52876
+ pubkey: keypair.publicKey,
52877
+ secretKey: keypair.secretKey,
52878
+ brokerUrl: m.broker_url,
52879
+ joinedAt: new Date().toISOString()
52880
+ });
52881
+ }
52882
+ config2.accountId = result.account_id;
52883
+ saveConfig2(config2);
52884
+ justSynced = true;
52885
+ console.log(`
52886
+ ${green("✓")} Synced ${result.meshes.length} mesh(es): ${result.meshes.map((m) => m.slug).join(", ")}
52887
+ `);
52888
+ }
52512
52889
  if (config2.meshes.length === 0) {
52513
52890
  console.error("No meshes joined. Run `claudemesh join <url>` or use --join <url>.");
52514
52891
  process.exit(1);
@@ -52528,7 +52905,7 @@ async function runLaunch(flags, rawArgs) {
52528
52905
  let role = args.role;
52529
52906
  let parsedGroups = args.groups ? parseGroupsString(args.groups) : [];
52530
52907
  let messageMode = args.messageMode ?? "push";
52531
- if (!args.quiet) {
52908
+ if (!args.quiet && !justSynced) {
52532
52909
  if (role === null) {
52533
52910
  const answer = await askLine(" Role (optional): ");
52534
52911
  if (answer)
@@ -52754,7 +53131,7 @@ init_config();
52754
53131
  // package.json
52755
53132
  var package_default = {
52756
53133
  name: "claudemesh-cli",
52757
- version: "0.8.9",
53134
+ version: "0.9.1",
52758
53135
  description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
52759
53136
  keywords: [
52760
53137
  "claude-code",
@@ -53698,6 +54075,151 @@ function runCreate(args) {
53698
54075
  console.log(" claudemesh create --list-templates");
53699
54076
  }
53700
54077
 
54078
+ // src/commands/sync.ts
54079
+ init_config();
54080
+ import { createInterface as createInterface2 } from "node:readline";
54081
+ import { hostname as hostname4 } from "node:os";
54082
+ init_keypair();
54083
+ async function runSync(args) {
54084
+ const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
54085
+ const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
54086
+ const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
54087
+ const config2 = loadConfig();
54088
+ const code = generatePairingCode();
54089
+ const listener = await startCallbackListener();
54090
+ const url = `https://claudemesh.com/cli-auth?port=${listener.port}&code=${code}&action=sync`;
54091
+ console.log(`Opening browser to sync meshes...`);
54092
+ console.log(dim(`Visit: ${url}`));
54093
+ await openBrowser(url);
54094
+ const manualPromise = new Promise((resolve2) => {
54095
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
54096
+ rl.question("Paste sync token (or wait for browser): ", (answer) => {
54097
+ rl.close();
54098
+ if (answer.trim())
54099
+ resolve2(answer.trim());
54100
+ });
54101
+ });
54102
+ const timeoutPromise = new Promise((resolve2) => {
54103
+ setTimeout(() => resolve2(null), 15 * 60000);
54104
+ });
54105
+ const syncToken = await Promise.race([
54106
+ listener.token,
54107
+ manualPromise,
54108
+ timeoutPromise
54109
+ ]);
54110
+ listener.close();
54111
+ if (!syncToken) {
54112
+ console.error("Timed out waiting for sign-in.");
54113
+ process.exit(1);
54114
+ }
54115
+ const keypair = config2.meshes.length > 0 ? { publicKey: config2.meshes[0].pubkey, secretKey: config2.meshes[0].secretKey } : await generateKeypair2();
54116
+ const displayName = config2.displayName ?? `${hostname4()}-${process.pid}`;
54117
+ const result = await syncWithBroker(syncToken, keypair.publicKey, displayName);
54118
+ let added = 0;
54119
+ for (const m of result.meshes) {
54120
+ if (config2.meshes.some((existing) => existing.meshId === m.mesh_id))
54121
+ continue;
54122
+ config2.meshes.push({
54123
+ meshId: m.mesh_id,
54124
+ memberId: m.member_id,
54125
+ slug: m.slug,
54126
+ name: m.slug,
54127
+ pubkey: keypair.publicKey,
54128
+ secretKey: keypair.secretKey,
54129
+ brokerUrl: m.broker_url,
54130
+ joinedAt: new Date().toISOString()
54131
+ });
54132
+ added++;
54133
+ }
54134
+ config2.accountId = result.account_id;
54135
+ saveConfig(config2);
54136
+ if (added > 0) {
54137
+ console.log(green(`✓ Added ${added} new mesh(es)`));
54138
+ } else {
54139
+ console.log(`Already up to date (${config2.meshes.length} meshes)`);
54140
+ }
54141
+ }
54142
+
54143
+ // src/commands/profile.ts
54144
+ init_config();
54145
+ async function runProfile(flags) {
54146
+ const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
54147
+ const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
54148
+ const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
54149
+ const config2 = loadConfig();
54150
+ if (config2.meshes.length === 0) {
54151
+ console.error("No meshes joined. Run `claudemesh join <url>` first.");
54152
+ process.exit(1);
54153
+ }
54154
+ const mesh = flags.mesh ? config2.meshes.find((m) => m.slug === flags.mesh) : config2.meshes[0];
54155
+ if (!mesh) {
54156
+ console.error(`Mesh "${flags.mesh}" not found. Joined: ${config2.meshes.map((m) => m.slug).join(", ")}`);
54157
+ process.exit(1);
54158
+ }
54159
+ const brokerUrl = mesh.brokerUrl.replace("wss://", "https://").replace("ws://", "http://").replace(/\/ws\/?$/, "");
54160
+ const hasEdits = flags["role-tag"] !== undefined || flags.groups !== undefined || flags["message-mode"] !== undefined || flags.name !== undefined;
54161
+ if (hasEdits) {
54162
+ const targetMemberId = flags.member ?? mesh.memberId;
54163
+ const body = {};
54164
+ if (flags.name !== undefined)
54165
+ body.displayName = flags.name;
54166
+ if (flags["role-tag"] !== undefined)
54167
+ body.roleTag = flags["role-tag"];
54168
+ if (flags.groups !== undefined) {
54169
+ body.groups = flags.groups.split(",").map((s) => {
54170
+ const [name, role] = s.trim().split(":");
54171
+ return role ? { name, role } : { name };
54172
+ });
54173
+ }
54174
+ if (flags["message-mode"] !== undefined)
54175
+ body.messageMode = flags["message-mode"];
54176
+ const res = await fetch(`${brokerUrl}/mesh/${mesh.meshId}/member/${targetMemberId}`, {
54177
+ method: "PATCH",
54178
+ headers: {
54179
+ "Content-Type": "application/json",
54180
+ "X-Member-Id": mesh.memberId
54181
+ },
54182
+ body: JSON.stringify(body)
54183
+ });
54184
+ const result = await res.json();
54185
+ if (flags.json) {
54186
+ console.log(JSON.stringify(result, null, 2));
54187
+ } else if (result.ok) {
54188
+ console.log(green("✓ Profile updated"));
54189
+ const member = result.member;
54190
+ printProfile(member, dim);
54191
+ } else {
54192
+ console.error(`Error: ${result.error}`);
54193
+ process.exit(1);
54194
+ }
54195
+ } else {
54196
+ const res = await fetch(`${brokerUrl}/mesh/${mesh.meshId}/members`);
54197
+ const result = await res.json();
54198
+ if (!result.ok) {
54199
+ console.error(`Error: ${result.error}`);
54200
+ process.exit(1);
54201
+ }
54202
+ const me = result.members?.find((m) => m.id === mesh.memberId);
54203
+ if (flags.json) {
54204
+ console.log(JSON.stringify(me ?? {}, null, 2));
54205
+ } else if (me) {
54206
+ printProfile(me, dim);
54207
+ } else {
54208
+ console.log("Member not found in mesh.");
54209
+ }
54210
+ }
54211
+ }
54212
+ function printProfile(member, dim) {
54213
+ const groups = member.groups;
54214
+ const groupStr = groups?.length ? groups.map((g) => g.role ? `${g.name} (${g.role})` : g.name).join(", ") : dim("(none)");
54215
+ console.log(` Name: ${member.displayName ?? dim("(not set)")}`);
54216
+ console.log(` Role: ${member.roleTag ?? dim("(not set)")}`);
54217
+ console.log(` Groups: ${groupStr}`);
54218
+ console.log(` Messages: ${member.messageMode ?? "push"}`);
54219
+ console.log(` Access: ${member.permission ?? "member"}`);
54220
+ console.log(` Mesh: ${dim(String(member.id ?? ""))}`);
54221
+ }
54222
+
53701
54223
  // src/index.ts
53702
54224
  var launch = defineCommand({
53703
54225
  meta: {
@@ -53954,6 +54476,30 @@ var main = defineCommand({
53954
54476
  await runRemind(args, positionals);
53955
54477
  }
53956
54478
  }),
54479
+ sync: defineCommand({
54480
+ meta: { name: "sync", description: "Sync meshes from your dashboard account" },
54481
+ args: {
54482
+ force: { type: "boolean", description: "Re-link account even if already linked", default: false }
54483
+ },
54484
+ async run({ args }) {
54485
+ await runSync(args);
54486
+ }
54487
+ }),
54488
+ profile: defineCommand({
54489
+ meta: { name: "profile", description: "View or edit your member profile" },
54490
+ args: {
54491
+ mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
54492
+ "role-tag": { type: "string", description: "Set role tag (e.g. 'backend-dev', 'lead')" },
54493
+ groups: { type: "string", description: "Set groups as 'group:role,...' (e.g. 'eng:lead,review')" },
54494
+ "message-mode": { type: "string", description: "'push' | 'inbox' | 'off'" },
54495
+ name: { type: "string", description: "Set display name" },
54496
+ member: { type: "string", description: "Edit another member (admin only)" },
54497
+ json: { type: "boolean", description: "Output as JSON", default: false }
54498
+ },
54499
+ async run({ args }) {
54500
+ await runProfile(args);
54501
+ }
54502
+ }),
53957
54503
  status: defineCommand({
53958
54504
  meta: { name: "status", description: "Check broker connectivity for each joined mesh" },
53959
54505
  async run() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemesh-cli",
3
- "version": "0.8.9",
3
+ "version": "0.9.1",
4
4
  "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
5
5
  "keywords": [
6
6
  "claude-code",