claudemesh-cli 1.23.0 → 1.24.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.
@@ -3,6 +3,7 @@ var __create = Object.create;
3
3
  var __getProtoOf = Object.getPrototypeOf;
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
8
  var __toESM = (mod, isNodeMode, target) => {
8
9
  target = mod != null ? __create(__getProtoOf(mod)) : {};
@@ -15,6 +16,20 @@ var __toESM = (mod, isNodeMode, target) => {
15
16
  });
16
17
  return to;
17
18
  };
19
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
20
+ var __toCommonJS = (from) => {
21
+ var entry = __moduleCache.get(from), desc;
22
+ if (entry)
23
+ return entry;
24
+ entry = __defProp({}, "__esModule", { value: true });
25
+ if (from && typeof from === "object" || typeof from === "function")
26
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
27
+ get: () => from[key],
28
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
29
+ }));
30
+ __moduleCache.set(from, entry);
31
+ return entry;
32
+ };
18
33
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
19
34
  var __export = (target, all) => {
20
35
  for (var name in all)
@@ -28,12 +43,6 @@ var __export = (target, all) => {
28
43
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
29
44
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
45
 
31
- // src/mcp/tools/definitions.ts
32
- var TOOLS;
33
- var init_definitions = __esm(() => {
34
- TOOLS = [];
35
- });
36
-
37
46
  // src/constants/paths.ts
38
47
  import { homedir } from "node:os";
39
48
  import { join } from "node:path";
@@ -59,6 +68,64 @@ var init_paths = __esm(() => {
59
68
  };
60
69
  });
61
70
 
71
+ // src/daemon/paths.ts
72
+ var exports_paths = {};
73
+ __export(exports_paths, {
74
+ DAEMON_TCP_HOST: () => DAEMON_TCP_HOST,
75
+ DAEMON_TCP_DEFAULT_PORT: () => DAEMON_TCP_DEFAULT_PORT,
76
+ DAEMON_PATHS: () => DAEMON_PATHS
77
+ });
78
+ import { join as join2 } from "node:path";
79
+ var DAEMON_PATHS, DAEMON_TCP_HOST = "127.0.0.1", DAEMON_TCP_DEFAULT_PORT = 47823;
80
+ var init_paths2 = __esm(() => {
81
+ init_paths();
82
+ DAEMON_PATHS = {
83
+ get DAEMON_DIR() {
84
+ return join2(PATHS.CONFIG_DIR, "daemon");
85
+ },
86
+ get PID_FILE() {
87
+ return join2(this.DAEMON_DIR, "daemon.pid");
88
+ },
89
+ get SOCK_FILE() {
90
+ return join2(this.DAEMON_DIR, "daemon.sock");
91
+ },
92
+ get TOKEN_FILE() {
93
+ return join2(this.DAEMON_DIR, "local-token");
94
+ },
95
+ get OUTBOX_DB() {
96
+ return join2(this.DAEMON_DIR, "outbox.db");
97
+ },
98
+ get INBOX_DB() {
99
+ return join2(this.DAEMON_DIR, "inbox.db");
100
+ },
101
+ get LOG_FILE() {
102
+ return join2(this.DAEMON_DIR, "daemon.log");
103
+ }
104
+ };
105
+ });
106
+
107
+ // src/constants/urls.ts
108
+ var exports_urls = {};
109
+ __export(exports_urls, {
110
+ env: () => env,
111
+ VERSION: () => VERSION,
112
+ URLS: () => URLS
113
+ });
114
+ var URLS, VERSION = "1.24.0", env;
115
+ var init_urls = __esm(() => {
116
+ URLS = {
117
+ BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
118
+ API_BASE: process.env.CLAUDEMESH_API_URL ?? "https://claudemesh.com",
119
+ DASHBOARD: "https://claudemesh.com/dashboard",
120
+ NPM_REGISTRY: "https://registry.npmjs.org/claudemesh-cli"
121
+ };
122
+ env = {
123
+ CLAUDEMESH_BROKER_URL: URLS.BROKER,
124
+ CLAUDEMESH_CONFIG_DIR: process.env.CLAUDEMESH_CONFIG_DIR || undefined,
125
+ CLAUDEMESH_DEBUG: process.env.CLAUDEMESH_DEBUG === "1" || process.env.CLAUDEMESH_DEBUG === "true"
126
+ };
127
+ });
128
+
62
129
  // src/services/config/schemas.ts
63
130
  function emptyConfig() {
64
131
  return { version: 1, meshes: [] };
@@ -348,17 +415,17 @@ import { randomBytes as randomBytes2 } from "node:crypto";
348
415
  function detectClaudeSessionId() {
349
416
  try {
350
417
  const { readdirSync, statSync, readFileSync: readFileSync2 } = __require("node:fs");
351
- const { join: join2 } = __require("node:path");
418
+ const { join: join3 } = __require("node:path");
352
419
  const { homedir: homedir2 } = __require("node:os");
353
420
  const cwd = process.cwd();
354
- const projectsDir = join2(homedir2(), ".claude", "projects");
421
+ const projectsDir = join3(homedir2(), ".claude", "projects");
355
422
  const cwdHash = cwd.replace(/\//g, "-");
356
423
  const entries = readdirSync(projectsDir);
357
424
  const projectDir = entries.find((e) => e === cwdHash || e.startsWith(cwdHash));
358
425
  if (!projectDir)
359
426
  return null;
360
- const fullDir = join2(projectsDir, projectDir);
361
- const jsonls = readdirSync(fullDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ name: f, mtime: statSync(join2(fullDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
427
+ const fullDir = join3(projectsDir, projectDir);
428
+ const jsonls = readdirSync(fullDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ name: f, mtime: statSync(join3(fullDir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
362
429
  if (jsonls.length === 0)
363
430
  return null;
364
431
  const latest = jsonls[0];
@@ -568,9 +635,9 @@ class BrokerClient {
568
635
  let nonce;
569
636
  let ciphertext;
570
637
  if (isDirectTarget(targetSpec)) {
571
- const env = await encryptDirect(message, targetSpec, this.sessionSecretKey ?? this.mesh.secretKey);
572
- nonce = env.nonce;
573
- ciphertext = env.ciphertext;
638
+ const env2 = await encryptDirect(message, targetSpec, this.sessionSecretKey ?? this.mesh.secretKey);
639
+ nonce = env2.nonce;
640
+ ciphertext = env2.ciphertext;
574
641
  } else {
575
642
  nonce = randomNonce();
576
643
  ciphertext = Buffer.from(message, "utf-8").toString("base64");
@@ -1696,7 +1763,7 @@ class BrokerClient {
1696
1763
  }
1697
1764
  static MAX_FILE_SIZE = 1048576;
1698
1765
  async handlePeerFileRequest(msg) {
1699
- const { resolve, join: join2, normalize } = await import("node:path");
1766
+ const { resolve, join: join3, normalize } = await import("node:path");
1700
1767
  const { readFileSync: readFileSync2, statSync, realpathSync } = await import("node:fs");
1701
1768
  const reqId = msg._reqId;
1702
1769
  const sendResponse = (content, error) => {
@@ -1721,7 +1788,7 @@ class BrokerClient {
1721
1788
  }
1722
1789
  let resolvedPath = null;
1723
1790
  for (const dir of this.sharedDirs) {
1724
- const candidate = resolve(join2(dir, msg.filePath));
1791
+ const candidate = resolve(join3(dir, msg.filePath));
1725
1792
  let realCandidate;
1726
1793
  let realDir;
1727
1794
  try {
@@ -1761,7 +1828,7 @@ class BrokerClient {
1761
1828
  }
1762
1829
  }
1763
1830
  async handlePeerDirRequest(msg) {
1764
- const { resolve, join: join2, normalize, relative } = await import("node:path");
1831
+ const { resolve, join: join3, normalize, relative } = await import("node:path");
1765
1832
  const { readdirSync, statSync, realpathSync } = await import("node:fs");
1766
1833
  const reqId = msg._reqId;
1767
1834
  const sendResponse = (entries, error) => {
@@ -1787,7 +1854,7 @@ class BrokerClient {
1787
1854
  }
1788
1855
  let resolvedPath = null;
1789
1856
  for (const dir of this.sharedDirs) {
1790
- const candidate = resolve(join2(dir, dirPath));
1857
+ const candidate = resolve(join3(dir, dirPath));
1791
1858
  let realCandidate;
1792
1859
  let realDir;
1793
1860
  try {
@@ -1833,16 +1900,16 @@ class BrokerClient {
1833
1900
  break;
1834
1901
  if (item.name.startsWith("."))
1835
1902
  continue;
1836
- const relPath = relative(resolvedPath, join2(dir, item.name));
1903
+ const relPath = relative(resolvedPath, join3(dir, item.name));
1837
1904
  const label = item.isDirectory() ? relPath + "/" : relPath;
1838
1905
  if (pattern && !pattern.test(item.name)) {
1839
1906
  if (item.isDirectory())
1840
- walk(join2(dir, item.name), depth + 1);
1907
+ walk(join3(dir, item.name), depth + 1);
1841
1908
  continue;
1842
1909
  }
1843
1910
  entries.push(label);
1844
1911
  if (item.isDirectory())
1845
- walk(join2(dir, item.name), depth + 1);
1912
+ walk(join3(dir, item.name), depth + 1);
1846
1913
  }
1847
1914
  } catch {}
1848
1915
  };
@@ -2541,44 +2608,10 @@ var init_ws_client = __esm(() => {
2541
2608
  });
2542
2609
 
2543
2610
  // src/services/broker/manager.ts
2544
- async function ensureClient(mesh) {
2545
- const existing = clients.get(mesh.meshId);
2546
- if (existing)
2547
- return existing;
2548
- const isDebug = process.env.CLAUDEMESH_DEBUG === "1" || process.env.CLAUDEMESH_DEBUG === "true";
2549
- const client = new BrokerClient(mesh, { debug: isDebug, displayName: configDisplayName });
2550
- clients.set(mesh.meshId, client);
2551
- try {
2552
- await client.connect();
2553
- for (const g of configGroups ?? []) {
2554
- try {
2555
- await client.joinGroup(g.name, g.role);
2556
- } catch {}
2557
- }
2558
- } catch (err) {
2559
- process.stderr.write(`[claudemesh] broker connect failed for ${mesh.slug}: ${err instanceof Error ? err.message : err} (will retry)
2560
- `);
2561
- }
2562
- return client;
2563
- }
2564
- async function startClients(config) {
2565
- configDisplayName = config.displayName;
2566
- configGroups = config.groups ?? [];
2567
- await Promise.allSettled(config.meshes.map(ensureClient));
2568
- }
2569
- function allClients() {
2570
- return [...clients.values()];
2571
- }
2572
- function stopAll() {
2573
- for (const c of clients.values())
2574
- c.close();
2575
- clients.clear();
2576
- }
2577
- var clients, configDisplayName, configGroups;
2611
+ var clients;
2578
2612
  var init_manager = __esm(() => {
2579
2613
  init_ws_client();
2580
2614
  clients = new Map;
2581
- configGroups = [];
2582
2615
  });
2583
2616
 
2584
2617
  // src/services/broker/envelope.ts
@@ -2598,1924 +2631,393 @@ var init_facade3 = __esm(() => {
2598
2631
  init_errors();
2599
2632
  });
2600
2633
 
2601
- // src/services/bridge/protocol.ts
2602
- import { homedir as homedir2 } from "node:os";
2603
- import { join as join2 } from "node:path";
2604
- function socketPath(meshSlug) {
2605
- return join2(homedir2(), ".claudemesh", "sockets", `${meshSlug}.sock`);
2634
+ // src/mcp/server.ts
2635
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2636
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2637
+ import {
2638
+ ListToolsRequestSchema,
2639
+ CallToolRequestSchema,
2640
+ ListPromptsRequestSchema,
2641
+ GetPromptRequestSchema,
2642
+ ListResourcesRequestSchema,
2643
+ ReadResourceRequestSchema
2644
+ } from "@modelcontextprotocol/sdk/types.js";
2645
+ import { existsSync as existsSync2 } from "node:fs";
2646
+ import { request as httpRequest } from "node:http";
2647
+ async function daemonReady() {
2648
+ for (let i = 0;i < DAEMON_BOOT_RETRIES; i++) {
2649
+ if (existsSync2(DAEMON_PATHS.SOCK_FILE))
2650
+ return true;
2651
+ await new Promise((r) => setTimeout(r, DAEMON_BOOT_RETRY_MS));
2652
+ }
2653
+ return false;
2606
2654
  }
2607
- function socketDir() {
2608
- return join2(homedir2(), ".claudemesh", "sockets");
2655
+ function bailNoDaemon() {
2656
+ process.stderr.write(`[claudemesh] daemon is not running.
2657
+ ` + ` Start it: claudemesh daemon up --mesh <slug>
2658
+ ` + ` Or install as service: claudemesh daemon install-service --mesh <slug>
2659
+ ` + ` Diagnose: claudemesh doctor
2660
+ ` + `
2661
+ ` + ` As of 1.24.0 the daemon is required for in-Claude-Code use of
2662
+ ` + ` claudemesh. The CLI itself (claudemesh send/peer/inbox/...) still
2663
+ ` + ` works without a daemon.
2664
+ `);
2665
+ process.exit(1);
2609
2666
  }
2610
- function frame(obj) {
2611
- return JSON.stringify(obj) + `
2612
- `;
2667
+ function daemonGet(path) {
2668
+ return new Promise((resolve, reject) => {
2669
+ const req = httpRequest({ socketPath: DAEMON_PATHS.SOCK_FILE, path, method: "GET", timeout: 5000 }, (res) => {
2670
+ const chunks = [];
2671
+ res.on("data", (c) => chunks.push(c));
2672
+ res.on("end", () => {
2673
+ const text = Buffer.concat(chunks).toString("utf8");
2674
+ let body = null;
2675
+ try {
2676
+ body = JSON.parse(text);
2677
+ } catch {
2678
+ body = text;
2679
+ }
2680
+ resolve({ status: res.statusCode ?? 0, body });
2681
+ });
2682
+ });
2683
+ req.on("error", reject);
2684
+ req.on("timeout", () => req.destroy(new Error("daemon_ipc_timeout")));
2685
+ req.end();
2686
+ });
2613
2687
  }
2614
-
2615
- class LineParser {
2616
- buf = "";
2617
- feed(chunk) {
2618
- this.buf += typeof chunk === "string" ? chunk : chunk.toString("utf-8");
2619
- const lines = [];
2620
- let nl = this.buf.indexOf(`
2688
+ function subscribeEvents(onEvent) {
2689
+ let active = true;
2690
+ let req = null;
2691
+ const connect = () => {
2692
+ if (!active)
2693
+ return;
2694
+ req = httpRequest({
2695
+ socketPath: DAEMON_PATHS.SOCK_FILE,
2696
+ path: "/v1/events",
2697
+ method: "GET",
2698
+ headers: { Accept: "text/event-stream" }
2699
+ });
2700
+ let buffer = "";
2701
+ req.on("response", (res) => {
2702
+ res.setEncoding("utf8");
2703
+ res.on("data", (chunk) => {
2704
+ buffer += chunk;
2705
+ let idx;
2706
+ while ((idx = buffer.indexOf(`
2707
+
2708
+ `)) >= 0) {
2709
+ const block = buffer.slice(0, idx);
2710
+ buffer = buffer.slice(idx + 2);
2711
+ if (!block.trim())
2712
+ continue;
2713
+ let kind = "message";
2714
+ let dataLine = "";
2715
+ for (const line of block.split(`
2716
+ `)) {
2717
+ if (line.startsWith(":"))
2718
+ continue;
2719
+ if (line.startsWith("event:"))
2720
+ kind = line.slice(6).trim();
2721
+ else if (line.startsWith("data:"))
2722
+ dataLine = line.slice(5).trim();
2723
+ }
2724
+ if (!dataLine)
2725
+ continue;
2726
+ try {
2727
+ const parsed = JSON.parse(dataLine);
2728
+ onEvent({ kind, ts: String(parsed.ts ?? ""), data: parsed });
2729
+ } catch {}
2730
+ }
2731
+ });
2732
+ res.on("end", () => {
2733
+ if (active) {
2734
+ process.stderr.write(`[claudemesh-mcp] sse stream ended; reconnecting in 1s
2621
2735
  `);
2622
- while (nl !== -1) {
2623
- lines.push(this.buf.slice(0, nl));
2624
- this.buf = this.buf.slice(nl + 1);
2625
- nl = this.buf.indexOf(`
2736
+ setTimeout(connect, 1000);
2737
+ }
2738
+ });
2739
+ res.on("error", (err) => process.stderr.write(`[claudemesh-mcp] sse error: ${err.message}
2740
+ `));
2741
+ });
2742
+ req.on("error", (err) => {
2743
+ process.stderr.write(`[claudemesh-mcp] sse connect error: ${err.message}
2626
2744
  `);
2745
+ if (active)
2746
+ setTimeout(connect, 2000);
2747
+ });
2748
+ req.end();
2749
+ };
2750
+ connect();
2751
+ return {
2752
+ close: () => {
2753
+ active = false;
2754
+ try {
2755
+ req?.destroy();
2756
+ } catch {}
2627
2757
  }
2628
- return lines;
2629
- }
2758
+ };
2630
2759
  }
2631
- var init_protocol = () => {};
2632
-
2633
- // src/services/bridge/server.ts
2634
- import { createServer } from "node:net";
2635
- import { mkdirSync as mkdirSync2, unlinkSync, existsSync as existsSync2, chmodSync as chmodSync2 } from "node:fs";
2636
- async function resolveTarget(client, to) {
2637
- if (to.startsWith("@") || to === "*" || /^[0-9a-f]{64}$/i.test(to)) {
2638
- return { ok: true, spec: to };
2639
- }
2640
- const peers = await client.listPeers();
2641
- const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
2642
- if (!match) {
2643
- return {
2644
- ok: false,
2645
- error: `peer "${to}" not found. online: ${peers.map((p) => p.displayName).join(", ") || "(none)"}`
2646
- };
2760
+ async function startMcpServer() {
2761
+ const serviceIdx = process.argv.indexOf("--service");
2762
+ if (serviceIdx !== -1 && process.argv[serviceIdx + 1]) {
2763
+ return startServiceProxy(process.argv[serviceIdx + 1]);
2647
2764
  }
2648
- return { ok: true, spec: match.pubkey };
2649
- }
2650
- async function dispatch(client, req) {
2651
- const args = req.args ?? {};
2652
- try {
2653
- switch (req.verb) {
2654
- case "ping": {
2655
- const peers = await client.listPeers();
2656
- return {
2657
- id: req.id,
2658
- ok: true,
2659
- result: {
2660
- mesh: client.meshSlug,
2661
- ws_status: client.status,
2662
- peers_online: peers.length,
2663
- push_buffer: client.pushHistory.length
2664
- }
2665
- };
2666
- }
2667
- case "peers": {
2668
- const peers = await client.listPeers();
2669
- return { id: req.id, ok: true, result: peers };
2670
- }
2671
- case "send": {
2672
- const to = String(args.to ?? "");
2673
- const message = String(args.message ?? "");
2674
- const priority = args.priority ?? "next";
2675
- if (!to || !message) {
2676
- return { id: req.id, ok: false, error: "send: `to` and `message` required" };
2677
- }
2678
- const resolved = await resolveTarget(client, to);
2679
- if (!resolved.ok)
2680
- return { id: req.id, ok: false, error: resolved.error };
2681
- const result = await client.send(resolved.spec, message, priority);
2682
- if (!result.ok) {
2683
- return { id: req.id, ok: false, error: result.error ?? "send failed" };
2684
- }
2685
- return {
2686
- id: req.id,
2687
- ok: true,
2688
- result: { messageId: result.messageId, target: resolved.spec }
2689
- };
2690
- }
2691
- case "summary": {
2692
- const text = String(args.summary ?? "");
2693
- if (!text)
2694
- return { id: req.id, ok: false, error: "summary: `summary` required" };
2695
- await client.setSummary(text);
2696
- return { id: req.id, ok: true, result: { summary: text } };
2697
- }
2698
- case "status_set": {
2699
- const state = String(args.status ?? "");
2700
- if (!["idle", "working", "dnd"].includes(state)) {
2701
- return { id: req.id, ok: false, error: "status_set: must be idle | working | dnd" };
2702
- }
2703
- await client.setStatus(state);
2704
- return { id: req.id, ok: true, result: { status: state } };
2765
+ const ok = await daemonReady();
2766
+ if (!ok)
2767
+ bailNoDaemon();
2768
+ const server = new Server({ name: "claudemesh", version: VERSION }, { capabilities: { tools: {}, prompts: {}, resources: {} } });
2769
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [] }));
2770
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
2771
+ try {
2772
+ const { status, body } = await daemonGet("/v1/skills");
2773
+ if (status !== 200)
2774
+ return { prompts: [] };
2775
+ const skills = body?.skills ?? [];
2776
+ return { prompts: skills.map((s) => ({ name: s.name, description: s.description, arguments: [] })) };
2777
+ } catch {
2778
+ return { prompts: [] };
2779
+ }
2780
+ });
2781
+ server.setRequestHandler(GetPromptRequestSchema, async (req) => {
2782
+ const name = req.params.name;
2783
+ const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
2784
+ if (status === 404)
2785
+ throw new Error(`Skill "${name}" not found in the mesh`);
2786
+ if (status !== 200)
2787
+ throw new Error(`daemon returned ${status} fetching skill`);
2788
+ const skill = body.skill;
2789
+ let content = skill.instructions;
2790
+ const m = skill.manifest;
2791
+ if (m && typeof m === "object") {
2792
+ const fm = ["---"];
2793
+ if (m.description)
2794
+ fm.push(`description: "${m.description}"`);
2795
+ if (m.when_to_use)
2796
+ fm.push(`when_to_use: "${m.when_to_use}"`);
2797
+ if (Array.isArray(m.allowed_tools) && m.allowed_tools.length) {
2798
+ fm.push(`allowed-tools:
2799
+ ${m.allowed_tools.map((t) => ` - ${t}`).join(`
2800
+ `)}`);
2705
2801
  }
2706
- case "visible": {
2707
- const visible = Boolean(args.visible);
2708
- await client.setVisible(visible);
2709
- return { id: req.id, ok: true, result: { visible } };
2802
+ if (m.model)
2803
+ fm.push(`model: ${m.model}`);
2804
+ if (m.context)
2805
+ fm.push(`context: ${m.context}`);
2806
+ if (m.agent)
2807
+ fm.push(`agent: ${m.agent}`);
2808
+ if (m.user_invocable === false)
2809
+ fm.push(`user-invocable: false`);
2810
+ if (m.argument_hint)
2811
+ fm.push(`argument-hint: "${m.argument_hint}"`);
2812
+ fm.push(`---
2813
+ `);
2814
+ if (fm.length > 3)
2815
+ content = fm.join(`
2816
+ `) + content;
2817
+ if (m.context === "fork") {
2818
+ const agentType = m.agent || "general-purpose";
2819
+ const modelHint = m.model ? `, model: "${m.model}"` : "";
2820
+ const toolsHint = m.allowed_tools?.length ? `
2821
+ Only use these tools: ${m.allowed_tools.join(", ")}.` : "";
2822
+ content = `IMPORTANT: Execute this skill in an isolated sub-agent. Use the Agent tool with subagent_type="${agentType}"${modelHint}. Pass the full instructions below as the agent prompt.${toolsHint}
2823
+
2824
+ ` + content;
2710
2825
  }
2711
- default:
2712
- return { id: req.id, ok: false, error: `unknown verb: ${req.verb}` };
2713
2826
  }
2714
- } catch (err) {
2715
2827
  return {
2716
- id: req.id,
2717
- ok: false,
2718
- error: err instanceof Error ? err.message : String(err)
2828
+ description: skill.description,
2829
+ messages: [{ role: "user", content: { type: "text", text: content } }]
2719
2830
  };
2720
- }
2721
- }
2722
- function handleConnection(socket, client) {
2723
- const parser = new LineParser;
2724
- socket.on("data", (chunk) => {
2725
- const lines = parser.feed(chunk);
2726
- for (const line of lines) {
2727
- if (!line.trim())
2728
- continue;
2729
- let req;
2730
- try {
2731
- req = JSON.parse(line);
2732
- } catch {
2733
- continue;
2734
- }
2735
- if (!req || typeof req !== "object" || !req.id || !req.verb)
2736
- continue;
2737
- dispatch(client, req).then((res) => {
2738
- try {
2739
- socket.write(frame(res));
2740
- } catch {}
2741
- });
2742
- }
2743
2831
  });
2744
- socket.on("error", () => {});
2745
- }
2746
- function startBridgeServer(client) {
2747
- const path = socketPath(client.meshSlug);
2748
- const dir = socketDir();
2749
- if (!existsSync2(dir)) {
2750
- mkdirSync2(dir, { recursive: true, mode: 448 });
2751
- }
2752
- if (existsSync2(path)) {
2832
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
2753
2833
  try {
2754
- unlinkSync(path);
2755
- } catch {}
2756
- }
2757
- const server = createServer((socket) => handleConnection(socket, client));
2758
- try {
2759
- server.listen(path);
2760
- } catch (err) {
2761
- process.stderr.write(`[claudemesh] bridge: failed to bind ${path}: ${String(err)}
2762
- `);
2763
- return null;
2764
- }
2765
- server.on("error", (err) => {
2766
- process.stderr.write(`[claudemesh] bridge: ${String(err)}
2834
+ const { body } = await daemonGet("/v1/skills");
2835
+ const skills = body?.skills ?? [];
2836
+ return {
2837
+ resources: skills.map((s) => ({
2838
+ uri: `skill://claudemesh/${encodeURIComponent(s.name)}`,
2839
+ name: s.name,
2840
+ description: s.description,
2841
+ mimeType: "text/markdown"
2842
+ }))
2843
+ };
2844
+ } catch {
2845
+ return { resources: [] };
2846
+ }
2847
+ });
2848
+ server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
2849
+ const uri = req.params.uri;
2850
+ const m = uri.match(/^skill:\/\/claudemesh\/(.+)$/);
2851
+ if (!m)
2852
+ throw new Error(`Unknown resource URI: ${uri}`);
2853
+ const name = decodeURIComponent(m[1]);
2854
+ const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
2855
+ if (status === 404)
2856
+ throw new Error(`Skill "${name}" not found`);
2857
+ if (status !== 200)
2858
+ throw new Error(`daemon returned ${status} fetching skill`);
2859
+ const skill = body.skill;
2860
+ const fm = ["---"];
2861
+ fm.push(`name: ${skill.name}`);
2862
+ fm.push(`description: "${skill.description}"`);
2863
+ if (skill.tags?.length)
2864
+ fm.push(`tags: [${skill.tags.join(", ")}]`);
2865
+ const mf = skill.manifest;
2866
+ if (mf && typeof mf === "object") {
2867
+ if (mf.when_to_use)
2868
+ fm.push(`when_to_use: "${mf.when_to_use}"`);
2869
+ if (Array.isArray(mf.allowed_tools) && mf.allowed_tools.length) {
2870
+ fm.push(`allowed-tools:
2871
+ ${mf.allowed_tools.map((t) => ` - ${t}`).join(`
2872
+ `)}`);
2873
+ }
2874
+ if (mf.model)
2875
+ fm.push(`model: ${mf.model}`);
2876
+ if (mf.context)
2877
+ fm.push(`context: ${mf.context}`);
2878
+ }
2879
+ fm.push(`---
2767
2880
  `);
2881
+ return { contents: [{ uri, mimeType: "text/markdown", text: fm.join(`
2882
+ `) + skill.instructions }] };
2768
2883
  });
2769
- try {
2770
- chmodSync2(path, 384);
2771
- } catch {}
2772
- let stopped = false;
2773
- return {
2774
- path,
2775
- stop() {
2776
- if (stopped)
2777
- return;
2778
- stopped = true;
2884
+ const sub = subscribeEvents(async (ev) => {
2885
+ if (ev.kind === "message") {
2886
+ const d = ev.data;
2887
+ const fromName = String(d.sender_name ?? "unknown");
2888
+ const fromMember = String(d.sender_member_pubkey ?? d.sender_pubkey ?? "");
2889
+ const body = String(d.body ?? "(decrypt failed)");
2890
+ const priority = String(d.priority ?? "next");
2891
+ const prioBadge = priority === "now" ? "[URGENT] " : priority === "low" ? "[low] " : "";
2892
+ const topicTag = d.topic ? ` (#${d.topic})` : "";
2893
+ const content = `${prioBadge}${fromName}${topicTag}: ${body}`;
2779
2894
  try {
2780
- server.close();
2781
- } catch {}
2895
+ await server.notification({
2896
+ method: "notifications/claude/channel",
2897
+ params: {
2898
+ content,
2899
+ meta: {
2900
+ from_id: fromMember,
2901
+ from_pubkey: fromMember,
2902
+ from_session_pubkey: String(d.sender_pubkey ?? ""),
2903
+ from_name: fromName,
2904
+ mesh_slug: String(d.mesh ?? ""),
2905
+ priority,
2906
+ message_id: String(d.broker_message_id ?? d.id ?? ""),
2907
+ client_message_id: String(d.client_message_id ?? ""),
2908
+ ...d.topic ? { topic: String(d.topic) } : {},
2909
+ ...d.reply_to_id ? { reply_to_id: String(d.reply_to_id) } : {},
2910
+ ...d.subtype ? { subtype: String(d.subtype) } : {}
2911
+ }
2912
+ }
2913
+ });
2914
+ } catch (err) {
2915
+ process.stderr.write(`[claudemesh-mcp] channel emit failed: ${err}
2916
+ `);
2917
+ }
2918
+ } else if (ev.kind === "peer_join" || ev.kind === "peer_leave" || ev.kind === "system") {
2919
+ const d = ev.data;
2920
+ const eventName = String(d.event ?? ev.kind);
2921
+ let content;
2922
+ if (ev.kind === "peer_join") {
2923
+ content = `[system] Peer "${String(d.name ?? "unknown")}" joined the mesh`;
2924
+ } else if (ev.kind === "peer_leave") {
2925
+ content = `[system] Peer "${String(d.name ?? "unknown")}" left the mesh`;
2926
+ } else {
2927
+ content = `[system] ${eventName}: ${JSON.stringify(d).slice(0, 240)}`;
2928
+ }
2782
2929
  try {
2783
- unlinkSync(path);
2930
+ await server.notification({
2931
+ method: "notifications/claude/channel",
2932
+ params: {
2933
+ content,
2934
+ meta: {
2935
+ kind: "system",
2936
+ event: eventName,
2937
+ mesh_slug: String(d.mesh ?? "")
2938
+ }
2939
+ }
2940
+ });
2784
2941
  } catch {}
2785
2942
  }
2786
- };
2787
- }
2788
- var init_server = __esm(() => {
2789
- init_protocol();
2790
- });
2791
-
2792
- // src/commands/connect.ts
2793
- import { hostname } from "node:os";
2794
- import { createInterface } from "node:readline";
2795
- async function pickMesh(meshes) {
2796
- console.log(`
2797
- Select mesh:`);
2798
- meshes.forEach((m, i) => {
2799
- console.log(` ${i + 1}) ${m.slug}`);
2800
- });
2801
- console.log("");
2802
- const rl = createInterface({ input: process.stdin, output: process.stdout });
2803
- return new Promise((resolve) => {
2804
- rl.question(" Choice [1]: ", (answer) => {
2805
- rl.close();
2806
- const idx = parseInt(answer || "1", 10) - 1;
2807
- if (idx >= 0 && idx < meshes.length) {
2808
- resolve(meshes[idx]);
2809
- } else {
2810
- console.error(" Invalid choice, using first mesh.");
2811
- resolve(meshes[0]);
2812
- }
2813
- });
2814
2943
  });
2944
+ const transport = new StdioServerTransport;
2945
+ await server.connect(transport);
2946
+ const keepalive = setInterval(() => {}, 1000);
2947
+ const shutdown = () => {
2948
+ clearInterval(keepalive);
2949
+ sub.close();
2950
+ process.exit(0);
2951
+ };
2952
+ process.on("SIGTERM", shutdown);
2953
+ process.on("SIGINT", shutdown);
2815
2954
  }
2816
- async function withMesh(opts, fn) {
2955
+ async function startServiceProxy(serviceName) {
2817
2956
  const config = readConfig();
2818
2957
  if (config.meshes.length === 0) {
2819
- console.error("No meshes joined. Run `claudemesh join <url>` first.");
2958
+ process.stderr.write(`[mesh:${serviceName}] no meshes joined
2959
+ `);
2820
2960
  process.exit(1);
2821
2961
  }
2822
- let mesh;
2823
- if (opts.meshSlug) {
2824
- const found = config.meshes.find((m) => m.slug === opts.meshSlug);
2825
- if (!found) {
2826
- console.error(`Mesh "${opts.meshSlug}" not found. Joined: ${config.meshes.map((m) => m.slug).join(", ")}`);
2827
- process.exit(1);
2828
- }
2829
- mesh = found;
2830
- } else if (config.meshes.length === 1) {
2831
- mesh = config.meshes[0];
2832
- } else {
2833
- mesh = await pickMesh(config.meshes);
2834
- }
2835
- const displayName = opts.displayName ?? config.displayName ?? `${hostname()}-${process.pid}`;
2836
- const client = new BrokerClient(mesh, { displayName, quiet: true });
2962
+ const mesh = config.meshes[0];
2963
+ const client = new BrokerClient(mesh, {
2964
+ displayName: config.displayName ?? `proxy:${serviceName}`
2965
+ });
2837
2966
  try {
2838
2967
  await client.connect();
2839
- const result = await fn(client, mesh);
2840
- return result;
2841
2968
  } catch (e) {
2842
- if (client.terminalClose) {
2843
- const { code, reason } = client.terminalClose;
2844
- if (code === 4002) {
2845
- console.error(`
2846
- ✘ ${reason}
2847
- `);
2848
- } else if (code === 4001) {
2849
- console.error(`
2850
- ✘ Kicked from this mesh. Run \`claudemesh\` to rejoin.
2969
+ process.stderr.write(`[mesh:${serviceName}] broker connect failed: ${e instanceof Error ? e.message : String(e)}
2851
2970
  `);
2852
- } else {
2853
- console.error(`
2854
- Broker closed connection: ${reason}
2971
+ process.exit(1);
2972
+ }
2973
+ await new Promise((r) => setTimeout(r, 1500));
2974
+ let tools = [];
2975
+ try {
2976
+ const fetched = await client.getServiceTools(serviceName);
2977
+ tools = fetched;
2978
+ } catch {
2979
+ const cached = client.serviceCatalog.find((s) => s.name === serviceName);
2980
+ if (cached)
2981
+ tools = cached.tools;
2982
+ }
2983
+ if (tools.length === 0) {
2984
+ process.stderr.write(`[mesh:${serviceName}] no tools found — service may not be running
2855
2985
  `);
2856
- }
2857
- process.exit(1);
2858
- }
2859
- throw e;
2860
- } finally {
2861
- client.close();
2862
- }
2863
- }
2864
- var init_connect = __esm(() => {
2865
- init_facade3();
2866
- init_facade();
2867
- });
2868
-
2869
- // src/ui/styles.ts
2870
- function moveTo(row, col) {
2871
- return isTTY ? `\x1B[${row};${col}H` : "";
2872
- }
2873
- function visibleLength(s) {
2874
- return s.replace(/\x1b\[[^m]*m/g, "").length;
2875
- }
2876
- var isTTY, esc = (code) => (s) => isTTY ? `${code}${s}\x1B[0m` : s, orange, clay, amber, bold, dim, green, yellow, red, cyan, boldOrange, HIDE_CURSOR, SHOW_CURSOR, CLEAR_SCREEN, CLEAR_LINE, icons;
2877
- var init_styles = __esm(() => {
2878
- isTTY = process.stdout.isTTY && !process.env.NO_COLOR && process.env.TERM !== "dumb";
2879
- orange = esc("\x1B[38;5;208m");
2880
- clay = esc("\x1B[38;5;173m");
2881
- amber = esc("\x1B[38;5;214m");
2882
- bold = esc("\x1B[1m");
2883
- dim = esc("\x1B[2m");
2884
- green = esc("\x1B[32m");
2885
- yellow = esc("\x1B[33m");
2886
- red = esc("\x1B[31m");
2887
- cyan = esc("\x1B[36m");
2888
- boldOrange = esc("\x1B[1m\x1B[38;5;208m");
2889
- HIDE_CURSOR = isTTY ? "\x1B[?25l" : "";
2890
- SHOW_CURSOR = isTTY ? "\x1B[?25h" : "";
2891
- CLEAR_SCREEN = isTTY ? "\x1B[2J\x1B[H" : "";
2892
- CLEAR_LINE = isTTY ? "\x1B[K" : "";
2893
- icons = {
2894
- check: "✔",
2895
- cross: "✘",
2896
- warn: "⚠",
2897
- arrow: "→",
2898
- bullet: "●",
2899
- dash: "—",
2900
- ellipsis: "…"
2901
- };
2902
- });
2903
-
2904
- // src/ui/render.ts
2905
- var OUT, ERR, INDENT = " ", render;
2906
- var init_render = __esm(() => {
2907
- init_styles();
2908
- OUT = process.stdout;
2909
- ERR = process.stderr;
2910
- render = {
2911
- blank() {
2912
- OUT.write(`
2913
- `);
2914
- },
2915
- ok(msg, detail) {
2916
- const d = detail ? ` ${dim("(" + detail + ")")}` : "";
2917
- OUT.write(`${INDENT}${green(icons.check)} ${msg}${d}
2918
- `);
2919
- },
2920
- warn(msg, hint) {
2921
- OUT.write(`${INDENT}${yellow(icons.warn)} ${msg}
2922
- `);
2923
- if (hint)
2924
- OUT.write(`${INDENT} ${dim(hint)}
2925
- `);
2926
- },
2927
- err(msg, hint) {
2928
- ERR.write(`${INDENT}${red(icons.cross)} ${msg}
2929
- `);
2930
- if (hint)
2931
- ERR.write(`${INDENT} ${dim(hint)}
2932
- `);
2933
- },
2934
- info(msg) {
2935
- OUT.write(`${INDENT}${msg}
2936
- `);
2937
- },
2938
- section(title) {
2939
- OUT.write(`
2940
- ${INDENT}${dim("—")} ${clay(title)}
2941
-
2942
- `);
2943
- },
2944
- heading(title) {
2945
- OUT.write(`${INDENT}${bold(title)}
2946
- `);
2947
- },
2948
- kv(pairs, opts) {
2949
- const pad = opts?.padTo ?? Math.max(...pairs.map(([k]) => k.length)) + 2;
2950
- for (const [k, v] of pairs) {
2951
- OUT.write(`${INDENT}${dim(k.padEnd(pad, " "))}${v}
2952
- `);
2953
- }
2954
- },
2955
- code(snippet) {
2956
- for (const line of snippet.split(`
2957
- `)) {
2958
- OUT.write(`${INDENT} ${cyan(line)}
2959
- `);
2960
- }
2961
- },
2962
- link(url) {
2963
- OUT.write(`${INDENT}${clay(url)}
2964
- `);
2965
- },
2966
- hint(msg) {
2967
- OUT.write(`${INDENT}${dim(icons.arrow + " " + msg)}
2968
- `);
2969
- }
2970
- };
2971
- });
2972
-
2973
- // src/constants/exit-codes.ts
2974
- var EXIT;
2975
- var init_exit_codes = __esm(() => {
2976
- EXIT = {
2977
- SUCCESS: 0,
2978
- USER_CANCELLED: 1,
2979
- AUTH_FAILED: 2,
2980
- INVALID_ARGS: 3,
2981
- NETWORK_ERROR: 4,
2982
- NOT_FOUND: 5,
2983
- ALREADY_EXISTS: 6,
2984
- PERMISSION_DENIED: 7,
2985
- INTERNAL_ERROR: 8,
2986
- CLAUDE_MISSING: 9
2987
- };
2988
- });
2989
-
2990
- // src/constants/timings.ts
2991
- var TIMINGS;
2992
- var init_timings = __esm(() => {
2993
- TIMINGS = {
2994
- DEVICE_CODE_POLL_MS: 1500,
2995
- DEVICE_CODE_TIMEOUT_MS: 5 * 60 * 1000,
2996
- WS_RECONNECT_BASE_MS: 1000,
2997
- WS_RECONNECT_MAX_MS: 30000,
2998
- UPDATE_CHECK_INTERVAL_MS: 24 * 60 * 60 * 1000,
2999
- TELEGRAM_CONNECT_TIMEOUT_MS: 5 * 60 * 1000,
3000
- TELEGRAM_POLL_INTERVAL_MS: 2000,
3001
- API_TIMEOUT_MS: 15000,
3002
- API_RETRY_COUNT: 2
3003
- };
3004
- });
3005
-
3006
- // src/constants/urls.ts
3007
- var exports_urls = {};
3008
- __export(exports_urls, {
3009
- env: () => env,
3010
- VERSION: () => VERSION,
3011
- URLS: () => URLS
3012
- });
3013
- var URLS, VERSION = "1.23.0", env;
3014
- var init_urls = __esm(() => {
3015
- URLS = {
3016
- BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
3017
- API_BASE: process.env.CLAUDEMESH_API_URL ?? "https://claudemesh.com",
3018
- DASHBOARD: "https://claudemesh.com/dashboard",
3019
- NPM_REGISTRY: "https://registry.npmjs.org/claudemesh-cli"
3020
- };
3021
- env = {
3022
- CLAUDEMESH_BROKER_URL: URLS.BROKER,
3023
- CLAUDEMESH_CONFIG_DIR: process.env.CLAUDEMESH_CONFIG_DIR || undefined,
3024
- CLAUDEMESH_DEBUG: process.env.CLAUDEMESH_DEBUG === "1" || process.env.CLAUDEMESH_DEBUG === "true"
3025
- };
3026
- });
3027
-
3028
- // src/services/logger/logger.ts
3029
- function timestamp() {
3030
- return new Date().toISOString();
3031
- }
3032
- function log(msg, ...args) {
3033
- if (!isQuiet)
3034
- console.log(msg, ...args);
3035
- }
3036
- function debug(msg, ...args) {
3037
- if (isDebug)
3038
- console.error(`[${timestamp()}] DEBUG ${msg}`, ...args);
3039
- }
3040
- function warn(msg, ...args) {
3041
- console.error(`⚠ ${msg}`, ...args);
3042
- }
3043
- var isDebug, isQuiet;
3044
- var init_logger = __esm(() => {
3045
- isDebug = process.env.CLAUDEMESH_DEBUG === "1" || process.env.CLAUDEMESH_DEBUG === "true";
3046
- isQuiet = process.argv.includes("-q") || process.argv.includes("--quiet");
3047
- });
3048
-
3049
- // src/services/logger/facade.ts
3050
- var init_facade4 = __esm(() => {
3051
- init_logger();
3052
- });
3053
-
3054
- // src/services/api/errors.ts
3055
- var ApiError, NetworkError;
3056
- var init_errors2 = __esm(() => {
3057
- ApiError = class ApiError extends Error {
3058
- status;
3059
- statusText;
3060
- body;
3061
- constructor(status, statusText, body) {
3062
- super(`API error ${status}: ${statusText}`);
3063
- this.status = status;
3064
- this.statusText = statusText;
3065
- this.body = body;
3066
- this.name = "ApiError";
3067
- }
3068
- get isUnauthorized() {
3069
- return this.status === 401;
3070
- }
3071
- get isNotFound() {
3072
- return this.status === 404;
3073
- }
3074
- get isConflict() {
3075
- return this.status === 409;
3076
- }
3077
- get isRateLimited() {
3078
- return this.status === 429;
3079
- }
3080
- };
3081
- NetworkError = class NetworkError extends Error {
3082
- url;
3083
- constructor(url, cause) {
3084
- super(`Network error reaching ${url}`);
3085
- this.url = url;
3086
- this.name = "NetworkError";
3087
- this.cause = cause;
3088
- }
3089
- };
3090
- });
3091
-
3092
- // src/services/api/client.ts
3093
- async function request(opts) {
3094
- const base = opts.baseUrl ?? URLS.API_BASE;
3095
- const url = `${base}${opts.path}`;
3096
- const method = opts.method ?? "GET";
3097
- const controller = new AbortController;
3098
- const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? TIMINGS.API_TIMEOUT_MS);
3099
- const headers = {
3100
- "Content-Type": "application/json",
3101
- Accept: "application/json",
3102
- "User-Agent": "claudemesh-cli/1.0"
3103
- };
3104
- if (opts.token)
3105
- headers.Authorization = `Bearer ${opts.token}`;
3106
- debug(`${method} ${url}`);
3107
- try {
3108
- const res = await fetch(url, {
3109
- method,
3110
- headers,
3111
- body: opts.body ? JSON.stringify(opts.body) : undefined,
3112
- signal: controller.signal
3113
- });
3114
- if (!res.ok) {
3115
- const text2 = await res.text();
3116
- let body = text2;
3117
- try {
3118
- body = JSON.parse(text2);
3119
- } catch {}
3120
- throw new ApiError(res.status, res.statusText, body);
3121
- }
3122
- const text = await res.text();
3123
- if (!text)
3124
- return;
3125
- return JSON.parse(text);
3126
- } catch (err) {
3127
- if (err instanceof ApiError)
3128
- throw err;
3129
- throw new NetworkError(url, err);
3130
- } finally {
3131
- clearTimeout(timeout);
3132
- }
3133
- }
3134
- async function get(path, token) {
3135
- return request({ path, token });
3136
- }
3137
- async function post(path, body, token) {
3138
- return request({ path, method: "POST", body, token });
3139
- }
3140
- var init_client = __esm(() => {
3141
- init_urls();
3142
- init_timings();
3143
- init_facade4();
3144
- init_errors2();
3145
- });
3146
-
3147
- // src/services/api/my.ts
3148
- var exports_my = {};
3149
- __export(exports_my, {
3150
- revokeSession: () => revokeSession,
3151
- renameMesh: () => renameMesh,
3152
- getProfile: () => getProfile,
3153
- getMeshes: () => getMeshes,
3154
- createMesh: () => createMesh,
3155
- createInvite: () => createInvite,
3156
- cliSync: () => cliSync
3157
- });
3158
- async function getProfile(token) {
3159
- return get("/api/my/profile", token);
3160
- }
3161
- async function getMeshes(token) {
3162
- return get("/api/my/meshes", token);
3163
- }
3164
- async function createMesh(token, body) {
3165
- return post("/api/my/meshes", body, token);
3166
- }
3167
- async function renameMesh(token, oldSlug, newSlug) {
3168
- return request({
3169
- path: `/api/cli/meshes/${oldSlug}`,
3170
- method: "PATCH",
3171
- body: { slug: newSlug },
3172
- token
3173
- });
3174
- }
3175
- async function createInvite(token, meshSlug, body) {
3176
- return post(`/api/my/meshes/${meshSlug}/invites`, body, token);
3177
- }
3178
- async function revokeSession(token) {
3179
- const BROKER_HTTP = (await Promise.resolve().then(() => (init_urls(), exports_urls))).URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
3180
- return request({
3181
- path: "/cli/session/revoke",
3182
- method: "POST",
3183
- body: { token },
3184
- baseUrl: BROKER_HTTP
3185
- });
3186
- }
3187
- async function cliSync(token) {
3188
- return post("/cli-sync", undefined, token);
3189
- }
3190
- var init_my = __esm(() => {
3191
- init_client();
3192
- });
3193
-
3194
- // src/services/api/public.ts
3195
- var exports_public = {};
3196
- __export(exports_public, {
3197
- requestDeviceCode: () => requestDeviceCode,
3198
- pollDeviceCode: () => pollDeviceCode,
3199
- claimInvite: () => claimInvite
3200
- });
3201
- async function claimInvite(code, body) {
3202
- return post(`/api/public/invites/${code}/claim`, body);
3203
- }
3204
- async function requestDeviceCode(deviceInfo) {
3205
- return request({
3206
- path: "/cli/device-code",
3207
- method: "POST",
3208
- body: deviceInfo,
3209
- baseUrl: BROKER_HTTP
3210
- });
3211
- }
3212
- async function pollDeviceCode(deviceCode) {
3213
- return request({
3214
- path: `/cli/device-code/${deviceCode}`,
3215
- baseUrl: BROKER_HTTP
3216
- });
3217
- }
3218
- var BROKER_HTTP;
3219
- var init_public = __esm(() => {
3220
- init_client();
3221
- init_urls();
3222
- BROKER_HTTP = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
3223
- });
3224
-
3225
- // src/services/api/facade.ts
3226
- var init_facade5 = __esm(() => {
3227
- init_client();
3228
- init_errors2();
3229
- init_my();
3230
- init_public();
3231
- });
3232
-
3233
- // src/services/device/info.ts
3234
- import { hostname as hostname2, platform as platform2, arch, release } from "node:os";
3235
- function getDeviceInfo() {
3236
- return {
3237
- hostname: hostname2(),
3238
- platform: platform2(),
3239
- arch: arch(),
3240
- osRelease: release(),
3241
- nodeVersion: process.version
3242
- };
3243
- }
3244
- var init_info = () => {};
3245
-
3246
- // src/services/device/facade.ts
3247
- var init_facade6 = __esm(() => {
3248
- init_info();
3249
- });
3250
-
3251
- // src/services/spawn/claude.ts
3252
- import { spawnSync } from "node:child_process";
3253
- import { existsSync as existsSync3 } from "node:fs";
3254
- function findClaudeBinary() {
3255
- const candidates = [
3256
- process.env.CLAUDE_BIN,
3257
- "/usr/local/bin/claude",
3258
- `${process.env.HOME}/.local/bin/claude`,
3259
- `${process.env.HOME}/.npm/bin/claude`
3260
- ].filter(Boolean);
3261
- for (const bin of candidates) {
3262
- if (existsSync3(bin))
3263
- return bin;
3264
- }
3265
- const which = spawnSync("which", ["claude"], { encoding: "utf-8" });
3266
- if (which.status === 0 && which.stdout.trim())
3267
- return which.stdout.trim();
3268
- return null;
3269
- }
3270
- function spawnClaude(opts) {
3271
- const bin = findClaudeBinary();
3272
- if (!bin)
3273
- throw new Error("Claude binary not found. Install with: npm i -g @anthropic-ai/claude-code");
3274
- return spawnSync(bin, opts.args, {
3275
- stdio: "inherit",
3276
- env: { ...process.env, ...opts.env },
3277
- cwd: opts.cwd
3278
- });
3279
- }
3280
- var init_claude = () => {};
3281
-
3282
- // src/services/spawn/browser.ts
3283
- import { execFile } from "node:child_process";
3284
- import { platform as platform3 } from "node:os";
3285
- function openBrowser(url) {
3286
- return new Promise((resolve, reject) => {
3287
- const os = platform3();
3288
- let bin;
3289
- let args;
3290
- if (os === "darwin") {
3291
- bin = "open";
3292
- args = [url];
3293
- } else if (os === "win32") {
3294
- bin = "cmd";
3295
- args = ["/c", "start", "", url];
3296
- } else {
3297
- bin = "xdg-open";
3298
- args = [url];
3299
- }
3300
- execFile(bin, args, (err) => {
3301
- if (err)
3302
- reject(err);
3303
- else
3304
- resolve();
3305
- });
3306
- });
3307
- }
3308
- var init_browser = () => {};
3309
-
3310
- // src/services/spawn/facade.ts
3311
- var exports_facade3 = {};
3312
- __export(exports_facade3, {
3313
- spawnClaude: () => spawnClaude,
3314
- openBrowser: () => openBrowser,
3315
- findClaudeBinary: () => findClaudeBinary
3316
- });
3317
- var init_facade7 = __esm(() => {
3318
- init_claude();
3319
- init_browser();
3320
- });
3321
-
3322
- // src/services/auth/token-store.ts
3323
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, existsSync as existsSync4, openSync as openSync2, closeSync as closeSync2 } from "node:fs";
3324
- function getStoredToken() {
3325
- if (!existsSync4(PATHS.AUTH_FILE))
3326
- return null;
3327
- try {
3328
- const raw = readFileSync2(PATHS.AUTH_FILE, "utf-8");
3329
- return JSON.parse(raw);
3330
- } catch {
3331
- return null;
3332
- }
3333
- }
3334
- function storeToken(auth) {
3335
- ensureConfigDir();
3336
- const data = { ...auth, stored_at: new Date().toISOString() };
3337
- const content = JSON.stringify(data, null, 2) + `
3338
- `;
3339
- const fd = openSync2(PATHS.AUTH_FILE, "w", 384);
3340
- try {
3341
- writeFileSync2(fd, content, "utf-8");
3342
- } finally {
3343
- closeSync2(fd);
3344
- }
3345
- }
3346
- function clearToken() {
3347
- try {
3348
- unlinkSync2(PATHS.AUTH_FILE);
3349
- } catch {}
3350
- }
3351
- var init_token_store = __esm(() => {
3352
- init_paths();
3353
- init_facade();
3354
- });
3355
-
3356
- // src/services/auth/errors.ts
3357
- var AuthError, DeviceCodeExpired, NotSignedIn;
3358
- var init_errors3 = __esm(() => {
3359
- AuthError = class AuthError extends Error {
3360
- constructor(message) {
3361
- super(message);
3362
- this.name = "AuthError";
3363
- }
3364
- };
3365
- DeviceCodeExpired = class DeviceCodeExpired extends AuthError {
3366
- constructor() {
3367
- super("Device code expired. Run `claudemesh login` again.");
3368
- }
3369
- };
3370
- NotSignedIn = class NotSignedIn extends AuthError {
3371
- constructor() {
3372
- super("Not signed in. Run `claudemesh login` first.");
3373
- }
3374
- };
3375
- });
3376
-
3377
- // src/services/auth/device-code.ts
3378
- import { createInterface as createInterface2 } from "node:readline";
3379
- function parseJwtUser(token) {
3380
- try {
3381
- const parts = token.split(".");
3382
- if (parts[1]) {
3383
- const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
3384
- if (payload.exp && payload.exp < Date.now() / 1000)
3385
- throw new Error("expired");
3386
- return {
3387
- id: payload.sub ?? "",
3388
- display_name: payload.name ?? payload.email ?? "",
3389
- email: payload.email ?? ""
3390
- };
3391
- }
3392
- } catch {}
3393
- throw new Error("Invalid token");
3394
- }
3395
- async function loginWithDeviceCode() {
3396
- const device = getDeviceInfo();
3397
- const { device_code, user_code, session_id, verification_url, token_url } = await exports_public.requestDeviceCode({
3398
- hostname: device.hostname,
3399
- platform: device.platform,
3400
- arch: device.arch
3401
- });
3402
- const browserUrl = `${verification_url}?session=${session_id}`;
3403
- const isTTY2 = process.stdout.isTTY && !process.env.NO_COLOR;
3404
- const orange2 = (s) => isTTY2 ? `\x1B[38;5;208m${s}\x1B[0m` : s;
3405
- const bold2 = (s) => isTTY2 ? `\x1B[1m${s}\x1B[0m` : s;
3406
- const dim2 = (s) => isTTY2 ? `\x1B[2m${s}\x1B[0m` : s;
3407
- log("");
3408
- log(" " + orange2("claudemesh") + " — sign in to connect your terminal");
3409
- log("");
3410
- log(" ┌──────────────────────────────────┐");
3411
- log(" │ │");
3412
- log(" │ Your code: " + bold2(user_code) + " │");
3413
- log(" │ │");
3414
- log(" └──────────────────────────────────┘");
3415
- log("");
3416
- log(" " + dim2("Confirm this code matches your browser."));
3417
- log("");
3418
- log(" " + dim2("If the browser didn't open, visit:"));
3419
- log(" " + browserUrl);
3420
- log("");
3421
- log(" " + dim2("Can't use a browser? Generate a token at:"));
3422
- log(" " + (token_url || verification_url.replace("/cli-auth", "/token")));
3423
- log(" " + dim2("Then paste it below."));
3424
- log("");
3425
- log(" Waiting… " + dim2("(paste token or Ctrl-C to cancel)"));
3426
- try {
3427
- await openBrowser(browserUrl);
3428
- } catch {
3429
- warn(" Could not open browser automatically.");
3430
- }
3431
- return new Promise((resolve, reject) => {
3432
- let done = false;
3433
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
3434
- rl.on("line", (line) => {
3435
- if (done)
3436
- return;
3437
- const trimmed = line.trim();
3438
- if (trimmed.split(".").length === 3 && trimmed.length > 50) {
3439
- done = true;
3440
- rl.close();
3441
- try {
3442
- const user = parseJwtUser(trimmed);
3443
- storeToken({ session_token: trimmed, user, token_source: "manual" });
3444
- resolve({ user, session_token: trimmed });
3445
- } catch (e) {
3446
- reject(new Error("Invalid or expired token. Generate a new one."));
3447
- }
3448
- }
3449
- });
3450
- const startTime = Date.now();
3451
- const poll = async () => {
3452
- while (!done && Date.now() - startTime < TIMINGS.DEVICE_CODE_TIMEOUT_MS) {
3453
- await new Promise((r) => setTimeout(r, TIMINGS.DEVICE_CODE_POLL_MS));
3454
- if (done)
3455
- return;
3456
- try {
3457
- const result = await exports_public.pollDeviceCode(device_code);
3458
- if (result.status === "approved" && result.session_token && result.user) {
3459
- if (done)
3460
- return;
3461
- done = true;
3462
- rl.close();
3463
- storeToken({ session_token: result.session_token, user: result.user, token_source: "device-code" });
3464
- resolve({ user: result.user, session_token: result.session_token });
3465
- return;
3466
- }
3467
- if (result.status === "expired") {
3468
- if (done)
3469
- return;
3470
- done = true;
3471
- rl.close();
3472
- reject(new DeviceCodeExpired);
3473
- return;
3474
- }
3475
- } catch {}
3476
- }
3477
- if (!done) {
3478
- done = true;
3479
- rl.close();
3480
- reject(new DeviceCodeExpired);
3481
- }
3482
- };
3483
- poll();
3484
- });
3485
- }
3486
- var init_device_code = __esm(() => {
3487
- init_timings();
3488
- init_facade5();
3489
- init_facade6();
3490
- init_facade7();
3491
- init_facade4();
3492
- init_token_store();
3493
- init_errors3();
3494
- });
3495
-
3496
- // src/services/auth/client.ts
3497
- function requireToken() {
3498
- const auth = getStoredToken();
3499
- if (!auth)
3500
- throw new NotSignedIn;
3501
- return auth.session_token;
3502
- }
3503
- function localView() {
3504
- const cfg = readConfig();
3505
- if (cfg.meshes.length === 0)
3506
- return;
3507
- return {
3508
- config_path: PATHS.CONFIG_FILE,
3509
- meshes: cfg.meshes.map((m) => ({
3510
- slug: m.slug,
3511
- mesh_id: m.meshId,
3512
- member_id: m.memberId,
3513
- pubkey_prefix: m.pubkey.slice(0, 12)
3514
- }))
3515
- };
3516
- }
3517
- async function whoAmI() {
3518
- const auth = getStoredToken();
3519
- const local = localView();
3520
- if (!auth)
3521
- return { signed_in: false, local };
3522
- try {
3523
- const profile = await exports_my.getProfile(auth.session_token);
3524
- const meshes = await exports_my.getMeshes(auth.session_token);
3525
- const owned = meshes.filter((m) => m.role === "owner").length;
3526
- return {
3527
- signed_in: true,
3528
- user: profile,
3529
- token_source: auth.token_source,
3530
- meshes: { owned, guest: meshes.length - owned },
3531
- local
3532
- };
3533
- } catch (err) {
3534
- if (err instanceof ApiError && err.isUnauthorized) {
3535
- clearToken();
3536
- return { signed_in: false, local };
3537
- }
3538
- throw err;
3539
- }
3540
- }
3541
- async function logout() {
3542
- const token = requireToken();
3543
- let revoked = false;
3544
- try {
3545
- await exports_my.revokeSession(token);
3546
- revoked = true;
3547
- } catch {}
3548
- clearToken();
3549
- return { revoked };
3550
- }
3551
- async function register(callbackPort) {
3552
- const { openBrowser: openBrowser2 } = await Promise.resolve().then(() => (init_facade7(), exports_facade3));
3553
- const url = `https://claudemesh.com/register?source=cli&callback=http://localhost:${callbackPort}`;
3554
- await openBrowser2(url);
3555
- }
3556
- var init_client2 = __esm(() => {
3557
- init_facade5();
3558
- init_facade5();
3559
- init_facade();
3560
- init_paths();
3561
- init_token_store();
3562
- init_errors3();
3563
- });
3564
-
3565
- // src/services/auth/dashboard-sync.ts
3566
- async function syncWithBroker(syncToken, peerPubkey, displayName, brokerBaseUrl) {
3567
- const base = brokerBaseUrl ?? deriveHttpUrl(URLS.BROKER);
3568
- const res = await fetch(`${base}/cli-sync`, {
3569
- method: "POST",
3570
- headers: { "Content-Type": "application/json" },
3571
- body: JSON.stringify({
3572
- sync_token: syncToken,
3573
- peer_pubkey: peerPubkey,
3574
- display_name: displayName
3575
- })
3576
- });
3577
- if (!res.ok) {
3578
- const body2 = await res.text();
3579
- let msg;
3580
- try {
3581
- msg = JSON.parse(body2).error ?? body2;
3582
- } catch {
3583
- msg = body2;
3584
- }
3585
- throw new Error(`Broker sync failed (${res.status}): ${msg}`);
3586
- }
3587
- const body = await res.json();
3588
- if (!body.ok)
3589
- throw new Error(`Broker sync failed: ${body.error ?? "unknown error"}`);
3590
- return { account_id: body.account_id, meshes: body.meshes };
3591
- }
3592
- function deriveHttpUrl(wssUrl) {
3593
- const url = new URL(wssUrl);
3594
- url.protocol = url.protocol === "wss:" ? "https:" : "http:";
3595
- url.pathname = url.pathname.replace(/\/ws\/?$/, "");
3596
- return url.toString().replace(/\/$/, "");
3597
- }
3598
- var init_dashboard_sync = __esm(() => {
3599
- init_urls();
3600
- });
3601
-
3602
- // src/services/auth/callback-listener.ts
3603
- import { createServer as createServer2 } from "node:http";
3604
- function startCallbackListener() {
3605
- return new Promise((resolveStart) => {
3606
- let resolveToken;
3607
- let resolved = false;
3608
- const tokenPromise = new Promise((r) => {
3609
- resolveToken = r;
3610
- });
3611
- const server = createServer2((req, res) => {
3612
- const url = new URL(req.url, "http://localhost");
3613
- if (req.method === "OPTIONS") {
3614
- res.writeHead(204, {
3615
- "Access-Control-Allow-Origin": "https://claudemesh.com",
3616
- "Access-Control-Allow-Methods": "GET",
3617
- "Access-Control-Allow-Headers": "Content-Type"
3618
- });
3619
- res.end();
3620
- return;
3621
- }
3622
- if (url.pathname === "/ping") {
3623
- res.writeHead(200, {
3624
- "Content-Type": "text/plain",
3625
- "Access-Control-Allow-Origin": "https://claudemesh.com"
3626
- });
3627
- res.end("ok");
3628
- return;
3629
- }
3630
- if (url.pathname === "/callback") {
3631
- const token = url.searchParams.get("token");
3632
- if (token && !resolved) {
3633
- resolved = true;
3634
- res.writeHead(200, {
3635
- "Content-Type": "text/html",
3636
- "Access-Control-Allow-Origin": "https://claudemesh.com"
3637
- });
3638
- res.end("<html><body><h2>Done! You can close this tab.</h2></body></html>");
3639
- resolveToken(token);
3640
- setTimeout(() => server.close(), 500);
3641
- } else {
3642
- res.writeHead(400, { "Content-Type": "text/plain" });
3643
- res.end("Missing token");
3644
- }
3645
- return;
3646
- }
3647
- res.writeHead(404);
3648
- res.end();
3649
- });
3650
- server.listen(0, "127.0.0.1", () => {
3651
- const addr = server.address();
3652
- resolveStart({
3653
- port: addr.port,
3654
- token: tokenPromise,
3655
- close: () => server.close()
3656
- });
3657
- });
3658
- });
3659
- }
3660
- var init_callback_listener = () => {};
3661
-
3662
- // src/services/auth/facade.ts
3663
- var exports_facade4 = {};
3664
- __export(exports_facade4, {
3665
- whoAmI: () => whoAmI,
3666
- syncWithBroker: () => syncWithBroker,
3667
- storeToken: () => storeToken,
3668
- startCallbackListener: () => startCallbackListener,
3669
- register: () => register,
3670
- logout: () => logout,
3671
- loginWithDeviceCode: () => loginWithDeviceCode,
3672
- getStoredToken: () => getStoredToken,
3673
- generatePairingCode: () => generatePairingCode,
3674
- clearToken: () => clearToken,
3675
- NotSignedIn: () => NotSignedIn,
3676
- DeviceCodeExpired: () => DeviceCodeExpired,
3677
- AuthError: () => AuthError
3678
- });
3679
- import { randomBytes as randomBytes3 } from "node:crypto";
3680
- function generatePairingCode() {
3681
- const bytes = randomBytes3(4);
3682
- return Array.from(bytes, (b) => CHARS[b % CHARS.length]).join("");
3683
- }
3684
- var CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
3685
- var init_facade8 = __esm(() => {
3686
- init_device_code();
3687
- init_client2();
3688
- init_dashboard_sync();
3689
- init_token_store();
3690
- init_callback_listener();
3691
- init_errors3();
3692
- });
3693
-
3694
- // src/commands/grants.ts
3695
- var exports_grants = {};
3696
- __export(exports_grants, {
3697
- runRevoke: () => runRevoke,
3698
- runGrants: () => runGrants,
3699
- runGrant: () => runGrant,
3700
- runBlock: () => runBlock,
3701
- isAllowed: () => isAllowed
3702
- });
3703
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
3704
- import { homedir as homedir3 } from "node:os";
3705
- import { join as join3 } from "node:path";
3706
- async function syncToBroker(meshSlug, grants) {
3707
- const auth = getStoredToken();
3708
- if (!auth)
3709
- return;
3710
- try {
3711
- await request({
3712
- path: `/cli/mesh/${meshSlug}/grants`,
3713
- method: "POST",
3714
- body: { grants },
3715
- baseUrl: BROKER_HTTP2,
3716
- token: auth.session_token
3717
- });
3718
- } catch (e) {
3719
- render.warn(`broker grant sync failed — client filter still active: ${e instanceof Error ? e.message : e}`);
3720
- }
3721
- }
3722
- function readGrants() {
3723
- if (!existsSync5(GRANT_FILE))
3724
- return {};
3725
- try {
3726
- return JSON.parse(readFileSync3(GRANT_FILE, "utf-8"));
3727
- } catch {
3728
- return {};
3729
- }
3730
- }
3731
- function writeGrants(g) {
3732
- const dir = join3(homedir3(), ".claudemesh");
3733
- if (!existsSync5(dir))
3734
- mkdirSync3(dir, { recursive: true });
3735
- writeFileSync3(GRANT_FILE, JSON.stringify(g, null, 2), { mode: 384 });
3736
- }
3737
- function resolveCaps(input) {
3738
- if (input.includes("all"))
3739
- return [...ALL_CAPS];
3740
- return input.filter((c) => ALL_CAPS.includes(c));
3741
- }
3742
- async function resolvePeer(meshSlug, name) {
3743
- return await withMesh({ meshSlug }, async (client) => {
3744
- const peers = await client.listPeers();
3745
- const match = peers.find((p) => p.displayName === name || p.pubkey === name || p.pubkey.startsWith(name) || p.memberPubkey === name || p.memberPubkey && p.memberPubkey.startsWith(name));
3746
- if (!match)
3747
- return null;
3748
- const key = match.memberPubkey ?? match.pubkey;
3749
- return { displayName: match.displayName, pubkey: key };
3750
- });
3751
- }
3752
- function pickMesh2(slug) {
3753
- const cfg = readConfig();
3754
- if (slug)
3755
- return cfg.meshes.find((m) => m.slug === slug) ? slug : null;
3756
- return cfg.meshes[0]?.slug ?? null;
3757
- }
3758
- async function runGrant(peer, caps, opts = {}) {
3759
- if (!peer || caps.length === 0) {
3760
- render.err("Usage: claudemesh grant <peer> <capability...>");
3761
- render.hint(`Capabilities: ${ALL_CAPS.join(", ")}, all`);
3762
- return EXIT.INVALID_ARGS;
3763
- }
3764
- const mesh = pickMesh2(opts.mesh);
3765
- if (!mesh) {
3766
- render.err("No matching mesh — join one first.");
3767
- return EXIT.NOT_FOUND;
3768
- }
3769
- const resolved = await resolvePeer(mesh, peer);
3770
- if (!resolved) {
3771
- render.err(`Peer "${peer}" not found on ${mesh}.`);
3772
- return EXIT.NOT_FOUND;
3773
- }
3774
- const wanted = resolveCaps(caps);
3775
- if (wanted.length === 0) {
3776
- render.err(`Unknown capabilities: ${caps.join(", ")}`);
3777
- return EXIT.INVALID_ARGS;
3778
- }
3779
- const store = readGrants();
3780
- const meshGrants = store[mesh] ?? {};
3781
- const existing = meshGrants[resolved.pubkey] ?? DEFAULT_CAPS.slice();
3782
- const merged = Array.from(new Set([...existing, ...wanted]));
3783
- meshGrants[resolved.pubkey] = merged;
3784
- store[mesh] = meshGrants;
3785
- writeGrants(store);
3786
- await syncToBroker(mesh, { [resolved.pubkey]: merged });
3787
- render.ok(`Granted ${wanted.join(", ")} to ${resolved.displayName} on ${mesh}.`);
3788
- render.kv([["now", merged.join(", ")]]);
3789
- return EXIT.SUCCESS;
3790
- }
3791
- async function runRevoke(peer, caps, opts = {}) {
3792
- if (!peer || caps.length === 0) {
3793
- render.err("Usage: claudemesh revoke <peer> <capability...>");
3794
- return EXIT.INVALID_ARGS;
3795
- }
3796
- const mesh = pickMesh2(opts.mesh);
3797
- if (!mesh) {
3798
- render.err("No matching mesh.");
3799
- return EXIT.NOT_FOUND;
3800
- }
3801
- const resolved = await resolvePeer(mesh, peer);
3802
- if (!resolved) {
3803
- render.err(`Peer "${peer}" not found on ${mesh}.`);
3804
- return EXIT.NOT_FOUND;
3805
- }
3806
- const wanted = caps.includes("all") ? ALL_CAPS.slice() : resolveCaps(caps);
3807
- const store = readGrants();
3808
- const meshGrants = store[mesh] ?? {};
3809
- const existing = meshGrants[resolved.pubkey] ?? DEFAULT_CAPS.slice();
3810
- const after = existing.filter((c) => !wanted.includes(c));
3811
- meshGrants[resolved.pubkey] = after;
3812
- store[mesh] = meshGrants;
3813
- writeGrants(store);
3814
- await syncToBroker(mesh, { [resolved.pubkey]: after });
3815
- render.ok(`Revoked ${wanted.join(", ")} from ${resolved.displayName} on ${mesh}.`);
3816
- render.kv([["now", after.length ? after.join(", ") : "(none)"]]);
3817
- return EXIT.SUCCESS;
3818
- }
3819
- async function runBlock(peer, opts = {}) {
3820
- if (!peer) {
3821
- render.err("Usage: claudemesh block <peer>");
3822
- return EXIT.INVALID_ARGS;
3823
- }
3824
- const mesh = pickMesh2(opts.mesh);
3825
- if (!mesh) {
3826
- render.err("No matching mesh.");
3827
- return EXIT.NOT_FOUND;
3828
- }
3829
- const resolved = await resolvePeer(mesh, peer);
3830
- if (!resolved) {
3831
- render.err(`Peer "${peer}" not found on ${mesh}.`);
3832
- return EXIT.NOT_FOUND;
3833
- }
3834
- const store = readGrants();
3835
- const meshGrants = store[mesh] ?? {};
3836
- meshGrants[resolved.pubkey] = [];
3837
- store[mesh] = meshGrants;
3838
- writeGrants(store);
3839
- await syncToBroker(mesh, { [resolved.pubkey]: [] });
3840
- render.ok(`Blocked ${resolved.displayName} on ${mesh} (all capabilities revoked).`);
3841
- render.hint(`Undo with: claudemesh grant ${resolved.displayName} all --mesh ${mesh}`);
3842
- return EXIT.SUCCESS;
3843
- }
3844
- async function runGrants(opts = {}) {
3845
- const mesh = pickMesh2(opts.mesh);
3846
- if (!mesh) {
3847
- render.err("No matching mesh.");
3848
- return EXIT.NOT_FOUND;
3849
- }
3850
- const store = readGrants();
3851
- const meshGrants = store[mesh] ?? {};
3852
- if (opts.json) {
3853
- console.log(JSON.stringify({ schema_version: "1.0", mesh, grants: meshGrants }, null, 2));
3854
- return EXIT.SUCCESS;
3855
- }
3856
- render.section(`grants on ${mesh}`);
3857
- const peerPubkeys = Object.keys(meshGrants);
3858
- if (peerPubkeys.length === 0) {
3859
- render.info("(no overrides — all peers use default caps: " + DEFAULT_CAPS.join(", ") + ")");
3860
- return EXIT.SUCCESS;
3861
- }
3862
- await withMesh({ meshSlug: mesh }, async (client) => {
3863
- const peers = await client.listPeers();
3864
- const byPk = new Map(peers.map((p) => [p.pubkey, p.displayName]));
3865
- for (const [pk, caps] of Object.entries(meshGrants)) {
3866
- const name = byPk.get(pk) ?? `${pk.slice(0, 10)}…`;
3867
- render.kv([[name, caps.length ? caps.join(", ") : "(blocked)"]]);
3868
- }
3869
- });
3870
- return EXIT.SUCCESS;
3871
- }
3872
- function isAllowed(meshSlug, peerPubkey, cap) {
3873
- const store = readGrants();
3874
- const entry = store[meshSlug]?.[peerPubkey];
3875
- if (entry === undefined)
3876
- return DEFAULT_CAPS.includes(cap);
3877
- return entry.includes(cap);
3878
- }
3879
- var BROKER_HTTP2, ALL_CAPS, DEFAULT_CAPS, GRANT_FILE;
3880
- var init_grants = __esm(() => {
3881
- init_facade();
3882
- init_connect();
3883
- init_render();
3884
- init_exit_codes();
3885
- init_facade8();
3886
- init_facade5();
3887
- init_urls();
3888
- BROKER_HTTP2 = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
3889
- ALL_CAPS = ["read", "dm", "broadcast", "state-read", "state-write", "file-read"];
3890
- DEFAULT_CAPS = ["read", "dm", "broadcast", "state-read"];
3891
- GRANT_FILE = join3(homedir3(), ".claudemesh", "grants.json");
3892
- });
3893
-
3894
- // src/mcp/server.ts
3895
- import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3896
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3897
- import {
3898
- ListToolsRequestSchema,
3899
- CallToolRequestSchema,
3900
- ListPromptsRequestSchema,
3901
- GetPromptRequestSchema,
3902
- ListResourcesRequestSchema,
3903
- ReadResourceRequestSchema
3904
- } from "@modelcontextprotocol/sdk/types.js";
3905
- function relativeTime(isoStr) {
3906
- const then = new Date(isoStr).getTime();
3907
- if (isNaN(then))
3908
- return "unknown";
3909
- const diffMs = Date.now() - then;
3910
- if (diffMs < 0)
3911
- return "just now";
3912
- const seconds = Math.floor(diffMs / 1000);
3913
- if (seconds < 60)
3914
- return `${seconds}s ago`;
3915
- const minutes = Math.floor(seconds / 60);
3916
- if (minutes < 60)
3917
- return `${minutes}m ago`;
3918
- const hours = Math.floor(minutes / 60);
3919
- if (hours < 24)
3920
- return `${hours}h ago`;
3921
- const days = Math.floor(hours / 24);
3922
- return `${days} day${days !== 1 ? "s" : ""} ago`;
3923
- }
3924
- async function resolvePeerName(client, pubkey) {
3925
- const now = Date.now();
3926
- if (now - peerNameCacheAge > CACHE_TTL_MS) {
3927
- peerNameCache.clear();
3928
- try {
3929
- const peers = await client.listPeers();
3930
- for (const p of peers)
3931
- peerNameCache.set(p.pubkey, p.displayName);
3932
- } catch {}
3933
- peerNameCacheAge = now;
3934
- }
3935
- return peerNameCache.get(pubkey) ?? `peer-${pubkey.slice(0, 8)}`;
3936
- }
3937
- function decryptFailedWarning(senderPubkey) {
3938
- const who = senderPubkey ? senderPubkey.slice(0, 12) + "…" : "unknown sender";
3939
- return `⚠ message from ${who} failed to decrypt (tampered or wrong keypair)`;
3940
- }
3941
- async function startMcpServer() {
3942
- const serviceIdx = process.argv.indexOf("--service");
3943
- if (serviceIdx !== -1 && process.argv[serviceIdx + 1]) {
3944
- return startServiceProxy(process.argv[serviceIdx + 1]);
3945
- }
3946
- const meshIdx = process.argv.indexOf("--mesh");
3947
- const onlyMesh = meshIdx !== -1 ? process.argv[meshIdx + 1] : null;
3948
- const config = readConfig();
3949
- if (onlyMesh) {
3950
- const available = config.meshes.map((m) => m.slug);
3951
- const filtered = config.meshes.filter((m) => m.slug === onlyMesh);
3952
- if (filtered.length === 0) {
3953
- process.stderr.write(`[claudemesh] --mesh "${onlyMesh}" not found in config. ` + `Joined meshes: ${available.join(", ") || "(none)"}
3954
- `);
3955
- process.exit(1);
3956
- }
3957
- config.meshes = filtered;
3958
- }
3959
- const myName = config.displayName ?? "unnamed";
3960
- const myRole = config.role ?? process.env.CLAUDEMESH_ROLE ?? null;
3961
- const myGroups = (config.groups ?? []).map((g) => `@${g.name}${g.role ? ":" + g.role : ""}`).join(", ") || "none";
3962
- const messageMode = config.messageMode ?? "push";
3963
- const server = new Server({ name: "claudemesh", version: "0.3.0" }, {
3964
- capabilities: {
3965
- experimental: { "claude/channel": {} },
3966
- tools: {},
3967
- prompts: {},
3968
- resources: {}
3969
- },
3970
- instructions: `## Identity
3971
- 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.
3972
-
3973
- ## Responding to messages
3974
- When you receive a <channel source="claudemesh" ...> message, RESPOND IMMEDIATELY. Pause your current task, reply via send_message (or \`claudemesh topic post --reply-to <message_id>\` for topic threads), then resume. Stay in character per your system prompt. Do not ignore low-priority messages — acknowledge them briefly even if you defer action.
3975
-
3976
- The channel attributes carry everything you need to reply — no extra lookups:
3977
- - \`from_name\` — sender display name. Use as the \`to\` arg when replying to a DM.
3978
- - \`from_pubkey\` / \`from_member_id\` — stable ids. Use \`from_member_id\` if the sender's display name might change.
3979
- - \`mesh_slug\` — pass via \`--mesh\` if your default mesh differs.
3980
- - \`priority\` — \`now\` / \`next\` / \`low\`.
3981
- - \`message_id\` — id of THIS message. To thread a reply onto it in a topic, run \`claudemesh topic post <topic> "<text>" --reply-to <message_id>\`.
3982
- - \`topic\` — set when the message arrived through a topic (vs DM). Reply in the same topic.
3983
- - \`reply_to_id\` — set when the incoming message is itself a reply. Render thread context if you re-narrate.
3984
-
3985
- If the channel meta contains \`subtype: reminder\`, this is a scheduled reminder you set for yourself — act on it immediately (no reply needed).
3986
-
3987
- ## Tools
3988
- | Tool | Description |
3989
- |------|-------------|
3990
- | send_message(to, message, priority?) | Send to peer name, @group, or * broadcast. \`to\` accepts display name, pubkey hex, @groupname, or *. |
3991
- | list_peers(mesh_slug?) | List connected peers with status, summary, groups, and roles. |
3992
- | check_messages() | Drain buffered inbound messages (auto-pushed in most cases, use as fallback). |
3993
- | set_summary(summary) | Set 1-2 sentence description of your current work, visible to all peers. |
3994
- | set_status(status) | Override status: idle, working, or dnd. |
3995
- | set_visible(visible) | Toggle visibility. Hidden peers skip list_peers and broadcasts; direct messages still arrive. |
3996
- | set_profile(avatar?, title?, bio?, capabilities?) | Set public profile: emoji avatar, short title, bio, capabilities list. |
3997
- | join_group(name, role?) | Join a @group with optional role (lead, member, observer, or any string). |
3998
- | leave_group(name) | Leave a @group. |
3999
- | set_state(key, value) | Write shared state; pushes change to all peers. |
4000
- | get_state(key) | Read a shared state value. |
4001
- | list_state() | List all state keys with values, authors, and timestamps. |
4002
- | remember(content, tags?) | Store persistent knowledge with optional tags. |
4003
- | recall(query) | Full-text search over mesh memory. |
4004
- | forget(id) | Soft-delete a memory entry. |
4005
- | claudemesh file share <path> [--to peer] [--tags a,b] | Share a file with the mesh, or DM it to a specific peer. Same-host fast path: when --to matches a peer on this machine, sends an absolute filepath instead of uploading (no MinIO round-trip). |
4006
- | claudemesh file get <id> [--out path] | Download a shared file by id. |
4007
- | claudemesh file list [query] | Find files shared in the mesh. |
4008
- | claudemesh file status <id> | Check who has accessed a file. |
4009
- | claudemesh file delete <id> | Remove a shared file from the mesh. |
4010
- | vector_store(collection, text, metadata?) | Store embedding in per-mesh Qdrant collection. |
4011
- | vector_search(collection, query, limit?) | Semantic search over stored embeddings. |
4012
- | vector_delete(collection, id) | Remove an embedding. |
4013
- | list_collections() | List vector collections in this mesh. |
4014
- | graph_query(cypher) | Read-only Cypher query on per-mesh Neo4j. |
4015
- | graph_execute(cypher) | Write Cypher query (CREATE, MERGE, DELETE). |
4016
- | mesh_query(sql) | Run a SELECT query on the per-mesh shared database. |
4017
- | mesh_execute(sql) | Run DDL/DML on the per-mesh database (CREATE TABLE, INSERT, UPDATE, DELETE). |
4018
- | mesh_schema() | List tables and columns in the per-mesh shared database. |
4019
- | create_stream(name) | Create a real-time data stream in the mesh. |
4020
- | publish(stream, data) | Push data to a stream. Subscribers receive it in real-time. |
4021
- | subscribe(stream) | Subscribe to a stream. Data pushes arrive as channel notifications. |
4022
- | list_streams() | List active streams in the mesh. |
4023
- | share_context(summary, files_read?, key_findings?, tags?) | Share session understanding with peers. |
4024
- | get_context(query) | Find context from peers who explored an area. |
4025
- | list_contexts() | See what all peers currently know. |
4026
- | create_task(title, assignee?, priority?, tags?) | Create a work item. |
4027
- | claim_task(id) | Claim an unclaimed task. |
4028
- | complete_task(id, result?) | Mark task done with optional result. |
4029
- | list_tasks(status?, assignee?) | List tasks filtered by status/assignee. |
4030
- | schedule_reminder(message, in_seconds?, deliver_at?, to?) | Schedule a reminder to yourself (no \`to\`) or a delayed message to a peer/group. Delivered as a push with \`subtype: reminder\` in the channel meta. |
4031
- | list_scheduled() | List pending scheduled reminders and messages. |
4032
- | cancel_scheduled(id) | Cancel a pending scheduled item. |
4033
- | read_peer_file(peer, path) | Read a file from another peer's project (max 1MB). |
4034
- | list_peer_files(peer, path?, pattern?) | List files in a peer's shared directory. |
4035
- | mesh_mcp_register(server_name, description, tools) | Register an MCP server with the mesh. Other peers can call its tools. |
4036
- | mesh_mcp_list() | List MCP servers available in the mesh with their tools. |
4037
- | mesh_tool_call(server_name, tool_name, args?) | Call a tool on a mesh-registered MCP server (30s timeout). |
4038
- | mesh_mcp_remove(server_name) | Unregister an MCP server you registered. |
4039
-
4040
- If multiple meshes are joined, prefix \`to\` with \`<mesh-slug>:\` to disambiguate (e.g. \`dev-team:Alice\`).
4041
-
4042
- Multi-target: send_message accepts an array of targets for the 'to' field.
4043
- send_message(to: ["Alice", "@backend"], message: "sprint starts")
4044
- Targets are deduplicated — each peer receives the message once.
4045
-
4046
- Targeted views: when different audiences need different details about the same event,
4047
- send tailored messages instead of one generic broadcast:
4048
- send_message(to: "@frontend", message: "Auth v2: useAuth hook changed, see src/auth/")
4049
- send_message(to: "@backend", message: "Auth v2: new /api/auth/v2 endpoints, v1 deprecated")
4050
- send_message(to: "@pm", message: "Auth v2 done. 3 points, no blockers.")
4051
-
4052
- ## Groups
4053
- Groups are routing labels. Send to @groupname to multicast to all members. Roles are metadata that peers interpret: a "lead" gathers input before synthesizing a response, a "member" contributes when asked, an "observer" watches silently. Join and leave groups dynamically with join_group/leave_group. Check list_peers to see who belongs to which groups and their roles.
4054
-
4055
- ## State
4056
- Shared key-value store scoped to the mesh. Use get_state/set_state for live coordination facts (deploy frozen? current sprint? PR queue). set_state pushes the change to all connected peers. Read state before asking peers questions — the answer may already be there. State is operational, not archival.
4057
-
4058
- ## Memory
4059
- 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.
4060
-
4061
- ## File access — decision guide
4062
- Three ways to access files. Pick the right one:
4063
-
4064
- 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.
4065
- 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.
4066
- 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.
4067
-
4068
- **Rule of thumb:** local peer → filesystem. Remote peer, small file → read_peer_file. Large file or needs to persist → share_file.
4069
-
4070
- ## Vectors
4071
- Store and search semantic embeddings. Use vector_store to index content, vector_search to find similar content.
4072
-
4073
- ## Graph
4074
- Build and query entity relationship graphs. Use graph_execute for writes (CREATE, MERGE), graph_query for reads (MATCH).
4075
-
4076
- ## Mesh Database
4077
- Per-mesh PostgreSQL database. Use mesh_execute for DDL/DML (CREATE TABLE, INSERT), mesh_query for SELECT, mesh_schema to inspect tables. Schema auto-created on first use.
4078
-
4079
- ## Streams
4080
- Real-time data channels. create_stream to start one, publish to push data, subscribe to receive pushes. Use for build logs, deploy status, live metrics.
4081
-
4082
- ## Context
4083
- Share your session understanding with peers. Use share_context after exploring a codebase area. Check get_context before re-reading files another peer already analyzed.
4084
-
4085
- ## Tasks
4086
- Create and claim work items. create_task to propose work, claim_task to take ownership, complete_task when done. Prevents duplicate effort.
4087
-
4088
- ## Priority
4089
- - "now": interrupt immediately, even if recipient is in DND (use for urgent: broken deploy, blocking issue)
4090
- - "next" (default): deliver when recipient goes idle (normal coordination)
4091
- - "low": pull-only via check_messages (FYI, non-blocking context)
4092
-
4093
- ## Coordination
4094
- Call list_peers at session start to understand who is online, their roles, and what they are working on. If you are a group lead, gather input from members before responding to external requests — do not answer alone. If you are a member, contribute to your lead when asked. Use @group messages for team-wide questions, direct messages for 1:1 coordination. Set a meaningful summary so peers know your current focus.
4095
-
4096
- ## Message Mode
4097
- Your message mode is "${messageMode}".
4098
- - push: messages arrive in real-time as channel notifications. Respond immediately.
4099
- - inbox: messages are held. You'll see "[inbox] New message from X" notifications. Call check_messages to read them.
4100
- - off: no message notifications. Use check_messages manually to poll.`
4101
- });
4102
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
4103
- tools: TOOLS
4104
- }));
4105
- server.setRequestHandler(ListPromptsRequestSchema, async () => {
4106
- const client = allClients()[0];
4107
- if (!client)
4108
- return { prompts: [] };
4109
- const skills = await client.listSkills();
4110
- return {
4111
- prompts: skills.map((s) => ({
4112
- name: s.name,
4113
- description: s.description,
4114
- arguments: []
4115
- }))
4116
- };
4117
- });
4118
- server.setRequestHandler(GetPromptRequestSchema, async (req) => {
4119
- const { name, arguments: promptArgs } = req.params;
4120
- const client = allClients()[0];
4121
- if (!client)
4122
- throw new Error("Not connected to any mesh");
4123
- const skill = await client.getSkill(name);
4124
- if (!skill)
4125
- throw new Error(`Skill "${name}" not found in the mesh`);
4126
- let content = skill.instructions;
4127
- const manifest = skill.manifest;
4128
- if (manifest && typeof manifest === "object") {
4129
- const fm = ["---"];
4130
- if (manifest.description)
4131
- fm.push(`description: "${manifest.description}"`);
4132
- if (manifest.when_to_use)
4133
- fm.push(`when_to_use: "${manifest.when_to_use}"`);
4134
- if (manifest.allowed_tools?.length)
4135
- fm.push(`allowed-tools:
4136
- ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
4137
- `)}`);
4138
- if (manifest.model)
4139
- fm.push(`model: ${manifest.model}`);
4140
- if (manifest.context)
4141
- fm.push(`context: ${manifest.context}`);
4142
- if (manifest.agent)
4143
- fm.push(`agent: ${manifest.agent}`);
4144
- if (manifest.user_invocable === false)
4145
- fm.push(`user-invocable: false`);
4146
- if (manifest.argument_hint)
4147
- fm.push(`argument-hint: "${manifest.argument_hint}"`);
4148
- fm.push(`---
4149
- `);
4150
- if (fm.length > 3)
4151
- content = fm.join(`
4152
- `) + content;
4153
- if (manifest.context === "fork") {
4154
- const agentType = manifest.agent || "general-purpose";
4155
- const modelHint = manifest.model ? `, model: "${manifest.model}"` : "";
4156
- const toolsHint = manifest.allowed_tools?.length ? `
4157
- Only use these tools: ${manifest.allowed_tools.join(", ")}.` : "";
4158
- content = `IMPORTANT: Execute this skill in an isolated sub-agent. Use the Agent tool with subagent_type="${agentType}"${modelHint}. Pass the full instructions below as the agent prompt.${toolsHint}
4159
-
4160
- ` + content;
4161
- }
4162
- }
4163
- return {
4164
- description: skill.description,
4165
- messages: [
4166
- {
4167
- role: "user",
4168
- content: { type: "text", text: content }
4169
- }
4170
- ]
4171
- };
4172
- });
4173
- server.setRequestHandler(ListResourcesRequestSchema, async () => {
4174
- const client = allClients()[0];
4175
- if (!client)
4176
- return { resources: [] };
4177
- const skills = await client.listSkills();
4178
- return {
4179
- resources: skills.map((s) => ({
4180
- uri: `skill://claudemesh/${encodeURIComponent(s.name)}`,
4181
- name: s.name,
4182
- description: s.description,
4183
- mimeType: "text/markdown"
4184
- }))
4185
- };
4186
- });
4187
- server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
4188
- const { uri } = req.params;
4189
- const match = uri.match(/^skill:\/\/claudemesh\/(.+)$/);
4190
- if (!match)
4191
- throw new Error(`Unknown resource URI: ${uri}`);
4192
- const name = decodeURIComponent(match[1]);
4193
- const client = allClients()[0];
4194
- if (!client)
4195
- throw new Error("Not connected to any mesh");
4196
- const skill = await client.getSkill(name);
4197
- if (!skill)
4198
- throw new Error(`Skill "${name}" not found`);
4199
- const manifest = skill.manifest;
4200
- const fmLines = ["---"];
4201
- fmLines.push(`name: ${skill.name}`);
4202
- fmLines.push(`description: "${skill.description}"`);
4203
- if (skill.tags.length)
4204
- fmLines.push(`tags: [${skill.tags.join(", ")}]`);
4205
- if (manifest && typeof manifest === "object") {
4206
- if (manifest.when_to_use)
4207
- fmLines.push(`when_to_use: "${manifest.when_to_use}"`);
4208
- if (manifest.allowed_tools?.length)
4209
- fmLines.push(`allowed-tools:
4210
- ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
4211
- `)}`);
4212
- if (manifest.model)
4213
- fmLines.push(`model: ${manifest.model}`);
4214
- if (manifest.context)
4215
- fmLines.push(`context: ${manifest.context}`);
4216
- if (manifest.agent)
4217
- fmLines.push(`agent: ${manifest.agent}`);
4218
- if (manifest.user_invocable === false)
4219
- fmLines.push(`user-invocable: false`);
4220
- if (manifest.argument_hint)
4221
- fmLines.push(`argument-hint: "${manifest.argument_hint}"`);
4222
- }
4223
- fmLines.push(`---
4224
- `);
4225
- const fullContent = fmLines.join(`
4226
- `) + skill.instructions;
4227
- return {
4228
- contents: [
4229
- {
4230
- uri,
4231
- mimeType: "text/markdown",
4232
- text: fullContent
4233
- }
4234
- ]
4235
- };
4236
- });
4237
- const transport = new StdioServerTransport;
4238
- await server.connect(transport);
4239
- const bridges = [];
4240
- startClients(config).then(() => {
4241
- wirePushHandlers().catch(() => {});
4242
- for (const client of allClients()) {
4243
- const bridge = startBridgeServer(client);
4244
- if (bridge)
4245
- bridges.push(bridge);
4246
- }
4247
- }).catch(() => {
4248
- wirePushHandlers().catch(() => {});
4249
- });
4250
- async function wirePushHandlers() {
4251
- for (const client of allClients()) {
4252
- client.onPush(async (msg) => {
4253
- if (messageMode === "off")
4254
- return;
4255
- if (msg.subtype === "system" && msg.event) {
4256
- const eventName = msg.event;
4257
- const data = msg.eventData ?? {};
4258
- let content2;
4259
- if (eventName === "tick") {
4260
- const tick = data.tick ?? 0;
4261
- const simTime = String(data.simTime ?? "").replace("T", " ").replace(/\..*/, "");
4262
- const speed = data.speed ?? 1;
4263
- content2 = `[heartbeat] tick ${tick} | sim time: ${simTime} | speed: x${speed}`;
4264
- } else if (eventName === "peer_joined") {
4265
- content2 = `[system] Peer "${data.name ?? "unknown"}" joined the mesh`;
4266
- } else if (eventName === "peer_returned") {
4267
- const peerName = String(data.name ?? "unknown");
4268
- const lastSeenAt = data.lastSeenAt ? relativeTime(String(data.lastSeenAt)) : "unknown";
4269
- const groups = Array.isArray(data.groups) ? data.groups.map((g) => g.role ? `@${g.name}:${g.role}` : `@${g.name}`).join(", ") : "";
4270
- const summary = data.summary ? ` Summary: "${data.summary}"` : "";
4271
- content2 = `[system] Welcome back, "${peerName}"! Last seen ${lastSeenAt}.${groups ? ` Restored: ${groups}` : ""}${summary}`;
4272
- } else if (eventName === "peer_left") {
4273
- content2 = `[system] Peer "${data.name ?? "unknown"}" left the mesh`;
4274
- } else if (eventName === "mcp_registered") {
4275
- const tools = Array.isArray(data.tools) ? data.tools.join(", ") : "";
4276
- content2 = `[system] New MCP server available: "${data.serverName}" (hosted by ${data.hostedBy}). Tools: ${tools}. Use mesh_tool_call to invoke.`;
4277
- } else if (eventName === "mcp_unregistered") {
4278
- content2 = `[system] MCP server "${data.serverName}" removed (was hosted by ${data.hostedBy})`;
4279
- } else if (eventName === "mcp_restored") {
4280
- content2 = `[system] MCP server "${data.serverName}" is back online (hosted by ${data.hostedBy})`;
4281
- } else if (eventName === "watch_triggered") {
4282
- content2 = `[WATCH] ${data.label ?? data.url}: ${data.oldValue} → ${data.newValue}`;
4283
- } else if (eventName === "mcp_deployed") {
4284
- content2 = `[SERVICE] "${data.name}" deployed (${data.tool_count} tools) by ${data.deployed_by}`;
4285
- } else if (eventName === "mcp_undeployed") {
4286
- content2 = `[SERVICE] "${data.name}" undeployed by ${data.by}`;
4287
- } else if (eventName === "mcp_scope_changed") {
4288
- content2 = `[SERVICE] "${data.name}" scope changed to ${JSON.stringify(data.scope)} by ${data.by}`;
4289
- } else {
4290
- content2 = `[system] ${eventName}: ${JSON.stringify(data)}`;
4291
- }
4292
- try {
4293
- await server.notification({
4294
- method: "notifications/claude/channel",
4295
- params: {
4296
- content: content2,
4297
- meta: {
4298
- kind: "system",
4299
- event: eventName,
4300
- mesh_slug: client.meshSlug,
4301
- mesh_id: client.meshId,
4302
- ...Object.keys(data).length > 0 ? { eventData: JSON.stringify(data) } : {}
4303
- }
4304
- }
4305
- });
4306
- process.stderr.write(`[claudemesh] system: ${content2}
4307
- `);
4308
- } catch (pushErr) {
4309
- process.stderr.write(`[claudemesh] system push FAILED: ${pushErr}
4310
- `);
4311
- }
4312
- return;
4313
- }
4314
- const fromPubkey = msg.senderPubkey || "";
4315
- const fromName = fromPubkey ? await resolvePeerName(client, fromPubkey) : "unknown";
4316
- if (fromPubkey) {
4317
- try {
4318
- const { isAllowed: isAllowed2 } = await Promise.resolve().then(() => (init_grants(), exports_grants));
4319
- const kindCap = msg.kind === "broadcast" ? "broadcast" : "dm";
4320
- if (!isAllowed2(client.meshSlug, fromPubkey, kindCap)) {
4321
- process.stderr.write(`[claudemesh] dropped ${kindCap} from ${fromName} (not granted)
4322
- `);
4323
- return;
4324
- }
4325
- } catch {}
4326
- }
4327
- if (messageMode === "inbox") {
4328
- try {
4329
- await server.notification({
4330
- method: "notifications/claude/channel",
4331
- params: {
4332
- content: `[inbox] New message from ${fromName}. Use check_messages to read.`,
4333
- meta: { kind: "inbox_notification", from_name: fromName }
4334
- }
4335
- });
4336
- } catch {}
4337
- return;
4338
- }
4339
- const body = msg.plaintext ?? decryptFailedWarning(fromPubkey);
4340
- const prioBadge = msg.priority === "now" ? "[URGENT] " : msg.priority === "low" ? "[low] " : "";
4341
- const kindBadge = msg.kind === "broadcast" ? " (broadcast)" : "";
4342
- const content = `${prioBadge}${fromName}${kindBadge}: ${body}`;
4343
- const fromMemberPubkey = msg.senderMemberPubkey ?? fromPubkey;
4344
- try {
4345
- await server.notification({
4346
- method: "notifications/claude/channel",
4347
- params: {
4348
- content,
4349
- meta: {
4350
- from_id: fromMemberPubkey,
4351
- from_pubkey: fromMemberPubkey,
4352
- from_session_pubkey: fromPubkey,
4353
- from_name: fromName,
4354
- ...msg.senderMemberId ? { from_member_id: msg.senderMemberId } : {},
4355
- mesh_slug: client.meshSlug,
4356
- mesh_id: client.meshId,
4357
- priority: msg.priority,
4358
- sent_at: msg.createdAt,
4359
- delivered_at: msg.receivedAt,
4360
- kind: msg.kind,
4361
- message_id: msg.messageId,
4362
- ...msg.topic ? { topic: msg.topic } : {},
4363
- ...msg.replyToId ? { reply_to_id: msg.replyToId } : {},
4364
- ...msg.subtype ? { subtype: msg.subtype } : {}
4365
- }
4366
- }
4367
- });
4368
- process.stderr.write(`[claudemesh] pushed: from=${fromName} content=${body.slice(0, 60)}
4369
- `);
4370
- } catch (pushErr) {
4371
- process.stderr.write(`[claudemesh] push FAILED: ${pushErr}
4372
- `);
4373
- }
4374
- });
4375
- client.onStreamData(async (evt) => {
4376
- try {
4377
- await server.notification({
4378
- method: "notifications/claude/channel",
4379
- params: {
4380
- content: `[stream:${evt.stream}] from ${evt.publishedBy}: ${JSON.stringify(evt.data)}`,
4381
- meta: {
4382
- kind: "stream_data",
4383
- stream: evt.stream,
4384
- published_by: evt.publishedBy
4385
- }
4386
- }
4387
- });
4388
- } catch {}
4389
- });
4390
- client.onStateChange(async (change) => {
4391
- try {
4392
- await server.notification({
4393
- method: "notifications/claude/channel",
4394
- params: {
4395
- content: `[state] ${change.key} = ${JSON.stringify(change.value)} (set by ${change.updatedBy})`,
4396
- meta: {
4397
- kind: "state_change",
4398
- key: change.key,
4399
- updated_by: change.updatedBy
4400
- }
4401
- }
4402
- });
4403
- } catch {}
4404
- });
4405
- }
4406
- setTimeout(async () => {
4407
- const welcomeClient = allClients()[0];
4408
- if (!welcomeClient || welcomeClient.status !== "open")
4409
- return;
4410
- try {
4411
- const peers = await welcomeClient.listPeers();
4412
- const peerNames = peers.filter((p) => p.displayName !== myName).map((p) => p.displayName).join(", ") || "none";
4413
- await server.notification({
4414
- method: "notifications/claude/channel",
4415
- params: {
4416
- 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.`,
4417
- meta: { kind: "welcome", mesh_slug: welcomeClient.meshSlug }
4418
- }
4419
- });
4420
- } catch {}
4421
- }, 2000);
4422
- }
4423
- const keepalive = setInterval(() => {}, 1000);
4424
- const shutdown = () => {
4425
- clearInterval(keepalive);
4426
- for (const b of bridges) {
4427
- try {
4428
- b.stop();
4429
- } catch {}
4430
- }
4431
- stopAll();
4432
- process.exit(0);
4433
- };
4434
- process.on("SIGTERM", shutdown);
4435
- process.on("SIGINT", shutdown);
4436
- }
4437
- async function startServiceProxy(serviceName) {
4438
- const config = readConfig();
4439
- if (config.meshes.length === 0) {
4440
- process.stderr.write(`[mesh:${serviceName}] no meshes joined
4441
- `);
4442
- process.exit(1);
4443
- }
4444
- const mesh = config.meshes[0];
4445
- const client = new BrokerClient(mesh, {
4446
- displayName: config.displayName ?? `proxy:${serviceName}`
4447
- });
4448
- try {
4449
- await client.connect();
4450
- } catch (e) {
4451
- process.stderr.write(`[mesh:${serviceName}] broker connect failed: ${e instanceof Error ? e.message : String(e)}
4452
- `);
4453
- process.exit(1);
4454
- }
4455
- await new Promise((r) => setTimeout(r, 1500));
4456
- let tools = [];
4457
- try {
4458
- const fetched = await client.getServiceTools(serviceName);
4459
- tools = fetched;
4460
- } catch {
4461
- const cached = client.serviceCatalog.find((s) => s.name === serviceName);
4462
- if (cached) {
4463
- tools = cached.tools;
4464
- }
4465
- }
4466
- if (tools.length === 0) {
4467
- process.stderr.write(`[mesh:${serviceName}] no tools found — service may not be running
4468
- `);
4469
- }
4470
- const server = new Server({ name: `mesh:${serviceName}`, version: "0.1.0" }, { capabilities: { tools: {} } });
4471
- server.setRequestHandler(ListToolsRequestSchema, () => ({
4472
- tools: tools.map((t) => ({
4473
- name: t.name,
4474
- description: `[mesh:${serviceName}] ${t.description}`,
4475
- inputSchema: t.inputSchema
4476
- }))
4477
- }));
4478
- server.setRequestHandler(CallToolRequestSchema, async (req) => {
4479
- const toolName = req.params.name;
4480
- const args = req.params.arguments ?? {};
4481
- if (client.status !== "open") {
4482
- let waited = 0;
4483
- while (client.status !== "open" && waited < 1e4) {
4484
- await new Promise((r) => setTimeout(r, 500));
4485
- waited += 500;
4486
- }
4487
- if (client.status !== "open") {
4488
- return {
4489
- content: [
4490
- {
4491
- type: "text",
4492
- text: `Service temporarily unavailable — broker reconnecting. Retry in a few seconds.`
4493
- }
4494
- ],
4495
- isError: true
4496
- };
2986
+ }
2987
+ const server = new Server({ name: `mesh:${serviceName}`, version: "0.1.0" }, { capabilities: { tools: {} } });
2988
+ server.setRequestHandler(ListToolsRequestSchema, () => ({
2989
+ tools: tools.map((t) => ({
2990
+ name: t.name,
2991
+ description: `[mesh:${serviceName}] ${t.description}`,
2992
+ inputSchema: t.inputSchema
2993
+ }))
2994
+ }));
2995
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
2996
+ const toolName = req.params.name;
2997
+ const args = req.params.arguments ?? {};
2998
+ if (client.status !== "open") {
2999
+ let waited = 0;
3000
+ while (client.status !== "open" && waited < 1e4) {
3001
+ await new Promise((r) => setTimeout(r, 500));
3002
+ waited += 500;
3003
+ }
3004
+ if (client.status !== "open") {
3005
+ return {
3006
+ content: [{ type: "text", text: "Service temporarily unavailable broker reconnecting. Retry in a few seconds." }],
3007
+ isError: true
3008
+ };
4497
3009
  }
4498
3010
  }
4499
3011
  try {
4500
3012
  const result = await client.mcpCall(serviceName, toolName, args);
4501
3013
  if (result.error) {
4502
- return {
4503
- content: [{ type: "text", text: `Error: ${result.error}` }],
4504
- isError: true
4505
- };
3014
+ return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
4506
3015
  }
4507
3016
  const resultText = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
4508
- return {
4509
- content: [{ type: "text", text: resultText }]
4510
- };
3017
+ return { content: [{ type: "text", text: resultText }] };
4511
3018
  } catch (e) {
4512
3019
  return {
4513
- content: [
4514
- {
4515
- type: "text",
4516
- text: `Call failed: ${e instanceof Error ? e.message : String(e)}`
4517
- }
4518
- ],
3020
+ content: [{ type: "text", text: `Call failed: ${e instanceof Error ? e.message : String(e)}` }],
4519
3021
  isError: true
4520
3022
  };
4521
3023
  }
@@ -4531,9 +3033,7 @@ async function startServiceProxy(serviceName) {
4531
3033
  const newTools = push.eventData?.tools;
4532
3034
  if (Array.isArray(newTools)) {
4533
3035
  tools = newTools;
4534
- server.notification({
4535
- method: "notifications/tools/list_changed"
4536
- }).catch(() => {});
3036
+ server.notification({ method: "notifications/tools/list_changed" }).catch(() => {});
4537
3037
  }
4538
3038
  }
4539
3039
  });
@@ -4548,21 +3048,20 @@ async function startServiceProxy(serviceName) {
4548
3048
  process.on("SIGTERM", shutdown);
4549
3049
  process.on("SIGINT", shutdown);
4550
3050
  }
4551
- var peerNameCache, peerNameCacheAge = 0, CACHE_TTL_MS = 30000;
4552
- var init_server2 = __esm(() => {
4553
- init_definitions();
3051
+ var DAEMON_BOOT_RETRIES = 4, DAEMON_BOOT_RETRY_MS = 500;
3052
+ var init_server = __esm(() => {
3053
+ init_paths2();
3054
+ init_urls();
4554
3055
  init_facade();
4555
3056
  init_facade3();
4556
- init_server();
4557
- peerNameCache = new Map;
4558
3057
  });
4559
3058
 
4560
3059
  // src/entrypoints/mcp.ts
4561
- init_server2();
3060
+ init_server();
4562
3061
  startMcpServer().catch((err) => {
4563
3062
  process.stderr.write(`MCP server error: ${err instanceof Error ? err.message : err}
4564
3063
  `);
4565
3064
  process.exit(1);
4566
3065
  });
4567
3066
 
4568
- //# debugId=1C8A5B32F35FD27064756E2164756E21
3067
+ //# debugId=D78D6D4009873E9364756E2164756E21