claudemesh-cli 0.8.8 → 0.9.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 +564 -14
  2. package/package.json +2 -2
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": {
@@ -51427,6 +51655,14 @@ ${lines.join(`
51427
51655
  content2 = `[system] MCP server "${data.serverName}" removed (was hosted by ${data.hostedBy})`;
51428
51656
  } else if (eventName === "mcp_restored") {
51429
51657
  content2 = `[system] MCP server "${data.serverName}" is back online (hosted by ${data.hostedBy})`;
51658
+ } else if (eventName === "watch_triggered") {
51659
+ content2 = `[WATCH] ${data.label ?? data.url}: ${data.oldValue} → ${data.newValue}`;
51660
+ } else if (eventName === "mcp_deployed") {
51661
+ content2 = `[SERVICE] "${data.name}" deployed (${data.tool_count} tools) by ${data.deployed_by}`;
51662
+ } else if (eventName === "mcp_undeployed") {
51663
+ content2 = `[SERVICE] "${data.name}" undeployed by ${data.by}`;
51664
+ } else if (eventName === "mcp_scope_changed") {
51665
+ content2 = `[SERVICE] "${data.name}" scope changed to ${JSON.stringify(data.scope)} by ${data.by}`;
51430
51666
  } else {
51431
51667
  content2 = `[system] ${eventName}: ${JSON.stringify(data)}`;
51432
51668
  }
@@ -52363,6 +52599,84 @@ import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, stat
52363
52599
  import { tmpdir, hostname as hostname2, homedir as homedir4 } from "node:os";
52364
52600
  import { join as join4 } from "node:path";
52365
52601
  import { createInterface } from "node:readline";
52602
+
52603
+ // src/auth/callback-listener.ts
52604
+ import { createServer } from "node:http";
52605
+ function startCallbackListener() {
52606
+ return new Promise((resolveStart) => {
52607
+ let resolveToken;
52608
+ const tokenPromise = new Promise((r) => {
52609
+ resolveToken = r;
52610
+ });
52611
+ const server = createServer((req, res) => {
52612
+ const url = new URL(req.url, "http://localhost");
52613
+ if (req.method === "OPTIONS") {
52614
+ res.writeHead(204, {
52615
+ "Access-Control-Allow-Origin": "https://claudemesh.com",
52616
+ "Access-Control-Allow-Methods": "GET",
52617
+ "Access-Control-Allow-Headers": "Content-Type"
52618
+ });
52619
+ res.end();
52620
+ return;
52621
+ }
52622
+ if (url.pathname === "/ping") {
52623
+ res.writeHead(200, {
52624
+ "Content-Type": "text/plain",
52625
+ "Access-Control-Allow-Origin": "https://claudemesh.com"
52626
+ });
52627
+ res.end("ok");
52628
+ return;
52629
+ }
52630
+ if (url.pathname === "/callback") {
52631
+ const token = url.searchParams.get("token");
52632
+ if (token) {
52633
+ res.writeHead(200, {
52634
+ "Content-Type": "text/html",
52635
+ "Access-Control-Allow-Origin": "https://claudemesh.com"
52636
+ });
52637
+ res.end("<html><body><h2>Done! You can close this tab.</h2><p>Launching claudemesh...</p></body></html>");
52638
+ resolveToken(token);
52639
+ setTimeout(() => server.close(), 500);
52640
+ } else {
52641
+ res.writeHead(400, { "Content-Type": "text/plain" });
52642
+ res.end("Missing token");
52643
+ }
52644
+ return;
52645
+ }
52646
+ res.writeHead(404);
52647
+ res.end();
52648
+ });
52649
+ server.listen(0, "127.0.0.1", () => {
52650
+ const addr = server.address();
52651
+ resolveStart({
52652
+ port: addr.port,
52653
+ token: tokenPromise,
52654
+ close: () => server.close()
52655
+ });
52656
+ });
52657
+ });
52658
+ }
52659
+ // src/auth/open-browser.ts
52660
+ import { exec } from "node:child_process";
52661
+ function openBrowser(url) {
52662
+ if (!url.startsWith("http://") && !url.startsWith("https://")) {
52663
+ return Promise.resolve(false);
52664
+ }
52665
+ const quoted = JSON.stringify(url);
52666
+ const browserCmd = process.env.BROWSER;
52667
+ const cmd = browserCmd ? `${browserCmd} ${quoted}` : process.platform === "darwin" ? `open ${quoted}` : process.platform === "win32" ? `rundll32 url.dll,FileProtocolHandler ${quoted}` : `xdg-open ${quoted}`;
52668
+ return new Promise((resolve2) => {
52669
+ exec(cmd, (err) => resolve2(!err));
52670
+ });
52671
+ }
52672
+ // src/auth/pairing-code.ts
52673
+ import { randomBytes as randomBytes2 } from "node:crypto";
52674
+ var CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
52675
+ function generatePairingCode() {
52676
+ const bytes = randomBytes2(4);
52677
+ return Array.from(bytes, (b) => CHARS[b % CHARS.length]).join("");
52678
+ }
52679
+ // src/commands/launch.ts
52366
52680
  async function pickMesh(meshes) {
52367
52681
  if (meshes.length === 1)
52368
52682
  return meshes[0];
@@ -52501,6 +52815,73 @@ async function runLaunch(flags, rawArgs) {
52501
52815
  console.log(`✓ Joined "${invite.payload.mesh_slug}"${enroll.alreadyMember ? " (already member)" : ""}`);
52502
52816
  }
52503
52817
  const config2 = loadConfig();
52818
+ let justSynced = false;
52819
+ if (config2.meshes.length === 0 && !args.joinLink) {
52820
+ const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
52821
+ const bold2 = (s) => useColor ? `\x1B[1m${s}\x1B[22m` : s;
52822
+ const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
52823
+ const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
52824
+ const code = generatePairingCode();
52825
+ const listener = await startCallbackListener();
52826
+ const url = `https://claudemesh.com/cli-auth?port=${listener.port}&code=${code}&action=sync`;
52827
+ console.log(`
52828
+ ${bold2("Welcome to claudemesh!")} No meshes found.`);
52829
+ console.log(` Opening browser to sign in...
52830
+ `);
52831
+ const opened = await openBrowser(url);
52832
+ if (!opened) {
52833
+ console.log(` Couldn't open browser automatically.`);
52834
+ }
52835
+ console.log(` ${dim(`Visit: ${url}`)}`);
52836
+ console.log(` ${dim(`Or join with invite: claudemesh launch --join <url>`)}
52837
+ `);
52838
+ const manualPromise = new Promise((resolve2) => {
52839
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
52840
+ rl.question(" Paste sync token (or wait for browser): ", (answer) => {
52841
+ rl.close();
52842
+ if (answer.trim())
52843
+ resolve2(answer.trim());
52844
+ });
52845
+ });
52846
+ const timeoutPromise = new Promise((resolve2) => {
52847
+ setTimeout(() => resolve2(null), 900000);
52848
+ });
52849
+ const syncToken = await Promise.race([
52850
+ listener.token,
52851
+ manualPromise,
52852
+ timeoutPromise
52853
+ ]);
52854
+ listener.close();
52855
+ if (!syncToken) {
52856
+ console.error(`
52857
+ Timed out waiting for sign-in.`);
52858
+ process.exit(1);
52859
+ }
52860
+ const { generateKeypair: generateKeypair3 } = await Promise.resolve().then(() => (init_keypair(), exports_keypair));
52861
+ const keypair = await generateKeypair3();
52862
+ const displayNameForSync = args.name ?? `${hostname2()}-${process.pid}`;
52863
+ const { syncWithBroker: syncWithBroker2 } = await Promise.resolve().then(() => exports_sync_with_broker);
52864
+ const result = await syncWithBroker2(syncToken, keypair.publicKey, displayNameForSync);
52865
+ const { saveConfig: saveConfig2 } = await Promise.resolve().then(() => (init_config(), exports_config));
52866
+ for (const m of result.meshes) {
52867
+ config2.meshes.push({
52868
+ meshId: m.mesh_id,
52869
+ memberId: m.member_id,
52870
+ slug: m.slug,
52871
+ name: m.slug,
52872
+ pubkey: keypair.publicKey,
52873
+ secretKey: keypair.secretKey,
52874
+ brokerUrl: m.broker_url,
52875
+ joinedAt: new Date().toISOString()
52876
+ });
52877
+ }
52878
+ config2.accountId = result.account_id;
52879
+ saveConfig2(config2);
52880
+ justSynced = true;
52881
+ console.log(`
52882
+ ${green("✓")} Synced ${result.meshes.length} mesh(es): ${result.meshes.map((m) => m.slug).join(", ")}
52883
+ `);
52884
+ }
52504
52885
  if (config2.meshes.length === 0) {
52505
52886
  console.error("No meshes joined. Run `claudemesh join <url>` or use --join <url>.");
52506
52887
  process.exit(1);
@@ -52520,7 +52901,7 @@ async function runLaunch(flags, rawArgs) {
52520
52901
  let role = args.role;
52521
52902
  let parsedGroups = args.groups ? parseGroupsString(args.groups) : [];
52522
52903
  let messageMode = args.messageMode ?? "push";
52523
- if (!args.quiet) {
52904
+ if (!args.quiet && !justSynced) {
52524
52905
  if (role === null) {
52525
52906
  const answer = await askLine(" Role (optional): ");
52526
52907
  if (answer)
@@ -52746,7 +53127,7 @@ init_config();
52746
53127
  // package.json
52747
53128
  var package_default = {
52748
53129
  name: "claudemesh-cli",
52749
- version: "0.8.8",
53130
+ version: "0.9.0",
52750
53131
  description: "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
52751
53132
  keywords: [
52752
53133
  "claude-code",
@@ -53690,6 +54071,151 @@ function runCreate(args) {
53690
54071
  console.log(" claudemesh create --list-templates");
53691
54072
  }
53692
54073
 
54074
+ // src/commands/sync.ts
54075
+ init_config();
54076
+ import { createInterface as createInterface2 } from "node:readline";
54077
+ import { hostname as hostname4 } from "node:os";
54078
+ init_keypair();
54079
+ async function runSync(args) {
54080
+ const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
54081
+ const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
54082
+ const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
54083
+ const config2 = loadConfig();
54084
+ const code = generatePairingCode();
54085
+ const listener = await startCallbackListener();
54086
+ const url = `https://claudemesh.com/cli-auth?port=${listener.port}&code=${code}&action=sync`;
54087
+ console.log(`Opening browser to sync meshes...`);
54088
+ console.log(dim(`Visit: ${url}`));
54089
+ await openBrowser(url);
54090
+ const manualPromise = new Promise((resolve2) => {
54091
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
54092
+ rl.question("Paste sync token (or wait for browser): ", (answer) => {
54093
+ rl.close();
54094
+ if (answer.trim())
54095
+ resolve2(answer.trim());
54096
+ });
54097
+ });
54098
+ const timeoutPromise = new Promise((resolve2) => {
54099
+ setTimeout(() => resolve2(null), 15 * 60000);
54100
+ });
54101
+ const syncToken = await Promise.race([
54102
+ listener.token,
54103
+ manualPromise,
54104
+ timeoutPromise
54105
+ ]);
54106
+ listener.close();
54107
+ if (!syncToken) {
54108
+ console.error("Timed out waiting for sign-in.");
54109
+ process.exit(1);
54110
+ }
54111
+ const keypair = config2.meshes.length > 0 ? { publicKey: config2.meshes[0].pubkey, secretKey: config2.meshes[0].secretKey } : await generateKeypair2();
54112
+ const displayName = config2.displayName ?? `${hostname4()}-${process.pid}`;
54113
+ const result = await syncWithBroker(syncToken, keypair.publicKey, displayName);
54114
+ let added = 0;
54115
+ for (const m of result.meshes) {
54116
+ if (config2.meshes.some((existing) => existing.meshId === m.mesh_id))
54117
+ continue;
54118
+ config2.meshes.push({
54119
+ meshId: m.mesh_id,
54120
+ memberId: m.member_id,
54121
+ slug: m.slug,
54122
+ name: m.slug,
54123
+ pubkey: keypair.publicKey,
54124
+ secretKey: keypair.secretKey,
54125
+ brokerUrl: m.broker_url,
54126
+ joinedAt: new Date().toISOString()
54127
+ });
54128
+ added++;
54129
+ }
54130
+ config2.accountId = result.account_id;
54131
+ saveConfig(config2);
54132
+ if (added > 0) {
54133
+ console.log(green(`✓ Added ${added} new mesh(es)`));
54134
+ } else {
54135
+ console.log(`Already up to date (${config2.meshes.length} meshes)`);
54136
+ }
54137
+ }
54138
+
54139
+ // src/commands/profile.ts
54140
+ init_config();
54141
+ async function runProfile(flags) {
54142
+ const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
54143
+ const dim = (s) => useColor ? `\x1B[2m${s}\x1B[22m` : s;
54144
+ const green = (s) => useColor ? `\x1B[32m${s}\x1B[39m` : s;
54145
+ const config2 = loadConfig();
54146
+ if (config2.meshes.length === 0) {
54147
+ console.error("No meshes joined. Run `claudemesh join <url>` first.");
54148
+ process.exit(1);
54149
+ }
54150
+ const mesh = flags.mesh ? config2.meshes.find((m) => m.slug === flags.mesh) : config2.meshes[0];
54151
+ if (!mesh) {
54152
+ console.error(`Mesh "${flags.mesh}" not found. Joined: ${config2.meshes.map((m) => m.slug).join(", ")}`);
54153
+ process.exit(1);
54154
+ }
54155
+ const brokerUrl = mesh.brokerUrl.replace("wss://", "https://").replace("ws://", "http://").replace(/\/ws\/?$/, "");
54156
+ const hasEdits = flags["role-tag"] !== undefined || flags.groups !== undefined || flags["message-mode"] !== undefined || flags.name !== undefined;
54157
+ if (hasEdits) {
54158
+ const targetMemberId = flags.member ?? mesh.memberId;
54159
+ const body = {};
54160
+ if (flags.name !== undefined)
54161
+ body.displayName = flags.name;
54162
+ if (flags["role-tag"] !== undefined)
54163
+ body.roleTag = flags["role-tag"];
54164
+ if (flags.groups !== undefined) {
54165
+ body.groups = flags.groups.split(",").map((s) => {
54166
+ const [name, role] = s.trim().split(":");
54167
+ return role ? { name, role } : { name };
54168
+ });
54169
+ }
54170
+ if (flags["message-mode"] !== undefined)
54171
+ body.messageMode = flags["message-mode"];
54172
+ const res = await fetch(`${brokerUrl}/mesh/${mesh.meshId}/member/${targetMemberId}`, {
54173
+ method: "PATCH",
54174
+ headers: {
54175
+ "Content-Type": "application/json",
54176
+ "X-Member-Id": mesh.memberId
54177
+ },
54178
+ body: JSON.stringify(body)
54179
+ });
54180
+ const result = await res.json();
54181
+ if (flags.json) {
54182
+ console.log(JSON.stringify(result, null, 2));
54183
+ } else if (result.ok) {
54184
+ console.log(green("✓ Profile updated"));
54185
+ const member = result.member;
54186
+ printProfile(member, dim);
54187
+ } else {
54188
+ console.error(`Error: ${result.error}`);
54189
+ process.exit(1);
54190
+ }
54191
+ } else {
54192
+ const res = await fetch(`${brokerUrl}/mesh/${mesh.meshId}/members`);
54193
+ const result = await res.json();
54194
+ if (!result.ok) {
54195
+ console.error(`Error: ${result.error}`);
54196
+ process.exit(1);
54197
+ }
54198
+ const me = result.members?.find((m) => m.id === mesh.memberId);
54199
+ if (flags.json) {
54200
+ console.log(JSON.stringify(me ?? {}, null, 2));
54201
+ } else if (me) {
54202
+ printProfile(me, dim);
54203
+ } else {
54204
+ console.log("Member not found in mesh.");
54205
+ }
54206
+ }
54207
+ }
54208
+ function printProfile(member, dim) {
54209
+ const groups = member.groups;
54210
+ const groupStr = groups?.length ? groups.map((g) => g.role ? `${g.name} (${g.role})` : g.name).join(", ") : dim("(none)");
54211
+ console.log(` Name: ${member.displayName ?? dim("(not set)")}`);
54212
+ console.log(` Role: ${member.roleTag ?? dim("(not set)")}`);
54213
+ console.log(` Groups: ${groupStr}`);
54214
+ console.log(` Messages: ${member.messageMode ?? "push"}`);
54215
+ console.log(` Access: ${member.permission ?? "member"}`);
54216
+ console.log(` Mesh: ${dim(String(member.id ?? ""))}`);
54217
+ }
54218
+
53693
54219
  // src/index.ts
53694
54220
  var launch = defineCommand({
53695
54221
  meta: {
@@ -53946,6 +54472,30 @@ var main = defineCommand({
53946
54472
  await runRemind(args, positionals);
53947
54473
  }
53948
54474
  }),
54475
+ sync: defineCommand({
54476
+ meta: { name: "sync", description: "Sync meshes from your dashboard account" },
54477
+ args: {
54478
+ force: { type: "boolean", description: "Re-link account even if already linked", default: false }
54479
+ },
54480
+ async run({ args }) {
54481
+ await runSync(args);
54482
+ }
54483
+ }),
54484
+ profile: defineCommand({
54485
+ meta: { name: "profile", description: "View or edit your member profile" },
54486
+ args: {
54487
+ mesh: { type: "string", description: "Mesh slug (auto-selected if only one joined)" },
54488
+ "role-tag": { type: "string", description: "Set role tag (e.g. 'backend-dev', 'lead')" },
54489
+ groups: { type: "string", description: "Set groups as 'group:role,...' (e.g. 'eng:lead,review')" },
54490
+ "message-mode": { type: "string", description: "'push' | 'inbox' | 'off'" },
54491
+ name: { type: "string", description: "Set display name" },
54492
+ member: { type: "string", description: "Edit another member (admin only)" },
54493
+ json: { type: "boolean", description: "Output as JSON", default: false }
54494
+ },
54495
+ async run({ args }) {
54496
+ await runProfile(args);
54497
+ }
54498
+ }),
53949
54499
  status: defineCommand({
53950
54500
  meta: { name: "status", description: "Check broker connectivity for each joined mesh" },
53951
54501
  async run() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudemesh-cli",
3
- "version": "0.8.8",
3
+ "version": "0.9.0",
4
4
  "description": "Claude Code MCP client for claudemesh — peer mesh messaging between Claude sessions.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -48,8 +48,8 @@
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",
52
51
  "@turbostarter/prettier-config": "0.1.0",
52
+ "@turbostarter/eslint-config": "0.1.0",
53
53
  "@turbostarter/tsconfig": "0.1.0",
54
54
  "@turbostarter/vitest-config": "0.1.0"
55
55
  },