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