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.
- package/dist/entrypoints/cli.js +650 -2820
- package/dist/entrypoints/cli.js.map +9 -7
- package/dist/entrypoints/mcp.js +4 -2592
- package/dist/entrypoints/mcp.js.map +4 -4
- package/package.json +1 -1
- package/skills/claudemesh/SKILL.md +7 -0
package/dist/entrypoints/cli.js
CHANGED
|
@@ -88,7 +88,7 @@ __export(exports_urls, {
|
|
|
88
88
|
VERSION: () => VERSION,
|
|
89
89
|
URLS: () => URLS
|
|
90
90
|
});
|
|
91
|
-
var URLS, VERSION = "1.
|
|
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 =
|
|
143
|
+
home = homedir2();
|
|
144
144
|
PATHS = {
|
|
145
|
-
CONFIG_DIR: process.env.CLAUDEMESH_CONFIG_DIR ||
|
|
145
|
+
CONFIG_DIR: process.env.CLAUDEMESH_CONFIG_DIR || join2(home, ".claudemesh"),
|
|
146
146
|
get CONFIG_FILE() {
|
|
147
|
-
return
|
|
147
|
+
return join2(this.CONFIG_DIR, "config.json");
|
|
148
148
|
},
|
|
149
149
|
get AUTH_FILE() {
|
|
150
|
-
return
|
|
150
|
+
return join2(this.CONFIG_DIR, "auth.json");
|
|
151
151
|
},
|
|
152
152
|
get KEYS_DIR() {
|
|
153
|
-
return
|
|
153
|
+
return join2(this.CONFIG_DIR, "keys");
|
|
154
154
|
},
|
|
155
155
|
get LAST_USED_FILE() {
|
|
156
|
-
return
|
|
156
|
+
return join2(this.CONFIG_DIR, "last-used.json");
|
|
157
157
|
},
|
|
158
|
-
CLAUDE_JSON:
|
|
159
|
-
CLAUDE_SETTINGS:
|
|
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 (!
|
|
171
|
+
if (!existsSync2(PATHS.CONFIG_FILE))
|
|
172
172
|
return emptyConfig();
|
|
173
173
|
try {
|
|
174
|
-
const raw =
|
|
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
|
-
|
|
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
|
-
|
|
219
|
+
writeFileSync2(tmpPath, content, "utf-8");
|
|
220
220
|
} else {
|
|
221
221
|
const fd = openSync(tmpPath, "w", 384);
|
|
222
222
|
try {
|
|
223
|
-
|
|
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
|
|
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 (
|
|
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
|
|
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 (!
|
|
576
|
+
if (!existsSync4(PATHS.AUTH_FILE))
|
|
577
577
|
return null;
|
|
578
578
|
try {
|
|
579
|
-
const raw =
|
|
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
|
-
|
|
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 =
|
|
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:
|
|
1106
|
-
const { join:
|
|
1107
|
-
const { homedir:
|
|
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 =
|
|
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 =
|
|
1116
|
-
const jsonls = readdirSync(fullDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ name: f, mtime: statSync(
|
|
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:
|
|
1625
|
+
const { readFileSync: readFileSync4 } = await import("node:fs");
|
|
1638
1626
|
const { basename } = await import("node:path");
|
|
1639
|
-
const data =
|
|
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:
|
|
2326
|
-
const { readFileSync:
|
|
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(
|
|
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 =
|
|
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:
|
|
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(
|
|
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,
|
|
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(
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
3416
|
-
import { tmpdir, hostname as hostname2, homedir as
|
|
3417
|
-
import { join as
|
|
3418
|
-
import { createInterface as
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
3746
|
-
if (
|
|
3747
|
-
const claudeConfig = JSON.parse(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
3776
|
+
const claudeConfigPath = join3(homedir3(), ".claude.json");
|
|
3799
3777
|
let claudeConfig = {};
|
|
3800
3778
|
try {
|
|
3801
|
-
claudeConfig = JSON.parse(
|
|
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
|
-
|
|
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
|
-
|
|
3843
|
+
join3(homedir3(), ".local", "bin", "claude"),
|
|
3866
3844
|
"/usr/local/bin/claude",
|
|
3867
|
-
|
|
3845
|
+
join3(homedir3(), ".claude", "bin", "claude")
|
|
3868
3846
|
];
|
|
3869
3847
|
for (const c of candidates) {
|
|
3870
|
-
if (
|
|
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 =
|
|
3880
|
-
const claudeConfig = JSON.parse(
|
|
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
|
-
|
|
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
|
|
3958
|
+
import { createInterface as createInterface5 } from "node:readline";
|
|
3981
3959
|
function prompt(question) {
|
|
3982
|
-
const rl =
|
|
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
|
|
4082
|
+
import { createInterface as createInterface6 } from "node:readline";
|
|
4105
4083
|
function prompt2(q) {
|
|
4106
|
-
const rl =
|
|
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
|
|
4584
|
-
import { join as
|
|
4585
|
-
import { homedir as
|
|
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 ??
|
|
4705
|
-
const inviteFile =
|
|
4682
|
+
const configDir = env.CLAUDEMESH_CONFIG_DIR ?? join4(homedir4(), ".claudemesh");
|
|
4683
|
+
const inviteFile = join4(configDir, `invite-${payload.mesh_slug}.txt`);
|
|
4706
4684
|
try {
|
|
4707
|
-
|
|
4708
|
-
|
|
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
|
|
4790
|
+
import { createInterface as createInterface7 } from "node:readline";
|
|
4813
4791
|
function prompt3(question) {
|
|
4814
|
-
const rl =
|
|
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
|
|
6026
|
+
import { createInterface as createInterface8 } from "node:readline";
|
|
6049
6027
|
function prompt4(question) {
|
|
6050
|
-
const rl =
|
|
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
|
|
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 =
|
|
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
|
|
6397
|
-
import { join as
|
|
6374
|
+
import { homedir as homedir5 } from "node:os";
|
|
6375
|
+
import { join as join5 } from "node:path";
|
|
6398
6376
|
function socketPath(meshSlug) {
|
|
6399
|
-
return
|
|
6377
|
+
return join5(homedir5(), ".claudemesh", "sockets", `${meshSlug}.sock`);
|
|
6400
6378
|
}
|
|
6401
6379
|
function socketDir() {
|
|
6402
|
-
return
|
|
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
|
|
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 (!
|
|
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
|
|
7415
|
-
mkdirSync as
|
|
7416
|
-
readFileSync as
|
|
7417
|
-
writeFileSync as
|
|
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
|
|
7420
|
-
import { dirname as
|
|
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 (!
|
|
7402
|
+
if (!existsSync7(CLAUDE_CONFIG))
|
|
7425
7403
|
return {};
|
|
7426
|
-
const text =
|
|
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 (!
|
|
7414
|
+
if (!existsSync7(CLAUDE_CONFIG))
|
|
7437
7415
|
return;
|
|
7438
|
-
const backupDir =
|
|
7439
|
-
|
|
7416
|
+
const backupDir = join6(dirname3(CLAUDE_CONFIG), ".claude", "backups");
|
|
7417
|
+
mkdirSync4(backupDir, { recursive: true });
|
|
7440
7418
|
const ts = Date.now();
|
|
7441
|
-
const dest =
|
|
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 (!
|
|
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
|
-
|
|
7479
|
-
|
|
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(
|
|
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(
|
|
7501
|
-
const skillsDir =
|
|
7502
|
-
if (
|
|
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 =
|
|
7516
|
-
const dstDir =
|
|
7517
|
-
|
|
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(
|
|
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 =
|
|
7537
|
-
if (
|
|
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 (!
|
|
7540
|
+
if (!existsSync7(CLAUDE_SETTINGS))
|
|
7563
7541
|
return {};
|
|
7564
|
-
const text =
|
|
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
|
-
|
|
7575
|
-
|
|
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 (!
|
|
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 (!
|
|
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 (!
|
|
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(` ${
|
|
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 =
|
|
7813
|
-
CLAUDE_SETTINGS =
|
|
7814
|
-
CLAUDE_SKILLS_ROOT =
|
|
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
|
|
7870
|
-
import { join as
|
|
7871
|
-
import { homedir as
|
|
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 =
|
|
7876
|
-
const skillsDir =
|
|
7877
|
-
return
|
|
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 (
|
|
7859
|
+
if (existsSync8(PATHS.CLAUDE_JSON)) {
|
|
7882
7860
|
try {
|
|
7883
|
-
const raw =
|
|
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
|
-
|
|
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 (
|
|
7873
|
+
if (existsSync8(PATHS.CLAUDE_SETTINGS)) {
|
|
7896
7874
|
try {
|
|
7897
|
-
const raw =
|
|
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
|
-
|
|
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 =
|
|
7934
|
-
if (
|
|
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 =
|
|
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
|
|
7967
|
-
import { homedir as
|
|
7968
|
-
import { join as
|
|
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 =
|
|
7993
|
-
if (!
|
|
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(
|
|
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 =
|
|
8019
|
-
if (!
|
|
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 =
|
|
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 (!
|
|
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
|
|
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 (
|
|
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
|
|
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 =
|
|
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
|
|
8381
|
+
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
|
|
8404
8382
|
function checkMcpRegistered2() {
|
|
8405
8383
|
try {
|
|
8406
|
-
if (!
|
|
8384
|
+
if (!existsSync11(PATHS.CLAUDE_JSON)) {
|
|
8407
8385
|
return { name: "mcp-registered", ok: false, message: "~/.claude.json not found" };
|
|
8408
8386
|
}
|
|
8409
|
-
const raw =
|
|
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
|
|
8402
|
+
import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
|
|
8425
8403
|
function checkHooksRegistered2() {
|
|
8426
8404
|
try {
|
|
8427
|
-
if (!
|
|
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 =
|
|
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
|
|
8423
|
+
import { existsSync as existsSync13, statSync as statSync4 } from "node:fs";
|
|
8446
8424
|
function checkConfigPerms() {
|
|
8447
8425
|
const configFile = PATHS.CONFIG_FILE;
|
|
8448
|
-
if (!
|
|
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
|
|
8444
|
+
import { existsSync as existsSync14, readFileSync as readFileSync10 } from "node:fs";
|
|
8467
8445
|
function checkKeypairsValid() {
|
|
8468
|
-
if (!
|
|
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 =
|
|
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
|
|
8953
|
-
import { existsSync as
|
|
8954
|
-
import { join as
|
|
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 =
|
|
8962
|
-
const contents =
|
|
8963
|
-
const macOS =
|
|
8964
|
-
|
|
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
|
-
|
|
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 =
|
|
9002
|
-
|
|
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 =
|
|
9014
|
-
|
|
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 =
|
|
9026
|
-
|
|
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 =
|
|
9049
|
-
|
|
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 =
|
|
9061
|
-
if (
|
|
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 =
|
|
9068
|
-
if (
|
|
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
|
|
9114
|
-
import { join as
|
|
9115
|
-
import { homedir as
|
|
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 =
|
|
9101
|
+
const cachePath = join10(homedir10(), ".claudemesh", "peer-cache.json");
|
|
9124
9102
|
let cache = {};
|
|
9125
|
-
if (
|
|
9103
|
+
if (existsSync16(cachePath)) {
|
|
9126
9104
|
try {
|
|
9127
|
-
cache = JSON.parse(
|
|
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
|
|
9159
|
-
import { createInterface as
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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 (!
|
|
9212
|
+
if (!existsSync17(inPath)) {
|
|
9235
9213
|
console.error(` ✗ File not found: ${inPath}`);
|
|
9236
9214
|
return EXIT.NOT_FOUND;
|
|
9237
9215
|
}
|
|
9238
|
-
const blob =
|
|
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 (
|
|
9235
|
+
if (existsSync17(configPath)) {
|
|
9258
9236
|
const backupOld = `${configPath}.before-restore.${Date.now()}`;
|
|
9259
|
-
|
|
9237
|
+
writeFileSync9(backupOld, readFileSync12(configPath), { mode: 384 });
|
|
9260
9238
|
console.log(` ↻ Existing config saved to ${backupOld}`);
|
|
9261
9239
|
}
|
|
9262
|
-
|
|
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
|
|
9283
|
-
import { dirname as
|
|
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 =
|
|
9297
|
-
if (
|
|
9298
|
-
return { npm: portable, prefix:
|
|
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 =
|
|
9303
|
-
const candidate =
|
|
9304
|
-
if (
|
|
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
|
|
9367
|
-
import { homedir as
|
|
9368
|
-
import { join as
|
|
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 (!
|
|
9364
|
+
if (!existsSync19(GRANT_FILE))
|
|
9387
9365
|
return {};
|
|
9388
9366
|
try {
|
|
9389
|
-
return JSON.parse(
|
|
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 =
|
|
9396
|
-
if (!
|
|
9397
|
-
|
|
9398
|
-
|
|
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 =
|
|
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
|
|
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 (!
|
|
11443
|
-
|
|
10503
|
+
if (!existsSync20(dir)) {
|
|
10504
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
11444
10505
|
}
|
|
11445
|
-
if (
|
|
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
|
|
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
|
-
-
|
|
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=
|
|
12669
|
+
//# debugId=C75596237B999F1364756E2164756E21
|