claudemesh-cli 1.23.0 → 1.25.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 +875 -893
- package/dist/entrypoints/cli.js.map +17 -19
- package/dist/entrypoints/mcp.js +424 -1919
- package/dist/entrypoints/mcp.js.map +6 -33
- package/package.json +1 -1
- package/skills/claudemesh/SKILL.md +7 -3
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.25.0", env;
|
|
92
107
|
var init_urls = __esm(() => {
|
|
93
108
|
URLS = {
|
|
94
109
|
BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
|
|
@@ -1011,6 +1026,12 @@ var init_file_crypto = __esm(() => {
|
|
|
1011
1026
|
});
|
|
1012
1027
|
|
|
1013
1028
|
// src/services/crypto/box.ts
|
|
1029
|
+
var exports_box = {};
|
|
1030
|
+
__export(exports_box, {
|
|
1031
|
+
isDirectTarget: () => isDirectTarget,
|
|
1032
|
+
encryptDirect: () => encryptDirect,
|
|
1033
|
+
decryptDirect: () => decryptDirect
|
|
1034
|
+
});
|
|
1014
1035
|
function isDirectTarget(targetSpec) {
|
|
1015
1036
|
return HEX_PUBKEY.test(targetSpec);
|
|
1016
1037
|
}
|
|
@@ -3308,44 +3329,10 @@ var init_ws_client = __esm(() => {
|
|
|
3308
3329
|
});
|
|
3309
3330
|
|
|
3310
3331
|
// 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;
|
|
3332
|
+
var clients;
|
|
3345
3333
|
var init_manager = __esm(() => {
|
|
3346
3334
|
init_ws_client();
|
|
3347
3335
|
clients = new Map;
|
|
3348
|
-
configGroups = [];
|
|
3349
3336
|
});
|
|
3350
3337
|
|
|
3351
3338
|
// src/services/broker/envelope.ts
|
|
@@ -3600,6 +3587,42 @@ var init_spinner = __esm(() => {
|
|
|
3600
3587
|
FRAME_HEIGHT = H;
|
|
3601
3588
|
});
|
|
3602
3589
|
|
|
3590
|
+
// src/daemon/paths.ts
|
|
3591
|
+
var exports_paths = {};
|
|
3592
|
+
__export(exports_paths, {
|
|
3593
|
+
DAEMON_TCP_HOST: () => DAEMON_TCP_HOST,
|
|
3594
|
+
DAEMON_TCP_DEFAULT_PORT: () => DAEMON_TCP_DEFAULT_PORT,
|
|
3595
|
+
DAEMON_PATHS: () => DAEMON_PATHS
|
|
3596
|
+
});
|
|
3597
|
+
import { join as join3 } from "node:path";
|
|
3598
|
+
var DAEMON_PATHS, DAEMON_TCP_HOST = "127.0.0.1", DAEMON_TCP_DEFAULT_PORT = 47823;
|
|
3599
|
+
var init_paths2 = __esm(() => {
|
|
3600
|
+
init_paths();
|
|
3601
|
+
DAEMON_PATHS = {
|
|
3602
|
+
get DAEMON_DIR() {
|
|
3603
|
+
return join3(PATHS.CONFIG_DIR, "daemon");
|
|
3604
|
+
},
|
|
3605
|
+
get PID_FILE() {
|
|
3606
|
+
return join3(this.DAEMON_DIR, "daemon.pid");
|
|
3607
|
+
},
|
|
3608
|
+
get SOCK_FILE() {
|
|
3609
|
+
return join3(this.DAEMON_DIR, "daemon.sock");
|
|
3610
|
+
},
|
|
3611
|
+
get TOKEN_FILE() {
|
|
3612
|
+
return join3(this.DAEMON_DIR, "local-token");
|
|
3613
|
+
},
|
|
3614
|
+
get OUTBOX_DB() {
|
|
3615
|
+
return join3(this.DAEMON_DIR, "outbox.db");
|
|
3616
|
+
},
|
|
3617
|
+
get INBOX_DB() {
|
|
3618
|
+
return join3(this.DAEMON_DIR, "inbox.db");
|
|
3619
|
+
},
|
|
3620
|
+
get LOG_FILE() {
|
|
3621
|
+
return join3(this.DAEMON_DIR, "daemon.log");
|
|
3622
|
+
}
|
|
3623
|
+
};
|
|
3624
|
+
});
|
|
3625
|
+
|
|
3603
3626
|
// src/commands/launch.ts
|
|
3604
3627
|
var exports_launch = {};
|
|
3605
3628
|
__export(exports_launch, {
|
|
@@ -3609,8 +3632,41 @@ import { spawnSync as spawnSync2 } from "node:child_process";
|
|
|
3609
3632
|
import { randomUUID } from "node:crypto";
|
|
3610
3633
|
import { mkdtempSync, writeFileSync as writeFileSync4, rmSync, readdirSync, statSync, existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
|
|
3611
3634
|
import { tmpdir, hostname as hostname2, homedir as homedir3 } from "node:os";
|
|
3612
|
-
import { join as
|
|
3635
|
+
import { join as join4 } from "node:path";
|
|
3613
3636
|
import { createInterface as createInterface4 } from "node:readline";
|
|
3637
|
+
async function ensureDaemonRunning(meshSlug, quiet) {
|
|
3638
|
+
const { DAEMON_PATHS: DAEMON_PATHS2 } = await Promise.resolve().then(() => (init_paths2(), exports_paths));
|
|
3639
|
+
if (existsSync5(DAEMON_PATHS2.SOCK_FILE))
|
|
3640
|
+
return;
|
|
3641
|
+
if (!quiet)
|
|
3642
|
+
render.info("starting claudemesh daemon…");
|
|
3643
|
+
const { spawn } = await import("node:child_process");
|
|
3644
|
+
const argv0 = process.argv[1] ?? "claudemesh";
|
|
3645
|
+
let binary = argv0;
|
|
3646
|
+
if (/\.ts$/.test(binary) || /node_modules|src\/entrypoints/.test(binary)) {
|
|
3647
|
+
try {
|
|
3648
|
+
const { execSync } = await import("node:child_process");
|
|
3649
|
+
binary = execSync("which claudemesh", { encoding: "utf8" }).trim();
|
|
3650
|
+
} catch {
|
|
3651
|
+
binary = "claudemesh";
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
const child = spawn(binary, ["daemon", "up", "--mesh", meshSlug], {
|
|
3655
|
+
detached: true,
|
|
3656
|
+
stdio: "ignore"
|
|
3657
|
+
});
|
|
3658
|
+
child.unref();
|
|
3659
|
+
const start = Date.now();
|
|
3660
|
+
while (Date.now() - start < 1e4) {
|
|
3661
|
+
if (existsSync5(DAEMON_PATHS2.SOCK_FILE)) {
|
|
3662
|
+
if (!quiet)
|
|
3663
|
+
render.ok("daemon ready");
|
|
3664
|
+
return;
|
|
3665
|
+
}
|
|
3666
|
+
await new Promise((r) => setTimeout(r, 200));
|
|
3667
|
+
}
|
|
3668
|
+
render.warn("daemon failed to start within 10s", "Run `claudemesh daemon up --mesh " + meshSlug + "` manually, then re-launch.");
|
|
3669
|
+
}
|
|
3614
3670
|
function parseGroupsString(raw) {
|
|
3615
3671
|
return raw.split(",").map((s) => s.trim()).filter(Boolean).map((token) => {
|
|
3616
3672
|
const idx = token.indexOf(":");
|
|
@@ -3930,14 +3986,15 @@ async function runLaunch(flags, rawArgs) {
|
|
|
3930
3986
|
for (const entry of readdirSync(tmpBase)) {
|
|
3931
3987
|
if (!entry.startsWith("claudemesh-"))
|
|
3932
3988
|
continue;
|
|
3933
|
-
const full =
|
|
3989
|
+
const full = join4(tmpBase, entry);
|
|
3934
3990
|
const age = Date.now() - statSync(full).mtimeMs;
|
|
3935
3991
|
if (age > 3600000)
|
|
3936
3992
|
rmSync(full, { recursive: true, force: true });
|
|
3937
3993
|
}
|
|
3938
3994
|
} catch {}
|
|
3995
|
+
await ensureDaemonRunning(mesh.slug, args.quiet);
|
|
3939
3996
|
try {
|
|
3940
|
-
const claudeConfigPath =
|
|
3997
|
+
const claudeConfigPath = join4(homedir3(), ".claude.json");
|
|
3941
3998
|
if (existsSync5(claudeConfigPath)) {
|
|
3942
3999
|
const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
|
|
3943
4000
|
const mcpServers = claudeConfig.mcpServers ?? {};
|
|
@@ -3974,7 +4031,7 @@ async function runLaunch(flags, rawArgs) {
|
|
|
3974
4031
|
console.log(" (Could not fetch service catalog — mesh services won't be natively available)");
|
|
3975
4032
|
}
|
|
3976
4033
|
}
|
|
3977
|
-
const tmpDir = mkdtempSync(
|
|
4034
|
+
const tmpDir = mkdtempSync(join4(tmpdir(), "claudemesh-"));
|
|
3978
4035
|
const sessionConfig = {
|
|
3979
4036
|
version: 1,
|
|
3980
4037
|
meshes: [mesh],
|
|
@@ -3983,14 +4040,14 @@ async function runLaunch(flags, rawArgs) {
|
|
|
3983
4040
|
...parsedGroups.length > 0 ? { groups: parsedGroups } : {},
|
|
3984
4041
|
messageMode
|
|
3985
4042
|
};
|
|
3986
|
-
writeFileSync4(
|
|
4043
|
+
writeFileSync4(join4(tmpDir, "config.json"), JSON.stringify(sessionConfig, null, 2) + `
|
|
3987
4044
|
`, "utf-8");
|
|
3988
4045
|
if (!args.quiet) {
|
|
3989
4046
|
printBanner(displayName, mesh.slug, role, parsedGroups, messageMode);
|
|
3990
4047
|
}
|
|
3991
4048
|
const meshMcpEntries = [];
|
|
3992
4049
|
if (serviceCatalog.length > 0) {
|
|
3993
|
-
const claudeConfigPath =
|
|
4050
|
+
const claudeConfigPath = join4(homedir3(), ".claude.json");
|
|
3994
4051
|
let claudeConfig = {};
|
|
3995
4052
|
try {
|
|
3996
4053
|
claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
|
|
@@ -4057,9 +4114,9 @@ async function runLaunch(flags, rawArgs) {
|
|
|
4057
4114
|
let claudeBin = "claude";
|
|
4058
4115
|
if (!isWindows2) {
|
|
4059
4116
|
const candidates = [
|
|
4060
|
-
|
|
4117
|
+
join4(homedir3(), ".local", "bin", "claude"),
|
|
4061
4118
|
"/usr/local/bin/claude",
|
|
4062
|
-
|
|
4119
|
+
join4(homedir3(), ".claude", "bin", "claude")
|
|
4063
4120
|
];
|
|
4064
4121
|
for (const c of candidates) {
|
|
4065
4122
|
if (existsSync5(c)) {
|
|
@@ -4071,7 +4128,7 @@ async function runLaunch(flags, rawArgs) {
|
|
|
4071
4128
|
const cleanup = () => {
|
|
4072
4129
|
if (meshMcpEntries.length > 0) {
|
|
4073
4130
|
try {
|
|
4074
|
-
const claudeConfigPath =
|
|
4131
|
+
const claudeConfigPath = join4(homedir3(), ".claude.json");
|
|
4075
4132
|
const claudeConfig = JSON.parse(readFileSync4(claudeConfigPath, "utf-8"));
|
|
4076
4133
|
const mcpServers = claudeConfig.mcpServers ?? {};
|
|
4077
4134
|
for (const { key } of meshMcpEntries) {
|
|
@@ -4776,7 +4833,7 @@ __export(exports_join, {
|
|
|
4776
4833
|
});
|
|
4777
4834
|
import sodium3 from "libsodium-wrappers";
|
|
4778
4835
|
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "node:fs";
|
|
4779
|
-
import { join as
|
|
4836
|
+
import { join as join5, dirname as dirname2 } from "node:path";
|
|
4780
4837
|
import { homedir as homedir4, hostname as hostname3 } from "node:os";
|
|
4781
4838
|
function deriveAppBaseUrl() {
|
|
4782
4839
|
const override = process.env.CLAUDEMESH_APP_URL;
|
|
@@ -4896,8 +4953,8 @@ async function runJoin(args) {
|
|
|
4896
4953
|
joinedAt: new Date().toISOString()
|
|
4897
4954
|
});
|
|
4898
4955
|
writeConfig(config);
|
|
4899
|
-
const configDir = env.CLAUDEMESH_CONFIG_DIR ??
|
|
4900
|
-
const inviteFile =
|
|
4956
|
+
const configDir = env.CLAUDEMESH_CONFIG_DIR ?? join5(homedir4(), ".claudemesh");
|
|
4957
|
+
const inviteFile = join5(configDir, `invite-${payload.mesh_slug}.txt`);
|
|
4901
4958
|
try {
|
|
4902
4959
|
mkdirSync3(dirname2(inviteFile), { recursive: true });
|
|
4903
4960
|
writeFileSync5(inviteFile, link, "utf-8");
|
|
@@ -6640,12 +6697,9 @@ var init_ban = __esm(() => {
|
|
|
6640
6697
|
|
|
6641
6698
|
// src/services/bridge/protocol.ts
|
|
6642
6699
|
import { homedir as homedir5 } from "node:os";
|
|
6643
|
-
import { join as
|
|
6700
|
+
import { join as join6 } from "node:path";
|
|
6644
6701
|
function socketPath(meshSlug) {
|
|
6645
|
-
return
|
|
6646
|
-
}
|
|
6647
|
-
function socketDir() {
|
|
6648
|
-
return join5(homedir5(), ".claudemesh", "sockets");
|
|
6702
|
+
return join6(homedir5(), ".claudemesh", "sockets", `${meshSlug}.sock`);
|
|
6649
6703
|
}
|
|
6650
6704
|
function frame(obj) {
|
|
6651
6705
|
return JSON.stringify(obj) + `
|
|
@@ -6742,135 +6796,6 @@ var init_client3 = __esm(() => {
|
|
|
6742
6796
|
init_protocol();
|
|
6743
6797
|
});
|
|
6744
6798
|
|
|
6745
|
-
// src/commands/peers.ts
|
|
6746
|
-
var exports_peers = {};
|
|
6747
|
-
__export(exports_peers, {
|
|
6748
|
-
runPeers: () => runPeers
|
|
6749
|
-
});
|
|
6750
|
-
function projectFields(record, fields) {
|
|
6751
|
-
const out = {};
|
|
6752
|
-
for (const f of fields) {
|
|
6753
|
-
const sourceKey = FIELD_ALIAS[f] ?? f;
|
|
6754
|
-
out[f] = record[sourceKey];
|
|
6755
|
-
}
|
|
6756
|
-
return out;
|
|
6757
|
-
}
|
|
6758
|
-
async function listPeersForMesh(slug) {
|
|
6759
|
-
const config = readConfig();
|
|
6760
|
-
const joined = config.meshes.find((m) => m.slug === slug);
|
|
6761
|
-
const selfMemberPubkey = joined?.pubkey ?? null;
|
|
6762
|
-
const bridged = await tryBridge(slug, "peers");
|
|
6763
|
-
if (bridged && bridged.ok) {
|
|
6764
|
-
const peers = bridged.result;
|
|
6765
|
-
return peers.map((p) => annotateSelf(p, selfMemberPubkey, null));
|
|
6766
|
-
}
|
|
6767
|
-
let result = [];
|
|
6768
|
-
await withMesh({ meshSlug: slug }, async (client) => {
|
|
6769
|
-
const all = await client.listPeers();
|
|
6770
|
-
const selfSessionPubkey = client.getSessionPubkey();
|
|
6771
|
-
result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
|
|
6772
|
-
});
|
|
6773
|
-
return result;
|
|
6774
|
-
}
|
|
6775
|
-
function annotateSelf(peer, selfMemberPubkey, selfSessionPubkey) {
|
|
6776
|
-
const isSelf = !!(selfMemberPubkey && peer.memberPubkey && peer.memberPubkey === selfMemberPubkey);
|
|
6777
|
-
const isThisSession = !!(isSelf && selfSessionPubkey && peer.pubkey === selfSessionPubkey);
|
|
6778
|
-
return { ...peer, isSelf, isThisSession };
|
|
6779
|
-
}
|
|
6780
|
-
async function runPeers(flags) {
|
|
6781
|
-
const config = readConfig();
|
|
6782
|
-
const slugs = flags.mesh ? [flags.mesh] : config.meshes.map((m) => m.slug);
|
|
6783
|
-
if (slugs.length === 0) {
|
|
6784
|
-
render.err("No meshes joined.");
|
|
6785
|
-
render.hint("claudemesh <invite-url> # join + launch");
|
|
6786
|
-
process.exit(1);
|
|
6787
|
-
}
|
|
6788
|
-
const fieldList = typeof flags.json === "string" && flags.json.length > 0 ? flags.json.split(",").map((s) => s.trim()).filter(Boolean) : null;
|
|
6789
|
-
const wantsJson = flags.json !== undefined && flags.json !== false;
|
|
6790
|
-
const allJson = [];
|
|
6791
|
-
for (const slug of slugs) {
|
|
6792
|
-
try {
|
|
6793
|
-
const peers = await listPeersForMesh(slug);
|
|
6794
|
-
if (wantsJson) {
|
|
6795
|
-
const projected = fieldList ? peers.map((p) => projectFields(p, fieldList)) : peers;
|
|
6796
|
-
allJson.push({ mesh: slug, peers: projected });
|
|
6797
|
-
continue;
|
|
6798
|
-
}
|
|
6799
|
-
render.section(`peers on ${slug} (${peers.length})`);
|
|
6800
|
-
if (peers.length === 0) {
|
|
6801
|
-
render.info(dim(" (no peers connected)"));
|
|
6802
|
-
continue;
|
|
6803
|
-
}
|
|
6804
|
-
for (const p of peers) {
|
|
6805
|
-
const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
|
|
6806
|
-
const statusDot = p.status === "working" ? yellow("●") : green("●");
|
|
6807
|
-
const name = bold(p.displayName);
|
|
6808
|
-
const meta = [];
|
|
6809
|
-
if (p.peerType)
|
|
6810
|
-
meta.push(p.peerType);
|
|
6811
|
-
if (p.channel)
|
|
6812
|
-
meta.push(p.channel);
|
|
6813
|
-
if (p.model)
|
|
6814
|
-
meta.push(p.model);
|
|
6815
|
-
const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
|
|
6816
|
-
const summary = p.summary ? dim(` — ${p.summary}`) : "";
|
|
6817
|
-
const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
|
|
6818
|
-
const selfTag = p.isThisSession ? dim(" ") + yellow("(this session)") : p.isSelf ? dim(" ") + yellow("(your other session)") : "";
|
|
6819
|
-
render.info(`${statusDot} ${name}${selfTag}${groups}${metaStr}${pubkeyTag}${summary}`);
|
|
6820
|
-
if (p.cwd)
|
|
6821
|
-
render.info(dim(` cwd: ${p.cwd}`));
|
|
6822
|
-
}
|
|
6823
|
-
} catch (e) {
|
|
6824
|
-
render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
|
|
6825
|
-
}
|
|
6826
|
-
}
|
|
6827
|
-
if (wantsJson) {
|
|
6828
|
-
process.stdout.write(JSON.stringify(slugs.length === 1 ? allJson[0]?.peers : allJson, null, 2) + `
|
|
6829
|
-
`);
|
|
6830
|
-
}
|
|
6831
|
-
}
|
|
6832
|
-
var FIELD_ALIAS;
|
|
6833
|
-
var init_peers = __esm(() => {
|
|
6834
|
-
init_connect();
|
|
6835
|
-
init_facade();
|
|
6836
|
-
init_client3();
|
|
6837
|
-
init_render();
|
|
6838
|
-
init_styles();
|
|
6839
|
-
FIELD_ALIAS = {
|
|
6840
|
-
name: "displayName"
|
|
6841
|
-
};
|
|
6842
|
-
});
|
|
6843
|
-
|
|
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
6799
|
// src/daemon/local-token.ts
|
|
6875
6800
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6876
6801
|
import { dirname as dirname3 } from "node:path";
|
|
@@ -6952,7 +6877,61 @@ var init_client4 = __esm(() => {
|
|
|
6952
6877
|
});
|
|
6953
6878
|
|
|
6954
6879
|
// src/services/bridge/daemon-route.ts
|
|
6880
|
+
var exports_daemon_route = {};
|
|
6881
|
+
__export(exports_daemon_route, {
|
|
6882
|
+
trySendViaDaemon: () => trySendViaDaemon,
|
|
6883
|
+
tryListSkillsViaDaemon: () => tryListSkillsViaDaemon,
|
|
6884
|
+
tryListPeersViaDaemon: () => tryListPeersViaDaemon,
|
|
6885
|
+
tryGetSkillViaDaemon: () => tryGetSkillViaDaemon
|
|
6886
|
+
});
|
|
6955
6887
|
import { existsSync as existsSync7 } from "node:fs";
|
|
6888
|
+
async function tryListPeersViaDaemon() {
|
|
6889
|
+
if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
|
|
6890
|
+
return null;
|
|
6891
|
+
try {
|
|
6892
|
+
const res = await ipc({ path: "/v1/peers", timeoutMs: 3000 });
|
|
6893
|
+
if (res.status !== 200)
|
|
6894
|
+
return null;
|
|
6895
|
+
return Array.isArray(res.body.peers) ? res.body.peers : [];
|
|
6896
|
+
} catch (err) {
|
|
6897
|
+
const msg = String(err);
|
|
6898
|
+
if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
|
|
6899
|
+
return null;
|
|
6900
|
+
return null;
|
|
6901
|
+
}
|
|
6902
|
+
}
|
|
6903
|
+
async function tryListSkillsViaDaemon() {
|
|
6904
|
+
if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
|
|
6905
|
+
return null;
|
|
6906
|
+
try {
|
|
6907
|
+
const res = await ipc({ path: "/v1/skills", timeoutMs: 3000 });
|
|
6908
|
+
if (res.status !== 200)
|
|
6909
|
+
return null;
|
|
6910
|
+
return Array.isArray(res.body.skills) ? res.body.skills : [];
|
|
6911
|
+
} catch (err) {
|
|
6912
|
+
const msg = String(err);
|
|
6913
|
+
if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
|
|
6914
|
+
return null;
|
|
6915
|
+
return null;
|
|
6916
|
+
}
|
|
6917
|
+
}
|
|
6918
|
+
async function tryGetSkillViaDaemon(name) {
|
|
6919
|
+
if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
|
|
6920
|
+
return null;
|
|
6921
|
+
try {
|
|
6922
|
+
const res = await ipc({
|
|
6923
|
+
path: `/v1/skills/${encodeURIComponent(name)}`,
|
|
6924
|
+
timeoutMs: 3000
|
|
6925
|
+
});
|
|
6926
|
+
if (res.status === 404)
|
|
6927
|
+
return null;
|
|
6928
|
+
if (res.status !== 200)
|
|
6929
|
+
return null;
|
|
6930
|
+
return res.body.skill ?? null;
|
|
6931
|
+
} catch {
|
|
6932
|
+
return null;
|
|
6933
|
+
}
|
|
6934
|
+
}
|
|
6956
6935
|
async function trySendViaDaemon(args) {
|
|
6957
6936
|
if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
|
|
6958
6937
|
return null;
|
|
@@ -6989,34 +6968,140 @@ var init_daemon_route = __esm(() => {
|
|
|
6989
6968
|
init_paths2();
|
|
6990
6969
|
});
|
|
6991
6970
|
|
|
6992
|
-
// src/commands/
|
|
6993
|
-
var
|
|
6994
|
-
__export(
|
|
6995
|
-
|
|
6971
|
+
// src/commands/peers.ts
|
|
6972
|
+
var exports_peers = {};
|
|
6973
|
+
__export(exports_peers, {
|
|
6974
|
+
runPeers: () => runPeers
|
|
6996
6975
|
});
|
|
6997
|
-
|
|
6998
|
-
|
|
6999
|
-
|
|
7000
|
-
|
|
6976
|
+
function projectFields(record, fields) {
|
|
6977
|
+
const out = {};
|
|
6978
|
+
for (const f of fields) {
|
|
6979
|
+
const sourceKey = FIELD_ALIAS[f] ?? f;
|
|
6980
|
+
out[f] = record[sourceKey];
|
|
7001
6981
|
}
|
|
7002
|
-
|
|
6982
|
+
return out;
|
|
6983
|
+
}
|
|
6984
|
+
async function listPeersForMesh(slug) {
|
|
7003
6985
|
const config = readConfig();
|
|
7004
|
-
const
|
|
7005
|
-
|
|
7006
|
-
|
|
7007
|
-
|
|
7008
|
-
|
|
7009
|
-
|
|
7010
|
-
|
|
6986
|
+
const joined = config.meshes.find((m) => m.slug === slug);
|
|
6987
|
+
const selfMemberPubkey = joined?.pubkey ?? null;
|
|
6988
|
+
try {
|
|
6989
|
+
const { tryListPeersViaDaemon: tryListPeersViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
|
|
6990
|
+
const dr = await tryListPeersViaDaemon2();
|
|
6991
|
+
if (dr !== null) {
|
|
6992
|
+
return dr.map((p) => annotateSelf(p, selfMemberPubkey, null));
|
|
7011
6993
|
}
|
|
6994
|
+
} catch {}
|
|
6995
|
+
const bridged = await tryBridge(slug, "peers");
|
|
6996
|
+
if (bridged && bridged.ok) {
|
|
6997
|
+
const peers = bridged.result;
|
|
6998
|
+
return peers.map((p) => annotateSelf(p, selfMemberPubkey, null));
|
|
7012
6999
|
}
|
|
7013
|
-
|
|
7014
|
-
|
|
7015
|
-
|
|
7016
|
-
|
|
7017
|
-
|
|
7018
|
-
|
|
7019
|
-
|
|
7000
|
+
let result = [];
|
|
7001
|
+
await withMesh({ meshSlug: slug }, async (client) => {
|
|
7002
|
+
const all = await client.listPeers();
|
|
7003
|
+
const selfSessionPubkey = client.getSessionPubkey();
|
|
7004
|
+
result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
|
|
7005
|
+
});
|
|
7006
|
+
return result;
|
|
7007
|
+
}
|
|
7008
|
+
function annotateSelf(peer, selfMemberPubkey, selfSessionPubkey) {
|
|
7009
|
+
const isSelf = !!(selfMemberPubkey && peer.memberPubkey && peer.memberPubkey === selfMemberPubkey);
|
|
7010
|
+
const isThisSession = !!(isSelf && selfSessionPubkey && peer.pubkey === selfSessionPubkey);
|
|
7011
|
+
return { ...peer, isSelf, isThisSession };
|
|
7012
|
+
}
|
|
7013
|
+
async function runPeers(flags) {
|
|
7014
|
+
const config = readConfig();
|
|
7015
|
+
const slugs = flags.mesh ? [flags.mesh] : config.meshes.map((m) => m.slug);
|
|
7016
|
+
if (slugs.length === 0) {
|
|
7017
|
+
render.err("No meshes joined.");
|
|
7018
|
+
render.hint("claudemesh <invite-url> # join + launch");
|
|
7019
|
+
process.exit(1);
|
|
7020
|
+
}
|
|
7021
|
+
const fieldList = typeof flags.json === "string" && flags.json.length > 0 ? flags.json.split(",").map((s) => s.trim()).filter(Boolean) : null;
|
|
7022
|
+
const wantsJson = flags.json !== undefined && flags.json !== false;
|
|
7023
|
+
const allJson = [];
|
|
7024
|
+
for (const slug of slugs) {
|
|
7025
|
+
try {
|
|
7026
|
+
const peers = await listPeersForMesh(slug);
|
|
7027
|
+
if (wantsJson) {
|
|
7028
|
+
const projected = fieldList ? peers.map((p) => projectFields(p, fieldList)) : peers;
|
|
7029
|
+
allJson.push({ mesh: slug, peers: projected });
|
|
7030
|
+
continue;
|
|
7031
|
+
}
|
|
7032
|
+
render.section(`peers on ${slug} (${peers.length})`);
|
|
7033
|
+
if (peers.length === 0) {
|
|
7034
|
+
render.info(dim(" (no peers connected)"));
|
|
7035
|
+
continue;
|
|
7036
|
+
}
|
|
7037
|
+
for (const p of peers) {
|
|
7038
|
+
const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
|
|
7039
|
+
const statusDot = p.status === "working" ? yellow("●") : green("●");
|
|
7040
|
+
const name = bold(p.displayName);
|
|
7041
|
+
const meta = [];
|
|
7042
|
+
if (p.peerType)
|
|
7043
|
+
meta.push(p.peerType);
|
|
7044
|
+
if (p.channel)
|
|
7045
|
+
meta.push(p.channel);
|
|
7046
|
+
if (p.model)
|
|
7047
|
+
meta.push(p.model);
|
|
7048
|
+
const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
|
|
7049
|
+
const summary = p.summary ? dim(` — ${p.summary}`) : "";
|
|
7050
|
+
const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
|
|
7051
|
+
const selfTag = p.isThisSession ? dim(" ") + yellow("(this session)") : p.isSelf ? dim(" ") + yellow("(your other session)") : "";
|
|
7052
|
+
render.info(`${statusDot} ${name}${selfTag}${groups}${metaStr}${pubkeyTag}${summary}`);
|
|
7053
|
+
if (p.cwd)
|
|
7054
|
+
render.info(dim(` cwd: ${p.cwd}`));
|
|
7055
|
+
}
|
|
7056
|
+
} catch (e) {
|
|
7057
|
+
render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
|
|
7058
|
+
}
|
|
7059
|
+
}
|
|
7060
|
+
if (wantsJson) {
|
|
7061
|
+
process.stdout.write(JSON.stringify(slugs.length === 1 ? allJson[0]?.peers : allJson, null, 2) + `
|
|
7062
|
+
`);
|
|
7063
|
+
}
|
|
7064
|
+
}
|
|
7065
|
+
var FIELD_ALIAS;
|
|
7066
|
+
var init_peers = __esm(() => {
|
|
7067
|
+
init_connect();
|
|
7068
|
+
init_facade();
|
|
7069
|
+
init_client3();
|
|
7070
|
+
init_render();
|
|
7071
|
+
init_styles();
|
|
7072
|
+
FIELD_ALIAS = {
|
|
7073
|
+
name: "displayName"
|
|
7074
|
+
};
|
|
7075
|
+
});
|
|
7076
|
+
|
|
7077
|
+
// src/commands/send.ts
|
|
7078
|
+
var exports_send = {};
|
|
7079
|
+
__export(exports_send, {
|
|
7080
|
+
runSend: () => runSend
|
|
7081
|
+
});
|
|
7082
|
+
async function runSend(flags, to, message) {
|
|
7083
|
+
if (!to || !message) {
|
|
7084
|
+
render.err("Usage: claudemesh send <to> <message>");
|
|
7085
|
+
process.exit(1);
|
|
7086
|
+
}
|
|
7087
|
+
const priority = flags.priority === "now" ? "now" : flags.priority === "low" ? "low" : "next";
|
|
7088
|
+
const config = readConfig();
|
|
7089
|
+
const meshSlug = flags.mesh ?? (config.meshes.length === 1 ? config.meshes[0].slug : null);
|
|
7090
|
+
if (!flags.self && meshSlug) {
|
|
7091
|
+
const joined = config.meshes.find((m) => m.slug === meshSlug);
|
|
7092
|
+
if (joined && /^[0-9a-f]{64}$/i.test(to) && to.toLowerCase() === joined.pubkey.toLowerCase()) {
|
|
7093
|
+
render.err(`Target "${to.slice(0, 16)}…" is your own member pubkey on mesh "${meshSlug}".`);
|
|
7094
|
+
render.hint("Pass --self to message a sibling session of your own member, or pick a different peer's pubkey.");
|
|
7095
|
+
process.exit(1);
|
|
7096
|
+
}
|
|
7097
|
+
}
|
|
7098
|
+
{
|
|
7099
|
+
const dr = await trySendViaDaemon({ to, message, priority, expectedMesh: meshSlug ?? undefined });
|
|
7100
|
+
if (dr !== null) {
|
|
7101
|
+
if (dr.ok) {
|
|
7102
|
+
if (flags.json)
|
|
7103
|
+
console.log(JSON.stringify({ ok: true, messageId: dr.messageId, target: to, via: "daemon", duplicate: !!dr.duplicate }));
|
|
7104
|
+
else
|
|
7020
7105
|
render.ok(`sent to ${to} (daemon)`, dr.messageId ? dim(dr.messageId.slice(0, 8)) : undefined);
|
|
7021
7106
|
return;
|
|
7022
7107
|
}
|
|
@@ -8423,12 +8508,32 @@ function migrateOutbox(db) {
|
|
|
8423
8508
|
CREATE INDEX IF NOT EXISTS outbox_aborted
|
|
8424
8509
|
ON outbox(status, aborted_at) WHERE status = 'aborted';
|
|
8425
8510
|
`);
|
|
8511
|
+
const hasMesh = columnExists(db, "outbox", "mesh");
|
|
8512
|
+
const hasTargetSpec = columnExists(db, "outbox", "target_spec");
|
|
8513
|
+
const hasNonce = columnExists(db, "outbox", "nonce");
|
|
8514
|
+
const hasCiphertext = columnExists(db, "outbox", "ciphertext");
|
|
8515
|
+
const hasPriority = columnExists(db, "outbox", "priority");
|
|
8516
|
+
if (!hasMesh)
|
|
8517
|
+
db.exec(`ALTER TABLE outbox ADD COLUMN mesh TEXT`);
|
|
8518
|
+
if (!hasTargetSpec)
|
|
8519
|
+
db.exec(`ALTER TABLE outbox ADD COLUMN target_spec TEXT`);
|
|
8520
|
+
if (!hasNonce)
|
|
8521
|
+
db.exec(`ALTER TABLE outbox ADD COLUMN nonce TEXT`);
|
|
8522
|
+
if (!hasCiphertext)
|
|
8523
|
+
db.exec(`ALTER TABLE outbox ADD COLUMN ciphertext TEXT`);
|
|
8524
|
+
if (!hasPriority)
|
|
8525
|
+
db.exec(`ALTER TABLE outbox ADD COLUMN priority TEXT`);
|
|
8526
|
+
}
|
|
8527
|
+
function columnExists(db, table, column) {
|
|
8528
|
+
const rows = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
8529
|
+
return rows.some((r) => r.name === column);
|
|
8426
8530
|
}
|
|
8427
8531
|
function findByClientId(db, clientMessageId) {
|
|
8428
8532
|
const row = db.prepare(`
|
|
8429
8533
|
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
8430
8534
|
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
8431
|
-
broker_message_id, aborted_at, aborted_by, superseded_by
|
|
8535
|
+
broker_message_id, aborted_at, aborted_by, superseded_by,
|
|
8536
|
+
mesh, target_spec, nonce, ciphertext, priority
|
|
8432
8537
|
FROM outbox WHERE client_message_id = ?
|
|
8433
8538
|
`).get(clientMessageId);
|
|
8434
8539
|
return row ?? null;
|
|
@@ -8437,9 +8542,10 @@ function insertPending(db, input) {
|
|
|
8437
8542
|
db.prepare(`
|
|
8438
8543
|
INSERT INTO outbox (
|
|
8439
8544
|
id, client_message_id, request_fingerprint, payload,
|
|
8440
|
-
enqueued_at, attempts, next_attempt_at, status
|
|
8441
|
-
|
|
8442
|
-
|
|
8545
|
+
enqueued_at, attempts, next_attempt_at, status,
|
|
8546
|
+
mesh, target_spec, nonce, ciphertext, priority
|
|
8547
|
+
) VALUES (?, ?, ?, ?, ?, 0, ?, 'pending', ?, ?, ?, ?, ?)
|
|
8548
|
+
`).run(input.id, input.client_message_id, input.request_fingerprint, input.payload, input.now, input.now, input.mesh ?? null, input.target_spec ?? null, input.nonce ?? null, input.ciphertext ?? null, input.priority ?? null);
|
|
8443
8549
|
}
|
|
8444
8550
|
function fingerprintsEqual(a, b) {
|
|
8445
8551
|
if (a.length !== b.length)
|
|
@@ -8459,7 +8565,8 @@ function listOutbox(db, p = {}) {
|
|
|
8459
8565
|
const sql = `
|
|
8460
8566
|
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
8461
8567
|
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
8462
|
-
broker_message_id, aborted_at, aborted_by, superseded_by
|
|
8568
|
+
broker_message_id, aborted_at, aborted_by, superseded_by,
|
|
8569
|
+
mesh, target_spec, nonce, ciphertext, priority
|
|
8463
8570
|
FROM outbox
|
|
8464
8571
|
${where.length ? "WHERE " + where.join(" AND ") : ""}
|
|
8465
8572
|
ORDER BY enqueued_at DESC
|
|
@@ -8472,7 +8579,8 @@ function findById(db, id) {
|
|
|
8472
8579
|
return db.prepare(`
|
|
8473
8580
|
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
8474
8581
|
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
8475
|
-
broker_message_id, aborted_at, aborted_by, superseded_by
|
|
8582
|
+
broker_message_id, aborted_at, aborted_by, superseded_by,
|
|
8583
|
+
mesh, target_spec, nonce, ciphertext, priority
|
|
8476
8584
|
FROM outbox WHERE id = ?
|
|
8477
8585
|
`).get(id) ?? null;
|
|
8478
8586
|
}
|
|
@@ -8615,7 +8723,12 @@ function acceptSend(req, deps) {
|
|
|
8615
8723
|
client_message_id: clientId,
|
|
8616
8724
|
request_fingerprint: fingerprint,
|
|
8617
8725
|
payload: body,
|
|
8618
|
-
now
|
|
8726
|
+
now,
|
|
8727
|
+
mesh: req.mesh,
|
|
8728
|
+
target_spec: req.target_spec,
|
|
8729
|
+
nonce: req.nonce,
|
|
8730
|
+
ciphertext: req.ciphertext,
|
|
8731
|
+
priority: req.priority
|
|
8619
8732
|
});
|
|
8620
8733
|
return { kind: "accepted_pending", status: 202, client_message_id: clientId };
|
|
8621
8734
|
}
|
|
@@ -8796,7 +8909,9 @@ function startIpcServer(opts) {
|
|
|
8796
8909
|
inboxDb: opts.inboxDb,
|
|
8797
8910
|
bus: opts.bus,
|
|
8798
8911
|
broker: opts.broker,
|
|
8799
|
-
onPendingInserted: opts.onPendingInserted
|
|
8912
|
+
onPendingInserted: opts.onPendingInserted,
|
|
8913
|
+
meshSecretKey: opts.meshSecretKey,
|
|
8914
|
+
meshSlug: opts.meshSlug
|
|
8800
8915
|
});
|
|
8801
8916
|
if (existsSync9(DAEMON_PATHS.SOCK_FILE)) {
|
|
8802
8917
|
try {
|
|
@@ -8888,7 +9003,7 @@ function makeHandler(opts) {
|
|
|
8888
9003
|
respond(res, 200, {
|
|
8889
9004
|
daemon_version: VERSION,
|
|
8890
9005
|
ipc_api: "v1",
|
|
8891
|
-
ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile"],
|
|
9006
|
+
ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile", "skills"],
|
|
8892
9007
|
schema_version: 1
|
|
8893
9008
|
});
|
|
8894
9009
|
return;
|
|
@@ -8918,6 +9033,42 @@ function makeHandler(opts) {
|
|
|
8918
9033
|
}
|
|
8919
9034
|
return;
|
|
8920
9035
|
}
|
|
9036
|
+
if (req.method === "GET" && url.pathname === "/v1/skills") {
|
|
9037
|
+
if (!opts.broker) {
|
|
9038
|
+
respond(res, 503, { error: "broker not initialised" });
|
|
9039
|
+
return;
|
|
9040
|
+
}
|
|
9041
|
+
const query = url.searchParams.get("query") ?? undefined;
|
|
9042
|
+
try {
|
|
9043
|
+
const skills = await opts.broker.listSkills(query);
|
|
9044
|
+
respond(res, 200, { skills });
|
|
9045
|
+
} catch (e) {
|
|
9046
|
+
respond(res, 502, { error: "broker_unreachable", detail: String(e) });
|
|
9047
|
+
}
|
|
9048
|
+
return;
|
|
9049
|
+
}
|
|
9050
|
+
if (req.method === "GET" && url.pathname.startsWith("/v1/skills/")) {
|
|
9051
|
+
if (!opts.broker) {
|
|
9052
|
+
respond(res, 503, { error: "broker not initialised" });
|
|
9053
|
+
return;
|
|
9054
|
+
}
|
|
9055
|
+
const name = decodeURIComponent(url.pathname.slice("/v1/skills/".length));
|
|
9056
|
+
if (!name) {
|
|
9057
|
+
respond(res, 400, { error: "missing skill name" });
|
|
9058
|
+
return;
|
|
9059
|
+
}
|
|
9060
|
+
try {
|
|
9061
|
+
const skill = await opts.broker.getSkill(name);
|
|
9062
|
+
if (!skill) {
|
|
9063
|
+
respond(res, 404, { error: "skill_not_found", name });
|
|
9064
|
+
return;
|
|
9065
|
+
}
|
|
9066
|
+
respond(res, 200, { skill });
|
|
9067
|
+
} catch (e) {
|
|
9068
|
+
respond(res, 502, { error: "broker_unreachable", detail: String(e) });
|
|
9069
|
+
}
|
|
9070
|
+
return;
|
|
9071
|
+
}
|
|
8921
9072
|
if (req.method === "POST" && url.pathname === "/v1/profile") {
|
|
8922
9073
|
if (!opts.broker) {
|
|
8923
9074
|
respond(res, 503, { error: "broker not initialised" });
|
|
@@ -9066,6 +9217,18 @@ function makeHandler(opts) {
|
|
|
9066
9217
|
respond(res, 400, { error: parsed.error });
|
|
9067
9218
|
return;
|
|
9068
9219
|
}
|
|
9220
|
+
if (opts.broker && opts.meshSecretKey) {
|
|
9221
|
+
try {
|
|
9222
|
+
const routed = await resolveAndEncrypt(parsed.req, opts.broker, opts.meshSecretKey, opts.meshSlug ?? null);
|
|
9223
|
+
parsed.req.target_spec = routed.target_spec;
|
|
9224
|
+
parsed.req.ciphertext = routed.ciphertext;
|
|
9225
|
+
parsed.req.nonce = routed.nonce;
|
|
9226
|
+
parsed.req.mesh = routed.mesh;
|
|
9227
|
+
} catch (e) {
|
|
9228
|
+
respond(res, 502, { error: "route_failed", detail: String(e) });
|
|
9229
|
+
return;
|
|
9230
|
+
}
|
|
9231
|
+
}
|
|
9069
9232
|
const outcome = acceptSend(parsed.req, { db: opts.outboxDb });
|
|
9070
9233
|
switch (outcome.kind) {
|
|
9071
9234
|
case "accepted_pending":
|
|
@@ -9171,6 +9334,48 @@ function parseSendRequest(body, idempotencyHeader) {
|
|
|
9171
9334
|
}
|
|
9172
9335
|
};
|
|
9173
9336
|
}
|
|
9337
|
+
async function resolveAndEncrypt(req, broker, meshSecretKey, meshSlug) {
|
|
9338
|
+
const { encryptDirect: encryptDirect2 } = await Promise.resolve().then(() => (init_box(), exports_box));
|
|
9339
|
+
const { randomBytes: randomBytes5 } = await import("node:crypto");
|
|
9340
|
+
const to = req.to.trim();
|
|
9341
|
+
if (to.startsWith("#") && /^#[0-9a-z_-]{20,}$/i.test(to)) {
|
|
9342
|
+
const ciphertext = Buffer.from(req.message, "utf8").toString("base64");
|
|
9343
|
+
const nonce = randomBytes5(24).toString("base64");
|
|
9344
|
+
return { target_spec: to, ciphertext, nonce, mesh: meshSlug ?? "" };
|
|
9345
|
+
}
|
|
9346
|
+
if (to.startsWith("@") || to === "*") {
|
|
9347
|
+
const ciphertext = Buffer.from(req.message, "utf8").toString("base64");
|
|
9348
|
+
const nonce = randomBytes5(24).toString("base64");
|
|
9349
|
+
return { target_spec: to, ciphertext, nonce, mesh: meshSlug ?? "" };
|
|
9350
|
+
}
|
|
9351
|
+
if (/^[0-9a-f]{64}$/i.test(to)) {
|
|
9352
|
+
const sessionKeys2 = broker.getSessionKeys();
|
|
9353
|
+
const senderSecret2 = sessionKeys2?.sessionSecretKey ?? meshSecretKey;
|
|
9354
|
+
const env3 = await encryptDirect2(req.message, to, senderSecret2);
|
|
9355
|
+
return { target_spec: to, ciphertext: env3.ciphertext, nonce: env3.nonce, mesh: meshSlug ?? "" };
|
|
9356
|
+
}
|
|
9357
|
+
const peers = await broker.listPeers().catch(() => []);
|
|
9358
|
+
if (/^[0-9a-f]{16,63}$/i.test(to)) {
|
|
9359
|
+
const matches2 = peers.filter((p) => p.pubkey.toLowerCase().startsWith(to.toLowerCase()) || (p.memberPubkey ?? "").toLowerCase().startsWith(to.toLowerCase()));
|
|
9360
|
+
if (matches2.length === 0)
|
|
9361
|
+
throw new Error(`no peer matching prefix "${to}"`);
|
|
9362
|
+
if (matches2.length > 1)
|
|
9363
|
+
throw new Error(`prefix "${to}" is ambiguous (${matches2.length} matches)`);
|
|
9364
|
+
const recipient2 = matches2[0].pubkey;
|
|
9365
|
+
const sessionKeys2 = broker.getSessionKeys();
|
|
9366
|
+
const senderSecret2 = sessionKeys2?.sessionSecretKey ?? meshSecretKey;
|
|
9367
|
+
const env3 = await encryptDirect2(req.message, recipient2, senderSecret2);
|
|
9368
|
+
return { target_spec: recipient2, ciphertext: env3.ciphertext, nonce: env3.nonce, mesh: meshSlug ?? "" };
|
|
9369
|
+
}
|
|
9370
|
+
const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
|
|
9371
|
+
if (!match)
|
|
9372
|
+
throw new Error(`peer "${to}" not found`);
|
|
9373
|
+
const recipient = match.pubkey;
|
|
9374
|
+
const sessionKeys = broker.getSessionKeys();
|
|
9375
|
+
const senderSecret = sessionKeys?.sessionSecretKey ?? meshSecretKey;
|
|
9376
|
+
const env2 = await encryptDirect2(req.message, recipient, senderSecret);
|
|
9377
|
+
return { target_spec: recipient, ciphertext: env2.ciphertext, nonce: env2.nonce, mesh: meshSlug ?? "" };
|
|
9378
|
+
}
|
|
9174
9379
|
function respond(res, status, body) {
|
|
9175
9380
|
const json = JSON.stringify(body);
|
|
9176
9381
|
res.statusCode = status;
|
|
@@ -9198,6 +9403,8 @@ class DaemonBrokerClient {
|
|
|
9198
9403
|
helloTimer = null;
|
|
9199
9404
|
pendingAcks = new Map;
|
|
9200
9405
|
peerListResolvers = new Map;
|
|
9406
|
+
skillListResolvers = new Map;
|
|
9407
|
+
skillDataResolvers = new Map;
|
|
9201
9408
|
sessionPubkey = null;
|
|
9202
9409
|
sessionSecretKey = null;
|
|
9203
9410
|
opens = [];
|
|
@@ -9318,6 +9525,26 @@ class DaemonBrokerClient {
|
|
|
9318
9525
|
}
|
|
9319
9526
|
return;
|
|
9320
9527
|
}
|
|
9528
|
+
if (msg.type === "skill_list") {
|
|
9529
|
+
const reqId = String(msg._reqId ?? "");
|
|
9530
|
+
const pending = this.skillListResolvers.get(reqId);
|
|
9531
|
+
if (pending) {
|
|
9532
|
+
this.skillListResolvers.delete(reqId);
|
|
9533
|
+
clearTimeout(pending.timer);
|
|
9534
|
+
pending.resolve(Array.isArray(msg.skills) ? msg.skills : []);
|
|
9535
|
+
}
|
|
9536
|
+
return;
|
|
9537
|
+
}
|
|
9538
|
+
if (msg.type === "skill_data") {
|
|
9539
|
+
const reqId = String(msg._reqId ?? "");
|
|
9540
|
+
const pending = this.skillDataResolvers.get(reqId);
|
|
9541
|
+
if (pending) {
|
|
9542
|
+
this.skillDataResolvers.delete(reqId);
|
|
9543
|
+
clearTimeout(pending.timer);
|
|
9544
|
+
pending.resolve(msg.skill ?? null);
|
|
9545
|
+
}
|
|
9546
|
+
return;
|
|
9547
|
+
}
|
|
9321
9548
|
if (msg.type === "push" || msg.type === "inbound") {
|
|
9322
9549
|
this.opts.onPush?.(msg);
|
|
9323
9550
|
return;
|
|
@@ -9400,6 +9627,44 @@ class DaemonBrokerClient {
|
|
|
9400
9627
|
}
|
|
9401
9628
|
});
|
|
9402
9629
|
}
|
|
9630
|
+
async listSkills(query, timeoutMs = 5000) {
|
|
9631
|
+
if (this._status !== "open" || !this.ws)
|
|
9632
|
+
return [];
|
|
9633
|
+
return new Promise((resolve) => {
|
|
9634
|
+
const reqId = `sl-${++this.reqCounter}`;
|
|
9635
|
+
const timer = setTimeout(() => {
|
|
9636
|
+
if (this.skillListResolvers.delete(reqId))
|
|
9637
|
+
resolve([]);
|
|
9638
|
+
}, timeoutMs);
|
|
9639
|
+
this.skillListResolvers.set(reqId, { resolve, timer });
|
|
9640
|
+
try {
|
|
9641
|
+
this.ws.send(JSON.stringify({ type: "list_skills", query, _reqId: reqId }));
|
|
9642
|
+
} catch {
|
|
9643
|
+
this.skillListResolvers.delete(reqId);
|
|
9644
|
+
clearTimeout(timer);
|
|
9645
|
+
resolve([]);
|
|
9646
|
+
}
|
|
9647
|
+
});
|
|
9648
|
+
}
|
|
9649
|
+
async getSkill(name, timeoutMs = 5000) {
|
|
9650
|
+
if (this._status !== "open" || !this.ws)
|
|
9651
|
+
return null;
|
|
9652
|
+
return new Promise((resolve) => {
|
|
9653
|
+
const reqId = `sg-${++this.reqCounter}`;
|
|
9654
|
+
const timer = setTimeout(() => {
|
|
9655
|
+
if (this.skillDataResolvers.delete(reqId))
|
|
9656
|
+
resolve(null);
|
|
9657
|
+
}, timeoutMs);
|
|
9658
|
+
this.skillDataResolvers.set(reqId, { resolve, timer });
|
|
9659
|
+
try {
|
|
9660
|
+
this.ws.send(JSON.stringify({ type: "get_skill", name, _reqId: reqId }));
|
|
9661
|
+
} catch {
|
|
9662
|
+
this.skillDataResolvers.delete(reqId);
|
|
9663
|
+
clearTimeout(timer);
|
|
9664
|
+
resolve(null);
|
|
9665
|
+
}
|
|
9666
|
+
});
|
|
9667
|
+
}
|
|
9403
9668
|
setProfile(profile) {
|
|
9404
9669
|
if (this._status !== "open" || !this.ws)
|
|
9405
9670
|
return;
|
|
@@ -9518,7 +9783,8 @@ function startDrainWorker(opts) {
|
|
|
9518
9783
|
async function drainOnce(opts, log2) {
|
|
9519
9784
|
const now = Date.now();
|
|
9520
9785
|
const rows = opts.db.prepare(`
|
|
9521
|
-
SELECT id, client_message_id, request_fingerprint, payload, attempts
|
|
9786
|
+
SELECT id, client_message_id, request_fingerprint, payload, attempts,
|
|
9787
|
+
target_spec, nonce, ciphertext, priority, mesh
|
|
9522
9788
|
FROM outbox
|
|
9523
9789
|
WHERE status = 'pending' AND next_attempt_at <= ?
|
|
9524
9790
|
ORDER BY enqueued_at
|
|
@@ -9530,15 +9796,26 @@ async function drainOnce(opts, log2) {
|
|
|
9530
9796
|
if (markInflight(opts.db, row.id, now) === 0)
|
|
9531
9797
|
continue;
|
|
9532
9798
|
const fpHex = bufferToHex(row.request_fingerprint);
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
|
|
9799
|
+
let targetSpec;
|
|
9800
|
+
let nonce;
|
|
9801
|
+
let ciphertext;
|
|
9802
|
+
let priority;
|
|
9803
|
+
if (row.target_spec && row.nonce && row.ciphertext) {
|
|
9804
|
+
targetSpec = row.target_spec;
|
|
9805
|
+
nonce = row.nonce;
|
|
9806
|
+
ciphertext = row.ciphertext;
|
|
9807
|
+
priority = row.priority === "now" || row.priority === "low" ? row.priority : "next";
|
|
9808
|
+
} else {
|
|
9809
|
+
targetSpec = "*";
|
|
9810
|
+
nonce = await randomNonce2();
|
|
9811
|
+
ciphertext = Buffer.from(row.payload).toString("base64");
|
|
9812
|
+
priority = "next";
|
|
9813
|
+
}
|
|
9537
9814
|
let res;
|
|
9538
9815
|
try {
|
|
9539
9816
|
res = await opts.broker.send({
|
|
9540
9817
|
targetSpec,
|
|
9541
|
-
priority
|
|
9818
|
+
priority,
|
|
9542
9819
|
nonce,
|
|
9543
9820
|
ciphertext,
|
|
9544
9821
|
client_message_id: row.client_message_id,
|
|
@@ -9626,11 +9903,14 @@ async function handleBrokerPush(msg, ctx) {
|
|
|
9626
9903
|
const brokerMessageId = stringOrNull(msg.messageId);
|
|
9627
9904
|
const senderPubkey = stringOrNull(msg.senderPubkey) ?? "";
|
|
9628
9905
|
const senderName = stringOrNull(msg.senderName) ?? senderPubkey.slice(0, 8);
|
|
9906
|
+
const senderMemberPk = stringOrNull(msg.senderMemberPubkey);
|
|
9629
9907
|
const topic = stringOrNull(msg.topic);
|
|
9630
9908
|
const replyToId = stringOrNull(msg.replyToId);
|
|
9631
9909
|
const ciphertext = stringOrNull(msg.ciphertext) ?? "";
|
|
9632
9910
|
const nonce = stringOrNull(msg.nonce) ?? "";
|
|
9633
9911
|
const createdAt = stringOrNull(msg.createdAt);
|
|
9912
|
+
const priority = stringOrNull(msg.priority) ?? "next";
|
|
9913
|
+
const subtype = stringOrNull(msg.subtype);
|
|
9634
9914
|
const clientMessageId = stringOrNull(msg.client_message_id) ?? brokerMessageId ?? randomUUID5();
|
|
9635
9915
|
const body = await decryptOrFallback({
|
|
9636
9916
|
ciphertext,
|
|
@@ -9660,9 +9940,12 @@ async function handleBrokerPush(msg, ctx) {
|
|
|
9660
9940
|
client_message_id: clientMessageId,
|
|
9661
9941
|
broker_message_id: brokerMessageId,
|
|
9662
9942
|
sender_pubkey: senderPubkey,
|
|
9943
|
+
sender_member_pubkey: senderMemberPk,
|
|
9663
9944
|
sender_name: senderName,
|
|
9664
9945
|
topic,
|
|
9665
9946
|
reply_to_id: replyToId,
|
|
9947
|
+
priority,
|
|
9948
|
+
...subtype ? { subtype } : {},
|
|
9666
9949
|
body,
|
|
9667
9950
|
created_at: createdAt
|
|
9668
9951
|
});
|
|
@@ -9946,7 +10229,9 @@ async function runDaemon(opts = {}) {
|
|
|
9946
10229
|
inboxDb,
|
|
9947
10230
|
bus,
|
|
9948
10231
|
broker,
|
|
9949
|
-
onPendingInserted: () => drain?.wake()
|
|
10232
|
+
onPendingInserted: () => drain?.wake(),
|
|
10233
|
+
meshSecretKey: mesh.secretKey,
|
|
10234
|
+
meshSlug: mesh.slug
|
|
9950
10235
|
});
|
|
9951
10236
|
try {
|
|
9952
10237
|
await ipc2.ready;
|
|
@@ -10761,6 +11046,7 @@ function installStatusLine() {
|
|
|
10761
11046
|
function runInstall(args = []) {
|
|
10762
11047
|
const skipHooks = args.includes("--no-hooks");
|
|
10763
11048
|
const skipSkill = args.includes("--no-skill");
|
|
11049
|
+
const skipService = args.includes("--no-service");
|
|
10764
11050
|
const wantStatusLine = args.includes("--status-line");
|
|
10765
11051
|
render.section("claudemesh install");
|
|
10766
11052
|
const entry = resolveEntry();
|
|
@@ -10842,10 +11128,25 @@ function runInstall(args = []) {
|
|
|
10842
11128
|
}
|
|
10843
11129
|
}
|
|
10844
11130
|
let hasMeshes = false;
|
|
11131
|
+
let primaryMesh;
|
|
10845
11132
|
try {
|
|
10846
11133
|
const meshConfig = readConfig();
|
|
10847
11134
|
hasMeshes = meshConfig.meshes.length > 0;
|
|
11135
|
+
primaryMesh = meshConfig.meshes[0]?.slug;
|
|
10848
11136
|
} catch {}
|
|
11137
|
+
if (!skipService && hasMeshes && primaryMesh) {
|
|
11138
|
+
try {
|
|
11139
|
+
installDaemonService(entry, primaryMesh);
|
|
11140
|
+
} catch (e) {
|
|
11141
|
+
render.warn(`daemon service install failed: ${e instanceof Error ? e.message : String(e)}`, "Run `claudemesh daemon install-service --mesh <slug>` to retry.");
|
|
11142
|
+
}
|
|
11143
|
+
} else if (skipService) {
|
|
11144
|
+
render.info(dim("· Daemon service skipped (--no-service)"));
|
|
11145
|
+
render.info(dim(" MCP integration will fail at boot until you start the daemon manually:"));
|
|
11146
|
+
render.info(dim(" claudemesh daemon up --mesh <slug>"));
|
|
11147
|
+
} else if (!hasMeshes) {
|
|
11148
|
+
render.info(dim("· Daemon service deferred — join a mesh first, then run install again."));
|
|
11149
|
+
}
|
|
10849
11150
|
render.blank();
|
|
10850
11151
|
render.warn(`${bold("RESTART CLAUDE CODE")} ${yellow("for MCP tools to appear.")}`);
|
|
10851
11152
|
if (!hasMeshes) {
|
|
@@ -10863,6 +11164,40 @@ function runInstall(args = []) {
|
|
|
10863
11164
|
render.info(dim(` claudemesh install --status-line # live peer count in Claude Code`));
|
|
10864
11165
|
render.info(dim(` claudemesh completions zsh # shell completions`));
|
|
10865
11166
|
}
|
|
11167
|
+
function installDaemonService(binaryEntry, meshSlug) {
|
|
11168
|
+
const {
|
|
11169
|
+
installService: installService2,
|
|
11170
|
+
detectPlatform: detectPlatform2
|
|
11171
|
+
} = (init_service_install(), __toCommonJS(exports_service_install));
|
|
11172
|
+
const platform6 = detectPlatform2();
|
|
11173
|
+
if (!platform6) {
|
|
11174
|
+
render.info(dim(`· Daemon service skipped — unsupported platform: ${process.platform}`));
|
|
11175
|
+
return;
|
|
11176
|
+
}
|
|
11177
|
+
let binary = process.argv[1] ?? binaryEntry;
|
|
11178
|
+
if (!binary || /\.ts$/.test(binary) || /node_modules|src\/entrypoints/.test(binary)) {
|
|
11179
|
+
try {
|
|
11180
|
+
const { execSync: execSync3 } = __require("node:child_process");
|
|
11181
|
+
binary = execSync3("which claudemesh", { encoding: "utf8" }).trim();
|
|
11182
|
+
} catch {
|
|
11183
|
+
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 + "`");
|
|
11184
|
+
return;
|
|
11185
|
+
}
|
|
11186
|
+
}
|
|
11187
|
+
const r = installService2({ binaryPath: binary, meshSlug });
|
|
11188
|
+
render.ok(`daemon service installed (${r.platform})`);
|
|
11189
|
+
render.kv([
|
|
11190
|
+
["unit", dim(r.unitPath)],
|
|
11191
|
+
["mesh", dim(meshSlug)]
|
|
11192
|
+
]);
|
|
11193
|
+
try {
|
|
11194
|
+
const { execSync: execSync3 } = __require("node:child_process");
|
|
11195
|
+
execSync3(r.bootCommand, { stdio: "ignore" });
|
|
11196
|
+
render.ok("daemon started");
|
|
11197
|
+
} catch (e) {
|
|
11198
|
+
render.warn(`daemon service installed but failed to start: ${e instanceof Error ? e.message : String(e)}`, `Run manually: ${r.bootCommand}`);
|
|
11199
|
+
}
|
|
11200
|
+
}
|
|
10866
11201
|
function runUninstall() {
|
|
10867
11202
|
render.section("claudemesh uninstall");
|
|
10868
11203
|
if (removeMcpServer()) {
|
|
@@ -13093,6 +13428,32 @@ async function runSqlSchema(opts) {
|
|
|
13093
13428
|
});
|
|
13094
13429
|
}
|
|
13095
13430
|
async function runSkillList(opts) {
|
|
13431
|
+
try {
|
|
13432
|
+
const { tryListSkillsViaDaemon: tryListSkillsViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
|
|
13433
|
+
const dr = await tryListSkillsViaDaemon2();
|
|
13434
|
+
if (dr !== null) {
|
|
13435
|
+
const skills = dr;
|
|
13436
|
+
if (opts.json) {
|
|
13437
|
+
emitJson(skills);
|
|
13438
|
+
return EXIT.SUCCESS;
|
|
13439
|
+
}
|
|
13440
|
+
if (skills.length === 0) {
|
|
13441
|
+
render.info(dim("(no skills)"));
|
|
13442
|
+
return EXIT.SUCCESS;
|
|
13443
|
+
}
|
|
13444
|
+
render.section(`mesh skills (${skills.length})`);
|
|
13445
|
+
for (const s of skills) {
|
|
13446
|
+
process.stdout.write(` ${bold(s.name)} ${dim("· by " + s.author)}
|
|
13447
|
+
`);
|
|
13448
|
+
process.stdout.write(` ${s.description}
|
|
13449
|
+
`);
|
|
13450
|
+
if (s.tags?.length)
|
|
13451
|
+
process.stdout.write(` ${dim("tags: " + s.tags.join(", "))}
|
|
13452
|
+
`);
|
|
13453
|
+
}
|
|
13454
|
+
return EXIT.SUCCESS;
|
|
13455
|
+
}
|
|
13456
|
+
} catch {}
|
|
13096
13457
|
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
|
|
13097
13458
|
const skills = await client.listSkills(opts.query);
|
|
13098
13459
|
if (opts.json) {
|
|
@@ -13121,6 +13482,29 @@ async function runSkillGet(name, opts) {
|
|
|
13121
13482
|
render.err("Usage: claudemesh skill get <name>");
|
|
13122
13483
|
return EXIT.INVALID_ARGS;
|
|
13123
13484
|
}
|
|
13485
|
+
try {
|
|
13486
|
+
const { tryGetSkillViaDaemon: tryGetSkillViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
|
|
13487
|
+
const dr = await tryGetSkillViaDaemon2(name);
|
|
13488
|
+
if (dr !== null) {
|
|
13489
|
+
const skill = dr;
|
|
13490
|
+
if (opts.json) {
|
|
13491
|
+
emitJson(skill);
|
|
13492
|
+
return EXIT.SUCCESS;
|
|
13493
|
+
}
|
|
13494
|
+
render.section(skill.name);
|
|
13495
|
+
render.kv([
|
|
13496
|
+
["author", skill.author],
|
|
13497
|
+
["created", skill.createdAt],
|
|
13498
|
+
["tags", skill.tags?.join(", ") || dim("(none)")]
|
|
13499
|
+
]);
|
|
13500
|
+
render.blank();
|
|
13501
|
+
render.info(skill.description);
|
|
13502
|
+
render.blank();
|
|
13503
|
+
process.stdout.write(skill.instructions + `
|
|
13504
|
+
`);
|
|
13505
|
+
return EXIT.SUCCESS;
|
|
13506
|
+
}
|
|
13507
|
+
} catch {}
|
|
13124
13508
|
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
|
|
13125
13509
|
const skill = await client.getSkill(name);
|
|
13126
13510
|
if (!skill) {
|
|
@@ -13638,7 +14022,7 @@ claudemesh send "<from_name>" "..." --mesh "<mesh_slug>"
|
|
|
13638
14022
|
|
|
13639
14023
|
If the parent Claude session was launched via \`claudemesh launch\`, an MCP push-pipe is running and holds the per-mesh WS connection. CLI invocations dial \`~/.claudemesh/sockets/<mesh-slug>.sock\` and reuse that warm connection (~200ms total round-trip including Node.js startup). If no push-pipe is running (cron, scripts, hooks fired outside a session), the CLI opens its own WS, which takes ~500-700ms cold. **You don't manage this** — every verb auto-detects and falls through.
|
|
13640
14024
|
|
|
13641
|
-
### Daemon path (
|
|
14025
|
+
### Daemon path (v1.24.0+, REQUIRED for in-Claude-Code use)
|
|
13642
14026
|
|
|
13643
14027
|
\`claudemesh daemon up [--mesh <slug>]\` starts a persistent per-user runtime that holds the broker WS, a durable SQLite outbox/inbox, and listens on \`~/.claudemesh/daemon/daemon.sock\` (UDS) plus an optional loopback TCP. When the daemon socket is present, every verb routes through it first (~1ms IPC) before falling back to bridge / cold paths. The send envelope carries a caller-stable \`client_message_id\`, so a \`claudemesh send\` that started before a daemon crash survives the restart via the on-disk outbox.
|
|
13644
14028
|
|
|
@@ -13653,11 +14037,15 @@ claudemesh daemon outbox requeue <id> # re-enqueue an aborted/d
|
|
|
13653
14037
|
claudemesh daemon down # SIGTERM + wait
|
|
13654
14038
|
\`\`\`
|
|
13655
14039
|
|
|
13656
|
-
\`claudemesh install\`
|
|
14040
|
+
As of 1.24.0 \`claudemesh install\` registers the MCP entry **and** installs/starts the daemon service for the user's primary mesh. The MCP shim hard-requires the daemon to be running — it bails at boot with actionable instructions if the socket isn't present. There is no fallback. CLI verbs (\`send\`, \`peer list\`, \`inbox\`, \`skill list/get\`, etc.) keep working without a daemon via bridge or cold paths, but for any in-Claude-Code use the daemon must be up.
|
|
14041
|
+
|
|
14042
|
+
### Ambient mode (1.25.0+)
|
|
14043
|
+
|
|
14044
|
+
Once \`claudemesh install\` has run (registers MCP entry + starts daemon service), **raw \`claude\` Just Works** for the daemon's attached mesh. No \`claudemesh launch\` ceremony, no manual flags, no per-session keypair. Channel push, slash commands, and resources all flow through the daemon-backed MCP shim. Use \`claudemesh launch\` only when you need to override defaults (different mesh, custom display name, system-prompt injection, headless modes).
|
|
13657
14045
|
|
|
13658
14046
|
## Spawning new sessions (no wizard)
|
|
13659
14047
|
|
|
13660
|
-
\`claudemesh launch\`
|
|
14048
|
+
\`claudemesh launch\` remains useful for non-default cases: explicit mesh selection, fresh display name, headless \`--quiet\` runs, system-prompt injection, multi-mesh users with one daemon attached to mesh A who want to spawn into mesh B. For the common case (single joined mesh, daemon installed), prefer raw \`claude\`. Pass every required flag up front so no interactive prompt fires — that's what makes the verb scriptable from tmux send-keys, AppleScript/iTerm spawn helpers, hooks, cron, and the \`claudemesh launch\` you call from inside another session.
|
|
13661
14049
|
|
|
13662
14050
|
### Full flag surface
|
|
13663
14051
|
|
|
@@ -15958,435 +16346,194 @@ var init_member = __esm(() => {
|
|
|
15958
16346
|
init_exit_codes();
|
|
15959
16347
|
});
|
|
15960
16348
|
|
|
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
|
-
};
|
|
16349
|
+
// src/mcp/server.ts
|
|
16350
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
16351
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
16352
|
+
import {
|
|
16353
|
+
ListToolsRequestSchema,
|
|
16354
|
+
CallToolRequestSchema,
|
|
16355
|
+
ListPromptsRequestSchema,
|
|
16356
|
+
GetPromptRequestSchema,
|
|
16357
|
+
ListResourcesRequestSchema,
|
|
16358
|
+
ReadResourceRequestSchema
|
|
16359
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
16360
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
16361
|
+
import { request as httpRequest2 } from "node:http";
|
|
16362
|
+
async function daemonReady() {
|
|
16363
|
+
for (let i = 0;i < DAEMON_BOOT_RETRIES; i++) {
|
|
16364
|
+
if (existsSync28(DAEMON_PATHS.SOCK_FILE))
|
|
16365
|
+
return true;
|
|
16366
|
+
await new Promise((r) => setTimeout(r, DAEMON_BOOT_RETRY_MS));
|
|
15981
16367
|
}
|
|
15982
|
-
return
|
|
16368
|
+
return false;
|
|
15983
16369
|
}
|
|
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
|
-
}
|
|
16370
|
+
function bailNoDaemon() {
|
|
16371
|
+
process.stderr.write(`[claudemesh] daemon is not running.
|
|
16372
|
+
` + ` Start it: claudemesh daemon up --mesh <slug>
|
|
16373
|
+
` + ` Or install as service: claudemesh daemon install-service --mesh <slug>
|
|
16374
|
+
` + ` Diagnose: claudemesh doctor
|
|
16375
|
+
` + `
|
|
16376
|
+
` + ` As of 1.24.0 the daemon is required for in-Claude-Code use of
|
|
16377
|
+
` + ` claudemesh. The CLI itself (claudemesh send/peer/inbox/...) still
|
|
16378
|
+
` + ` works without a daemon.
|
|
16379
|
+
`);
|
|
16380
|
+
process.exit(1);
|
|
16055
16381
|
}
|
|
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) => {
|
|
16382
|
+
function daemonGet(path2) {
|
|
16383
|
+
return new Promise((resolve3, reject) => {
|
|
16384
|
+
const req = httpRequest2({ socketPath: DAEMON_PATHS.SOCK_FILE, path: path2, method: "GET", timeout: 5000 }, (res) => {
|
|
16385
|
+
const chunks = [];
|
|
16386
|
+
res.on("data", (c) => chunks.push(c));
|
|
16387
|
+
res.on("end", () => {
|
|
16388
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
16389
|
+
let body = null;
|
|
16072
16390
|
try {
|
|
16073
|
-
|
|
16074
|
-
} catch {
|
|
16391
|
+
body = JSON.parse(text);
|
|
16392
|
+
} catch {
|
|
16393
|
+
body = text;
|
|
16394
|
+
}
|
|
16395
|
+
resolve3({ status: res.statusCode ?? 0, body });
|
|
16075
16396
|
});
|
|
16076
|
-
}
|
|
16397
|
+
});
|
|
16398
|
+
req.on("error", reject);
|
|
16399
|
+
req.on("timeout", () => req.destroy(new Error("daemon_ipc_timeout")));
|
|
16400
|
+
req.end();
|
|
16077
16401
|
});
|
|
16078
|
-
socket.on("error", () => {});
|
|
16079
16402
|
}
|
|
16080
|
-
function
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
|
|
16084
|
-
|
|
16085
|
-
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
|
|
16403
|
+
function subscribeEvents(onEvent) {
|
|
16404
|
+
let active = true;
|
|
16405
|
+
let req = null;
|
|
16406
|
+
const connect = () => {
|
|
16407
|
+
if (!active)
|
|
16408
|
+
return;
|
|
16409
|
+
req = httpRequest2({
|
|
16410
|
+
socketPath: DAEMON_PATHS.SOCK_FILE,
|
|
16411
|
+
path: "/v1/events",
|
|
16412
|
+
method: "GET",
|
|
16413
|
+
headers: { Accept: "text/event-stream" }
|
|
16414
|
+
});
|
|
16415
|
+
let buffer = "";
|
|
16416
|
+
req.on("response", (res) => {
|
|
16417
|
+
res.setEncoding("utf8");
|
|
16418
|
+
res.on("data", (chunk) => {
|
|
16419
|
+
buffer += chunk;
|
|
16420
|
+
let idx;
|
|
16421
|
+
while ((idx = buffer.indexOf(`
|
|
16422
|
+
|
|
16423
|
+
`)) >= 0) {
|
|
16424
|
+
const block = buffer.slice(0, idx);
|
|
16425
|
+
buffer = buffer.slice(idx + 2);
|
|
16426
|
+
if (!block.trim())
|
|
16427
|
+
continue;
|
|
16428
|
+
let kind = "message";
|
|
16429
|
+
let dataLine = "";
|
|
16430
|
+
for (const line of block.split(`
|
|
16431
|
+
`)) {
|
|
16432
|
+
if (line.startsWith(":"))
|
|
16433
|
+
continue;
|
|
16434
|
+
if (line.startsWith("event:"))
|
|
16435
|
+
kind = line.slice(6).trim();
|
|
16436
|
+
else if (line.startsWith("data:"))
|
|
16437
|
+
dataLine = line.slice(5).trim();
|
|
16438
|
+
}
|
|
16439
|
+
if (!dataLine)
|
|
16440
|
+
continue;
|
|
16441
|
+
try {
|
|
16442
|
+
const parsed = JSON.parse(dataLine);
|
|
16443
|
+
onEvent({ kind, ts: String(parsed.ts ?? ""), data: parsed });
|
|
16444
|
+
} catch {}
|
|
16445
|
+
}
|
|
16446
|
+
});
|
|
16447
|
+
res.on("end", () => {
|
|
16448
|
+
if (active) {
|
|
16449
|
+
process.stderr.write(`[claudemesh-mcp] sse stream ended; reconnecting in 1s
|
|
16096
16450
|
`);
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
|
|
16451
|
+
setTimeout(connect, 1000);
|
|
16452
|
+
}
|
|
16453
|
+
});
|
|
16454
|
+
res.on("error", (err) => process.stderr.write(`[claudemesh-mcp] sse error: ${err.message}
|
|
16455
|
+
`));
|
|
16456
|
+
});
|
|
16457
|
+
req.on("error", (err) => {
|
|
16458
|
+
process.stderr.write(`[claudemesh-mcp] sse connect error: ${err.message}
|
|
16101
16459
|
`);
|
|
16102
|
-
|
|
16103
|
-
|
|
16104
|
-
|
|
16105
|
-
|
|
16106
|
-
|
|
16460
|
+
if (active)
|
|
16461
|
+
setTimeout(connect, 2000);
|
|
16462
|
+
});
|
|
16463
|
+
req.end();
|
|
16464
|
+
};
|
|
16465
|
+
connect();
|
|
16107
16466
|
return {
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
if (stopped)
|
|
16111
|
-
return;
|
|
16112
|
-
stopped = true;
|
|
16113
|
-
try {
|
|
16114
|
-
server.close();
|
|
16115
|
-
} catch {}
|
|
16467
|
+
close: () => {
|
|
16468
|
+
active = false;
|
|
16116
16469
|
try {
|
|
16117
|
-
|
|
16470
|
+
req?.destroy();
|
|
16118
16471
|
} catch {}
|
|
16119
16472
|
}
|
|
16120
16473
|
};
|
|
16121
16474
|
}
|
|
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
16475
|
async function startMcpServer() {
|
|
16174
16476
|
const serviceIdx = process.argv.indexOf("--service");
|
|
16175
16477
|
if (serviceIdx !== -1 && process.argv[serviceIdx + 1]) {
|
|
16176
16478
|
return startServiceProxy(process.argv[serviceIdx + 1]);
|
|
16177
16479
|
}
|
|
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
|
-
}));
|
|
16480
|
+
const ok = await daemonReady();
|
|
16481
|
+
if (!ok)
|
|
16482
|
+
bailNoDaemon();
|
|
16483
|
+
const server = new Server({ name: "claudemesh", version: VERSION }, { capabilities: { tools: {}, prompts: {}, resources: {} } });
|
|
16484
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [] }));
|
|
16337
16485
|
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
16338
|
-
|
|
16339
|
-
|
|
16486
|
+
try {
|
|
16487
|
+
const { status, body } = await daemonGet("/v1/skills");
|
|
16488
|
+
if (status !== 200)
|
|
16489
|
+
return { prompts: [] };
|
|
16490
|
+
const skills = body?.skills ?? [];
|
|
16491
|
+
return { prompts: skills.map((s) => ({ name: s.name, description: s.description, arguments: [] })) };
|
|
16492
|
+
} catch {
|
|
16340
16493
|
return { prompts: [] };
|
|
16341
|
-
|
|
16342
|
-
return {
|
|
16343
|
-
prompts: skills.map((s) => ({
|
|
16344
|
-
name: s.name,
|
|
16345
|
-
description: s.description,
|
|
16346
|
-
arguments: []
|
|
16347
|
-
}))
|
|
16348
|
-
};
|
|
16494
|
+
}
|
|
16349
16495
|
});
|
|
16350
16496
|
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)
|
|
16497
|
+
const name = req.params.name;
|
|
16498
|
+
const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
|
|
16499
|
+
if (status === 404)
|
|
16357
16500
|
throw new Error(`Skill "${name}" not found in the mesh`);
|
|
16501
|
+
if (status !== 200)
|
|
16502
|
+
throw new Error(`daemon returned ${status} fetching skill`);
|
|
16503
|
+
const skill = body.skill;
|
|
16358
16504
|
let content = skill.instructions;
|
|
16359
|
-
const
|
|
16360
|
-
if (
|
|
16505
|
+
const m = skill.manifest;
|
|
16506
|
+
if (m && typeof m === "object") {
|
|
16361
16507
|
const fm = ["---"];
|
|
16362
|
-
if (
|
|
16363
|
-
fm.push(`description: "${
|
|
16364
|
-
if (
|
|
16365
|
-
fm.push(`when_to_use: "${
|
|
16366
|
-
if (
|
|
16508
|
+
if (m.description)
|
|
16509
|
+
fm.push(`description: "${m.description}"`);
|
|
16510
|
+
if (m.when_to_use)
|
|
16511
|
+
fm.push(`when_to_use: "${m.when_to_use}"`);
|
|
16512
|
+
if (Array.isArray(m.allowed_tools) && m.allowed_tools.length) {
|
|
16367
16513
|
fm.push(`allowed-tools:
|
|
16368
|
-
${
|
|
16514
|
+
${m.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
16369
16515
|
`)}`);
|
|
16370
|
-
|
|
16371
|
-
|
|
16372
|
-
|
|
16373
|
-
|
|
16374
|
-
|
|
16375
|
-
|
|
16376
|
-
|
|
16516
|
+
}
|
|
16517
|
+
if (m.model)
|
|
16518
|
+
fm.push(`model: ${m.model}`);
|
|
16519
|
+
if (m.context)
|
|
16520
|
+
fm.push(`context: ${m.context}`);
|
|
16521
|
+
if (m.agent)
|
|
16522
|
+
fm.push(`agent: ${m.agent}`);
|
|
16523
|
+
if (m.user_invocable === false)
|
|
16377
16524
|
fm.push(`user-invocable: false`);
|
|
16378
|
-
if (
|
|
16379
|
-
fm.push(`argument-hint: "${
|
|
16525
|
+
if (m.argument_hint)
|
|
16526
|
+
fm.push(`argument-hint: "${m.argument_hint}"`);
|
|
16380
16527
|
fm.push(`---
|
|
16381
16528
|
`);
|
|
16382
16529
|
if (fm.length > 3)
|
|
16383
16530
|
content = fm.join(`
|
|
16384
16531
|
`) + content;
|
|
16385
|
-
if (
|
|
16386
|
-
const agentType =
|
|
16387
|
-
const modelHint =
|
|
16388
|
-
const toolsHint =
|
|
16389
|
-
Only use these tools: ${
|
|
16532
|
+
if (m.context === "fork") {
|
|
16533
|
+
const agentType = m.agent || "general-purpose";
|
|
16534
|
+
const modelHint = m.model ? `, model: "${m.model}"` : "";
|
|
16535
|
+
const toolsHint = m.allowed_tools?.length ? `
|
|
16536
|
+
Only use these tools: ${m.allowed_tools.join(", ")}.` : "";
|
|
16390
16537
|
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
16538
|
|
|
16392
16539
|
` + content;
|
|
@@ -16394,273 +16541,127 @@ Only use these tools: ${manifest.allowed_tools.join(", ")}.` : "";
|
|
|
16394
16541
|
}
|
|
16395
16542
|
return {
|
|
16396
16543
|
description: skill.description,
|
|
16397
|
-
messages: [
|
|
16398
|
-
{
|
|
16399
|
-
role: "user",
|
|
16400
|
-
content: { type: "text", text: content }
|
|
16401
|
-
}
|
|
16402
|
-
]
|
|
16544
|
+
messages: [{ role: "user", content: { type: "text", text: content } }]
|
|
16403
16545
|
};
|
|
16404
16546
|
});
|
|
16405
16547
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
16406
|
-
|
|
16407
|
-
|
|
16548
|
+
try {
|
|
16549
|
+
const { body } = await daemonGet("/v1/skills");
|
|
16550
|
+
const skills = body?.skills ?? [];
|
|
16551
|
+
return {
|
|
16552
|
+
resources: skills.map((s) => ({
|
|
16553
|
+
uri: `skill://claudemesh/${encodeURIComponent(s.name)}`,
|
|
16554
|
+
name: s.name,
|
|
16555
|
+
description: s.description,
|
|
16556
|
+
mimeType: "text/markdown"
|
|
16557
|
+
}))
|
|
16558
|
+
};
|
|
16559
|
+
} catch {
|
|
16408
16560
|
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
|
-
};
|
|
16561
|
+
}
|
|
16418
16562
|
});
|
|
16419
16563
|
server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
|
|
16420
|
-
const
|
|
16421
|
-
const
|
|
16422
|
-
if (!
|
|
16564
|
+
const uri = req.params.uri;
|
|
16565
|
+
const m = uri.match(/^skill:\/\/claudemesh\/(.+)$/);
|
|
16566
|
+
if (!m)
|
|
16423
16567
|
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)
|
|
16568
|
+
const name = decodeURIComponent(m[1]);
|
|
16569
|
+
const { status, body } = await daemonGet(`/v1/skills/${encodeURIComponent(name)}`);
|
|
16570
|
+
if (status === 404)
|
|
16430
16571
|
throw new Error(`Skill "${name}" not found`);
|
|
16431
|
-
|
|
16432
|
-
|
|
16433
|
-
|
|
16434
|
-
|
|
16435
|
-
|
|
16436
|
-
|
|
16437
|
-
if (
|
|
16438
|
-
|
|
16439
|
-
|
|
16440
|
-
|
|
16441
|
-
|
|
16442
|
-
|
|
16572
|
+
if (status !== 200)
|
|
16573
|
+
throw new Error(`daemon returned ${status} fetching skill`);
|
|
16574
|
+
const skill = body.skill;
|
|
16575
|
+
const fm = ["---"];
|
|
16576
|
+
fm.push(`name: ${skill.name}`);
|
|
16577
|
+
fm.push(`description: "${skill.description}"`);
|
|
16578
|
+
if (skill.tags?.length)
|
|
16579
|
+
fm.push(`tags: [${skill.tags.join(", ")}]`);
|
|
16580
|
+
const mf = skill.manifest;
|
|
16581
|
+
if (mf && typeof mf === "object") {
|
|
16582
|
+
if (mf.when_to_use)
|
|
16583
|
+
fm.push(`when_to_use: "${mf.when_to_use}"`);
|
|
16584
|
+
if (Array.isArray(mf.allowed_tools) && mf.allowed_tools.length) {
|
|
16585
|
+
fm.push(`allowed-tools:
|
|
16586
|
+
${mf.allowed_tools.map((t) => ` - ${t}`).join(`
|
|
16443
16587
|
`)}`);
|
|
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(`---
|
|
16588
|
+
}
|
|
16589
|
+
if (mf.model)
|
|
16590
|
+
fm.push(`model: ${mf.model}`);
|
|
16591
|
+
if (mf.context)
|
|
16592
|
+
fm.push(`context: ${mf.context}`);
|
|
16593
|
+
}
|
|
16594
|
+
fm.push(`---
|
|
16456
16595
|
`);
|
|
16457
|
-
|
|
16458
|
-
`) + skill.instructions;
|
|
16459
|
-
return {
|
|
16460
|
-
contents: [
|
|
16461
|
-
{
|
|
16462
|
-
uri,
|
|
16463
|
-
mimeType: "text/markdown",
|
|
16464
|
-
text: fullContent
|
|
16465
|
-
}
|
|
16466
|
-
]
|
|
16467
|
-
};
|
|
16596
|
+
return { contents: [{ uri, mimeType: "text/markdown", text: fm.join(`
|
|
16597
|
+
`) + skill.instructions }] };
|
|
16468
16598
|
});
|
|
16469
|
-
const
|
|
16470
|
-
|
|
16471
|
-
|
|
16472
|
-
|
|
16473
|
-
|
|
16474
|
-
|
|
16475
|
-
const
|
|
16476
|
-
|
|
16477
|
-
|
|
16478
|
-
|
|
16479
|
-
|
|
16480
|
-
|
|
16481
|
-
|
|
16482
|
-
|
|
16483
|
-
|
|
16484
|
-
|
|
16485
|
-
|
|
16486
|
-
|
|
16487
|
-
|
|
16488
|
-
|
|
16489
|
-
|
|
16490
|
-
|
|
16491
|
-
|
|
16492
|
-
|
|
16493
|
-
|
|
16494
|
-
|
|
16495
|
-
|
|
16496
|
-
} else if (eventName === "peer_joined") {
|
|
16497
|
-
content2 = `[system] Peer "${data.name ?? "unknown"}" joined the mesh`;
|
|
16498
|
-
} else if (eventName === "peer_returned") {
|
|
16499
|
-
const peerName = String(data.name ?? "unknown");
|
|
16500
|
-
const lastSeenAt = data.lastSeenAt ? relativeTime(String(data.lastSeenAt)) : "unknown";
|
|
16501
|
-
const groups = Array.isArray(data.groups) ? data.groups.map((g) => g.role ? `@${g.name}:${g.role}` : `@${g.name}`).join(", ") : "";
|
|
16502
|
-
const summary = data.summary ? ` Summary: "${data.summary}"` : "";
|
|
16503
|
-
content2 = `[system] Welcome back, "${peerName}"! Last seen ${lastSeenAt}.${groups ? ` Restored: ${groups}` : ""}${summary}`;
|
|
16504
|
-
} else if (eventName === "peer_left") {
|
|
16505
|
-
content2 = `[system] Peer "${data.name ?? "unknown"}" left the mesh`;
|
|
16506
|
-
} else if (eventName === "mcp_registered") {
|
|
16507
|
-
const tools = Array.isArray(data.tools) ? data.tools.join(", ") : "";
|
|
16508
|
-
content2 = `[system] New MCP server available: "${data.serverName}" (hosted by ${data.hostedBy}). Tools: ${tools}. Use mesh_tool_call to invoke.`;
|
|
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
|
-
}
|
|
16599
|
+
const sub = subscribeEvents(async (ev) => {
|
|
16600
|
+
if (ev.kind === "message") {
|
|
16601
|
+
const d = ev.data;
|
|
16602
|
+
const fromName = String(d.sender_name ?? "unknown");
|
|
16603
|
+
const fromMember = String(d.sender_member_pubkey ?? d.sender_pubkey ?? "");
|
|
16604
|
+
const body = String(d.body ?? "(decrypt failed)");
|
|
16605
|
+
const priority = String(d.priority ?? "next");
|
|
16606
|
+
const prioBadge = priority === "now" ? "[URGENT] " : priority === "low" ? "[low] " : "";
|
|
16607
|
+
const topicTag = d.topic ? ` (#${d.topic})` : "";
|
|
16608
|
+
const content = `${prioBadge}${fromName}${topicTag}: ${body}`;
|
|
16609
|
+
try {
|
|
16610
|
+
await server.notification({
|
|
16611
|
+
method: "notifications/claude/channel",
|
|
16612
|
+
params: {
|
|
16613
|
+
content,
|
|
16614
|
+
meta: {
|
|
16615
|
+
from_id: fromMember,
|
|
16616
|
+
from_pubkey: fromMember,
|
|
16617
|
+
from_session_pubkey: String(d.sender_pubkey ?? ""),
|
|
16618
|
+
from_name: fromName,
|
|
16619
|
+
mesh_slug: String(d.mesh ?? ""),
|
|
16620
|
+
priority,
|
|
16621
|
+
message_id: String(d.broker_message_id ?? d.id ?? ""),
|
|
16622
|
+
client_message_id: String(d.client_message_id ?? ""),
|
|
16623
|
+
...d.topic ? { topic: String(d.topic) } : {},
|
|
16624
|
+
...d.reply_to_id ? { reply_to_id: String(d.reply_to_id) } : {},
|
|
16625
|
+
...d.subtype ? { subtype: String(d.subtype) } : {}
|
|
16598
16626
|
}
|
|
16599
|
-
}
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
process.stderr.write(`[claudemesh] push FAILED: ${pushErr}
|
|
16627
|
+
}
|
|
16628
|
+
});
|
|
16629
|
+
} catch (err) {
|
|
16630
|
+
process.stderr.write(`[claudemesh-mcp] channel emit failed: ${err}
|
|
16604
16631
|
`);
|
|
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;
|
|
16632
|
+
}
|
|
16633
|
+
} else if (ev.kind === "peer_join" || ev.kind === "peer_leave" || ev.kind === "system") {
|
|
16634
|
+
const d = ev.data;
|
|
16635
|
+
const eventName = String(d.event ?? ev.kind);
|
|
16636
|
+
let content;
|
|
16637
|
+
if (ev.kind === "peer_join") {
|
|
16638
|
+
content = `[system] Peer "${String(d.name ?? "unknown")}" joined the mesh`;
|
|
16639
|
+
} else if (ev.kind === "peer_leave") {
|
|
16640
|
+
content = `[system] Peer "${String(d.name ?? "unknown")}" left the mesh`;
|
|
16641
|
+
} else {
|
|
16642
|
+
content = `[system] ${eventName}: ${JSON.stringify(d).slice(0, 240)}`;
|
|
16643
|
+
}
|
|
16642
16644
|
try {
|
|
16643
|
-
const peers = await welcomeClient.listPeers();
|
|
16644
|
-
const peerNames = peers.filter((p) => p.displayName !== myName).map((p) => p.displayName).join(", ") || "none";
|
|
16645
16645
|
await server.notification({
|
|
16646
16646
|
method: "notifications/claude/channel",
|
|
16647
16647
|
params: {
|
|
16648
|
-
content
|
|
16649
|
-
meta: {
|
|
16648
|
+
content,
|
|
16649
|
+
meta: {
|
|
16650
|
+
kind: "system",
|
|
16651
|
+
event: eventName,
|
|
16652
|
+
mesh_slug: String(d.mesh ?? "")
|
|
16653
|
+
}
|
|
16650
16654
|
}
|
|
16651
16655
|
});
|
|
16652
16656
|
} catch {}
|
|
16653
|
-
}
|
|
16654
|
-
}
|
|
16657
|
+
}
|
|
16658
|
+
});
|
|
16659
|
+
const transport = new StdioServerTransport;
|
|
16660
|
+
await server.connect(transport);
|
|
16655
16661
|
const keepalive = setInterval(() => {}, 1000);
|
|
16656
16662
|
const shutdown = () => {
|
|
16657
16663
|
clearInterval(keepalive);
|
|
16658
|
-
|
|
16659
|
-
try {
|
|
16660
|
-
b.stop();
|
|
16661
|
-
} catch {}
|
|
16662
|
-
}
|
|
16663
|
-
stopAll();
|
|
16664
|
+
sub.close();
|
|
16664
16665
|
process.exit(0);
|
|
16665
16666
|
};
|
|
16666
16667
|
process.on("SIGTERM", shutdown);
|
|
@@ -16691,9 +16692,8 @@ async function startServiceProxy(serviceName) {
|
|
|
16691
16692
|
tools = fetched;
|
|
16692
16693
|
} catch {
|
|
16693
16694
|
const cached2 = client.serviceCatalog.find((s) => s.name === serviceName);
|
|
16694
|
-
if (cached2)
|
|
16695
|
+
if (cached2)
|
|
16695
16696
|
tools = cached2.tools;
|
|
16696
|
-
}
|
|
16697
16697
|
}
|
|
16698
16698
|
if (tools.length === 0) {
|
|
16699
16699
|
process.stderr.write(`[mesh:${serviceName}] no tools found — service may not be running
|
|
@@ -16718,12 +16718,7 @@ async function startServiceProxy(serviceName) {
|
|
|
16718
16718
|
}
|
|
16719
16719
|
if (client.status !== "open") {
|
|
16720
16720
|
return {
|
|
16721
|
-
content: [
|
|
16722
|
-
{
|
|
16723
|
-
type: "text",
|
|
16724
|
-
text: `Service temporarily unavailable — broker reconnecting. Retry in a few seconds.`
|
|
16725
|
-
}
|
|
16726
|
-
],
|
|
16721
|
+
content: [{ type: "text", text: "Service temporarily unavailable — broker reconnecting. Retry in a few seconds." }],
|
|
16727
16722
|
isError: true
|
|
16728
16723
|
};
|
|
16729
16724
|
}
|
|
@@ -16731,23 +16726,13 @@ async function startServiceProxy(serviceName) {
|
|
|
16731
16726
|
try {
|
|
16732
16727
|
const result = await client.mcpCall(serviceName, toolName, args);
|
|
16733
16728
|
if (result.error) {
|
|
16734
|
-
return {
|
|
16735
|
-
content: [{ type: "text", text: `Error: ${result.error}` }],
|
|
16736
|
-
isError: true
|
|
16737
|
-
};
|
|
16729
|
+
return { content: [{ type: "text", text: `Error: ${result.error}` }], isError: true };
|
|
16738
16730
|
}
|
|
16739
16731
|
const resultText = typeof result.result === "string" ? result.result : JSON.stringify(result.result, null, 2);
|
|
16740
|
-
return {
|
|
16741
|
-
content: [{ type: "text", text: resultText }]
|
|
16742
|
-
};
|
|
16732
|
+
return { content: [{ type: "text", text: resultText }] };
|
|
16743
16733
|
} catch (e) {
|
|
16744
16734
|
return {
|
|
16745
|
-
content: [
|
|
16746
|
-
{
|
|
16747
|
-
type: "text",
|
|
16748
|
-
text: `Call failed: ${e instanceof Error ? e.message : String(e)}`
|
|
16749
|
-
}
|
|
16750
|
-
],
|
|
16735
|
+
content: [{ type: "text", text: `Call failed: ${e instanceof Error ? e.message : String(e)}` }],
|
|
16751
16736
|
isError: true
|
|
16752
16737
|
};
|
|
16753
16738
|
}
|
|
@@ -16763,9 +16748,7 @@ async function startServiceProxy(serviceName) {
|
|
|
16763
16748
|
const newTools = push.eventData?.tools;
|
|
16764
16749
|
if (Array.isArray(newTools)) {
|
|
16765
16750
|
tools = newTools;
|
|
16766
|
-
server.notification({
|
|
16767
|
-
method: "notifications/tools/list_changed"
|
|
16768
|
-
}).catch(() => {});
|
|
16751
|
+
server.notification({ method: "notifications/tools/list_changed" }).catch(() => {});
|
|
16769
16752
|
}
|
|
16770
16753
|
}
|
|
16771
16754
|
});
|
|
@@ -16780,13 +16763,12 @@ async function startServiceProxy(serviceName) {
|
|
|
16780
16763
|
process.on("SIGTERM", shutdown);
|
|
16781
16764
|
process.on("SIGINT", shutdown);
|
|
16782
16765
|
}
|
|
16783
|
-
var
|
|
16784
|
-
var
|
|
16785
|
-
|
|
16766
|
+
var DAEMON_BOOT_RETRIES = 4, DAEMON_BOOT_RETRY_MS = 500;
|
|
16767
|
+
var init_server2 = __esm(() => {
|
|
16768
|
+
init_paths2();
|
|
16769
|
+
init_urls();
|
|
16786
16770
|
init_facade();
|
|
16787
16771
|
init_facade8();
|
|
16788
|
-
init_server2();
|
|
16789
|
-
peerNameCache = new Map;
|
|
16790
16772
|
});
|
|
16791
16773
|
|
|
16792
16774
|
// src/commands/mcp.ts
|
|
@@ -16801,7 +16783,7 @@ async function runMcp() {
|
|
|
16801
16783
|
process.exit(0);
|
|
16802
16784
|
}
|
|
16803
16785
|
var init_mcp = __esm(() => {
|
|
16804
|
-
|
|
16786
|
+
init_server2();
|
|
16805
16787
|
});
|
|
16806
16788
|
|
|
16807
16789
|
// src/commands/hook.ts
|
|
@@ -18612,4 +18594,4 @@ main().catch((err) => {
|
|
|
18612
18594
|
process.exit(EXIT.INTERNAL_ERROR);
|
|
18613
18595
|
});
|
|
18614
18596
|
|
|
18615
|
-
//# debugId=
|
|
18597
|
+
//# debugId=EC115E4068A7B5F664756E2164756E21
|