claudemesh-cli 1.23.0 → 1.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entrypoints/cli.js +517 -753
- package/dist/entrypoints/cli.js.map +10 -12
- package/dist/entrypoints/mcp.js +441 -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",
|
|
@@ -3308,44 +3323,10 @@ var init_ws_client = __esm(() => {
|
|
|
3308
3323
|
});
|
|
3309
3324
|
|
|
3310
3325
|
// src/services/broker/manager.ts
|
|
3311
|
-
|
|
3312
|
-
const existing = clients.get(mesh.meshId);
|
|
3313
|
-
if (existing)
|
|
3314
|
-
return existing;
|
|
3315
|
-
const isDebug2 = process.env.CLAUDEMESH_DEBUG === "1" || process.env.CLAUDEMESH_DEBUG === "true";
|
|
3316
|
-
const client = new BrokerClient(mesh, { debug: isDebug2, displayName: configDisplayName });
|
|
3317
|
-
clients.set(mesh.meshId, client);
|
|
3318
|
-
try {
|
|
3319
|
-
await client.connect();
|
|
3320
|
-
for (const g of configGroups ?? []) {
|
|
3321
|
-
try {
|
|
3322
|
-
await client.joinGroup(g.name, g.role);
|
|
3323
|
-
} catch {}
|
|
3324
|
-
}
|
|
3325
|
-
} catch (err) {
|
|
3326
|
-
process.stderr.write(`[claudemesh] broker connect failed for ${mesh.slug}: ${err instanceof Error ? err.message : err} (will retry)
|
|
3327
|
-
`);
|
|
3328
|
-
}
|
|
3329
|
-
return client;
|
|
3330
|
-
}
|
|
3331
|
-
async function startClients(config) {
|
|
3332
|
-
configDisplayName = config.displayName;
|
|
3333
|
-
configGroups = config.groups ?? [];
|
|
3334
|
-
await Promise.allSettled(config.meshes.map(ensureClient));
|
|
3335
|
-
}
|
|
3336
|
-
function allClients() {
|
|
3337
|
-
return [...clients.values()];
|
|
3338
|
-
}
|
|
3339
|
-
function stopAll() {
|
|
3340
|
-
for (const c of clients.values())
|
|
3341
|
-
c.close();
|
|
3342
|
-
clients.clear();
|
|
3343
|
-
}
|
|
3344
|
-
var clients, configDisplayName, configGroups;
|
|
3326
|
+
var clients;
|
|
3345
3327
|
var init_manager = __esm(() => {
|
|
3346
3328
|
init_ws_client();
|
|
3347
3329
|
clients = new Map;
|
|
3348
|
-
configGroups = [];
|
|
3349
3330
|
});
|
|
3350
3331
|
|
|
3351
3332
|
// src/services/broker/envelope.ts
|
|
@@ -3600,6 +3581,42 @@ var init_spinner = __esm(() => {
|
|
|
3600
3581
|
FRAME_HEIGHT = H;
|
|
3601
3582
|
});
|
|
3602
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
|
+
|
|
3603
3620
|
// src/commands/launch.ts
|
|
3604
3621
|
var exports_launch = {};
|
|
3605
3622
|
__export(exports_launch, {
|
|
@@ -3609,8 +3626,41 @@ import { spawnSync as spawnSync2 } from "node:child_process";
|
|
|
3609
3626
|
import { randomUUID } from "node:crypto";
|
|
3610
3627
|
import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, statSync, existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
|
|
3611
3628
|
import { tmpdir, hostname as hostname2, homedir as homedir3 } from "node:os";
|
|
3612
|
-
import { join as
|
|
3629
|
+
import { join as join4 } from "node:path";
|
|
3613
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
|
+
}
|
|
3614
3664
|
function parseGroupsString(raw) {
|
|
3615
3665
|
return raw.split(",").map((s) => s.trim()).filter(Boolean).map((token) => {
|
|
3616
3666
|
const idx = token.indexOf(":");
|
|
@@ -3930,14 +3980,15 @@ async function runLaunch(flags, rawArgs) {
|
|
|
3930
3980
|
for (const entry of readdirSync(tmpBase)) {
|
|
3931
3981
|
if (!entry.startsWith("claudemesh-"))
|
|
3932
3982
|
continue;
|
|
3933
|
-
const full =
|
|
3983
|
+
const full = join4(tmpBase, entry);
|
|
3934
3984
|
const age = Date.now() - statSync(full).mtimeMs;
|
|
3935
3985
|
if (age > 3600000)
|
|
3936
3986
|
rmSync(full, { recursive: true, force: true });
|
|
3937
3987
|
}
|
|
3938
3988
|
} catch {}
|
|
3989
|
+
await ensureDaemonRunning(mesh.slug, args.quiet);
|
|
3939
3990
|
try {
|
|
3940
|
-
const claudeConfigPath =
|
|
3991
|
+
const claudeConfigPath = join4(homedir3(), ".claude.json");
|
|
3941
3992
|
if (existsSync5(claudeConfigPath)) {
|
|
3942
3993
|
const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
|
|
3943
3994
|
const mcpServers = claudeConfig.mcpServers ?? {};
|
|
@@ -3974,7 +4025,7 @@ async function runLaunch(flags, rawArgs) {
|
|
|
3974
4025
|
console.log(" (Could not fetch service catalog — mesh services won't be natively available)");
|
|
3975
4026
|
}
|
|
3976
4027
|
}
|
|
3977
|
-
const tmpDir = mkdtempSync(
|
|
4028
|
+
const tmpDir = mkdtempSync(join4(tmpdir(), "claudemesh-"));
|
|
3978
4029
|
const sessionConfig = {
|
|
3979
4030
|
version: 1,
|
|
3980
4031
|
meshes: [mesh],
|
|
@@ -3983,14 +4034,14 @@ async function runLaunch(flags, rawArgs) {
|
|
|
3983
4034
|
...parsedGroups.length > 0 ? { groups: parsedGroups } : {},
|
|
3984
4035
|
messageMode
|
|
3985
4036
|
};
|
|
3986
|
-
writeFileSync4(
|
|
4037
|
+
writeFileSync4(join4(tmpDir, "config.json"), JSON.stringify(sessionConfig, null, 2) + `
|
|
3987
4038
|
`, "utf-8");
|
|
3988
4039
|
if (!args.quiet) {
|
|
3989
4040
|
printBanner(displayName, mesh.slug, role, parsedGroups, messageMode);
|
|
3990
4041
|
}
|
|
3991
4042
|
const meshMcpEntries = [];
|
|
3992
4043
|
if (serviceCatalog.length > 0) {
|
|
3993
|
-
const claudeConfigPath =
|
|
4044
|
+
const claudeConfigPath = join4(homedir3(), ".claude.json");
|
|
3994
4045
|
let claudeConfig = {};
|
|
3995
4046
|
try {
|
|
3996
4047
|
claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
|
|
@@ -4057,9 +4108,9 @@ async function runLaunch(flags, rawArgs) {
|
|
|
4057
4108
|
let claudeBin = "claude";
|
|
4058
4109
|
if (!isWindows2) {
|
|
4059
4110
|
const candidates = [
|
|
4060
|
-
|
|
4111
|
+
join4(homedir3(), ".local", "bin", "claude"),
|
|
4061
4112
|
"/usr/local/bin/claude",
|
|
4062
|
-
|
|
4113
|
+
join4(homedir3(), ".claude", "bin", "claude")
|
|
4063
4114
|
];
|
|
4064
4115
|
for (const c of candidates) {
|
|
4065
4116
|
if (existsSync5(c)) {
|
|
@@ -4071,7 +4122,7 @@ async function runLaunch(flags, rawArgs) {
|
|
|
4071
4122
|
const cleanup = () => {
|
|
4072
4123
|
if (meshMcpEntries.length > 0) {
|
|
4073
4124
|
try {
|
|
4074
|
-
const claudeConfigPath =
|
|
4125
|
+
const claudeConfigPath = join4(homedir3(), ".claude.json");
|
|
4075
4126
|
const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
|
|
4076
4127
|
const mcpServers = claudeConfig.mcpServers ?? {};
|
|
4077
4128
|
for (const { key } of meshMcpEntries) {
|
|
@@ -4776,7 +4827,7 @@ __export(exports_join, {
|
|
|
4776
4827
|
});
|
|
4777
4828
|
import sodium3 from "libsodium-wrappers";
|
|
4778
4829
|
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "node:fs";
|
|
4779
|
-
import { join as
|
|
4830
|
+
import { join as join5, dirname as dirname2 } from "node:path";
|
|
4780
4831
|
import { homedir as homedir4, hostname as hostname3 } from "node:os";
|
|
4781
4832
|
function deriveAppBaseUrl() {
|
|
4782
4833
|
const override = process.env.CLAUDEMESH_APP_URL;
|
|
@@ -4896,8 +4947,8 @@ async function runJoin(args) {
|
|
|
4896
4947
|
joinedAt: new Date().toISOString()
|
|
4897
4948
|
});
|
|
4898
4949
|
writeConfig(config);
|
|
4899
|
-
const configDir = env.CLAUDEMESH_CONFIG_DIR ??
|
|
4900
|
-
const inviteFile =
|
|
4950
|
+
const configDir = env.CLAUDEMESH_CONFIG_DIR ?? join5(homedir4(), ".claudemesh");
|
|
4951
|
+
const inviteFile = join5(configDir, `invite-${payload.mesh_slug}.txt`);
|
|
4901
4952
|
try {
|
|
4902
4953
|
mkdirSync3(dirname2(inviteFile), { recursive: true });
|
|
4903
4954
|
writeFileSync5(inviteFile, link, "utf-8");
|
|
@@ -6640,12 +6691,9 @@ var init_ban = __esm(() => {
|
|
|
6640
6691
|
|
|
6641
6692
|
// src/services/bridge/protocol.ts
|
|
6642
6693
|
import { homedir as homedir5 } from "node:os";
|
|
6643
|
-
import { join as
|
|
6694
|
+
import { join as join6 } from "node:path";
|
|
6644
6695
|
function socketPath(meshSlug) {
|
|
6645
|
-
return
|
|
6646
|
-
}
|
|
6647
|
-
function socketDir() {
|
|
6648
|
-
return join5(homedir5(), ".claudemesh", "sockets");
|
|
6696
|
+
return join6(homedir5(), ".claudemesh", "sockets", `${meshSlug}.sock`);
|
|
6649
6697
|
}
|
|
6650
6698
|
function frame(obj) {
|
|
6651
6699
|
return JSON.stringify(obj) + `
|
|
@@ -6841,36 +6889,6 @@ var init_peers = __esm(() => {
|
|
|
6841
6889
|
};
|
|
6842
6890
|
});
|
|
6843
6891
|
|
|
6844
|
-
// src/daemon/paths.ts
|
|
6845
|
-
import { join as join6 } from "node:path";
|
|
6846
|
-
var DAEMON_PATHS, DAEMON_TCP_HOST = "127.0.0.1", DAEMON_TCP_DEFAULT_PORT = 47823;
|
|
6847
|
-
var init_paths2 = __esm(() => {
|
|
6848
|
-
init_paths();
|
|
6849
|
-
DAEMON_PATHS = {
|
|
6850
|
-
get DAEMON_DIR() {
|
|
6851
|
-
return join6(PATHS.CONFIG_DIR, "daemon");
|
|
6852
|
-
},
|
|
6853
|
-
get PID_FILE() {
|
|
6854
|
-
return join6(this.DAEMON_DIR, "daemon.pid");
|
|
6855
|
-
},
|
|
6856
|
-
get SOCK_FILE() {
|
|
6857
|
-
return join6(this.DAEMON_DIR, "daemon.sock");
|
|
6858
|
-
},
|
|
6859
|
-
get TOKEN_FILE() {
|
|
6860
|
-
return join6(this.DAEMON_DIR, "local-token");
|
|
6861
|
-
},
|
|
6862
|
-
get OUTBOX_DB() {
|
|
6863
|
-
return join6(this.DAEMON_DIR, "outbox.db");
|
|
6864
|
-
},
|
|
6865
|
-
get INBOX_DB() {
|
|
6866
|
-
return join6(this.DAEMON_DIR, "inbox.db");
|
|
6867
|
-
},
|
|
6868
|
-
get LOG_FILE() {
|
|
6869
|
-
return join6(this.DAEMON_DIR, "daemon.log");
|
|
6870
|
-
}
|
|
6871
|
-
};
|
|
6872
|
-
});
|
|
6873
|
-
|
|
6874
6892
|
// src/daemon/local-token.ts
|
|
6875
6893
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6876
6894
|
import { dirname as dirname3 } from "node:path";
|
|
@@ -8888,7 +8906,7 @@ function makeHandler(opts) {
|
|
|
8888
8906
|
respond(res, 200, {
|
|
8889
8907
|
daemon_version: VERSION,
|
|
8890
8908
|
ipc_api: "v1",
|
|
8891
|
-
ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile"],
|
|
8909
|
+
ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile", "skills"],
|
|
8892
8910
|
schema_version: 1
|
|
8893
8911
|
});
|
|
8894
8912
|
return;
|
|
@@ -8918,6 +8936,42 @@ function makeHandler(opts) {
|
|
|
8918
8936
|
}
|
|
8919
8937
|
return;
|
|
8920
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
|
+
}
|
|
8921
8975
|
if (req.method === "POST" && url.pathname === "/v1/profile") {
|
|
8922
8976
|
if (!opts.broker) {
|
|
8923
8977
|
respond(res, 503, { error: "broker not initialised" });
|
|
@@ -9198,6 +9252,8 @@ class DaemonBrokerClient {
|
|
|
9198
9252
|
helloTimer = null;
|
|
9199
9253
|
pendingAcks = new Map;
|
|
9200
9254
|
peerListResolvers = new Map;
|
|
9255
|
+
skillListResolvers = new Map;
|
|
9256
|
+
skillDataResolvers = new Map;
|
|
9201
9257
|
sessionPubkey = null;
|
|
9202
9258
|
sessionSecretKey = null;
|
|
9203
9259
|
opens = [];
|
|
@@ -9318,6 +9374,26 @@ class DaemonBrokerClient {
|
|
|
9318
9374
|
}
|
|
9319
9375
|
return;
|
|
9320
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
|
+
}
|
|
9321
9397
|
if (msg.type === "push" || msg.type === "inbound") {
|
|
9322
9398
|
this.opts.onPush?.(msg);
|
|
9323
9399
|
return;
|
|
@@ -9400,6 +9476,44 @@ class DaemonBrokerClient {
|
|
|
9400
9476
|
}
|
|
9401
9477
|
});
|
|
9402
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
|
+
}
|
|
9403
9517
|
setProfile(profile) {
|
|
9404
9518
|
if (this._status !== "open" || !this.ws)
|
|
9405
9519
|
return;
|
|
@@ -9626,11 +9740,14 @@ async function handleBrokerPush(msg, ctx) {
|
|
|
9626
9740
|
const brokerMessageId = stringOrNull(msg.messageId);
|
|
9627
9741
|
const senderPubkey = stringOrNull(msg.senderPubkey) ?? "";
|
|
9628
9742
|
const senderName = stringOrNull(msg.senderName) ?? senderPubkey.slice(0, 8);
|
|
9743
|
+
const senderMemberPk = stringOrNull(msg.senderMemberPubkey);
|
|
9629
9744
|
const topic = stringOrNull(msg.topic);
|
|
9630
9745
|
const replyToId = stringOrNull(msg.replyToId);
|
|
9631
9746
|
const ciphertext = stringOrNull(msg.ciphertext) ?? "";
|
|
9632
9747
|
const nonce = stringOrNull(msg.nonce) ?? "";
|
|
9633
9748
|
const createdAt = stringOrNull(msg.createdAt);
|
|
9749
|
+
const priority = stringOrNull(msg.priority) ?? "next";
|
|
9750
|
+
const subtype = stringOrNull(msg.subtype);
|
|
9634
9751
|
const clientMessageId = stringOrNull(msg.client_message_id) ?? brokerMessageId ?? randomUUID5();
|
|
9635
9752
|
const body = await decryptOrFallback({
|
|
9636
9753
|
ciphertext,
|
|
@@ -9660,9 +9777,12 @@ async function handleBrokerPush(msg, ctx) {
|
|
|
9660
9777
|
client_message_id: clientMessageId,
|
|
9661
9778
|
broker_message_id: brokerMessageId,
|
|
9662
9779
|
sender_pubkey: senderPubkey,
|
|
9780
|
+
sender_member_pubkey: senderMemberPk,
|
|
9663
9781
|
sender_name: senderName,
|
|
9664
9782
|
topic,
|
|
9665
9783
|
reply_to_id: replyToId,
|
|
9784
|
+
priority,
|
|
9785
|
+
...subtype ? { subtype } : {},
|
|
9666
9786
|
body,
|
|
9667
9787
|
created_at: createdAt
|
|
9668
9788
|
});
|
|
@@ -10761,6 +10881,7 @@ function installStatusLine() {
|
|
|
10761
10881
|
function runInstall(args = []) {
|
|
10762
10882
|
const skipHooks = args.includes("--no-hooks");
|
|
10763
10883
|
const skipSkill = args.includes("--no-skill");
|
|
10884
|
+
const skipService = args.includes("--no-service");
|
|
10764
10885
|
const wantStatusLine = args.includes("--status-line");
|
|
10765
10886
|
render.section("claudemesh install");
|
|
10766
10887
|
const entry = resolveEntry();
|
|
@@ -10842,10 +10963,25 @@ function runInstall(args = []) {
|
|
|
10842
10963
|
}
|
|
10843
10964
|
}
|
|
10844
10965
|
let hasMeshes = false;
|
|
10966
|
+
let primaryMesh;
|
|
10845
10967
|
try {
|
|
10846
10968
|
const meshConfig = readConfig();
|
|
10847
10969
|
hasMeshes = meshConfig.meshes.length > 0;
|
|
10970
|
+
primaryMesh = meshConfig.meshes[0]?.slug;
|
|
10848
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
|
+
}
|
|
10849
10985
|
render.blank();
|
|
10850
10986
|
render.warn(`${bold("RESTART CLAUDE CODE")} ${yellow("for MCP tools to appear.")}`);
|
|
10851
10987
|
if (!hasMeshes) {
|
|
@@ -10863,6 +10999,40 @@ function runInstall(args = []) {
|
|
|
10863
10999
|
render.info(dim(` claudemesh install --status-line # live peer count in Claude Code`));
|
|
10864
11000
|
render.info(dim(` claudemesh completions zsh # shell completions`));
|
|
10865
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
|
+
}
|
|
10866
11036
|
function runUninstall() {
|
|
10867
11037
|
render.section("claudemesh uninstall");
|
|
10868
11038
|
if (removeMcpServer()) {
|
|
@@ -15958,435 +16128,194 @@ var init_member = __esm(() => {
|
|
|
15958
16128
|
init_exit_codes();
|
|
15959
16129
|
});
|
|
15960
16130
|
|
|
15961
|
-
// src/mcp/
|
|
15962
|
-
|
|
15963
|
-
|
|
15964
|
-
|
|
15965
|
-
|
|
15966
|
-
|
|
15967
|
-
|
|
15968
|
-
|
|
15969
|
-
|
|
15970
|
-
|
|
15971
|
-
|
|
15972
|
-
|
|
15973
|
-
|
|
15974
|
-
|
|
15975
|
-
|
|
15976
|
-
|
|
15977
|
-
|
|
15978
|
-
|
|
15979
|
-
error: `peer "${to}" not found. online: ${peers.map((p) => p.displayName).join(", ") || "(none)"}`
|
|
15980
|
-
};
|
|
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));
|
|
15981
16149
|
}
|
|
15982
|
-
return
|
|
16150
|
+
return false;
|
|
15983
16151
|
}
|
|
15984
|
-
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
|
|
15990
|
-
|
|
15991
|
-
|
|
15992
|
-
|
|
15993
|
-
|
|
15994
|
-
|
|
15995
|
-
ws_status: client.status,
|
|
15996
|
-
peers_online: peers.length,
|
|
15997
|
-
push_buffer: client.pushHistory.length
|
|
15998
|
-
}
|
|
15999
|
-
};
|
|
16000
|
-
}
|
|
16001
|
-
case "peers": {
|
|
16002
|
-
const peers = await client.listPeers();
|
|
16003
|
-
return { id: req.id, ok: true, result: peers };
|
|
16004
|
-
}
|
|
16005
|
-
case "send": {
|
|
16006
|
-
const to = String(args.to ?? "");
|
|
16007
|
-
const message = String(args.message ?? "");
|
|
16008
|
-
const priority = args.priority ?? "next";
|
|
16009
|
-
if (!to || !message) {
|
|
16010
|
-
return { id: req.id, ok: false, error: "send: `to` and `message` required" };
|
|
16011
|
-
}
|
|
16012
|
-
const resolved = await resolveTarget2(client, to);
|
|
16013
|
-
if (!resolved.ok)
|
|
16014
|
-
return { id: req.id, ok: false, error: resolved.error };
|
|
16015
|
-
const result = await client.send(resolved.spec, message, priority);
|
|
16016
|
-
if (!result.ok) {
|
|
16017
|
-
return { id: req.id, ok: false, error: result.error ?? "send failed" };
|
|
16018
|
-
}
|
|
16019
|
-
return {
|
|
16020
|
-
id: req.id,
|
|
16021
|
-
ok: true,
|
|
16022
|
-
result: { messageId: result.messageId, target: resolved.spec }
|
|
16023
|
-
};
|
|
16024
|
-
}
|
|
16025
|
-
case "summary": {
|
|
16026
|
-
const text = String(args.summary ?? "");
|
|
16027
|
-
if (!text)
|
|
16028
|
-
return { id: req.id, ok: false, error: "summary: `summary` required" };
|
|
16029
|
-
await client.setSummary(text);
|
|
16030
|
-
return { id: req.id, ok: true, result: { summary: text } };
|
|
16031
|
-
}
|
|
16032
|
-
case "status_set": {
|
|
16033
|
-
const state = String(args.status ?? "");
|
|
16034
|
-
if (!["idle", "working", "dnd"].includes(state)) {
|
|
16035
|
-
return { id: req.id, ok: false, error: "status_set: must be idle | working | dnd" };
|
|
16036
|
-
}
|
|
16037
|
-
await client.setStatus(state);
|
|
16038
|
-
return { id: req.id, ok: true, result: { status: state } };
|
|
16039
|
-
}
|
|
16040
|
-
case "visible": {
|
|
16041
|
-
const visible = Boolean(args.visible);
|
|
16042
|
-
await client.setVisible(visible);
|
|
16043
|
-
return { id: req.id, ok: true, result: { visible } };
|
|
16044
|
-
}
|
|
16045
|
-
default:
|
|
16046
|
-
return { id: req.id, ok: false, error: `unknown verb: ${req.verb}` };
|
|
16047
|
-
}
|
|
16048
|
-
} catch (err) {
|
|
16049
|
-
return {
|
|
16050
|
-
id: req.id,
|
|
16051
|
-
ok: false,
|
|
16052
|
-
error: err instanceof Error ? err.message : String(err)
|
|
16053
|
-
};
|
|
16054
|
-
}
|
|
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);
|
|
16055
16163
|
}
|
|
16056
|
-
function
|
|
16057
|
-
|
|
16058
|
-
|
|
16059
|
-
|
|
16060
|
-
|
|
16061
|
-
|
|
16062
|
-
|
|
16063
|
-
|
|
16064
|
-
try {
|
|
16065
|
-
req = JSON.parse(line);
|
|
16066
|
-
} catch {
|
|
16067
|
-
continue;
|
|
16068
|
-
}
|
|
16069
|
-
if (!req || typeof req !== "object" || !req.id || !req.verb)
|
|
16070
|
-
continue;
|
|
16071
|
-
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;
|
|
16072
16172
|
try {
|
|
16073
|
-
|
|
16074
|
-
} catch {
|
|
16173
|
+
body = JSON.parse(text);
|
|
16174
|
+
} catch {
|
|
16175
|
+
body = text;
|
|
16176
|
+
}
|
|
16177
|
+
resolve3({ status: res.statusCode ?? 0, body });
|
|
16075
16178
|
});
|
|
16076
|
-
}
|
|
16179
|
+
});
|
|
16180
|
+
req.on("error", reject);
|
|
16181
|
+
req.on("timeout", () => req.destroy(new Error("daemon_ipc_timeout")));
|
|
16182
|
+
req.end();
|
|
16077
16183
|
});
|
|
16078
|
-
socket.on("error", () => {});
|
|
16079
16184
|
}
|
|
16080
|
-
function
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
|
|
16084
|
-
|
|
16085
|
-
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
|
|
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
|
|
16096
16232
|
`);
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
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}
|
|
16101
16241
|
`);
|
|
16102
|
-
|
|
16103
|
-
|
|
16104
|
-
|
|
16105
|
-
|
|
16106
|
-
|
|
16242
|
+
if (active)
|
|
16243
|
+
setTimeout(connect, 2000);
|
|
16244
|
+
});
|
|
16245
|
+
req.end();
|
|
16246
|
+
};
|
|
16247
|
+
connect();
|
|
16107
16248
|
return {
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
if (stopped)
|
|
16111
|
-
return;
|
|
16112
|
-
stopped = true;
|
|
16113
|
-
try {
|
|
16114
|
-
server.close();
|
|
16115
|
-
} catch {}
|
|
16249
|
+
close: () => {
|
|
16250
|
+
active = false;
|
|
16116
16251
|
try {
|
|
16117
|
-
|
|
16252
|
+
req?.destroy();
|
|
16118
16253
|
} catch {}
|
|
16119
16254
|
}
|
|
16120
16255
|
};
|
|
16121
16256
|
}
|
|
16122
|
-
var init_server2 = __esm(() => {
|
|
16123
|
-
init_protocol();
|
|
16124
|
-
});
|
|
16125
|
-
|
|
16126
|
-
// src/mcp/server.ts
|
|
16127
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
16128
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16129
|
-
import {
|
|
16130
|
-
ListToolsRequestSchema,
|
|
16131
|
-
CallToolRequestSchema,
|
|
16132
|
-
ListPromptsRequestSchema,
|
|
16133
|
-
GetPromptRequestSchema,
|
|
16134
|
-
ListResourcesRequestSchema,
|
|
16135
|
-
ReadResourceRequestSchema
|
|
16136
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
16137
|
-
function relativeTime(isoStr) {
|
|
16138
|
-
const then = new Date(isoStr).getTime();
|
|
16139
|
-
if (isNaN(then))
|
|
16140
|
-
return "unknown";
|
|
16141
|
-
const diffMs = Date.now() - then;
|
|
16142
|
-
if (diffMs < 0)
|
|
16143
|
-
return "just now";
|
|
16144
|
-
const seconds = Math.floor(diffMs / 1000);
|
|
16145
|
-
if (seconds < 60)
|
|
16146
|
-
return `${seconds}s ago`;
|
|
16147
|
-
const minutes = Math.floor(seconds / 60);
|
|
16148
|
-
if (minutes < 60)
|
|
16149
|
-
return `${minutes}m ago`;
|
|
16150
|
-
const hours = Math.floor(minutes / 60);
|
|
16151
|
-
if (hours < 24)
|
|
16152
|
-
return `${hours}h ago`;
|
|
16153
|
-
const days = Math.floor(hours / 24);
|
|
16154
|
-
return `${days} day${days !== 1 ? "s" : ""} ago`;
|
|
16155
|
-
}
|
|
16156
|
-
async function resolvePeerName(client, pubkey) {
|
|
16157
|
-
const now = Date.now();
|
|
16158
|
-
if (now - peerNameCacheAge > CACHE_TTL_MS) {
|
|
16159
|
-
peerNameCache.clear();
|
|
16160
|
-
try {
|
|
16161
|
-
const peers = await client.listPeers();
|
|
16162
|
-
for (const p of peers)
|
|
16163
|
-
peerNameCache.set(p.pubkey, p.displayName);
|
|
16164
|
-
} catch {}
|
|
16165
|
-
peerNameCacheAge = now;
|
|
16166
|
-
}
|
|
16167
|
-
return peerNameCache.get(pubkey) ?? `peer-${pubkey.slice(0, 8)}`;
|
|
16168
|
-
}
|
|
16169
|
-
function decryptFailedWarning(senderPubkey) {
|
|
16170
|
-
const who = senderPubkey ? senderPubkey.slice(0, 12) + "…" : "unknown sender";
|
|
16171
|
-
return `⚠ message from ${who} failed to decrypt (tampered or wrong keypair)`;
|
|
16172
|
-
}
|
|
16173
16257
|
async function startMcpServer() {
|
|
16174
16258
|
const serviceIdx = process.argv.indexOf("--service");
|
|
16175
16259
|
if (serviceIdx !== -1 && process.argv[serviceIdx + 1]) {
|
|
16176
16260
|
return startServiceProxy(process.argv[serviceIdx + 1]);
|
|
16177
16261
|
}
|
|
16178
|
-
const
|
|
16179
|
-
|
|
16180
|
-
|
|
16181
|
-
|
|
16182
|
-
|
|
16183
|
-
const filtered = config.meshes.filter((m) => m.slug === onlyMesh);
|
|
16184
|
-
if (filtered.length === 0) {
|
|
16185
|
-
process.stderr.write(`[claudemesh] --mesh "${onlyMesh}" not found in config. ` + `Joined meshes: ${available.join(", ") || "(none)"}
|
|
16186
|
-
`);
|
|
16187
|
-
process.exit(1);
|
|
16188
|
-
}
|
|
16189
|
-
config.meshes = filtered;
|
|
16190
|
-
}
|
|
16191
|
-
const myName = config.displayName ?? "unnamed";
|
|
16192
|
-
const myRole = config.role ?? process.env.CLAUDEMESH_ROLE ?? null;
|
|
16193
|
-
const myGroups = (config.groups ?? []).map((g) => `@${g.name}${g.role ? ":" + g.role : ""}`).join(", ") || "none";
|
|
16194
|
-
const messageMode = config.messageMode ?? "push";
|
|
16195
|
-
const server = new Server({ name: "claudemesh", version: "0.3.0" }, {
|
|
16196
|
-
capabilities: {
|
|
16197
|
-
experimental: { "claude/channel": {} },
|
|
16198
|
-
tools: {},
|
|
16199
|
-
prompts: {},
|
|
16200
|
-
resources: {}
|
|
16201
|
-
},
|
|
16202
|
-
instructions: `## Identity
|
|
16203
|
-
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.
|
|
16204
|
-
|
|
16205
|
-
## Responding to messages
|
|
16206
|
-
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.
|
|
16207
|
-
|
|
16208
|
-
The channel attributes carry everything you need to reply — no extra lookups:
|
|
16209
|
-
- \`from_name\` — sender display name. Use as the \`to\` arg when replying to a DM.
|
|
16210
|
-
- \`from_pubkey\` / \`from_member_id\` — stable ids. Use \`from_member_id\` if the sender's display name might change.
|
|
16211
|
-
- \`mesh_slug\` — pass via \`--mesh\` if your default mesh differs.
|
|
16212
|
-
- \`priority\` — \`now\` / \`next\` / \`low\`.
|
|
16213
|
-
- \`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>\`.
|
|
16214
|
-
- \`topic\` — set when the message arrived through a topic (vs DM). Reply in the same topic.
|
|
16215
|
-
- \`reply_to_id\` — set when the incoming message is itself a reply. Render thread context if you re-narrate.
|
|
16216
|
-
|
|
16217
|
-
If the channel meta contains \`subtype: reminder\`, this is a scheduled reminder you set for yourself — act on it immediately (no reply needed).
|
|
16218
|
-
|
|
16219
|
-
## Tools
|
|
16220
|
-
| Tool | Description |
|
|
16221
|
-
|------|-------------|
|
|
16222
|
-
| send_message(to, message, priority?) | Send to peer name, @group, or * broadcast. \`to\` accepts display name, pubkey hex, @groupname, or *. |
|
|
16223
|
-
| list_peers(mesh_slug?) | List connected peers with status, summary, groups, and roles. |
|
|
16224
|
-
| check_messages() | Drain buffered inbound messages (auto-pushed in most cases, use as fallback). |
|
|
16225
|
-
| set_summary(summary) | Set 1-2 sentence description of your current work, visible to all peers. |
|
|
16226
|
-
| set_status(status) | Override status: idle, working, or dnd. |
|
|
16227
|
-
| set_visible(visible) | Toggle visibility. Hidden peers skip list_peers and broadcasts; direct messages still arrive. |
|
|
16228
|
-
| set_profile(avatar?, title?, bio?, capabilities?) | Set public profile: emoji avatar, short title, bio, capabilities list. |
|
|
16229
|
-
| join_group(name, role?) | Join a @group with optional role (lead, member, observer, or any string). |
|
|
16230
|
-
| leave_group(name) | Leave a @group. |
|
|
16231
|
-
| set_state(key, value) | Write shared state; pushes change to all peers. |
|
|
16232
|
-
| get_state(key) | Read a shared state value. |
|
|
16233
|
-
| list_state() | List all state keys with values, authors, and timestamps. |
|
|
16234
|
-
| remember(content, tags?) | Store persistent knowledge with optional tags. |
|
|
16235
|
-
| recall(query) | Full-text search over mesh memory. |
|
|
16236
|
-
| forget(id) | Soft-delete a memory entry. |
|
|
16237
|
-
| 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). |
|
|
16238
|
-
| claudemesh file get <id> [--out path] | Download a shared file by id. |
|
|
16239
|
-
| claudemesh file list [query] | Find files shared in the mesh. |
|
|
16240
|
-
| claudemesh file status <id> | Check who has accessed a file. |
|
|
16241
|
-
| claudemesh file delete <id> | Remove a shared file from the mesh. |
|
|
16242
|
-
| vector_store(collection, text, metadata?) | Store embedding in per-mesh Qdrant collection. |
|
|
16243
|
-
| vector_search(collection, query, limit?) | Semantic search over stored embeddings. |
|
|
16244
|
-
| vector_delete(collection, id) | Remove an embedding. |
|
|
16245
|
-
| list_collections() | List vector collections in this mesh. |
|
|
16246
|
-
| graph_query(cypher) | Read-only Cypher query on per-mesh Neo4j. |
|
|
16247
|
-
| graph_execute(cypher) | Write Cypher query (CREATE, MERGE, DELETE). |
|
|
16248
|
-
| mesh_query(sql) | Run a SELECT query on the per-mesh shared database. |
|
|
16249
|
-
| mesh_execute(sql) | Run DDL/DML on the per-mesh database (CREATE TABLE, INSERT, UPDATE, DELETE). |
|
|
16250
|
-
| mesh_schema() | List tables and columns in the per-mesh shared database. |
|
|
16251
|
-
| create_stream(name) | Create a real-time data stream in the mesh. |
|
|
16252
|
-
| publish(stream, data) | Push data to a stream. Subscribers receive it in real-time. |
|
|
16253
|
-
| subscribe(stream) | Subscribe to a stream. Data pushes arrive as channel notifications. |
|
|
16254
|
-
| list_streams() | List active streams in the mesh. |
|
|
16255
|
-
| share_context(summary, files_read?, key_findings?, tags?) | Share session understanding with peers. |
|
|
16256
|
-
| get_context(query) | Find context from peers who explored an area. |
|
|
16257
|
-
| list_contexts() | See what all peers currently know. |
|
|
16258
|
-
| create_task(title, assignee?, priority?, tags?) | Create a work item. |
|
|
16259
|
-
| claim_task(id) | Claim an unclaimed task. |
|
|
16260
|
-
| complete_task(id, result?) | Mark task done with optional result. |
|
|
16261
|
-
| list_tasks(status?, assignee?) | List tasks filtered by status/assignee. |
|
|
16262
|
-
| 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. |
|
|
16263
|
-
| list_scheduled() | List pending scheduled reminders and messages. |
|
|
16264
|
-
| cancel_scheduled(id) | Cancel a pending scheduled item. |
|
|
16265
|
-
| read_peer_file(peer, path) | Read a file from another peer's project (max 1MB). |
|
|
16266
|
-
| list_peer_files(peer, path?, pattern?) | List files in a peer's shared directory. |
|
|
16267
|
-
| mesh_mcp_register(server_name, description, tools) | Register an MCP server with the mesh. Other peers can call its tools. |
|
|
16268
|
-
| mesh_mcp_list() | List MCP servers available in the mesh with their tools. |
|
|
16269
|
-
| mesh_tool_call(server_name, tool_name, args?) | Call a tool on a mesh-registered MCP server (30s timeout). |
|
|
16270
|
-
| mesh_mcp_remove(server_name) | Unregister an MCP server you registered. |
|
|
16271
|
-
|
|
16272
|
-
If multiple meshes are joined, prefix \`to\` with \`<mesh-slug>:\` to disambiguate (e.g. \`dev-team:Alice\`).
|
|
16273
|
-
|
|
16274
|
-
Multi-target: send_message accepts an array of targets for the 'to' field.
|
|
16275
|
-
send_message(to: ["Alice", "@backend"], message: "sprint starts")
|
|
16276
|
-
Targets are deduplicated — each peer receives the message once.
|
|
16277
|
-
|
|
16278
|
-
Targeted views: when different audiences need different details about the same event,
|
|
16279
|
-
send tailored messages instead of one generic broadcast:
|
|
16280
|
-
send_message(to: "@frontend", message: "Auth v2: useAuth hook changed, see src/auth/")
|
|
16281
|
-
send_message(to: "@backend", message: "Auth v2: new /api/auth/v2 endpoints, v1 deprecated")
|
|
16282
|
-
send_message(to: "@pm", message: "Auth v2 done. 3 points, no blockers.")
|
|
16283
|
-
|
|
16284
|
-
## Groups
|
|
16285
|
-
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.
|
|
16286
|
-
|
|
16287
|
-
## State
|
|
16288
|
-
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.
|
|
16289
|
-
|
|
16290
|
-
## Memory
|
|
16291
|
-
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.
|
|
16292
|
-
|
|
16293
|
-
## File access — decision guide
|
|
16294
|
-
Three ways to access files. Pick the right one:
|
|
16295
|
-
|
|
16296
|
-
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.
|
|
16297
|
-
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.
|
|
16298
|
-
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.
|
|
16299
|
-
|
|
16300
|
-
**Rule of thumb:** local peer → filesystem. Remote peer, small file → read_peer_file. Large file or needs to persist → share_file.
|
|
16301
|
-
|
|
16302
|
-
## Vectors
|
|
16303
|
-
Store and search semantic embeddings. Use vector_store to index content, vector_search to find similar content.
|
|
16304
|
-
|
|
16305
|
-
## Graph
|
|
16306
|
-
Build and query entity relationship graphs. Use graph_execute for writes (CREATE, MERGE), graph_query for reads (MATCH).
|
|
16307
|
-
|
|
16308
|
-
## Mesh Database
|
|
16309
|
-
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.
|
|
16310
|
-
|
|
16311
|
-
## Streams
|
|
16312
|
-
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.
|
|
16313
|
-
|
|
16314
|
-
## Context
|
|
16315
|
-
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.
|
|
16316
|
-
|
|
16317
|
-
## Tasks
|
|
16318
|
-
Create and claim work items. create_task to propose work, claim_task to take ownership, complete_task when done. Prevents duplicate effort.
|
|
16319
|
-
|
|
16320
|
-
## Priority
|
|
16321
|
-
- "now": interrupt immediately, even if recipient is in DND (use for urgent: broken deploy, blocking issue)
|
|
16322
|
-
- "next" (default): deliver when recipient goes idle (normal coordination)
|
|
16323
|
-
- "low": pull-only via check_messages (FYI, non-blocking context)
|
|
16324
|
-
|
|
16325
|
-
## Coordination
|
|
16326
|
-
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.
|
|
16327
|
-
|
|
16328
|
-
## Message Mode
|
|
16329
|
-
Your message mode is "${messageMode}".
|
|
16330
|
-
- push: messages arrive in real-time as channel notifications. Respond immediately.
|
|
16331
|
-
- inbox: messages are held. You'll see "[inbox] New message from X" notifications. Call check_messages to read them.
|
|
16332
|
-
- off: no message notifications. Use check_messages manually to poll.`
|
|
16333
|
-
});
|
|
16334
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
16335
|
-
tools: TOOLS
|
|
16336
|
-
}));
|
|
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: [] }));
|
|
16337
16267
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
16338
|
-
|
|
16339
|
-
|
|
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 {
|
|
16340
16275
|
return { prompts: [] };
|
|
16341
|
-
|
|
16342
|
-
return {
|
|
16343
|
-
prompts: skills.map((s) => ({
|
|
16344
|
-
name: s.name,
|
|
16345
|
-
description: s.description,
|
|
16346
|
-
arguments: []
|
|
16347
|
-
}))
|
|
16348
|
-
};
|
|
16276
|
+
}
|
|
16349
16277
|
});
|
|
16350
16278
|
server.setRequestHandler(GetPromptRequestSchema, async (req) => {
|
|
16351
|
-
const
|
|
16352
|
-
const
|
|
16353
|
-
if (
|
|
16354
|
-
throw new Error("Not connected to any mesh");
|
|
16355
|
-
const skill = await client.getSkill(name);
|
|
16356
|
-
if (!skill)
|
|
16279
|
+
const name = req.params.name;
|
|
16280
|
+
const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
|
|
16281
|
+
if (status === 404)
|
|
16357
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;
|
|
16358
16286
|
let content = skill.instructions;
|
|
16359
|
-
const
|
|
16360
|
-
if (
|
|
16287
|
+
const m = skill.manifest;
|
|
16288
|
+
if (m && typeof m === "object") {
|
|
16361
16289
|
const fm = ["---"];
|
|
16362
|
-
if (
|
|
16363
|
-
fm.push(`description: "${
|
|
16364
|
-
if (
|
|
16365
|
-
fm.push(`when_to_use: "${
|
|
16366
|
-
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) {
|
|
16367
16295
|
fm.push(`allowed-tools:
|
|
16368
|
-
${
|
|
16296
|
+
${m.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
16369
16297
|
`)}`);
|
|
16370
|
-
|
|
16371
|
-
|
|
16372
|
-
|
|
16373
|
-
|
|
16374
|
-
|
|
16375
|
-
|
|
16376
|
-
|
|
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)
|
|
16377
16306
|
fm.push(`user-invocable: false`);
|
|
16378
|
-
if (
|
|
16379
|
-
fm.push(`argument-hint: "${
|
|
16307
|
+
if (m.argument_hint)
|
|
16308
|
+
fm.push(`argument-hint: "${m.argument_hint}"`);
|
|
16380
16309
|
fm.push(`---
|
|
16381
16310
|
`);
|
|
16382
16311
|
if (fm.length > 3)
|
|
16383
16312
|
content = fm.join(`
|
|
16384
16313
|
`) + content;
|
|
16385
|
-
if (
|
|
16386
|
-
const agentType =
|
|
16387
|
-
const modelHint =
|
|
16388
|
-
const toolsHint =
|
|
16389
|
-
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(", ")}.` : "";
|
|
16390
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}
|
|
16391
16320
|
|
|
16392
16321
|
` + content;
|
|
@@ -16394,273 +16323,127 @@ Only use these tools: ${manifest.allowed_tools.join(", ")}.` : "";
|
|
|
16394
16323
|
}
|
|
16395
16324
|
return {
|
|
16396
16325
|
description: skill.description,
|
|
16397
|
-
messages: [
|
|
16398
|
-
{
|
|
16399
|
-
role: "user",
|
|
16400
|
-
content: { type: "text", text: content }
|
|
16401
|
-
}
|
|
16402
|
-
]
|
|
16326
|
+
messages: [{ role: "user", content: { type: "text", text: content } }]
|
|
16403
16327
|
};
|
|
16404
16328
|
});
|
|
16405
16329
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
16406
|
-
|
|
16407
|
-
|
|
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 {
|
|
16408
16342
|
return { resources: [] };
|
|
16409
|
-
|
|
16410
|
-
return {
|
|
16411
|
-
resources: skills.map((s) => ({
|
|
16412
|
-
uri: `skill://claudemesh/${encodeURIComponent(s.name)}`,
|
|
16413
|
-
name: s.name,
|
|
16414
|
-
description: s.description,
|
|
16415
|
-
mimeType: "text/markdown"
|
|
16416
|
-
}))
|
|
16417
|
-
};
|
|
16343
|
+
}
|
|
16418
16344
|
});
|
|
16419
16345
|
server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
|
|
16420
|
-
const
|
|
16421
|
-
const
|
|
16422
|
-
if (!
|
|
16346
|
+
const uri = req.params.uri;
|
|
16347
|
+
const m = uri.match(/^skill:\/\/claudemesh\/(.+)$/);
|
|
16348
|
+
if (!m)
|
|
16423
16349
|
throw new Error(`Unknown resource URI: ${uri}`);
|
|
16424
|
-
const name = decodeURIComponent(
|
|
16425
|
-
const
|
|
16426
|
-
if (
|
|
16427
|
-
throw new Error("Not connected to any mesh");
|
|
16428
|
-
const skill = await client.getSkill(name);
|
|
16429
|
-
if (!skill)
|
|
16350
|
+
const name = decodeURIComponent(m[1]);
|
|
16351
|
+
const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
|
|
16352
|
+
if (status === 404)
|
|
16430
16353
|
throw new Error(`Skill "${name}" not found`);
|
|
16431
|
-
|
|
16432
|
-
|
|
16433
|
-
|
|
16434
|
-
|
|
16435
|
-
|
|
16436
|
-
|
|
16437
|
-
if (
|
|
16438
|
-
|
|
16439
|
-
|
|
16440
|
-
|
|
16441
|
-
|
|
16442
|
-
|
|
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(`
|
|
16443
16369
|
`)}`);
|
|
16444
|
-
|
|
16445
|
-
|
|
16446
|
-
|
|
16447
|
-
|
|
16448
|
-
|
|
16449
|
-
|
|
16450
|
-
|
|
16451
|
-
fmLines.push(`user-invocable: false`);
|
|
16452
|
-
if (manifest.argument_hint)
|
|
16453
|
-
fmLines.push(`argument-hint: "${manifest.argument_hint}"`);
|
|
16454
|
-
}
|
|
16455
|
-
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(`---
|
|
16456
16377
|
`);
|
|
16457
|
-
|
|
16458
|
-
`) + skill.instructions;
|
|
16459
|
-
return {
|
|
16460
|
-
contents: [
|
|
16461
|
-
{
|
|
16462
|
-
uri,
|
|
16463
|
-
mimeType: "text/markdown",
|
|
16464
|
-
text: fullContent
|
|
16465
|
-
}
|
|
16466
|
-
]
|
|
16467
|
-
};
|
|
16468
|
-
});
|
|
16469
|
-
const transport = new StdioServerTransport;
|
|
16470
|
-
await server.connect(transport);
|
|
16471
|
-
const bridges = [];
|
|
16472
|
-
startClients(config).then(() => {
|
|
16473
|
-
wirePushHandlers().catch(() => {});
|
|
16474
|
-
for (const client of allClients()) {
|
|
16475
|
-
const bridge = startBridgeServer(client);
|
|
16476
|
-
if (bridge)
|
|
16477
|
-
bridges.push(bridge);
|
|
16478
|
-
}
|
|
16479
|
-
}).catch(() => {
|
|
16480
|
-
wirePushHandlers().catch(() => {});
|
|
16378
|
+
return { contents: [{ uri, mimeType: "text/markdown", text: fm.join(`
|
|
16379
|
+
`) + skill.instructions }] };
|
|
16481
16380
|
});
|
|
16482
|
-
|
|
16483
|
-
|
|
16484
|
-
|
|
16485
|
-
|
|
16486
|
-
|
|
16487
|
-
|
|
16488
|
-
|
|
16489
|
-
|
|
16490
|
-
|
|
16491
|
-
|
|
16492
|
-
|
|
16493
|
-
|
|
16494
|
-
|
|
16495
|
-
|
|
16496
|
-
|
|
16497
|
-
|
|
16498
|
-
|
|
16499
|
-
|
|
16500
|
-
|
|
16501
|
-
|
|
16502
|
-
|
|
16503
|
-
|
|
16504
|
-
|
|
16505
|
-
|
|
16506
|
-
|
|
16507
|
-
|
|
16508
|
-
|
|
16509
|
-
} else if (eventName === "mcp_unregistered") {
|
|
16510
|
-
content2 = `[system] MCP server "${data.serverName}" removed (was hosted by ${data.hostedBy})`;
|
|
16511
|
-
} else if (eventName === "mcp_restored") {
|
|
16512
|
-
content2 = `[system] MCP server "${data.serverName}" is back online (hosted by ${data.hostedBy})`;
|
|
16513
|
-
} else if (eventName === "watch_triggered") {
|
|
16514
|
-
content2 = `[WATCH] ${data.label ?? data.url}: ${data.oldValue} → ${data.newValue}`;
|
|
16515
|
-
} else if (eventName === "mcp_deployed") {
|
|
16516
|
-
content2 = `[SERVICE] "${data.name}" deployed (${data.tool_count} tools) by ${data.deployed_by}`;
|
|
16517
|
-
} else if (eventName === "mcp_undeployed") {
|
|
16518
|
-
content2 = `[SERVICE] "${data.name}" undeployed by ${data.by}`;
|
|
16519
|
-
} else if (eventName === "mcp_scope_changed") {
|
|
16520
|
-
content2 = `[SERVICE] "${data.name}" scope changed to ${JSON.stringify(data.scope)} by ${data.by}`;
|
|
16521
|
-
} else {
|
|
16522
|
-
content2 = `[system] ${eventName}: ${JSON.stringify(data)}`;
|
|
16523
|
-
}
|
|
16524
|
-
try {
|
|
16525
|
-
await server.notification({
|
|
16526
|
-
method: "notifications/claude/channel",
|
|
16527
|
-
params: {
|
|
16528
|
-
content: content2,
|
|
16529
|
-
meta: {
|
|
16530
|
-
kind: "system",
|
|
16531
|
-
event: eventName,
|
|
16532
|
-
mesh_slug: client.meshSlug,
|
|
16533
|
-
mesh_id: client.meshId,
|
|
16534
|
-
...Object.keys(data).length > 0 ? { eventData: JSON.stringify(data) } : {}
|
|
16535
|
-
}
|
|
16536
|
-
}
|
|
16537
|
-
});
|
|
16538
|
-
process.stderr.write(`[claudemesh] system: ${content2}
|
|
16539
|
-
`);
|
|
16540
|
-
} catch (pushErr) {
|
|
16541
|
-
process.stderr.write(`[claudemesh] system push FAILED: ${pushErr}
|
|
16542
|
-
`);
|
|
16543
|
-
}
|
|
16544
|
-
return;
|
|
16545
|
-
}
|
|
16546
|
-
const fromPubkey = msg.senderPubkey || "";
|
|
16547
|
-
const fromName = fromPubkey ? await resolvePeerName(client, fromPubkey) : "unknown";
|
|
16548
|
-
if (fromPubkey) {
|
|
16549
|
-
try {
|
|
16550
|
-
const { isAllowed: isAllowed2 } = await Promise.resolve().then(() => (init_grants(), exports_grants));
|
|
16551
|
-
const kindCap = msg.kind === "broadcast" ? "broadcast" : "dm";
|
|
16552
|
-
if (!isAllowed2(client.meshSlug, fromPubkey, kindCap)) {
|
|
16553
|
-
process.stderr.write(`[claudemesh] dropped ${kindCap} from ${fromName} (not granted)
|
|
16554
|
-
`);
|
|
16555
|
-
return;
|
|
16556
|
-
}
|
|
16557
|
-
} catch {}
|
|
16558
|
-
}
|
|
16559
|
-
if (messageMode === "inbox") {
|
|
16560
|
-
try {
|
|
16561
|
-
await server.notification({
|
|
16562
|
-
method: "notifications/claude/channel",
|
|
16563
|
-
params: {
|
|
16564
|
-
content: `[inbox] New message from ${fromName}. Use check_messages to read.`,
|
|
16565
|
-
meta: { kind: "inbox_notification", from_name: fromName }
|
|
16566
|
-
}
|
|
16567
|
-
});
|
|
16568
|
-
} catch {}
|
|
16569
|
-
return;
|
|
16570
|
-
}
|
|
16571
|
-
const body = msg.plaintext ?? decryptFailedWarning(fromPubkey);
|
|
16572
|
-
const prioBadge = msg.priority === "now" ? "[URGENT] " : msg.priority === "low" ? "[low] " : "";
|
|
16573
|
-
const kindBadge = msg.kind === "broadcast" ? " (broadcast)" : "";
|
|
16574
|
-
const content = `${prioBadge}${fromName}${kindBadge}: ${body}`;
|
|
16575
|
-
const fromMemberPubkey = msg.senderMemberPubkey ?? fromPubkey;
|
|
16576
|
-
try {
|
|
16577
|
-
await server.notification({
|
|
16578
|
-
method: "notifications/claude/channel",
|
|
16579
|
-
params: {
|
|
16580
|
-
content,
|
|
16581
|
-
meta: {
|
|
16582
|
-
from_id: fromMemberPubkey,
|
|
16583
|
-
from_pubkey: fromMemberPubkey,
|
|
16584
|
-
from_session_pubkey: fromPubkey,
|
|
16585
|
-
from_name: fromName,
|
|
16586
|
-
...msg.senderMemberId ? { from_member_id: msg.senderMemberId } : {},
|
|
16587
|
-
mesh_slug: client.meshSlug,
|
|
16588
|
-
mesh_id: client.meshId,
|
|
16589
|
-
priority: msg.priority,
|
|
16590
|
-
sent_at: msg.createdAt,
|
|
16591
|
-
delivered_at: msg.receivedAt,
|
|
16592
|
-
kind: msg.kind,
|
|
16593
|
-
message_id: msg.messageId,
|
|
16594
|
-
...msg.topic ? { topic: msg.topic } : {},
|
|
16595
|
-
...msg.replyToId ? { reply_to_id: msg.replyToId } : {},
|
|
16596
|
-
...msg.subtype ? { subtype: msg.subtype } : {}
|
|
16597
|
-
}
|
|
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) } : {}
|
|
16598
16408
|
}
|
|
16599
|
-
}
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
process.stderr.write(`[claudemesh] push FAILED: ${pushErr}
|
|
16409
|
+
}
|
|
16410
|
+
});
|
|
16411
|
+
} catch (err) {
|
|
16412
|
+
process.stderr.write(`[claudemesh-mcp] channel emit failed: ${err}
|
|
16604
16413
|
`);
|
|
16605
|
-
|
|
16606
|
-
|
|
16607
|
-
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
}
|
|
16618
|
-
}
|
|
16619
|
-
});
|
|
16620
|
-
} catch {}
|
|
16621
|
-
});
|
|
16622
|
-
client.onStateChange(async (change) => {
|
|
16623
|
-
try {
|
|
16624
|
-
await server.notification({
|
|
16625
|
-
method: "notifications/claude/channel",
|
|
16626
|
-
params: {
|
|
16627
|
-
content: `[state] ${change.key} = ${JSON.stringify(change.value)} (set by ${change.updatedBy})`,
|
|
16628
|
-
meta: {
|
|
16629
|
-
kind: "state_change",
|
|
16630
|
-
key: change.key,
|
|
16631
|
-
updated_by: change.updatedBy
|
|
16632
|
-
}
|
|
16633
|
-
}
|
|
16634
|
-
});
|
|
16635
|
-
} catch {}
|
|
16636
|
-
});
|
|
16637
|
-
}
|
|
16638
|
-
setTimeout(async () => {
|
|
16639
|
-
const welcomeClient = allClients()[0];
|
|
16640
|
-
if (!welcomeClient || welcomeClient.status !== "open")
|
|
16641
|
-
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
|
+
}
|
|
16642
16426
|
try {
|
|
16643
|
-
const peers = await welcomeClient.listPeers();
|
|
16644
|
-
const peerNames = peers.filter((p) => p.displayName !== myName).map((p) => p.displayName).join(", ") || "none";
|
|
16645
16427
|
await server.notification({
|
|
16646
16428
|
method: "notifications/claude/channel",
|
|
16647
16429
|
params: {
|
|
16648
|
-
content
|
|
16649
|
-
meta: {
|
|
16430
|
+
content,
|
|
16431
|
+
meta: {
|
|
16432
|
+
kind: "system",
|
|
16433
|
+
event: eventName,
|
|
16434
|
+
mesh_slug: String(d.mesh ?? "")
|
|
16435
|
+
}
|
|
16650
16436
|
}
|
|
16651
16437
|
});
|
|
16652
16438
|
} catch {}
|
|
16653
|
-
}
|
|
16654
|
-
}
|
|
16439
|
+
}
|
|
16440
|
+
});
|
|
16441
|
+
const transport = new StdioServerTransport;
|
|
16442
|
+
await server.connect(transport);
|
|
16655
16443
|
const keepalive = setInterval(() => {}, 1000);
|
|
16656
16444
|
const shutdown = () => {
|
|
16657
16445
|
clearInterval(keepalive);
|
|
16658
|
-
|
|
16659
|
-
try {
|
|
16660
|
-
b.stop();
|
|
16661
|
-
} catch {}
|
|
16662
|
-
}
|
|
16663
|
-
stopAll();
|
|
16446
|
+
sub.close();
|
|
16664
16447
|
process.exit(0);
|
|
16665
16448
|
};
|
|
16666
16449
|
process.on("SIGTERM", shutdown);
|
|
@@ -16691,9 +16474,8 @@ async function startServiceProxy(serviceName) {
|
|
|
16691
16474
|
tools = fetched;
|
|
16692
16475
|
} catch {
|
|
16693
16476
|
const cached2 = client.serviceCatalog.find((s) => s.name === serviceName);
|
|
16694
|
-
if (cached2)
|
|
16477
|
+
if (cached2)
|
|
16695
16478
|
tools = cached2.tools;
|
|
16696
|
-
}
|
|
16697
16479
|
}
|
|
16698
16480
|
if (tools.length === 0) {
|
|
16699
16481
|
process.stderr.write(`[mesh:${serviceName}] no tools found — service may not be running
|
|
@@ -16718,12 +16500,7 @@ async function startServiceProxy(serviceName) {
|
|
|
16718
16500
|
}
|
|
16719
16501
|
if (client.status !== "open") {
|
|
16720
16502
|
return {
|
|
16721
|
-
content: [
|
|
16722
|
-
{
|
|
16723
|
-
type: "text",
|
|
16724
|
-
text: `Service temporarily unavailable — broker reconnecting. Retry in a few seconds.`
|
|
16725
|
-
}
|
|
16726
|
-
],
|
|
16503
|
+
content: [{ type: "text", text: "Service temporarily unavailable — broker reconnecting. Retry in a few seconds." }],
|
|
16727
16504
|
isError: true
|
|
16728
16505
|
};
|
|
16729
16506
|
}
|
|
@@ -16731,23 +16508,13 @@ async function startServiceProxy(serviceName) {
|
|
|
16731
16508
|
try {
|
|
16732
16509
|
const result = await client.mcpCall(serviceName, toolName, args);
|
|
16733
16510
|
if (result.error) {
|
|
16734
|
-
return {
|
|
16735
|
-
content: [{ type: "text", text: `Error: ${result.error}` }],
|
|
16736
|
-
isError: true
|
|
16737
|
-
};
|
|
16511
|
+
return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
|
|
16738
16512
|
}
|
|
16739
16513
|
const resultText = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
|
|
16740
|
-
return {
|
|
16741
|
-
content: [{ type: "text", text: resultText }]
|
|
16742
|
-
};
|
|
16514
|
+
return { content: [{ type: "text", text: resultText }] };
|
|
16743
16515
|
} catch (e) {
|
|
16744
16516
|
return {
|
|
16745
|
-
content: [
|
|
16746
|
-
{
|
|
16747
|
-
type: "text",
|
|
16748
|
-
text: `Call failed: ${e instanceof Error ? e.message : String(e)}`
|
|
16749
|
-
}
|
|
16750
|
-
],
|
|
16517
|
+
content: [{ type: "text", text: `Call failed: ${e instanceof Error ? e.message : String(e)}` }],
|
|
16751
16518
|
isError: true
|
|
16752
16519
|
};
|
|
16753
16520
|
}
|
|
@@ -16763,9 +16530,7 @@ async function startServiceProxy(serviceName) {
|
|
|
16763
16530
|
const newTools = push.eventData?.tools;
|
|
16764
16531
|
if (Array.isArray(newTools)) {
|
|
16765
16532
|
tools = newTools;
|
|
16766
|
-
server.notification({
|
|
16767
|
-
method: "notifications/tools/list_changed"
|
|
16768
|
-
}).catch(() => {});
|
|
16533
|
+
server.notification({ method: "notifications/tools/list_changed" }).catch(() => {});
|
|
16769
16534
|
}
|
|
16770
16535
|
}
|
|
16771
16536
|
});
|
|
@@ -16780,13 +16545,12 @@ async function startServiceProxy(serviceName) {
|
|
|
16780
16545
|
process.on("SIGTERM", shutdown);
|
|
16781
16546
|
process.on("SIGINT", shutdown);
|
|
16782
16547
|
}
|
|
16783
|
-
var
|
|
16784
|
-
var
|
|
16785
|
-
|
|
16548
|
+
var DAEMON_BOOT_RETRIES = 4, DAEMON_BOOT_RETRY_MS = 500;
|
|
16549
|
+
var init_server2 = __esm(() => {
|
|
16550
|
+
init_paths2();
|
|
16551
|
+
init_urls();
|
|
16786
16552
|
init_facade();
|
|
16787
16553
|
init_facade8();
|
|
16788
|
-
init_server2();
|
|
16789
|
-
peerNameCache = new Map;
|
|
16790
16554
|
});
|
|
16791
16555
|
|
|
16792
16556
|
// src/commands/mcp.ts
|
|
@@ -16801,7 +16565,7 @@ async function runMcp() {
|
|
|
16801
16565
|
process.exit(0);
|
|
16802
16566
|
}
|
|
16803
16567
|
var init_mcp = __esm(() => {
|
|
16804
|
-
|
|
16568
|
+
init_server2();
|
|
16805
16569
|
});
|
|
16806
16570
|
|
|
16807
16571
|
// src/commands/hook.ts
|
|
@@ -18612,4 +18376,4 @@ main().catch((err) => {
|
|
|
18612
18376
|
process.exit(EXIT.INTERNAL_ERROR);
|
|
18613
18377
|
});
|
|
18614
18378
|
|
|
18615
|
-
//# debugId=
|
|
18379
|
+
//# debugId=BAEC2C4FE7977E7264756E2164756E21
|