claudemesh-cli 1.24.0 → 1.26.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 +480 -194
- package/dist/entrypoints/cli.js.map +11 -11
- package/dist/entrypoints/mcp.js +8 -2
- package/dist/entrypoints/mcp.js.map +2 -2
- package/package.json +1 -1
- package/skills/claudemesh/SKILL.md +7 -3
package/dist/entrypoints/cli.js
CHANGED
|
@@ -103,7 +103,7 @@ __export(exports_urls, {
|
|
|
103
103
|
VERSION: () => VERSION,
|
|
104
104
|
URLS: () => URLS
|
|
105
105
|
});
|
|
106
|
-
var URLS, VERSION = "1.
|
|
106
|
+
var URLS, VERSION = "1.26.0", env;
|
|
107
107
|
var init_urls = __esm(() => {
|
|
108
108
|
URLS = {
|
|
109
109
|
BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
|
|
@@ -1026,6 +1026,12 @@ var init_file_crypto = __esm(() => {
|
|
|
1026
1026
|
});
|
|
1027
1027
|
|
|
1028
1028
|
// src/services/crypto/box.ts
|
|
1029
|
+
var exports_box = {};
|
|
1030
|
+
__export(exports_box, {
|
|
1031
|
+
isDirectTarget: () => isDirectTarget,
|
|
1032
|
+
encryptDirect: () => encryptDirect,
|
|
1033
|
+
decryptDirect: () => decryptDirect
|
|
1034
|
+
});
|
|
1029
1035
|
function isDirectTarget(targetSpec) {
|
|
1030
1036
|
return HEX_PUBKEY.test(targetSpec);
|
|
1031
1037
|
}
|
|
@@ -6790,105 +6796,6 @@ var init_client3 = __esm(() => {
|
|
|
6790
6796
|
init_protocol();
|
|
6791
6797
|
});
|
|
6792
6798
|
|
|
6793
|
-
// src/commands/peers.ts
|
|
6794
|
-
var exports_peers = {};
|
|
6795
|
-
__export(exports_peers, {
|
|
6796
|
-
runPeers: () => runPeers
|
|
6797
|
-
});
|
|
6798
|
-
function projectFields(record, fields) {
|
|
6799
|
-
const out = {};
|
|
6800
|
-
for (const f of fields) {
|
|
6801
|
-
const sourceKey = FIELD_ALIAS[f] ?? f;
|
|
6802
|
-
out[f] = record[sourceKey];
|
|
6803
|
-
}
|
|
6804
|
-
return out;
|
|
6805
|
-
}
|
|
6806
|
-
async function listPeersForMesh(slug) {
|
|
6807
|
-
const config = readConfig();
|
|
6808
|
-
const joined = config.meshes.find((m) => m.slug === slug);
|
|
6809
|
-
const selfMemberPubkey = joined?.pubkey ?? null;
|
|
6810
|
-
const bridged = await tryBridge(slug, "peers");
|
|
6811
|
-
if (bridged && bridged.ok) {
|
|
6812
|
-
const peers = bridged.result;
|
|
6813
|
-
return peers.map((p) => annotateSelf(p, selfMemberPubkey, null));
|
|
6814
|
-
}
|
|
6815
|
-
let result = [];
|
|
6816
|
-
await withMesh({ meshSlug: slug }, async (client) => {
|
|
6817
|
-
const all = await client.listPeers();
|
|
6818
|
-
const selfSessionPubkey = client.getSessionPubkey();
|
|
6819
|
-
result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
|
|
6820
|
-
});
|
|
6821
|
-
return result;
|
|
6822
|
-
}
|
|
6823
|
-
function annotateSelf(peer, selfMemberPubkey, selfSessionPubkey) {
|
|
6824
|
-
const isSelf = !!(selfMemberPubkey && peer.memberPubkey && peer.memberPubkey === selfMemberPubkey);
|
|
6825
|
-
const isThisSession = !!(isSelf && selfSessionPubkey && peer.pubkey === selfSessionPubkey);
|
|
6826
|
-
return { ...peer, isSelf, isThisSession };
|
|
6827
|
-
}
|
|
6828
|
-
async function runPeers(flags) {
|
|
6829
|
-
const config = readConfig();
|
|
6830
|
-
const slugs = flags.mesh ? [flags.mesh] : config.meshes.map((m) => m.slug);
|
|
6831
|
-
if (slugs.length === 0) {
|
|
6832
|
-
render.err("No meshes joined.");
|
|
6833
|
-
render.hint("claudemesh <invite-url> # join + launch");
|
|
6834
|
-
process.exit(1);
|
|
6835
|
-
}
|
|
6836
|
-
const fieldList = typeof flags.json === "string" && flags.json.length > 0 ? flags.json.split(",").map((s) => s.trim()).filter(Boolean) : null;
|
|
6837
|
-
const wantsJson = flags.json !== undefined && flags.json !== false;
|
|
6838
|
-
const allJson = [];
|
|
6839
|
-
for (const slug of slugs) {
|
|
6840
|
-
try {
|
|
6841
|
-
const peers = await listPeersForMesh(slug);
|
|
6842
|
-
if (wantsJson) {
|
|
6843
|
-
const projected = fieldList ? peers.map((p) => projectFields(p, fieldList)) : peers;
|
|
6844
|
-
allJson.push({ mesh: slug, peers: projected });
|
|
6845
|
-
continue;
|
|
6846
|
-
}
|
|
6847
|
-
render.section(`peers on ${slug} (${peers.length})`);
|
|
6848
|
-
if (peers.length === 0) {
|
|
6849
|
-
render.info(dim(" (no peers connected)"));
|
|
6850
|
-
continue;
|
|
6851
|
-
}
|
|
6852
|
-
for (const p of peers) {
|
|
6853
|
-
const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
|
|
6854
|
-
const statusDot = p.status === "working" ? yellow("●") : green("●");
|
|
6855
|
-
const name = bold(p.displayName);
|
|
6856
|
-
const meta = [];
|
|
6857
|
-
if (p.peerType)
|
|
6858
|
-
meta.push(p.peerType);
|
|
6859
|
-
if (p.channel)
|
|
6860
|
-
meta.push(p.channel);
|
|
6861
|
-
if (p.model)
|
|
6862
|
-
meta.push(p.model);
|
|
6863
|
-
const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
|
|
6864
|
-
const summary = p.summary ? dim(` — ${p.summary}`) : "";
|
|
6865
|
-
const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
|
|
6866
|
-
const selfTag = p.isThisSession ? dim(" ") + yellow("(this session)") : p.isSelf ? dim(" ") + yellow("(your other session)") : "";
|
|
6867
|
-
render.info(`${statusDot} ${name}${selfTag}${groups}${metaStr}${pubkeyTag}${summary}`);
|
|
6868
|
-
if (p.cwd)
|
|
6869
|
-
render.info(dim(` cwd: ${p.cwd}`));
|
|
6870
|
-
}
|
|
6871
|
-
} catch (e) {
|
|
6872
|
-
render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
|
|
6873
|
-
}
|
|
6874
|
-
}
|
|
6875
|
-
if (wantsJson) {
|
|
6876
|
-
process.stdout.write(JSON.stringify(slugs.length === 1 ? allJson[0]?.peers : allJson, null, 2) + `
|
|
6877
|
-
`);
|
|
6878
|
-
}
|
|
6879
|
-
}
|
|
6880
|
-
var FIELD_ALIAS;
|
|
6881
|
-
var init_peers = __esm(() => {
|
|
6882
|
-
init_connect();
|
|
6883
|
-
init_facade();
|
|
6884
|
-
init_client3();
|
|
6885
|
-
init_render();
|
|
6886
|
-
init_styles();
|
|
6887
|
-
FIELD_ALIAS = {
|
|
6888
|
-
name: "displayName"
|
|
6889
|
-
};
|
|
6890
|
-
});
|
|
6891
|
-
|
|
6892
6799
|
// src/daemon/local-token.ts
|
|
6893
6800
|
import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6894
6801
|
import { dirname as dirname3 } from "node:path";
|
|
@@ -6970,7 +6877,61 @@ var init_client4 = __esm(() => {
|
|
|
6970
6877
|
});
|
|
6971
6878
|
|
|
6972
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
|
+
});
|
|
6973
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
|
+
}
|
|
6974
6935
|
async function trySendViaDaemon(args) {
|
|
6975
6936
|
if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
|
|
6976
6937
|
return null;
|
|
@@ -6983,7 +6944,8 @@ async function trySendViaDaemon(args) {
|
|
|
6983
6944
|
to: args.to,
|
|
6984
6945
|
message: args.message,
|
|
6985
6946
|
priority: args.priority,
|
|
6986
|
-
...args.idempotencyKey ? { client_message_id: args.idempotencyKey } : {}
|
|
6947
|
+
...args.idempotencyKey ? { client_message_id: args.idempotencyKey } : {},
|
|
6948
|
+
...args.expectedMesh ? { mesh: args.expectedMesh } : {}
|
|
6987
6949
|
}
|
|
6988
6950
|
});
|
|
6989
6951
|
if (res.status === 202 || res.status === 200) {
|
|
@@ -7007,6 +6969,112 @@ var init_daemon_route = __esm(() => {
|
|
|
7007
6969
|
init_paths2();
|
|
7008
6970
|
});
|
|
7009
6971
|
|
|
6972
|
+
// src/commands/peers.ts
|
|
6973
|
+
var exports_peers = {};
|
|
6974
|
+
__export(exports_peers, {
|
|
6975
|
+
runPeers: () => runPeers
|
|
6976
|
+
});
|
|
6977
|
+
function projectFields(record, fields) {
|
|
6978
|
+
const out = {};
|
|
6979
|
+
for (const f of fields) {
|
|
6980
|
+
const sourceKey = FIELD_ALIAS[f] ?? f;
|
|
6981
|
+
out[f] = record[sourceKey];
|
|
6982
|
+
}
|
|
6983
|
+
return out;
|
|
6984
|
+
}
|
|
6985
|
+
async function listPeersForMesh(slug) {
|
|
6986
|
+
const config = readConfig();
|
|
6987
|
+
const joined = config.meshes.find((m) => m.slug === slug);
|
|
6988
|
+
const selfMemberPubkey = joined?.pubkey ?? null;
|
|
6989
|
+
try {
|
|
6990
|
+
const { tryListPeersViaDaemon: tryListPeersViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
|
|
6991
|
+
const dr = await tryListPeersViaDaemon2();
|
|
6992
|
+
if (dr !== null) {
|
|
6993
|
+
return dr.map((p) => annotateSelf(p, selfMemberPubkey, null));
|
|
6994
|
+
}
|
|
6995
|
+
} catch {}
|
|
6996
|
+
const bridged = await tryBridge(slug, "peers");
|
|
6997
|
+
if (bridged && bridged.ok) {
|
|
6998
|
+
const peers = bridged.result;
|
|
6999
|
+
return peers.map((p) => annotateSelf(p, selfMemberPubkey, null));
|
|
7000
|
+
}
|
|
7001
|
+
let result = [];
|
|
7002
|
+
await withMesh({ meshSlug: slug }, async (client) => {
|
|
7003
|
+
const all = await client.listPeers();
|
|
7004
|
+
const selfSessionPubkey = client.getSessionPubkey();
|
|
7005
|
+
result = all.map((p) => annotateSelf(p, selfMemberPubkey, selfSessionPubkey));
|
|
7006
|
+
});
|
|
7007
|
+
return result;
|
|
7008
|
+
}
|
|
7009
|
+
function annotateSelf(peer, selfMemberPubkey, selfSessionPubkey) {
|
|
7010
|
+
const isSelf = !!(selfMemberPubkey && peer.memberPubkey && peer.memberPubkey === selfMemberPubkey);
|
|
7011
|
+
const isThisSession = !!(isSelf && selfSessionPubkey && peer.pubkey === selfSessionPubkey);
|
|
7012
|
+
return { ...peer, isSelf, isThisSession };
|
|
7013
|
+
}
|
|
7014
|
+
async function runPeers(flags) {
|
|
7015
|
+
const config = readConfig();
|
|
7016
|
+
const slugs = flags.mesh ? [flags.mesh] : config.meshes.map((m) => m.slug);
|
|
7017
|
+
if (slugs.length === 0) {
|
|
7018
|
+
render.err("No meshes joined.");
|
|
7019
|
+
render.hint("claudemesh <invite-url> # join + launch");
|
|
7020
|
+
process.exit(1);
|
|
7021
|
+
}
|
|
7022
|
+
const fieldList = typeof flags.json === "string" && flags.json.length > 0 ? flags.json.split(",").map((s) => s.trim()).filter(Boolean) : null;
|
|
7023
|
+
const wantsJson = flags.json !== undefined && flags.json !== false;
|
|
7024
|
+
const allJson = [];
|
|
7025
|
+
for (const slug of slugs) {
|
|
7026
|
+
try {
|
|
7027
|
+
const peers = await listPeersForMesh(slug);
|
|
7028
|
+
if (wantsJson) {
|
|
7029
|
+
const projected = fieldList ? peers.map((p) => projectFields(p, fieldList)) : peers;
|
|
7030
|
+
allJson.push({ mesh: slug, peers: projected });
|
|
7031
|
+
continue;
|
|
7032
|
+
}
|
|
7033
|
+
render.section(`peers on ${slug} (${peers.length})`);
|
|
7034
|
+
if (peers.length === 0) {
|
|
7035
|
+
render.info(dim(" (no peers connected)"));
|
|
7036
|
+
continue;
|
|
7037
|
+
}
|
|
7038
|
+
for (const p of peers) {
|
|
7039
|
+
const groups = p.groups.length ? " [" + p.groups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`).join(", ") + "]" : "";
|
|
7040
|
+
const statusDot = p.status === "working" ? yellow("●") : green("●");
|
|
7041
|
+
const name = bold(p.displayName);
|
|
7042
|
+
const meta = [];
|
|
7043
|
+
if (p.peerType)
|
|
7044
|
+
meta.push(p.peerType);
|
|
7045
|
+
if (p.channel)
|
|
7046
|
+
meta.push(p.channel);
|
|
7047
|
+
if (p.model)
|
|
7048
|
+
meta.push(p.model);
|
|
7049
|
+
const metaStr = meta.length ? dim(` (${meta.join(", ")})`) : "";
|
|
7050
|
+
const summary = p.summary ? dim(` — ${p.summary}`) : "";
|
|
7051
|
+
const pubkeyTag = dim(` · ${p.pubkey.slice(0, 16)}…`);
|
|
7052
|
+
const selfTag = p.isThisSession ? dim(" ") + yellow("(this session)") : p.isSelf ? dim(" ") + yellow("(your other session)") : "";
|
|
7053
|
+
render.info(`${statusDot} ${name}${selfTag}${groups}${metaStr}${pubkeyTag}${summary}`);
|
|
7054
|
+
if (p.cwd)
|
|
7055
|
+
render.info(dim(` cwd: ${p.cwd}`));
|
|
7056
|
+
}
|
|
7057
|
+
} catch (e) {
|
|
7058
|
+
render.err(`${slug}: ${e instanceof Error ? e.message : String(e)}`);
|
|
7059
|
+
}
|
|
7060
|
+
}
|
|
7061
|
+
if (wantsJson) {
|
|
7062
|
+
process.stdout.write(JSON.stringify(slugs.length === 1 ? allJson[0]?.peers : allJson, null, 2) + `
|
|
7063
|
+
`);
|
|
7064
|
+
}
|
|
7065
|
+
}
|
|
7066
|
+
var FIELD_ALIAS;
|
|
7067
|
+
var init_peers = __esm(() => {
|
|
7068
|
+
init_connect();
|
|
7069
|
+
init_facade();
|
|
7070
|
+
init_client3();
|
|
7071
|
+
init_render();
|
|
7072
|
+
init_styles();
|
|
7073
|
+
FIELD_ALIAS = {
|
|
7074
|
+
name: "displayName"
|
|
7075
|
+
};
|
|
7076
|
+
});
|
|
7077
|
+
|
|
7010
7078
|
// src/commands/send.ts
|
|
7011
7079
|
var exports_send = {};
|
|
7012
7080
|
__export(exports_send, {
|
|
@@ -8441,12 +8509,32 @@ function migrateOutbox(db) {
|
|
|
8441
8509
|
CREATE INDEX IF NOT EXISTS outbox_aborted
|
|
8442
8510
|
ON outbox(status, aborted_at) WHERE status = 'aborted';
|
|
8443
8511
|
`);
|
|
8512
|
+
const hasMesh = columnExists(db, "outbox", "mesh");
|
|
8513
|
+
const hasTargetSpec = columnExists(db, "outbox", "target_spec");
|
|
8514
|
+
const hasNonce = columnExists(db, "outbox", "nonce");
|
|
8515
|
+
const hasCiphertext = columnExists(db, "outbox", "ciphertext");
|
|
8516
|
+
const hasPriority = columnExists(db, "outbox", "priority");
|
|
8517
|
+
if (!hasMesh)
|
|
8518
|
+
db.exec(`ALTER TABLE outbox ADD COLUMN mesh TEXT`);
|
|
8519
|
+
if (!hasTargetSpec)
|
|
8520
|
+
db.exec(`ALTER TABLE outbox ADD COLUMN target_spec TEXT`);
|
|
8521
|
+
if (!hasNonce)
|
|
8522
|
+
db.exec(`ALTER TABLE outbox ADD COLUMN nonce TEXT`);
|
|
8523
|
+
if (!hasCiphertext)
|
|
8524
|
+
db.exec(`ALTER TABLE outbox ADD COLUMN ciphertext TEXT`);
|
|
8525
|
+
if (!hasPriority)
|
|
8526
|
+
db.exec(`ALTER TABLE outbox ADD COLUMN priority TEXT`);
|
|
8527
|
+
}
|
|
8528
|
+
function columnExists(db, table, column) {
|
|
8529
|
+
const rows = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
8530
|
+
return rows.some((r) => r.name === column);
|
|
8444
8531
|
}
|
|
8445
8532
|
function findByClientId(db, clientMessageId) {
|
|
8446
8533
|
const row = db.prepare(`
|
|
8447
8534
|
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
8448
8535
|
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
8449
|
-
broker_message_id, aborted_at, aborted_by, superseded_by
|
|
8536
|
+
broker_message_id, aborted_at, aborted_by, superseded_by,
|
|
8537
|
+
mesh, target_spec, nonce, ciphertext, priority
|
|
8450
8538
|
FROM outbox WHERE client_message_id = ?
|
|
8451
8539
|
`).get(clientMessageId);
|
|
8452
8540
|
return row ?? null;
|
|
@@ -8455,9 +8543,10 @@ function insertPending(db, input) {
|
|
|
8455
8543
|
db.prepare(`
|
|
8456
8544
|
INSERT INTO outbox (
|
|
8457
8545
|
id, client_message_id, request_fingerprint, payload,
|
|
8458
|
-
enqueued_at, attempts, next_attempt_at, status
|
|
8459
|
-
|
|
8460
|
-
|
|
8546
|
+
enqueued_at, attempts, next_attempt_at, status,
|
|
8547
|
+
mesh, target_spec, nonce, ciphertext, priority
|
|
8548
|
+
) VALUES (?, ?, ?, ?, ?, 0, ?, 'pending', ?, ?, ?, ?, ?)
|
|
8549
|
+
`).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);
|
|
8461
8550
|
}
|
|
8462
8551
|
function fingerprintsEqual(a, b) {
|
|
8463
8552
|
if (a.length !== b.length)
|
|
@@ -8477,7 +8566,8 @@ function listOutbox(db, p = {}) {
|
|
|
8477
8566
|
const sql = `
|
|
8478
8567
|
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
8479
8568
|
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
8480
|
-
broker_message_id, aborted_at, aborted_by, superseded_by
|
|
8569
|
+
broker_message_id, aborted_at, aborted_by, superseded_by,
|
|
8570
|
+
mesh, target_spec, nonce, ciphertext, priority
|
|
8481
8571
|
FROM outbox
|
|
8482
8572
|
${where.length ? "WHERE " + where.join(" AND ") : ""}
|
|
8483
8573
|
ORDER BY enqueued_at DESC
|
|
@@ -8490,7 +8580,8 @@ function findById(db, id) {
|
|
|
8490
8580
|
return db.prepare(`
|
|
8491
8581
|
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
8492
8582
|
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
8493
|
-
broker_message_id, aborted_at, aborted_by, superseded_by
|
|
8583
|
+
broker_message_id, aborted_at, aborted_by, superseded_by,
|
|
8584
|
+
mesh, target_spec, nonce, ciphertext, priority
|
|
8494
8585
|
FROM outbox WHERE id = ?
|
|
8495
8586
|
`).get(id) ?? null;
|
|
8496
8587
|
}
|
|
@@ -8633,7 +8724,12 @@ function acceptSend(req, deps) {
|
|
|
8633
8724
|
client_message_id: clientId,
|
|
8634
8725
|
request_fingerprint: fingerprint,
|
|
8635
8726
|
payload: body,
|
|
8636
|
-
now
|
|
8727
|
+
now,
|
|
8728
|
+
mesh: req.mesh,
|
|
8729
|
+
target_spec: req.target_spec,
|
|
8730
|
+
nonce: req.nonce,
|
|
8731
|
+
ciphertext: req.ciphertext,
|
|
8732
|
+
priority: req.priority
|
|
8637
8733
|
});
|
|
8638
8734
|
return { kind: "accepted_pending", status: 202, client_message_id: clientId };
|
|
8639
8735
|
}
|
|
@@ -8813,7 +8909,8 @@ function startIpcServer(opts) {
|
|
|
8813
8909
|
outboxDb: opts.outboxDb,
|
|
8814
8910
|
inboxDb: opts.inboxDb,
|
|
8815
8911
|
bus: opts.bus,
|
|
8816
|
-
|
|
8912
|
+
brokers: opts.brokers,
|
|
8913
|
+
meshConfigs: opts.meshConfigs,
|
|
8817
8914
|
onPendingInserted: opts.onPendingInserted
|
|
8818
8915
|
});
|
|
8819
8916
|
if (existsSync9(DAEMON_PATHS.SOCK_FILE)) {
|
|
@@ -8924,34 +9021,58 @@ function makeHandler(opts) {
|
|
|
8924
9021
|
return;
|
|
8925
9022
|
}
|
|
8926
9023
|
if (req.method === "GET" && url.pathname === "/v1/peers") {
|
|
8927
|
-
if (!opts.
|
|
9024
|
+
if (!opts.brokers || opts.brokers.size === 0) {
|
|
8928
9025
|
respond(res, 503, { error: "broker not initialised" });
|
|
8929
9026
|
return;
|
|
8930
9027
|
}
|
|
9028
|
+
const filterMesh = url.searchParams.get("mesh") ?? undefined;
|
|
8931
9029
|
try {
|
|
8932
|
-
const
|
|
8933
|
-
|
|
9030
|
+
const all = [];
|
|
9031
|
+
for (const [slug, b] of opts.brokers.entries()) {
|
|
9032
|
+
if (filterMesh && filterMesh !== slug)
|
|
9033
|
+
continue;
|
|
9034
|
+
try {
|
|
9035
|
+
const peers = await b.listPeers();
|
|
9036
|
+
for (const p of peers)
|
|
9037
|
+
all.push({ ...p, mesh: slug });
|
|
9038
|
+
} catch (e) {
|
|
9039
|
+
opts.log("warn", "ipc_peers_broker_failed", { mesh: slug, err: String(e) });
|
|
9040
|
+
}
|
|
9041
|
+
}
|
|
9042
|
+
respond(res, 200, { peers: all });
|
|
8934
9043
|
} catch (e) {
|
|
8935
9044
|
respond(res, 502, { error: "broker_unreachable", detail: String(e) });
|
|
8936
9045
|
}
|
|
8937
9046
|
return;
|
|
8938
9047
|
}
|
|
8939
9048
|
if (req.method === "GET" && url.pathname === "/v1/skills") {
|
|
8940
|
-
if (!opts.
|
|
9049
|
+
if (!opts.brokers || opts.brokers.size === 0) {
|
|
8941
9050
|
respond(res, 503, { error: "broker not initialised" });
|
|
8942
9051
|
return;
|
|
8943
9052
|
}
|
|
8944
9053
|
const query = url.searchParams.get("query") ?? undefined;
|
|
9054
|
+
const filterMesh = url.searchParams.get("mesh") ?? undefined;
|
|
8945
9055
|
try {
|
|
8946
|
-
const
|
|
8947
|
-
|
|
9056
|
+
const all = [];
|
|
9057
|
+
for (const [slug, b] of opts.brokers.entries()) {
|
|
9058
|
+
if (filterMesh && filterMesh !== slug)
|
|
9059
|
+
continue;
|
|
9060
|
+
try {
|
|
9061
|
+
const skills = await b.listSkills(query);
|
|
9062
|
+
for (const s of skills)
|
|
9063
|
+
all.push({ ...s, mesh: slug });
|
|
9064
|
+
} catch (e) {
|
|
9065
|
+
opts.log("warn", "ipc_skills_broker_failed", { mesh: slug, err: String(e) });
|
|
9066
|
+
}
|
|
9067
|
+
}
|
|
9068
|
+
respond(res, 200, { skills: all });
|
|
8948
9069
|
} catch (e) {
|
|
8949
9070
|
respond(res, 502, { error: "broker_unreachable", detail: String(e) });
|
|
8950
9071
|
}
|
|
8951
9072
|
return;
|
|
8952
9073
|
}
|
|
8953
9074
|
if (req.method === "GET" && url.pathname.startsWith("/v1/skills/")) {
|
|
8954
|
-
if (!opts.
|
|
9075
|
+
if (!opts.brokers || opts.brokers.size === 0) {
|
|
8955
9076
|
respond(res, 503, { error: "broker not initialised" });
|
|
8956
9077
|
return;
|
|
8957
9078
|
}
|
|
@@ -8960,20 +9081,25 @@ function makeHandler(opts) {
|
|
|
8960
9081
|
respond(res, 400, { error: "missing skill name" });
|
|
8961
9082
|
return;
|
|
8962
9083
|
}
|
|
9084
|
+
const filterMesh = url.searchParams.get("mesh") ?? undefined;
|
|
8963
9085
|
try {
|
|
8964
|
-
const
|
|
8965
|
-
|
|
8966
|
-
|
|
8967
|
-
|
|
9086
|
+
for (const [slug, b] of opts.brokers.entries()) {
|
|
9087
|
+
if (filterMesh && filterMesh !== slug)
|
|
9088
|
+
continue;
|
|
9089
|
+
const skill = await b.getSkill(name).catch(() => null);
|
|
9090
|
+
if (skill) {
|
|
9091
|
+
respond(res, 200, { skill: { ...skill, mesh: slug } });
|
|
9092
|
+
return;
|
|
9093
|
+
}
|
|
8968
9094
|
}
|
|
8969
|
-
respond(res,
|
|
9095
|
+
respond(res, 404, { error: "skill_not_found", name });
|
|
8970
9096
|
} catch (e) {
|
|
8971
9097
|
respond(res, 502, { error: "broker_unreachable", detail: String(e) });
|
|
8972
9098
|
}
|
|
8973
9099
|
return;
|
|
8974
9100
|
}
|
|
8975
9101
|
if (req.method === "POST" && url.pathname === "/v1/profile") {
|
|
8976
|
-
if (!opts.
|
|
9102
|
+
if (!opts.brokers || opts.brokers.size === 0) {
|
|
8977
9103
|
respond(res, 503, { error: "broker not initialised" });
|
|
8978
9104
|
return;
|
|
8979
9105
|
}
|
|
@@ -8983,26 +9109,34 @@ function makeHandler(opts) {
|
|
|
8983
9109
|
respond(res, 400, { error: "expected JSON object" });
|
|
8984
9110
|
return;
|
|
8985
9111
|
}
|
|
9112
|
+
const requested = (typeof body.mesh === "string" ? body.mesh : url.searchParams.get("mesh")) || null;
|
|
9113
|
+
const targets = requested ? [opts.brokers.get(requested)].filter(Boolean) : [...opts.brokers.values()];
|
|
9114
|
+
if (targets.length === 0) {
|
|
9115
|
+
respond(res, 404, { error: "mesh_not_attached", mesh: requested });
|
|
9116
|
+
return;
|
|
9117
|
+
}
|
|
8986
9118
|
const updates = {};
|
|
8987
|
-
|
|
8988
|
-
|
|
8989
|
-
|
|
8990
|
-
|
|
8991
|
-
|
|
8992
|
-
|
|
8993
|
-
|
|
8994
|
-
|
|
8995
|
-
|
|
8996
|
-
|
|
8997
|
-
|
|
8998
|
-
|
|
8999
|
-
|
|
9000
|
-
|
|
9001
|
-
|
|
9002
|
-
|
|
9003
|
-
|
|
9119
|
+
for (const b of targets) {
|
|
9120
|
+
if (typeof body.summary === "string")
|
|
9121
|
+
b.setSummary(body.summary);
|
|
9122
|
+
if (body.status === "idle" || body.status === "working" || body.status === "dnd")
|
|
9123
|
+
b.setStatus(body.status);
|
|
9124
|
+
if (typeof body.visible === "boolean")
|
|
9125
|
+
b.setVisible(body.visible);
|
|
9126
|
+
const profile = {};
|
|
9127
|
+
if (typeof body.avatar === "string")
|
|
9128
|
+
profile.avatar = body.avatar;
|
|
9129
|
+
if (typeof body.title === "string")
|
|
9130
|
+
profile.title = body.title;
|
|
9131
|
+
if (typeof body.bio === "string")
|
|
9132
|
+
profile.bio = body.bio;
|
|
9133
|
+
if (Array.isArray(body.capabilities))
|
|
9134
|
+
profile.capabilities = body.capabilities.filter((c) => typeof c === "string");
|
|
9135
|
+
if (Object.keys(profile).length > 0)
|
|
9136
|
+
b.setProfile(profile);
|
|
9137
|
+
}
|
|
9004
9138
|
Object.assign(updates, body);
|
|
9005
|
-
respond(res, 200, { ok: true, applied: Object.keys(updates) });
|
|
9139
|
+
respond(res, 200, { ok: true, applied: Object.keys(updates), meshes: requested ? [requested] : [...opts.brokers.keys()] });
|
|
9006
9140
|
} catch (e) {
|
|
9007
9141
|
respond(res, 400, { error: String(e) });
|
|
9008
9142
|
}
|
|
@@ -9120,6 +9254,36 @@ function makeHandler(opts) {
|
|
|
9120
9254
|
respond(res, 400, { error: parsed.error });
|
|
9121
9255
|
return;
|
|
9122
9256
|
}
|
|
9257
|
+
if (opts.brokers && opts.brokers.size > 0 && opts.meshConfigs) {
|
|
9258
|
+
let chosenSlug = parsed.req.mesh ?? null;
|
|
9259
|
+
if (!chosenSlug && opts.brokers.size === 1) {
|
|
9260
|
+
chosenSlug = opts.brokers.keys().next().value;
|
|
9261
|
+
}
|
|
9262
|
+
if (!chosenSlug) {
|
|
9263
|
+
respond(res, 400, {
|
|
9264
|
+
error: "mesh_required",
|
|
9265
|
+
detail: `daemon attached to ${opts.brokers.size} meshes; pass 'mesh' in request body`,
|
|
9266
|
+
attached: [...opts.brokers.keys()]
|
|
9267
|
+
});
|
|
9268
|
+
return;
|
|
9269
|
+
}
|
|
9270
|
+
const broker = opts.brokers.get(chosenSlug);
|
|
9271
|
+
const meshCfg = opts.meshConfigs.get(chosenSlug);
|
|
9272
|
+
if (!broker || !meshCfg) {
|
|
9273
|
+
respond(res, 404, { error: "mesh_not_attached", mesh: chosenSlug });
|
|
9274
|
+
return;
|
|
9275
|
+
}
|
|
9276
|
+
try {
|
|
9277
|
+
const routed = await resolveAndEncrypt(parsed.req, broker, meshCfg.secretKey, chosenSlug);
|
|
9278
|
+
parsed.req.target_spec = routed.target_spec;
|
|
9279
|
+
parsed.req.ciphertext = routed.ciphertext;
|
|
9280
|
+
parsed.req.nonce = routed.nonce;
|
|
9281
|
+
parsed.req.mesh = routed.mesh;
|
|
9282
|
+
} catch (e) {
|
|
9283
|
+
respond(res, 502, { error: "route_failed", detail: String(e) });
|
|
9284
|
+
return;
|
|
9285
|
+
}
|
|
9286
|
+
}
|
|
9123
9287
|
const outcome = acceptSend(parsed.req, { db: opts.outboxDb });
|
|
9124
9288
|
switch (outcome.kind) {
|
|
9125
9289
|
case "accepted_pending":
|
|
@@ -9212,6 +9376,7 @@ function parseSendRequest(body, idempotencyHeader) {
|
|
|
9212
9376
|
const headerId = Array.isArray(idempotencyHeader) ? idempotencyHeader[0] : idempotencyHeader;
|
|
9213
9377
|
const client_message_id = typeof b.client_message_id === "string" && b.client_message_id.trim() ? b.client_message_id.trim() : typeof headerId === "string" && headerId.trim() ? headerId.trim() : undefined;
|
|
9214
9378
|
const reply_to_id = typeof b.reply_to_id === "string" ? b.reply_to_id : undefined;
|
|
9379
|
+
const mesh = typeof b.mesh === "string" ? b.mesh.trim() : undefined;
|
|
9215
9380
|
return {
|
|
9216
9381
|
req: {
|
|
9217
9382
|
to,
|
|
@@ -9221,10 +9386,53 @@ function parseSendRequest(body, idempotencyHeader) {
|
|
|
9221
9386
|
reply_to_id,
|
|
9222
9387
|
client_message_id,
|
|
9223
9388
|
destination_kind,
|
|
9224
|
-
destination_ref
|
|
9389
|
+
destination_ref,
|
|
9390
|
+
mesh
|
|
9225
9391
|
}
|
|
9226
9392
|
};
|
|
9227
9393
|
}
|
|
9394
|
+
async function resolveAndEncrypt(req, broker, meshSecretKey, meshSlug) {
|
|
9395
|
+
const { encryptDirect: encryptDirect2 } = await Promise.resolve().then(() => (init_box(), exports_box));
|
|
9396
|
+
const { randomBytes: randomBytes5 } = await import("node:crypto");
|
|
9397
|
+
const to = req.to.trim();
|
|
9398
|
+
if (to.startsWith("#") && /^#[0-9a-z_-]{20,}$/i.test(to)) {
|
|
9399
|
+
const ciphertext = Buffer.from(req.message, "utf8").toString("base64");
|
|
9400
|
+
const nonce = randomBytes5(24).toString("base64");
|
|
9401
|
+
return { target_spec: to, ciphertext, nonce, mesh: meshSlug ?? "" };
|
|
9402
|
+
}
|
|
9403
|
+
if (to.startsWith("@") || to === "*") {
|
|
9404
|
+
const ciphertext = Buffer.from(req.message, "utf8").toString("base64");
|
|
9405
|
+
const nonce = randomBytes5(24).toString("base64");
|
|
9406
|
+
return { target_spec: to, ciphertext, nonce, mesh: meshSlug ?? "" };
|
|
9407
|
+
}
|
|
9408
|
+
if (/^[0-9a-f]{64}$/i.test(to)) {
|
|
9409
|
+
const sessionKeys2 = broker.getSessionKeys();
|
|
9410
|
+
const senderSecret2 = sessionKeys2?.sessionSecretKey ?? meshSecretKey;
|
|
9411
|
+
const env3 = await encryptDirect2(req.message, to, senderSecret2);
|
|
9412
|
+
return { target_spec: to, ciphertext: env3.ciphertext, nonce: env3.nonce, mesh: meshSlug ?? "" };
|
|
9413
|
+
}
|
|
9414
|
+
const peers = await broker.listPeers().catch(() => []);
|
|
9415
|
+
if (/^[0-9a-f]{16,63}$/i.test(to)) {
|
|
9416
|
+
const matches2 = peers.filter((p) => p.pubkey.toLowerCase().startsWith(to.toLowerCase()) || (p.memberPubkey ?? "").toLowerCase().startsWith(to.toLowerCase()));
|
|
9417
|
+
if (matches2.length === 0)
|
|
9418
|
+
throw new Error(`no peer matching prefix "${to}"`);
|
|
9419
|
+
if (matches2.length > 1)
|
|
9420
|
+
throw new Error(`prefix "${to}" is ambiguous (${matches2.length} matches)`);
|
|
9421
|
+
const recipient2 = matches2[0].pubkey;
|
|
9422
|
+
const sessionKeys2 = broker.getSessionKeys();
|
|
9423
|
+
const senderSecret2 = sessionKeys2?.sessionSecretKey ?? meshSecretKey;
|
|
9424
|
+
const env3 = await encryptDirect2(req.message, recipient2, senderSecret2);
|
|
9425
|
+
return { target_spec: recipient2, ciphertext: env3.ciphertext, nonce: env3.nonce, mesh: meshSlug ?? "" };
|
|
9426
|
+
}
|
|
9427
|
+
const match = peers.find((p) => p.displayName.toLowerCase() === to.toLowerCase());
|
|
9428
|
+
if (!match)
|
|
9429
|
+
throw new Error(`peer "${to}" not found`);
|
|
9430
|
+
const recipient = match.pubkey;
|
|
9431
|
+
const sessionKeys = broker.getSessionKeys();
|
|
9432
|
+
const senderSecret = sessionKeys?.sessionSecretKey ?? meshSecretKey;
|
|
9433
|
+
const env2 = await encryptDirect2(req.message, recipient, senderSecret);
|
|
9434
|
+
return { target_spec: recipient, ciphertext: env2.ciphertext, nonce: env2.nonce, mesh: meshSlug ?? "" };
|
|
9435
|
+
}
|
|
9228
9436
|
function respond(res, status, body) {
|
|
9229
9437
|
const json = JSON.stringify(body);
|
|
9230
9438
|
res.statusCode = status;
|
|
@@ -9632,7 +9840,8 @@ function startDrainWorker(opts) {
|
|
|
9632
9840
|
async function drainOnce(opts, log2) {
|
|
9633
9841
|
const now = Date.now();
|
|
9634
9842
|
const rows = opts.db.prepare(`
|
|
9635
|
-
SELECT id, client_message_id, request_fingerprint, payload, attempts
|
|
9843
|
+
SELECT id, client_message_id, request_fingerprint, payload, attempts,
|
|
9844
|
+
target_spec, nonce, ciphertext, priority, mesh
|
|
9636
9845
|
FROM outbox
|
|
9637
9846
|
WHERE status = 'pending' AND next_attempt_at <= ?
|
|
9638
9847
|
ORDER BY enqueued_at
|
|
@@ -9644,15 +9853,37 @@ async function drainOnce(opts, log2) {
|
|
|
9644
9853
|
if (markInflight(opts.db, row.id, now) === 0)
|
|
9645
9854
|
continue;
|
|
9646
9855
|
const fpHex = bufferToHex(row.request_fingerprint);
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9856
|
+
let broker;
|
|
9857
|
+
if (row.mesh) {
|
|
9858
|
+
broker = opts.brokers.get(row.mesh);
|
|
9859
|
+
} else if (opts.brokers.size === 1) {
|
|
9860
|
+
broker = opts.brokers.values().next().value;
|
|
9861
|
+
}
|
|
9862
|
+
if (!broker) {
|
|
9863
|
+
log2("warn", "drain_no_broker_for_mesh", { id: row.id, mesh: row.mesh ?? "(null)" });
|
|
9864
|
+
markDead(opts.db, row.id, `no_broker_for_mesh:${row.mesh ?? "null"}`);
|
|
9865
|
+
continue;
|
|
9866
|
+
}
|
|
9867
|
+
let targetSpec;
|
|
9868
|
+
let nonce;
|
|
9869
|
+
let ciphertext;
|
|
9870
|
+
let priority;
|
|
9871
|
+
if (row.target_spec && row.nonce && row.ciphertext) {
|
|
9872
|
+
targetSpec = row.target_spec;
|
|
9873
|
+
nonce = row.nonce;
|
|
9874
|
+
ciphertext = row.ciphertext;
|
|
9875
|
+
priority = row.priority === "now" || row.priority === "low" ? row.priority : "next";
|
|
9876
|
+
} else {
|
|
9877
|
+
targetSpec = "*";
|
|
9878
|
+
nonce = await randomNonce2();
|
|
9879
|
+
ciphertext = Buffer.from(row.payload).toString("base64");
|
|
9880
|
+
priority = "next";
|
|
9881
|
+
}
|
|
9651
9882
|
let res;
|
|
9652
9883
|
try {
|
|
9653
|
-
res = await
|
|
9884
|
+
res = await broker.send({
|
|
9654
9885
|
targetSpec,
|
|
9655
|
-
priority
|
|
9886
|
+
priority,
|
|
9656
9887
|
nonce,
|
|
9657
9888
|
ciphertext,
|
|
9658
9889
|
client_message_id: row.client_message_id,
|
|
@@ -9996,10 +10227,10 @@ async function runDaemon(opts = {}) {
|
|
|
9996
10227
|
}
|
|
9997
10228
|
const bus = new EventBus;
|
|
9998
10229
|
const cfg = readConfig();
|
|
9999
|
-
let
|
|
10230
|
+
let meshes;
|
|
10000
10231
|
if (opts.mesh) {
|
|
10001
|
-
|
|
10002
|
-
if (!
|
|
10232
|
+
const found = cfg.meshes.find((m) => m.slug === opts.mesh);
|
|
10233
|
+
if (!found) {
|
|
10003
10234
|
process.stderr.write(`mesh not found: ${opts.mesh}
|
|
10004
10235
|
`);
|
|
10005
10236
|
process.stderr.write(`joined meshes: ${cfg.meshes.map((m) => m.slug).join(", ") || "(none)"}
|
|
@@ -10010,8 +10241,7 @@ async function runDaemon(opts = {}) {
|
|
|
10010
10241
|
} catch {}
|
|
10011
10242
|
return 2;
|
|
10012
10243
|
}
|
|
10013
|
-
|
|
10014
|
-
mesh = cfg.meshes[0];
|
|
10244
|
+
meshes = [found];
|
|
10015
10245
|
} else if (cfg.meshes.length === 0) {
|
|
10016
10246
|
process.stderr.write(`no mesh joined; run \`claudemesh join <invite-url>\` first
|
|
10017
10247
|
`);
|
|
@@ -10021,43 +10251,41 @@ async function runDaemon(opts = {}) {
|
|
|
10021
10251
|
} catch {}
|
|
10022
10252
|
return 2;
|
|
10023
10253
|
} else {
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
status: s,
|
|
10040
|
-
mesh: mesh.slug,
|
|
10041
|
-
ts: new Date().toISOString()
|
|
10042
|
-
}) + `
|
|
10254
|
+
meshes = cfg.meshes;
|
|
10255
|
+
}
|
|
10256
|
+
const brokers = new Map;
|
|
10257
|
+
const meshConfigs = new Map;
|
|
10258
|
+
for (const mesh of meshes) {
|
|
10259
|
+
meshConfigs.set(mesh.slug, mesh);
|
|
10260
|
+
const broker = new DaemonBrokerClient(mesh, {
|
|
10261
|
+
displayName: opts.displayName,
|
|
10262
|
+
onStatusChange: (s) => {
|
|
10263
|
+
process.stdout.write(JSON.stringify({
|
|
10264
|
+
msg: "broker_status",
|
|
10265
|
+
status: s,
|
|
10266
|
+
mesh: mesh.slug,
|
|
10267
|
+
ts: new Date().toISOString()
|
|
10268
|
+
}) + `
|
|
10043
10269
|
`);
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
10053
|
-
|
|
10054
|
-
|
|
10055
|
-
|
|
10056
|
-
|
|
10057
|
-
|
|
10270
|
+
bus.publish("broker_status", { mesh: mesh.slug, status: s });
|
|
10271
|
+
},
|
|
10272
|
+
onPush: (m) => {
|
|
10273
|
+
const sessionKeys = broker.getSessionKeys();
|
|
10274
|
+
handleBrokerPush(m, {
|
|
10275
|
+
db: inboxDb,
|
|
10276
|
+
bus,
|
|
10277
|
+
meshSlug: mesh.slug,
|
|
10278
|
+
recipientSecretKeyHex: mesh.secretKey,
|
|
10279
|
+
sessionSecretKeyHex: sessionKeys?.sessionSecretKey
|
|
10280
|
+
});
|
|
10281
|
+
}
|
|
10282
|
+
});
|
|
10283
|
+
broker.connect().catch((err) => process.stderr.write(`broker connect failed for ${mesh.slug}: ${String(err)}
|
|
10058
10284
|
`));
|
|
10285
|
+
brokers.set(mesh.slug, broker);
|
|
10286
|
+
}
|
|
10059
10287
|
let drain = null;
|
|
10060
|
-
drain = startDrainWorker({ db: outboxDb,
|
|
10288
|
+
drain = startDrainWorker({ db: outboxDb, brokers });
|
|
10061
10289
|
const ipc2 = startIpcServer({
|
|
10062
10290
|
localToken,
|
|
10063
10291
|
tcpEnabled,
|
|
@@ -10065,7 +10293,8 @@ async function runDaemon(opts = {}) {
|
|
|
10065
10293
|
outboxDb,
|
|
10066
10294
|
inboxDb,
|
|
10067
10295
|
bus,
|
|
10068
|
-
|
|
10296
|
+
brokers,
|
|
10297
|
+
meshConfigs,
|
|
10069
10298
|
onPendingInserted: () => drain?.wake()
|
|
10070
10299
|
});
|
|
10071
10300
|
try {
|
|
@@ -10081,7 +10310,7 @@ async function runDaemon(opts = {}) {
|
|
|
10081
10310
|
pid: process.pid,
|
|
10082
10311
|
sock: DAEMON_PATHS.SOCK_FILE,
|
|
10083
10312
|
tcp: tcpEnabled ? `127.0.0.1:47823` : null,
|
|
10084
|
-
|
|
10313
|
+
meshes: meshes.map((m) => m.slug),
|
|
10085
10314
|
ts: new Date().toISOString()
|
|
10086
10315
|
}) + `
|
|
10087
10316
|
`);
|
|
@@ -10094,7 +10323,11 @@ async function runDaemon(opts = {}) {
|
|
|
10094
10323
|
`);
|
|
10095
10324
|
if (drain)
|
|
10096
10325
|
await drain.close();
|
|
10097
|
-
|
|
10326
|
+
for (const b of brokers.values()) {
|
|
10327
|
+
try {
|
|
10328
|
+
await b.close();
|
|
10329
|
+
} catch {}
|
|
10330
|
+
}
|
|
10098
10331
|
await ipc2.close();
|
|
10099
10332
|
try {
|
|
10100
10333
|
outboxDb.close();
|
|
@@ -13263,6 +13496,32 @@ async function runSqlSchema(opts) {
|
|
|
13263
13496
|
});
|
|
13264
13497
|
}
|
|
13265
13498
|
async function runSkillList(opts) {
|
|
13499
|
+
try {
|
|
13500
|
+
const { tryListSkillsViaDaemon: tryListSkillsViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
|
|
13501
|
+
const dr = await tryListSkillsViaDaemon2();
|
|
13502
|
+
if (dr !== null) {
|
|
13503
|
+
const skills = dr;
|
|
13504
|
+
if (opts.json) {
|
|
13505
|
+
emitJson(skills);
|
|
13506
|
+
return EXIT.SUCCESS;
|
|
13507
|
+
}
|
|
13508
|
+
if (skills.length === 0) {
|
|
13509
|
+
render.info(dim("(no skills)"));
|
|
13510
|
+
return EXIT.SUCCESS;
|
|
13511
|
+
}
|
|
13512
|
+
render.section(`mesh skills (${skills.length})`);
|
|
13513
|
+
for (const s of skills) {
|
|
13514
|
+
process.stdout.write(` ${bold(s.name)} ${dim("· by " + s.author)}
|
|
13515
|
+
`);
|
|
13516
|
+
process.stdout.write(` ${s.description}
|
|
13517
|
+
`);
|
|
13518
|
+
if (s.tags?.length)
|
|
13519
|
+
process.stdout.write(` ${dim("tags: " + s.tags.join(", "))}
|
|
13520
|
+
`);
|
|
13521
|
+
}
|
|
13522
|
+
return EXIT.SUCCESS;
|
|
13523
|
+
}
|
|
13524
|
+
} catch {}
|
|
13266
13525
|
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
|
|
13267
13526
|
const skills = await client.listSkills(opts.query);
|
|
13268
13527
|
if (opts.json) {
|
|
@@ -13291,6 +13550,29 @@ async function runSkillGet(name, opts) {
|
|
|
13291
13550
|
render.err("Usage: claudemesh skill get <name>");
|
|
13292
13551
|
return EXIT.INVALID_ARGS;
|
|
13293
13552
|
}
|
|
13553
|
+
try {
|
|
13554
|
+
const { tryGetSkillViaDaemon: tryGetSkillViaDaemon2 } = await Promise.resolve().then(() => (init_daemon_route(), exports_daemon_route));
|
|
13555
|
+
const dr = await tryGetSkillViaDaemon2(name);
|
|
13556
|
+
if (dr !== null) {
|
|
13557
|
+
const skill = dr;
|
|
13558
|
+
if (opts.json) {
|
|
13559
|
+
emitJson(skill);
|
|
13560
|
+
return EXIT.SUCCESS;
|
|
13561
|
+
}
|
|
13562
|
+
render.section(skill.name);
|
|
13563
|
+
render.kv([
|
|
13564
|
+
["author", skill.author],
|
|
13565
|
+
["created", skill.createdAt],
|
|
13566
|
+
["tags", skill.tags?.join(", ") || dim("(none)")]
|
|
13567
|
+
]);
|
|
13568
|
+
render.blank();
|
|
13569
|
+
render.info(skill.description);
|
|
13570
|
+
render.blank();
|
|
13571
|
+
process.stdout.write(skill.instructions + `
|
|
13572
|
+
`);
|
|
13573
|
+
return EXIT.SUCCESS;
|
|
13574
|
+
}
|
|
13575
|
+
} catch {}
|
|
13294
13576
|
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
|
|
13295
13577
|
const skill = await client.getSkill(name);
|
|
13296
13578
|
if (!skill) {
|
|
@@ -13808,7 +14090,7 @@ claudemesh send "<from_name>" "..." --mesh "<mesh_slug>"
|
|
|
13808
14090
|
|
|
13809
14091
|
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.
|
|
13810
14092
|
|
|
13811
|
-
### Daemon path (
|
|
14093
|
+
### Daemon path (v1.24.0+, REQUIRED for in-Claude-Code use)
|
|
13812
14094
|
|
|
13813
14095
|
\`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.
|
|
13814
14096
|
|
|
@@ -13823,11 +14105,15 @@ claudemesh daemon outbox requeue <id> # re-enqueue an aborted/d
|
|
|
13823
14105
|
claudemesh daemon down # SIGTERM + wait
|
|
13824
14106
|
\`\`\`
|
|
13825
14107
|
|
|
13826
|
-
\`claudemesh install\`
|
|
14108
|
+
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.
|
|
14109
|
+
|
|
14110
|
+
### Ambient mode (1.25.0+)
|
|
14111
|
+
|
|
14112
|
+
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).
|
|
13827
14113
|
|
|
13828
14114
|
## Spawning new sessions (no wizard)
|
|
13829
14115
|
|
|
13830
|
-
\`claudemesh launch\`
|
|
14116
|
+
\`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.
|
|
13831
14117
|
|
|
13832
14118
|
### Full flag surface
|
|
13833
14119
|
|
|
@@ -18376,4 +18662,4 @@ main().catch((err) => {
|
|
|
18376
18662
|
process.exit(EXIT.INTERNAL_ERROR);
|
|
18377
18663
|
});
|
|
18378
18664
|
|
|
18379
|
-
//# debugId=
|
|
18665
|
+
//# debugId=3BADD117052552A564756E2164756E21
|