claudemesh-cli 1.24.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 +337 -119
- 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.25.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;
|
|
@@ -7007,6 +6968,112 @@ var init_daemon_route = __esm(() => {
|
|
|
7007
6968
|
init_paths2();
|
|
7008
6969
|
});
|
|
7009
6970
|
|
|
6971
|
+
// src/commands/peers.ts
|
|
6972
|
+
var exports_peers = {};
|
|
6973
|
+
__export(exports_peers, {
|
|
6974
|
+
runPeers: () => runPeers
|
|
6975
|
+
});
|
|
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];
|
|
6981
|
+
}
|
|
6982
|
+
return out;
|
|
6983
|
+
}
|
|
6984
|
+
async function listPeersForMesh(slug) {
|
|
6985
|
+
const config = readConfig();
|
|
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));
|
|
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));
|
|
6999
|
+
}
|
|
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
|
+
|
|
7010
7077
|
// src/commands/send.ts
|
|
7011
7078
|
var exports_send = {};
|
|
7012
7079
|
__export(exports_send, {
|
|
@@ -8441,12 +8508,32 @@ function migrateOutbox(db) {
|
|
|
8441
8508
|
CREATE INDEX IF NOT EXISTS outbox_aborted
|
|
8442
8509
|
ON outbox(status, aborted_at) WHERE status = 'aborted';
|
|
8443
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);
|
|
8444
8530
|
}
|
|
8445
8531
|
function findByClientId(db, clientMessageId) {
|
|
8446
8532
|
const row = db.prepare(`
|
|
8447
8533
|
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
8448
8534
|
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
8449
|
-
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
|
|
8450
8537
|
FROM outbox WHERE client_message_id = ?
|
|
8451
8538
|
`).get(clientMessageId);
|
|
8452
8539
|
return row ?? null;
|
|
@@ -8455,9 +8542,10 @@ function insertPending(db, input) {
|
|
|
8455
8542
|
db.prepare(`
|
|
8456
8543
|
INSERT INTO outbox (
|
|
8457
8544
|
id, client_message_id, request_fingerprint, payload,
|
|
8458
|
-
enqueued_at, attempts, next_attempt_at, status
|
|
8459
|
-
|
|
8460
|
-
|
|
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);
|
|
8461
8549
|
}
|
|
8462
8550
|
function fingerprintsEqual(a, b) {
|
|
8463
8551
|
if (a.length !== b.length)
|
|
@@ -8477,7 +8565,8 @@ function listOutbox(db, p = {}) {
|
|
|
8477
8565
|
const sql = `
|
|
8478
8566
|
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
8479
8567
|
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
8480
|
-
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
|
|
8481
8570
|
FROM outbox
|
|
8482
8571
|
${where.length ? "WHERE " + where.join(" AND ") : ""}
|
|
8483
8572
|
ORDER BY enqueued_at DESC
|
|
@@ -8490,7 +8579,8 @@ function findById(db, id) {
|
|
|
8490
8579
|
return db.prepare(`
|
|
8491
8580
|
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
8492
8581
|
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
8493
|
-
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
|
|
8494
8584
|
FROM outbox WHERE id = ?
|
|
8495
8585
|
`).get(id) ?? null;
|
|
8496
8586
|
}
|
|
@@ -8633,7 +8723,12 @@ function acceptSend(req, deps) {
|
|
|
8633
8723
|
client_message_id: clientId,
|
|
8634
8724
|
request_fingerprint: fingerprint,
|
|
8635
8725
|
payload: body,
|
|
8636
|
-
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
|
|
8637
8732
|
});
|
|
8638
8733
|
return { kind: "accepted_pending", status: 202, client_message_id: clientId };
|
|
8639
8734
|
}
|
|
@@ -8814,7 +8909,9 @@ function startIpcServer(opts) {
|
|
|
8814
8909
|
inboxDb: opts.inboxDb,
|
|
8815
8910
|
bus: opts.bus,
|
|
8816
8911
|
broker: opts.broker,
|
|
8817
|
-
onPendingInserted: opts.onPendingInserted
|
|
8912
|
+
onPendingInserted: opts.onPendingInserted,
|
|
8913
|
+
meshSecretKey: opts.meshSecretKey,
|
|
8914
|
+
meshSlug: opts.meshSlug
|
|
8818
8915
|
});
|
|
8819
8916
|
if (existsSync9(DAEMON_PATHS.SOCK_FILE)) {
|
|
8820
8917
|
try {
|
|
@@ -9120,6 +9217,18 @@ function makeHandler(opts) {
|
|
|
9120
9217
|
respond(res, 400, { error: parsed.error });
|
|
9121
9218
|
return;
|
|
9122
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
|
+
}
|
|
9123
9232
|
const outcome = acceptSend(parsed.req, { db: opts.outboxDb });
|
|
9124
9233
|
switch (outcome.kind) {
|
|
9125
9234
|
case "accepted_pending":
|
|
@@ -9225,6 +9334,48 @@ function parseSendRequest(body, idempotencyHeader) {
|
|
|
9225
9334
|
}
|
|
9226
9335
|
};
|
|
9227
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
|
+
}
|
|
9228
9379
|
function respond(res, status, body) {
|
|
9229
9380
|
const json = JSON.stringify(body);
|
|
9230
9381
|
res.statusCode = status;
|
|
@@ -9632,7 +9783,8 @@ function startDrainWorker(opts) {
|
|
|
9632
9783
|
async function drainOnce(opts, log2) {
|
|
9633
9784
|
const now = Date.now();
|
|
9634
9785
|
const rows = opts.db.prepare(`
|
|
9635
|
-
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
|
|
9636
9788
|
FROM outbox
|
|
9637
9789
|
WHERE status = 'pending' AND next_attempt_at <= ?
|
|
9638
9790
|
ORDER BY enqueued_at
|
|
@@ -9644,15 +9796,26 @@ async function drainOnce(opts, log2) {
|
|
|
9644
9796
|
if (markInflight(opts.db, row.id, now) === 0)
|
|
9645
9797
|
continue;
|
|
9646
9798
|
const fpHex = bufferToHex(row.request_fingerprint);
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
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
|
+
}
|
|
9651
9814
|
let res;
|
|
9652
9815
|
try {
|
|
9653
9816
|
res = await opts.broker.send({
|
|
9654
9817
|
targetSpec,
|
|
9655
|
-
priority
|
|
9818
|
+
priority,
|
|
9656
9819
|
nonce,
|
|
9657
9820
|
ciphertext,
|
|
9658
9821
|
client_message_id: row.client_message_id,
|
|
@@ -10066,7 +10229,9 @@ async function runDaemon(opts = {}) {
|
|
|
10066
10229
|
inboxDb,
|
|
10067
10230
|
bus,
|
|
10068
10231
|
broker,
|
|
10069
|
-
onPendingInserted: () => drain?.wake()
|
|
10232
|
+
onPendingInserted: () => drain?.wake(),
|
|
10233
|
+
meshSecretKey: mesh.secretKey,
|
|
10234
|
+
meshSlug: mesh.slug
|
|
10070
10235
|
});
|
|
10071
10236
|
try {
|
|
10072
10237
|
await ipc2.ready;
|
|
@@ -13263,6 +13428,32 @@ async function runSqlSchema(opts) {
|
|
|
13263
13428
|
});
|
|
13264
13429
|
}
|
|
13265
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 {}
|
|
13266
13457
|
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
|
|
13267
13458
|
const skills = await client.listSkills(opts.query);
|
|
13268
13459
|
if (opts.json) {
|
|
@@ -13291,6 +13482,29 @@ async function runSkillGet(name, opts) {
|
|
|
13291
13482
|
render.err("Usage: claudemesh skill get <name>");
|
|
13292
13483
|
return EXIT.INVALID_ARGS;
|
|
13293
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 {}
|
|
13294
13508
|
return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
|
|
13295
13509
|
const skill = await client.getSkill(name);
|
|
13296
13510
|
if (!skill) {
|
|
@@ -13808,7 +14022,7 @@ claudemesh send "<from_name>" "..." --mesh "<mesh_slug>"
|
|
|
13808
14022
|
|
|
13809
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.
|
|
13810
14024
|
|
|
13811
|
-
### Daemon path (
|
|
14025
|
+
### Daemon path (v1.24.0+, REQUIRED for in-Claude-Code use)
|
|
13812
14026
|
|
|
13813
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.
|
|
13814
14028
|
|
|
@@ -13823,11 +14037,15 @@ claudemesh daemon outbox requeue <id> # re-enqueue an aborted/d
|
|
|
13823
14037
|
claudemesh daemon down # SIGTERM + wait
|
|
13824
14038
|
\`\`\`
|
|
13825
14039
|
|
|
13826
|
-
\`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).
|
|
13827
14045
|
|
|
13828
14046
|
## Spawning new sessions (no wizard)
|
|
13829
14047
|
|
|
13830
|
-
\`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.
|
|
13831
14049
|
|
|
13832
14050
|
### Full flag surface
|
|
13833
14051
|
|
|
@@ -18376,4 +18594,4 @@ main().catch((err) => {
|
|
|
18376
18594
|
process.exit(EXIT.INTERNAL_ERROR);
|
|
18377
18595
|
});
|
|
18378
18596
|
|
|
18379
|
-
//# debugId=
|
|
18597
|
+
//# debugId=EC115E4068A7B5F664756E2164756E21
|