claudemesh-cli 1.4.0 → 1.5.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.
@@ -88,7 +88,7 @@ __export(exports_urls, {
88
88
  VERSION: () => VERSION,
89
89
  URLS: () => URLS
90
90
  });
91
- var URLS, VERSION = "1.4.0", env;
91
+ var URLS, VERSION = "1.5.0", env;
92
92
  var init_urls = __esm(() => {
93
93
  URLS = {
94
94
  BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
@@ -136,27 +136,27 @@ function normaliseInviteUrl(input, host = "claudemesh.com") {
136
136
  }
137
137
 
138
138
  // src/constants/paths.ts
139
- import { homedir } from "node:os";
140
- import { join } from "node:path";
139
+ import { homedir as homedir2 } from "node:os";
140
+ import { join as join2 } from "node:path";
141
141
  var home, PATHS;
142
142
  var init_paths = __esm(() => {
143
- home = homedir();
143
+ home = homedir2();
144
144
  PATHS = {
145
- CONFIG_DIR: process.env.CLAUDEMESH_CONFIG_DIR || join(home, ".claudemesh"),
145
+ CONFIG_DIR: process.env.CLAUDEMESH_CONFIG_DIR || join2(home, ".claudemesh"),
146
146
  get CONFIG_FILE() {
147
- return join(this.CONFIG_DIR, "config.json");
147
+ return join2(this.CONFIG_DIR, "config.json");
148
148
  },
149
149
  get AUTH_FILE() {
150
- return join(this.CONFIG_DIR, "auth.json");
150
+ return join2(this.CONFIG_DIR, "auth.json");
151
151
  },
152
152
  get KEYS_DIR() {
153
- return join(this.CONFIG_DIR, "keys");
153
+ return join2(this.CONFIG_DIR, "keys");
154
154
  },
155
155
  get LAST_USED_FILE() {
156
- return join(this.CONFIG_DIR, "last-used.json");
156
+ return join2(this.CONFIG_DIR, "last-used.json");
157
157
  },
158
- CLAUDE_JSON: join(home, ".claude.json"),
159
- CLAUDE_SETTINGS: join(home, ".claude", "settings.json")
158
+ CLAUDE_JSON: join2(home, ".claude.json"),
159
+ CLAUDE_SETTINGS: join2(home, ".claude", "settings.json")
160
160
  };
161
161
  });
162
162
 
@@ -166,12 +166,12 @@ function emptyConfig() {
166
166
  }
167
167
 
168
168
  // src/services/config/read.ts
169
- import { readFileSync, existsSync } from "node:fs";
169
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
170
170
  function readConfig() {
171
- if (!existsSync(PATHS.CONFIG_FILE))
171
+ if (!existsSync2(PATHS.CONFIG_FILE))
172
172
  return emptyConfig();
173
173
  try {
174
- const raw = readFileSync(PATHS.CONFIG_FILE, "utf-8");
174
+ const raw = readFileSync2(PATHS.CONFIG_FILE, "utf-8");
175
175
  const parsed = JSON.parse(raw);
176
176
  if (!parsed || !Array.isArray(parsed.meshes))
177
177
  return emptyConfig();
@@ -197,10 +197,10 @@ var init_read = __esm(() => {
197
197
  });
198
198
 
199
199
  // src/services/config/write.ts
200
- import { writeFileSync, mkdirSync, chmodSync, openSync, closeSync, renameSync } from "node:fs";
200
+ import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, chmodSync, openSync, closeSync, renameSync } from "node:fs";
201
201
  import { platform } from "node:os";
202
202
  function ensureConfigDir() {
203
- mkdirSync(PATHS.CONFIG_DIR, { recursive: true });
203
+ mkdirSync2(PATHS.CONFIG_DIR, { recursive: true });
204
204
  if (!isWindows) {
205
205
  try {
206
206
  chmodSync(PATHS.CONFIG_DIR, 448);
@@ -216,11 +216,11 @@ function writeConfig(config) {
216
216
  `;
217
217
  const tmpPath = PATHS.CONFIG_FILE + ".tmp";
218
218
  if (isWindows) {
219
- writeFileSync(tmpPath, content, "utf-8");
219
+ writeFileSync2(tmpPath, content, "utf-8");
220
220
  } else {
221
221
  const fd = openSync(tmpPath, "w", 384);
222
222
  try {
223
- writeFileSync(fd, content, "utf-8");
223
+ writeFileSync2(fd, content, "utf-8");
224
224
  } finally {
225
225
  closeSync(fd);
226
226
  }
@@ -501,7 +501,7 @@ var init_facade4 = __esm(() => {
501
501
 
502
502
  // src/services/spawn/claude.ts
503
503
  import { spawnSync } from "node:child_process";
504
- import { existsSync as existsSync2 } from "node:fs";
504
+ import { existsSync as existsSync3 } from "node:fs";
505
505
  function findClaudeBinary() {
506
506
  const candidates = [
507
507
  process.env.CLAUDE_BIN,
@@ -510,7 +510,7 @@ function findClaudeBinary() {
510
510
  `${process.env.HOME}/.npm/bin/claude`
511
511
  ].filter(Boolean);
512
512
  for (const bin of candidates) {
513
- if (existsSync2(bin))
513
+ if (existsSync3(bin))
514
514
  return bin;
515
515
  }
516
516
  const which = spawnSync("which", ["claude"], { encoding: "utf-8" });
@@ -571,12 +571,12 @@ var init_facade5 = __esm(() => {
571
571
  });
572
572
 
573
573
  // src/services/auth/token-store.ts
574
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, existsSync as existsSync3, openSync as openSync2, closeSync as closeSync2 } from "node:fs";
574
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync, existsSync as existsSync4, openSync as openSync2, closeSync as closeSync2 } from "node:fs";
575
575
  function getStoredToken() {
576
- if (!existsSync3(PATHS.AUTH_FILE))
576
+ if (!existsSync4(PATHS.AUTH_FILE))
577
577
  return null;
578
578
  try {
579
- const raw = readFileSync2(PATHS.AUTH_FILE, "utf-8");
579
+ const raw = readFileSync3(PATHS.AUTH_FILE, "utf-8");
580
580
  return JSON.parse(raw);
581
581
  } catch {
582
582
  return null;
@@ -589,7 +589,7 @@ function storeToken(auth) {
589
589
  `;
590
590
  const fd = openSync2(PATHS.AUTH_FILE, "w", 384);
591
591
  try {
592
- writeFileSync2(fd, content, "utf-8");
592
+ writeFileSync3(fd, content, "utf-8");
593
593
  } finally {
594
594
  closeSync2(fd);
595
595
  }
@@ -626,7 +626,7 @@ var init_errors2 = __esm(() => {
626
626
  });
627
627
 
628
628
  // src/services/auth/device-code.ts
629
- import { createInterface } from "node:readline";
629
+ import { createInterface as createInterface2 } from "node:readline";
630
630
  function parseJwtUser(token) {
631
631
  try {
632
632
  const parts = token.split(".");
@@ -681,7 +681,7 @@ async function loginWithDeviceCode() {
681
681
  }
682
682
  return new Promise((resolve, reject) => {
683
683
  let done = false;
684
- const rl = createInterface({ input: process.stdin, output: process.stdout });
684
+ const rl = createInterface2({ input: process.stdin, output: process.stdout });
685
685
  rl.on("line", (line) => {
686
686
  if (done)
687
687
  return;
@@ -925,11 +925,6 @@ var init_facade6 = __esm(() => {
925
925
  });
926
926
 
927
927
  // src/services/crypto/keypair.ts
928
- var exports_keypair = {};
929
- __export(exports_keypair, {
930
- generateKeypair: () => generateKeypair2,
931
- ensureSodium: () => ensureSodium
932
- });
933
928
  import sodium from "libsodium-wrappers";
934
929
  async function ensureSodium() {
935
930
  if (!ready) {
@@ -950,13 +945,6 @@ var ready = false;
950
945
  var init_keypair = () => {};
951
946
 
952
947
  // src/services/crypto/file-crypto.ts
953
- var exports_file_crypto = {};
954
- __export(exports_file_crypto, {
955
- sealKeyForPeer: () => sealKeyForPeer,
956
- openSealedKey: () => openSealedKey,
957
- encryptFile: () => encryptFile,
958
- decryptFile: () => decryptFile
959
- });
960
948
  async function encryptFile(plaintext) {
961
949
  const s = await ensureSodium();
962
950
  const key = s.randombytes_buf(s.crypto_secretbox_KEYBYTES);
@@ -1102,18 +1090,18 @@ import WebSocket from "ws";
1102
1090
  import { randomBytes as randomBytes3 } from "node:crypto";
1103
1091
  function detectClaudeSessionId() {
1104
1092
  try {
1105
- const { readdirSync, statSync, readFileSync: readFileSync3 } = __require("node:fs");
1106
- const { join: join2 } = __require("node:path");
1107
- const { homedir: homedir2 } = __require("node:os");
1093
+ const { readdirSync, statSync, readFileSync: readFileSync4 } = __require("node:fs");
1094
+ const { join: join3 } = __require("node:path");
1095
+ const { homedir: homedir3 } = __require("node:os");
1108
1096
  const cwd = process.cwd();
1109
- const projectsDir = join2(homedir2(), ".claude", "projects");
1097
+ const projectsDir = join3(homedir3(), ".claude", "projects");
1110
1098
  const cwdHash = cwd.replace(/\//g, "-");
1111
1099
  const entries = readdirSync(projectsDir);
1112
1100
  const projectDir = entries.find((e) => e === cwdHash || e.startsWith(cwdHash));
1113
1101
  if (!projectDir)
1114
1102
  return null;
1115
- const fullDir = join2(projectsDir, projectDir);
1116
- 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);
1103
+ const fullDir = join3(projectsDir, projectDir);
1104
+ 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);
1117
1105
  if (jsonls.length === 0)
1118
1106
  return null;
1119
1107
  const latest = jsonls[0];
@@ -1634,9 +1622,9 @@ class BrokerClient {
1634
1622
  this.ws.send(JSON.stringify({ type: "delete_file", fileId }));
1635
1623
  }
1636
1624
  async uploadFile(filePath, meshId, memberId, opts) {
1637
- const { readFileSync: readFileSync3 } = await import("node:fs");
1625
+ const { readFileSync: readFileSync4 } = await import("node:fs");
1638
1626
  const { basename } = await import("node:path");
1639
- const data = readFileSync3(filePath);
1627
+ const data = readFileSync4(filePath);
1640
1628
  const fileName = opts.name ?? basename(filePath);
1641
1629
  const brokerHttp = this.mesh.brokerUrl.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
1642
1630
  const res = await fetch(`${brokerHttp}/upload`, {
@@ -2322,8 +2310,8 @@ class BrokerClient {
2322
2310
  }
2323
2311
  static MAX_FILE_SIZE = 1048576;
2324
2312
  async handlePeerFileRequest(msg) {
2325
- const { resolve, join: join2, normalize } = await import("node:path");
2326
- const { readFileSync: readFileSync3, statSync, realpathSync } = await import("node:fs");
2313
+ const { resolve, join: join3, normalize } = await import("node:path");
2314
+ const { readFileSync: readFileSync4, statSync, realpathSync } = await import("node:fs");
2327
2315
  const reqId = msg._reqId;
2328
2316
  const sendResponse = (content, error2) => {
2329
2317
  if (!this.ws || this.ws.readyState !== this.ws.OPEN)
@@ -2347,7 +2335,7 @@ class BrokerClient {
2347
2335
  }
2348
2336
  let resolvedPath = null;
2349
2337
  for (const dir of this.sharedDirs) {
2350
- const candidate = resolve(join2(dir, msg.filePath));
2338
+ const candidate = resolve(join3(dir, msg.filePath));
2351
2339
  let realCandidate;
2352
2340
  let realDir;
2353
2341
  try {
@@ -2375,7 +2363,7 @@ class BrokerClient {
2375
2363
  sendResponse(undefined, `file too large (${stat.size} bytes, max ${BrokerClient.MAX_FILE_SIZE})`);
2376
2364
  return;
2377
2365
  }
2378
- const content = readFileSync3(resolvedPath);
2366
+ const content = readFileSync4(resolvedPath);
2379
2367
  sendResponse(content.toString("base64"));
2380
2368
  } catch (e) {
2381
2369
  const errMsg = e instanceof Error ? e.message : String(e);
@@ -2387,7 +2375,7 @@ class BrokerClient {
2387
2375
  }
2388
2376
  }
2389
2377
  async handlePeerDirRequest(msg) {
2390
- const { resolve, join: join2, normalize, relative } = await import("node:path");
2378
+ const { resolve, join: join3, normalize, relative } = await import("node:path");
2391
2379
  const { readdirSync, statSync, realpathSync } = await import("node:fs");
2392
2380
  const reqId = msg._reqId;
2393
2381
  const sendResponse = (entries, error2) => {
@@ -2413,7 +2401,7 @@ class BrokerClient {
2413
2401
  }
2414
2402
  let resolvedPath = null;
2415
2403
  for (const dir of this.sharedDirs) {
2416
- const candidate = resolve(join2(dir, dirPath));
2404
+ const candidate = resolve(join3(dir, dirPath));
2417
2405
  let realCandidate;
2418
2406
  let realDir;
2419
2407
  try {
@@ -2459,16 +2447,16 @@ class BrokerClient {
2459
2447
  break;
2460
2448
  if (item.name.startsWith("."))
2461
2449
  continue;
2462
- const relPath = relative(resolvedPath, join2(dir, item.name));
2450
+ const relPath = relative(resolvedPath, join3(dir, item.name));
2463
2451
  const label = item.isDirectory() ? relPath + "/" : relPath;
2464
2452
  if (pattern && !pattern.test(item.name)) {
2465
2453
  if (item.isDirectory())
2466
- walk(join2(dir, item.name), depth + 1);
2454
+ walk(join3(dir, item.name), depth + 1);
2467
2455
  continue;
2468
2456
  }
2469
2457
  entries.push(label);
2470
2458
  if (item.isDirectory())
2471
- walk(join2(dir, item.name), depth + 1);
2459
+ walk(join3(dir, item.name), depth + 1);
2472
2460
  }
2473
2461
  } catch {}
2474
2462
  };
@@ -3128,16 +3116,6 @@ async function startClients(config) {
3128
3116
  configGroups = config.groups ?? [];
3129
3117
  await Promise.allSettled(config.meshes.map(ensureClient));
3130
3118
  }
3131
- function findClient(needle) {
3132
- const byId = clients.get(needle);
3133
- if (byId)
3134
- return byId;
3135
- for (const c of clients.values()) {
3136
- if (c.meshSlug === needle)
3137
- return c;
3138
- }
3139
- return null;
3140
- }
3141
3119
  function allClients() {
3142
3120
  return [...clients.values()];
3143
3121
  }
@@ -3240,7 +3218,7 @@ ${INDENT}${dim("—")} ${clay(title)}
3240
3218
  });
3241
3219
 
3242
3220
  // src/ui/screen.ts
3243
- import { createInterface as createInterface2 } from "node:readline";
3221
+ import { createInterface as createInterface3 } from "node:readline";
3244
3222
  function termSize() {
3245
3223
  return { cols: process.stdout.columns || 80, rows: process.stdout.rows || 24 };
3246
3224
  }
@@ -3276,7 +3254,7 @@ async function menuSelect(itemsOrOpts, prompt = "Choice") {
3276
3254
  ${title}`);
3277
3255
  items.forEach((item, i) => console.log(` ${bold(String(i + 1) + ")")} ${item}`));
3278
3256
  console.log("");
3279
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
3257
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
3280
3258
  return new Promise((resolve) => {
3281
3259
  rl.question(` ${prompt} [1]: `, (answer) => {
3282
3260
  rl.close();
@@ -3288,7 +3266,7 @@ async function menuSelect(itemsOrOpts, prompt = "Choice") {
3288
3266
  async function textInput(promptOrOpts, defaultVal = "") {
3289
3267
  const label = typeof promptOrOpts === "string" ? promptOrOpts : promptOrOpts.label;
3290
3268
  const placeholder = typeof promptOrOpts === "object" ? promptOrOpts.placeholder : undefined;
3291
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
3269
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
3292
3270
  return new Promise((resolve) => {
3293
3271
  const hint = placeholder ? ` (${placeholder})` : defaultVal ? ` [${defaultVal}]` : "";
3294
3272
  rl.question(` ${label}${hint}: `, (answer) => {
@@ -3297,10 +3275,10 @@ async function textInput(promptOrOpts, defaultVal = "") {
3297
3275
  });
3298
3276
  });
3299
3277
  }
3300
- async function confirmPrompt(promptOrOpts, defaultYes = true) {
3278
+ async function confirmPrompt2(promptOrOpts, defaultYes = true) {
3301
3279
  const message = typeof promptOrOpts === "string" ? promptOrOpts : promptOrOpts.message;
3302
3280
  const defYes = typeof promptOrOpts === "object" && promptOrOpts.defaultYes !== undefined ? promptOrOpts.defaultYes : defaultYes;
3303
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
3281
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
3304
3282
  const hint = defYes ? "[Y/n]" : "[y/N]";
3305
3283
  return new Promise((resolve) => {
3306
3284
  rl.question(` ${message} ${hint}: `, (answer) => {
@@ -3412,10 +3390,10 @@ __export(exports_launch, {
3412
3390
  });
3413
3391
  import { spawnSync as spawnSync2 } from "node:child_process";
3414
3392
  import { randomUUID } from "node:crypto";
3415
- import { mkdtempSync, writeFileSync as writeFileSync3, rmSync, readdirSync, statSync, existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
3416
- import { tmpdir, hostname as hostname2, homedir as homedir2 } from "node:os";
3417
- import { join as join2 } from "node:path";
3418
- import { createInterface as createInterface3 } from "node:readline";
3393
+ import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, statSync, existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
3394
+ import { tmpdir, hostname as hostname2, homedir as homedir3 } from "node:os";
3395
+ import { join as join3 } from "node:path";
3396
+ import { createInterface as createInterface4 } from "node:readline";
3419
3397
  function parseGroupsString(raw) {
3420
3398
  return raw.split(",").map((s) => s.trim()).filter(Boolean).map((token) => {
3421
3399
  const idx = token.indexOf(":");
@@ -3534,7 +3512,7 @@ async function runLaunchWizard(opts) {
3534
3512
  writeCentered(row, dim("Claude will run with --dangerously-skip-permissions,"));
3535
3513
  writeCentered(row + 1, dim("bypassing ALL permission prompts — not just claudemesh."));
3536
3514
  row += 3;
3537
- const confirmed = await confirmPrompt({
3515
+ const confirmed = await confirmPrompt2({
3538
3516
  message: boldOrange("Autonomous mode?"),
3539
3517
  row,
3540
3518
  defaultYes: true
@@ -3644,7 +3622,7 @@ async function runLaunch(flags, rawArgs) {
3644
3622
  console.log(` ${dim2(`Or join with invite: claudemesh launch --join <url>`)}
3645
3623
  `);
3646
3624
  const manualPromise = new Promise((resolve) => {
3647
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
3625
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
3648
3626
  rl.question(" Paste sync token (or wait for browser): ", (answer) => {
3649
3627
  rl.close();
3650
3628
  if (answer.trim())
@@ -3735,16 +3713,16 @@ async function runLaunch(flags, rawArgs) {
3735
3713
  for (const entry of readdirSync(tmpBase)) {
3736
3714
  if (!entry.startsWith("claudemesh-"))
3737
3715
  continue;
3738
- const full = join2(tmpBase, entry);
3716
+ const full = join3(tmpBase, entry);
3739
3717
  const age = Date.now() - statSync(full).mtimeMs;
3740
3718
  if (age > 3600000)
3741
3719
  rmSync(full, { recursive: true, force: true });
3742
3720
  }
3743
3721
  } catch {}
3744
3722
  try {
3745
- const claudeConfigPath = join2(homedir2(), ".claude.json");
3746
- if (existsSync4(claudeConfigPath)) {
3747
- const claudeConfig = JSON.parse(readFileSync3(claudeConfigPath, "utf-8"));
3723
+ const claudeConfigPath = join3(homedir3(), ".claude.json");
3724
+ if (existsSync5(claudeConfigPath)) {
3725
+ const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
3748
3726
  const mcpServers = claudeConfig.mcpServers ?? {};
3749
3727
  let cleaned = 0;
3750
3728
  for (const key of Object.keys(mcpServers)) {
@@ -3762,7 +3740,7 @@ async function runLaunch(flags, rawArgs) {
3762
3740
  }
3763
3741
  if (cleaned > 0) {
3764
3742
  claudeConfig.mcpServers = mcpServers;
3765
- writeFileSync3(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + `
3743
+ writeFileSync4(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + `
3766
3744
  `, "utf-8");
3767
3745
  }
3768
3746
  }
@@ -3779,7 +3757,7 @@ async function runLaunch(flags, rawArgs) {
3779
3757
  console.log(" (Could not fetch service catalog — mesh services won't be natively available)");
3780
3758
  }
3781
3759
  }
3782
- const tmpDir = mkdtempSync(join2(tmpdir(), "claudemesh-"));
3760
+ const tmpDir = mkdtempSync(join3(tmpdir(), "claudemesh-"));
3783
3761
  const sessionConfig = {
3784
3762
  version: 1,
3785
3763
  meshes: [mesh],
@@ -3788,17 +3766,17 @@ async function runLaunch(flags, rawArgs) {
3788
3766
  ...parsedGroups.length > 0 ? { groups: parsedGroups } : {},
3789
3767
  messageMode
3790
3768
  };
3791
- writeFileSync3(join2(tmpDir, "config.json"), JSON.stringify(sessionConfig, null, 2) + `
3769
+ writeFileSync4(join3(tmpDir, "config.json"), JSON.stringify(sessionConfig, null, 2) + `
3792
3770
  `, "utf-8");
3793
3771
  if (!args.quiet) {
3794
3772
  printBanner(displayName, mesh.slug, role, parsedGroups, messageMode);
3795
3773
  }
3796
3774
  const meshMcpEntries = [];
3797
3775
  if (serviceCatalog.length > 0) {
3798
- const claudeConfigPath = join2(homedir2(), ".claude.json");
3776
+ const claudeConfigPath = join3(homedir3(), ".claude.json");
3799
3777
  let claudeConfig = {};
3800
3778
  try {
3801
- claudeConfig = JSON.parse(readFileSync3(claudeConfigPath, "utf-8"));
3779
+ claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
3802
3780
  } catch {
3803
3781
  claudeConfig = {};
3804
3782
  }
@@ -3825,7 +3803,7 @@ async function runLaunch(flags, rawArgs) {
3825
3803
  meshMcpEntries.push({ key: entryKey, entry });
3826
3804
  }
3827
3805
  claudeConfig.mcpServers = mcpServers;
3828
- writeFileSync3(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + `
3806
+ writeFileSync4(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + `
3829
3807
  `, "utf-8");
3830
3808
  if (!args.quiet && meshMcpEntries.length > 0) {
3831
3809
  console.log(` ${meshMcpEntries.length} mesh service(s) registered as native MCPs:`);
@@ -3862,12 +3840,12 @@ async function runLaunch(flags, rawArgs) {
3862
3840
  let claudeBin = "claude";
3863
3841
  if (!isWindows2) {
3864
3842
  const candidates = [
3865
- join2(homedir2(), ".local", "bin", "claude"),
3843
+ join3(homedir3(), ".local", "bin", "claude"),
3866
3844
  "/usr/local/bin/claude",
3867
- join2(homedir2(), ".claude", "bin", "claude")
3845
+ join3(homedir3(), ".claude", "bin", "claude")
3868
3846
  ];
3869
3847
  for (const c of candidates) {
3870
- if (existsSync4(c)) {
3848
+ if (existsSync5(c)) {
3871
3849
  claudeBin = c;
3872
3850
  break;
3873
3851
  }
@@ -3876,14 +3854,14 @@ async function runLaunch(flags, rawArgs) {
3876
3854
  const cleanup = () => {
3877
3855
  if (meshMcpEntries.length > 0) {
3878
3856
  try {
3879
- const claudeConfigPath = join2(homedir2(), ".claude.json");
3880
- const claudeConfig = JSON.parse(readFileSync3(claudeConfigPath, "utf-8"));
3857
+ const claudeConfigPath = join3(homedir3(), ".claude.json");
3858
+ const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
3881
3859
  const mcpServers = claudeConfig.mcpServers ?? {};
3882
3860
  for (const { key } of meshMcpEntries) {
3883
3861
  delete mcpServers[key];
3884
3862
  }
3885
3863
  claudeConfig.mcpServers = mcpServers;
3886
- writeFileSync3(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + `
3864
+ writeFileSync4(claudeConfigPath, JSON.stringify(claudeConfig, null, 2) + `
3887
3865
  `, "utf-8");
3888
3866
  } catch {}
3889
3867
  }
@@ -3977,9 +3955,9 @@ var exports_login = {};
3977
3955
  __export(exports_login, {
3978
3956
  login: () => login
3979
3957
  });
3980
- import { createInterface as createInterface4 } from "node:readline";
3958
+ import { createInterface as createInterface5 } from "node:readline";
3981
3959
  function prompt(question) {
3982
- const rl = createInterface4({ input: process.stdin, output: process.stdout });
3960
+ const rl = createInterface5({ input: process.stdin, output: process.stdout });
3983
3961
  return new Promise((resolve) => {
3984
3962
  rl.question(question, (answer) => {
3985
3963
  rl.close();
@@ -4101,9 +4079,9 @@ __export(exports_welcome, {
4101
4079
  runWelcome: () => runWelcome,
4102
4080
  _stub: () => runWelcome
4103
4081
  });
4104
- import { createInterface as createInterface5 } from "node:readline";
4082
+ import { createInterface as createInterface6 } from "node:readline";
4105
4083
  function prompt2(q) {
4106
- const rl = createInterface5({ input: process.stdin, output: process.stdout });
4084
+ const rl = createInterface6({ input: process.stdin, output: process.stdout });
4107
4085
  return new Promise((resolve) => {
4108
4086
  rl.question(q, (a) => {
4109
4087
  rl.close();
@@ -4580,9 +4558,9 @@ __export(exports_join, {
4580
4558
  runJoin: () => runJoin
4581
4559
  });
4582
4560
  import sodium3 from "libsodium-wrappers";
4583
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync2 } from "node:fs";
4584
- import { join as join3, dirname } from "node:path";
4585
- import { homedir as homedir3, hostname as hostname3 } from "node:os";
4561
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "node:fs";
4562
+ import { join as join4, dirname as dirname2 } from "node:path";
4563
+ import { homedir as homedir4, hostname as hostname3 } from "node:os";
4586
4564
  function deriveAppBaseUrl() {
4587
4565
  const override = process.env.CLAUDEMESH_APP_URL;
4588
4566
  if (override)
@@ -4701,11 +4679,11 @@ async function runJoin(args) {
4701
4679
  joinedAt: new Date().toISOString()
4702
4680
  });
4703
4681
  writeConfig(config);
4704
- const configDir = env.CLAUDEMESH_CONFIG_DIR ?? join3(homedir3(), ".claudemesh");
4705
- const inviteFile = join3(configDir, `invite-${payload.mesh_slug}.txt`);
4682
+ const configDir = env.CLAUDEMESH_CONFIG_DIR ?? join4(homedir4(), ".claudemesh");
4683
+ const inviteFile = join4(configDir, `invite-${payload.mesh_slug}.txt`);
4706
4684
  try {
4707
- mkdirSync2(dirname(inviteFile), { recursive: true });
4708
- writeFileSync4(inviteFile, link, "utf-8");
4685
+ mkdirSync3(dirname2(inviteFile), { recursive: true });
4686
+ writeFileSync5(inviteFile, link, "utf-8");
4709
4687
  } catch {}
4710
4688
  console.log("");
4711
4689
  console.log(`✓ Joined "${payload.mesh_slug}" as ${displayName}${enroll.alreadyMember ? " (already a member — re-enrolled with same pubkey)" : ""}`);
@@ -4809,9 +4787,9 @@ var exports_delete_mesh = {};
4809
4787
  __export(exports_delete_mesh, {
4810
4788
  deleteMesh: () => deleteMesh
4811
4789
  });
4812
- import { createInterface as createInterface6 } from "node:readline";
4790
+ import { createInterface as createInterface7 } from "node:readline";
4813
4791
  function prompt3(question) {
4814
- const rl = createInterface6({ input: process.stdin, output: process.stdout });
4792
+ const rl = createInterface7({ input: process.stdin, output: process.stdout });
4815
4793
  return new Promise((resolve) => {
4816
4794
  rl.question(question, (a) => {
4817
4795
  rl.close();
@@ -6045,9 +6023,9 @@ var exports_invite = {};
6045
6023
  __export(exports_invite, {
6046
6024
  invite: () => invite
6047
6025
  });
6048
- import { createInterface as createInterface7 } from "node:readline";
6026
+ import { createInterface as createInterface8 } from "node:readline";
6049
6027
  function prompt4(question) {
6050
- const rl = createInterface7({ input: process.stdin, output: process.stdout });
6028
+ const rl = createInterface8({ input: process.stdin, output: process.stdout });
6051
6029
  return new Promise((resolve) => {
6052
6030
  rl.question(question, (a) => {
6053
6031
  rl.close();
@@ -6146,7 +6124,7 @@ var init_invite = __esm(() => {
6146
6124
 
6147
6125
  // src/commands/connect.ts
6148
6126
  import { hostname as hostname4 } from "node:os";
6149
- import { createInterface as createInterface8 } from "node:readline";
6127
+ import { createInterface as createInterface9 } from "node:readline";
6150
6128
  async function pickMesh(meshes) {
6151
6129
  console.log(`
6152
6130
  Select mesh:`);
@@ -6154,7 +6132,7 @@ async function pickMesh(meshes) {
6154
6132
  console.log(` ${i + 1}) ${m.slug}`);
6155
6133
  });
6156
6134
  console.log("");
6157
- const rl = createInterface8({ input: process.stdin, output: process.stdout });
6135
+ const rl = createInterface9({ input: process.stdin, output: process.stdout });
6158
6136
  return new Promise((resolve) => {
6159
6137
  rl.question(" Choice [1]: ", (answer) => {
6160
6138
  rl.close();
@@ -6393,13 +6371,13 @@ var init_ban = __esm(() => {
6393
6371
  });
6394
6372
 
6395
6373
  // src/services/bridge/protocol.ts
6396
- import { homedir as homedir4 } from "node:os";
6397
- import { join as join4 } from "node:path";
6374
+ import { homedir as homedir5 } from "node:os";
6375
+ import { join as join5 } from "node:path";
6398
6376
  function socketPath(meshSlug) {
6399
- return join4(homedir4(), ".claudemesh", "sockets", `${meshSlug}.sock`);
6377
+ return join5(homedir5(), ".claudemesh", "sockets", `${meshSlug}.sock`);
6400
6378
  }
6401
6379
  function socketDir() {
6402
- return join4(homedir4(), ".claudemesh", "sockets");
6380
+ return join5(homedir5(), ".claudemesh", "sockets");
6403
6381
  }
6404
6382
  function frame(obj) {
6405
6383
  return JSON.stringify(obj) + `
@@ -6426,11 +6404,11 @@ var init_protocol = () => {};
6426
6404
 
6427
6405
  // src/services/bridge/client.ts
6428
6406
  import { createConnection } from "node:net";
6429
- import { existsSync as existsSync5 } from "node:fs";
6407
+ import { existsSync as existsSync6 } from "node:fs";
6430
6408
  import { randomUUID as randomUUID2 } from "node:crypto";
6431
6409
  async function tryBridge(meshSlug, verb, args = {}, timeoutMs = DEFAULT_TIMEOUT_MS) {
6432
6410
  const path = socketPath(meshSlug);
6433
- if (!existsSync5(path))
6411
+ if (!existsSync6(path))
6434
6412
  return null;
6435
6413
  return new Promise((resolve) => {
6436
6414
  const id = randomUUID2();
@@ -7411,19 +7389,19 @@ __export(exports_install, {
7411
7389
  import {
7412
7390
  chmodSync as chmodSync3,
7413
7391
  copyFileSync,
7414
- existsSync as existsSync6,
7415
- mkdirSync as mkdirSync3,
7416
- readFileSync as readFileSync4,
7417
- writeFileSync as writeFileSync5
7392
+ existsSync as existsSync7,
7393
+ mkdirSync as mkdirSync4,
7394
+ readFileSync as readFileSync5,
7395
+ writeFileSync as writeFileSync6
7418
7396
  } from "node:fs";
7419
- import { homedir as homedir5, platform as platform5 } from "node:os";
7420
- import { dirname as dirname2, join as join5, resolve } from "node:path";
7397
+ import { homedir as homedir6, platform as platform5 } from "node:os";
7398
+ import { dirname as dirname3, join as join6, resolve } from "node:path";
7421
7399
  import { fileURLToPath } from "node:url";
7422
7400
  import { spawnSync as spawnSync3 } from "node:child_process";
7423
7401
  function readClaudeConfig() {
7424
- if (!existsSync6(CLAUDE_CONFIG))
7402
+ if (!existsSync7(CLAUDE_CONFIG))
7425
7403
  return {};
7426
- const text = readFileSync4(CLAUDE_CONFIG, "utf-8").trim();
7404
+ const text = readFileSync5(CLAUDE_CONFIG, "utf-8").trim();
7427
7405
  if (!text)
7428
7406
  return {};
7429
7407
  try {
@@ -7433,12 +7411,12 @@ function readClaudeConfig() {
7433
7411
  }
7434
7412
  }
7435
7413
  function backupClaudeConfig() {
7436
- if (!existsSync6(CLAUDE_CONFIG))
7414
+ if (!existsSync7(CLAUDE_CONFIG))
7437
7415
  return;
7438
- const backupDir = join5(dirname2(CLAUDE_CONFIG), ".claude", "backups");
7439
- mkdirSync3(backupDir, { recursive: true });
7416
+ const backupDir = join6(dirname3(CLAUDE_CONFIG), ".claude", "backups");
7417
+ mkdirSync4(backupDir, { recursive: true });
7440
7418
  const ts = Date.now();
7441
- const dest = join5(backupDir, `.claude.json.pre-claudemesh.${ts}`);
7419
+ const dest = join6(backupDir, `.claude.json.pre-claudemesh.${ts}`);
7442
7420
  copyFileSync(CLAUDE_CONFIG, dest);
7443
7421
  }
7444
7422
  function patchMcpServer(entry) {
@@ -7462,7 +7440,7 @@ function patchMcpServer(entry) {
7462
7440
  return action;
7463
7441
  }
7464
7442
  function removeMcpServer() {
7465
- if (!existsSync6(CLAUDE_CONFIG))
7443
+ if (!existsSync7(CLAUDE_CONFIG))
7466
7444
  return false;
7467
7445
  backupClaudeConfig();
7468
7446
  const cfg = readClaudeConfig();
@@ -7475,8 +7453,8 @@ function removeMcpServer() {
7475
7453
  return true;
7476
7454
  }
7477
7455
  function flushClaudeConfig(obj) {
7478
- mkdirSync3(dirname2(CLAUDE_CONFIG), { recursive: true });
7479
- writeFileSync5(CLAUDE_CONFIG, JSON.stringify(obj, null, 2) + `
7456
+ mkdirSync4(dirname3(CLAUDE_CONFIG), { recursive: true });
7457
+ writeFileSync6(CLAUDE_CONFIG, JSON.stringify(obj, null, 2) + `
7480
7458
  `, "utf-8");
7481
7459
  try {
7482
7460
  chmodSync3(CLAUDE_CONFIG, 384);
@@ -7493,13 +7471,13 @@ function resolveEntry() {
7493
7471
  const here = fileURLToPath(import.meta.url);
7494
7472
  if (isBundledFile(here))
7495
7473
  return here;
7496
- return resolve(dirname2(here), "..", "index.ts");
7474
+ return resolve(dirname3(here), "..", "index.ts");
7497
7475
  }
7498
7476
  function resolveBundledSkillsDir() {
7499
7477
  const here = fileURLToPath(import.meta.url);
7500
- const pkgRoot = resolve(dirname2(here), "..", "..");
7501
- const skillsDir = join5(pkgRoot, "skills");
7502
- if (existsSync6(skillsDir))
7478
+ const pkgRoot = resolve(dirname3(here), "..", "..");
7479
+ const skillsDir = join6(pkgRoot, "skills");
7480
+ if (existsSync7(skillsDir))
7503
7481
  return skillsDir;
7504
7482
  return null;
7505
7483
  }
@@ -7512,13 +7490,13 @@ function installSkills() {
7512
7490
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
7513
7491
  if (!entry.isDirectory())
7514
7492
  continue;
7515
- const srcDir = join5(src, entry.name);
7516
- const dstDir = join5(CLAUDE_SKILLS_ROOT, entry.name);
7517
- mkdirSync3(dstDir, { recursive: true });
7493
+ const srcDir = join6(src, entry.name);
7494
+ const dstDir = join6(CLAUDE_SKILLS_ROOT, entry.name);
7495
+ mkdirSync4(dstDir, { recursive: true });
7518
7496
  for (const file of fs.readdirSync(srcDir, { withFileTypes: true })) {
7519
7497
  if (!file.isFile())
7520
7498
  continue;
7521
- copyFileSync(join5(srcDir, file.name), join5(dstDir, file.name));
7499
+ copyFileSync(join6(srcDir, file.name), join6(dstDir, file.name));
7522
7500
  }
7523
7501
  installed.push(entry.name);
7524
7502
  }
@@ -7533,8 +7511,8 @@ function uninstallSkills() {
7533
7511
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
7534
7512
  if (!entry.isDirectory())
7535
7513
  continue;
7536
- const dstDir = join5(CLAUDE_SKILLS_ROOT, entry.name);
7537
- if (existsSync6(dstDir)) {
7514
+ const dstDir = join6(CLAUDE_SKILLS_ROOT, entry.name);
7515
+ if (existsSync7(dstDir)) {
7538
7516
  try {
7539
7517
  fs.rmSync(dstDir, { recursive: true, force: true });
7540
7518
  removed.push(entry.name);
@@ -7559,9 +7537,9 @@ function entriesEqual(a, b) {
7559
7537
  return a.command === b.command && JSON.stringify(a.args ?? []) === JSON.stringify(b.args ?? []);
7560
7538
  }
7561
7539
  function readClaudeSettings() {
7562
- if (!existsSync6(CLAUDE_SETTINGS))
7540
+ if (!existsSync7(CLAUDE_SETTINGS))
7563
7541
  return {};
7564
- const text = readFileSync4(CLAUDE_SETTINGS, "utf-8").trim();
7542
+ const text = readFileSync5(CLAUDE_SETTINGS, "utf-8").trim();
7565
7543
  if (!text)
7566
7544
  return {};
7567
7545
  try {
@@ -7571,8 +7549,8 @@ function readClaudeSettings() {
7571
7549
  }
7572
7550
  }
7573
7551
  function writeClaudeSettings(obj) {
7574
- mkdirSync3(dirname2(CLAUDE_SETTINGS), { recursive: true });
7575
- writeFileSync5(CLAUDE_SETTINGS, JSON.stringify(obj, null, 2) + `
7552
+ mkdirSync4(dirname3(CLAUDE_SETTINGS), { recursive: true });
7553
+ writeFileSync6(CLAUDE_SETTINGS, JSON.stringify(obj, null, 2) + `
7576
7554
  `, "utf-8");
7577
7555
  }
7578
7556
  function installAllowedTools() {
@@ -7586,7 +7564,7 @@ function installAllowedTools() {
7586
7564
  return { added: toAdd, unchanged: CLAUDEMESH_TOOLS.length - toAdd.length };
7587
7565
  }
7588
7566
  function uninstallAllowedTools() {
7589
- if (!existsSync6(CLAUDE_SETTINGS))
7567
+ if (!existsSync7(CLAUDE_SETTINGS))
7590
7568
  return 0;
7591
7569
  const settings = readClaudeSettings();
7592
7570
  const existing = settings.allowedTools ?? [];
@@ -7621,7 +7599,7 @@ function installHooks() {
7621
7599
  return { added, unchanged };
7622
7600
  }
7623
7601
  function uninstallHooks() {
7624
- if (!existsSync6(CLAUDE_SETTINGS))
7602
+ if (!existsSync7(CLAUDE_SETTINGS))
7625
7603
  return 0;
7626
7604
  const settings = readClaudeSettings();
7627
7605
  const hooks = settings.hooks;
@@ -7670,7 +7648,7 @@ function runInstall(args = []) {
7670
7648
  render.err("`bun` is not on PATH.", "Install Bun first: https://bun.com");
7671
7649
  process.exit(1);
7672
7650
  }
7673
- if (!existsSync6(entry)) {
7651
+ if (!existsSync7(entry)) {
7674
7652
  render.err(`MCP entry not found at ${entry}`);
7675
7653
  process.exit(1);
7676
7654
  }
@@ -7721,7 +7699,7 @@ function runInstall(args = []) {
7721
7699
  const installed = installSkills();
7722
7700
  if (installed.length > 0) {
7723
7701
  render.ok(`Claude skill${installed.length === 1 ? "" : "s"} installed`, installed.join(", "));
7724
- render.info(dim(` ${join5(CLAUDE_SKILLS_ROOT, installed[0])}/SKILL.md`));
7702
+ render.info(dim(` ${join6(CLAUDE_SKILLS_ROOT, installed[0])}/SKILL.md`));
7725
7703
  }
7726
7704
  } catch (e) {
7727
7705
  render.warn(`skill install failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -7809,9 +7787,9 @@ var init_install = __esm(() => {
7809
7787
  init_facade();
7810
7788
  init_render();
7811
7789
  init_styles();
7812
- CLAUDE_CONFIG = join5(homedir5(), ".claude.json");
7813
- CLAUDE_SETTINGS = join5(homedir5(), ".claude", "settings.json");
7814
- CLAUDE_SKILLS_ROOT = join5(homedir5(), ".claude", "skills");
7790
+ CLAUDE_CONFIG = join6(homedir6(), ".claude.json");
7791
+ CLAUDE_SETTINGS = join6(homedir6(), ".claude", "settings.json");
7792
+ CLAUDE_SKILLS_ROOT = join6(homedir6(), ".claude", "skills");
7815
7793
  CLAUDEMESH_TOOLS = [
7816
7794
  "mcp__claudemesh__cancel_scheduled",
7817
7795
  "mcp__claudemesh__check_messages",
@@ -7866,35 +7844,35 @@ var exports_uninstall = {};
7866
7844
  __export(exports_uninstall, {
7867
7845
  uninstall: () => uninstall
7868
7846
  });
7869
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync6, existsSync as existsSync7, rmSync as rmSync2, readdirSync as readdirSync2 } from "node:fs";
7870
- import { join as join6, dirname as dirname3 } from "node:path";
7871
- import { homedir as homedir6 } from "node:os";
7847
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync7, existsSync as existsSync8, rmSync as rmSync2, readdirSync as readdirSync2 } from "node:fs";
7848
+ import { join as join7, dirname as dirname4 } from "node:path";
7849
+ import { homedir as homedir7 } from "node:os";
7872
7850
  import { fileURLToPath as fileURLToPath2 } from "node:url";
7873
7851
  function bundledSkillsDir() {
7874
7852
  const here = fileURLToPath2(import.meta.url);
7875
- const pkgRoot = join6(dirname3(here), "..", "..");
7876
- const skillsDir = join6(pkgRoot, "skills");
7877
- return existsSync7(skillsDir) ? skillsDir : null;
7853
+ const pkgRoot = join7(dirname4(here), "..", "..");
7854
+ const skillsDir = join7(pkgRoot, "skills");
7855
+ return existsSync8(skillsDir) ? skillsDir : null;
7878
7856
  }
7879
7857
  async function uninstall() {
7880
7858
  let removed = 0;
7881
- if (existsSync7(PATHS.CLAUDE_JSON)) {
7859
+ if (existsSync8(PATHS.CLAUDE_JSON)) {
7882
7860
  try {
7883
- const raw = readFileSync5(PATHS.CLAUDE_JSON, "utf-8");
7861
+ const raw = readFileSync6(PATHS.CLAUDE_JSON, "utf-8");
7884
7862
  const config = JSON.parse(raw);
7885
7863
  const servers = config.mcpServers;
7886
7864
  if (servers && "claudemesh" in servers) {
7887
7865
  delete servers.claudemesh;
7888
- writeFileSync6(PATHS.CLAUDE_JSON, JSON.stringify(config, null, 2) + `
7866
+ writeFileSync7(PATHS.CLAUDE_JSON, JSON.stringify(config, null, 2) + `
7889
7867
  `, "utf-8");
7890
7868
  render.ok("removed MCP server", dim("~/.claude.json"));
7891
7869
  removed++;
7892
7870
  }
7893
7871
  } catch {}
7894
7872
  }
7895
- if (existsSync7(PATHS.CLAUDE_SETTINGS)) {
7873
+ if (existsSync8(PATHS.CLAUDE_SETTINGS)) {
7896
7874
  try {
7897
- const raw = readFileSync5(PATHS.CLAUDE_SETTINGS, "utf-8");
7875
+ const raw = readFileSync6(PATHS.CLAUDE_SETTINGS, "utf-8");
7898
7876
  const config = JSON.parse(raw);
7899
7877
  const hooks = config.hooks;
7900
7878
  if (hooks) {
@@ -7915,7 +7893,7 @@ async function uninstall() {
7915
7893
  }
7916
7894
  }
7917
7895
  if (removedHooks > 0) {
7918
- writeFileSync6(PATHS.CLAUDE_SETTINGS, JSON.stringify(config, null, 2) + `
7896
+ writeFileSync7(PATHS.CLAUDE_SETTINGS, JSON.stringify(config, null, 2) + `
7919
7897
  `, "utf-8");
7920
7898
  render.ok(`removed ${removedHooks} claudemesh hook${removedHooks === 1 ? "" : "s"}`, dim("settings.json"));
7921
7899
  removed++;
@@ -7930,8 +7908,8 @@ async function uninstall() {
7930
7908
  for (const entry of readdirSync2(src, { withFileTypes: true })) {
7931
7909
  if (!entry.isDirectory())
7932
7910
  continue;
7933
- const dst = join6(CLAUDE_SKILLS_ROOT2, entry.name);
7934
- if (existsSync7(dst)) {
7911
+ const dst = join7(CLAUDE_SKILLS_ROOT2, entry.name);
7912
+ if (existsSync8(dst)) {
7935
7913
  try {
7936
7914
  rmSync2(dst, { recursive: true, force: true });
7937
7915
  removedSkills.push(entry.name);
@@ -7955,7 +7933,7 @@ var init_uninstall = __esm(() => {
7955
7933
  init_render();
7956
7934
  init_styles();
7957
7935
  init_exit_codes();
7958
- CLAUDE_SKILLS_ROOT2 = join6(homedir6(), ".claude", "skills");
7936
+ CLAUDE_SKILLS_ROOT2 = join7(homedir7(), ".claude", "skills");
7959
7937
  });
7960
7938
 
7961
7939
  // src/commands/doctor.ts
@@ -7963,9 +7941,9 @@ var exports_doctor = {};
7963
7941
  __export(exports_doctor, {
7964
7942
  runDoctor: () => runDoctor
7965
7943
  });
7966
- import { existsSync as existsSync8, readFileSync as readFileSync6, statSync as statSync2 } from "node:fs";
7967
- import { homedir as homedir7, platform as platform6 } from "node:os";
7968
- import { join as join7 } from "node:path";
7944
+ import { existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync2 } from "node:fs";
7945
+ import { homedir as homedir8, platform as platform6 } from "node:os";
7946
+ import { join as join8 } from "node:path";
7969
7947
  import { spawnSync as spawnSync4 } from "node:child_process";
7970
7948
  function checkNode() {
7971
7949
  const major = Number(process.versions.node.split(".")[0]);
@@ -7989,8 +7967,8 @@ function checkClaudeOnPath() {
7989
7967
  };
7990
7968
  }
7991
7969
  function checkMcpRegistered() {
7992
- const claudeConfig = join7(homedir7(), ".claude.json");
7993
- if (!existsSync8(claudeConfig)) {
7970
+ const claudeConfig = join8(homedir8(), ".claude.json");
7971
+ if (!existsSync9(claudeConfig)) {
7994
7972
  return {
7995
7973
  name: "claudemesh MCP registered in ~/.claude.json",
7996
7974
  pass: false,
@@ -7998,7 +7976,7 @@ function checkMcpRegistered() {
7998
7976
  };
7999
7977
  }
8000
7978
  try {
8001
- const cfg = JSON.parse(readFileSync6(claudeConfig, "utf-8"));
7979
+ const cfg = JSON.parse(readFileSync7(claudeConfig, "utf-8"));
8002
7980
  const registered = Boolean(cfg.mcpServers?.["claudemesh"]);
8003
7981
  return {
8004
7982
  name: "claudemesh MCP registered in ~/.claude.json",
@@ -8015,8 +7993,8 @@ function checkMcpRegistered() {
8015
7993
  }
8016
7994
  }
8017
7995
  function checkHooksRegistered() {
8018
- const settings = join7(homedir7(), ".claude", "settings.json");
8019
- if (!existsSync8(settings)) {
7996
+ const settings = join8(homedir8(), ".claude", "settings.json");
7997
+ if (!existsSync9(settings)) {
8020
7998
  return {
8021
7999
  name: "Status hooks registered in ~/.claude/settings.json",
8022
8000
  pass: false,
@@ -8024,7 +8002,7 @@ function checkHooksRegistered() {
8024
8002
  };
8025
8003
  }
8026
8004
  try {
8027
- const raw = readFileSync6(settings, "utf-8");
8005
+ const raw = readFileSync7(settings, "utf-8");
8028
8006
  const has = raw.includes("claudemesh hook ");
8029
8007
  return {
8030
8008
  name: "Status hooks registered in ~/.claude/settings.json",
@@ -8041,7 +8019,7 @@ function checkHooksRegistered() {
8041
8019
  }
8042
8020
  function checkConfigFile() {
8043
8021
  const path = getConfigPath();
8044
- if (!existsSync8(path)) {
8022
+ if (!existsSync9(path)) {
8045
8023
  return {
8046
8024
  name: "~/.claudemesh/config.json exists and parses",
8047
8025
  pass: true,
@@ -8224,7 +8202,7 @@ var exports_status = {};
8224
8202
  __export(exports_status, {
8225
8203
  runStatus: () => runStatus
8226
8204
  });
8227
- import { statSync as statSync3, existsSync as existsSync9 } from "node:fs";
8205
+ import { statSync as statSync3, existsSync as existsSync10 } from "node:fs";
8228
8206
  import WebSocket2 from "ws";
8229
8207
  async function probeBroker(url, timeoutMs = 4000) {
8230
8208
  return new Promise((resolve2) => {
@@ -8254,7 +8232,7 @@ async function runStatus() {
8254
8232
  render.section(`status (v${VERSION})`);
8255
8233
  const configPath = getConfigPath();
8256
8234
  let configPermsNote = "missing";
8257
- if (existsSync9(configPath)) {
8235
+ if (existsSync10(configPath)) {
8258
8236
  const mode = (statSync3(configPath).mode & 511).toString(8).padStart(4, "0");
8259
8237
  configPermsNote = mode === "0600" ? `${mode}` : `${mode} — expected 0600`;
8260
8238
  }
@@ -8312,7 +8290,7 @@ var exports_sync = {};
8312
8290
  __export(exports_sync, {
8313
8291
  runSync: () => runSync
8314
8292
  });
8315
- import { createInterface as createInterface9 } from "node:readline";
8293
+ import { createInterface as createInterface10 } from "node:readline";
8316
8294
  import { hostname as hostname5 } from "node:os";
8317
8295
  async function runSync(args) {
8318
8296
  const useColor = !process.env.NO_COLOR && process.env.TERM !== "dumb" && process.stdout.isTTY;
@@ -8326,7 +8304,7 @@ async function runSync(args) {
8326
8304
  console.log(dim2(`Visit: ${url}`));
8327
8305
  await openBrowser(url);
8328
8306
  const manualPromise = new Promise((resolve2) => {
8329
- const rl = createInterface9({ input: process.stdin, output: process.stdout });
8307
+ const rl = createInterface10({ input: process.stdin, output: process.stdout });
8330
8308
  rl.question("Paste sync token (or wait for browser): ", (answer) => {
8331
8309
  rl.close();
8332
8310
  if (answer.trim())
@@ -8400,13 +8378,13 @@ var init_check_claude_binary = __esm(() => {
8400
8378
  });
8401
8379
 
8402
8380
  // src/services/health/check-mcp-registered.ts
8403
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "node:fs";
8381
+ import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
8404
8382
  function checkMcpRegistered2() {
8405
8383
  try {
8406
- if (!existsSync10(PATHS.CLAUDE_JSON)) {
8384
+ if (!existsSync11(PATHS.CLAUDE_JSON)) {
8407
8385
  return { name: "mcp-registered", ok: false, message: "~/.claude.json not found" };
8408
8386
  }
8409
- const raw = readFileSync7(PATHS.CLAUDE_JSON, "utf-8");
8387
+ const raw = readFileSync8(PATHS.CLAUDE_JSON, "utf-8");
8410
8388
  const config = JSON.parse(raw);
8411
8389
  if (config.mcpServers && "claudemesh" in config.mcpServers) {
8412
8390
  return { name: "mcp-registered", ok: true, message: "MCP server registered" };
@@ -8421,13 +8399,13 @@ var init_check_mcp_registered = __esm(() => {
8421
8399
  });
8422
8400
 
8423
8401
  // src/services/health/check-hooks-registered.ts
8424
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
8402
+ import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
8425
8403
  function checkHooksRegistered2() {
8426
8404
  try {
8427
- if (!existsSync11(PATHS.CLAUDE_SETTINGS)) {
8405
+ if (!existsSync12(PATHS.CLAUDE_SETTINGS)) {
8428
8406
  return { name: "hooks-registered", ok: false, message: "~/.claude/settings.json not found" };
8429
8407
  }
8430
- const raw = readFileSync8(PATHS.CLAUDE_SETTINGS, "utf-8");
8408
+ const raw = readFileSync9(PATHS.CLAUDE_SETTINGS, "utf-8");
8431
8409
  const config = JSON.parse(raw);
8432
8410
  if (config.hooks) {
8433
8411
  return { name: "hooks-registered", ok: true, message: "Hooks configured" };
@@ -8442,10 +8420,10 @@ var init_check_hooks_registered = __esm(() => {
8442
8420
  });
8443
8421
 
8444
8422
  // src/services/health/check-config-perms.ts
8445
- import { existsSync as existsSync12, statSync as statSync4 } from "node:fs";
8423
+ import { existsSync as existsSync13, statSync as statSync4 } from "node:fs";
8446
8424
  function checkConfigPerms() {
8447
8425
  const configFile = PATHS.CONFIG_FILE;
8448
- if (!existsSync12(configFile)) {
8426
+ if (!existsSync13(configFile)) {
8449
8427
  return { name: "config-perms", ok: true, message: "No config file yet (first run)" };
8450
8428
  }
8451
8429
  try {
@@ -8463,13 +8441,13 @@ var init_check_config_perms = __esm(() => {
8463
8441
  });
8464
8442
 
8465
8443
  // src/services/health/check-keypairs-valid.ts
8466
- import { existsSync as existsSync13, readFileSync as readFileSync9 } from "node:fs";
8444
+ import { existsSync as existsSync14, readFileSync as readFileSync10 } from "node:fs";
8467
8445
  function checkKeypairsValid() {
8468
- if (!existsSync13(PATHS.CONFIG_FILE)) {
8446
+ if (!existsSync14(PATHS.CONFIG_FILE)) {
8469
8447
  return { name: "keypairs-valid", ok: true, message: "No config (first run)" };
8470
8448
  }
8471
8449
  try {
8472
- const raw = readFileSync9(PATHS.CONFIG_FILE, "utf-8");
8450
+ const raw = readFileSync10(PATHS.CONFIG_FILE, "utf-8");
8473
8451
  const config = JSON.parse(raw);
8474
8452
  const meshes = config.meshes ?? [];
8475
8453
  if (meshes.length === 0) {
@@ -8949,19 +8927,19 @@ var exports_url_handler = {};
8949
8927
  __export(exports_url_handler, {
8950
8928
  runUrlHandler: () => runUrlHandler
8951
8929
  });
8952
- import { platform as platform7, homedir as homedir8 } from "node:os";
8953
- import { existsSync as existsSync14, mkdirSync as mkdirSync4, writeFileSync as writeFileSync7, rmSync as rmSync3, chmodSync as chmodSync4 } from "node:fs";
8954
- import { join as join8 } from "node:path";
8930
+ import { platform as platform7, homedir as homedir9 } from "node:os";
8931
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync8, rmSync as rmSync3, chmodSync as chmodSync4 } from "node:fs";
8932
+ import { join as join9 } from "node:path";
8955
8933
  import { spawnSync as spawnSync5 } from "node:child_process";
8956
8934
  function resolveClaudemeshBin() {
8957
8935
  return process.argv[1] ?? "claudemesh";
8958
8936
  }
8959
8937
  function installDarwin() {
8960
8938
  const binPath = resolveClaudemeshBin();
8961
- const appDir = join8(homedir8(), "Library", "Application Support", "claudemesh", "ClaudemeshHandler.app");
8962
- const contents = join8(appDir, "Contents");
8963
- const macOS = join8(contents, "MacOS");
8964
- mkdirSync4(macOS, { recursive: true });
8939
+ const appDir = join9(homedir9(), "Library", "Application Support", "claudemesh", "ClaudemeshHandler.app");
8940
+ const contents = join9(appDir, "Contents");
8941
+ const macOS = join9(contents, "MacOS");
8942
+ mkdirSync5(macOS, { recursive: true });
8965
8943
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
8966
8944
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
8967
8945
  <plist version="1.0">
@@ -8984,7 +8962,7 @@ function installDarwin() {
8984
8962
  </array>
8985
8963
  </dict>
8986
8964
  </plist>`;
8987
- writeFileSync7(join8(contents, "Info.plist"), plist);
8965
+ writeFileSync8(join9(contents, "Info.plist"), plist);
8988
8966
  const shim = `#!/bin/sh
8989
8967
  URL="$1"
8990
8968
  CODE=\${URL#claudemesh://}
@@ -8998,8 +8976,8 @@ tell application "Terminal"
8998
8976
  end tell
8999
8977
  EOF
9000
8978
  `;
9001
- const shimPath = join8(macOS, "open-url");
9002
- writeFileSync7(shimPath, shim);
8979
+ const shimPath = join9(macOS, "open-url");
8980
+ writeFileSync8(shimPath, shim);
9003
8981
  chmodSync4(shimPath, 493);
9004
8982
  const lsreg = spawnSync5("/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", ["-f", appDir], { encoding: "utf-8" });
9005
8983
  if (lsreg.status !== 0) {
@@ -9010,8 +8988,8 @@ EOF
9010
8988
  }
9011
8989
  function installLinux() {
9012
8990
  const binPath = resolveClaudemeshBin();
9013
- const appsDir = join8(homedir8(), ".local", "share", "applications");
9014
- mkdirSync4(appsDir, { recursive: true });
8991
+ const appsDir = join9(homedir9(), ".local", "share", "applications");
8992
+ mkdirSync5(appsDir, { recursive: true });
9015
8993
  const desktop = `[Desktop Entry]
9016
8994
  Type=Application
9017
8995
  Name=Claudemesh
@@ -9022,8 +9000,8 @@ Terminal=true
9022
9000
  MimeType=x-scheme-handler/claudemesh;
9023
9001
  NoDisplay=true
9024
9002
  `;
9025
- const desktopPath = join8(appsDir, "claudemesh.desktop");
9026
- writeFileSync7(desktopPath, desktop);
9003
+ const desktopPath = join9(appsDir, "claudemesh.desktop");
9004
+ writeFileSync8(desktopPath, desktop);
9027
9005
  const xdg1 = spawnSync5("xdg-mime", ["default", "claudemesh.desktop", "x-scheme-handler/claudemesh"], { encoding: "utf-8" });
9028
9006
  if (xdg1.status !== 0) {
9029
9007
  render.warn("xdg-mime not available — skipped mime default registration");
@@ -9045,8 +9023,8 @@ function installWindows() {
9045
9023
  `[HKEY_CURRENT_USER\\Software\\Classes\\claudemesh\\shell\\open\\command]`,
9046
9024
  `@="\\"${binPath.replace(/\\/g, "\\\\")}\\" \\"%1\\""`
9047
9025
  ];
9048
- const regPath = join8(homedir8(), "claudemesh-handler.reg");
9049
- writeFileSync7(regPath, lines.join(`\r
9026
+ const regPath = join9(homedir9(), "claudemesh-handler.reg");
9027
+ writeFileSync8(regPath, lines.join(`\r
9050
9028
  `));
9051
9029
  const res = spawnSync5("reg.exe", ["import", regPath], { encoding: "utf-8" });
9052
9030
  if (res.status !== 0) {
@@ -9057,15 +9035,15 @@ function installWindows() {
9057
9035
  return EXIT.SUCCESS;
9058
9036
  }
9059
9037
  function uninstallDarwin() {
9060
- const appDir = join8(homedir8(), "Library", "Application Support", "claudemesh", "ClaudemeshHandler.app");
9061
- if (existsSync14(appDir))
9038
+ const appDir = join9(homedir9(), "Library", "Application Support", "claudemesh", "ClaudemeshHandler.app");
9039
+ if (existsSync15(appDir))
9062
9040
  rmSync3(appDir, { recursive: true, force: true });
9063
9041
  render.ok("removed claudemesh:// handler on macOS");
9064
9042
  return EXIT.SUCCESS;
9065
9043
  }
9066
9044
  function uninstallLinux() {
9067
- const desktopPath = join8(homedir8(), ".local", "share", "applications", "claudemesh.desktop");
9068
- if (existsSync14(desktopPath))
9045
+ const desktopPath = join9(homedir9(), ".local", "share", "applications", "claudemesh.desktop");
9046
+ if (existsSync15(desktopPath))
9069
9047
  rmSync3(desktopPath, { force: true });
9070
9048
  render.ok("removed claudemesh:// handler on Linux");
9071
9049
  return EXIT.SUCCESS;
@@ -9110,9 +9088,9 @@ var exports_status_line = {};
9110
9088
  __export(exports_status_line, {
9111
9089
  runStatusLine: () => runStatusLine
9112
9090
  });
9113
- import { existsSync as existsSync15, readFileSync as readFileSync10 } from "node:fs";
9114
- import { join as join9 } from "node:path";
9115
- import { homedir as homedir9 } from "node:os";
9091
+ import { existsSync as existsSync16, readFileSync as readFileSync11 } from "node:fs";
9092
+ import { join as join10 } from "node:path";
9093
+ import { homedir as homedir10 } from "node:os";
9116
9094
  async function runStatusLine() {
9117
9095
  try {
9118
9096
  const config = readConfig();
@@ -9120,11 +9098,11 @@ async function runStatusLine() {
9120
9098
  process.stdout.write("◇ claudemesh (not joined)");
9121
9099
  return EXIT.SUCCESS;
9122
9100
  }
9123
- const cachePath = join9(homedir9(), ".claudemesh", "peer-cache.json");
9101
+ const cachePath = join10(homedir10(), ".claudemesh", "peer-cache.json");
9124
9102
  let cache = {};
9125
- if (existsSync15(cachePath)) {
9103
+ if (existsSync16(cachePath)) {
9126
9104
  try {
9127
- cache = JSON.parse(readFileSync10(cachePath, "utf-8"));
9105
+ cache = JSON.parse(readFileSync11(cachePath, "utf-8"));
9128
9106
  } catch {}
9129
9107
  }
9130
9108
  const pick = config.meshes[0];
@@ -9155,12 +9133,12 @@ __export(exports_backup, {
9155
9133
  runRestore: () => runRestore,
9156
9134
  runBackup: () => runBackup
9157
9135
  });
9158
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync16 } from "node:fs";
9159
- import { createInterface as createInterface10 } from "node:readline";
9136
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync17 } from "node:fs";
9137
+ import { createInterface as createInterface11 } from "node:readline";
9160
9138
  function readHidden(prompt5) {
9161
9139
  return new Promise((resolve2) => {
9162
9140
  process.stdout.write(prompt5);
9163
- const rl = createInterface10({ input: process.stdin, output: process.stdout, terminal: true });
9141
+ const rl = createInterface11({ input: process.stdin, output: process.stdout, terminal: true });
9164
9142
  const stdin = process.stdin;
9165
9143
  const wasRaw = Boolean(stdin.isRaw);
9166
9144
  if (stdin.isTTY) {
@@ -9197,11 +9175,11 @@ async function deriveKey(pass, salt, s) {
9197
9175
  }
9198
9176
  async function runBackup(outPath) {
9199
9177
  const configPath = getConfigPath();
9200
- if (!existsSync16(configPath)) {
9178
+ if (!existsSync17(configPath)) {
9201
9179
  console.error(" No config found — nothing to back up. Join a mesh first.");
9202
9180
  return EXIT.NOT_FOUND;
9203
9181
  }
9204
- const plaintext = readFileSync11(configPath);
9182
+ const plaintext = readFileSync12(configPath);
9205
9183
  const pass = await readHidden(" Passphrase (min 12 chars): ");
9206
9184
  if (pass.length < 12) {
9207
9185
  console.error(" ✗ Passphrase too short.");
@@ -9219,7 +9197,7 @@ async function runBackup(outPath) {
9219
9197
  const ciphertext = Buffer.from(s.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, null, null, nonce, key));
9220
9198
  const blob = Buffer.concat([MAGIC, salt, nonce, ciphertext]);
9221
9199
  const file = outPath ?? `claudemesh-backup-${new Date().toISOString().replace(/[:.]/g, "-")}.cmb`;
9222
- writeFileSync8(file, blob, { mode: 384 });
9200
+ writeFileSync9(file, blob, { mode: 384 });
9223
9201
  console.log(`
9224
9202
  ✓ Backup saved: ${file}`);
9225
9203
  console.log(` Size: ${blob.length} bytes. Guard the passphrase — there is no recovery.
@@ -9231,11 +9209,11 @@ async function runRestore(inPath) {
9231
9209
  console.error(" Usage: claudemesh restore <backup-file>");
9232
9210
  return EXIT.INVALID_ARGS;
9233
9211
  }
9234
- if (!existsSync16(inPath)) {
9212
+ if (!existsSync17(inPath)) {
9235
9213
  console.error(` ✗ File not found: ${inPath}`);
9236
9214
  return EXIT.NOT_FOUND;
9237
9215
  }
9238
- const blob = readFileSync11(inPath);
9216
+ const blob = readFileSync12(inPath);
9239
9217
  if (blob.length < 4 + 16 + 24 + 17 || !blob.subarray(0, 4).equals(MAGIC)) {
9240
9218
  console.error(" ✗ Not a claudemesh backup file (bad magic).");
9241
9219
  return EXIT.INVALID_ARGS;
@@ -9254,12 +9232,12 @@ async function runRestore(inPath) {
9254
9232
  return EXIT.INTERNAL_ERROR;
9255
9233
  }
9256
9234
  const configPath = getConfigPath();
9257
- if (existsSync16(configPath)) {
9235
+ if (existsSync17(configPath)) {
9258
9236
  const backupOld = `${configPath}.before-restore.${Date.now()}`;
9259
- writeFileSync8(backupOld, readFileSync11(configPath), { mode: 384 });
9237
+ writeFileSync9(backupOld, readFileSync12(configPath), { mode: 384 });
9260
9238
  console.log(` ↻ Existing config saved to ${backupOld}`);
9261
9239
  }
9262
- writeFileSync8(configPath, Buffer.from(plaintext), { mode: 384 });
9240
+ writeFileSync9(configPath, Buffer.from(plaintext), { mode: 384 });
9263
9241
  console.log(`
9264
9242
  ✓ Config restored to ${configPath}`);
9265
9243
  console.log(" Run `claudemesh list` to verify your meshes.\n");
@@ -9279,8 +9257,8 @@ __export(exports_upgrade, {
9279
9257
  runUpgrade: () => runUpgrade
9280
9258
  });
9281
9259
  import { spawnSync as spawnSync6 } from "node:child_process";
9282
- import { existsSync as existsSync17 } from "node:fs";
9283
- import { dirname as dirname4, join as join10, resolve as resolve2 } from "node:path";
9260
+ import { existsSync as existsSync18 } from "node:fs";
9261
+ import { dirname as dirname5, join as join11, resolve as resolve2 } from "node:path";
9284
9262
  async function latestVersion() {
9285
9263
  try {
9286
9264
  const res = await fetch(URLS.NPM_REGISTRY, { signal: AbortSignal.timeout(8000) });
@@ -9293,15 +9271,15 @@ async function latestVersion() {
9293
9271
  }
9294
9272
  }
9295
9273
  function findNpm() {
9296
- const portable = join10(process.env.HOME ?? "", ".claudemesh", "node", "bin", "npm");
9297
- if (existsSync17(portable)) {
9298
- return { npm: portable, prefix: join10(process.env.HOME ?? "", ".claudemesh") };
9274
+ const portable = join11(process.env.HOME ?? "", ".claudemesh", "node", "bin", "npm");
9275
+ if (existsSync18(portable)) {
9276
+ return { npm: portable, prefix: join11(process.env.HOME ?? "", ".claudemesh") };
9299
9277
  }
9300
9278
  let cur = resolve2(process.argv[1] ?? ".");
9301
9279
  for (let i = 0;i < 6; i++) {
9302
- cur = dirname4(cur);
9303
- const candidate = join10(cur, "bin", "npm");
9304
- if (existsSync17(candidate))
9280
+ cur = dirname5(cur);
9281
+ const candidate = join11(cur, "bin", "npm");
9282
+ if (existsSync18(candidate))
9305
9283
  return { npm: candidate };
9306
9284
  }
9307
9285
  return { npm: "npm" };
@@ -9363,9 +9341,9 @@ __export(exports_grants, {
9363
9341
  runBlock: () => runBlock,
9364
9342
  isAllowed: () => isAllowed
9365
9343
  });
9366
- import { existsSync as existsSync18, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync9 } from "node:fs";
9367
- import { homedir as homedir10 } from "node:os";
9368
- import { join as join11 } from "node:path";
9344
+ import { existsSync as existsSync19, mkdirSync as mkdirSync6, readFileSync as readFileSync13, writeFileSync as writeFileSync10 } from "node:fs";
9345
+ import { homedir as homedir11 } from "node:os";
9346
+ import { join as join12 } from "node:path";
9369
9347
  async function syncToBroker(meshSlug, grants) {
9370
9348
  const auth = getStoredToken();
9371
9349
  if (!auth)
@@ -9383,19 +9361,19 @@ async function syncToBroker(meshSlug, grants) {
9383
9361
  }
9384
9362
  }
9385
9363
  function readGrants() {
9386
- if (!existsSync18(GRANT_FILE))
9364
+ if (!existsSync19(GRANT_FILE))
9387
9365
  return {};
9388
9366
  try {
9389
- return JSON.parse(readFileSync12(GRANT_FILE, "utf-8"));
9367
+ return JSON.parse(readFileSync13(GRANT_FILE, "utf-8"));
9390
9368
  } catch {
9391
9369
  return {};
9392
9370
  }
9393
9371
  }
9394
9372
  function writeGrants(g) {
9395
- const dir = join11(homedir10(), ".claudemesh");
9396
- if (!existsSync18(dir))
9397
- mkdirSync5(dir, { recursive: true });
9398
- writeFileSync9(GRANT_FILE, JSON.stringify(g, null, 2), { mode: 384 });
9373
+ const dir = join12(homedir11(), ".claudemesh");
9374
+ if (!existsSync19(dir))
9375
+ mkdirSync6(dir, { recursive: true });
9376
+ writeFileSync10(GRANT_FILE, JSON.stringify(g, null, 2), { mode: 384 });
9399
9377
  }
9400
9378
  function resolveCaps(input) {
9401
9379
  if (input.includes("all"))
@@ -9551,7 +9529,7 @@ var init_grants = __esm(() => {
9551
9529
  BROKER_HTTP7 = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
9552
9530
  ALL_CAPS = ["read", "dm", "broadcast", "state-read", "state-write", "file-read"];
9553
9531
  DEFAULT_CAPS = ["read", "dm", "broadcast", "state-read"];
9554
- GRANT_FILE = join11(homedir10(), ".claudemesh", "grants.json");
9532
+ GRANT_FILE = join12(homedir11(), ".claudemesh", "grants.json");
9555
9533
  });
9556
9534
 
9557
9535
  // src/commands/profile.ts
@@ -10403,929 +10381,12 @@ var init_platform_actions = __esm(() => {
10403
10381
  // src/mcp/tools/definitions.ts
10404
10382
  var TOOLS;
10405
10383
  var init_definitions = __esm(() => {
10406
- TOOLS = [
10407
- {
10408
- name: "send_message",
10409
- description: "Send a message to a peer in one of your joined meshes. `to` can be a peer display name (resolved via list_peers), hex pubkey, @group, `#channel`, or `*` for broadcast. `priority` controls delivery: `now` bypasses busy gates, `next` waits for idle (default), `low` is pull-only.",
10410
- inputSchema: {
10411
- type: "object",
10412
- properties: {
10413
- to: {
10414
- oneOf: [
10415
- { type: "string", description: "Peer name, pubkey, @group" },
10416
- { type: "array", items: { type: "string" }, description: "Multiple targets" }
10417
- ],
10418
- description: "Single target or array of targets"
10419
- },
10420
- message: { type: "string", description: "Message text" },
10421
- priority: {
10422
- type: "string",
10423
- enum: ["now", "next", "low"],
10424
- description: "Delivery priority (default: next)"
10425
- }
10426
- },
10427
- required: ["to", "message"]
10428
- }
10429
- },
10430
- {
10431
- name: "list_peers",
10432
- description: "List peers across all joined meshes. Shows name, mesh, status (idle/working/dnd), and current summary.",
10433
- inputSchema: {
10434
- type: "object",
10435
- properties: {
10436
- mesh_slug: {
10437
- type: "string",
10438
- description: "Only list peers in this mesh (optional)"
10439
- }
10440
- }
10441
- }
10442
- },
10443
- {
10444
- name: "message_status",
10445
- description: "Check the delivery status of a sent message. Shows whether each recipient received it.",
10446
- inputSchema: {
10447
- type: "object",
10448
- properties: {
10449
- id: {
10450
- type: "string",
10451
- description: "Message ID (returned by send_message)"
10452
- }
10453
- },
10454
- required: ["id"]
10455
- }
10456
- },
10457
- {
10458
- name: "check_messages",
10459
- description: "Pull any undelivered messages from the broker. Normally messages arrive via push; use this to drain the queue after being offline.",
10460
- inputSchema: { type: "object", properties: {} }
10461
- },
10462
- {
10463
- name: "set_summary",
10464
- description: "Set a 1–2 sentence summary of what you're working on. Visible to other peers.",
10465
- inputSchema: {
10466
- type: "object",
10467
- properties: {
10468
- summary: { type: "string", description: "1-2 sentence summary" }
10469
- },
10470
- required: ["summary"]
10471
- }
10472
- },
10473
- {
10474
- name: "set_status",
10475
- description: "Manually override your status. `dnd` blocks everything except `now`-priority messages.",
10476
- inputSchema: {
10477
- type: "object",
10478
- properties: {
10479
- status: {
10480
- type: "string",
10481
- enum: ["idle", "working", "dnd"],
10482
- description: "Your status"
10483
- }
10484
- },
10485
- required: ["status"]
10486
- }
10487
- },
10488
- {
10489
- name: "set_visible",
10490
- description: "Control your visibility in the mesh. When hidden, you won't appear in list_peers and won't receive broadcasts — but direct messages still reach you.",
10491
- inputSchema: {
10492
- type: "object",
10493
- properties: {
10494
- visible: {
10495
- type: "boolean",
10496
- description: "true to be visible (default), false to hide"
10497
- }
10498
- },
10499
- required: ["visible"]
10500
- }
10501
- },
10502
- {
10503
- name: "set_profile",
10504
- description: "Set your public profile — what other peers see about you. Avatar (emoji), title, bio, and capabilities list.",
10505
- inputSchema: {
10506
- type: "object",
10507
- properties: {
10508
- avatar: {
10509
- type: "string",
10510
- description: "Emoji or URL for your avatar"
10511
- },
10512
- title: {
10513
- type: "string",
10514
- description: "Short role label (e.g. 'Frontend Lead', 'DevOps')"
10515
- },
10516
- bio: {
10517
- type: "string",
10518
- description: "One-liner about yourself"
10519
- },
10520
- capabilities: {
10521
- type: "array",
10522
- items: { type: "string" },
10523
- description: "What you can help with"
10524
- }
10525
- }
10526
- }
10527
- },
10528
- {
10529
- name: "join_group",
10530
- description: "Join a group with an optional role. Other peers see your group membership in list_peers.",
10531
- inputSchema: {
10532
- type: "object",
10533
- properties: {
10534
- name: { type: "string", description: "Group name (without @)" },
10535
- role: {
10536
- type: "string",
10537
- description: "Your role in the group (e.g. lead, member, observer)"
10538
- }
10539
- },
10540
- required: ["name"]
10541
- }
10542
- },
10543
- {
10544
- name: "leave_group",
10545
- description: "Leave a group.",
10546
- inputSchema: {
10547
- type: "object",
10548
- properties: {
10549
- name: { type: "string", description: "Group name (without @)" }
10550
- },
10551
- required: ["name"]
10552
- }
10553
- },
10554
- {
10555
- name: "set_state",
10556
- description: "Set a shared state value visible to all peers in the mesh. Pushes a change notification.",
10557
- inputSchema: {
10558
- type: "object",
10559
- properties: {
10560
- key: { type: "string" },
10561
- value: { description: "Any JSON value" }
10562
- },
10563
- required: ["key", "value"]
10564
- }
10565
- },
10566
- {
10567
- name: "get_state",
10568
- description: "Read a shared state value.",
10569
- inputSchema: {
10570
- type: "object",
10571
- properties: {
10572
- key: { type: "string" }
10573
- },
10574
- required: ["key"]
10575
- }
10576
- },
10577
- {
10578
- name: "list_state",
10579
- description: "List all shared state keys and values in the mesh.",
10580
- inputSchema: { type: "object", properties: {} }
10581
- },
10582
- {
10583
- name: "remember",
10584
- description: "Store persistent knowledge in the mesh's shared memory. Survives across sessions.",
10585
- inputSchema: {
10586
- type: "object",
10587
- properties: {
10588
- content: {
10589
- type: "string",
10590
- description: "The knowledge to remember"
10591
- },
10592
- tags: {
10593
- type: "array",
10594
- items: { type: "string" },
10595
- description: "Optional categorization tags"
10596
- }
10597
- },
10598
- required: ["content"]
10599
- }
10600
- },
10601
- {
10602
- name: "recall",
10603
- description: "Search the mesh's shared memory by relevance.",
10604
- inputSchema: {
10605
- type: "object",
10606
- properties: {
10607
- query: { type: "string", description: "Search query" }
10608
- },
10609
- required: ["query"]
10610
- }
10611
- },
10612
- {
10613
- name: "forget",
10614
- description: "Remove a memory from the mesh's shared knowledge.",
10615
- inputSchema: {
10616
- type: "object",
10617
- properties: {
10618
- id: { type: "string", description: "Memory ID to forget" }
10619
- },
10620
- required: ["id"]
10621
- }
10622
- },
10623
- {
10624
- name: "share_file",
10625
- description: "Share a persistent file with the mesh. All current and future peers can access it. If `to` is specified, the file is E2E encrypted and only accessible to that peer (and you).",
10626
- inputSchema: {
10627
- type: "object",
10628
- properties: {
10629
- path: { type: "string", description: "Local file path to share" },
10630
- name: {
10631
- type: "string",
10632
- description: "Display name (defaults to filename)"
10633
- },
10634
- tags: {
10635
- type: "array",
10636
- items: { type: "string" },
10637
- description: "Tags for categorization"
10638
- },
10639
- to: {
10640
- type: "string",
10641
- description: "Peer display name or pubkey hex — if set, file is E2E encrypted for this peer only"
10642
- }
10643
- },
10644
- required: ["path"]
10645
- }
10646
- },
10647
- {
10648
- name: "get_file",
10649
- description: "Download a shared file to a local path.",
10650
- inputSchema: {
10651
- type: "object",
10652
- properties: {
10653
- id: { type: "string", description: "File ID" },
10654
- save_to: {
10655
- type: "string",
10656
- description: "Local path to save the file"
10657
- }
10658
- },
10659
- required: ["id", "save_to"]
10660
- }
10661
- },
10662
- {
10663
- name: "list_files",
10664
- description: "List files shared in the mesh.",
10665
- inputSchema: {
10666
- type: "object",
10667
- properties: {
10668
- query: { type: "string", description: "Search by name or tags" },
10669
- from: { type: "string", description: "Filter by uploader name" }
10670
- }
10671
- }
10672
- },
10673
- {
10674
- name: "file_status",
10675
- description: "Check who has accessed a shared file.",
10676
- inputSchema: {
10677
- type: "object",
10678
- properties: {
10679
- id: { type: "string", description: "File ID" }
10680
- },
10681
- required: ["id"]
10682
- }
10683
- },
10684
- {
10685
- name: "delete_file",
10686
- description: "Remove a shared file from the mesh.",
10687
- inputSchema: {
10688
- type: "object",
10689
- properties: {
10690
- id: { type: "string", description: "File ID" }
10691
- },
10692
- required: ["id"]
10693
- }
10694
- },
10695
- {
10696
- name: "grant_file_access",
10697
- description: "Grant a peer access to an E2E encrypted file you shared. You must be the owner.",
10698
- inputSchema: {
10699
- type: "object",
10700
- properties: {
10701
- fileId: { type: "string", description: "File ID" },
10702
- to: { type: "string", description: "Peer display name or pubkey hex to grant access to" }
10703
- },
10704
- required: ["fileId", "to"]
10705
- }
10706
- },
10707
- {
10708
- name: "vector_store",
10709
- description: "Store an embedding in a per-mesh Qdrant collection. Auto-creates the collection on first use.",
10710
- inputSchema: {
10711
- type: "object",
10712
- properties: {
10713
- collection: { type: "string", description: "Collection name" },
10714
- text: { type: "string", description: "Text to embed and store" },
10715
- metadata: {
10716
- type: "object",
10717
- description: "Optional metadata to attach"
10718
- }
10719
- },
10720
- required: ["collection", "text"]
10721
- }
10722
- },
10723
- {
10724
- name: "vector_search",
10725
- description: "Semantic search over stored embeddings in a collection.",
10726
- inputSchema: {
10727
- type: "object",
10728
- properties: {
10729
- collection: { type: "string", description: "Collection name" },
10730
- query: { type: "string", description: "Search query text" },
10731
- limit: {
10732
- type: "number",
10733
- description: "Max results (default: 10)"
10734
- }
10735
- },
10736
- required: ["collection", "query"]
10737
- }
10738
- },
10739
- {
10740
- name: "vector_delete",
10741
- description: "Remove an embedding from a collection.",
10742
- inputSchema: {
10743
- type: "object",
10744
- properties: {
10745
- collection: { type: "string", description: "Collection name" },
10746
- id: { type: "string", description: "Embedding ID to delete" }
10747
- },
10748
- required: ["collection", "id"]
10749
- }
10750
- },
10751
- {
10752
- name: "list_collections",
10753
- description: "List vector collections in this mesh.",
10754
- inputSchema: { type: "object", properties: {} }
10755
- },
10756
- {
10757
- name: "graph_query",
10758
- description: "Run a read-only Cypher query on the per-mesh Neo4j database.",
10759
- inputSchema: {
10760
- type: "object",
10761
- properties: {
10762
- cypher: { type: "string", description: "Cypher MATCH query" }
10763
- },
10764
- required: ["cypher"]
10765
- }
10766
- },
10767
- {
10768
- name: "graph_execute",
10769
- description: "Run a write Cypher query (CREATE, MERGE, DELETE) on the per-mesh Neo4j database.",
10770
- inputSchema: {
10771
- type: "object",
10772
- properties: {
10773
- cypher: { type: "string", description: "Cypher write query" }
10774
- },
10775
- required: ["cypher"]
10776
- }
10777
- },
10778
- {
10779
- name: "mesh_query",
10780
- description: "Run a SELECT query on the per-mesh shared database.",
10781
- inputSchema: {
10782
- type: "object",
10783
- properties: {
10784
- sql: { type: "string", description: "SQL SELECT query" }
10785
- },
10786
- required: ["sql"]
10787
- }
10788
- },
10789
- {
10790
- name: "mesh_execute",
10791
- description: "Run DDL/DML on the per-mesh database (CREATE TABLE, INSERT, UPDATE, DELETE).",
10792
- inputSchema: {
10793
- type: "object",
10794
- properties: {
10795
- sql: { type: "string", description: "SQL statement" }
10796
- },
10797
- required: ["sql"]
10798
- }
10799
- },
10800
- {
10801
- name: "mesh_schema",
10802
- description: "List tables and columns in the per-mesh shared database.",
10803
- inputSchema: { type: "object", properties: {} }
10804
- },
10805
- {
10806
- name: "create_stream",
10807
- description: "Create a real-time data stream in the mesh.",
10808
- inputSchema: {
10809
- type: "object",
10810
- properties: {
10811
- name: { type: "string", description: "Stream name" }
10812
- },
10813
- required: ["name"]
10814
- }
10815
- },
10816
- {
10817
- name: "publish",
10818
- description: "Push data to a stream. Subscribers receive it in real-time.",
10819
- inputSchema: {
10820
- type: "object",
10821
- properties: {
10822
- stream: { type: "string", description: "Stream name" },
10823
- data: { description: "Any JSON data to publish" }
10824
- },
10825
- required: ["stream", "data"]
10826
- }
10827
- },
10828
- {
10829
- name: "subscribe",
10830
- description: "Subscribe to a stream. Data pushes arrive as channel notifications.",
10831
- inputSchema: {
10832
- type: "object",
10833
- properties: {
10834
- stream: { type: "string", description: "Stream name" }
10835
- },
10836
- required: ["stream"]
10837
- }
10838
- },
10839
- {
10840
- name: "list_streams",
10841
- description: "List active streams in the mesh.",
10842
- inputSchema: { type: "object", properties: {} }
10843
- },
10844
- {
10845
- name: "share_context",
10846
- description: "Share your session understanding with the mesh. Call after exploring a codebase area.",
10847
- inputSchema: {
10848
- type: "object",
10849
- properties: {
10850
- summary: {
10851
- type: "string",
10852
- description: "Summary of what you explored/learned"
10853
- },
10854
- files_read: {
10855
- type: "array",
10856
- items: { type: "string" },
10857
- description: "File paths you read"
10858
- },
10859
- key_findings: {
10860
- type: "array",
10861
- items: { type: "string" },
10862
- description: "Key findings or insights"
10863
- },
10864
- tags: {
10865
- type: "array",
10866
- items: { type: "string" },
10867
- description: "Tags for categorization"
10868
- }
10869
- },
10870
- required: ["summary"]
10871
- }
10872
- },
10873
- {
10874
- name: "get_context",
10875
- description: "Find context from peers who explored an area. Check before re-reading files another peer already analyzed.",
10876
- inputSchema: {
10877
- type: "object",
10878
- properties: {
10879
- query: {
10880
- type: "string",
10881
- description: "Search query (file path, topic, etc.)"
10882
- }
10883
- },
10884
- required: ["query"]
10885
- }
10886
- },
10887
- {
10888
- name: "list_contexts",
10889
- description: "See what all peers currently know about the codebase.",
10890
- inputSchema: { type: "object", properties: {} }
10891
- },
10892
- {
10893
- name: "create_task",
10894
- description: "Create a work item for the mesh.",
10895
- inputSchema: {
10896
- type: "object",
10897
- properties: {
10898
- title: { type: "string", description: "Task title" },
10899
- assignee: {
10900
- type: "string",
10901
- description: "Peer name to assign (optional)"
10902
- },
10903
- priority: {
10904
- type: "string",
10905
- enum: ["low", "normal", "high", "urgent"],
10906
- description: "Priority level (default: normal)"
10907
- },
10908
- tags: {
10909
- type: "array",
10910
- items: { type: "string" },
10911
- description: "Tags for categorization"
10912
- }
10913
- },
10914
- required: ["title"]
10915
- }
10916
- },
10917
- {
10918
- name: "claim_task",
10919
- description: "Claim an unclaimed task to take ownership.",
10920
- inputSchema: {
10921
- type: "object",
10922
- properties: {
10923
- id: { type: "string", description: "Task ID" }
10924
- },
10925
- required: ["id"]
10926
- }
10927
- },
10928
- {
10929
- name: "complete_task",
10930
- description: "Mark a task as done with an optional result summary.",
10931
- inputSchema: {
10932
- type: "object",
10933
- properties: {
10934
- id: { type: "string", description: "Task ID" },
10935
- result: {
10936
- type: "string",
10937
- description: "Summary of what was done"
10938
- }
10939
- },
10940
- required: ["id"]
10941
- }
10942
- },
10943
- {
10944
- name: "list_tasks",
10945
- description: "List tasks filtered by status and/or assignee.",
10946
- inputSchema: {
10947
- type: "object",
10948
- properties: {
10949
- status: {
10950
- type: "string",
10951
- enum: ["open", "claimed", "completed"],
10952
- description: "Filter by status"
10953
- },
10954
- assignee: {
10955
- type: "string",
10956
- description: "Filter by assignee name"
10957
- }
10958
- }
10959
- }
10960
- },
10961
- {
10962
- name: "schedule_reminder",
10963
- description: "Schedule a one-shot or recurring message. Without `to`, it fires back to yourself (a self-reminder). With `to`, it delivers to a peer, @group, or * broadcast. For one-shot, provide `deliver_at` or `in_seconds`. For recurring, provide `cron` (standard 5-field expression). The broker persists schedules to the database — they survive restarts. Receivers see `subtype: reminder` in the push envelope.",
10964
- inputSchema: {
10965
- type: "object",
10966
- properties: {
10967
- message: { type: "string", description: "Message or reminder text" },
10968
- deliver_at: { type: "number", description: "Unix timestamp (ms) when to deliver (one-shot)" },
10969
- in_seconds: { type: "number", description: "Alternative to deliver_at: fire after N seconds (one-shot)" },
10970
- cron: { type: "string", description: "Cron expression for recurring reminders (e.g. '0 */2 * * *' for every 2 hours, '30 9 * * 1-5' for 9:30 weekdays)" },
10971
- to: {
10972
- type: "string",
10973
- description: "Recipient: display name, pubkey hex, @group, or * (omit for self-reminder)"
10974
- }
10975
- },
10976
- required: ["message"]
10977
- }
10978
- },
10979
- {
10980
- name: "list_scheduled",
10981
- description: "List all your pending scheduled messages: id, recipient, preview, and delivery time.",
10982
- inputSchema: { type: "object", properties: {} }
10983
- },
10984
- {
10985
- name: "cancel_scheduled",
10986
- description: "Cancel a pending scheduled message before it fires.",
10987
- inputSchema: {
10988
- type: "object",
10989
- properties: {
10990
- id: { type: "string", description: "Scheduled message ID" }
10991
- },
10992
- required: ["id"]
10993
- }
10994
- },
10995
- {
10996
- name: "mesh_info",
10997
- description: "Get a complete overview of the mesh: peers, groups, state, memory, files, tasks, streams, tables. Call on session start for full situational awareness.",
10998
- inputSchema: { type: "object", properties: {} }
10999
- },
11000
- {
11001
- name: "mesh_stats",
11002
- description: "View resource usage stats for all peers: messages sent/received, tool calls, uptime, errors.",
11003
- inputSchema: { type: "object", properties: {} }
11004
- },
11005
- {
11006
- name: "mesh_mcp_register",
11007
- description: "Register an MCP server with the mesh. Other peers can invoke its tools through the mesh without restarting their sessions. Provide the server name, description, and full tool definitions.",
11008
- inputSchema: {
11009
- type: "object",
11010
- properties: {
11011
- server_name: { type: "string", description: "Unique name for the MCP server (e.g. 'github', 'jira')" },
11012
- description: { type: "string", description: "What this MCP server does" },
11013
- tools: {
11014
- type: "array",
11015
- items: {
11016
- type: "object",
11017
- properties: {
11018
- name: { type: "string" },
11019
- description: { type: "string" },
11020
- inputSchema: { type: "object", description: "JSON Schema for tool arguments" }
11021
- },
11022
- required: ["name", "description", "inputSchema"]
11023
- },
11024
- description: "Tool definitions to expose"
11025
- },
11026
- persistent: {
11027
- type: "boolean",
11028
- description: "If true, registration survives peer disconnect. Other peers see it as 'offline' until you reconnect. Default: false"
11029
- }
11030
- },
11031
- required: ["server_name", "description", "tools"]
11032
- }
11033
- },
11034
- {
11035
- name: "mesh_mcp_list",
11036
- description: "List MCP servers available in the mesh with their tools. Shows which peer hosts each server.",
11037
- inputSchema: { type: "object", properties: {} }
11038
- },
11039
- {
11040
- name: "mesh_tool_call",
11041
- description: "Call a tool on a mesh-registered MCP server. Route: you -> broker -> hosting peer -> execute -> result back. Timeout: 30s.",
11042
- inputSchema: {
11043
- type: "object",
11044
- properties: {
11045
- server_name: { type: "string", description: "Name of the MCP server" },
11046
- tool_name: { type: "string", description: "Name of the tool to call" },
11047
- args: { type: "object", description: "Tool arguments (JSON object)" }
11048
- },
11049
- required: ["server_name", "tool_name"]
11050
- }
11051
- },
11052
- {
11053
- name: "mesh_mcp_remove",
11054
- description: "Unregister an MCP server you previously registered with the mesh.",
11055
- inputSchema: {
11056
- type: "object",
11057
- properties: {
11058
- server_name: { type: "string", description: "Name of the MCP server to remove" }
11059
- },
11060
- required: ["server_name"]
11061
- }
11062
- },
11063
- {
11064
- name: "mesh_set_clock",
11065
- description: "Set the simulation clock speed. x1 = real-time, x10 = 10x faster, x100 = 100x. Peers receive heartbeat ticks at the simulated rate.",
11066
- inputSchema: {
11067
- type: "object",
11068
- properties: {
11069
- speed: {
11070
- type: "number",
11071
- description: "Speed multiplier (1-100). x1 = tick every 60s, x10 = tick every 6s, x100 = tick every 600ms."
11072
- }
11073
- },
11074
- required: ["speed"]
11075
- }
11076
- },
11077
- {
11078
- name: "mesh_pause_clock",
11079
- description: "Pause the simulation clock. Ticks stop until resumed.",
11080
- inputSchema: { type: "object", properties: {} }
11081
- },
11082
- {
11083
- name: "mesh_resume_clock",
11084
- description: "Resume a paused simulation clock.",
11085
- inputSchema: { type: "object", properties: {} }
11086
- },
11087
- {
11088
- name: "mesh_clock",
11089
- description: "Get current simulation clock status: speed, tick count, simulated time.",
11090
- inputSchema: { type: "object", properties: {} }
11091
- },
11092
- {
11093
- name: "share_skill",
11094
- description: "Publish a reusable skill to the mesh. Other peers can discover and load it as a slash command. If a skill with the same name exists, it is updated. Skills are automatically exposed as MCP prompts and skill:// resources for native Claude Code integration.",
11095
- inputSchema: {
11096
- type: "object",
11097
- properties: {
11098
- name: { type: "string", description: "Unique skill name (e.g. 'code-review', 'deploy-checklist'). Becomes the slash command name." },
11099
- description: { type: "string", description: "Short description of what the skill does" },
11100
- instructions: { type: "string", description: "Full instructions/prompt markdown. Can include frontmatter (---) block." },
11101
- tags: {
11102
- type: "array",
11103
- items: { type: "string" },
11104
- description: "Tags for discoverability"
11105
- },
11106
- when_to_use: { type: "string", description: "Detailed description of when Claude should auto-invoke this skill" },
11107
- allowed_tools: {
11108
- type: "array",
11109
- items: { type: "string" },
11110
- description: "Tool names this skill is allowed to use (e.g. ['Bash', 'Read', 'Edit'])"
11111
- },
11112
- model: { type: "string", description: "Model override (e.g. 'sonnet', 'opus', 'haiku')" },
11113
- context: { type: "string", enum: ["inline", "fork"], description: "Execution context: 'inline' (default) or 'fork' (sub-agent)" },
11114
- agent: { type: "string", description: "Agent type when forked (e.g. 'general-purpose')" },
11115
- user_invocable: { type: "boolean", description: "Whether users can invoke via /skill-name (default: true)" },
11116
- argument_hint: { type: "string", description: "Hint text for arguments (e.g. '<file-path>')" }
11117
- },
11118
- required: ["name", "description", "instructions"]
11119
- }
11120
- },
11121
- {
11122
- name: "get_skill",
11123
- description: "Load a skill's full instructions by name. Use to acquire capabilities shared by other peers.",
11124
- inputSchema: {
11125
- type: "object",
11126
- properties: {
11127
- name: { type: "string", description: "Skill name to load" }
11128
- },
11129
- required: ["name"]
11130
- }
11131
- },
11132
- {
11133
- name: "list_skills",
11134
- description: "Browse available skills in the mesh. Optionally filter by keyword across name, description, and tags.",
11135
- inputSchema: {
11136
- type: "object",
11137
- properties: {
11138
- query: { type: "string", description: "Search keyword (optional)" }
11139
- }
11140
- }
11141
- },
11142
- {
11143
- name: "remove_skill",
11144
- description: "Remove a skill you published from the mesh.",
11145
- inputSchema: {
11146
- type: "object",
11147
- properties: {
11148
- name: { type: "string", description: "Skill name to remove" }
11149
- },
11150
- required: ["name"]
11151
- }
11152
- },
11153
- {
11154
- name: "ping_mesh",
11155
- description: "Send test messages through the full pipeline and measure round-trip timing per priority. Diagnoses push delivery issues.",
11156
- inputSchema: {
11157
- type: "object",
11158
- properties: {
11159
- priorities: {
11160
- type: "array",
11161
- items: { type: "string", enum: ["now", "next", "low"] },
11162
- description: 'Priorities to test (default: ["now", "next"])'
11163
- }
11164
- }
11165
- }
11166
- },
11167
- {
11168
- name: "read_peer_file",
11169
- description: "Read a file from another peer's project. Specify the peer (by name) and the file path relative to their working directory. The peer must be online and sharing files. Max file size: 1MB.",
11170
- inputSchema: {
11171
- type: "object",
11172
- properties: {
11173
- peer: { type: "string", description: "Peer display name or pubkey" },
11174
- path: { type: "string", description: "File path relative to peer's working directory" }
11175
- },
11176
- required: ["peer", "path"]
11177
- }
11178
- },
11179
- {
11180
- name: "list_peer_files",
11181
- description: "List files in a peer's shared directory. Returns a tree of file names (not contents). The peer must be online and sharing files.",
11182
- inputSchema: {
11183
- type: "object",
11184
- properties: {
11185
- peer: { type: "string", description: "Peer display name or pubkey" },
11186
- path: { type: "string", description: "Directory path relative to peer's cwd (default: root)" },
11187
- pattern: { type: "string", description: "Glob-like filter pattern (e.g. '*.ts', 'src/*')" }
11188
- },
11189
- required: ["peer"]
11190
- }
11191
- },
11192
- {
11193
- name: "create_webhook",
11194
- description: "Create an inbound webhook. Returns a URL that external services (GitHub, CI/CD, monitoring) can POST to — the payload becomes a mesh message to all peers.",
11195
- inputSchema: {
11196
- type: "object",
11197
- properties: {
11198
- name: {
11199
- type: "string",
11200
- description: "Webhook name (e.g. 'github-ci', 'datadog-alerts')"
11201
- }
11202
- },
11203
- required: ["name"]
11204
- }
11205
- },
11206
- {
11207
- name: "list_webhooks",
11208
- description: "List active webhooks for this mesh.",
11209
- inputSchema: { type: "object", properties: {} }
11210
- },
11211
- {
11212
- name: "delete_webhook",
11213
- description: "Deactivate a webhook.",
11214
- inputSchema: {
11215
- type: "object",
11216
- properties: {
11217
- name: { type: "string", description: "Webhook name to deactivate" }
11218
- },
11219
- required: ["name"]
11220
- }
11221
- },
11222
- {
11223
- name: "mesh_mcp_deploy",
11224
- description: "Deploy an MCP server to the mesh from a zip file or git repo. Runs on the broker VPS, persists across peer sessions. Default scope: private (only you).",
11225
- inputSchema: {
11226
- type: "object",
11227
- properties: {
11228
- server_name: { type: "string", description: "Unique name for the server in this mesh" },
11229
- file_id: { type: "string", description: "File ID of uploaded zip (from share_file)" },
11230
- git_url: { type: "string", description: "Git repo URL" },
11231
- git_branch: { type: "string", description: "Branch to clone (default: main)" },
11232
- npx_package: { type: "string", description: "npm package name to run via npx (e.g. @upstash/context7-mcp)" },
11233
- env: { type: "object", description: "Environment variables. Use $vault:<key> for vault secrets." },
11234
- runtime: { type: "string", enum: ["node", "python", "bun"], description: "Runtime (auto-detected if omitted)" },
11235
- memory_mb: { type: "number", description: "Memory limit in MB (default: 256)" },
11236
- network_allow: { type: "array", items: { type: "string" }, description: "Allowed outbound hosts (default: none)" },
11237
- scope: { description: "Visibility: 'peer' (default), 'mesh', or {group/groups/role/peers}" }
11238
- },
11239
- required: ["server_name"]
11240
- }
11241
- },
11242
- {
11243
- name: "mesh_mcp_undeploy",
11244
- description: "Stop and remove a managed MCP server from the mesh.",
11245
- inputSchema: { type: "object", properties: { server_name: { type: "string" } }, required: ["server_name"] }
11246
- },
11247
- {
11248
- name: "mesh_mcp_update",
11249
- description: "Pull latest code and restart a git-sourced MCP server.",
11250
- inputSchema: { type: "object", properties: { server_name: { type: "string" } }, required: ["server_name"] }
11251
- },
11252
- {
11253
- name: "mesh_mcp_logs",
11254
- description: "View recent logs from a managed MCP server.",
11255
- inputSchema: { type: "object", properties: { server_name: { type: "string" }, lines: { type: "number", description: "Lines (default: 50, max: 1000)" } }, required: ["server_name"] }
11256
- },
11257
- {
11258
- name: "mesh_mcp_scope",
11259
- description: "Get or set the visibility scope of a deployed MCP server.",
11260
- inputSchema: { type: "object", properties: { server_name: { type: "string" }, scope: { description: "New scope to set. Omit to read current." } }, required: ["server_name"] }
11261
- },
11262
- {
11263
- name: "mesh_mcp_schema",
11264
- description: "Inspect tool schemas for a deployed MCP server.",
11265
- inputSchema: { type: "object", properties: { server_name: { type: "string" }, tool_name: { type: "string", description: "Specific tool (omit for all)" } }, required: ["server_name"] }
11266
- },
11267
- {
11268
- name: "mesh_mcp_catalog",
11269
- description: "List all deployed services in the mesh with status, scope, and tool count.",
11270
- inputSchema: { type: "object", properties: {} }
11271
- },
11272
- {
11273
- name: "mesh_skill_deploy",
11274
- description: "Deploy a multi-file skill bundle from a zip or git repo.",
11275
- inputSchema: { type: "object", properties: { file_id: { type: "string" }, git_url: { type: "string" }, git_branch: { type: "string" } } }
11276
- },
11277
- {
11278
- name: "vault_set",
11279
- description: "Store an encrypted credential in your vault. Reference in mesh_mcp_deploy with $vault:<key>.",
11280
- inputSchema: { type: "object", properties: { key: { type: "string" }, value: { type: "string", description: "Secret value or local file path (for type=file)" }, type: { type: "string", enum: ["env", "file"] }, mount_path: { type: "string" }, description: { type: "string" } }, required: ["key", "value"] }
11281
- },
11282
- {
11283
- name: "vault_list",
11284
- description: "List your vault entries (keys and metadata only, no secret values).",
11285
- inputSchema: { type: "object", properties: {} }
11286
- },
11287
- {
11288
- name: "vault_delete",
11289
- description: "Remove a credential from your vault.",
11290
- inputSchema: { type: "object", properties: { key: { type: "string" } }, required: ["key"] }
11291
- },
11292
- {
11293
- name: "mesh_watch",
11294
- description: "Watch a URL for changes. The broker polls it at the given interval and notifies you when the response changes. Works with any URL — websites (hash mode), JSON APIs (json mode), or status codes (status mode).",
11295
- inputSchema: {
11296
- type: "object",
11297
- properties: {
11298
- url: { type: "string", description: "URL to watch" },
11299
- mode: { type: "string", enum: ["hash", "json", "status"], description: "Detection mode: hash (SHA-256 of body), json (extract jsonpath value), status (HTTP status code). Default: hash" },
11300
- extract: { type: "string", description: "For json mode: dot path to extract (e.g. 'status' or 'data.deployments[0].status')" },
11301
- interval: { type: "number", description: "Poll interval in seconds (min: 5, default: 30)" },
11302
- notify_on: { type: "string", description: "When to notify: 'change' (default), 'match:<value>', 'not_match:<value>'" },
11303
- headers: { type: "object", description: "Optional HTTP headers (e.g. for auth)" },
11304
- label: { type: "string", description: "Human-readable label for this watch" }
11305
- },
11306
- required: ["url"]
11307
- }
11308
- },
11309
- {
11310
- name: "mesh_unwatch",
11311
- description: "Stop watching a URL.",
11312
- inputSchema: {
11313
- type: "object",
11314
- properties: { watch_id: { type: "string" } },
11315
- required: ["watch_id"]
11316
- }
11317
- },
11318
- {
11319
- name: "mesh_watches",
11320
- description: "List your active URL watches.",
11321
- inputSchema: { type: "object", properties: {} }
11322
- }
11323
- ];
10384
+ TOOLS = [];
11324
10385
  });
11325
10386
 
11326
10387
  // src/services/bridge/server.ts
11327
10388
  import { createServer as createServer2 } from "node:net";
11328
- import { mkdirSync as mkdirSync6, unlinkSync as unlinkSync2, existsSync as existsSync19, chmodSync as chmodSync5 } from "node:fs";
10389
+ import { mkdirSync as mkdirSync7, unlinkSync as unlinkSync2, existsSync as existsSync20, chmodSync as chmodSync5 } from "node:fs";
11329
10390
  async function resolveTarget2(client, to) {
11330
10391
  if (to.startsWith("@") || to === "*" || /^[0-9a-f]{64}$/i.test(to)) {
11331
10392
  return { ok: true, spec: to };
@@ -11439,10 +10500,10 @@ function handleConnection(socket, client) {
11439
10500
  function startBridgeServer(client) {
11440
10501
  const path = socketPath(client.meshSlug);
11441
10502
  const dir = socketDir();
11442
- if (!existsSync19(dir)) {
11443
- mkdirSync6(dir, { recursive: true, mode: 448 });
10503
+ if (!existsSync20(dir)) {
10504
+ mkdirSync7(dir, { recursive: true, mode: 448 });
11444
10505
  }
11445
- if (existsSync19(path)) {
10506
+ if (existsSync20(path)) {
11446
10507
  try {
11447
10508
  unlinkSync2(path);
11448
10509
  } catch {}
@@ -11512,135 +10573,6 @@ function relativeTime(isoStr) {
11512
10573
  const days = Math.floor(hours / 24);
11513
10574
  return `${days} day${days !== 1 ? "s" : ""} ago`;
11514
10575
  }
11515
- function text(msg, isError = false) {
11516
- return {
11517
- content: [{ type: "text", text: msg }],
11518
- ...isError ? { isError: true } : {}
11519
- };
11520
- }
11521
- async function resolveClient(to) {
11522
- const clients2 = allClients();
11523
- if (clients2.length === 0) {
11524
- return { client: null, targetSpec: to, error: "no meshes joined" };
11525
- }
11526
- let targetClients = clients2;
11527
- let target = to;
11528
- const colonIdx = to.indexOf(":");
11529
- if (colonIdx > 0 && colonIdx < to.length - 1) {
11530
- const slug = to.slice(0, colonIdx);
11531
- const rest = to.slice(colonIdx + 1);
11532
- const match = findClient(slug);
11533
- if (match) {
11534
- targetClients = [match];
11535
- target = rest;
11536
- }
11537
- }
11538
- if (target.startsWith("#") || target.startsWith("@") || target === "*") {
11539
- if (targetClients.length === 1) {
11540
- return { client: targetClients[0], targetSpec: target };
11541
- }
11542
- return {
11543
- client: null,
11544
- targetSpec: target,
11545
- error: `multiple meshes joined; prefix target with "<mesh-slug>:" (joined: ${clients2.map((c) => c.meshSlug).join(", ")})`
11546
- };
11547
- }
11548
- if (/^[0-9a-f]{8,64}$/.test(target)) {
11549
- const hits = [];
11550
- for (const c of targetClients) {
11551
- const peers = await c.listPeers();
11552
- for (const p of peers) {
11553
- if (p.pubkey.startsWith(target)) {
11554
- hits.push({ mesh: c, pubkey: p.pubkey, displayName: p.displayName });
11555
- }
11556
- }
11557
- }
11558
- if (hits.length === 1) {
11559
- return { client: hits[0].mesh, targetSpec: hits[0].pubkey };
11560
- }
11561
- if (hits.length > 1) {
11562
- const lines = hits.map((h) => ` - ${h.displayName} @ ${h.mesh.meshSlug} · pubkey ${h.pubkey.slice(0, 20)}…`).join(`
11563
- `);
11564
- return {
11565
- client: null,
11566
- targetSpec: target,
11567
- error: `ambiguous pubkey prefix "${target}" matches ${hits.length} peers:
11568
- ${lines}
11569
- Use a longer prefix.`
11570
- };
11571
- }
11572
- if (target.length === 64) {
11573
- if (targetClients.length === 1) {
11574
- return { client: targetClients[0], targetSpec: target };
11575
- }
11576
- return {
11577
- client: null,
11578
- targetSpec: target,
11579
- error: `multiple meshes joined; prefix target with "<mesh-slug>:" (joined: ${clients2.map((c) => c.meshSlug).join(", ")})`
11580
- };
11581
- }
11582
- return {
11583
- client: null,
11584
- targetSpec: target,
11585
- error: `no online peer's pubkey starts with "${target}".`
11586
- };
11587
- }
11588
- const nameLower = target.toLowerCase();
11589
- const candidates = [];
11590
- const exactMatches = [];
11591
- const partialMatches = [];
11592
- for (const c of targetClients) {
11593
- const ownSession = c.getSessionPubkey();
11594
- const peers = await c.listPeers();
11595
- candidates.push({ mesh: c.meshSlug, peers });
11596
- for (const p of peers) {
11597
- if (ownSession && p.pubkey === ownSession)
11598
- continue;
11599
- const nameLow = p.displayName.toLowerCase();
11600
- if (nameLow === nameLower) {
11601
- exactMatches.push({ mesh: c, pubkey: p.pubkey, displayName: p.displayName, cwd: p.cwd });
11602
- } else if (nameLow.includes(nameLower)) {
11603
- partialMatches.push({ mesh: c, pubkey: p.pubkey, displayName: p.displayName, cwd: p.cwd });
11604
- }
11605
- }
11606
- }
11607
- if (exactMatches.length === 1) {
11608
- return { client: exactMatches[0].mesh, targetSpec: exactMatches[0].pubkey };
11609
- }
11610
- if (exactMatches.length > 1) {
11611
- const lines = exactMatches.map((m) => ` - ${m.displayName} · pubkey ${m.pubkey.slice(0, 16)}…${m.cwd ? ` · cwd ${m.cwd}` : ""}`).join(`
11612
- `);
11613
- return {
11614
- client: null,
11615
- targetSpec: target,
11616
- error: `"${target}" is ambiguous — ${exactMatches.length} peers share that display name:
11617
- ${lines}
11618
- ` + `Disambiguate by pubkey prefix (e.g. send to "${exactMatches[0].pubkey.slice(0, 12)}…").`
11619
- };
11620
- }
11621
- if (partialMatches.length === 1) {
11622
- process.stderr.write(`[claudemesh] resolved "${target}" → "${partialMatches[0].displayName}" (partial match)
11623
- `);
11624
- return { client: partialMatches[0].mesh, targetSpec: partialMatches[0].pubkey };
11625
- }
11626
- if (partialMatches.length > 1) {
11627
- const lines = partialMatches.map((m) => ` - ${m.displayName} · pubkey ${m.pubkey.slice(0, 16)}…`).join(`
11628
- `);
11629
- return {
11630
- client: null,
11631
- targetSpec: target,
11632
- error: `"${target}" partially matches ${partialMatches.length} peers:
11633
- ${lines}
11634
- Be more specific, or use a pubkey prefix.`
11635
- };
11636
- }
11637
- const known = candidates.flatMap((c) => c.peers.map((p) => `${c.mesh}/${p.displayName}`));
11638
- return {
11639
- client: null,
11640
- targetSpec: target,
11641
- error: `peer "${target}" not found. ` + (known.length ? `Known peers: ${known.slice(0, 10).join(", ")}${known.length > 10 ? ", …" : ""}` : "No connected peers on your mesh(es). Use pubkey hex, @group, or * for broadcast.")
11642
- };
11643
- }
11644
10576
  async function resolvePeerName(client, pubkey) {
11645
10577
  const now = Date.now();
11646
10578
  if (now - peerNameCacheAge > CACHE_TTL_MS) {
@@ -11658,22 +10590,6 @@ function decryptFailedWarning(senderPubkey) {
11658
10590
  const who = senderPubkey ? senderPubkey.slice(0, 12) + "…" : "unknown sender";
11659
10591
  return `⚠ message from ${who} failed to decrypt (tampered or wrong keypair)`;
11660
10592
  }
11661
- function formatPush(p, meshSlug) {
11662
- const body = p.plaintext ?? decryptFailedWarning(p.senderPubkey);
11663
- const tag = p.subtype === "reminder" ? " [REMINDER]" : "";
11664
- return `[${meshSlug}]${tag} from ${p.senderPubkey.slice(0, 12)}… (${p.priority}, ${p.createdAt}):
11665
- ${body}`;
11666
- }
11667
- function maybeWarnDeprecated(toolName) {
11668
- const hint = DEPRECATED_TOOL_HINTS[toolName];
11669
- if (!hint)
11670
- return;
11671
- if (warnedTools.has(toolName))
11672
- return;
11673
- warnedTools.add(toolName);
11674
- process.stderr.write(`[claudemesh] mcp tool "${toolName}" is soft-deprecated in 1.1.0 ` + `and will be removed in 2.0.0. Use \`${hint}\` instead.
11675
- `);
11676
- }
11677
10593
  async function startMcpServer() {
11678
10594
  const serviceIdx = process.argv.indexOf("--service");
11679
10595
  if (serviceIdx !== -1 && process.argv[serviceIdx + 1]) {
@@ -11961,1482 +10877,6 @@ ${manifest.allowed_tools.map((t) => ` - ${t}`).join(`
11961
10877
  ]
11962
10878
  };
11963
10879
  });
11964
- server.setRequestHandler(CallToolRequestSchema, async (req) => {
11965
- const { name, arguments: args } = req.params;
11966
- for (const c of allClients()) {
11967
- c.incrementToolCalls();
11968
- }
11969
- if (config.meshes.length === 0) {
11970
- return text("No meshes joined. Run `claudemesh join https://claudemesh.com/join/<token>` first.", true);
11971
- }
11972
- maybeWarnDeprecated(name);
11973
- switch (name) {
11974
- case "send_message": {
11975
- const { to, message, priority } = args ?? {};
11976
- if (!to || !message)
11977
- return text("send_message: `to` and `message` required", true);
11978
- const targets = Array.isArray(to) ? to : [to];
11979
- const results2 = [];
11980
- const seen2 = new Set;
11981
- for (const target of targets) {
11982
- const { client, targetSpec, error: error2 } = await resolveClient(target);
11983
- if (!client) {
11984
- results2.push(`✗ ${target}: ${error2 ?? "no client resolved"}`);
11985
- continue;
11986
- }
11987
- if (seen2.has(targetSpec))
11988
- continue;
11989
- seen2.add(targetSpec);
11990
- const result = await client.send(targetSpec, message, priority ?? "next");
11991
- if (!result.ok) {
11992
- results2.push(`✗ ${target}: ${result.error}`);
11993
- } else {
11994
- results2.push(`✓ ${target} → ${result.messageId}`);
11995
- }
11996
- }
11997
- return text(results2.join(`
11998
- `));
11999
- }
12000
- case "list_peers": {
12001
- const { mesh_slug } = args ?? {};
12002
- const clients2 = mesh_slug ? [findClient(mesh_slug)].filter(Boolean) : allClients();
12003
- if (clients2.length === 0)
12004
- return text(mesh_slug ? `list_peers: no joined mesh "${mesh_slug}"` : "list_peers: no joined meshes", true);
12005
- const sections = [];
12006
- const statusCache = {};
12007
- for (const c of clients2) {
12008
- const peers = await c.listPeers();
12009
- const header = `## ${c.meshSlug} (${c.status}, mesh ${c.meshId.slice(0, 8)}…)`;
12010
- statusCache[c.meshSlug] = {
12011
- total: peers.length,
12012
- online: peers.filter((p) => p.status !== "offline").length,
12013
- updatedAt: new Date().toISOString(),
12014
- you: process.env.CLAUDEMESH_DISPLAY_NAME ?? undefined
12015
- };
12016
- if (peers.length === 0) {
12017
- sections.push(`${header}
12018
- No peers connected.`);
12019
- } else {
12020
- const pubkeyCounts = new Map;
12021
- for (const p of peers)
12022
- pubkeyCounts.set(p.pubkey, (pubkeyCounts.get(p.pubkey) ?? 0) + 1);
12023
- const peerLines = peers.map((p) => {
12024
- const summary = p.summary ? ` — "${p.summary}"` : "";
12025
- const groupsStr = p.groups?.length ? ` [${p.groups.map((g) => `@${g.name}${g.role ? ":" + g.role : ""}`).join(", ")}]` : "";
12026
- const meta = [];
12027
- if (p.peerType)
12028
- meta.push(`type:${p.peerType}`);
12029
- if (p.channel)
12030
- meta.push(`channel:${p.channel}`);
12031
- if (p.model)
12032
- meta.push(`model:${p.model}`);
12033
- const metaStr = meta.length ? ` {${meta.join(", ")}}` : "";
12034
- const cwdStr = p.cwd ? ` cwd:${p.cwd}` : "";
12035
- const locality = p.hostname && p.hostname === __require("os").hostname() ? "local" : "remote";
12036
- const localityTag = ` [${locality}]`;
12037
- const profileAvatar = p.profile?.avatar ? `${p.profile.avatar} ` : "";
12038
- const profileTitle = p.profile?.title ? ` (${p.profile.title})` : "";
12039
- const hiddenTag = p.visible === false ? " [hidden]" : "";
12040
- const sameKeyCount = pubkeyCounts.get(p.pubkey) ?? 1;
12041
- const sameKeyTag = sameKeyCount > 1 ? ` [shares key with ${sameKeyCount - 1} other session(s)]` : "";
12042
- return `- ${profileAvatar}**${p.displayName}**${profileTitle} [${p.status}]${localityTag}${hiddenTag}${sameKeyTag}${groupsStr}${metaStr} (pubkey: ${p.pubkey.slice(0, 16)}…)${cwdStr}${summary}`;
12043
- });
12044
- sections.push(`${header}
12045
- ${peerLines.join(`
12046
- `)}`);
12047
- }
12048
- }
12049
- try {
12050
- const { writeFileSync: writeFileSync10, mkdirSync: mkdirSync7, existsSync: existsSync20 } = await import("node:fs");
12051
- const { join: joinPath } = await import("node:path");
12052
- const { homedir: homedir11 } = await import("node:os");
12053
- const dir = joinPath(homedir11(), ".claudemesh");
12054
- if (!existsSync20(dir))
12055
- mkdirSync7(dir, { recursive: true });
12056
- writeFileSync10(joinPath(dir, "peer-cache.json"), JSON.stringify(statusCache));
12057
- } catch {}
12058
- return text(sections.join(`
12059
-
12060
- `));
12061
- }
12062
- case "message_status": {
12063
- const { id } = args ?? {};
12064
- if (!id)
12065
- return text("message_status: `id` required", true);
12066
- const clients2 = allClients();
12067
- if (!clients2.length)
12068
- return text("message_status: not connected", true);
12069
- let result = null;
12070
- for (const c of clients2) {
12071
- result = await c.messageStatus(id);
12072
- if (result)
12073
- break;
12074
- }
12075
- if (!result)
12076
- return text(`Message ${id} not found or timed out.`);
12077
- const recipientLines = result.recipients.map((r) => ` - ${r.name} (${r.pubkey.slice(0, 12)}…): ${r.status}`);
12078
- return text(`Message ${id.slice(0, 12)}… → ${result.targetSpec}
12079
- Delivered: ${result.delivered}${result.deliveredAt ? ` at ${result.deliveredAt}` : ""}
12080
- Recipients:
12081
- ${recipientLines.join(`
12082
- `)}`);
12083
- }
12084
- case "check_messages": {
12085
- const drained = [];
12086
- for (const c of allClients()) {
12087
- const msgs = c.drainPushBuffer();
12088
- for (const m of msgs)
12089
- drained.push(formatPush(m, c.meshSlug));
12090
- }
12091
- if (drained.length === 0)
12092
- return text("No new messages.");
12093
- return text(`${drained.length} new message(s):
12094
-
12095
- ${drained.join(`
12096
-
12097
- ---
12098
-
12099
- `)}`);
12100
- }
12101
- case "set_summary": {
12102
- const { summary } = args ?? {};
12103
- if (!summary)
12104
- return text("set_summary: `summary` required", true);
12105
- for (const c of allClients())
12106
- await c.setSummary(summary);
12107
- return text(`Summary set: "${summary}" (visible to ${allClients().length} mesh(es)).`);
12108
- }
12109
- case "set_status": {
12110
- const { status } = args ?? {};
12111
- if (!status)
12112
- return text("set_status: `status` required", true);
12113
- const s = status;
12114
- for (const c of allClients())
12115
- await c.setStatus(s);
12116
- return text(`Status set to ${s} across ${allClients().length} mesh(es).`);
12117
- }
12118
- case "set_visible": {
12119
- const { visible } = args ?? {};
12120
- if (visible === undefined)
12121
- return text("set_visible: `visible` required", true);
12122
- for (const c of allClients())
12123
- await c.setVisible(visible);
12124
- return text(visible ? "You are now visible to peers." : "You are now hidden. Direct messages still reach you, but you won't appear in list_peers or receive broadcasts.");
12125
- }
12126
- case "set_profile": {
12127
- const { avatar, title, bio, capabilities } = args ?? {};
12128
- const profile = { avatar, title, bio, capabilities };
12129
- for (const c of allClients())
12130
- await c.setProfile(profile);
12131
- const parts = [];
12132
- if (avatar)
12133
- parts.push(`Avatar: ${avatar}`);
12134
- if (title)
12135
- parts.push(`Title: ${title}`);
12136
- if (bio)
12137
- parts.push(`Bio: ${bio}`);
12138
- if (capabilities?.length)
12139
- parts.push(`Capabilities: ${capabilities.join(", ")}`);
12140
- return text(parts.length > 0 ? `Profile updated:
12141
- ${parts.join(`
12142
- `)}` : "Profile cleared.");
12143
- }
12144
- case "join_group": {
12145
- const { name: groupName, role } = args ?? {};
12146
- if (!groupName)
12147
- return text("join_group: `name` required", true);
12148
- for (const c of allClients())
12149
- await c.joinGroup(groupName, role);
12150
- return text(`Joined @${groupName}${role ? ` as ${role}` : ""}`);
12151
- }
12152
- case "leave_group": {
12153
- const { name: groupName } = args ?? {};
12154
- if (!groupName)
12155
- return text("leave_group: `name` required", true);
12156
- for (const c of allClients())
12157
- await c.leaveGroup(groupName);
12158
- return text(`Left @${groupName}`);
12159
- }
12160
- case "set_state": {
12161
- const { key, value } = args ?? {};
12162
- if (!key)
12163
- return text("set_state: `key` required", true);
12164
- for (const c of allClients())
12165
- await c.setState(key, value);
12166
- return text(`State set: ${key} = ${JSON.stringify(value)}`);
12167
- }
12168
- case "get_state": {
12169
- const { key } = args ?? {};
12170
- if (!key)
12171
- return text("get_state: `key` required", true);
12172
- const client = allClients()[0];
12173
- if (!client)
12174
- return text("get_state: not connected", true);
12175
- const result = await client.getState(key);
12176
- if (!result)
12177
- return text(`State "${key}" not found.`);
12178
- return text(`${key} = ${JSON.stringify(result.value)} (set by ${result.updatedBy} at ${result.updatedAt})`);
12179
- }
12180
- case "list_state": {
12181
- const client = allClients()[0];
12182
- if (!client)
12183
- return text("list_state: not connected", true);
12184
- const entries = await client.listState();
12185
- if (entries.length === 0)
12186
- return text("No shared state set.");
12187
- const lines = entries.map((e) => `- **${e.key}** = ${JSON.stringify(e.value)} (by ${e.updatedBy})`);
12188
- return text(lines.join(`
12189
- `));
12190
- }
12191
- case "remember": {
12192
- const { content, tags } = args ?? {};
12193
- if (!content)
12194
- return text("remember: `content` required", true);
12195
- const client = allClients()[0];
12196
- if (!client)
12197
- return text("remember: not connected", true);
12198
- const id = await client.remember(content, tags);
12199
- return text(`Remembered${id ? ` (${id})` : ""}: "${content.slice(0, 80)}${content.length > 80 ? "..." : ""}"`);
12200
- }
12201
- case "recall": {
12202
- const { query } = args ?? {};
12203
- if (!query)
12204
- return text("recall: `query` required", true);
12205
- const client = allClients()[0];
12206
- if (!client)
12207
- return text("recall: not connected", true);
12208
- const memories = await client.recall(query);
12209
- if (memories.length === 0)
12210
- return text(`No memories found for "${query}".`);
12211
- const lines = memories.map((m) => `- [${m.id.slice(0, 8)}] ${m.content} (by ${m.rememberedBy}, ${m.rememberedAt})`);
12212
- return text(`${memories.length} memor${memories.length === 1 ? "y" : "ies"}:
12213
- ${lines.join(`
12214
- `)}`);
12215
- }
12216
- case "forget": {
12217
- const { id } = args ?? {};
12218
- if (!id)
12219
- return text("forget: `id` required", true);
12220
- const client = allClients()[0];
12221
- if (!client)
12222
- return text("forget: not connected", true);
12223
- await client.forget(id);
12224
- return text(`Forgotten: ${id}`);
12225
- }
12226
- case "schedule_reminder": {
12227
- const sArgs = args ?? {};
12228
- if (!sArgs.message)
12229
- return text("schedule_reminder: `message` required", true);
12230
- const client = allClients()[0];
12231
- if (!client)
12232
- return text("schedule_reminder: not connected", true);
12233
- const isCron = !!sArgs.cron;
12234
- let deliverAt;
12235
- if (isCron) {
12236
- deliverAt = 0;
12237
- } else if (sArgs.deliver_at) {
12238
- deliverAt = Number(sArgs.deliver_at);
12239
- } else if (sArgs.in_seconds) {
12240
- deliverAt = Date.now() + Number(sArgs.in_seconds) * 1000;
12241
- } else {
12242
- return text("schedule_reminder: provide `deliver_at` (ms timestamp), `in_seconds`, or `cron` expression", true);
12243
- }
12244
- const isSelf = !sArgs.to;
12245
- let targetSpec;
12246
- if (isSelf) {
12247
- targetSpec = client.getSessionPubkey() ?? "*";
12248
- } else {
12249
- const to = sArgs.to;
12250
- if (!to.startsWith("@") && to !== "*" && !/^[0-9a-f]{64}$/i.test(to)) {
12251
- const peers = await client.listPeers();
12252
- const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
12253
- if (!match) {
12254
- const names = peers.map((p) => p.displayName).join(", ");
12255
- return text(`schedule_reminder: peer "${to}" not found. Online: ${names || "(none)"}`, true);
12256
- }
12257
- targetSpec = match.pubkey;
12258
- } else {
12259
- targetSpec = to;
12260
- }
12261
- }
12262
- const result = await client.scheduleMessage(targetSpec, sArgs.message, deliverAt, true, sArgs.cron);
12263
- if (!result)
12264
- return text("schedule_reminder: broker did not acknowledge — check connection", true);
12265
- if (isCron) {
12266
- const nextFire = new Date(result.deliverAt).toISOString();
12267
- return text(isSelf ? `Recurring self-reminder scheduled (${result.scheduledId.slice(0, 8)}): "${sArgs.message.slice(0, 60)}" — cron: ${sArgs.cron}, next fire: ${nextFire}` : `Recurring reminder to "${sArgs.to}" scheduled (${result.scheduledId.slice(0, 8)}) — cron: ${sArgs.cron}, next fire: ${nextFire}`);
12268
- }
12269
- const when = new Date(result.deliverAt).toISOString();
12270
- return text(isSelf ? `Self-reminder scheduled (${result.scheduledId.slice(0, 8)}): "${sArgs.message.slice(0, 60)}" at ${when}` : `Reminder to "${sArgs.to}" scheduled (${result.scheduledId.slice(0, 8)}) for ${when}`);
12271
- }
12272
- case "list_scheduled": {
12273
- const client = allClients()[0];
12274
- if (!client)
12275
- return text("list_scheduled: not connected", true);
12276
- const scheduled = await client.listScheduled();
12277
- if (scheduled.length === 0)
12278
- return text("No pending scheduled messages.");
12279
- const lines = scheduled.map((m) => `- [${m.id.slice(0, 8)}] → ${m.to === client.getSessionPubkey() ? "self (reminder)" : m.to} at ${new Date(m.deliverAt).toISOString()}: "${m.message.slice(0, 60)}${m.message.length > 60 ? "…" : ""}"`);
12280
- return text(`${scheduled.length} scheduled:
12281
- ${lines.join(`
12282
- `)}`);
12283
- }
12284
- case "cancel_scheduled": {
12285
- const client = allClients()[0];
12286
- if (!client)
12287
- return text("cancel_scheduled: not connected", true);
12288
- const { id: schedId } = args ?? {};
12289
- if (!schedId)
12290
- return text("cancel_scheduled: `id` required", true);
12291
- const ok = await client.cancelScheduled(schedId);
12292
- return text(ok ? `Cancelled: ${schedId}` : `Not found or already fired: ${schedId}`, !ok);
12293
- }
12294
- case "share_file": {
12295
- const { path: filePath, name: fileName, tags, to: fileTo } = args ?? {};
12296
- if (!filePath)
12297
- return text("share_file: `path` required", true);
12298
- const { existsSync: existsSync20 } = await import("node:fs");
12299
- if (!existsSync20(filePath))
12300
- return text(`share_file: file not found: ${filePath}`, true);
12301
- const client = allClients()[0];
12302
- if (!client)
12303
- return text("share_file: not connected", true);
12304
- if (fileTo) {
12305
- const { encryptFile: encryptFile2, sealKeyForPeer: sealKeyForPeer2 } = await Promise.resolve().then(() => (init_file_crypto(), exports_file_crypto));
12306
- const { readFileSync: readFileSync13, writeFileSync: writeFileSync10, mkdtempSync: mkdtempSync2, unlinkSync: unlinkSync3, rmdirSync } = await import("node:fs");
12307
- const { tmpdir: tmpdir2 } = await import("node:os");
12308
- const { join: join12, basename } = await import("node:path");
12309
- const peers = await client.listPeers();
12310
- const targetPeer = peers.find((p) => p.pubkey === fileTo || p.displayName === fileTo);
12311
- if (!targetPeer) {
12312
- return text(`share_file: peer not found: ${fileTo}`, true);
12313
- }
12314
- const plaintext = readFileSync13(filePath);
12315
- const { ciphertext, nonce, key } = await encryptFile2(new Uint8Array(plaintext));
12316
- const sealedForTarget = await sealKeyForPeer2(key, targetPeer.pubkey);
12317
- const myPubkey = client.getSessionPubkey();
12318
- const sealedForSelf = myPubkey ? await sealKeyForPeer2(key, myPubkey) : null;
12319
- const fileKeys = [
12320
- { peerPubkey: targetPeer.pubkey, sealedKey: sealedForTarget },
12321
- ...sealedForSelf && myPubkey ? [{ peerPubkey: myPubkey, sealedKey: sealedForSelf }] : []
12322
- ];
12323
- const { ensureSodium: ensureSodium3 } = await Promise.resolve().then(() => (init_keypair(), exports_keypair));
12324
- const sodium4 = await ensureSodium3();
12325
- const nonceBytes = sodium4.from_base64(nonce, sodium4.base64_variants.ORIGINAL);
12326
- const combined = new Uint8Array(nonceBytes.length + ciphertext.length);
12327
- combined.set(nonceBytes, 0);
12328
- combined.set(ciphertext, nonceBytes.length);
12329
- const rawName = fileName ?? basename(filePath);
12330
- const baseName = basename(rawName).replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 255);
12331
- const tmpDir = mkdtempSync2(join12(tmpdir2(), "cm-"));
12332
- const tmpPath = join12(tmpDir, baseName);
12333
- writeFileSync10(tmpPath, combined);
12334
- try {
12335
- const fileId = await client.uploadFile(tmpPath, client.meshId, client.meshSlug, {
12336
- name: baseName,
12337
- tags,
12338
- persistent: true,
12339
- encrypted: true,
12340
- ownerPubkey: myPubkey ?? undefined,
12341
- fileKeys
12342
- });
12343
- return text(`Shared (E2E encrypted): ${baseName} → ${targetPeer.displayName} (${fileId})`);
12344
- } catch (e) {
12345
- return text(`share_file: upload failed — ${e instanceof Error ? e.message : String(e)}`, true);
12346
- } finally {
12347
- try {
12348
- unlinkSync3(tmpPath);
12349
- } catch {}
12350
- try {
12351
- rmdirSync(tmpDir);
12352
- } catch {}
12353
- }
12354
- }
12355
- try {
12356
- const fileId = await client.uploadFile(filePath, client.meshId, client.meshSlug, {
12357
- name: fileName,
12358
- tags,
12359
- persistent: true
12360
- });
12361
- return text(`Shared: ${fileName ?? filePath} (${fileId})`);
12362
- } catch (e) {
12363
- return text(`share_file: upload failed — ${e instanceof Error ? e.message : String(e)}`, true);
12364
- }
12365
- }
12366
- case "get_file": {
12367
- const { id, save_to } = args ?? {};
12368
- if (!id || !save_to)
12369
- return text("get_file: `id` and `save_to` required", true);
12370
- const client = allClients()[0];
12371
- if (!client)
12372
- return text("get_file: not connected", true);
12373
- const result = await client.getFile(id);
12374
- if (!result)
12375
- return text(`get_file: file ${id} not found`, true);
12376
- if (result.encrypted) {
12377
- const genericErr = "get_file: could not decrypt — you may not have access to this file";
12378
- if (!result.sealedKey)
12379
- return text(genericErr, true);
12380
- const { openSealedKey: openSealedKey2, decryptFile: decryptFile2 } = await Promise.resolve().then(() => (init_file_crypto(), exports_file_crypto));
12381
- const { ensureSodium: ensureSodium3 } = await Promise.resolve().then(() => (init_keypair(), exports_keypair));
12382
- const myPubkey = client.getSessionPubkey();
12383
- const mySecret = client.getSessionSecretKey();
12384
- if (!myPubkey || !mySecret)
12385
- return text(genericErr, true);
12386
- const kf = await openSealedKey2(result.sealedKey, myPubkey, mySecret);
12387
- if (!kf)
12388
- return text(genericErr, true);
12389
- const MAX_DOWNLOAD = 104857600;
12390
- const resp = await fetch(result.url, { signal: AbortSignal.timeout(30000) });
12391
- if (!resp.ok)
12392
- return text(`get_file: download failed (${resp.status})`, true);
12393
- const contentLength = parseInt(resp.headers.get("content-length") ?? "0", 10);
12394
- if (contentLength > MAX_DOWNLOAD)
12395
- return text(`get_file: file too large (${contentLength} bytes)`, true);
12396
- const buf = new Uint8Array(await resp.arrayBuffer());
12397
- if (buf.length > MAX_DOWNLOAD)
12398
- return text(`get_file: file too large (${buf.length} bytes)`, true);
12399
- const sodium4 = await ensureSodium3();
12400
- const NONCE_BYTES = sodium4.crypto_secretbox_NONCEBYTES;
12401
- if (buf.length < NONCE_BYTES)
12402
- return text(genericErr, true);
12403
- const nonce = sodium4.to_base64(buf.slice(0, NONCE_BYTES), sodium4.base64_variants.ORIGINAL);
12404
- const ciphertext = buf.slice(NONCE_BYTES);
12405
- const plaintext = await decryptFile2(ciphertext, nonce, kf);
12406
- if (!plaintext)
12407
- return text(genericErr, true);
12408
- const { writeFileSync: writeFileSync11, mkdirSync: mkdirSync8 } = await import("node:fs");
12409
- const { dirname: dirname6 } = await import("node:path");
12410
- mkdirSync8(dirname6(save_to), { recursive: true });
12411
- writeFileSync11(save_to, plaintext);
12412
- return text(`Downloaded and decrypted: ${result.name} → ${save_to}`);
12413
- }
12414
- let res = await fetch(result.url, { signal: AbortSignal.timeout(1e4) }).catch(() => null);
12415
- if (!res || !res.ok) {
12416
- const brokerHttp = client.brokerUrl.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
12417
- res = await fetch(`${brokerHttp}/download/${id}?mesh=${client.meshId}`, { signal: AbortSignal.timeout(30000) });
12418
- }
12419
- if (!res.ok)
12420
- return text(`get_file: download failed (${res.status})`, true);
12421
- const { writeFileSync: writeFileSync10, mkdirSync: mkdirSync7 } = await import("node:fs");
12422
- const { dirname: dirname5 } = await import("node:path");
12423
- mkdirSync7(dirname5(save_to), { recursive: true });
12424
- writeFileSync10(save_to, Buffer.from(await res.arrayBuffer()));
12425
- return text(`Downloaded: ${result.name} → ${save_to}`);
12426
- }
12427
- case "list_files": {
12428
- const { query, from } = args ?? {};
12429
- const client = allClients()[0];
12430
- if (!client)
12431
- return text("list_files: not connected", true);
12432
- const files = await client.listFiles(query, from);
12433
- if (files.length === 0)
12434
- return text("No files found.");
12435
- const lines = files.map((f) => `- **${f.name}** (${f.id.slice(0, 8)}…, ${f.size} bytes) by ${f.uploadedBy}${f.tags.length ? ` [${f.tags.join(", ")}]` : ""}`);
12436
- return text(lines.join(`
12437
- `));
12438
- }
12439
- case "file_status": {
12440
- const { id } = args ?? {};
12441
- if (!id)
12442
- return text("file_status: `id` required", true);
12443
- const client = allClients()[0];
12444
- if (!client)
12445
- return text("file_status: not connected", true);
12446
- const accesses = await client.fileStatus(id);
12447
- if (accesses.length === 0)
12448
- return text("No one has accessed this file yet.");
12449
- const lines = accesses.map((a) => `- ${a.peerName} at ${a.accessedAt}`);
12450
- return text(`Accessed by:
12451
- ${lines.join(`
12452
- `)}`);
12453
- }
12454
- case "delete_file": {
12455
- const { id } = args ?? {};
12456
- if (!id)
12457
- return text("delete_file: `id` required", true);
12458
- const client = allClients()[0];
12459
- if (!client)
12460
- return text("delete_file: not connected", true);
12461
- await client.deleteFile(id);
12462
- return text(`Deleted: ${id}`);
12463
- }
12464
- case "vector_store": {
12465
- const { collection, text: storeText, metadata } = args ?? {};
12466
- if (!collection || !storeText)
12467
- return text("vector_store: `collection` and `text` required", true);
12468
- const client = allClients()[0];
12469
- if (!client)
12470
- return text("vector_store: not connected", true);
12471
- const id = await client.vectorStore(collection, storeText, metadata);
12472
- return text(`Stored in ${collection}${id ? ` (${id})` : ""}`);
12473
- }
12474
- case "vector_search": {
12475
- const { collection, query, limit } = args ?? {};
12476
- if (!collection || !query)
12477
- return text("vector_search: `collection` and `query` required", true);
12478
- const client = allClients()[0];
12479
- if (!client)
12480
- return text("vector_search: not connected", true);
12481
- const results2 = await client.vectorSearch(collection, query, limit);
12482
- if (results2.length === 0)
12483
- return text(`No results in ${collection} for "${query}".`);
12484
- const lines = results2.map((r) => `- [${r.id.slice(0, 8)}…] (score: ${r.score.toFixed(3)}) ${r.text.slice(0, 120)}${r.text.length > 120 ? "…" : ""}`);
12485
- return text(`${results2.length} result(s) in ${collection}:
12486
- ${lines.join(`
12487
- `)}`);
12488
- }
12489
- case "vector_delete": {
12490
- const { collection, id } = args ?? {};
12491
- if (!collection || !id)
12492
- return text("vector_delete: `collection` and `id` required", true);
12493
- const client = allClients()[0];
12494
- if (!client)
12495
- return text("vector_delete: not connected", true);
12496
- await client.vectorDelete(collection, id);
12497
- return text(`Deleted ${id} from ${collection}`);
12498
- }
12499
- case "list_collections": {
12500
- const client = allClients()[0];
12501
- if (!client)
12502
- return text("list_collections: not connected", true);
12503
- const collections = await client.listCollections();
12504
- if (collections.length === 0)
12505
- return text("No vector collections.");
12506
- return text(`Collections:
12507
- ${collections.map((c) => `- ${c}`).join(`
12508
- `)}`);
12509
- }
12510
- case "graph_query": {
12511
- const { cypher } = args ?? {};
12512
- if (!cypher)
12513
- return text("graph_query: `cypher` required", true);
12514
- const client = allClients()[0];
12515
- if (!client)
12516
- return text("graph_query: not connected", true);
12517
- const rows = await client.graphQuery(cypher);
12518
- if (rows.length === 0)
12519
- return text("No results.");
12520
- return text(JSON.stringify(rows, null, 2));
12521
- }
12522
- case "graph_execute": {
12523
- const { cypher } = args ?? {};
12524
- if (!cypher)
12525
- return text("graph_execute: `cypher` required", true);
12526
- const client = allClients()[0];
12527
- if (!client)
12528
- return text("graph_execute: not connected", true);
12529
- const rows = await client.graphExecute(cypher);
12530
- return text(rows.length > 0 ? JSON.stringify(rows, null, 2) : "Executed successfully.");
12531
- }
12532
- case "share_context": {
12533
- const { summary, files_read, key_findings, tags } = args ?? {};
12534
- if (!summary)
12535
- return text("share_context: `summary` required", true);
12536
- const client = allClients()[0];
12537
- if (!client)
12538
- return text("share_context: not connected", true);
12539
- await client.shareContext(summary, files_read, key_findings, tags);
12540
- return text(`Context shared: "${summary.slice(0, 80)}${summary.length > 80 ? "…" : ""}"`);
12541
- }
12542
- case "get_context": {
12543
- const { query } = args ?? {};
12544
- if (!query)
12545
- return text("get_context: `query` required", true);
12546
- const client = allClients()[0];
12547
- if (!client)
12548
- return text("get_context: not connected", true);
12549
- const contexts = await client.getContext(query);
12550
- if (contexts.length === 0)
12551
- return text(`No context found for "${query}".`);
12552
- const lines = contexts.map((c) => {
12553
- const files = c.filesRead.length ? `
12554
- Files: ${c.filesRead.join(", ")}` : "";
12555
- const findings = c.keyFindings.length ? `
12556
- Findings: ${c.keyFindings.join("; ")}` : "";
12557
- return `- **${c.peerName}** (${c.updatedAt}): ${c.summary}${files}${findings}`;
12558
- });
12559
- return text(`${contexts.length} context(s):
12560
- ${lines.join(`
12561
- `)}`);
12562
- }
12563
- case "list_contexts": {
12564
- const client = allClients()[0];
12565
- if (!client)
12566
- return text("list_contexts: not connected", true);
12567
- const contexts = await client.listContexts();
12568
- if (contexts.length === 0)
12569
- return text("No peer contexts shared yet.");
12570
- const lines = contexts.map((c) => `- **${c.peerName}**: ${c.summary}${c.tags.length ? ` [${c.tags.join(", ")}]` : ""}`);
12571
- return text(`Peer contexts:
12572
- ${lines.join(`
12573
- `)}`);
12574
- }
12575
- case "create_task": {
12576
- const { title, assignee, priority, tags } = args ?? {};
12577
- if (!title)
12578
- return text("create_task: `title` required", true);
12579
- const client = allClients()[0];
12580
- if (!client)
12581
- return text("create_task: not connected", true);
12582
- const id = await client.createTask(title, assignee, priority, tags);
12583
- return text(`Task created${id ? ` (${id})` : ""}: "${title}"${assignee ? ` → ${assignee}` : ""}`);
12584
- }
12585
- case "claim_task": {
12586
- const { id } = args ?? {};
12587
- if (!id)
12588
- return text("claim_task: `id` required", true);
12589
- const client = allClients()[0];
12590
- if (!client)
12591
- return text("claim_task: not connected", true);
12592
- await client.claimTask(id);
12593
- return text(`Claimed task: ${id}`);
12594
- }
12595
- case "complete_task": {
12596
- const { id, result } = args ?? {};
12597
- if (!id)
12598
- return text("complete_task: `id` required", true);
12599
- const client = allClients()[0];
12600
- if (!client)
12601
- return text("complete_task: not connected", true);
12602
- await client.completeTask(id, result);
12603
- return text(`Completed task: ${id}${result ? ` — ${result}` : ""}`);
12604
- }
12605
- case "list_tasks": {
12606
- const { status, assignee } = args ?? {};
12607
- const client = allClients()[0];
12608
- if (!client)
12609
- return text("list_tasks: not connected", true);
12610
- const tasks = await client.listTasks(status, assignee);
12611
- if (tasks.length === 0)
12612
- return text("No tasks found.");
12613
- const lines = tasks.map((t) => `- [${t.id.slice(0, 8)}…] **${t.title}** (${t.status}, ${t.priority}) ${t.assignee ? `→ ${t.assignee}` : "unassigned"} (by ${t.createdBy})`);
12614
- return text(`${tasks.length} task(s):
12615
- ${lines.join(`
12616
- `)}`);
12617
- }
12618
- case "mesh_query": {
12619
- const { sql: querySql } = args ?? {};
12620
- if (!querySql)
12621
- return text("mesh_query: `sql` required", true);
12622
- const client = allClients()[0];
12623
- if (!client)
12624
- return text("mesh_query: not connected", true);
12625
- const result = await client.meshQuery(querySql);
12626
- if (!result)
12627
- return text("mesh_query: query failed or timed out", true);
12628
- if (result.rows.length === 0)
12629
- return text(`Query returned 0 rows.`);
12630
- const header = `| ${result.columns.join(" | ")} |`;
12631
- const sep = `| ${result.columns.map(() => "---").join(" | ")} |`;
12632
- const rows = result.rows.map((r) => `| ${result.columns.map((c) => String(r[c] ?? "")).join(" | ")} |`);
12633
- return text(`${result.rowCount} row(s):
12634
- ${header}
12635
- ${sep}
12636
- ${rows.join(`
12637
- `)}`);
12638
- }
12639
- case "mesh_execute": {
12640
- const { sql: execSql } = args ?? {};
12641
- if (!execSql)
12642
- return text("mesh_execute: `sql` required", true);
12643
- const client = allClients()[0];
12644
- if (!client)
12645
- return text("mesh_execute: not connected", true);
12646
- await client.meshExecute(execSql);
12647
- return text(`Executed.`);
12648
- }
12649
- case "mesh_schema": {
12650
- const client = allClients()[0];
12651
- if (!client)
12652
- return text("mesh_schema: not connected", true);
12653
- const tables = await client.meshSchema();
12654
- if (!tables || tables.length === 0)
12655
- return text("No tables in mesh database.");
12656
- const lines = tables.map((t) => `**${t.name}**: ${t.columns.map((c) => `${c.name} (${c.type}${c.nullable ? ", nullable" : ""})`).join(", ")}`);
12657
- return text(lines.join(`
12658
- `));
12659
- }
12660
- case "create_stream": {
12661
- const { name: streamName } = args ?? {};
12662
- if (!streamName)
12663
- return text("create_stream: `name` required", true);
12664
- const client = allClients()[0];
12665
- if (!client)
12666
- return text("create_stream: not connected", true);
12667
- const streamId = await client.createStream(streamName);
12668
- return text(`Stream created: ${streamName}${streamId ? ` (${streamId})` : ""}`);
12669
- }
12670
- case "publish": {
12671
- const { stream: pubStream, data: pubData } = args ?? {};
12672
- if (!pubStream)
12673
- return text("publish: `stream` required", true);
12674
- const client = allClients()[0];
12675
- if (!client)
12676
- return text("publish: not connected", true);
12677
- await client.publish(pubStream, pubData);
12678
- return text(`Published to ${pubStream}.`);
12679
- }
12680
- case "subscribe": {
12681
- const { stream: subStream } = args ?? {};
12682
- if (!subStream)
12683
- return text("subscribe: `stream` required", true);
12684
- const client = allClients()[0];
12685
- if (!client)
12686
- return text("subscribe: not connected", true);
12687
- await client.subscribe(subStream);
12688
- return text(`Subscribed to ${subStream}. Data pushes will arrive as channel notifications.`);
12689
- }
12690
- case "list_streams": {
12691
- const client = allClients()[0];
12692
- if (!client)
12693
- return text("list_streams: not connected", true);
12694
- const streams = await client.listStreams();
12695
- if (streams.length === 0)
12696
- return text("No active streams.");
12697
- const lines = streams.map((s) => `- **${s.name}** (${s.id.slice(0, 8)}…) by ${s.createdBy}, ${s.subscriberCount} subscriber(s)`);
12698
- return text(lines.join(`
12699
- `));
12700
- }
12701
- case "mesh_set_clock": {
12702
- const { speed } = args ?? {};
12703
- if (!speed || speed < 1 || speed > 100)
12704
- return text("mesh_set_clock: speed must be 1-100", true);
12705
- const client = allClients()[0];
12706
- if (!client)
12707
- return text("mesh_set_clock: not connected", true);
12708
- const result = await client.setClock(speed);
12709
- if (!result)
12710
- return text("mesh_set_clock: timed out", true);
12711
- return text([
12712
- `**Clock set to x${result.speed}**`,
12713
- `Paused: ${result.paused}`,
12714
- `Tick: ${result.tick}`,
12715
- `Sim time: ${result.simTime}`,
12716
- `Started at: ${result.startedAt}`
12717
- ].join(`
12718
- `));
12719
- }
12720
- case "mesh_pause_clock": {
12721
- const client = allClients()[0];
12722
- if (!client)
12723
- return text("mesh_pause_clock: not connected", true);
12724
- const result = await client.pauseClock();
12725
- if (!result)
12726
- return text("mesh_pause_clock: timed out", true);
12727
- return text([
12728
- "**Clock paused**",
12729
- `Speed: x${result.speed}`,
12730
- `Tick: ${result.tick}`,
12731
- `Sim time: ${result.simTime}`
12732
- ].join(`
12733
- `));
12734
- }
12735
- case "mesh_resume_clock": {
12736
- const client = allClients()[0];
12737
- if (!client)
12738
- return text("mesh_resume_clock: not connected", true);
12739
- const result = await client.resumeClock();
12740
- if (!result)
12741
- return text("mesh_resume_clock: timed out", true);
12742
- return text([
12743
- "**Clock resumed**",
12744
- `Speed: x${result.speed}`,
12745
- `Tick: ${result.tick}`,
12746
- `Sim time: ${result.simTime}`
12747
- ].join(`
12748
- `));
12749
- }
12750
- case "mesh_clock": {
12751
- const client = allClients()[0];
12752
- if (!client)
12753
- return text("mesh_clock: not connected", true);
12754
- const result = await client.getClock();
12755
- if (!result)
12756
- return text("mesh_clock: timed out", true);
12757
- const statusLabel = result.speed === 0 ? "not started" : result.paused ? "paused" : "running";
12758
- return text([
12759
- `**Clock status: ${statusLabel}**`,
12760
- `Speed: x${result.speed}`,
12761
- `Tick: ${result.tick}`,
12762
- `Sim time: ${result.simTime}`,
12763
- `Started at: ${result.startedAt}`
12764
- ].join(`
12765
- `));
12766
- }
12767
- case "mesh_info": {
12768
- const client = allClients()[0];
12769
- if (!client)
12770
- return text("mesh_info: not connected", true);
12771
- const info = await client.meshInfo();
12772
- if (!info)
12773
- return text("mesh_info: timed out", true);
12774
- const lines = [
12775
- `**Mesh**: ${info.mesh}`,
12776
- `**Peers**: ${info.peers}`,
12777
- `**Groups**: ${info.groups?.join(", ") || "none"}`,
12778
- `**State keys**: ${info.stateKeys?.join(", ") || "none"}`,
12779
- `**Memories**: ${info.memoryCount}`,
12780
- `**Files**: ${info.fileCount}`,
12781
- `**Tasks**: open=${info.tasks?.open ?? 0}, claimed=${info.tasks?.claimed ?? 0}, done=${info.tasks?.done ?? 0}`,
12782
- `**Streams**: ${info.streams?.join(", ") || "none"}`,
12783
- `**Tables**: ${info.tables?.join(", ") || "none"}`,
12784
- `**Your name**: ${info.yourName}`,
12785
- `**Your groups**: ${info.yourGroups?.map((g) => `@${g.name}${g.role ? ":" + g.role : ""}`).join(", ") || "none"}`
12786
- ];
12787
- return text(lines.join(`
12788
- `));
12789
- }
12790
- case "mesh_stats": {
12791
- const clients2 = allClients();
12792
- if (clients2.length === 0)
12793
- return text("mesh_stats: no joined meshes", true);
12794
- const sections = [];
12795
- for (const c of clients2) {
12796
- const peers = await c.listPeers();
12797
- const header = `## ${c.meshSlug}`;
12798
- const rows = peers.map((p) => {
12799
- const s = p.stats;
12800
- if (!s)
12801
- return `| ${p.displayName} | - | - | - | - | - |`;
12802
- const up = s.uptime != null ? `${Math.floor(s.uptime / 60)}m` : "-";
12803
- return `| ${p.displayName} | ${s.messagesIn ?? 0} | ${s.messagesOut ?? 0} | ${s.toolCalls ?? 0} | ${up} | ${s.errors ?? 0} |`;
12804
- });
12805
- sections.push(`${header}
12806
- | Peer | Msgs In | Msgs Out | Tool Calls | Uptime | Errors |
12807
- |------|---------|----------|------------|--------|--------|
12808
- ${rows.join(`
12809
- `)}`);
12810
- }
12811
- return text(sections.join(`
12812
-
12813
- `));
12814
- }
12815
- case "share_skill": {
12816
- const {
12817
- name: skillName,
12818
- description: skillDesc,
12819
- instructions: skillInstr,
12820
- tags: skillTags,
12821
- when_to_use,
12822
- allowed_tools,
12823
- model,
12824
- context: skillContext,
12825
- agent,
12826
- user_invocable,
12827
- argument_hint
12828
- } = args ?? {};
12829
- if (!skillName || !skillDesc || !skillInstr)
12830
- return text("share_skill: `name`, `description`, and `instructions` required", true);
12831
- const client = allClients()[0];
12832
- if (!client)
12833
- return text("share_skill: not connected", true);
12834
- const manifest = {};
12835
- if (when_to_use)
12836
- manifest.when_to_use = when_to_use;
12837
- if (allowed_tools?.length)
12838
- manifest.allowed_tools = allowed_tools;
12839
- if (model)
12840
- manifest.model = model;
12841
- if (skillContext)
12842
- manifest.context = skillContext;
12843
- if (agent)
12844
- manifest.agent = agent;
12845
- if (user_invocable === false)
12846
- manifest.user_invocable = false;
12847
- if (argument_hint)
12848
- manifest.argument_hint = argument_hint;
12849
- const result = await client.shareSkill(skillName, skillDesc, skillInstr, skillTags, Object.keys(manifest).length > 0 ? manifest : undefined);
12850
- if (!result)
12851
- return text("share_skill: broker did not acknowledge", true);
12852
- server.notification({ method: "notifications/prompts/list_changed" });
12853
- server.notification({ method: "notifications/resources/list_changed" });
12854
- return text(`Skill "${skillName}" published to the mesh. It will appear as /claudemesh:${skillName} in Claude Code.`);
12855
- }
12856
- case "get_skill": {
12857
- const { name: gsName } = args ?? {};
12858
- if (!gsName)
12859
- return text("get_skill: `name` required", true);
12860
- const client = allClients()[0];
12861
- if (!client)
12862
- return text("get_skill: not connected", true);
12863
- const skill = await client.getSkill(gsName);
12864
- if (!skill)
12865
- return text(`Skill "${gsName}" not found in the mesh.`);
12866
- const manifest = skill.manifest;
12867
- const metaLines = [];
12868
- if (manifest) {
12869
- if (manifest.when_to_use)
12870
- metaLines.push(`**When to use:** ${manifest.when_to_use}`);
12871
- if (manifest.allowed_tools)
12872
- metaLines.push(`**Allowed tools:** ${manifest.allowed_tools.join(", ")}`);
12873
- if (manifest.model)
12874
- metaLines.push(`**Model:** ${manifest.model}`);
12875
- if (manifest.context)
12876
- metaLines.push(`**Context:** ${manifest.context}`);
12877
- if (manifest.agent)
12878
- metaLines.push(`**Agent:** ${manifest.agent}`);
12879
- }
12880
- return text(`# Skill: ${skill.name}
12881
-
12882
- **Description:** ${skill.description}
12883
- **Author:** ${skill.author}
12884
- **Tags:** ${skill.tags.length ? skill.tags.join(", ") : "none"}
12885
- **Created:** ${skill.createdAt}
12886
- **Slash command:** /claudemesh:${skill.name}
12887
- ` + (metaLines.length ? metaLines.join(`
12888
- `) + `
12889
- ` : "") + `
12890
- ---
12891
-
12892
- ## Instructions
12893
-
12894
- ${skill.instructions}`);
12895
- }
12896
- case "list_skills": {
12897
- const { query: skillQuery } = args ?? {};
12898
- const client = allClients()[0];
12899
- if (!client)
12900
- return text("list_skills: not connected", true);
12901
- const skills = await client.listSkills(skillQuery);
12902
- if (skills.length === 0)
12903
- return text(skillQuery ? `No skills found for "${skillQuery}".` : "No skills in the mesh yet.");
12904
- const lines = skills.map((s) => `- **${s.name}**: ${s.description}${s.tags.length ? ` [${s.tags.join(", ")}]` : ""} (by ${s.author})`);
12905
- return text(`${skills.length} skill(s):
12906
- ${lines.join(`
12907
- `)}`);
12908
- }
12909
- case "remove_skill": {
12910
- const { name: rsName } = args ?? {};
12911
- if (!rsName)
12912
- return text("remove_skill: `name` required", true);
12913
- const client = allClients()[0];
12914
- if (!client)
12915
- return text("remove_skill: not connected", true);
12916
- const removed = await client.removeSkill(rsName);
12917
- if (removed) {
12918
- server.notification({ method: "notifications/prompts/list_changed" });
12919
- server.notification({ method: "notifications/resources/list_changed" });
12920
- }
12921
- return text(removed ? `Skill "${rsName}" removed.` : `Skill "${rsName}" not found.`, !removed);
12922
- }
12923
- case "ping_mesh": {
12924
- const { priorities: pingPriorities } = args ?? {};
12925
- const toTest = pingPriorities ?? ["now", "next"];
12926
- const client = allClients()[0];
12927
- if (!client)
12928
- return text("ping_mesh: not connected", true);
12929
- const results2 = [];
12930
- results2.push(`WS status: ${client.status}`);
12931
- results2.push(`Mesh: ${client.meshSlug}`);
12932
- const peers = await client.listPeers();
12933
- const selfPeer = peers.find((p) => p.displayName === myName);
12934
- results2.push(`Your status: ${selfPeer?.status ?? "not found in peer list"}`);
12935
- results2.push(`Peers online: ${peers.length}`);
12936
- results2.push(`Push buffer: ${client.pushHistory.length} buffered`);
12937
- for (const prio of toTest) {
12938
- const sendTime = Date.now();
12939
- const target = peers.find((p) => p.displayName !== myName);
12940
- const sendResult = await client.send(target?.pubkey ?? "*", `__ping__ ${prio} from ${myName} at ${new Date().toISOString()}`, prio);
12941
- const ackTime = Date.now();
12942
- if (!sendResult.ok) {
12943
- results2.push(`[${prio}] SEND FAILED: ${sendResult.error}`);
12944
- } else {
12945
- results2.push(`[${prio}] send→ack: ${ackTime - sendTime}ms (msgId: ${sendResult.messageId?.slice(0, 12)})`);
12946
- if (prio !== "now" && selfPeer?.status === "working") {
12947
- results2.push(` ⚠ peer status is "working" — broker holds "${prio}" until idle`);
12948
- }
12949
- }
12950
- }
12951
- results2.push("");
12952
- results2.push("Pipeline check:");
12953
- results2.push(` onPush handlers: active`);
12954
- results2.push(` messageMode: ${messageMode}`);
12955
- results2.push(` server.notification: ${messageMode === "off" ? "disabled (mode=off)" : "enabled"}`);
12956
- return text(results2.join(`
12957
- `));
12958
- }
12959
- case "mesh_mcp_register": {
12960
- const { server_name, description, tools: regTools, persistent: regPersistent } = args ?? {};
12961
- if (!server_name || !description || !regTools?.length)
12962
- return text("mesh_mcp_register: `server_name`, `description`, and `tools` required", true);
12963
- const client = allClients()[0];
12964
- if (!client)
12965
- return text("mesh_mcp_register: not connected", true);
12966
- const result = await client.mcpRegister(server_name, description, regTools, regPersistent);
12967
- if (!result)
12968
- return text("mesh_mcp_register: broker did not acknowledge", true);
12969
- const persistLabel = regPersistent ? " (persistent — survives disconnect)" : "";
12970
- return text(`Registered MCP server "${result.serverName}" with ${result.toolCount} tool(s)${persistLabel}. Other peers can now call its tools via mesh_tool_call.`);
12971
- }
12972
- case "mesh_mcp_list": {
12973
- const client = allClients()[0];
12974
- if (!client)
12975
- return text("mesh_mcp_list: not connected", true);
12976
- const servers = await client.mcpList();
12977
- if (servers.length === 0)
12978
- return text("No MCP servers registered in the mesh.");
12979
- const lines = servers.map((s) => {
12980
- const toolList = s.tools.map((t) => ` - **${t.name}**: ${t.description}`).join(`
12981
- `);
12982
- const status = s.online === false ? ` [OFFLINE${s.offlineSince ? ` since ${s.offlineSince}` : ""}]` : "";
12983
- return `- **${s.name}** (hosted by ${s.hostedBy})${status}: ${s.description}
12984
- ${toolList}`;
12985
- });
12986
- return text(`${servers.length} MCP server(s) in mesh:
12987
- ${lines.join(`
12988
- `)}`);
12989
- }
12990
- case "mesh_tool_call": {
12991
- const { server_name: callServer, tool_name: callTool, args: callArgs } = args ?? {};
12992
- if (!callServer || !callTool)
12993
- return text("mesh_tool_call: `server_name` and `tool_name` required", true);
12994
- const client = allClients()[0];
12995
- if (!client)
12996
- return text("mesh_tool_call: not connected", true);
12997
- const callResult = await client.mcpCall(callServer, callTool, callArgs ?? {});
12998
- if (callResult.error)
12999
- return text(`mesh_tool_call error: ${callResult.error}`, true);
13000
- return text(typeof callResult.result === "string" ? callResult.result : JSON.stringify(callResult.result, null, 2));
13001
- }
13002
- case "mesh_mcp_remove": {
13003
- const { server_name: rmServer } = args ?? {};
13004
- if (!rmServer)
13005
- return text("mesh_mcp_remove: `server_name` required", true);
13006
- const client = allClients()[0];
13007
- if (!client)
13008
- return text("mesh_mcp_remove: not connected", true);
13009
- await client.mcpUnregister(rmServer);
13010
- return text(`Unregistered MCP server "${rmServer}" from the mesh.`);
13011
- }
13012
- case "grant_file_access": {
13013
- const { fileId, to: grantTo } = args ?? {};
13014
- if (!fileId || !grantTo)
13015
- return text("grant_file_access: `fileId` and `to` required", true);
13016
- const client = allClients()[0];
13017
- if (!client)
13018
- return text("grant_file_access: not connected", true);
13019
- const peers = await client.listPeers();
13020
- const targetPeer = peers.find((p) => p.pubkey === grantTo || p.displayName === grantTo);
13021
- if (!targetPeer)
13022
- return text(`grant_file_access: peer not found: ${grantTo}`, true);
13023
- const result = await client.getFile(fileId);
13024
- if (!result)
13025
- return text("grant_file_access: file not found", true);
13026
- if (!result.encrypted)
13027
- return text("grant_file_access: file is not encrypted", true);
13028
- if (!result.sealedKey)
13029
- return text("grant_file_access: no key available (are you the owner?)", true);
13030
- const { openSealedKey: openSealedKey2, sealKeyForPeer: sealKeyForPeer2 } = await Promise.resolve().then(() => (init_file_crypto(), exports_file_crypto));
13031
- const myPubkey = client.getSessionPubkey();
13032
- const mySecret = client.getSessionSecretKey();
13033
- if (!myPubkey || !mySecret)
13034
- return text("grant_file_access: no session keypair", true);
13035
- const kf = await openSealedKey2(result.sealedKey, myPubkey, mySecret);
13036
- if (!kf)
13037
- return text("grant_file_access: cannot decrypt your own key", true);
13038
- const sealedForPeer = await sealKeyForPeer2(kf, targetPeer.pubkey);
13039
- const ok = await client.grantFileAccess(fileId, targetPeer.pubkey, sealedForPeer);
13040
- if (!ok)
13041
- return text("grant_file_access: broker did not confirm", true);
13042
- return text(`Access granted: ${targetPeer.displayName} can now download file ${fileId}`);
13043
- }
13044
- case "read_peer_file": {
13045
- const { peer: peerName, path: filePath } = args ?? {};
13046
- if (!peerName || !filePath)
13047
- return text("read_peer_file: `peer` and `path` required", true);
13048
- const client = allClients()[0];
13049
- if (!client)
13050
- return text("read_peer_file: not connected", true);
13051
- const peers = await client.listPeers();
13052
- const nameLower = peerName.toLowerCase();
13053
- let targetPubkey = null;
13054
- if (/^[0-9a-f]{64}$/.test(peerName)) {
13055
- targetPubkey = peerName;
13056
- } else {
13057
- const match = peers.find((p) => p.displayName.toLowerCase() === nameLower);
13058
- if (!match) {
13059
- const partials = peers.filter((p) => p.displayName.toLowerCase().includes(nameLower));
13060
- if (partials.length === 1) {
13061
- targetPubkey = partials[0].pubkey;
13062
- } else {
13063
- const names = peers.map((p) => p.displayName).join(", ");
13064
- return text(`read_peer_file: peer "${peerName}" not found. Online: ${names || "(none)"}`, true);
13065
- }
13066
- } else {
13067
- targetPubkey = match.pubkey;
13068
- }
13069
- }
13070
- const resolvedPeer = peers.find((p) => p.pubkey === targetPubkey);
13071
- const isLocal = resolvedPeer?.hostname && resolvedPeer.hostname === __require("os").hostname();
13072
- let localHint = "";
13073
- if (isLocal && resolvedPeer?.cwd) {
13074
- const directPath = __require("path").resolve(resolvedPeer.cwd, filePath);
13075
- localHint = `
13076
-
13077
- > **Hint:** This peer is LOCAL (same machine). Next time, read directly: \`${directPath}\` — faster, no size limit.
13078
-
13079
- `;
13080
- }
13081
- const result = await client.requestFile(targetPubkey, filePath);
13082
- if (result.error)
13083
- return text(`read_peer_file: ${result.error}`, true);
13084
- if (!result.content)
13085
- return text("read_peer_file: empty response from peer", true);
13086
- try {
13087
- const decoded = Buffer.from(result.content, "base64").toString("utf-8");
13088
- return text(localHint + decoded);
13089
- } catch {
13090
- return text("read_peer_file: failed to decode file content (binary file?)", true);
13091
- }
13092
- }
13093
- case "list_peer_files": {
13094
- const { peer: peerName, path: dirPath, pattern } = args ?? {};
13095
- if (!peerName)
13096
- return text("list_peer_files: `peer` required", true);
13097
- const client = allClients()[0];
13098
- if (!client)
13099
- return text("list_peer_files: not connected", true);
13100
- const peers = await client.listPeers();
13101
- const nameLower = peerName.toLowerCase();
13102
- let targetPubkey = null;
13103
- if (/^[0-9a-f]{64}$/.test(peerName)) {
13104
- targetPubkey = peerName;
13105
- } else {
13106
- const match = peers.find((p) => p.displayName.toLowerCase() === nameLower);
13107
- if (!match) {
13108
- const partials = peers.filter((p) => p.displayName.toLowerCase().includes(nameLower));
13109
- if (partials.length === 1) {
13110
- targetPubkey = partials[0].pubkey;
13111
- } else {
13112
- const names = peers.map((p) => p.displayName).join(", ");
13113
- return text(`list_peer_files: peer "${peerName}" not found. Online: ${names || "(none)"}`, true);
13114
- }
13115
- } else {
13116
- targetPubkey = match.pubkey;
13117
- }
13118
- }
13119
- const result = await client.requestDir(targetPubkey, dirPath ?? ".", pattern);
13120
- if (result.error)
13121
- return text(`list_peer_files: ${result.error}`, true);
13122
- if (!result.entries || result.entries.length === 0)
13123
- return text("No files found.");
13124
- return text(result.entries.join(`
13125
- `));
13126
- }
13127
- case "create_webhook": {
13128
- const { name: whName } = args ?? {};
13129
- if (!whName)
13130
- return text("create_webhook: `name` required", true);
13131
- const client = allClients()[0];
13132
- if (!client)
13133
- return text("create_webhook: not connected", true);
13134
- const wh = await client.createWebhook(whName);
13135
- if (!wh)
13136
- return text("create_webhook: broker did not acknowledge — check connection", true);
13137
- return text(`Webhook **${wh.name}** created.
13138
-
13139
- URL: ${wh.url}
13140
- Secret: ${wh.secret}
13141
-
13142
- External services can POST JSON to this URL. The payload will be pushed to all connected mesh peers.`);
13143
- }
13144
- case "list_webhooks": {
13145
- const client = allClients()[0];
13146
- if (!client)
13147
- return text("list_webhooks: not connected", true);
13148
- const webhooks = await client.listWebhooks();
13149
- if (webhooks.length === 0)
13150
- return text("No active webhooks.");
13151
- const lines = webhooks.map((w) => `- **${w.name}** — ${w.url} (created ${w.createdAt})`);
13152
- return text(`${webhooks.length} webhook(s):
13153
- ${lines.join(`
13154
- `)}`);
13155
- }
13156
- case "delete_webhook": {
13157
- const { name: delName } = args ?? {};
13158
- if (!delName)
13159
- return text("delete_webhook: `name` required", true);
13160
- const client = allClients()[0];
13161
- if (!client)
13162
- return text("delete_webhook: not connected", true);
13163
- const ok = await client.deleteWebhook(delName);
13164
- return text(ok ? `Webhook "${delName}" deactivated.` : `Failed to deactivate webhook "${delName}".`, !ok);
13165
- }
13166
- case "vault_set": {
13167
- const { key, value, type: vType, mount_path, description } = args ?? {};
13168
- if (!key || !value)
13169
- return text("vault_set: `key` and `value` required", true);
13170
- if (!/^[a-zA-Z0-9_.-]{1,128}$/.test(key))
13171
- return text("vault_set: `key` must be 1-128 alphanumeric/underscore/dot/dash chars", true);
13172
- if (mount_path && (mount_path.includes("..") || mount_path.length > 512))
13173
- return text("vault_set: invalid `mount_path`", true);
13174
- if (description && description.length > 500)
13175
- return text("vault_set: `description` too long (max 500 chars)", true);
13176
- const client = allClients()[0];
13177
- if (!client)
13178
- return text("vault_set: not connected", true);
13179
- const entryType = vType ?? "env";
13180
- let plaintextBytes;
13181
- if (entryType === "file") {
13182
- const { existsSync: existsSync20, readFileSync: readFileSync13 } = await import("node:fs");
13183
- if (!existsSync20(value))
13184
- return text(`vault_set: file not found: ${value}`, true);
13185
- plaintextBytes = new Uint8Array(readFileSync13(value));
13186
- } else {
13187
- plaintextBytes = new TextEncoder().encode(value);
13188
- }
13189
- const { encryptFile: encryptFile2, sealKeyForPeer: sealKeyForPeer2 } = await Promise.resolve().then(() => (init_file_crypto(), exports_file_crypto));
13190
- const { ciphertext, nonce, key: kf } = await encryptFile2(plaintextBytes);
13191
- const sealedKey = await sealKeyForPeer2(kf, client.getMeshPubkey());
13192
- const { ensureSodium: ensureSodium3 } = await Promise.resolve().then(() => (init_keypair(), exports_keypair));
13193
- const sodium4 = await ensureSodium3();
13194
- const ciphertextB64 = sodium4.to_base64(ciphertext, sodium4.base64_variants.ORIGINAL);
13195
- const ok = await client.vaultSet(key, ciphertextB64, nonce, sealedKey, entryType, mount_path, description);
13196
- if (!ok)
13197
- return text("vault_set: broker did not acknowledge", true);
13198
- return text(`Vault entry "${key}" stored (${entryType}, E2E encrypted).`);
13199
- }
13200
- case "vault_list": {
13201
- const client = allClients()[0];
13202
- if (!client)
13203
- return text("vault_list: not connected", true);
13204
- const entries = await client.vaultList();
13205
- if (entries.length === 0)
13206
- return text("Vault is empty.");
13207
- const lines = entries.map((e) => `- **${e.key}** (${e.entry_type}${e.mount_path ? ` → ${e.mount_path}` : ""})${e.description ? ` — ${e.description}` : ""} (${e.updated_at})`);
13208
- return text(`${entries.length} vault entry(s):
13209
- ${lines.join(`
13210
- `)}`);
13211
- }
13212
- case "vault_delete": {
13213
- const { key } = args ?? {};
13214
- if (!key)
13215
- return text("vault_delete: `key` required", true);
13216
- const client = allClients()[0];
13217
- if (!client)
13218
- return text("vault_delete: not connected", true);
13219
- const ok = await client.vaultDelete(key);
13220
- return text(ok ? `Vault entry "${key}" deleted.` : `Vault entry "${key}" not found.`);
13221
- }
13222
- case "mesh_mcp_deploy": {
13223
- const { server_name, file_id, git_url, git_branch, npx_package, env: deployEnv, runtime, memory_mb, network_allow, scope } = args ?? {};
13224
- if (!server_name)
13225
- return text("mesh_mcp_deploy: `server_name` required", true);
13226
- if (!file_id && !git_url && !npx_package)
13227
- return text("mesh_mcp_deploy: one of `file_id`, `git_url`, or `npx_package` required", true);
13228
- const client = allClients()[0];
13229
- if (!client)
13230
- return text("mesh_mcp_deploy: not connected", true);
13231
- const source = npx_package ? { type: "npx", package: npx_package } : file_id ? { type: "zip", file_id } : { type: "git", url: git_url, branch: git_branch };
13232
- const resolvedEnv = {};
13233
- const vaultResolved = [];
13234
- if (deployEnv) {
13235
- const vaultRefs = [];
13236
- for (const [envKey, envVal] of Object.entries(deployEnv)) {
13237
- if (typeof envVal === "string" && envVal.startsWith("$vault:")) {
13238
- const parts = envVal.slice(7).split(":");
13239
- const vaultKey = parts[0];
13240
- const isFile = parts[1] === "file";
13241
- const mountPath = isFile ? parts.slice(2).join(":") : undefined;
13242
- vaultRefs.push({ envKey, vaultKey, isFile, mountPath });
13243
- } else {
13244
- resolvedEnv[envKey] = envVal;
13245
- }
13246
- }
13247
- if (vaultRefs.length > 0) {
13248
- const { openSealedKey: openSealedKey2, decryptFile: decryptFile2 } = await Promise.resolve().then(() => (init_file_crypto(), exports_file_crypto));
13249
- const { ensureSodium: ensureSodium3 } = await Promise.resolve().then(() => (init_keypair(), exports_keypair));
13250
- const sodium4 = await ensureSodium3();
13251
- const keys = vaultRefs.map((r) => r.vaultKey);
13252
- const encryptedEntries = await client.vaultGet(keys);
13253
- for (const ref of vaultRefs) {
13254
- const entry = encryptedEntries.find((e) => e.key === ref.vaultKey);
13255
- if (!entry)
13256
- return text(`mesh_mcp_deploy: a referenced vault key was not found. Use vault_set first.`, true);
13257
- const kf = await openSealedKey2(entry.sealed_key, client.getMeshPubkey(), client.getMeshSecretKey());
13258
- if (!kf)
13259
- return text(`mesh_mcp_deploy: failed to decrypt a vault entry — wrong keypair?`, true);
13260
- const ciphertextBytes = sodium4.from_base64(entry.ciphertext, sodium4.base64_variants.ORIGINAL);
13261
- const plainBytes = await decryptFile2(ciphertextBytes, entry.nonce, kf);
13262
- if (!plainBytes)
13263
- return text(`mesh_mcp_deploy: failed to decrypt a vault entry — data may be corrupted`, true);
13264
- if (ref.isFile && ref.mountPath) {
13265
- resolvedEnv[ref.envKey] = `__vault_file__:${ref.mountPath}:${sodium4.to_base64(plainBytes, sodium4.base64_variants.ORIGINAL)}`;
13266
- } else {
13267
- resolvedEnv[ref.envKey] = new TextDecoder().decode(plainBytes);
13268
- }
13269
- vaultResolved.push(ref.vaultKey);
13270
- }
13271
- }
13272
- }
13273
- const config2 = {};
13274
- if (Object.keys(resolvedEnv).length > 0 || deployEnv && Object.keys(deployEnv).length > 0) {
13275
- config2.env = Object.keys(resolvedEnv).length > 0 ? resolvedEnv : deployEnv;
13276
- }
13277
- if (runtime)
13278
- config2.runtime = runtime;
13279
- if (memory_mb)
13280
- config2.memory_mb = memory_mb;
13281
- if (network_allow)
13282
- config2.network_allow = network_allow;
13283
- const result = await client.mcpDeploy(server_name, source, Object.keys(config2).length > 0 ? config2 : undefined, scope);
13284
- const toolList = result.tools?.map((t) => ` - ${t.name}: ${t.description}`).join(`
13285
- `) ?? " (pending)";
13286
- let vaultNote = "";
13287
- if (vaultResolved.length > 0) {
13288
- vaultNote = `
13289
-
13290
- Vault keys resolved: ${vaultResolved.join(", ")} (decrypted client-side, sent over TLS)`;
13291
- }
13292
- return text(`Deployed "${server_name}" (status: ${result.status}).
13293
-
13294
- Tools:
13295
- ${toolList}
13296
-
13297
- Default scope: peer (private). Use mesh_mcp_scope to share.${vaultNote}`);
13298
- }
13299
- case "mesh_mcp_undeploy": {
13300
- const { server_name } = args ?? {};
13301
- if (!server_name)
13302
- return text("mesh_mcp_undeploy: `server_name` required", true);
13303
- const client = allClients()[0];
13304
- if (!client)
13305
- return text("mesh_mcp_undeploy: not connected", true);
13306
- const ok = await client.mcpUndeploy(server_name);
13307
- return text(ok ? `Service "${server_name}" undeployed.` : `Failed to undeploy "${server_name}".`);
13308
- }
13309
- case "mesh_mcp_update": {
13310
- const { server_name } = args ?? {};
13311
- if (!server_name)
13312
- return text("mesh_mcp_update: `server_name` required", true);
13313
- const client = allClients()[0];
13314
- if (!client)
13315
- return text("mesh_mcp_update: not connected", true);
13316
- const result = await client.mcpUpdate(server_name);
13317
- return text(`Updated "${server_name}" (status: ${result.status}).`);
13318
- }
13319
- case "mesh_mcp_logs": {
13320
- const { server_name, lines: logLines } = args ?? {};
13321
- if (!server_name)
13322
- return text("mesh_mcp_logs: `server_name` required", true);
13323
- const client = allClients()[0];
13324
- if (!client)
13325
- return text("mesh_mcp_logs: not connected", true);
13326
- const logs = await client.mcpLogs(server_name, logLines);
13327
- if (logs.length === 0)
13328
- return text(`No logs for "${server_name}".`);
13329
- return text(`Logs for "${server_name}" (${logs.length} lines):
13330
- \`\`\`
13331
- ${logs.join(`
13332
- `)}
13333
- \`\`\``);
13334
- }
13335
- case "mesh_mcp_scope": {
13336
- const { server_name, scope } = args ?? {};
13337
- if (!server_name)
13338
- return text("mesh_mcp_scope: `server_name` required", true);
13339
- const client = allClients()[0];
13340
- if (!client)
13341
- return text("mesh_mcp_scope: not connected", true);
13342
- const result = await client.mcpScope(server_name, scope);
13343
- if (scope !== undefined) {
13344
- return text(`Scope for "${server_name}" updated to: ${JSON.stringify(result.scope)}`);
13345
- }
13346
- return text(`**${server_name}** scope: ${JSON.stringify(result.scope)}
13347
- Deployed by: ${result.deployed_by}`);
13348
- }
13349
- case "mesh_mcp_schema": {
13350
- const { server_name, tool_name } = args ?? {};
13351
- if (!server_name)
13352
- return text("mesh_mcp_schema: `server_name` required", true);
13353
- const client = allClients()[0];
13354
- if (!client)
13355
- return text("mesh_mcp_schema: not connected", true);
13356
- const tools = await client.mcpServiceSchema(server_name, tool_name);
13357
- if (tools.length === 0)
13358
- return text(`No tools found for "${server_name}"${tool_name ? ` (tool: ${tool_name})` : ""}.`);
13359
- const lines = tools.map((t) => `### ${t.name}
13360
- ${t.description}
13361
- \`\`\`json
13362
- ${JSON.stringify(t.inputSchema, null, 2)}
13363
- \`\`\``);
13364
- return text(`Tools for "${server_name}":
13365
-
13366
- ${lines.join(`
13367
-
13368
- `)}`);
13369
- }
13370
- case "mesh_mcp_catalog": {
13371
- const client = allClients()[0];
13372
- if (!client)
13373
- return text("mesh_mcp_catalog: not connected", true);
13374
- const services = await client.mcpCatalog();
13375
- if (services.length === 0)
13376
- return text("No services deployed in the mesh.");
13377
- const lines = services.map((s) => {
13378
- const scopeStr = typeof s.scope === "string" ? s.scope : JSON.stringify(s.scope);
13379
- return `- **${s.name}** (${s.type}, ${s.status}) — ${s.description}
13380
- ${s.tool_count} tools | scope: ${scopeStr} | by ${s.deployed_by} | ${s.source_type}${s.runtime ? ` (${s.runtime})` : ""}`;
13381
- });
13382
- return text(`${services.length} service(s) in mesh:
13383
-
13384
- ${lines.join(`
13385
- `)}`);
13386
- }
13387
- case "mesh_skill_deploy": {
13388
- const { file_id, git_url, git_branch } = args ?? {};
13389
- if (!file_id && !git_url)
13390
- return text("mesh_skill_deploy: either `file_id` or `git_url` required", true);
13391
- const client = allClients()[0];
13392
- if (!client)
13393
- return text("mesh_skill_deploy: not connected", true);
13394
- const source = file_id ? { type: "zip", file_id } : { type: "git", url: git_url, branch: git_branch };
13395
- const result = await client.skillDeploy(source);
13396
- return text(`Skill "${result.name}" deployed.
13397
- Files: ${result.files.join(", ")}`);
13398
- }
13399
- case "mesh_watch": {
13400
- const { url, mode, extract, interval, notify_on, headers, label } = args ?? {};
13401
- if (!url)
13402
- return text("mesh_watch: `url` required", true);
13403
- const client = allClients()[0];
13404
- if (!client)
13405
- return text("mesh_watch: not connected", true);
13406
- const result = await client.watch(url, { mode, extract, interval, notify_on, headers, label });
13407
- if (result.error)
13408
- return text(`mesh_watch: ${result.error}`, true);
13409
- return text(`Watching "${label ?? url}" (${result.mode}, every ${result.interval}s)
13410
- Watch ID: ${result.watchId}`);
13411
- }
13412
- case "mesh_unwatch": {
13413
- const { watch_id } = args ?? {};
13414
- if (!watch_id)
13415
- return text("mesh_unwatch: `watch_id` required", true);
13416
- const client = allClients()[0];
13417
- if (!client)
13418
- return text("mesh_unwatch: not connected", true);
13419
- await client.unwatch(watch_id);
13420
- return text(`Watch ${watch_id} stopped.`);
13421
- }
13422
- case "mesh_watches": {
13423
- const client = allClients()[0];
13424
- if (!client)
13425
- return text("mesh_watches: not connected", true);
13426
- const watches = await client.watchList();
13427
- if (watches.length === 0)
13428
- return text("No active watches.");
13429
- const lines = watches.map((w) => `- **${w.id}** ${w.label ? `(${w.label}) ` : ""}${w.url}
13430
- mode: ${w.mode} | interval: ${w.interval}s | last: ${w.lastValue?.slice(0, 30) ?? "pending"} | checked: ${w.lastCheck ?? "never"}`);
13431
- return text(`${watches.length} active watch(es):
13432
-
13433
- ${lines.join(`
13434
- `)}`);
13435
- }
13436
- default:
13437
- return text(`Unknown tool: ${name}`, true);
13438
- }
13439
- });
13440
10880
  const transport = new StdioServerTransport;
13441
10881
  await server.connect(transport);
13442
10882
  const bridges = [];
@@ -13744,41 +11184,13 @@ async function startServiceProxy(serviceName) {
13744
11184
  process.on("SIGTERM", shutdown);
13745
11185
  process.on("SIGINT", shutdown);
13746
11186
  }
13747
- var peerNameCache, peerNameCacheAge = 0, CACHE_TTL_MS = 30000, DEPRECATED_TOOL_HINTS, warnedTools;
11187
+ var peerNameCache, peerNameCacheAge = 0, CACHE_TTL_MS = 30000;
13748
11188
  var init_server2 = __esm(() => {
13749
11189
  init_definitions();
13750
11190
  init_facade();
13751
11191
  init_facade8();
13752
11192
  init_server();
13753
11193
  peerNameCache = new Map;
13754
- DEPRECATED_TOOL_HINTS = {
13755
- send_message: "claudemesh send <to> <msg> [--mesh X] [--priority Y]",
13756
- list_peers: "claudemesh peers --json",
13757
- check_messages: "claudemesh inbox --json",
13758
- message_status: "claudemesh msg-status <id>",
13759
- set_profile: "claudemesh profile --avatar X --bio Y",
13760
- set_status: "claudemesh status set <state>",
13761
- set_summary: "claudemesh summary <text>",
13762
- set_visible: "claudemesh visible <true|false>",
13763
- join_group: "claudemesh group join @<name> [--role X]",
13764
- leave_group: "claudemesh group leave @<name>",
13765
- get_state: "claudemesh state get <key> --json",
13766
- set_state: "claudemesh state set <key> <value>",
13767
- list_state: "claudemesh state list --json",
13768
- remember: "claudemesh remember <text>",
13769
- recall: "claudemesh recall <query> --json",
13770
- forget: "claudemesh forget <id>",
13771
- schedule_reminder: "claudemesh remind <msg> --in/--at/--cron",
13772
- list_scheduled: "claudemesh remind list --json",
13773
- cancel_scheduled: "claudemesh remind cancel <id>",
13774
- mesh_info: "claudemesh info --json",
13775
- mesh_stats: "claudemesh stats --json",
13776
- mesh_clock: "claudemesh clock --json",
13777
- ping_mesh: "claudemesh ping",
13778
- claim_task: "claudemesh task claim <id>",
13779
- complete_task: "claudemesh task complete <id>"
13780
- };
13781
- warnedTools = new Set;
13782
11194
  });
13783
11195
 
13784
11196
  // src/commands/mcp.ts
@@ -14039,11 +11451,425 @@ init_styles();
14039
11451
  function renderVersion() {
14040
11452
  return " " + boldOrange("claudemesh") + " v" + VERSION;
14041
11453
  }
11454
+ // src/cli/policy-classify.ts
11455
+ var SKIP = new Set([
11456
+ "",
11457
+ "help",
11458
+ "version",
11459
+ "login",
11460
+ "register",
11461
+ "logout",
11462
+ "whoami",
11463
+ "install",
11464
+ "uninstall",
11465
+ "doctor",
11466
+ "sync",
11467
+ "completions",
11468
+ "url-handler",
11469
+ "status-line",
11470
+ "backup",
11471
+ "restore",
11472
+ "upgrade",
11473
+ "update",
11474
+ "list",
11475
+ "ls",
11476
+ "launch",
11477
+ "connect",
11478
+ "status",
11479
+ "test",
11480
+ "mcp",
11481
+ "hook",
11482
+ "seed-test-mesh",
11483
+ "disconnect"
11484
+ ]);
11485
+ var WRITE_VERBS = new Set([
11486
+ "create",
11487
+ "send",
11488
+ "remember",
11489
+ "forget",
11490
+ "remind",
11491
+ "schedule",
11492
+ "summary",
11493
+ "visible",
11494
+ "join",
11495
+ "leave",
11496
+ "kick",
11497
+ "ban",
11498
+ "unban",
11499
+ "disconnect",
11500
+ "delete",
11501
+ "rename",
11502
+ "share",
11503
+ "invite",
11504
+ "store",
11505
+ "publish",
11506
+ "execute",
11507
+ "set",
11508
+ "remove",
11509
+ "pause",
11510
+ "resume",
11511
+ "claim",
11512
+ "complete",
11513
+ "grant",
11514
+ "revoke",
11515
+ "block",
11516
+ "call"
11517
+ ]);
11518
+ function isWrite(verb) {
11519
+ return WRITE_VERBS.has(verb);
11520
+ }
11521
+ function classifyInvocation(command, positionals) {
11522
+ if (SKIP.has(command))
11523
+ return null;
11524
+ const sub = positionals[0] ?? "";
11525
+ switch (command) {
11526
+ case "peer": {
11527
+ const verb = sub || "list";
11528
+ return { resource: "peer", verb, isWrite: isWrite(verb) };
11529
+ }
11530
+ case "message": {
11531
+ const verb = sub || "inbox";
11532
+ return { resource: "message", verb, isWrite: isWrite(verb) };
11533
+ }
11534
+ case "memory": {
11535
+ const verb = sub || "recall";
11536
+ return { resource: "memory", verb, isWrite: isWrite(verb) };
11537
+ }
11538
+ case "profile": {
11539
+ if (!sub)
11540
+ return { resource: "profile", verb: "view", isWrite: false };
11541
+ if (sub === "status") {
11542
+ return positionals[1] === "set" ? { resource: "profile", verb: "status", isWrite: true } : { resource: "profile", verb: "view", isWrite: false };
11543
+ }
11544
+ return { resource: "profile", verb: sub, isWrite: true };
11545
+ }
11546
+ case "schedule": {
11547
+ const verb = sub || "list";
11548
+ return { resource: "schedule", verb, isWrite: verb !== "list" };
11549
+ }
11550
+ case "group": {
11551
+ return { resource: "group", verb: sub || "list", isWrite: sub === "join" || sub === "leave" };
11552
+ }
11553
+ case "task": {
11554
+ return { resource: "task", verb: sub || "list", isWrite: isWrite(sub) };
11555
+ }
11556
+ case "vector":
11557
+ case "graph":
11558
+ case "context":
11559
+ case "stream":
11560
+ case "sql":
11561
+ case "skill":
11562
+ case "vault":
11563
+ case "watch":
11564
+ case "webhook":
11565
+ case "file":
11566
+ case "mesh-mcp":
11567
+ case "clock": {
11568
+ const verb = sub || "list";
11569
+ return { resource: command, verb, isWrite: isWrite(verb) };
11570
+ }
11571
+ case "state": {
11572
+ const verb = sub === "set" ? "set" : sub === "list" ? "list" : "get";
11573
+ return { resource: "state", verb, isWrite: verb === "set" };
11574
+ }
11575
+ }
11576
+ switch (command) {
11577
+ case "create":
11578
+ case "new":
11579
+ return { resource: "mesh", verb: "create", isWrite: true };
11580
+ case "join":
11581
+ case "add":
11582
+ return { resource: "mesh", verb: "join", isWrite: true };
11583
+ case "delete":
11584
+ case "rm":
11585
+ return { resource: "mesh", verb: "delete", isWrite: true };
11586
+ case "rename":
11587
+ return { resource: "mesh", verb: "rename", isWrite: true };
11588
+ case "share":
11589
+ case "invite":
11590
+ return { resource: "mesh", verb: "share", isWrite: true };
11591
+ case "info":
11592
+ return { resource: "mesh", verb: "info", isWrite: false };
11593
+ case "peers":
11594
+ return { resource: "peer", verb: "list", isWrite: false };
11595
+ case "kick":
11596
+ return { resource: "peer", verb: "kick", isWrite: true };
11597
+ case "ban":
11598
+ return { resource: "peer", verb: "ban", isWrite: true };
11599
+ case "unban":
11600
+ return { resource: "peer", verb: "unban", isWrite: true };
11601
+ case "bans":
11602
+ return { resource: "peer", verb: "bans", isWrite: false };
11603
+ case "verify":
11604
+ return { resource: "peer", verb: "verify", isWrite: false };
11605
+ case "send":
11606
+ return { resource: "message", verb: "send", isWrite: true };
11607
+ case "inbox":
11608
+ return { resource: "message", verb: "inbox", isWrite: false };
11609
+ case "msg-status":
11610
+ return { resource: "message", verb: "status", isWrite: false };
11611
+ case "remember":
11612
+ return { resource: "memory", verb: "remember", isWrite: true };
11613
+ case "recall":
11614
+ return { resource: "memory", verb: "recall", isWrite: false };
11615
+ case "forget":
11616
+ return { resource: "memory", verb: "forget", isWrite: true };
11617
+ case "remind":
11618
+ return { resource: "schedule", verb: "msg", isWrite: true };
11619
+ case "summary":
11620
+ return { resource: "profile", verb: "summary", isWrite: true };
11621
+ case "visible":
11622
+ return { resource: "profile", verb: "visible", isWrite: true };
11623
+ case "stats":
11624
+ return { resource: "mesh", verb: "stats", isWrite: false };
11625
+ case "ping":
11626
+ return { resource: "mesh", verb: "ping", isWrite: false };
11627
+ case "grant":
11628
+ return { resource: "grant", verb: "grant", isWrite: true };
11629
+ case "revoke":
11630
+ return { resource: "grant", verb: "revoke", isWrite: true };
11631
+ case "block":
11632
+ return { resource: "grant", verb: "block", isWrite: true };
11633
+ case "grants":
11634
+ return { resource: "grant", verb: "list", isWrite: false };
11635
+ }
11636
+ return null;
11637
+ }
11638
+
11639
+ // src/services/policy/index.ts
11640
+ import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from "node:fs";
11641
+ import { homedir } from "node:os";
11642
+ import { join, dirname } from "node:path";
11643
+ import { createInterface } from "node:readline";
11644
+ var DEFAULT_POLICY = {
11645
+ default: "allow",
11646
+ rules: [
11647
+ { resource: "peer", verb: "kick", decision: "prompt", reason: "ends a peer's session" },
11648
+ { resource: "peer", verb: "ban", decision: "prompt", reason: "permanently revokes membership" },
11649
+ { resource: "peer", verb: "disconnect", decision: "prompt", reason: "disconnects a peer" },
11650
+ { resource: "file", verb: "delete", decision: "prompt", reason: "deletes a shared file" },
11651
+ { resource: "vector", verb: "delete", decision: "prompt", reason: "removes vector entries" },
11652
+ { resource: "vault", verb: "delete", decision: "prompt", reason: "deletes encrypted secret" },
11653
+ { resource: "memory", verb: "forget", decision: "prompt", reason: "removes shared memory" },
11654
+ { resource: "skill", verb: "remove", decision: "prompt", reason: "removes published skill" },
11655
+ { resource: "webhook", verb: "delete", decision: "prompt", reason: "removes webhook integration" },
11656
+ { resource: "watch", verb: "remove", decision: "prompt", reason: "removes URL watcher" },
11657
+ { resource: "sql", verb: "execute", decision: "prompt", reason: "raw SQL write to mesh DB" },
11658
+ { resource: "graph", verb: "execute", decision: "prompt", reason: "graph mutation" },
11659
+ { resource: "mesh", verb: "delete", decision: "prompt", reason: "deletes the mesh for everyone" }
11660
+ ]
11661
+ };
11662
+ var USER_POLICY_PATH = join(homedir(), ".claudemesh", "policy.yaml");
11663
+ var AUDIT_LOG_PATH = join(homedir(), ".claudemesh", "audit.log");
11664
+ function parsePolicyYaml(text) {
11665
+ const trimmed = text.trim();
11666
+ if (trimmed.startsWith("{")) {
11667
+ return JSON.parse(trimmed);
11668
+ }
11669
+ const policy = { default: "allow", rules: [] };
11670
+ const lines = text.split(`
11671
+ `);
11672
+ let cur = null;
11673
+ const flush = () => {
11674
+ if (cur && cur.resource && cur.verb && cur.decision) {
11675
+ policy.rules.push(cur);
11676
+ }
11677
+ cur = null;
11678
+ };
11679
+ for (const raw of lines) {
11680
+ const line = raw.replace(/#.*$/, "").trimEnd();
11681
+ if (!line.trim())
11682
+ continue;
11683
+ const top = line.match(/^(default):\s*(\S+)/);
11684
+ if (top) {
11685
+ policy.default = top[2];
11686
+ continue;
11687
+ }
11688
+ if (/^rules\s*:/.test(line))
11689
+ continue;
11690
+ if (/^\s*-\s/.test(line)) {
11691
+ flush();
11692
+ cur = {};
11693
+ const m = line.match(/-\s*(\w+)\s*:\s*(.*)$/);
11694
+ if (m)
11695
+ cur[m[1]] = parseValue(m[2]);
11696
+ continue;
11697
+ }
11698
+ const kv = line.match(/^\s+(\w+)\s*:\s*(.*)$/);
11699
+ if (kv && cur) {
11700
+ cur[kv[1]] = parseValue(kv[2]);
11701
+ }
11702
+ }
11703
+ flush();
11704
+ return policy;
11705
+ }
11706
+ function parseValue(raw) {
11707
+ const v = raw.trim();
11708
+ if (!v)
11709
+ return "";
11710
+ if (v.startsWith("[") && v.endsWith("]")) {
11711
+ return v.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
11712
+ }
11713
+ const q = v.match(/^["'](.*)["']$/);
11714
+ if (q)
11715
+ return q[1];
11716
+ if (v === "true")
11717
+ return true;
11718
+ if (v === "false")
11719
+ return false;
11720
+ if (/^-?\d+(\.\d+)?$/.test(v))
11721
+ return Number(v);
11722
+ return v;
11723
+ }
11724
+ function serializePolicyYaml(p) {
11725
+ let out = `# claudemesh policy file
11726
+ `;
11727
+ out += `# Edit to change which CLI ops require confirmation or are forbidden.
11728
+ `;
11729
+ out += `# Decisions: allow | prompt | deny
11730
+ `;
11731
+ out += `# See: ~/.claude/skills/claudemesh/SKILL.md or claudemesh policy --help
11732
+
11733
+ `;
11734
+ out += `default: ${p.default}
11735
+
11736
+ `;
11737
+ out += `rules:
11738
+ `;
11739
+ for (const r of p.rules) {
11740
+ out += ` - resource: ${r.resource}
11741
+ `;
11742
+ out += ` verb: ${r.verb}
11743
+ `;
11744
+ if (r.mesh)
11745
+ out += ` mesh: ${r.mesh}
11746
+ `;
11747
+ if (r.peers)
11748
+ out += ` peers: [${r.peers.map((p2) => `"${p2}"`).join(", ")}]
11749
+ `;
11750
+ out += ` decision: ${r.decision}
11751
+ `;
11752
+ if (r.reason)
11753
+ out += ` reason: "${r.reason}"
11754
+ `;
11755
+ }
11756
+ return out;
11757
+ }
11758
+ function loadPolicy(opts) {
11759
+ const path = opts?.policyPath ?? opts?.envOverride ?? process.env.CLAUDEMESH_POLICY ?? USER_POLICY_PATH;
11760
+ if (!existsSync(path)) {
11761
+ if (path === USER_POLICY_PATH) {
11762
+ try {
11763
+ mkdirSync(dirname(path), { recursive: true });
11764
+ writeFileSync(path, serializePolicyYaml(DEFAULT_POLICY), "utf-8");
11765
+ } catch {}
11766
+ }
11767
+ return DEFAULT_POLICY;
11768
+ }
11769
+ try {
11770
+ return parsePolicyYaml(readFileSync(path, "utf-8"));
11771
+ } catch (e) {
11772
+ process.stderr.write(`[claudemesh] policy: failed to parse ${path}: ${e instanceof Error ? e.message : String(e)}
11773
+ `);
11774
+ return DEFAULT_POLICY;
11775
+ }
11776
+ }
11777
+ function matches(rule, value) {
11778
+ if (rule === "*")
11779
+ return true;
11780
+ return rule === value;
11781
+ }
11782
+ function evaluate(policy, ctx) {
11783
+ if (ctx.mode === "yolo")
11784
+ return { decision: "allow", reason: "yolo mode" };
11785
+ if ((ctx.mode === "plan" || ctx.mode === "read-only") && ctx.isWrite) {
11786
+ return { decision: "deny", reason: `${ctx.mode} mode forbids writes` };
11787
+ }
11788
+ for (const r of policy.rules) {
11789
+ if (!matches(r.resource, ctx.resource))
11790
+ continue;
11791
+ if (!matches(r.verb, ctx.verb))
11792
+ continue;
11793
+ if (r.mesh && ctx.mesh && r.mesh !== ctx.mesh)
11794
+ continue;
11795
+ return { decision: r.decision, reason: r.reason, matchedRule: r };
11796
+ }
11797
+ return { decision: policy.default };
11798
+ }
11799
+ function audit(record) {
11800
+ try {
11801
+ mkdirSync(dirname(AUDIT_LOG_PATH), { recursive: true });
11802
+ appendFileSync(AUDIT_LOG_PATH, JSON.stringify({ ts: new Date().toISOString(), ...record }) + `
11803
+ `, "utf-8");
11804
+ } catch {}
11805
+ }
11806
+ async function confirmPrompt(message) {
11807
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
11808
+ return false;
11809
+ }
11810
+ return new Promise((resolve) => {
11811
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
11812
+ rl.question(`${message} [y/N] `, (answer) => {
11813
+ rl.close();
11814
+ const a = answer.trim().toLowerCase();
11815
+ resolve(a === "y" || a === "yes");
11816
+ });
11817
+ });
11818
+ }
11819
+ async function gate(ctx, opts) {
11820
+ const policy = loadPolicy(opts);
11821
+ const result = evaluate(policy, ctx);
11822
+ audit({ ...ctx, decision: result.decision, reason: result.reason });
11823
+ if (result.decision === "allow")
11824
+ return true;
11825
+ if (result.decision === "deny") {
11826
+ process.stderr.write(`
11827
+ ✘ blocked by policy: ${ctx.resource} ${ctx.verb}` + (result.reason ? ` — ${result.reason}` : "") + `
11828
+ edit ${USER_POLICY_PATH} to change.
11829
+ `);
11830
+ return false;
11831
+ }
11832
+ if (ctx.yes)
11833
+ return true;
11834
+ const reason = result.reason ? ` — ${result.reason}` : "";
11835
+ const confirmed = await confirmPrompt(`
11836
+ ⚠ ${ctx.resource} ${ctx.verb}${reason}. Continue?`);
11837
+ if (!confirmed) {
11838
+ process.stderr.write(` cancelled.
11839
+ `);
11840
+ audit({ ...ctx, decision: "cancelled-at-prompt" });
11841
+ }
11842
+ return confirmed;
11843
+ }
14042
11844
 
14043
11845
  // src/entrypoints/cli.ts
14044
11846
  installSignalHandlers();
14045
11847
  installErrorHandlers();
14046
11848
  var { command, positionals, flags } = parseArgv(process.argv);
11849
+ function resolveApprovalMode() {
11850
+ const raw = flags["approval-mode"] ?? process.env.CLAUDEMESH_APPROVAL_MODE ?? null;
11851
+ if (raw === "plan" || raw === "read-only" || raw === "write" || raw === "yolo")
11852
+ return raw;
11853
+ if (flags.y || flags.yes)
11854
+ return "yolo";
11855
+ return "write";
11856
+ }
11857
+ async function policyGate() {
11858
+ const cls = classifyInvocation(command, positionals);
11859
+ if (!cls)
11860
+ return true;
11861
+ const mode = resolveApprovalMode();
11862
+ const yes = !!flags.y || !!flags.yes || mode === "yolo";
11863
+ const ok = await gate({
11864
+ resource: cls.resource,
11865
+ verb: cls.verb,
11866
+ mesh: flags.mesh,
11867
+ mode,
11868
+ isWrite: cls.isWrite,
11869
+ yes
11870
+ }, { policyPath: flags.policy });
11871
+ return ok;
11872
+ }
14047
11873
  var HELP = `
14048
11874
  claudemesh — peer mesh for Claude Code sessions
14049
11875
  ${VERSION}
@@ -14150,7 +11976,9 @@ Flags
14150
11976
  --help, -h show this help
14151
11977
  --json machine-readable output
14152
11978
  --mesh <slug> override mesh selection
14153
- -y, --yes skip confirmations
11979
+ --approval-mode <mode> plan | read-only | write (default) | yolo
11980
+ --policy <path> override policy file
11981
+ -y, --yes skip confirmations (= --approval-mode yolo)
14154
11982
  -q, --quiet suppress non-essential output
14155
11983
  `;
14156
11984
  async function main() {
@@ -14162,6 +11990,8 @@ async function main() {
14162
11990
  console.log(renderVersion());
14163
11991
  process.exit(EXIT.SUCCESS);
14164
11992
  }
11993
+ if (!await policyGate())
11994
+ process.exit(EXIT.PERMISSION_DENIED);
14165
11995
  if (!command || isInviteUrl(command)) {
14166
11996
  if (command && isInviteUrl(command)) {
14167
11997
  const { runLaunch: runLaunch3 } = await Promise.resolve().then(() => (init_launch(), exports_launch));
@@ -14836,4 +12666,4 @@ main().catch((err) => {
14836
12666
  process.exit(EXIT.INTERNAL_ERROR);
14837
12667
  });
14838
12668
 
14839
- //# debugId=4E4265490A3D56F164756E2164756E21
12669
+ //# debugId=C75596237B999F1364756E2164756E21