claudemesh-cli 1.22.1 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entrypoints/cli.js +642 -759
- package/dist/entrypoints/cli.js.map +12 -14
- package/dist/entrypoints/mcp.js +448 -1942
- package/dist/entrypoints/mcp.js.map +6 -33
- package/package.json +1 -1
package/dist/entrypoints/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ var __create = Object.create;
|
|
|
4
4
|
var __getProtoOf = Object.getPrototypeOf;
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
7
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
9
|
var __toESM = (mod, isNodeMode, target) => {
|
|
9
10
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
@@ -16,6 +17,20 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
16
17
|
});
|
|
17
18
|
return to;
|
|
18
19
|
};
|
|
20
|
+
var __moduleCache = /* @__PURE__ */ new WeakMap;
|
|
21
|
+
var __toCommonJS = (from) => {
|
|
22
|
+
var entry = __moduleCache.get(from), desc;
|
|
23
|
+
if (entry)
|
|
24
|
+
return entry;
|
|
25
|
+
entry = __defProp({}, "__esModule", { value: true });
|
|
26
|
+
if (from && typeof from === "object" || typeof from === "function")
|
|
27
|
+
__getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
|
|
28
|
+
get: () => from[key],
|
|
29
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
30
|
+
}));
|
|
31
|
+
__moduleCache.set(from, entry);
|
|
32
|
+
return entry;
|
|
33
|
+
};
|
|
19
34
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
20
35
|
var __export = (target, all) => {
|
|
21
36
|
for (var name in all)
|
|
@@ -88,7 +103,7 @@ __export(exports_urls, {
|
|
|
88
103
|
VERSION: () => VERSION,
|
|
89
104
|
URLS: () => URLS
|
|
90
105
|
});
|
|
91
|
-
var URLS, VERSION = "1.
|
|
106
|
+
var URLS, VERSION = "1.24.0", env;
|
|
92
107
|
var init_urls = __esm(() => {
|
|
93
108
|
URLS = {
|
|
94
109
|
BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
|
|
@@ -962,6 +977,13 @@ var ready = false;
|
|
|
962
977
|
var init_keypair = () => {};
|
|
963
978
|
|
|
964
979
|
// src/services/crypto/file-crypto.ts
|
|
980
|
+
var exports_file_crypto = {};
|
|
981
|
+
__export(exports_file_crypto, {
|
|
982
|
+
sealKeyForPeer: () => sealKeyForPeer,
|
|
983
|
+
openSealedKey: () => openSealedKey,
|
|
984
|
+
encryptFile: () => encryptFile,
|
|
985
|
+
decryptFile: () => decryptFile
|
|
986
|
+
});
|
|
965
987
|
async function encryptFile(plaintext) {
|
|
966
988
|
const s = await ensureSodium();
|
|
967
989
|
const key = s.randombytes_buf(s.crypto_secretbox_KEYBYTES);
|
|
@@ -3301,44 +3323,10 @@ var init_ws_client = __esm(() => {
|
|
|
3301
3323
|
});
|
|
3302
3324
|
|
|
3303
3325
|
// src/services/broker/manager.ts
|
|
3304
|
-
|
|
3305
|
-
const existing = clients.get(mesh.meshId);
|
|
3306
|
-
if (existing)
|
|
3307
|
-
return existing;
|
|
3308
|
-
const isDebug2 = process.env.CLAUDEMESH_DEBUG === "1" || process.env.CLAUDEMESH_DEBUG === "true";
|
|
3309
|
-
const client = new BrokerClient(mesh, { debug: isDebug2, displayName: configDisplayName });
|
|
3310
|
-
clients.set(mesh.meshId, client);
|
|
3311
|
-
try {
|
|
3312
|
-
await client.connect();
|
|
3313
|
-
for (const g of configGroups ?? []) {
|
|
3314
|
-
try {
|
|
3315
|
-
await client.joinGroup(g.name, g.role);
|
|
3316
|
-
} catch {}
|
|
3317
|
-
}
|
|
3318
|
-
} catch (err) {
|
|
3319
|
-
process.stderr.write(`[claudemesh] broker connect failed for ${mesh.slug}: ${err instanceof Error ? err.message : err} (will retry)
|
|
3320
|
-
`);
|
|
3321
|
-
}
|
|
3322
|
-
return client;
|
|
3323
|
-
}
|
|
3324
|
-
async function startClients(config) {
|
|
3325
|
-
configDisplayName = config.displayName;
|
|
3326
|
-
configGroups = config.groups ?? [];
|
|
3327
|
-
await Promise.allSettled(config.meshes.map(ensureClient));
|
|
3328
|
-
}
|
|
3329
|
-
function allClients() {
|
|
3330
|
-
return [...clients.values()];
|
|
3331
|
-
}
|
|
3332
|
-
function stopAll() {
|
|
3333
|
-
for (const c of clients.values())
|
|
3334
|
-
c.close();
|
|
3335
|
-
clients.clear();
|
|
3336
|
-
}
|
|
3337
|
-
var clients, configDisplayName, configGroups;
|
|
3326
|
+
var clients;
|
|
3338
3327
|
var init_manager = __esm(() => {
|
|
3339
3328
|
init_ws_client();
|
|
3340
3329
|
clients = new Map;
|
|
3341
|
-
configGroups = [];
|
|
3342
3330
|
});
|
|
3343
3331
|
|
|
3344
3332
|
// src/services/broker/envelope.ts
|
|
@@ -3593,6 +3581,42 @@ var init_spinner = __esm(() => {
|
|
|
3593
3581
|
FRAME_HEIGHT = H;
|
|
3594
3582
|
});
|
|
3595
3583
|
|
|
3584
|
+
// src/daemon/paths.ts
|
|
3585
|
+
var exports_paths = {};
|
|
3586
|
+
__export(exports_paths, {
|
|
3587
|
+
DAEMON_TCP_HOST: () => DAEMON_TCP_HOST,
|
|
3588
|
+
DAEMON_TCP_DEFAULT_PORT: () => DAEMON_TCP_DEFAULT_PORT,
|
|
3589
|
+
DAEMON_PATHS: () => DAEMON_PATHS
|
|
3590
|
+
});
|
|
3591
|
+
import { join as join3 } from "node:path";
|
|
3592
|
+
var DAEMON_PATHS, DAEMON_TCP_HOST = "127.0.0.1", DAEMON_TCP_DEFAULT_PORT = 47823;
|
|
3593
|
+
var init_paths2 = __esm(() => {
|
|
3594
|
+
init_paths();
|
|
3595
|
+
DAEMON_PATHS = {
|
|
3596
|
+
get DAEMON_DIR() {
|
|
3597
|
+
return join3(PATHS.CONFIG_DIR, "daemon");
|
|
3598
|
+
},
|
|
3599
|
+
get PID_FILE() {
|
|
3600
|
+
return join3(this.DAEMON_DIR, "daemon.pid");
|
|
3601
|
+
},
|
|
3602
|
+
get SOCK_FILE() {
|
|
3603
|
+
return join3(this.DAEMON_DIR, "daemon.sock");
|
|
3604
|
+
},
|
|
3605
|
+
get TOKEN_FILE() {
|
|
3606
|
+
return join3(this.DAEMON_DIR, "local-token");
|
|
3607
|
+
},
|
|
3608
|
+
get OUTBOX_DB() {
|
|
3609
|
+
return join3(this.DAEMON_DIR, "outbox.db");
|
|
3610
|
+
},
|
|
3611
|
+
get INBOX_DB() {
|
|
3612
|
+
return join3(this.DAEMON_DIR, "inbox.db");
|
|
3613
|
+
},
|
|
3614
|
+
get LOG_FILE() {
|
|
3615
|
+
return join3(this.DAEMON_DIR, "daemon.log");
|
|
3616
|
+
}
|
|
3617
|
+
};
|
|
3618
|
+
});
|
|
3619
|
+
|
|
3596
3620
|
// src/commands/launch.ts
|
|
3597
3621
|
var exports_launch = {};
|
|
3598
3622
|
__export(exports_launch, {
|
|
@@ -3602,8 +3626,41 @@ import { spawnSync as spawnSync2 } from "node:child_process";
|
|
|
3602
3626
|
import { randomUUID } from "node:crypto";
|
|
3603
3627
|
import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, statSync, existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
|
|
3604
3628
|
import { tmpdir, hostname as hostname2, homedir as homedir3 } from "node:os";
|
|
3605
|
-
import { join as
|
|
3629
|
+
import { join as join4 } from "node:path";
|
|
3606
3630
|
import { createInterface as createInterface4 } from "node:readline";
|
|
3631
|
+
async function ensureDaemonRunning(meshSlug, quiet) {
|
|
3632
|
+
const { DAEMON_PATHS: DAEMON_PATHS2 } = await Promise.resolve().then(() => (init_paths2(), exports_paths));
|
|
3633
|
+
if (existsSync5(DAEMON_PATHS2.SOCK_FILE))
|
|
3634
|
+
return;
|
|
3635
|
+
if (!quiet)
|
|
3636
|
+
render.info("starting claudemesh daemon…");
|
|
3637
|
+
const { spawn } = await import("node:child_process");
|
|
3638
|
+
const argv0 = process.argv[1] ?? "claudemesh";
|
|
3639
|
+
let binary = argv0;
|
|
3640
|
+
if (/\.ts$/.test(binary) || /node_modules|src\/entrypoints/.test(binary)) {
|
|
3641
|
+
try {
|
|
3642
|
+
const { execSync } = await import("node:child_process");
|
|
3643
|
+
binary = execSync("which claudemesh", { encoding: "utf8" }).trim();
|
|
3644
|
+
} catch {
|
|
3645
|
+
binary = "claudemesh";
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
const child = spawn(binary, ["daemon", "up", "--mesh", meshSlug], {
|
|
3649
|
+
detached: true,
|
|
3650
|
+
stdio: "ignore"
|
|
3651
|
+
});
|
|
3652
|
+
child.unref();
|
|
3653
|
+
const start = Date.now();
|
|
3654
|
+
while (Date.now() - start < 1e4) {
|
|
3655
|
+
if (existsSync5(DAEMON_PATHS2.SOCK_FILE)) {
|
|
3656
|
+
if (!quiet)
|
|
3657
|
+
render.ok("daemon ready");
|
|
3658
|
+
return;
|
|
3659
|
+
}
|
|
3660
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
3661
|
+
}
|
|
3662
|
+
render.warn("daemon failed to start within 10s", "Run `claudemesh daemon up --mesh " + meshSlug + "` manually, then re-launch.");
|
|
3663
|
+
}
|
|
3607
3664
|
function parseGroupsString(raw) {
|
|
3608
3665
|
return raw.split(",").map((s) => s.trim()).filter(Boolean).map((token) => {
|
|
3609
3666
|
const idx = token.indexOf(":");
|
|
@@ -3923,14 +3980,15 @@ async function runLaunch(flags, rawArgs) {
|
|
|
3923
3980
|
for (const entry of readdirSync(tmpBase)) {
|
|
3924
3981
|
if (!entry.startsWith("claudemesh-"))
|
|
3925
3982
|
continue;
|
|
3926
|
-
const full =
|
|
3983
|
+
const full = join4(tmpBase, entry);
|
|
3927
3984
|
const age = Date.now() - statSync(full).mtimeMs;
|
|
3928
3985
|
if (age > 3600000)
|
|
3929
3986
|
rmSync(full, { recursive: true, force: true });
|
|
3930
3987
|
}
|
|
3931
3988
|
} catch {}
|
|
3989
|
+
await ensureDaemonRunning(mesh.slug, args.quiet);
|
|
3932
3990
|
try {
|
|
3933
|
-
const claudeConfigPath =
|
|
3991
|
+
const claudeConfigPath = join4(homedir3(), ".claude.json");
|
|
3934
3992
|
if (existsSync5(claudeConfigPath)) {
|
|
3935
3993
|
const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
|
|
3936
3994
|
const mcpServers = claudeConfig.mcpServers ?? {};
|
|
@@ -3967,7 +4025,7 @@ async function runLaunch(flags, rawArgs) {
|
|
|
3967
4025
|
console.log(" (Could not fetch service catalog — mesh services won't be natively available)");
|
|
3968
4026
|
}
|
|
3969
4027
|
}
|
|
3970
|
-
const tmpDir = mkdtempSync(
|
|
4028
|
+
const tmpDir = mkdtempSync(join4(tmpdir(), "claudemesh-"));
|
|
3971
4029
|
const sessionConfig = {
|
|
3972
4030
|
version: 1,
|
|
3973
4031
|
meshes: [mesh],
|
|
@@ -3976,14 +4034,14 @@ async function runLaunch(flags, rawArgs) {
|
|
|
3976
4034
|
...parsedGroups.length > 0 ? { groups: parsedGroups } : {},
|
|
3977
4035
|
messageMode
|
|
3978
4036
|
};
|
|
3979
|
-
writeFileSync4(
|
|
4037
|
+
writeFileSync4(join4(tmpDir, "config.json"), JSON.stringify(sessionConfig, null, 2) + `
|
|
3980
4038
|
`, "utf-8");
|
|
3981
4039
|
if (!args.quiet) {
|
|
3982
4040
|
printBanner(displayName, mesh.slug, role, parsedGroups, messageMode);
|
|
3983
4041
|
}
|
|
3984
4042
|
const meshMcpEntries = [];
|
|
3985
4043
|
if (serviceCatalog.length > 0) {
|
|
3986
|
-
const claudeConfigPath =
|
|
4044
|
+
const claudeConfigPath = join4(homedir3(), ".claude.json");
|
|
3987
4045
|
let claudeConfig = {};
|
|
3988
4046
|
try {
|
|
3989
4047
|
claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
|
|
@@ -4050,9 +4108,9 @@ async function runLaunch(flags, rawArgs) {
|
|
|
4050
4108
|
let claudeBin = "claude";
|
|
4051
4109
|
if (!isWindows2) {
|
|
4052
4110
|
const candidates = [
|
|
4053
|
-
|
|
4111
|
+
join4(homedir3(), ".local", "bin", "claude"),
|
|
4054
4112
|
"/usr/local/bin/claude",
|
|
4055
|
-
|
|
4113
|
+
join4(homedir3(), ".claude", "bin", "claude")
|
|
4056
4114
|
];
|
|
4057
4115
|
for (const c of candidates) {
|
|
4058
4116
|
if (existsSync5(c)) {
|
|
@@ -4064,7 +4122,7 @@ async function runLaunch(flags, rawArgs) {
|
|
|
4064
4122
|
const cleanup = () => {
|
|
4065
4123
|
if (meshMcpEntries.length > 0) {
|
|
4066
4124
|
try {
|
|
4067
|
-
const claudeConfigPath =
|
|
4125
|
+
const claudeConfigPath = join4(homedir3(), ".claude.json");
|
|
4068
4126
|
const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
|
|
4069
4127
|
const mcpServers = claudeConfig.mcpServers ?? {};
|
|
4070
4128
|
for (const { key } of meshMcpEntries) {
|
|
@@ -4769,7 +4827,7 @@ __export(exports_join, {
|
|
|
4769
4827
|
});
|
|
4770
4828
|
import sodium3 from "libsodium-wrappers";
|
|
4771
4829
|
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "node:fs";
|
|
4772
|
-
import { join as
|
|
4830
|
+
import { join as join5, dirname as dirname2 } from "node:path";
|
|
4773
4831
|
import { homedir as homedir4, hostname as hostname3 } from "node:os";
|
|
4774
4832
|
function deriveAppBaseUrl() {
|
|
4775
4833
|
const override = process.env.CLAUDEMESH_APP_URL;
|
|
@@ -4889,8 +4947,8 @@ async function runJoin(args) {
|
|
|
4889
4947
|
joinedAt: new Date().toISOString()
|
|
4890
4948
|
});
|
|
4891
4949
|
writeConfig(config);
|
|
4892
|
-
const configDir = env.CLAUDEMESH_CONFIG_DIR ??
|
|
4893
|
-
const inviteFile =
|
|
4950
|
+
const configDir = env.CLAUDEMESH_CONFIG_DIR ?? join5(homedir4(), ".claudemesh");
|
|
4951
|
+
const inviteFile = join5(configDir, `invite-${payload.mesh_slug}.txt`);
|
|
4894
4952
|
try {
|
|
4895
4953
|
mkdirSync3(dirname2(inviteFile), { recursive: true });
|
|
4896
4954
|
writeFileSync5(inviteFile, link, "utf-8");
|
|
@@ -6633,12 +6691,9 @@ var init_ban = __esm(() => {
|
|
|
6633
6691
|
|
|
6634
6692
|
// src/services/bridge/protocol.ts
|
|
6635
6693
|
import { homedir as homedir5 } from "node:os";
|
|
6636
|
-
import { join as
|
|
6694
|
+
import { join as join6 } from "node:path";
|
|
6637
6695
|
function socketPath(meshSlug) {
|
|
6638
|
-
return
|
|
6639
|
-
}
|
|
6640
|
-
function socketDir() {
|
|
6641
|
-
return join5(homedir5(), ".claudemesh", "sockets");
|
|
6696
|
+
return join6(homedir5(), ".claudemesh", "sockets", `${meshSlug}.sock`);
|
|
6642
6697
|
}
|
|
6643
6698
|
function frame(obj) {
|
|
6644
6699
|
return JSON.stringify(obj) + `
|
|
@@ -6834,36 +6889,6 @@ var init_peers = __esm(() => {
|
|
|
6834
6889
|
};
|
|
6835
6890
|
});
|
|
6836
6891
|
|
|
6837
|
-
// src/daemon/paths.ts
|
|
6838
|
-
import { join as join6 } from "node:path";
|
|
6839
|
-
var DAEMON_PATHS, DAEMON_TCP_HOST = "127.0.0.1", DAEMON_TCP_DEFAULT_PORT = 47823;
|
|
6840
|
-
var init_paths2 = __esm(() => {
|
|
6841
|
-
init_paths();
|
|
6842
|
-
DAEMON_PATHS = {
|
|
6843
|
-
get DAEMON_DIR() {
|
|
6844
|
-
return join6(PATHS.CONFIG_DIR, "daemon");
|
|
6845
|
-
},
|
|
6846
|
-
get PID_FILE() {
|
|
6847
|
-
return join6(this.DAEMON_DIR, "daemon.pid");
|
|
6848
|
-
},
|
|
6849
|
-
get SOCK_FILE() {
|
|
6850
|
-
return join6(this.DAEMON_DIR, "daemon.sock");
|
|
6851
|
-
},
|
|
6852
|
-
get TOKEN_FILE() {
|
|
6853
|
-
return join6(this.DAEMON_DIR, "local-token");
|
|
6854
|
-
},
|
|
6855
|
-
get OUTBOX_DB() {
|
|
6856
|
-
return join6(this.DAEMON_DIR, "outbox.db");
|
|
6857
|
-
},
|
|
6858
|
-
get INBOX_DB() {
|
|
6859
|
-
return join6(this.DAEMON_DIR, "inbox.db");
|
|
6860
|
-
},
|
|
6861
|
-
get LOG_FILE() {
|
|
6862
|
-
return join6(this.DAEMON_DIR, "daemon.log");
|
|
6863
|
-
}
|
|
6864
|
-
};
|
|
6865
|
-
});
|
|
6866
|
-
|
|
6867
6892
|
// src/daemon/local-token.ts
|
|
6868
6893
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6869
6894
|
import { dirname as dirname3 } from "node:path";
|
|
@@ -8881,7 +8906,7 @@ function makeHandler(opts) {
|
|
|
8881
8906
|
respond(res, 200, {
|
|
8882
8907
|
daemon_version: VERSION,
|
|
8883
8908
|
ipc_api: "v1",
|
|
8884
|
-
ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile"],
|
|
8909
|
+
ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile", "skills"],
|
|
8885
8910
|
schema_version: 1
|
|
8886
8911
|
});
|
|
8887
8912
|
return;
|
|
@@ -8911,6 +8936,42 @@ function makeHandler(opts) {
|
|
|
8911
8936
|
}
|
|
8912
8937
|
return;
|
|
8913
8938
|
}
|
|
8939
|
+
if (req.method === "GET" && url.pathname === "/v1/skills") {
|
|
8940
|
+
if (!opts.broker) {
|
|
8941
|
+
respond(res, 503, { error: "broker not initialised" });
|
|
8942
|
+
return;
|
|
8943
|
+
}
|
|
8944
|
+
const query = url.searchParams.get("query") ?? undefined;
|
|
8945
|
+
try {
|
|
8946
|
+
const skills = await opts.broker.listSkills(query);
|
|
8947
|
+
respond(res, 200, { skills });
|
|
8948
|
+
} catch (e) {
|
|
8949
|
+
respond(res, 502, { error: "broker_unreachable", detail: String(e) });
|
|
8950
|
+
}
|
|
8951
|
+
return;
|
|
8952
|
+
}
|
|
8953
|
+
if (req.method === "GET" && url.pathname.startsWith("/v1/skills/")) {
|
|
8954
|
+
if (!opts.broker) {
|
|
8955
|
+
respond(res, 503, { error: "broker not initialised" });
|
|
8956
|
+
return;
|
|
8957
|
+
}
|
|
8958
|
+
const name = decodeURIComponent(url.pathname.slice("/v1/skills/".length));
|
|
8959
|
+
if (!name) {
|
|
8960
|
+
respond(res, 400, { error: "missing skill name" });
|
|
8961
|
+
return;
|
|
8962
|
+
}
|
|
8963
|
+
try {
|
|
8964
|
+
const skill = await opts.broker.getSkill(name);
|
|
8965
|
+
if (!skill) {
|
|
8966
|
+
respond(res, 404, { error: "skill_not_found", name });
|
|
8967
|
+
return;
|
|
8968
|
+
}
|
|
8969
|
+
respond(res, 200, { skill });
|
|
8970
|
+
} catch (e) {
|
|
8971
|
+
respond(res, 502, { error: "broker_unreachable", detail: String(e) });
|
|
8972
|
+
}
|
|
8973
|
+
return;
|
|
8974
|
+
}
|
|
8914
8975
|
if (req.method === "POST" && url.pathname === "/v1/profile") {
|
|
8915
8976
|
if (!opts.broker) {
|
|
8916
8977
|
respond(res, 503, { error: "broker not initialised" });
|
|
@@ -9191,6 +9252,8 @@ class DaemonBrokerClient {
|
|
|
9191
9252
|
helloTimer = null;
|
|
9192
9253
|
pendingAcks = new Map;
|
|
9193
9254
|
peerListResolvers = new Map;
|
|
9255
|
+
skillListResolvers = new Map;
|
|
9256
|
+
skillDataResolvers = new Map;
|
|
9194
9257
|
sessionPubkey = null;
|
|
9195
9258
|
sessionSecretKey = null;
|
|
9196
9259
|
opens = [];
|
|
@@ -9311,6 +9374,26 @@ class DaemonBrokerClient {
|
|
|
9311
9374
|
}
|
|
9312
9375
|
return;
|
|
9313
9376
|
}
|
|
9377
|
+
if (msg.type === "skill_list") {
|
|
9378
|
+
const reqId = String(msg._reqId ?? "");
|
|
9379
|
+
const pending = this.skillListResolvers.get(reqId);
|
|
9380
|
+
if (pending) {
|
|
9381
|
+
this.skillListResolvers.delete(reqId);
|
|
9382
|
+
clearTimeout(pending.timer);
|
|
9383
|
+
pending.resolve(Array.isArray(msg.skills) ? msg.skills : []);
|
|
9384
|
+
}
|
|
9385
|
+
return;
|
|
9386
|
+
}
|
|
9387
|
+
if (msg.type === "skill_data") {
|
|
9388
|
+
const reqId = String(msg._reqId ?? "");
|
|
9389
|
+
const pending = this.skillDataResolvers.get(reqId);
|
|
9390
|
+
if (pending) {
|
|
9391
|
+
this.skillDataResolvers.delete(reqId);
|
|
9392
|
+
clearTimeout(pending.timer);
|
|
9393
|
+
pending.resolve(msg.skill ?? null);
|
|
9394
|
+
}
|
|
9395
|
+
return;
|
|
9396
|
+
}
|
|
9314
9397
|
if (msg.type === "push" || msg.type === "inbound") {
|
|
9315
9398
|
this.opts.onPush?.(msg);
|
|
9316
9399
|
return;
|
|
@@ -9393,6 +9476,44 @@ class DaemonBrokerClient {
|
|
|
9393
9476
|
}
|
|
9394
9477
|
});
|
|
9395
9478
|
}
|
|
9479
|
+
async listSkills(query, timeoutMs = 5000) {
|
|
9480
|
+
if (this._status !== "open" || !this.ws)
|
|
9481
|
+
return [];
|
|
9482
|
+
return new Promise((resolve) => {
|
|
9483
|
+
const reqId = `sl-${++this.reqCounter}`;
|
|
9484
|
+
const timer = setTimeout(() => {
|
|
9485
|
+
if (this.skillListResolvers.delete(reqId))
|
|
9486
|
+
resolve([]);
|
|
9487
|
+
}, timeoutMs);
|
|
9488
|
+
this.skillListResolvers.set(reqId, { resolve, timer });
|
|
9489
|
+
try {
|
|
9490
|
+
this.ws.send(JSON.stringify({ type: "list_skills", query, _reqId: reqId }));
|
|
9491
|
+
} catch {
|
|
9492
|
+
this.skillListResolvers.delete(reqId);
|
|
9493
|
+
clearTimeout(timer);
|
|
9494
|
+
resolve([]);
|
|
9495
|
+
}
|
|
9496
|
+
});
|
|
9497
|
+
}
|
|
9498
|
+
async getSkill(name, timeoutMs = 5000) {
|
|
9499
|
+
if (this._status !== "open" || !this.ws)
|
|
9500
|
+
return null;
|
|
9501
|
+
return new Promise((resolve) => {
|
|
9502
|
+
const reqId = `sg-${++this.reqCounter}`;
|
|
9503
|
+
const timer = setTimeout(() => {
|
|
9504
|
+
if (this.skillDataResolvers.delete(reqId))
|
|
9505
|
+
resolve(null);
|
|
9506
|
+
}, timeoutMs);
|
|
9507
|
+
this.skillDataResolvers.set(reqId, { resolve, timer });
|
|
9508
|
+
try {
|
|
9509
|
+
this.ws.send(JSON.stringify({ type: "get_skill", name, _reqId: reqId }));
|
|
9510
|
+
} catch {
|
|
9511
|
+
this.skillDataResolvers.delete(reqId);
|
|
9512
|
+
clearTimeout(timer);
|
|
9513
|
+
resolve(null);
|
|
9514
|
+
}
|
|
9515
|
+
});
|
|
9516
|
+
}
|
|
9396
9517
|
setProfile(profile) {
|
|
9397
9518
|
if (this._status !== "open" || !this.ws)
|
|
9398
9519
|
return;
|
|
@@ -9619,11 +9740,14 @@ async function handleBrokerPush(msg, ctx) {
|
|
|
9619
9740
|
const brokerMessageId = stringOrNull(msg.messageId);
|
|
9620
9741
|
const senderPubkey = stringOrNull(msg.senderPubkey) ?? "";
|
|
9621
9742
|
const senderName = stringOrNull(msg.senderName) ?? senderPubkey.slice(0, 8);
|
|
9743
|
+
const senderMemberPk = stringOrNull(msg.senderMemberPubkey);
|
|
9622
9744
|
const topic = stringOrNull(msg.topic);
|
|
9623
9745
|
const replyToId = stringOrNull(msg.replyToId);
|
|
9624
9746
|
const ciphertext = stringOrNull(msg.ciphertext) ?? "";
|
|
9625
9747
|
const nonce = stringOrNull(msg.nonce) ?? "";
|
|
9626
9748
|
const createdAt = stringOrNull(msg.createdAt);
|
|
9749
|
+
const priority = stringOrNull(msg.priority) ?? "next";
|
|
9750
|
+
const subtype = stringOrNull(msg.subtype);
|
|
9627
9751
|
const clientMessageId = stringOrNull(msg.client_message_id) ?? brokerMessageId ?? randomUUID5();
|
|
9628
9752
|
const body = await decryptOrFallback({
|
|
9629
9753
|
ciphertext,
|
|
@@ -9653,9 +9777,12 @@ async function handleBrokerPush(msg, ctx) {
|
|
|
9653
9777
|
client_message_id: clientMessageId,
|
|
9654
9778
|
broker_message_id: brokerMessageId,
|
|
9655
9779
|
sender_pubkey: senderPubkey,
|
|
9780
|
+
sender_member_pubkey: senderMemberPk,
|
|
9656
9781
|
sender_name: senderName,
|
|
9657
9782
|
topic,
|
|
9658
9783
|
reply_to_id: replyToId,
|
|
9784
|
+
priority,
|
|
9785
|
+
...subtype ? { subtype } : {},
|
|
9659
9786
|
body,
|
|
9660
9787
|
created_at: createdAt
|
|
9661
9788
|
});
|
|
@@ -10754,6 +10881,7 @@ function installStatusLine() {
|
|
|
10754
10881
|
function runInstall(args = []) {
|
|
10755
10882
|
const skipHooks = args.includes("--no-hooks");
|
|
10756
10883
|
const skipSkill = args.includes("--no-skill");
|
|
10884
|
+
const skipService = args.includes("--no-service");
|
|
10757
10885
|
const wantStatusLine = args.includes("--status-line");
|
|
10758
10886
|
render.section("claudemesh install");
|
|
10759
10887
|
const entry = resolveEntry();
|
|
@@ -10835,10 +10963,25 @@ function runInstall(args = []) {
|
|
|
10835
10963
|
}
|
|
10836
10964
|
}
|
|
10837
10965
|
let hasMeshes = false;
|
|
10966
|
+
let primaryMesh;
|
|
10838
10967
|
try {
|
|
10839
10968
|
const meshConfig = readConfig();
|
|
10840
10969
|
hasMeshes = meshConfig.meshes.length > 0;
|
|
10970
|
+
primaryMesh = meshConfig.meshes[0]?.slug;
|
|
10841
10971
|
} catch {}
|
|
10972
|
+
if (!skipService && hasMeshes && primaryMesh) {
|
|
10973
|
+
try {
|
|
10974
|
+
installDaemonService(entry, primaryMesh);
|
|
10975
|
+
} catch (e) {
|
|
10976
|
+
render.warn(`daemon service install failed: ${e instanceof Error ? e.message : String(e)}`, "Run `claudemesh daemon install-service --mesh <slug>` to retry.");
|
|
10977
|
+
}
|
|
10978
|
+
} else if (skipService) {
|
|
10979
|
+
render.info(dim("· Daemon service skipped (--no-service)"));
|
|
10980
|
+
render.info(dim(" MCP integration will fail at boot until you start the daemon manually:"));
|
|
10981
|
+
render.info(dim(" claudemesh daemon up --mesh <slug>"));
|
|
10982
|
+
} else if (!hasMeshes) {
|
|
10983
|
+
render.info(dim("· Daemon service deferred — join a mesh first, then run install again."));
|
|
10984
|
+
}
|
|
10842
10985
|
render.blank();
|
|
10843
10986
|
render.warn(`${bold("RESTART CLAUDE CODE")} ${yellow("for MCP tools to appear.")}`);
|
|
10844
10987
|
if (!hasMeshes) {
|
|
@@ -10856,6 +10999,40 @@ function runInstall(args = []) {
|
|
|
10856
10999
|
render.info(dim(` claudemesh install --status-line # live peer count in Claude Code`));
|
|
10857
11000
|
render.info(dim(` claudemesh completions zsh # shell completions`));
|
|
10858
11001
|
}
|
|
11002
|
+
function installDaemonService(binaryEntry, meshSlug) {
|
|
11003
|
+
const {
|
|
11004
|
+
installService: installService2,
|
|
11005
|
+
detectPlatform: detectPlatform2
|
|
11006
|
+
} = (init_service_install(), __toCommonJS(exports_service_install));
|
|
11007
|
+
const platform6 = detectPlatform2();
|
|
11008
|
+
if (!platform6) {
|
|
11009
|
+
render.info(dim(`· Daemon service skipped — unsupported platform: ${process.platform}`));
|
|
11010
|
+
return;
|
|
11011
|
+
}
|
|
11012
|
+
let binary = process.argv[1] ?? binaryEntry;
|
|
11013
|
+
if (!binary || /\.ts$/.test(binary) || /node_modules|src\/entrypoints/.test(binary)) {
|
|
11014
|
+
try {
|
|
11015
|
+
const { execSync: execSync3 } = __require("node:child_process");
|
|
11016
|
+
binary = execSync3("which claudemesh", { encoding: "utf8" }).trim();
|
|
11017
|
+
} catch {
|
|
11018
|
+
render.warn("couldn't resolve a 'claudemesh' binary on PATH; daemon service skipped", "Install via npm/homebrew, then run `claudemesh daemon install-service --mesh " + meshSlug + "`");
|
|
11019
|
+
return;
|
|
11020
|
+
}
|
|
11021
|
+
}
|
|
11022
|
+
const r = installService2({ binaryPath: binary, meshSlug });
|
|
11023
|
+
render.ok(`daemon service installed (${r.platform})`);
|
|
11024
|
+
render.kv([
|
|
11025
|
+
["unit", dim(r.unitPath)],
|
|
11026
|
+
["mesh", dim(meshSlug)]
|
|
11027
|
+
]);
|
|
11028
|
+
try {
|
|
11029
|
+
const { execSync: execSync3 } = __require("node:child_process");
|
|
11030
|
+
execSync3(r.bootCommand, { stdio: "ignore" });
|
|
11031
|
+
render.ok("daemon started");
|
|
11032
|
+
} catch (e) {
|
|
11033
|
+
render.warn(`daemon service installed but failed to start: ${e instanceof Error ? e.message : String(e)}`, `Run manually: ${r.bootCommand}`);
|
|
11034
|
+
}
|
|
11035
|
+
}
|
|
10859
11036
|
function runUninstall() {
|
|
10860
11037
|
render.section("claudemesh uninstall");
|
|
10861
11038
|
if (removeMcpServer()) {
|
|
@@ -12737,11 +12914,14 @@ var exports_platform_actions = {};
|
|
|
12737
12914
|
__export(exports_platform_actions, {
|
|
12738
12915
|
runWebhookList: () => runWebhookList,
|
|
12739
12916
|
runWebhookDelete: () => runWebhookDelete,
|
|
12917
|
+
runWebhookCreate: () => runWebhookCreate,
|
|
12740
12918
|
runWatchList: () => runWatchList,
|
|
12919
|
+
runWatchAdd: () => runWatchAdd,
|
|
12741
12920
|
runVectorStore: () => runVectorStore,
|
|
12742
12921
|
runVectorSearch: () => runVectorSearch,
|
|
12743
12922
|
runVectorDelete: () => runVectorDelete,
|
|
12744
12923
|
runVectorCollections: () => runVectorCollections,
|
|
12924
|
+
runVaultSet: () => runVaultSet,
|
|
12745
12925
|
runVaultList: () => runVaultList,
|
|
12746
12926
|
runVaultDelete: () => runVaultDelete,
|
|
12747
12927
|
runUnwatch: () => runUnwatch,
|
|
@@ -13188,6 +13368,40 @@ async function runVaultDelete(key, opts) {
|
|
|
13188
13368
|
return ok ? EXIT.SUCCESS : EXIT.NOT_FOUND;
|
|
13189
13369
|
});
|
|
13190
13370
|
}
|
|
13371
|
+
async function runVaultSet(key, value, opts) {
|
|
13372
|
+
if (!key || value == null) {
|
|
13373
|
+
render.err("Usage: claudemesh vault set <key> <value> [--type env|file] [--mount /path] [--description ...]");
|
|
13374
|
+
return EXIT.INVALID_ARGS;
|
|
13375
|
+
}
|
|
13376
|
+
const { encryptFile: encryptFile2, sealKeyForPeer: sealKeyForPeer2 } = await Promise.resolve().then(() => (init_file_crypto(), exports_file_crypto));
|
|
13377
|
+
const { getMeshConfig: getMeshConfig2 } = await Promise.resolve().then(() => (init_facade(), exports_facade));
|
|
13378
|
+
const { readConfig: readConfig2 } = await Promise.resolve().then(() => (init_facade(), exports_facade));
|
|
13379
|
+
const config = readConfig2();
|
|
13380
|
+
const slug = opts.mesh ?? (config.meshes.length === 1 ? config.meshes[0].slug : null);
|
|
13381
|
+
if (!slug) {
|
|
13382
|
+
render.err("multiple meshes joined; pass --mesh <slug>");
|
|
13383
|
+
return EXIT.INVALID_ARGS;
|
|
13384
|
+
}
|
|
13385
|
+
const mesh = getMeshConfig2(slug);
|
|
13386
|
+
if (!mesh) {
|
|
13387
|
+
render.err(`not joined to mesh "${slug}"`);
|
|
13388
|
+
return EXIT.NOT_FOUND;
|
|
13389
|
+
}
|
|
13390
|
+
const plaintext = new TextEncoder().encode(value);
|
|
13391
|
+
const enc = await encryptFile2(plaintext);
|
|
13392
|
+
const ciphertextB64 = Buffer.from(enc.ciphertext).toString("base64");
|
|
13393
|
+
const sealed = await sealKeyForPeer2(enc.key, mesh.pubkey);
|
|
13394
|
+
return await withMesh({ meshSlug: slug }, async (client) => {
|
|
13395
|
+
const ok = await client.vaultSet(key, ciphertextB64, enc.nonce, sealed, opts.entryType ?? "env", opts.mountPath, opts.description);
|
|
13396
|
+
if (opts.json)
|
|
13397
|
+
emitJson({ key, stored: ok });
|
|
13398
|
+
else if (ok)
|
|
13399
|
+
render.ok(`vault[${bold(key)}] stored`, dim(`(${ciphertextB64.length}b)`));
|
|
13400
|
+
else
|
|
13401
|
+
render.err(`vault set failed for "${key}"`);
|
|
13402
|
+
return ok ? EXIT.SUCCESS : EXIT.IO_ERROR;
|
|
13403
|
+
});
|
|
13404
|
+
}
|
|
13191
13405
|
async function runWatchList(opts) {
|
|
13192
13406
|
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
|
|
13193
13407
|
const watches = await client.watchList();
|
|
@@ -13210,6 +13424,34 @@ async function runWatchList(opts) {
|
|
|
13210
13424
|
return EXIT.SUCCESS;
|
|
13211
13425
|
});
|
|
13212
13426
|
}
|
|
13427
|
+
async function runWatchAdd(url, opts) {
|
|
13428
|
+
if (!url) {
|
|
13429
|
+
render.err("Usage: claudemesh watch add <url> [--label ...] [--interval <sec>] [--extract <css>] [--notify-on changed|always]");
|
|
13430
|
+
return EXIT.INVALID_ARGS;
|
|
13431
|
+
}
|
|
13432
|
+
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
|
|
13433
|
+
const result = await client.watch(url, {
|
|
13434
|
+
label: opts.label,
|
|
13435
|
+
interval: opts.interval,
|
|
13436
|
+
mode: opts.mode,
|
|
13437
|
+
extract: opts.extract,
|
|
13438
|
+
notify_on: opts.notifyOn
|
|
13439
|
+
});
|
|
13440
|
+
if (result?.error) {
|
|
13441
|
+
if (opts.json)
|
|
13442
|
+
emitJson({ ok: false, error: result.error });
|
|
13443
|
+
else
|
|
13444
|
+
render.err(`watch add failed: ${result.error}`);
|
|
13445
|
+
return EXIT.IO_ERROR;
|
|
13446
|
+
}
|
|
13447
|
+
const id = String(result?.id ?? result?.watch_id ?? "?");
|
|
13448
|
+
if (opts.json)
|
|
13449
|
+
emitJson({ ok: true, id, url, ...opts.label ? { label: opts.label } : {} });
|
|
13450
|
+
else
|
|
13451
|
+
render.ok(`watching ${clay(url)}`, dim(id.slice(0, 8)));
|
|
13452
|
+
return EXIT.SUCCESS;
|
|
13453
|
+
});
|
|
13454
|
+
}
|
|
13213
13455
|
async function runUnwatch(id, opts) {
|
|
13214
13456
|
if (!id) {
|
|
13215
13457
|
render.err("Usage: claudemesh watch remove <id>");
|
|
@@ -13246,6 +13488,32 @@ async function runWebhookList(opts) {
|
|
|
13246
13488
|
return EXIT.SUCCESS;
|
|
13247
13489
|
});
|
|
13248
13490
|
}
|
|
13491
|
+
async function runWebhookCreate(name, opts) {
|
|
13492
|
+
if (!name) {
|
|
13493
|
+
render.err("Usage: claudemesh webhook create <name>");
|
|
13494
|
+
return EXIT.INVALID_ARGS;
|
|
13495
|
+
}
|
|
13496
|
+
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
|
|
13497
|
+
const created = await client.createWebhook(name);
|
|
13498
|
+
if (!created) {
|
|
13499
|
+
if (opts.json)
|
|
13500
|
+
emitJson({ ok: false, error: "create failed (timeout or duplicate)" });
|
|
13501
|
+
else
|
|
13502
|
+
render.err(`webhook create "${name}" failed`);
|
|
13503
|
+
return EXIT.IO_ERROR;
|
|
13504
|
+
}
|
|
13505
|
+
if (opts.json)
|
|
13506
|
+
emitJson({ ok: true, ...created });
|
|
13507
|
+
else {
|
|
13508
|
+
render.ok(`created webhook ${bold(created.name)}`);
|
|
13509
|
+
process.stdout.write(` url: ${clay(created.url)}
|
|
13510
|
+
`);
|
|
13511
|
+
process.stdout.write(` secret: ${dim(created.secret)} ${dim("(shown once)")}
|
|
13512
|
+
`);
|
|
13513
|
+
}
|
|
13514
|
+
return EXIT.SUCCESS;
|
|
13515
|
+
});
|
|
13516
|
+
}
|
|
13249
13517
|
async function runWebhookDelete(name, opts) {
|
|
13250
13518
|
if (!name) {
|
|
13251
13519
|
render.err("Usage: claudemesh webhook delete <name>");
|
|
@@ -15860,435 +16128,194 @@ var init_member = __esm(() => {
|
|
|
15860
16128
|
init_exit_codes();
|
|
15861
16129
|
});
|
|
15862
16130
|
|
|
15863
|
-
// src/mcp/
|
|
15864
|
-
|
|
15865
|
-
|
|
15866
|
-
|
|
15867
|
-
|
|
15868
|
-
|
|
15869
|
-
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
|
|
15877
|
-
|
|
15878
|
-
|
|
15879
|
-
|
|
15880
|
-
|
|
15881
|
-
error: `peer "${to}" not found. online: ${peers.map((p) => p.displayName).join(", ") || "(none)"}`
|
|
15882
|
-
};
|
|
16131
|
+
// src/mcp/server.ts
|
|
16132
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
16133
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16134
|
+
import {
|
|
16135
|
+
ListToolsRequestSchema,
|
|
16136
|
+
CallToolRequestSchema,
|
|
16137
|
+
ListPromptsRequestSchema,
|
|
16138
|
+
GetPromptRequestSchema,
|
|
16139
|
+
ListResourcesRequestSchema,
|
|
16140
|
+
ReadResourceRequestSchema
|
|
16141
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
16142
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
16143
|
+
import { request as httpRequest2 } from "node:http";
|
|
16144
|
+
async function daemonReady() {
|
|
16145
|
+
for (let i = 0;i < DAEMON_BOOT_RETRIES; i++) {
|
|
16146
|
+
if (existsSync28(DAEMON_PATHS.SOCK_FILE))
|
|
16147
|
+
return true;
|
|
16148
|
+
await new Promise((r) => setTimeout(r, DAEMON_BOOT_RETRY_MS));
|
|
15883
16149
|
}
|
|
15884
|
-
return
|
|
16150
|
+
return false;
|
|
15885
16151
|
}
|
|
15886
|
-
|
|
15887
|
-
|
|
15888
|
-
|
|
15889
|
-
|
|
15890
|
-
|
|
15891
|
-
|
|
15892
|
-
|
|
15893
|
-
|
|
15894
|
-
|
|
15895
|
-
|
|
15896
|
-
|
|
15897
|
-
ws_status: client.status,
|
|
15898
|
-
peers_online: peers.length,
|
|
15899
|
-
push_buffer: client.pushHistory.length
|
|
15900
|
-
}
|
|
15901
|
-
};
|
|
15902
|
-
}
|
|
15903
|
-
case "peers": {
|
|
15904
|
-
const peers = await client.listPeers();
|
|
15905
|
-
return { id: req.id, ok: true, result: peers };
|
|
15906
|
-
}
|
|
15907
|
-
case "send": {
|
|
15908
|
-
const to = String(args.to ?? "");
|
|
15909
|
-
const message = String(args.message ?? "");
|
|
15910
|
-
const priority = args.priority ?? "next";
|
|
15911
|
-
if (!to || !message) {
|
|
15912
|
-
return { id: req.id, ok: false, error: "send: `to` and `message` required" };
|
|
15913
|
-
}
|
|
15914
|
-
const resolved = await resolveTarget2(client, to);
|
|
15915
|
-
if (!resolved.ok)
|
|
15916
|
-
return { id: req.id, ok: false, error: resolved.error };
|
|
15917
|
-
const result = await client.send(resolved.spec, message, priority);
|
|
15918
|
-
if (!result.ok) {
|
|
15919
|
-
return { id: req.id, ok: false, error: result.error ?? "send failed" };
|
|
15920
|
-
}
|
|
15921
|
-
return {
|
|
15922
|
-
id: req.id,
|
|
15923
|
-
ok: true,
|
|
15924
|
-
result: { messageId: result.messageId, target: resolved.spec }
|
|
15925
|
-
};
|
|
15926
|
-
}
|
|
15927
|
-
case "summary": {
|
|
15928
|
-
const text = String(args.summary ?? "");
|
|
15929
|
-
if (!text)
|
|
15930
|
-
return { id: req.id, ok: false, error: "summary: `summary` required" };
|
|
15931
|
-
await client.setSummary(text);
|
|
15932
|
-
return { id: req.id, ok: true, result: { summary: text } };
|
|
15933
|
-
}
|
|
15934
|
-
case "status_set": {
|
|
15935
|
-
const state = String(args.status ?? "");
|
|
15936
|
-
if (!["idle", "working", "dnd"].includes(state)) {
|
|
15937
|
-
return { id: req.id, ok: false, error: "status_set: must be idle | working | dnd" };
|
|
15938
|
-
}
|
|
15939
|
-
await client.setStatus(state);
|
|
15940
|
-
return { id: req.id, ok: true, result: { status: state } };
|
|
15941
|
-
}
|
|
15942
|
-
case "visible": {
|
|
15943
|
-
const visible = Boolean(args.visible);
|
|
15944
|
-
await client.setVisible(visible);
|
|
15945
|
-
return { id: req.id, ok: true, result: { visible } };
|
|
15946
|
-
}
|
|
15947
|
-
default:
|
|
15948
|
-
return { id: req.id, ok: false, error: `unknown verb: ${req.verb}` };
|
|
15949
|
-
}
|
|
15950
|
-
} catch (err) {
|
|
15951
|
-
return {
|
|
15952
|
-
id: req.id,
|
|
15953
|
-
ok: false,
|
|
15954
|
-
error: err instanceof Error ? err.message : String(err)
|
|
15955
|
-
};
|
|
15956
|
-
}
|
|
16152
|
+
function bailNoDaemon() {
|
|
16153
|
+
process.stderr.write(`[claudemesh] daemon is not running.
|
|
16154
|
+
` + ` Start it: claudemesh daemon up --mesh <slug>
|
|
16155
|
+
` + ` Or install as service: claudemesh daemon install-service --mesh <slug>
|
|
16156
|
+
` + ` Diagnose: claudemesh doctor
|
|
16157
|
+
` + `
|
|
16158
|
+
` + ` As of 1.24.0 the daemon is required for in-Claude-Code use of
|
|
16159
|
+
` + ` claudemesh. The CLI itself (claudemesh send/peer/inbox/...) still
|
|
16160
|
+
` + ` works without a daemon.
|
|
16161
|
+
`);
|
|
16162
|
+
process.exit(1);
|
|
15957
16163
|
}
|
|
15958
|
-
function
|
|
15959
|
-
|
|
15960
|
-
|
|
15961
|
-
|
|
15962
|
-
|
|
15963
|
-
|
|
15964
|
-
|
|
15965
|
-
|
|
15966
|
-
try {
|
|
15967
|
-
req = JSON.parse(line);
|
|
15968
|
-
} catch {
|
|
15969
|
-
continue;
|
|
15970
|
-
}
|
|
15971
|
-
if (!req || typeof req !== "object" || !req.id || !req.verb)
|
|
15972
|
-
continue;
|
|
15973
|
-
dispatch(client, req).then((res) => {
|
|
16164
|
+
function daemonGet(path2) {
|
|
16165
|
+
return new Promise((resolve3, reject) => {
|
|
16166
|
+
const req = httpRequest2({ socketPath: DAEMON_PATHS.SOCK_FILE, path: path2, method: "GET", timeout: 5000 }, (res) => {
|
|
16167
|
+
const chunks = [];
|
|
16168
|
+
res.on("data", (c) => chunks.push(c));
|
|
16169
|
+
res.on("end", () => {
|
|
16170
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
16171
|
+
let body = null;
|
|
15974
16172
|
try {
|
|
15975
|
-
|
|
15976
|
-
} catch {
|
|
16173
|
+
body = JSON.parse(text);
|
|
16174
|
+
} catch {
|
|
16175
|
+
body = text;
|
|
16176
|
+
}
|
|
16177
|
+
resolve3({ status: res.statusCode ?? 0, body });
|
|
15977
16178
|
});
|
|
15978
|
-
}
|
|
16179
|
+
});
|
|
16180
|
+
req.on("error", reject);
|
|
16181
|
+
req.on("timeout", () => req.destroy(new Error("daemon_ipc_timeout")));
|
|
16182
|
+
req.end();
|
|
15979
16183
|
});
|
|
15980
|
-
socket.on("error", () => {});
|
|
15981
16184
|
}
|
|
15982
|
-
function
|
|
15983
|
-
|
|
15984
|
-
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
|
|
15993
|
-
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
-
|
|
16185
|
+
function subscribeEvents(onEvent) {
|
|
16186
|
+
let active = true;
|
|
16187
|
+
let req = null;
|
|
16188
|
+
const connect = () => {
|
|
16189
|
+
if (!active)
|
|
16190
|
+
return;
|
|
16191
|
+
req = httpRequest2({
|
|
16192
|
+
socketPath: DAEMON_PATHS.SOCK_FILE,
|
|
16193
|
+
path: "/v1/events",
|
|
16194
|
+
method: "GET",
|
|
16195
|
+
headers: { Accept: "text/event-stream" }
|
|
16196
|
+
});
|
|
16197
|
+
let buffer = "";
|
|
16198
|
+
req.on("response", (res) => {
|
|
16199
|
+
res.setEncoding("utf8");
|
|
16200
|
+
res.on("data", (chunk) => {
|
|
16201
|
+
buffer += chunk;
|
|
16202
|
+
let idx;
|
|
16203
|
+
while ((idx = buffer.indexOf(`
|
|
16204
|
+
|
|
16205
|
+
`)) >= 0) {
|
|
16206
|
+
const block = buffer.slice(0, idx);
|
|
16207
|
+
buffer = buffer.slice(idx + 2);
|
|
16208
|
+
if (!block.trim())
|
|
16209
|
+
continue;
|
|
16210
|
+
let kind = "message";
|
|
16211
|
+
let dataLine = "";
|
|
16212
|
+
for (const line of block.split(`
|
|
16213
|
+
`)) {
|
|
16214
|
+
if (line.startsWith(":"))
|
|
16215
|
+
continue;
|
|
16216
|
+
if (line.startsWith("event:"))
|
|
16217
|
+
kind = line.slice(6).trim();
|
|
16218
|
+
else if (line.startsWith("data:"))
|
|
16219
|
+
dataLine = line.slice(5).trim();
|
|
16220
|
+
}
|
|
16221
|
+
if (!dataLine)
|
|
16222
|
+
continue;
|
|
16223
|
+
try {
|
|
16224
|
+
const parsed = JSON.parse(dataLine);
|
|
16225
|
+
onEvent({ kind, ts: String(parsed.ts ?? ""), data: parsed });
|
|
16226
|
+
} catch {}
|
|
16227
|
+
}
|
|
16228
|
+
});
|
|
16229
|
+
res.on("end", () => {
|
|
16230
|
+
if (active) {
|
|
16231
|
+
process.stderr.write(`[claudemesh-mcp] sse stream ended; reconnecting in 1s
|
|
15998
16232
|
`);
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16233
|
+
setTimeout(connect, 1000);
|
|
16234
|
+
}
|
|
16235
|
+
});
|
|
16236
|
+
res.on("error", (err) => process.stderr.write(`[claudemesh-mcp] sse error: ${err.message}
|
|
16237
|
+
`));
|
|
16238
|
+
});
|
|
16239
|
+
req.on("error", (err) => {
|
|
16240
|
+
process.stderr.write(`[claudemesh-mcp] sse connect error: ${err.message}
|
|
16003
16241
|
`);
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
|
|
16242
|
+
if (active)
|
|
16243
|
+
setTimeout(connect, 2000);
|
|
16244
|
+
});
|
|
16245
|
+
req.end();
|
|
16246
|
+
};
|
|
16247
|
+
connect();
|
|
16009
16248
|
return {
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
if (stopped)
|
|
16013
|
-
return;
|
|
16014
|
-
stopped = true;
|
|
16015
|
-
try {
|
|
16016
|
-
server.close();
|
|
16017
|
-
} catch {}
|
|
16249
|
+
close: () => {
|
|
16250
|
+
active = false;
|
|
16018
16251
|
try {
|
|
16019
|
-
|
|
16252
|
+
req?.destroy();
|
|
16020
16253
|
} catch {}
|
|
16021
16254
|
}
|
|
16022
16255
|
};
|
|
16023
16256
|
}
|
|
16024
|
-
var init_server2 = __esm(() => {
|
|
16025
|
-
init_protocol();
|
|
16026
|
-
});
|
|
16027
|
-
|
|
16028
|
-
// src/mcp/server.ts
|
|
16029
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
16030
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16031
|
-
import {
|
|
16032
|
-
ListToolsRequestSchema,
|
|
16033
|
-
CallToolRequestSchema,
|
|
16034
|
-
ListPromptsRequestSchema,
|
|
16035
|
-
GetPromptRequestSchema,
|
|
16036
|
-
ListResourcesRequestSchema,
|
|
16037
|
-
ReadResourceRequestSchema
|
|
16038
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
16039
|
-
function relativeTime(isoStr) {
|
|
16040
|
-
const then = new Date(isoStr).getTime();
|
|
16041
|
-
if (isNaN(then))
|
|
16042
|
-
return "unknown";
|
|
16043
|
-
const diffMs = Date.now() - then;
|
|
16044
|
-
if (diffMs < 0)
|
|
16045
|
-
return "just now";
|
|
16046
|
-
const seconds = Math.floor(diffMs / 1000);
|
|
16047
|
-
if (seconds < 60)
|
|
16048
|
-
return `${seconds}s ago`;
|
|
16049
|
-
const minutes = Math.floor(seconds / 60);
|
|
16050
|
-
if (minutes < 60)
|
|
16051
|
-
return `${minutes}m ago`;
|
|
16052
|
-
const hours = Math.floor(minutes / 60);
|
|
16053
|
-
if (hours < 24)
|
|
16054
|
-
return `${hours}h ago`;
|
|
16055
|
-
const days = Math.floor(hours / 24);
|
|
16056
|
-
return `${days} day${days !== 1 ? "s" : ""} ago`;
|
|
16057
|
-
}
|
|
16058
|
-
async function resolvePeerName(client, pubkey) {
|
|
16059
|
-
const now = Date.now();
|
|
16060
|
-
if (now - peerNameCacheAge > CACHE_TTL_MS) {
|
|
16061
|
-
peerNameCache.clear();
|
|
16062
|
-
try {
|
|
16063
|
-
const peers = await client.listPeers();
|
|
16064
|
-
for (const p of peers)
|
|
16065
|
-
peerNameCache.set(p.pubkey, p.displayName);
|
|
16066
|
-
} catch {}
|
|
16067
|
-
peerNameCacheAge = now;
|
|
16068
|
-
}
|
|
16069
|
-
return peerNameCache.get(pubkey) ?? `peer-${pubkey.slice(0, 8)}`;
|
|
16070
|
-
}
|
|
16071
|
-
function decryptFailedWarning(senderPubkey) {
|
|
16072
|
-
const who = senderPubkey ? senderPubkey.slice(0, 12) + "…" : "unknown sender";
|
|
16073
|
-
return `⚠ message from ${who} failed to decrypt (tampered or wrong keypair)`;
|
|
16074
|
-
}
|
|
16075
16257
|
async function startMcpServer() {
|
|
16076
16258
|
const serviceIdx = process.argv.indexOf("--service");
|
|
16077
16259
|
if (serviceIdx !== -1 && process.argv[serviceIdx + 1]) {
|
|
16078
16260
|
return startServiceProxy(process.argv[serviceIdx + 1]);
|
|
16079
16261
|
}
|
|
16080
|
-
const
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
|
|
16084
|
-
|
|
16085
|
-
const filtered = config.meshes.filter((m) => m.slug === onlyMesh);
|
|
16086
|
-
if (filtered.length === 0) {
|
|
16087
|
-
process.stderr.write(`[claudemesh] --mesh "${onlyMesh}" not found in config. ` + `Joined meshes: ${available.join(", ") || "(none)"}
|
|
16088
|
-
`);
|
|
16089
|
-
process.exit(1);
|
|
16090
|
-
}
|
|
16091
|
-
config.meshes = filtered;
|
|
16092
|
-
}
|
|
16093
|
-
const myName = config.displayName ?? "unnamed";
|
|
16094
|
-
const myRole = config.role ?? process.env.CLAUDEMESH_ROLE ?? null;
|
|
16095
|
-
const myGroups = (config.groups ?? []).map((g) => `@${g.name}${g.role ? ":" + g.role : ""}`).join(", ") || "none";
|
|
16096
|
-
const messageMode = config.messageMode ?? "push";
|
|
16097
|
-
const server = new Server({ name: "claudemesh", version: "0.3.0" }, {
|
|
16098
|
-
capabilities: {
|
|
16099
|
-
experimental: { "claude/channel": {} },
|
|
16100
|
-
tools: {},
|
|
16101
|
-
prompts: {},
|
|
16102
|
-
resources: {}
|
|
16103
|
-
},
|
|
16104
|
-
instructions: `## Identity
|
|
16105
|
-
You are "${myName}"${myRole ? ` (${myRole})` : ""} — a peer in the claudemesh network. Your groups: ${myGroups}. You are one of several Claude Code sessions connected to the same mesh. No orchestrator exists — peers are equals. Your identity comes from your name and group roles, not from a central authority.
|
|
16106
|
-
|
|
16107
|
-
## Responding to messages
|
|
16108
|
-
When you receive a <channel source="claudemesh" ...> message, RESPOND IMMEDIATELY. Pause your current task, reply via send_message (or \`claudemesh topic post --reply-to <message_id>\` for topic threads), then resume. Stay in character per your system prompt. Do not ignore low-priority messages — acknowledge them briefly even if you defer action.
|
|
16109
|
-
|
|
16110
|
-
The channel attributes carry everything you need to reply — no extra lookups:
|
|
16111
|
-
- \`from_name\` — sender display name. Use as the \`to\` arg when replying to a DM.
|
|
16112
|
-
- \`from_pubkey\` / \`from_member_id\` — stable ids. Use \`from_member_id\` if the sender's display name might change.
|
|
16113
|
-
- \`mesh_slug\` — pass via \`--mesh\` if your default mesh differs.
|
|
16114
|
-
- \`priority\` — \`now\` / \`next\` / \`low\`.
|
|
16115
|
-
- \`message_id\` — id of THIS message. To thread a reply onto it in a topic, run \`claudemesh topic post <topic> "<text>" --reply-to <message_id>\`.
|
|
16116
|
-
- \`topic\` — set when the message arrived through a topic (vs DM). Reply in the same topic.
|
|
16117
|
-
- \`reply_to_id\` — set when the incoming message is itself a reply. Render thread context if you re-narrate.
|
|
16118
|
-
|
|
16119
|
-
If the channel meta contains \`subtype: reminder\`, this is a scheduled reminder you set for yourself — act on it immediately (no reply needed).
|
|
16120
|
-
|
|
16121
|
-
## Tools
|
|
16122
|
-
| Tool | Description |
|
|
16123
|
-
|------|-------------|
|
|
16124
|
-
| send_message(to, message, priority?) | Send to peer name, @group, or * broadcast. \`to\` accepts display name, pubkey hex, @groupname, or *. |
|
|
16125
|
-
| list_peers(mesh_slug?) | List connected peers with status, summary, groups, and roles. |
|
|
16126
|
-
| check_messages() | Drain buffered inbound messages (auto-pushed in most cases, use as fallback). |
|
|
16127
|
-
| set_summary(summary) | Set 1-2 sentence description of your current work, visible to all peers. |
|
|
16128
|
-
| set_status(status) | Override status: idle, working, or dnd. |
|
|
16129
|
-
| set_visible(visible) | Toggle visibility. Hidden peers skip list_peers and broadcasts; direct messages still arrive. |
|
|
16130
|
-
| set_profile(avatar?, title?, bio?, capabilities?) | Set public profile: emoji avatar, short title, bio, capabilities list. |
|
|
16131
|
-
| join_group(name, role?) | Join a @group with optional role (lead, member, observer, or any string). |
|
|
16132
|
-
| leave_group(name) | Leave a @group. |
|
|
16133
|
-
| set_state(key, value) | Write shared state; pushes change to all peers. |
|
|
16134
|
-
| get_state(key) | Read a shared state value. |
|
|
16135
|
-
| list_state() | List all state keys with values, authors, and timestamps. |
|
|
16136
|
-
| remember(content, tags?) | Store persistent knowledge with optional tags. |
|
|
16137
|
-
| recall(query) | Full-text search over mesh memory. |
|
|
16138
|
-
| forget(id) | Soft-delete a memory entry. |
|
|
16139
|
-
| claudemesh file share <path> [--to peer] [--tags a,b] | Share a file with the mesh, or DM it to a specific peer. Same-host fast path: when --to matches a peer on this machine, sends an absolute filepath instead of uploading (no MinIO round-trip). |
|
|
16140
|
-
| claudemesh file get <id> [--out path] | Download a shared file by id. |
|
|
16141
|
-
| claudemesh file list [query] | Find files shared in the mesh. |
|
|
16142
|
-
| claudemesh file status <id> | Check who has accessed a file. |
|
|
16143
|
-
| claudemesh file delete <id> | Remove a shared file from the mesh. |
|
|
16144
|
-
| vector_store(collection, text, metadata?) | Store embedding in per-mesh Qdrant collection. |
|
|
16145
|
-
| vector_search(collection, query, limit?) | Semantic search over stored embeddings. |
|
|
16146
|
-
| vector_delete(collection, id) | Remove an embedding. |
|
|
16147
|
-
| list_collections() | List vector collections in this mesh. |
|
|
16148
|
-
| graph_query(cypher) | Read-only Cypher query on per-mesh Neo4j. |
|
|
16149
|
-
| graph_execute(cypher) | Write Cypher query (CREATE, MERGE, DELETE). |
|
|
16150
|
-
| mesh_query(sql) | Run a SELECT query on the per-mesh shared database. |
|
|
16151
|
-
| mesh_execute(sql) | Run DDL/DML on the per-mesh database (CREATE TABLE, INSERT, UPDATE, DELETE). |
|
|
16152
|
-
| mesh_schema() | List tables and columns in the per-mesh shared database. |
|
|
16153
|
-
| create_stream(name) | Create a real-time data stream in the mesh. |
|
|
16154
|
-
| publish(stream, data) | Push data to a stream. Subscribers receive it in real-time. |
|
|
16155
|
-
| subscribe(stream) | Subscribe to a stream. Data pushes arrive as channel notifications. |
|
|
16156
|
-
| list_streams() | List active streams in the mesh. |
|
|
16157
|
-
| share_context(summary, files_read?, key_findings?, tags?) | Share session understanding with peers. |
|
|
16158
|
-
| get_context(query) | Find context from peers who explored an area. |
|
|
16159
|
-
| list_contexts() | See what all peers currently know. |
|
|
16160
|
-
| create_task(title, assignee?, priority?, tags?) | Create a work item. |
|
|
16161
|
-
| claim_task(id) | Claim an unclaimed task. |
|
|
16162
|
-
| complete_task(id, result?) | Mark task done with optional result. |
|
|
16163
|
-
| list_tasks(status?, assignee?) | List tasks filtered by status/assignee. |
|
|
16164
|
-
| schedule_reminder(message, in_seconds?, deliver_at?, to?) | Schedule a reminder to yourself (no \`to\`) or a delayed message to a peer/group. Delivered as a push with \`subtype: reminder\` in the channel meta. |
|
|
16165
|
-
| list_scheduled() | List pending scheduled reminders and messages. |
|
|
16166
|
-
| cancel_scheduled(id) | Cancel a pending scheduled item. |
|
|
16167
|
-
| read_peer_file(peer, path) | Read a file from another peer's project (max 1MB). |
|
|
16168
|
-
| list_peer_files(peer, path?, pattern?) | List files in a peer's shared directory. |
|
|
16169
|
-
| mesh_mcp_register(server_name, description, tools) | Register an MCP server with the mesh. Other peers can call its tools. |
|
|
16170
|
-
| mesh_mcp_list() | List MCP servers available in the mesh with their tools. |
|
|
16171
|
-
| mesh_tool_call(server_name, tool_name, args?) | Call a tool on a mesh-registered MCP server (30s timeout). |
|
|
16172
|
-
| mesh_mcp_remove(server_name) | Unregister an MCP server you registered. |
|
|
16173
|
-
|
|
16174
|
-
If multiple meshes are joined, prefix \`to\` with \`<mesh-slug>:\` to disambiguate (e.g. \`dev-team:Alice\`).
|
|
16175
|
-
|
|
16176
|
-
Multi-target: send_message accepts an array of targets for the 'to' field.
|
|
16177
|
-
send_message(to: ["Alice", "@backend"], message: "sprint starts")
|
|
16178
|
-
Targets are deduplicated — each peer receives the message once.
|
|
16179
|
-
|
|
16180
|
-
Targeted views: when different audiences need different details about the same event,
|
|
16181
|
-
send tailored messages instead of one generic broadcast:
|
|
16182
|
-
send_message(to: "@frontend", message: "Auth v2: useAuth hook changed, see src/auth/")
|
|
16183
|
-
send_message(to: "@backend", message: "Auth v2: new /api/auth/v2 endpoints, v1 deprecated")
|
|
16184
|
-
send_message(to: "@pm", message: "Auth v2 done. 3 points, no blockers.")
|
|
16185
|
-
|
|
16186
|
-
## Groups
|
|
16187
|
-
Groups are routing labels. Send to @groupname to multicast to all members. Roles are metadata that peers interpret: a "lead" gathers input before synthesizing a response, a "member" contributes when asked, an "observer" watches silently. Join and leave groups dynamically with join_group/leave_group. Check list_peers to see who belongs to which groups and their roles.
|
|
16188
|
-
|
|
16189
|
-
## State
|
|
16190
|
-
Shared key-value store scoped to the mesh. Use get_state/set_state for live coordination facts (deploy frozen? current sprint? PR queue). set_state pushes the change to all connected peers. Read state before asking peers questions — the answer may already be there. State is operational, not archival.
|
|
16191
|
-
|
|
16192
|
-
## Memory
|
|
16193
|
-
Persistent knowledge that survives across sessions. Use remember(content, tags?) to store lessons, decisions, and incidents. Use recall(query) to search before asking peers. New peers should recall at session start to load institutional knowledge.
|
|
16194
|
-
|
|
16195
|
-
## File access — decision guide
|
|
16196
|
-
Three ways to access files. Pick the right one:
|
|
16197
|
-
|
|
16198
|
-
1. **Local peer (same machine, [local] tag):** Read files directly via filesystem using their \`cwd\` path from list_peers. No limit, instant. This is the default for local peers.
|
|
16199
|
-
2. **Remote peer (different machine, [remote] tag):** Use \`read_peer_file(peer, path)\` — relays through the mesh. **1 MB limit**, base64 encoded. Use \`list_peer_files\` to browse first.
|
|
16200
|
-
3. **Persistent sharing (any peer):** Use \`share_file(path)\` — uploads to mesh storage (MinIO). **No size limit**. All peers can download anytime via \`get_file\`. Use for files that need to persist or be shared with multiple peers.
|
|
16201
|
-
|
|
16202
|
-
**Rule of thumb:** local peer → filesystem. Remote peer, small file → read_peer_file. Large file or needs to persist → share_file.
|
|
16203
|
-
|
|
16204
|
-
## Vectors
|
|
16205
|
-
Store and search semantic embeddings. Use vector_store to index content, vector_search to find similar content.
|
|
16206
|
-
|
|
16207
|
-
## Graph
|
|
16208
|
-
Build and query entity relationship graphs. Use graph_execute for writes (CREATE, MERGE), graph_query for reads (MATCH).
|
|
16209
|
-
|
|
16210
|
-
## Mesh Database
|
|
16211
|
-
Per-mesh PostgreSQL database. Use mesh_execute for DDL/DML (CREATE TABLE, INSERT), mesh_query for SELECT, mesh_schema to inspect tables. Schema auto-created on first use.
|
|
16212
|
-
|
|
16213
|
-
## Streams
|
|
16214
|
-
Real-time data channels. create_stream to start one, publish to push data, subscribe to receive pushes. Use for build logs, deploy status, live metrics.
|
|
16215
|
-
|
|
16216
|
-
## Context
|
|
16217
|
-
Share your session understanding with peers. Use share_context after exploring a codebase area. Check get_context before re-reading files another peer already analyzed.
|
|
16218
|
-
|
|
16219
|
-
## Tasks
|
|
16220
|
-
Create and claim work items. create_task to propose work, claim_task to take ownership, complete_task when done. Prevents duplicate effort.
|
|
16221
|
-
|
|
16222
|
-
## Priority
|
|
16223
|
-
- "now": interrupt immediately, even if recipient is in DND (use for urgent: broken deploy, blocking issue)
|
|
16224
|
-
- "next" (default): deliver when recipient goes idle (normal coordination)
|
|
16225
|
-
- "low": pull-only via check_messages (FYI, non-blocking context)
|
|
16226
|
-
|
|
16227
|
-
## Coordination
|
|
16228
|
-
Call list_peers at session start to understand who is online, their roles, and what they are working on. If you are a group lead, gather input from members before responding to external requests — do not answer alone. If you are a member, contribute to your lead when asked. Use @group messages for team-wide questions, direct messages for 1:1 coordination. Set a meaningful summary so peers know your current focus.
|
|
16229
|
-
|
|
16230
|
-
## Message Mode
|
|
16231
|
-
Your message mode is "${messageMode}".
|
|
16232
|
-
- push: messages arrive in real-time as channel notifications. Respond immediately.
|
|
16233
|
-
- inbox: messages are held. You'll see "[inbox] New message from X" notifications. Call check_messages to read them.
|
|
16234
|
-
- off: no message notifications. Use check_messages manually to poll.`
|
|
16235
|
-
});
|
|
16236
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
16237
|
-
tools: TOOLS
|
|
16238
|
-
}));
|
|
16262
|
+
const ok = await daemonReady();
|
|
16263
|
+
if (!ok)
|
|
16264
|
+
bailNoDaemon();
|
|
16265
|
+
const server = new Server({ name: "claudemesh", version: VERSION }, { capabilities: { tools: {}, prompts: {}, resources: {} } });
|
|
16266
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [] }));
|
|
16239
16267
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
16240
|
-
|
|
16241
|
-
|
|
16268
|
+
try {
|
|
16269
|
+
const { status, body } = await daemonGet("/v1/skills");
|
|
16270
|
+
if (status !== 200)
|
|
16271
|
+
return { prompts: [] };
|
|
16272
|
+
const skills = body?.skills ?? [];
|
|
16273
|
+
return { prompts: skills.map((s) => ({ name: s.name, description: s.description, arguments: [] })) };
|
|
16274
|
+
} catch {
|
|
16242
16275
|
return { prompts: [] };
|
|
16243
|
-
|
|
16244
|
-
return {
|
|
16245
|
-
prompts: skills.map((s) => ({
|
|
16246
|
-
name: s.name,
|
|
16247
|
-
description: s.description,
|
|
16248
|
-
arguments: []
|
|
16249
|
-
}))
|
|
16250
|
-
};
|
|
16276
|
+
}
|
|
16251
16277
|
});
|
|
16252
16278
|
server.setRequestHandler(GetPromptRequestSchema, async (req) => {
|
|
16253
|
-
const
|
|
16254
|
-
const
|
|
16255
|
-
if (
|
|
16256
|
-
throw new Error("Not connected to any mesh");
|
|
16257
|
-
const skill = await client.getSkill(name);
|
|
16258
|
-
if (!skill)
|
|
16279
|
+
const name = req.params.name;
|
|
16280
|
+
const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
|
|
16281
|
+
if (status === 404)
|
|
16259
16282
|
throw new Error(`Skill "${name}" not found in the mesh`);
|
|
16283
|
+
if (status !== 200)
|
|
16284
|
+
throw new Error(`daemon returned ${status} fetching skill`);
|
|
16285
|
+
const skill = body.skill;
|
|
16260
16286
|
let content = skill.instructions;
|
|
16261
|
-
const
|
|
16262
|
-
if (
|
|
16287
|
+
const m = skill.manifest;
|
|
16288
|
+
if (m && typeof m === "object") {
|
|
16263
16289
|
const fm = ["---"];
|
|
16264
|
-
if (
|
|
16265
|
-
fm.push(`description: "${
|
|
16266
|
-
if (
|
|
16267
|
-
fm.push(`when_to_use: "${
|
|
16268
|
-
if (
|
|
16290
|
+
if (m.description)
|
|
16291
|
+
fm.push(`description: "${m.description}"`);
|
|
16292
|
+
if (m.when_to_use)
|
|
16293
|
+
fm.push(`when_to_use: "${m.when_to_use}"`);
|
|
16294
|
+
if (Array.isArray(m.allowed_tools) && m.allowed_tools.length) {
|
|
16269
16295
|
fm.push(`allowed-tools:
|
|
16270
|
-
${
|
|
16296
|
+
${m.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
16271
16297
|
`)}`);
|
|
16272
|
-
|
|
16273
|
-
|
|
16274
|
-
|
|
16275
|
-
|
|
16276
|
-
|
|
16277
|
-
|
|
16278
|
-
|
|
16298
|
+
}
|
|
16299
|
+
if (m.model)
|
|
16300
|
+
fm.push(`model: ${m.model}`);
|
|
16301
|
+
if (m.context)
|
|
16302
|
+
fm.push(`context: ${m.context}`);
|
|
16303
|
+
if (m.agent)
|
|
16304
|
+
fm.push(`agent: ${m.agent}`);
|
|
16305
|
+
if (m.user_invocable === false)
|
|
16279
16306
|
fm.push(`user-invocable: false`);
|
|
16280
|
-
if (
|
|
16281
|
-
fm.push(`argument-hint: "${
|
|
16307
|
+
if (m.argument_hint)
|
|
16308
|
+
fm.push(`argument-hint: "${m.argument_hint}"`);
|
|
16282
16309
|
fm.push(`---
|
|
16283
16310
|
`);
|
|
16284
16311
|
if (fm.length > 3)
|
|
16285
16312
|
content = fm.join(`
|
|
16286
16313
|
`) + content;
|
|
16287
|
-
if (
|
|
16288
|
-
const agentType =
|
|
16289
|
-
const modelHint =
|
|
16290
|
-
const toolsHint =
|
|
16291
|
-
Only use these tools: ${
|
|
16314
|
+
if (m.context === "fork") {
|
|
16315
|
+
const agentType = m.agent || "general-purpose";
|
|
16316
|
+
const modelHint = m.model ? `, model: "${m.model}"` : "";
|
|
16317
|
+
const toolsHint = m.allowed_tools?.length ? `
|
|
16318
|
+
Only use these tools: ${m.allowed_tools.join(", ")}.` : "";
|
|
16292
16319
|
content = `IMPORTANT: Execute this skill in an isolated sub-agent. Use the Agent tool with subagent_type="${agentType}"${modelHint}. Pass the full instructions below as the agent prompt.${toolsHint}
|
|
16293
16320
|
|
|
16294
16321
|
` + content;
|
|
@@ -16296,273 +16323,127 @@ Only use these tools: ${manifest.allowed_tools.join(", ")}.` : "";
|
|
|
16296
16323
|
}
|
|
16297
16324
|
return {
|
|
16298
16325
|
description: skill.description,
|
|
16299
|
-
messages: [
|
|
16300
|
-
{
|
|
16301
|
-
role: "user",
|
|
16302
|
-
content: { type: "text", text: content }
|
|
16303
|
-
}
|
|
16304
|
-
]
|
|
16326
|
+
messages: [{ role: "user", content: { type: "text", text: content } }]
|
|
16305
16327
|
};
|
|
16306
16328
|
});
|
|
16307
16329
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
16308
|
-
|
|
16309
|
-
|
|
16330
|
+
try {
|
|
16331
|
+
const { body } = await daemonGet("/v1/skills");
|
|
16332
|
+
const skills = body?.skills ?? [];
|
|
16333
|
+
return {
|
|
16334
|
+
resources: skills.map((s) => ({
|
|
16335
|
+
uri: `skill://claudemesh/${encodeURIComponent(s.name)}`,
|
|
16336
|
+
name: s.name,
|
|
16337
|
+
description: s.description,
|
|
16338
|
+
mimeType: "text/markdown"
|
|
16339
|
+
}))
|
|
16340
|
+
};
|
|
16341
|
+
} catch {
|
|
16310
16342
|
return { resources: [] };
|
|
16311
|
-
|
|
16312
|
-
return {
|
|
16313
|
-
resources: skills.map((s) => ({
|
|
16314
|
-
uri: `skill://claudemesh/${encodeURIComponent(s.name)}`,
|
|
16315
|
-
name: s.name,
|
|
16316
|
-
description: s.description,
|
|
16317
|
-
mimeType: "text/markdown"
|
|
16318
|
-
}))
|
|
16319
|
-
};
|
|
16343
|
+
}
|
|
16320
16344
|
});
|
|
16321
16345
|
server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
|
|
16322
|
-
const
|
|
16323
|
-
const
|
|
16324
|
-
if (!
|
|
16346
|
+
const uri = req.params.uri;
|
|
16347
|
+
const m = uri.match(/^skill:\/\/claudemesh\/(.+)$/);
|
|
16348
|
+
if (!m)
|
|
16325
16349
|
throw new Error(`Unknown resource URI: ${uri}`);
|
|
16326
|
-
const name = decodeURIComponent(
|
|
16327
|
-
const
|
|
16328
|
-
if (
|
|
16329
|
-
throw new Error("Not connected to any mesh");
|
|
16330
|
-
const skill = await client.getSkill(name);
|
|
16331
|
-
if (!skill)
|
|
16350
|
+
const name = decodeURIComponent(m[1]);
|
|
16351
|
+
const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
|
|
16352
|
+
if (status === 404)
|
|
16332
16353
|
throw new Error(`Skill "${name}" not found`);
|
|
16333
|
-
|
|
16334
|
-
|
|
16335
|
-
|
|
16336
|
-
|
|
16337
|
-
|
|
16338
|
-
|
|
16339
|
-
if (
|
|
16340
|
-
|
|
16341
|
-
|
|
16342
|
-
|
|
16343
|
-
|
|
16344
|
-
|
|
16354
|
+
if (status !== 200)
|
|
16355
|
+
throw new Error(`daemon returned ${status} fetching skill`);
|
|
16356
|
+
const skill = body.skill;
|
|
16357
|
+
const fm = ["---"];
|
|
16358
|
+
fm.push(`name: ${skill.name}`);
|
|
16359
|
+
fm.push(`description: "${skill.description}"`);
|
|
16360
|
+
if (skill.tags?.length)
|
|
16361
|
+
fm.push(`tags: [${skill.tags.join(", ")}]`);
|
|
16362
|
+
const mf = skill.manifest;
|
|
16363
|
+
if (mf && typeof mf === "object") {
|
|
16364
|
+
if (mf.when_to_use)
|
|
16365
|
+
fm.push(`when_to_use: "${mf.when_to_use}"`);
|
|
16366
|
+
if (Array.isArray(mf.allowed_tools) && mf.allowed_tools.length) {
|
|
16367
|
+
fm.push(`allowed-tools:
|
|
16368
|
+
${mf.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
16345
16369
|
`)}`);
|
|
16346
|
-
|
|
16347
|
-
|
|
16348
|
-
|
|
16349
|
-
|
|
16350
|
-
|
|
16351
|
-
|
|
16352
|
-
|
|
16353
|
-
fmLines.push(`user-invocable: false`);
|
|
16354
|
-
if (manifest.argument_hint)
|
|
16355
|
-
fmLines.push(`argument-hint: "${manifest.argument_hint}"`);
|
|
16356
|
-
}
|
|
16357
|
-
fmLines.push(`---
|
|
16370
|
+
}
|
|
16371
|
+
if (mf.model)
|
|
16372
|
+
fm.push(`model: ${mf.model}`);
|
|
16373
|
+
if (mf.context)
|
|
16374
|
+
fm.push(`context: ${mf.context}`);
|
|
16375
|
+
}
|
|
16376
|
+
fm.push(`---
|
|
16358
16377
|
`);
|
|
16359
|
-
|
|
16360
|
-
`) + skill.instructions;
|
|
16361
|
-
return {
|
|
16362
|
-
contents: [
|
|
16363
|
-
{
|
|
16364
|
-
uri,
|
|
16365
|
-
mimeType: "text/markdown",
|
|
16366
|
-
text: fullContent
|
|
16367
|
-
}
|
|
16368
|
-
]
|
|
16369
|
-
};
|
|
16378
|
+
return { contents: [{ uri, mimeType: "text/markdown", text: fm.join(`
|
|
16379
|
+
`) + skill.instructions }] };
|
|
16370
16380
|
});
|
|
16371
|
-
const
|
|
16372
|
-
|
|
16373
|
-
|
|
16374
|
-
|
|
16375
|
-
|
|
16376
|
-
|
|
16377
|
-
const
|
|
16378
|
-
|
|
16379
|
-
|
|
16380
|
-
|
|
16381
|
-
|
|
16382
|
-
|
|
16383
|
-
|
|
16384
|
-
|
|
16385
|
-
|
|
16386
|
-
|
|
16387
|
-
|
|
16388
|
-
|
|
16389
|
-
|
|
16390
|
-
|
|
16391
|
-
|
|
16392
|
-
|
|
16393
|
-
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16397
|
-
|
|
16398
|
-
} else if (eventName === "peer_joined") {
|
|
16399
|
-
content2 = `[system] Peer "${data.name ?? "unknown"}" joined the mesh`;
|
|
16400
|
-
} else if (eventName === "peer_returned") {
|
|
16401
|
-
const peerName = String(data.name ?? "unknown");
|
|
16402
|
-
const lastSeenAt = data.lastSeenAt ? relativeTime(String(data.lastSeenAt)) : "unknown";
|
|
16403
|
-
const groups = Array.isArray(data.groups) ? data.groups.map((g) => g.role ? `@${g.name}:${g.role}` : `@${g.name}`).join(", ") : "";
|
|
16404
|
-
const summary = data.summary ? ` Summary: "${data.summary}"` : "";
|
|
16405
|
-
content2 = `[system] Welcome back, "${peerName}"! Last seen ${lastSeenAt}.${groups ? ` Restored: ${groups}` : ""}${summary}`;
|
|
16406
|
-
} else if (eventName === "peer_left") {
|
|
16407
|
-
content2 = `[system] Peer "${data.name ?? "unknown"}" left the mesh`;
|
|
16408
|
-
} else if (eventName === "mcp_registered") {
|
|
16409
|
-
const tools = Array.isArray(data.tools) ? data.tools.join(", ") : "";
|
|
16410
|
-
content2 = `[system] New MCP server available: "${data.serverName}" (hosted by ${data.hostedBy}). Tools: ${tools}. Use mesh_tool_call to invoke.`;
|
|
16411
|
-
} else if (eventName === "mcp_unregistered") {
|
|
16412
|
-
content2 = `[system] MCP server "${data.serverName}" removed (was hosted by ${data.hostedBy})`;
|
|
16413
|
-
} else if (eventName === "mcp_restored") {
|
|
16414
|
-
content2 = `[system] MCP server "${data.serverName}" is back online (hosted by ${data.hostedBy})`;
|
|
16415
|
-
} else if (eventName === "watch_triggered") {
|
|
16416
|
-
content2 = `[WATCH] ${data.label ?? data.url}: ${data.oldValue} → ${data.newValue}`;
|
|
16417
|
-
} else if (eventName === "mcp_deployed") {
|
|
16418
|
-
content2 = `[SERVICE] "${data.name}" deployed (${data.tool_count} tools) by ${data.deployed_by}`;
|
|
16419
|
-
} else if (eventName === "mcp_undeployed") {
|
|
16420
|
-
content2 = `[SERVICE] "${data.name}" undeployed by ${data.by}`;
|
|
16421
|
-
} else if (eventName === "mcp_scope_changed") {
|
|
16422
|
-
content2 = `[SERVICE] "${data.name}" scope changed to ${JSON.stringify(data.scope)} by ${data.by}`;
|
|
16423
|
-
} else {
|
|
16424
|
-
content2 = `[system] ${eventName}: ${JSON.stringify(data)}`;
|
|
16425
|
-
}
|
|
16426
|
-
try {
|
|
16427
|
-
await server.notification({
|
|
16428
|
-
method: "notifications/claude/channel",
|
|
16429
|
-
params: {
|
|
16430
|
-
content: content2,
|
|
16431
|
-
meta: {
|
|
16432
|
-
kind: "system",
|
|
16433
|
-
event: eventName,
|
|
16434
|
-
mesh_slug: client.meshSlug,
|
|
16435
|
-
mesh_id: client.meshId,
|
|
16436
|
-
...Object.keys(data).length > 0 ? { eventData: JSON.stringify(data) } : {}
|
|
16437
|
-
}
|
|
16438
|
-
}
|
|
16439
|
-
});
|
|
16440
|
-
process.stderr.write(`[claudemesh] system: ${content2}
|
|
16441
|
-
`);
|
|
16442
|
-
} catch (pushErr) {
|
|
16443
|
-
process.stderr.write(`[claudemesh] system push FAILED: ${pushErr}
|
|
16444
|
-
`);
|
|
16445
|
-
}
|
|
16446
|
-
return;
|
|
16447
|
-
}
|
|
16448
|
-
const fromPubkey = msg.senderPubkey || "";
|
|
16449
|
-
const fromName = fromPubkey ? await resolvePeerName(client, fromPubkey) : "unknown";
|
|
16450
|
-
if (fromPubkey) {
|
|
16451
|
-
try {
|
|
16452
|
-
const { isAllowed: isAllowed2 } = await Promise.resolve().then(() => (init_grants(), exports_grants));
|
|
16453
|
-
const kindCap = msg.kind === "broadcast" ? "broadcast" : "dm";
|
|
16454
|
-
if (!isAllowed2(client.meshSlug, fromPubkey, kindCap)) {
|
|
16455
|
-
process.stderr.write(`[claudemesh] dropped ${kindCap} from ${fromName} (not granted)
|
|
16456
|
-
`);
|
|
16457
|
-
return;
|
|
16458
|
-
}
|
|
16459
|
-
} catch {}
|
|
16460
|
-
}
|
|
16461
|
-
if (messageMode === "inbox") {
|
|
16462
|
-
try {
|
|
16463
|
-
await server.notification({
|
|
16464
|
-
method: "notifications/claude/channel",
|
|
16465
|
-
params: {
|
|
16466
|
-
content: `[inbox] New message from ${fromName}. Use check_messages to read.`,
|
|
16467
|
-
meta: { kind: "inbox_notification", from_name: fromName }
|
|
16468
|
-
}
|
|
16469
|
-
});
|
|
16470
|
-
} catch {}
|
|
16471
|
-
return;
|
|
16472
|
-
}
|
|
16473
|
-
const body = msg.plaintext ?? decryptFailedWarning(fromPubkey);
|
|
16474
|
-
const prioBadge = msg.priority === "now" ? "[URGENT] " : msg.priority === "low" ? "[low] " : "";
|
|
16475
|
-
const kindBadge = msg.kind === "broadcast" ? " (broadcast)" : "";
|
|
16476
|
-
const content = `${prioBadge}${fromName}${kindBadge}: ${body}`;
|
|
16477
|
-
const fromMemberPubkey = msg.senderMemberPubkey ?? fromPubkey;
|
|
16478
|
-
try {
|
|
16479
|
-
await server.notification({
|
|
16480
|
-
method: "notifications/claude/channel",
|
|
16481
|
-
params: {
|
|
16482
|
-
content,
|
|
16483
|
-
meta: {
|
|
16484
|
-
from_id: fromMemberPubkey,
|
|
16485
|
-
from_pubkey: fromMemberPubkey,
|
|
16486
|
-
from_session_pubkey: fromPubkey,
|
|
16487
|
-
from_name: fromName,
|
|
16488
|
-
...msg.senderMemberId ? { from_member_id: msg.senderMemberId } : {},
|
|
16489
|
-
mesh_slug: client.meshSlug,
|
|
16490
|
-
mesh_id: client.meshId,
|
|
16491
|
-
priority: msg.priority,
|
|
16492
|
-
sent_at: msg.createdAt,
|
|
16493
|
-
delivered_at: msg.receivedAt,
|
|
16494
|
-
kind: msg.kind,
|
|
16495
|
-
message_id: msg.messageId,
|
|
16496
|
-
...msg.topic ? { topic: msg.topic } : {},
|
|
16497
|
-
...msg.replyToId ? { reply_to_id: msg.replyToId } : {},
|
|
16498
|
-
...msg.subtype ? { subtype: msg.subtype } : {}
|
|
16499
|
-
}
|
|
16381
|
+
const sub = subscribeEvents(async (ev) => {
|
|
16382
|
+
if (ev.kind === "message") {
|
|
16383
|
+
const d = ev.data;
|
|
16384
|
+
const fromName = String(d.sender_name ?? "unknown");
|
|
16385
|
+
const fromMember = String(d.sender_member_pubkey ?? d.sender_pubkey ?? "");
|
|
16386
|
+
const body = String(d.body ?? "(decrypt failed)");
|
|
16387
|
+
const priority = String(d.priority ?? "next");
|
|
16388
|
+
const prioBadge = priority === "now" ? "[URGENT] " : priority === "low" ? "[low] " : "";
|
|
16389
|
+
const topicTag = d.topic ? ` (#${d.topic})` : "";
|
|
16390
|
+
const content = `${prioBadge}${fromName}${topicTag}: ${body}`;
|
|
16391
|
+
try {
|
|
16392
|
+
await server.notification({
|
|
16393
|
+
method: "notifications/claude/channel",
|
|
16394
|
+
params: {
|
|
16395
|
+
content,
|
|
16396
|
+
meta: {
|
|
16397
|
+
from_id: fromMember,
|
|
16398
|
+
from_pubkey: fromMember,
|
|
16399
|
+
from_session_pubkey: String(d.sender_pubkey ?? ""),
|
|
16400
|
+
from_name: fromName,
|
|
16401
|
+
mesh_slug: String(d.mesh ?? ""),
|
|
16402
|
+
priority,
|
|
16403
|
+
message_id: String(d.broker_message_id ?? d.id ?? ""),
|
|
16404
|
+
client_message_id: String(d.client_message_id ?? ""),
|
|
16405
|
+
...d.topic ? { topic: String(d.topic) } : {},
|
|
16406
|
+
...d.reply_to_id ? { reply_to_id: String(d.reply_to_id) } : {},
|
|
16407
|
+
...d.subtype ? { subtype: String(d.subtype) } : {}
|
|
16500
16408
|
}
|
|
16501
|
-
}
|
|
16502
|
-
|
|
16503
|
-
|
|
16504
|
-
|
|
16505
|
-
process.stderr.write(`[claudemesh] push FAILED: ${pushErr}
|
|
16409
|
+
}
|
|
16410
|
+
});
|
|
16411
|
+
} catch (err) {
|
|
16412
|
+
process.stderr.write(`[claudemesh-mcp] channel emit failed: ${err}
|
|
16506
16413
|
`);
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
|
|
16510
|
-
|
|
16511
|
-
|
|
16512
|
-
|
|
16513
|
-
|
|
16514
|
-
|
|
16515
|
-
|
|
16516
|
-
|
|
16517
|
-
|
|
16518
|
-
|
|
16519
|
-
}
|
|
16520
|
-
}
|
|
16521
|
-
});
|
|
16522
|
-
} catch {}
|
|
16523
|
-
});
|
|
16524
|
-
client.onStateChange(async (change) => {
|
|
16525
|
-
try {
|
|
16526
|
-
await server.notification({
|
|
16527
|
-
method: "notifications/claude/channel",
|
|
16528
|
-
params: {
|
|
16529
|
-
content: `[state] ${change.key} = ${JSON.stringify(change.value)} (set by ${change.updatedBy})`,
|
|
16530
|
-
meta: {
|
|
16531
|
-
kind: "state_change",
|
|
16532
|
-
key: change.key,
|
|
16533
|
-
updated_by: change.updatedBy
|
|
16534
|
-
}
|
|
16535
|
-
}
|
|
16536
|
-
});
|
|
16537
|
-
} catch {}
|
|
16538
|
-
});
|
|
16539
|
-
}
|
|
16540
|
-
setTimeout(async () => {
|
|
16541
|
-
const welcomeClient = allClients()[0];
|
|
16542
|
-
if (!welcomeClient || welcomeClient.status !== "open")
|
|
16543
|
-
return;
|
|
16414
|
+
}
|
|
16415
|
+
} else if (ev.kind === "peer_join" || ev.kind === "peer_leave" || ev.kind === "system") {
|
|
16416
|
+
const d = ev.data;
|
|
16417
|
+
const eventName = String(d.event ?? ev.kind);
|
|
16418
|
+
let content;
|
|
16419
|
+
if (ev.kind === "peer_join") {
|
|
16420
|
+
content = `[system] Peer "${String(d.name ?? "unknown")}" joined the mesh`;
|
|
16421
|
+
} else if (ev.kind === "peer_leave") {
|
|
16422
|
+
content = `[system] Peer "${String(d.name ?? "unknown")}" left the mesh`;
|
|
16423
|
+
} else {
|
|
16424
|
+
content = `[system] ${eventName}: ${JSON.stringify(d).slice(0, 240)}`;
|
|
16425
|
+
}
|
|
16544
16426
|
try {
|
|
16545
|
-
const peers = await welcomeClient.listPeers();
|
|
16546
|
-
const peerNames = peers.filter((p) => p.displayName !== myName).map((p) => p.displayName).join(", ") || "none";
|
|
16547
16427
|
await server.notification({
|
|
16548
16428
|
method: "notifications/claude/channel",
|
|
16549
16429
|
params: {
|
|
16550
|
-
content
|
|
16551
|
-
meta: {
|
|
16430
|
+
content,
|
|
16431
|
+
meta: {
|
|
16432
|
+
kind: "system",
|
|
16433
|
+
event: eventName,
|
|
16434
|
+
mesh_slug: String(d.mesh ?? "")
|
|
16435
|
+
}
|
|
16552
16436
|
}
|
|
16553
16437
|
});
|
|
16554
16438
|
} catch {}
|
|
16555
|
-
}
|
|
16556
|
-
}
|
|
16439
|
+
}
|
|
16440
|
+
});
|
|
16441
|
+
const transport = new StdioServerTransport;
|
|
16442
|
+
await server.connect(transport);
|
|
16557
16443
|
const keepalive = setInterval(() => {}, 1000);
|
|
16558
16444
|
const shutdown = () => {
|
|
16559
16445
|
clearInterval(keepalive);
|
|
16560
|
-
|
|
16561
|
-
try {
|
|
16562
|
-
b.stop();
|
|
16563
|
-
} catch {}
|
|
16564
|
-
}
|
|
16565
|
-
stopAll();
|
|
16446
|
+
sub.close();
|
|
16566
16447
|
process.exit(0);
|
|
16567
16448
|
};
|
|
16568
16449
|
process.on("SIGTERM", shutdown);
|
|
@@ -16593,9 +16474,8 @@ async function startServiceProxy(serviceName) {
|
|
|
16593
16474
|
tools = fetched;
|
|
16594
16475
|
} catch {
|
|
16595
16476
|
const cached2 = client.serviceCatalog.find((s) => s.name === serviceName);
|
|
16596
|
-
if (cached2)
|
|
16477
|
+
if (cached2)
|
|
16597
16478
|
tools = cached2.tools;
|
|
16598
|
-
}
|
|
16599
16479
|
}
|
|
16600
16480
|
if (tools.length === 0) {
|
|
16601
16481
|
process.stderr.write(`[mesh:${serviceName}] no tools found — service may not be running
|
|
@@ -16620,12 +16500,7 @@ async function startServiceProxy(serviceName) {
|
|
|
16620
16500
|
}
|
|
16621
16501
|
if (client.status !== "open") {
|
|
16622
16502
|
return {
|
|
16623
|
-
content: [
|
|
16624
|
-
{
|
|
16625
|
-
type: "text",
|
|
16626
|
-
text: `Service temporarily unavailable — broker reconnecting. Retry in a few seconds.`
|
|
16627
|
-
}
|
|
16628
|
-
],
|
|
16503
|
+
content: [{ type: "text", text: "Service temporarily unavailable — broker reconnecting. Retry in a few seconds." }],
|
|
16629
16504
|
isError: true
|
|
16630
16505
|
};
|
|
16631
16506
|
}
|
|
@@ -16633,23 +16508,13 @@ async function startServiceProxy(serviceName) {
|
|
|
16633
16508
|
try {
|
|
16634
16509
|
const result = await client.mcpCall(serviceName, toolName, args);
|
|
16635
16510
|
if (result.error) {
|
|
16636
|
-
return {
|
|
16637
|
-
content: [{ type: "text", text: `Error: ${result.error}` }],
|
|
16638
|
-
isError: true
|
|
16639
|
-
};
|
|
16511
|
+
return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
|
|
16640
16512
|
}
|
|
16641
16513
|
const resultText = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
|
|
16642
|
-
return {
|
|
16643
|
-
content: [{ type: "text", text: resultText }]
|
|
16644
|
-
};
|
|
16514
|
+
return { content: [{ type: "text", text: resultText }] };
|
|
16645
16515
|
} catch (e) {
|
|
16646
16516
|
return {
|
|
16647
|
-
content: [
|
|
16648
|
-
{
|
|
16649
|
-
type: "text",
|
|
16650
|
-
text: `Call failed: ${e instanceof Error ? e.message : String(e)}`
|
|
16651
|
-
}
|
|
16652
|
-
],
|
|
16517
|
+
content: [{ type: "text", text: `Call failed: ${e instanceof Error ? e.message : String(e)}` }],
|
|
16653
16518
|
isError: true
|
|
16654
16519
|
};
|
|
16655
16520
|
}
|
|
@@ -16665,9 +16530,7 @@ async function startServiceProxy(serviceName) {
|
|
|
16665
16530
|
const newTools = push.eventData?.tools;
|
|
16666
16531
|
if (Array.isArray(newTools)) {
|
|
16667
16532
|
tools = newTools;
|
|
16668
|
-
server.notification({
|
|
16669
|
-
method: "notifications/tools/list_changed"
|
|
16670
|
-
}).catch(() => {});
|
|
16533
|
+
server.notification({ method: "notifications/tools/list_changed" }).catch(() => {});
|
|
16671
16534
|
}
|
|
16672
16535
|
}
|
|
16673
16536
|
});
|
|
@@ -16682,13 +16545,12 @@ async function startServiceProxy(serviceName) {
|
|
|
16682
16545
|
process.on("SIGTERM", shutdown);
|
|
16683
16546
|
process.on("SIGINT", shutdown);
|
|
16684
16547
|
}
|
|
16685
|
-
var
|
|
16686
|
-
var
|
|
16687
|
-
|
|
16548
|
+
var DAEMON_BOOT_RETRIES = 4, DAEMON_BOOT_RETRY_MS = 500;
|
|
16549
|
+
var init_server2 = __esm(() => {
|
|
16550
|
+
init_paths2();
|
|
16551
|
+
init_urls();
|
|
16688
16552
|
init_facade();
|
|
16689
16553
|
init_facade8();
|
|
16690
|
-
init_server2();
|
|
16691
|
-
peerNameCache = new Map;
|
|
16692
16554
|
});
|
|
16693
16555
|
|
|
16694
16556
|
// src/commands/mcp.ts
|
|
@@ -16703,7 +16565,7 @@ async function runMcp() {
|
|
|
16703
16565
|
process.exit(0);
|
|
16704
16566
|
}
|
|
16705
16567
|
var init_mcp = __esm(() => {
|
|
16706
|
-
|
|
16568
|
+
init_server2();
|
|
16707
16569
|
});
|
|
16708
16570
|
|
|
16709
16571
|
// src/commands/hook.ts
|
|
@@ -17484,9 +17346,9 @@ Platform
|
|
|
17484
17346
|
claudemesh stream create|publish|list pub/sub event bus
|
|
17485
17347
|
claudemesh sql query|execute|schema per-mesh SQL
|
|
17486
17348
|
claudemesh skill list|get|remove mesh-published skills
|
|
17487
|
-
claudemesh vault list|delete
|
|
17488
|
-
claudemesh watch list|remove
|
|
17489
|
-
claudemesh webhook list|delete
|
|
17349
|
+
claudemesh vault set|list|delete encrypted secrets (set: --type env|file --mount /p)
|
|
17350
|
+
claudemesh watch add|list|remove URL change watchers (add: --label --interval --extract)
|
|
17351
|
+
claudemesh webhook create|list|delete outbound HTTP triggers
|
|
17490
17352
|
claudemesh file share <path> [--to peer] upload (or local-host fast path if --to matches)
|
|
17491
17353
|
claudemesh file get <id> [--out path] download by id
|
|
17492
17354
|
claudemesh file list|status|delete shared mesh files
|
|
@@ -18150,8 +18012,16 @@ async function main() {
|
|
|
18150
18012
|
} else if (sub === "delete") {
|
|
18151
18013
|
const { runVaultDelete: runVaultDelete2 } = await Promise.resolve().then(() => (init_platform_actions(), exports_platform_actions));
|
|
18152
18014
|
process.exit(await runVaultDelete2(positionals[1] ?? "", f));
|
|
18015
|
+
} else if (sub === "set") {
|
|
18016
|
+
const { runVaultSet: runVaultSet2 } = await Promise.resolve().then(() => (init_platform_actions(), exports_platform_actions));
|
|
18017
|
+
process.exit(await runVaultSet2(positionals[1] ?? "", positionals[2] ?? "", {
|
|
18018
|
+
...f,
|
|
18019
|
+
entryType: flags.type,
|
|
18020
|
+
mountPath: flags.mount,
|
|
18021
|
+
description: flags.description
|
|
18022
|
+
}));
|
|
18153
18023
|
} else {
|
|
18154
|
-
console.error("Usage: claudemesh vault <list|delete>
|
|
18024
|
+
console.error("Usage: claudemesh vault <list|set|delete>");
|
|
18155
18025
|
process.exit(EXIT.INVALID_ARGS);
|
|
18156
18026
|
}
|
|
18157
18027
|
break;
|
|
@@ -18165,8 +18035,18 @@ async function main() {
|
|
|
18165
18035
|
} else if (sub === "remove") {
|
|
18166
18036
|
const { runUnwatch: runUnwatch2 } = await Promise.resolve().then(() => (init_platform_actions(), exports_platform_actions));
|
|
18167
18037
|
process.exit(await runUnwatch2(positionals[1] ?? "", f));
|
|
18038
|
+
} else if (sub === "add") {
|
|
18039
|
+
const { runWatchAdd: runWatchAdd2 } = await Promise.resolve().then(() => (init_platform_actions(), exports_platform_actions));
|
|
18040
|
+
process.exit(await runWatchAdd2(positionals[1] ?? "", {
|
|
18041
|
+
...f,
|
|
18042
|
+
label: flags.label,
|
|
18043
|
+
interval: flags.interval ? Number(flags.interval) : undefined,
|
|
18044
|
+
mode: flags.mode,
|
|
18045
|
+
extract: flags.extract,
|
|
18046
|
+
notifyOn: flags["notify-on"]
|
|
18047
|
+
}));
|
|
18168
18048
|
} else {
|
|
18169
|
-
console.error("Usage: claudemesh watch <list|remove>");
|
|
18049
|
+
console.error("Usage: claudemesh watch <list|add|remove>");
|
|
18170
18050
|
process.exit(EXIT.INVALID_ARGS);
|
|
18171
18051
|
}
|
|
18172
18052
|
break;
|
|
@@ -18180,8 +18060,11 @@ async function main() {
|
|
|
18180
18060
|
} else if (sub === "delete") {
|
|
18181
18061
|
const { runWebhookDelete: runWebhookDelete2 } = await Promise.resolve().then(() => (init_platform_actions(), exports_platform_actions));
|
|
18182
18062
|
process.exit(await runWebhookDelete2(positionals[1] ?? "", f));
|
|
18063
|
+
} else if (sub === "create") {
|
|
18064
|
+
const { runWebhookCreate: runWebhookCreate2 } = await Promise.resolve().then(() => (init_platform_actions(), exports_platform_actions));
|
|
18065
|
+
process.exit(await runWebhookCreate2(positionals[1] ?? "", f));
|
|
18183
18066
|
} else {
|
|
18184
|
-
console.error("Usage: claudemesh webhook <list|delete>");
|
|
18067
|
+
console.error("Usage: claudemesh webhook <list|create|delete>");
|
|
18185
18068
|
process.exit(EXIT.INVALID_ARGS);
|
|
18186
18069
|
}
|
|
18187
18070
|
break;
|
|
@@ -18493,4 +18376,4 @@ main().catch((err) => {
|
|
|
18493
18376
|
process.exit(EXIT.INTERNAL_ERROR);
|
|
18494
18377
|
});
|
|
18495
18378
|
|
|
18496
|
-
//# debugId=
|
|
18379
|
+
//# debugId=BAEC2C4FE7977E7264756E2164756E21
|