claudemesh-cli 1.21.1 → 1.22.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 +2628 -333
- package/dist/entrypoints/cli.js.map +24 -5
- package/dist/entrypoints/mcp.js +2 -2
- package/dist/entrypoints/mcp.js.map +1 -1
- package/package.json +1 -1
package/dist/entrypoints/cli.js
CHANGED
|
@@ -88,7 +88,7 @@ __export(exports_urls, {
|
|
|
88
88
|
VERSION: () => VERSION,
|
|
89
89
|
URLS: () => URLS
|
|
90
90
|
});
|
|
91
|
-
var URLS, VERSION = "1.
|
|
91
|
+
var URLS, VERSION = "1.22.0", env;
|
|
92
92
|
var init_urls = __esm(() => {
|
|
93
93
|
URLS = {
|
|
94
94
|
BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
|
|
@@ -6834,6 +6834,154 @@ var init_peers = __esm(() => {
|
|
|
6834
6834
|
};
|
|
6835
6835
|
});
|
|
6836
6836
|
|
|
6837
|
+
// src/daemon/paths.ts
|
|
6838
|
+
import { join as join6 } from "node:path";
|
|
6839
|
+
var DAEMON_PATHS, DAEMON_TCP_HOST = "127.0.0.1", DAEMON_TCP_DEFAULT_PORT = 47823;
|
|
6840
|
+
var init_paths2 = __esm(() => {
|
|
6841
|
+
init_paths();
|
|
6842
|
+
DAEMON_PATHS = {
|
|
6843
|
+
get DAEMON_DIR() {
|
|
6844
|
+
return join6(PATHS.CONFIG_DIR, "daemon");
|
|
6845
|
+
},
|
|
6846
|
+
get PID_FILE() {
|
|
6847
|
+
return join6(this.DAEMON_DIR, "daemon.pid");
|
|
6848
|
+
},
|
|
6849
|
+
get SOCK_FILE() {
|
|
6850
|
+
return join6(this.DAEMON_DIR, "daemon.sock");
|
|
6851
|
+
},
|
|
6852
|
+
get TOKEN_FILE() {
|
|
6853
|
+
return join6(this.DAEMON_DIR, "local-token");
|
|
6854
|
+
},
|
|
6855
|
+
get OUTBOX_DB() {
|
|
6856
|
+
return join6(this.DAEMON_DIR, "outbox.db");
|
|
6857
|
+
},
|
|
6858
|
+
get INBOX_DB() {
|
|
6859
|
+
return join6(this.DAEMON_DIR, "inbox.db");
|
|
6860
|
+
},
|
|
6861
|
+
get LOG_FILE() {
|
|
6862
|
+
return join6(this.DAEMON_DIR, "daemon.log");
|
|
6863
|
+
}
|
|
6864
|
+
};
|
|
6865
|
+
});
|
|
6866
|
+
|
|
6867
|
+
// src/daemon/local-token.ts
|
|
6868
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
6869
|
+
import { dirname as dirname3 } from "node:path";
|
|
6870
|
+
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
6871
|
+
function readLocalToken() {
|
|
6872
|
+
try {
|
|
6873
|
+
return readFileSync5(DAEMON_PATHS.TOKEN_FILE, "utf8").trim();
|
|
6874
|
+
} catch {
|
|
6875
|
+
return null;
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6878
|
+
function ensureLocalToken() {
|
|
6879
|
+
const existing = readLocalToken();
|
|
6880
|
+
if (existing)
|
|
6881
|
+
return existing;
|
|
6882
|
+
mkdirSync4(dirname3(DAEMON_PATHS.TOKEN_FILE), { recursive: true, mode: 448 });
|
|
6883
|
+
const tok = randomBytes4(32).toString("base64url");
|
|
6884
|
+
writeFileSync6(DAEMON_PATHS.TOKEN_FILE, tok + `
|
|
6885
|
+
`, { mode: 384 });
|
|
6886
|
+
return tok;
|
|
6887
|
+
}
|
|
6888
|
+
var init_local_token = __esm(() => {
|
|
6889
|
+
init_paths2();
|
|
6890
|
+
});
|
|
6891
|
+
|
|
6892
|
+
// src/daemon/ipc/client.ts
|
|
6893
|
+
import { request as httpRequest } from "node:http";
|
|
6894
|
+
async function ipc(opts) {
|
|
6895
|
+
const useTcp = !!opts.preferTcp;
|
|
6896
|
+
const headers = {
|
|
6897
|
+
accept: "application/json",
|
|
6898
|
+
host: "localhost"
|
|
6899
|
+
};
|
|
6900
|
+
let bodyBuf;
|
|
6901
|
+
if (opts.body !== undefined) {
|
|
6902
|
+
bodyBuf = Buffer.from(JSON.stringify(opts.body), "utf8");
|
|
6903
|
+
headers["content-type"] = "application/json";
|
|
6904
|
+
headers["content-length"] = String(bodyBuf.length);
|
|
6905
|
+
}
|
|
6906
|
+
if (useTcp) {
|
|
6907
|
+
const tok = readLocalToken();
|
|
6908
|
+
if (!tok)
|
|
6909
|
+
throw new IpcError(0, null, "daemon local token not found; is the daemon running?");
|
|
6910
|
+
headers.authorization = `Bearer ${tok}`;
|
|
6911
|
+
}
|
|
6912
|
+
return new Promise((resolve, reject) => {
|
|
6913
|
+
const req = httpRequest(useTcp ? { host: DAEMON_TCP_HOST, port: DAEMON_TCP_DEFAULT_PORT, path: opts.path, method: opts.method ?? "GET", headers } : { socketPath: DAEMON_PATHS.SOCK_FILE, path: opts.path, method: opts.method ?? "GET", headers }, (res) => {
|
|
6914
|
+
const chunks = [];
|
|
6915
|
+
res.on("data", (c) => chunks.push(c));
|
|
6916
|
+
res.on("end", () => {
|
|
6917
|
+
const raw = Buffer.concat(chunks).toString("utf8");
|
|
6918
|
+
let parsed = raw;
|
|
6919
|
+
try {
|
|
6920
|
+
parsed = raw.length > 0 ? JSON.parse(raw) : null;
|
|
6921
|
+
} catch {}
|
|
6922
|
+
resolve({ status: res.statusCode ?? 0, body: parsed });
|
|
6923
|
+
});
|
|
6924
|
+
});
|
|
6925
|
+
req.setTimeout(opts.timeoutMs ?? 5000, () => req.destroy(new Error("ipc_timeout")));
|
|
6926
|
+
req.on("error", (err) => reject(err));
|
|
6927
|
+
if (bodyBuf)
|
|
6928
|
+
req.write(bodyBuf);
|
|
6929
|
+
req.end();
|
|
6930
|
+
});
|
|
6931
|
+
}
|
|
6932
|
+
var IpcError;
|
|
6933
|
+
var init_client4 = __esm(() => {
|
|
6934
|
+
init_paths2();
|
|
6935
|
+
init_local_token();
|
|
6936
|
+
IpcError = class IpcError extends Error {
|
|
6937
|
+
status;
|
|
6938
|
+
payload;
|
|
6939
|
+
constructor(status, payload, msg) {
|
|
6940
|
+
super(msg);
|
|
6941
|
+
this.status = status;
|
|
6942
|
+
this.payload = payload;
|
|
6943
|
+
}
|
|
6944
|
+
};
|
|
6945
|
+
});
|
|
6946
|
+
|
|
6947
|
+
// src/services/bridge/daemon-route.ts
|
|
6948
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
6949
|
+
async function trySendViaDaemon(args) {
|
|
6950
|
+
if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
|
|
6951
|
+
return null;
|
|
6952
|
+
try {
|
|
6953
|
+
const res = await ipc({
|
|
6954
|
+
method: "POST",
|
|
6955
|
+
path: "/v1/send",
|
|
6956
|
+
timeoutMs: 3000,
|
|
6957
|
+
body: {
|
|
6958
|
+
to: args.to,
|
|
6959
|
+
message: args.message,
|
|
6960
|
+
priority: args.priority,
|
|
6961
|
+
...args.idempotencyKey ? { client_message_id: args.idempotencyKey } : {}
|
|
6962
|
+
}
|
|
6963
|
+
});
|
|
6964
|
+
if (res.status === 202 || res.status === 200) {
|
|
6965
|
+
return {
|
|
6966
|
+
ok: true,
|
|
6967
|
+
messageId: res.body.broker_message_id ?? res.body.client_message_id ?? "",
|
|
6968
|
+
duplicate: res.body.duplicate,
|
|
6969
|
+
status: res.body.status
|
|
6970
|
+
};
|
|
6971
|
+
}
|
|
6972
|
+
return { ok: false, error: res.body.error ?? `daemon http ${res.status}` };
|
|
6973
|
+
} catch (err) {
|
|
6974
|
+
const msg = String(err);
|
|
6975
|
+
if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
|
|
6976
|
+
return null;
|
|
6977
|
+
return { ok: false, error: msg };
|
|
6978
|
+
}
|
|
6979
|
+
}
|
|
6980
|
+
var init_daemon_route = __esm(() => {
|
|
6981
|
+
init_client4();
|
|
6982
|
+
init_paths2();
|
|
6983
|
+
});
|
|
6984
|
+
|
|
6837
6985
|
// src/commands/send.ts
|
|
6838
6986
|
var exports_send = {};
|
|
6839
6987
|
__export(exports_send, {
|
|
@@ -6855,6 +7003,23 @@ async function runSend(flags, to, message) {
|
|
|
6855
7003
|
process.exit(1);
|
|
6856
7004
|
}
|
|
6857
7005
|
}
|
|
7006
|
+
{
|
|
7007
|
+
const dr = await trySendViaDaemon({ to, message, priority, expectedMesh: meshSlug ?? undefined });
|
|
7008
|
+
if (dr !== null) {
|
|
7009
|
+
if (dr.ok) {
|
|
7010
|
+
if (flags.json)
|
|
7011
|
+
console.log(JSON.stringify({ ok: true, messageId: dr.messageId, target: to, via: "daemon", duplicate: !!dr.duplicate }));
|
|
7012
|
+
else
|
|
7013
|
+
render.ok(`sent to ${to} (daemon)`, dr.messageId ? dim(dr.messageId.slice(0, 8)) : undefined);
|
|
7014
|
+
return;
|
|
7015
|
+
}
|
|
7016
|
+
if (flags.json)
|
|
7017
|
+
console.log(JSON.stringify({ ok: false, error: dr.error, via: "daemon" }));
|
|
7018
|
+
else
|
|
7019
|
+
render.err(`send failed (daemon): ${dr.error}`);
|
|
7020
|
+
process.exit(1);
|
|
7021
|
+
}
|
|
7022
|
+
}
|
|
6858
7023
|
if (meshSlug) {
|
|
6859
7024
|
const bridged = await tryBridge(meshSlug, "send", { to, message, priority });
|
|
6860
7025
|
if (bridged !== null) {
|
|
@@ -6918,6 +7083,7 @@ var init_send = __esm(() => {
|
|
|
6918
7083
|
init_connect();
|
|
6919
7084
|
init_facade();
|
|
6920
7085
|
init_client3();
|
|
7086
|
+
init_daemon_route();
|
|
6921
7087
|
init_render();
|
|
6922
7088
|
init_styles();
|
|
6923
7089
|
});
|
|
@@ -8001,180 +8167,2292 @@ async function runRemind(flags, positional) {
|
|
|
8001
8167
|
console.log(JSON.stringify(scheduled, null, 2));
|
|
8002
8168
|
return;
|
|
8003
8169
|
}
|
|
8004
|
-
if (scheduled.length === 0) {
|
|
8005
|
-
render.info(dim("No pending reminders."));
|
|
8006
|
-
return;
|
|
8170
|
+
if (scheduled.length === 0) {
|
|
8171
|
+
render.info(dim("No pending reminders."));
|
|
8172
|
+
return;
|
|
8173
|
+
}
|
|
8174
|
+
render.section(`reminders (${scheduled.length})`);
|
|
8175
|
+
for (const m of scheduled) {
|
|
8176
|
+
const when = new Date(m.deliverAt).toLocaleString();
|
|
8177
|
+
const to = m.to === client.getSessionPubkey() ? dim("(self)") : m.to;
|
|
8178
|
+
process.stdout.write(` ${bold(m.id.slice(0, 8))} ${dim("→")} ${to} ${dim("at")} ${when}
|
|
8179
|
+
`);
|
|
8180
|
+
process.stdout.write(` ${dim(m.message.slice(0, 80))}
|
|
8181
|
+
|
|
8182
|
+
`);
|
|
8183
|
+
}
|
|
8184
|
+
});
|
|
8185
|
+
return;
|
|
8186
|
+
}
|
|
8187
|
+
if (action === "cancel") {
|
|
8188
|
+
const id = positional[1];
|
|
8189
|
+
if (!id) {
|
|
8190
|
+
render.err("Usage: claudemesh remind cancel <id>");
|
|
8191
|
+
process.exit(1);
|
|
8192
|
+
}
|
|
8193
|
+
await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
8194
|
+
const ok = await client.cancelScheduled(id);
|
|
8195
|
+
if (ok)
|
|
8196
|
+
render.ok(`cancelled ${bold(id.slice(0, 8))}`);
|
|
8197
|
+
else {
|
|
8198
|
+
render.err(`not found or already fired: ${id}`);
|
|
8199
|
+
process.exit(1);
|
|
8200
|
+
}
|
|
8201
|
+
});
|
|
8202
|
+
return;
|
|
8203
|
+
}
|
|
8204
|
+
const message = action ?? positional.join(" ");
|
|
8205
|
+
if (!message) {
|
|
8206
|
+
render.err("Usage: claudemesh remind <message> --in <duration>");
|
|
8207
|
+
render.info(dim(" claudemesh remind <message> --at <time>"));
|
|
8208
|
+
render.info(dim(' claudemesh remind <message> --cron "0 */2 * * *"'));
|
|
8209
|
+
render.info(dim(" claudemesh remind list"));
|
|
8210
|
+
render.info(dim(" claudemesh remind cancel <id>"));
|
|
8211
|
+
process.exit(1);
|
|
8212
|
+
}
|
|
8213
|
+
const isCron = !!flags.cron;
|
|
8214
|
+
const deliverAt = isCron ? 0 : parseDeliverAt(flags);
|
|
8215
|
+
if (!isCron && deliverAt === null) {
|
|
8216
|
+
render.err("Specify when", 'use --in <duration> (e.g. "2h", "30m"), --at <time> (e.g. "15:00"), or --cron <expression>');
|
|
8217
|
+
process.exit(1);
|
|
8218
|
+
}
|
|
8219
|
+
await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
|
|
8220
|
+
let targetSpec;
|
|
8221
|
+
if (flags.to && flags.to !== "self") {
|
|
8222
|
+
if (flags.to.startsWith("@") || flags.to === "*" || /^[0-9a-f]{64}$/i.test(flags.to)) {
|
|
8223
|
+
targetSpec = flags.to;
|
|
8224
|
+
} else {
|
|
8225
|
+
const peers = await client.listPeers();
|
|
8226
|
+
const match = peers.find((p) => p.displayName.toLowerCase() === flags.to.toLowerCase());
|
|
8227
|
+
if (!match) {
|
|
8228
|
+
render.err(`Peer "${flags.to}" not found`, `online: ${peers.map((p) => p.displayName).join(", ") || "(none)"}`);
|
|
8229
|
+
process.exit(1);
|
|
8230
|
+
}
|
|
8231
|
+
targetSpec = match.pubkey;
|
|
8232
|
+
}
|
|
8233
|
+
} else {
|
|
8234
|
+
targetSpec = client.getSessionPubkey() ?? "*";
|
|
8235
|
+
}
|
|
8236
|
+
const result = await client.scheduleMessage(targetSpec, message, deliverAt ?? 0, false, flags.cron);
|
|
8237
|
+
if (!result) {
|
|
8238
|
+
render.err("Broker did not acknowledge — check connection");
|
|
8239
|
+
process.exit(1);
|
|
8240
|
+
}
|
|
8241
|
+
if (flags.json) {
|
|
8242
|
+
console.log(JSON.stringify(result));
|
|
8243
|
+
return;
|
|
8244
|
+
}
|
|
8245
|
+
const toLabel = !flags.to || flags.to === "self" ? "yourself" : flags.to;
|
|
8246
|
+
if (isCron) {
|
|
8247
|
+
const nextFire = new Date(result.deliverAt).toLocaleString();
|
|
8248
|
+
render.ok(`recurring reminder set`, `${result.scheduledId.slice(0, 8)} · ${clay(message)} → ${toLabel} · cron ${flags.cron} · next ${nextFire}`);
|
|
8249
|
+
} else {
|
|
8250
|
+
const when = new Date(result.deliverAt).toLocaleString();
|
|
8251
|
+
render.ok(`reminder set`, `${result.scheduledId.slice(0, 8)} · ${clay(message)} → ${toLabel} at ${when}`);
|
|
8252
|
+
}
|
|
8253
|
+
});
|
|
8254
|
+
}
|
|
8255
|
+
var init_remind = __esm(() => {
|
|
8256
|
+
init_connect();
|
|
8257
|
+
init_render();
|
|
8258
|
+
init_styles();
|
|
8259
|
+
});
|
|
8260
|
+
|
|
8261
|
+
// src/commands/register.ts
|
|
8262
|
+
var exports_register = {};
|
|
8263
|
+
__export(exports_register, {
|
|
8264
|
+
register: () => register2
|
|
8265
|
+
});
|
|
8266
|
+
async function register2() {
|
|
8267
|
+
return login();
|
|
8268
|
+
}
|
|
8269
|
+
var init_register = __esm(() => {
|
|
8270
|
+
init_login();
|
|
8271
|
+
});
|
|
8272
|
+
|
|
8273
|
+
// src/commands/logout.ts
|
|
8274
|
+
var exports_logout = {};
|
|
8275
|
+
__export(exports_logout, {
|
|
8276
|
+
logout: () => logout2
|
|
8277
|
+
});
|
|
8278
|
+
async function logout2() {
|
|
8279
|
+
try {
|
|
8280
|
+
const { revoked } = await logout();
|
|
8281
|
+
if (revoked) {
|
|
8282
|
+
console.log(` ${green(icons.check)} Revoked session on claudemesh.com`);
|
|
8283
|
+
} else {
|
|
8284
|
+
console.log(` ${yellow(icons.warn)} Could not revoke session on claudemesh.com.`);
|
|
8285
|
+
console.log(` Revoke manually at https://claudemesh.com/dashboard/settings/sessions`);
|
|
8286
|
+
}
|
|
8287
|
+
console.log(` ${green(icons.check)} Removed local credentials.`);
|
|
8288
|
+
return EXIT.SUCCESS;
|
|
8289
|
+
} catch (err) {
|
|
8290
|
+
console.error(` ${icons.cross} Logout failed: ${err instanceof Error ? err.message : err}`);
|
|
8291
|
+
return EXIT.AUTH_FAILED;
|
|
8292
|
+
}
|
|
8293
|
+
}
|
|
8294
|
+
var init_logout = __esm(() => {
|
|
8295
|
+
init_facade6();
|
|
8296
|
+
init_styles();
|
|
8297
|
+
init_exit_codes();
|
|
8298
|
+
});
|
|
8299
|
+
|
|
8300
|
+
// src/commands/whoami.ts
|
|
8301
|
+
var exports_whoami = {};
|
|
8302
|
+
__export(exports_whoami, {
|
|
8303
|
+
whoami: () => whoami
|
|
8304
|
+
});
|
|
8305
|
+
async function whoami(opts) {
|
|
8306
|
+
const result = await whoAmI();
|
|
8307
|
+
if (opts.json) {
|
|
8308
|
+
console.log(JSON.stringify({ schema_version: "1.0", ...result }, null, 2));
|
|
8309
|
+
return result.signed_in || result.local ? EXIT.SUCCESS : EXIT.AUTH_FAILED;
|
|
8310
|
+
}
|
|
8311
|
+
if (!result.signed_in && !result.local) {
|
|
8312
|
+
render.err("Not signed in", "Run `claudemesh login` to sign in or `claudemesh <invite>` to join.");
|
|
8313
|
+
return EXIT.AUTH_FAILED;
|
|
8314
|
+
}
|
|
8315
|
+
render.section("whoami");
|
|
8316
|
+
if (result.signed_in) {
|
|
8317
|
+
render.kv([
|
|
8318
|
+
["user", `${bold(result.user.display_name)} ${dim(`(${result.user.email})`)}`],
|
|
8319
|
+
["token", `${result.token_source} ${dim("(~/.claudemesh/auth.json)")}`],
|
|
8320
|
+
...result.meshes ? [["meshes", `${result.meshes.owned} owned · ${result.meshes.guest} guest`]] : []
|
|
8321
|
+
]);
|
|
8322
|
+
} else {
|
|
8323
|
+
render.kv([
|
|
8324
|
+
["web", dim("not signed in · run `claudemesh login` for account features")]
|
|
8325
|
+
]);
|
|
8326
|
+
}
|
|
8327
|
+
if (result.local) {
|
|
8328
|
+
render.blank();
|
|
8329
|
+
render.kv([
|
|
8330
|
+
["local", `${result.local.meshes.length} mesh${result.local.meshes.length === 1 ? "" : "es"} · ${dim(result.local.config_path)}`]
|
|
8331
|
+
]);
|
|
8332
|
+
for (const m of result.local.meshes) {
|
|
8333
|
+
console.log(` ${clay("●")} ${bold(m.slug)} ${dim(`member ${m.member_id.slice(0, 8)}… pk ${m.pubkey_prefix}…`)}`);
|
|
8334
|
+
}
|
|
8335
|
+
}
|
|
8336
|
+
render.blank();
|
|
8337
|
+
return EXIT.SUCCESS;
|
|
8338
|
+
}
|
|
8339
|
+
var init_whoami = __esm(() => {
|
|
8340
|
+
init_facade6();
|
|
8341
|
+
init_render();
|
|
8342
|
+
init_styles();
|
|
8343
|
+
init_exit_codes();
|
|
8344
|
+
});
|
|
8345
|
+
|
|
8346
|
+
// src/daemon/lock.ts
|
|
8347
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync7 } from "node:fs";
|
|
8348
|
+
import { dirname as dirname4 } from "node:path";
|
|
8349
|
+
function acquireSingletonLock() {
|
|
8350
|
+
mkdirSync5(dirname4(DAEMON_PATHS.PID_FILE), { recursive: true, mode: 448 });
|
|
8351
|
+
if (existsSync8(DAEMON_PATHS.PID_FILE)) {
|
|
8352
|
+
const raw = readFileSync6(DAEMON_PATHS.PID_FILE, "utf8").trim();
|
|
8353
|
+
const oldPid = Number.parseInt(raw, 10);
|
|
8354
|
+
if (Number.isFinite(oldPid) && oldPid > 0 && isProcessAlive(oldPid)) {
|
|
8355
|
+
return { result: "already-running", pid: oldPid };
|
|
8356
|
+
}
|
|
8357
|
+
try {
|
|
8358
|
+
unlinkSync2(DAEMON_PATHS.PID_FILE);
|
|
8359
|
+
} catch {}
|
|
8360
|
+
writeFileSync7(DAEMON_PATHS.PID_FILE, String(process.pid), { mode: 384 });
|
|
8361
|
+
return { result: "stale", pid: process.pid };
|
|
8362
|
+
}
|
|
8363
|
+
writeFileSync7(DAEMON_PATHS.PID_FILE, String(process.pid), { mode: 384 });
|
|
8364
|
+
return { result: "acquired", pid: process.pid };
|
|
8365
|
+
}
|
|
8366
|
+
function releaseSingletonLock() {
|
|
8367
|
+
try {
|
|
8368
|
+
const raw = readFileSync6(DAEMON_PATHS.PID_FILE, "utf8").trim();
|
|
8369
|
+
if (Number.parseInt(raw, 10) === process.pid)
|
|
8370
|
+
unlinkSync2(DAEMON_PATHS.PID_FILE);
|
|
8371
|
+
} catch {}
|
|
8372
|
+
}
|
|
8373
|
+
function readRunningPid() {
|
|
8374
|
+
try {
|
|
8375
|
+
const raw = readFileSync6(DAEMON_PATHS.PID_FILE, "utf8").trim();
|
|
8376
|
+
const pid = Number.parseInt(raw, 10);
|
|
8377
|
+
if (Number.isFinite(pid) && pid > 0 && isProcessAlive(pid))
|
|
8378
|
+
return pid;
|
|
8379
|
+
} catch {}
|
|
8380
|
+
return null;
|
|
8381
|
+
}
|
|
8382
|
+
function isProcessAlive(pid) {
|
|
8383
|
+
try {
|
|
8384
|
+
process.kill(pid, 0);
|
|
8385
|
+
return true;
|
|
8386
|
+
} catch (err) {
|
|
8387
|
+
return err.code === "EPERM";
|
|
8388
|
+
}
|
|
8389
|
+
}
|
|
8390
|
+
var init_lock = __esm(() => {
|
|
8391
|
+
init_paths2();
|
|
8392
|
+
});
|
|
8393
|
+
|
|
8394
|
+
// src/daemon/db/outbox.ts
|
|
8395
|
+
function migrateOutbox(db) {
|
|
8396
|
+
db.exec(`
|
|
8397
|
+
CREATE TABLE IF NOT EXISTS outbox (
|
|
8398
|
+
id TEXT PRIMARY KEY,
|
|
8399
|
+
client_message_id TEXT NOT NULL UNIQUE,
|
|
8400
|
+
request_fingerprint BLOB NOT NULL,
|
|
8401
|
+
payload BLOB NOT NULL,
|
|
8402
|
+
enqueued_at INTEGER NOT NULL,
|
|
8403
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
8404
|
+
next_attempt_at INTEGER NOT NULL,
|
|
8405
|
+
status TEXT NOT NULL CHECK(status IN
|
|
8406
|
+
('pending','inflight','done','dead','aborted')),
|
|
8407
|
+
last_error TEXT,
|
|
8408
|
+
delivered_at INTEGER,
|
|
8409
|
+
broker_message_id TEXT,
|
|
8410
|
+
aborted_at INTEGER,
|
|
8411
|
+
aborted_by TEXT,
|
|
8412
|
+
superseded_by TEXT
|
|
8413
|
+
);
|
|
8414
|
+
CREATE INDEX IF NOT EXISTS outbox_pending
|
|
8415
|
+
ON outbox(status, next_attempt_at);
|
|
8416
|
+
CREATE INDEX IF NOT EXISTS outbox_aborted
|
|
8417
|
+
ON outbox(status, aborted_at) WHERE status = 'aborted';
|
|
8418
|
+
`);
|
|
8419
|
+
}
|
|
8420
|
+
function findByClientId(db, clientMessageId) {
|
|
8421
|
+
const row = db.prepare(`
|
|
8422
|
+
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
8423
|
+
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
8424
|
+
broker_message_id, aborted_at, aborted_by, superseded_by
|
|
8425
|
+
FROM outbox WHERE client_message_id = ?
|
|
8426
|
+
`).get(clientMessageId);
|
|
8427
|
+
return row ?? null;
|
|
8428
|
+
}
|
|
8429
|
+
function insertPending(db, input) {
|
|
8430
|
+
db.prepare(`
|
|
8431
|
+
INSERT INTO outbox (
|
|
8432
|
+
id, client_message_id, request_fingerprint, payload,
|
|
8433
|
+
enqueued_at, attempts, next_attempt_at, status
|
|
8434
|
+
) VALUES (?, ?, ?, ?, ?, 0, ?, 'pending')
|
|
8435
|
+
`).run(input.id, input.client_message_id, input.request_fingerprint, input.payload, input.now, input.now);
|
|
8436
|
+
}
|
|
8437
|
+
function fingerprintsEqual(a, b) {
|
|
8438
|
+
if (a.length !== b.length)
|
|
8439
|
+
return false;
|
|
8440
|
+
let diff = 0;
|
|
8441
|
+
for (let i = 0;i < a.length; i++)
|
|
8442
|
+
diff |= a[i] ^ b[i];
|
|
8443
|
+
return diff === 0;
|
|
8444
|
+
}
|
|
8445
|
+
function listOutbox(db, p = {}) {
|
|
8446
|
+
const where = [];
|
|
8447
|
+
const args = [];
|
|
8448
|
+
if (p.status) {
|
|
8449
|
+
where.push("status = ?");
|
|
8450
|
+
args.push(p.status);
|
|
8451
|
+
}
|
|
8452
|
+
const sql = `
|
|
8453
|
+
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
8454
|
+
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
8455
|
+
broker_message_id, aborted_at, aborted_by, superseded_by
|
|
8456
|
+
FROM outbox
|
|
8457
|
+
${where.length ? "WHERE " + where.join(" AND ") : ""}
|
|
8458
|
+
ORDER BY enqueued_at DESC
|
|
8459
|
+
LIMIT ?
|
|
8460
|
+
`;
|
|
8461
|
+
args.push(Math.min(Math.max(p.limit ?? 50, 1), 500));
|
|
8462
|
+
return db.prepare(sql).all(...args);
|
|
8463
|
+
}
|
|
8464
|
+
function findById(db, id) {
|
|
8465
|
+
return db.prepare(`
|
|
8466
|
+
SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
|
|
8467
|
+
attempts, next_attempt_at, status, last_error, delivered_at,
|
|
8468
|
+
broker_message_id, aborted_at, aborted_by, superseded_by
|
|
8469
|
+
FROM outbox WHERE id = ?
|
|
8470
|
+
`).get(id) ?? null;
|
|
8471
|
+
}
|
|
8472
|
+
function requeueDeadOrPending(db, args) {
|
|
8473
|
+
const existing = findById(db, args.id);
|
|
8474
|
+
if (!existing)
|
|
8475
|
+
return null;
|
|
8476
|
+
if (existing.status === "aborted" || existing.status === "done")
|
|
8477
|
+
return null;
|
|
8478
|
+
db.prepare(`
|
|
8479
|
+
UPDATE outbox
|
|
8480
|
+
SET status = 'aborted', aborted_at = ?, aborted_by = ?, superseded_by = ?
|
|
8481
|
+
WHERE id = ? AND status IN ('pending','inflight','dead')
|
|
8482
|
+
`).run(args.now, args.abortedBy, args.newRowId, args.id);
|
|
8483
|
+
db.prepare(`
|
|
8484
|
+
INSERT INTO outbox (
|
|
8485
|
+
id, client_message_id, request_fingerprint, payload,
|
|
8486
|
+
enqueued_at, attempts, next_attempt_at, status
|
|
8487
|
+
) VALUES (?, ?, ?, ?, ?, 0, ?, 'pending')
|
|
8488
|
+
`).run(args.newRowId, args.newClientMessageId, existing.request_fingerprint, existing.payload, args.now, args.now);
|
|
8489
|
+
return {
|
|
8490
|
+
abortedRowId: existing.id,
|
|
8491
|
+
newRowId: args.newRowId,
|
|
8492
|
+
newClientMessageId: args.newClientMessageId
|
|
8493
|
+
};
|
|
8494
|
+
}
|
|
8495
|
+
|
|
8496
|
+
// src/daemon/db/sqlite.ts
|
|
8497
|
+
async function loadSqlite() {
|
|
8498
|
+
if (cached)
|
|
8499
|
+
return cached;
|
|
8500
|
+
try {
|
|
8501
|
+
const mod = await import("node:sqlite");
|
|
8502
|
+
cached = mod.DatabaseSync;
|
|
8503
|
+
return cached;
|
|
8504
|
+
} catch (nodeErr) {
|
|
8505
|
+
try {
|
|
8506
|
+
const bunMod = await import("bun:sqlite");
|
|
8507
|
+
cached = bunMod.Database;
|
|
8508
|
+
return cached;
|
|
8509
|
+
} catch {
|
|
8510
|
+
const msg = `claudemesh daemon requires Node.js 22.5+ for the embedded SQLite store (node:sqlite), or Bun (bun:sqlite) for dev. Current: ${process.version}. Original error: ${String(nodeErr)}`;
|
|
8511
|
+
throw new Error(msg);
|
|
8512
|
+
}
|
|
8513
|
+
}
|
|
8514
|
+
}
|
|
8515
|
+
async function openSqlite(path) {
|
|
8516
|
+
const Database = await loadSqlite();
|
|
8517
|
+
const db = new Database(path);
|
|
8518
|
+
db.exec(`
|
|
8519
|
+
PRAGMA journal_mode = WAL;
|
|
8520
|
+
PRAGMA synchronous = NORMAL;
|
|
8521
|
+
PRAGMA foreign_keys = ON;
|
|
8522
|
+
PRAGMA busy_timeout = 5000;
|
|
8523
|
+
`);
|
|
8524
|
+
return db;
|
|
8525
|
+
}
|
|
8526
|
+
function inImmediateTx(db, fn) {
|
|
8527
|
+
db.exec("BEGIN IMMEDIATE");
|
|
8528
|
+
try {
|
|
8529
|
+
const out = fn();
|
|
8530
|
+
db.exec("COMMIT");
|
|
8531
|
+
return out;
|
|
8532
|
+
} catch (err) {
|
|
8533
|
+
try {
|
|
8534
|
+
db.exec("ROLLBACK");
|
|
8535
|
+
} catch {}
|
|
8536
|
+
throw err;
|
|
8537
|
+
}
|
|
8538
|
+
}
|
|
8539
|
+
var cached = null;
|
|
8540
|
+
|
|
8541
|
+
// src/daemon/fingerprint.ts
|
|
8542
|
+
import { createHash } from "node:crypto";
|
|
8543
|
+
function computeRequestFingerprint(req) {
|
|
8544
|
+
const h = createHash("sha256");
|
|
8545
|
+
h.update(String(req.envelope_version), "utf8");
|
|
8546
|
+
h.update(NUL);
|
|
8547
|
+
h.update(req.destination_kind, "utf8");
|
|
8548
|
+
h.update(NUL);
|
|
8549
|
+
h.update(req.destination_ref, "utf8");
|
|
8550
|
+
h.update(NUL);
|
|
8551
|
+
h.update(req.reply_to_id ?? "", "utf8");
|
|
8552
|
+
h.update(NUL);
|
|
8553
|
+
h.update(req.priority, "utf8");
|
|
8554
|
+
h.update(NUL);
|
|
8555
|
+
h.update(req.meta ? canonicalJson(req.meta) : "", "utf8");
|
|
8556
|
+
h.update(NUL);
|
|
8557
|
+
h.update(createHash("sha256").update(req.body).digest());
|
|
8558
|
+
return h.digest();
|
|
8559
|
+
}
|
|
8560
|
+
function canonicalJson(value) {
|
|
8561
|
+
return JSON.stringify(sortKeys(value));
|
|
8562
|
+
}
|
|
8563
|
+
function sortKeys(value) {
|
|
8564
|
+
if (Array.isArray(value))
|
|
8565
|
+
return value.map(sortKeys);
|
|
8566
|
+
if (value !== null && typeof value === "object") {
|
|
8567
|
+
const obj = value;
|
|
8568
|
+
const out = {};
|
|
8569
|
+
for (const k of Object.keys(obj).sort())
|
|
8570
|
+
out[k] = sortKeys(obj[k]);
|
|
8571
|
+
return out;
|
|
8572
|
+
}
|
|
8573
|
+
return value;
|
|
8574
|
+
}
|
|
8575
|
+
function fingerprintHexPrefix(fp, bytes = 8) {
|
|
8576
|
+
let s = "";
|
|
8577
|
+
for (let i = 0;i < bytes && i < fp.length; i++) {
|
|
8578
|
+
s += fp[i].toString(16).padStart(2, "0");
|
|
8579
|
+
}
|
|
8580
|
+
return s;
|
|
8581
|
+
}
|
|
8582
|
+
var NUL;
|
|
8583
|
+
var init_fingerprint = __esm(() => {
|
|
8584
|
+
NUL = Buffer.from([0]);
|
|
8585
|
+
});
|
|
8586
|
+
|
|
8587
|
+
// src/daemon/ipc/handlers/send.ts
|
|
8588
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
8589
|
+
function acceptSend(req, deps) {
|
|
8590
|
+
const now = (deps.now ?? Date.now)();
|
|
8591
|
+
const newId = deps.newId ?? randomUUID3;
|
|
8592
|
+
const clientId = req.client_message_id?.trim() || ulidLike(newId);
|
|
8593
|
+
const body = Buffer.from(req.message, "utf8");
|
|
8594
|
+
const fingerprint = computeRequestFingerprint({
|
|
8595
|
+
envelope_version: ENVELOPE_VERSION,
|
|
8596
|
+
destination_kind: req.destination_kind,
|
|
8597
|
+
destination_ref: req.destination_ref,
|
|
8598
|
+
reply_to_id: req.reply_to_id ?? null,
|
|
8599
|
+
priority: req.priority ?? "next",
|
|
8600
|
+
meta: req.meta ?? null,
|
|
8601
|
+
body
|
|
8602
|
+
});
|
|
8603
|
+
return inImmediateTx(deps.db, () => {
|
|
8604
|
+
const existing = findByClientId(deps.db, clientId);
|
|
8605
|
+
if (!existing) {
|
|
8606
|
+
insertPending(deps.db, {
|
|
8607
|
+
id: newId(),
|
|
8608
|
+
client_message_id: clientId,
|
|
8609
|
+
request_fingerprint: fingerprint,
|
|
8610
|
+
payload: body,
|
|
8611
|
+
now
|
|
8612
|
+
});
|
|
8613
|
+
return { kind: "accepted_pending", status: 202, client_message_id: clientId };
|
|
8614
|
+
}
|
|
8615
|
+
return decideForExistingRow(existing, fingerprint);
|
|
8616
|
+
});
|
|
8617
|
+
}
|
|
8618
|
+
function decideForExistingRow(row, fp) {
|
|
8619
|
+
const match = fingerprintsEqual(fp, row.request_fingerprint);
|
|
8620
|
+
const fpPrefix = fingerprintHexPrefix(fp);
|
|
8621
|
+
switch (row.status) {
|
|
8622
|
+
case "pending":
|
|
8623
|
+
return match ? { kind: "accepted_pending", status: 202, client_message_id: row.client_message_id } : conflict("outbox_pending_fingerprint_mismatch", fpPrefix);
|
|
8624
|
+
case "inflight":
|
|
8625
|
+
return match ? { kind: "accepted_inflight", status: 202, client_message_id: row.client_message_id } : conflict("outbox_inflight_fingerprint_mismatch", fpPrefix);
|
|
8626
|
+
case "done":
|
|
8627
|
+
return match ? {
|
|
8628
|
+
kind: "accepted_done",
|
|
8629
|
+
status: 200,
|
|
8630
|
+
client_message_id: row.client_message_id,
|
|
8631
|
+
broker_message_id: row.broker_message_id
|
|
8632
|
+
} : conflict("outbox_done_fingerprint_mismatch", fpPrefix, row.broker_message_id);
|
|
8633
|
+
case "dead":
|
|
8634
|
+
return match ? conflict("outbox_dead_fingerprint_match", fpPrefix, row.broker_message_id) : conflict("outbox_dead_fingerprint_mismatch", fpPrefix);
|
|
8635
|
+
case "aborted":
|
|
8636
|
+
return match ? conflict("outbox_aborted_fingerprint_match", fpPrefix) : conflict("outbox_aborted_fingerprint_mismatch", fpPrefix);
|
|
8637
|
+
default: {
|
|
8638
|
+
const _ = row.status;
|
|
8639
|
+
throw new Error(`unknown outbox status: ${String(_)}`);
|
|
8640
|
+
}
|
|
8641
|
+
}
|
|
8642
|
+
}
|
|
8643
|
+
function conflict(reason, fpPrefix, brokerMessageId = null) {
|
|
8644
|
+
return {
|
|
8645
|
+
kind: "conflict",
|
|
8646
|
+
status: 409,
|
|
8647
|
+
reason,
|
|
8648
|
+
daemon_fingerprint_prefix: fpPrefix,
|
|
8649
|
+
broker_message_id: brokerMessageId
|
|
8650
|
+
};
|
|
8651
|
+
}
|
|
8652
|
+
function ulidLike(newId) {
|
|
8653
|
+
return newId();
|
|
8654
|
+
}
|
|
8655
|
+
var ENVELOPE_VERSION = 1;
|
|
8656
|
+
var init_send2 = __esm(() => {
|
|
8657
|
+
init_fingerprint();
|
|
8658
|
+
});
|
|
8659
|
+
|
|
8660
|
+
// src/daemon/db/inbox.ts
|
|
8661
|
+
function migrateInbox(db) {
|
|
8662
|
+
db.exec(`
|
|
8663
|
+
CREATE TABLE IF NOT EXISTS inbox (
|
|
8664
|
+
id TEXT PRIMARY KEY,
|
|
8665
|
+
client_message_id TEXT NOT NULL UNIQUE,
|
|
8666
|
+
broker_message_id TEXT,
|
|
8667
|
+
mesh TEXT NOT NULL,
|
|
8668
|
+
topic TEXT,
|
|
8669
|
+
sender_pubkey TEXT NOT NULL,
|
|
8670
|
+
sender_name TEXT NOT NULL,
|
|
8671
|
+
body TEXT,
|
|
8672
|
+
meta TEXT,
|
|
8673
|
+
received_at INTEGER NOT NULL,
|
|
8674
|
+
reply_to_id TEXT
|
|
8675
|
+
);
|
|
8676
|
+
CREATE INDEX IF NOT EXISTS inbox_received_at ON inbox(received_at);
|
|
8677
|
+
CREATE INDEX IF NOT EXISTS inbox_topic ON inbox(topic);
|
|
8678
|
+
CREATE INDEX IF NOT EXISTS inbox_sender ON inbox(sender_pubkey);
|
|
8679
|
+
`);
|
|
8680
|
+
}
|
|
8681
|
+
function insertIfNew(db, row) {
|
|
8682
|
+
const before = db.prepare(`SELECT id FROM inbox WHERE client_message_id = ?`).get(row.client_message_id);
|
|
8683
|
+
if (before)
|
|
8684
|
+
return null;
|
|
8685
|
+
db.prepare(`
|
|
8686
|
+
INSERT INTO inbox (
|
|
8687
|
+
id, client_message_id, broker_message_id, mesh, topic,
|
|
8688
|
+
sender_pubkey, sender_name, body, meta, received_at, reply_to_id
|
|
8689
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
8690
|
+
ON CONFLICT(client_message_id) DO NOTHING
|
|
8691
|
+
`).run(row.id, row.client_message_id, row.broker_message_id, row.mesh, row.topic, row.sender_pubkey, row.sender_name, row.body, row.meta, row.received_at, row.reply_to_id);
|
|
8692
|
+
const after = db.prepare(`SELECT id FROM inbox WHERE client_message_id = ?`).get(row.client_message_id);
|
|
8693
|
+
return after?.id === row.id ? row.id : null;
|
|
8694
|
+
}
|
|
8695
|
+
function listInbox(db, p) {
|
|
8696
|
+
const where = [];
|
|
8697
|
+
const args = [];
|
|
8698
|
+
if (p.since !== undefined) {
|
|
8699
|
+
where.push("received_at >= ?");
|
|
8700
|
+
args.push(p.since);
|
|
8701
|
+
}
|
|
8702
|
+
if (p.topic !== undefined) {
|
|
8703
|
+
where.push("topic = ?");
|
|
8704
|
+
args.push(p.topic);
|
|
8705
|
+
}
|
|
8706
|
+
if (p.fromPubkey !== undefined) {
|
|
8707
|
+
where.push("sender_pubkey = ?");
|
|
8708
|
+
args.push(p.fromPubkey);
|
|
8709
|
+
}
|
|
8710
|
+
const sql = `
|
|
8711
|
+
SELECT id, client_message_id, broker_message_id, mesh, topic,
|
|
8712
|
+
sender_pubkey, sender_name, body, meta, received_at, reply_to_id
|
|
8713
|
+
FROM inbox
|
|
8714
|
+
${where.length ? "WHERE " + where.join(" AND ") : ""}
|
|
8715
|
+
ORDER BY received_at DESC
|
|
8716
|
+
LIMIT ?
|
|
8717
|
+
`;
|
|
8718
|
+
args.push(Math.min(Math.max(p.limit ?? 100, 1), 1000));
|
|
8719
|
+
return db.prepare(sql).all(...args);
|
|
8720
|
+
}
|
|
8721
|
+
|
|
8722
|
+
// src/daemon/events.ts
|
|
8723
|
+
class EventBus {
|
|
8724
|
+
subs = new Set;
|
|
8725
|
+
publish(kind, data) {
|
|
8726
|
+
const e = { kind, ts: new Date().toISOString(), data };
|
|
8727
|
+
for (const s of this.subs) {
|
|
8728
|
+
try {
|
|
8729
|
+
s(e);
|
|
8730
|
+
} catch {}
|
|
8731
|
+
}
|
|
8732
|
+
}
|
|
8733
|
+
subscribe(fn) {
|
|
8734
|
+
this.subs.add(fn);
|
|
8735
|
+
return () => this.subs.delete(fn);
|
|
8736
|
+
}
|
|
8737
|
+
}
|
|
8738
|
+
function writeSse(res, e, idCounter) {
|
|
8739
|
+
res.write(`id: ${idCounter}
|
|
8740
|
+
`);
|
|
8741
|
+
res.write(`event: ${e.kind}
|
|
8742
|
+
`);
|
|
8743
|
+
res.write(`data: ${JSON.stringify({ ts: e.ts, ...e.data })}
|
|
8744
|
+
|
|
8745
|
+
`);
|
|
8746
|
+
}
|
|
8747
|
+
function bindSseStream(res, bus) {
|
|
8748
|
+
res.statusCode = 200;
|
|
8749
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
8750
|
+
res.setHeader("Cache-Control", "no-cache, no-transform");
|
|
8751
|
+
res.setHeader("Connection", "keep-alive");
|
|
8752
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
8753
|
+
res.write(`: connected
|
|
8754
|
+
|
|
8755
|
+
`);
|
|
8756
|
+
let counter = 0;
|
|
8757
|
+
const unsubscribe = bus.subscribe((e) => writeSse(res, e, ++counter));
|
|
8758
|
+
const heartbeat = setInterval(() => {
|
|
8759
|
+
try {
|
|
8760
|
+
res.write(`: keepalive
|
|
8761
|
+
|
|
8762
|
+
`);
|
|
8763
|
+
} catch {}
|
|
8764
|
+
}, 15000);
|
|
8765
|
+
const cleanup = () => {
|
|
8766
|
+
clearInterval(heartbeat);
|
|
8767
|
+
unsubscribe();
|
|
8768
|
+
try {
|
|
8769
|
+
res.end();
|
|
8770
|
+
} catch {}
|
|
8771
|
+
};
|
|
8772
|
+
res.on("close", cleanup);
|
|
8773
|
+
res.on("error", cleanup);
|
|
8774
|
+
return cleanup;
|
|
8775
|
+
}
|
|
8776
|
+
|
|
8777
|
+
// src/daemon/ipc/server.ts
|
|
8778
|
+
import { createServer as createServer2 } from "node:http";
|
|
8779
|
+
import { chmodSync as chmodSync3, existsSync as existsSync9, unlinkSync as unlinkSync3 } from "node:fs";
|
|
8780
|
+
import { timingSafeEqual } from "node:crypto";
|
|
8781
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
8782
|
+
function startIpcServer(opts) {
|
|
8783
|
+
const log2 = opts.log ?? defaultLogger;
|
|
8784
|
+
const handler = makeHandler({
|
|
8785
|
+
localToken: opts.localToken,
|
|
8786
|
+
publicHealthCheck: !!opts.publicHealthCheck,
|
|
8787
|
+
log: log2,
|
|
8788
|
+
outboxDb: opts.outboxDb,
|
|
8789
|
+
inboxDb: opts.inboxDb,
|
|
8790
|
+
bus: opts.bus,
|
|
8791
|
+
broker: opts.broker,
|
|
8792
|
+
onPendingInserted: opts.onPendingInserted
|
|
8793
|
+
});
|
|
8794
|
+
if (existsSync9(DAEMON_PATHS.SOCK_FILE)) {
|
|
8795
|
+
try {
|
|
8796
|
+
unlinkSync3(DAEMON_PATHS.SOCK_FILE);
|
|
8797
|
+
} catch {}
|
|
8798
|
+
}
|
|
8799
|
+
const uds = createServer2(handler);
|
|
8800
|
+
const udsReady = new Promise((resolve, reject) => {
|
|
8801
|
+
uds.once("error", reject);
|
|
8802
|
+
uds.listen(DAEMON_PATHS.SOCK_FILE, () => {
|
|
8803
|
+
try {
|
|
8804
|
+
chmodSync3(DAEMON_PATHS.SOCK_FILE, 384);
|
|
8805
|
+
} catch (err) {
|
|
8806
|
+
log2("warn", "uds_chmod_failed", { err: String(err) });
|
|
8807
|
+
}
|
|
8808
|
+
resolve();
|
|
8809
|
+
});
|
|
8810
|
+
});
|
|
8811
|
+
let tcp = null;
|
|
8812
|
+
let tcpReady = Promise.resolve();
|
|
8813
|
+
if (opts.tcpEnabled !== false) {
|
|
8814
|
+
tcp = createServer2(handler);
|
|
8815
|
+
tcpReady = new Promise((resolve, reject) => {
|
|
8816
|
+
tcp.once("error", reject);
|
|
8817
|
+
tcp.listen(opts.tcpPort ?? DAEMON_TCP_DEFAULT_PORT, DAEMON_TCP_HOST, () => resolve());
|
|
8818
|
+
});
|
|
8819
|
+
}
|
|
8820
|
+
return {
|
|
8821
|
+
uds,
|
|
8822
|
+
tcp,
|
|
8823
|
+
ready: Promise.all([udsReady, tcpReady]).then(() => {
|
|
8824
|
+
return;
|
|
8825
|
+
}),
|
|
8826
|
+
close: async () => {
|
|
8827
|
+
await Promise.allSettled([
|
|
8828
|
+
new Promise((res) => uds.close(() => res())),
|
|
8829
|
+
tcp ? new Promise((res) => tcp.close(() => res())) : Promise.resolve()
|
|
8830
|
+
]);
|
|
8831
|
+
try {
|
|
8832
|
+
unlinkSync3(DAEMON_PATHS.SOCK_FILE);
|
|
8833
|
+
} catch {}
|
|
8834
|
+
}
|
|
8835
|
+
};
|
|
8836
|
+
}
|
|
8837
|
+
function defaultLogger(level, msg, meta) {
|
|
8838
|
+
const line = JSON.stringify({ level, msg, ...meta, ts: new Date().toISOString() });
|
|
8839
|
+
if (level === "info")
|
|
8840
|
+
process.stdout.write(line + `
|
|
8841
|
+
`);
|
|
8842
|
+
else
|
|
8843
|
+
process.stderr.write(line + `
|
|
8844
|
+
`);
|
|
8845
|
+
}
|
|
8846
|
+
function makeHandler(opts) {
|
|
8847
|
+
const tokenBytes = Buffer.from(opts.localToken, "utf8");
|
|
8848
|
+
return async (req, res) => {
|
|
8849
|
+
const url = new URL(req.url ?? "/", "http://daemon.local");
|
|
8850
|
+
if (url.searchParams.has("token")) {
|
|
8851
|
+
opts.log("warn", "ipc_token_in_query_string_rejected", { path: url.pathname });
|
|
8852
|
+
respond(res, 400, { error: "token must be in Authorization header, not query string" });
|
|
8853
|
+
return;
|
|
8854
|
+
}
|
|
8855
|
+
const host = (req.headers.host ?? "").toLowerCase().split(":")[0]?.trim() ?? "";
|
|
8856
|
+
if (host && host !== "localhost" && host !== "127.0.0.1" && host !== "[::1]" && host !== "::1") {
|
|
8857
|
+
respond(res, 403, { error: "forbidden host" });
|
|
8858
|
+
return;
|
|
8859
|
+
}
|
|
8860
|
+
if (req.headers.origin) {
|
|
8861
|
+
respond(res, 403, { error: "forbidden origin" });
|
|
8862
|
+
return;
|
|
8863
|
+
}
|
|
8864
|
+
const isUds = req.socket.remoteAddress === undefined;
|
|
8865
|
+
const isPublicHealth = opts.publicHealthCheck && url.pathname === "/v1/health";
|
|
8866
|
+
if (!isUds && !isPublicHealth) {
|
|
8867
|
+
const authz = req.headers.authorization ?? "";
|
|
8868
|
+
const m = /^Bearer\s+(.+)$/.exec(authz.trim());
|
|
8869
|
+
if (!m || !m[1]) {
|
|
8870
|
+
respond(res, 401, { error: "missing bearer token" });
|
|
8871
|
+
return;
|
|
8872
|
+
}
|
|
8873
|
+
const provided = Buffer.from(m[1], "utf8");
|
|
8874
|
+
if (provided.length !== tokenBytes.length || !timingSafeEqual(provided, tokenBytes)) {
|
|
8875
|
+
opts.log("warn", "ipc_bearer_mismatch", { path: url.pathname });
|
|
8876
|
+
respond(res, 401, { error: "invalid bearer token" });
|
|
8877
|
+
return;
|
|
8878
|
+
}
|
|
8879
|
+
}
|
|
8880
|
+
if (req.method === "GET" && url.pathname === "/v1/version") {
|
|
8881
|
+
respond(res, 200, {
|
|
8882
|
+
daemon_version: VERSION,
|
|
8883
|
+
ipc_api: "v1",
|
|
8884
|
+
ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile"],
|
|
8885
|
+
schema_version: 1
|
|
8886
|
+
});
|
|
8887
|
+
return;
|
|
8888
|
+
}
|
|
8889
|
+
if (req.method === "GET" && url.pathname === "/v1/health") {
|
|
8890
|
+
respond(res, 200, { ok: true, pid: process.pid });
|
|
8891
|
+
return;
|
|
8892
|
+
}
|
|
8893
|
+
if (req.method === "GET" && url.pathname === "/v1/events") {
|
|
8894
|
+
if (!opts.bus) {
|
|
8895
|
+
respond(res, 503, { error: "event bus not initialised" });
|
|
8896
|
+
return;
|
|
8897
|
+
}
|
|
8898
|
+
bindSseStream(res, opts.bus);
|
|
8899
|
+
return;
|
|
8900
|
+
}
|
|
8901
|
+
if (req.method === "GET" && url.pathname === "/v1/peers") {
|
|
8902
|
+
if (!opts.broker) {
|
|
8903
|
+
respond(res, 503, { error: "broker not initialised" });
|
|
8904
|
+
return;
|
|
8905
|
+
}
|
|
8906
|
+
try {
|
|
8907
|
+
const peers = await opts.broker.listPeers();
|
|
8908
|
+
respond(res, 200, { peers });
|
|
8909
|
+
} catch (e) {
|
|
8910
|
+
respond(res, 502, { error: "broker_unreachable", detail: String(e) });
|
|
8911
|
+
}
|
|
8912
|
+
return;
|
|
8913
|
+
}
|
|
8914
|
+
if (req.method === "POST" && url.pathname === "/v1/profile") {
|
|
8915
|
+
if (!opts.broker) {
|
|
8916
|
+
respond(res, 503, { error: "broker not initialised" });
|
|
8917
|
+
return;
|
|
8918
|
+
}
|
|
8919
|
+
try {
|
|
8920
|
+
const body = await readJsonBody(req, 16 * 1024);
|
|
8921
|
+
if (!body) {
|
|
8922
|
+
respond(res, 400, { error: "expected JSON object" });
|
|
8923
|
+
return;
|
|
8924
|
+
}
|
|
8925
|
+
const updates = {};
|
|
8926
|
+
if (typeof body.summary === "string")
|
|
8927
|
+
opts.broker.setSummary(body.summary);
|
|
8928
|
+
if (body.status === "idle" || body.status === "working" || body.status === "dnd")
|
|
8929
|
+
opts.broker.setStatus(body.status);
|
|
8930
|
+
if (typeof body.visible === "boolean")
|
|
8931
|
+
opts.broker.setVisible(body.visible);
|
|
8932
|
+
const profile = {};
|
|
8933
|
+
if (typeof body.avatar === "string")
|
|
8934
|
+
profile.avatar = body.avatar;
|
|
8935
|
+
if (typeof body.title === "string")
|
|
8936
|
+
profile.title = body.title;
|
|
8937
|
+
if (typeof body.bio === "string")
|
|
8938
|
+
profile.bio = body.bio;
|
|
8939
|
+
if (Array.isArray(body.capabilities))
|
|
8940
|
+
profile.capabilities = body.capabilities.filter((c) => typeof c === "string");
|
|
8941
|
+
if (Object.keys(profile).length > 0)
|
|
8942
|
+
opts.broker.setProfile(profile);
|
|
8943
|
+
Object.assign(updates, body);
|
|
8944
|
+
respond(res, 200, { ok: true, applied: Object.keys(updates) });
|
|
8945
|
+
} catch (e) {
|
|
8946
|
+
respond(res, 400, { error: String(e) });
|
|
8947
|
+
}
|
|
8948
|
+
return;
|
|
8949
|
+
}
|
|
8950
|
+
if (req.method === "GET" && url.pathname === "/v1/inbox") {
|
|
8951
|
+
if (!opts.inboxDb) {
|
|
8952
|
+
respond(res, 503, { error: "inbox not initialised" });
|
|
8953
|
+
return;
|
|
8954
|
+
}
|
|
8955
|
+
const sinceRaw = url.searchParams.get("since");
|
|
8956
|
+
const since = sinceRaw ? Date.parse(sinceRaw) : undefined;
|
|
8957
|
+
const topic = url.searchParams.get("topic") ?? undefined;
|
|
8958
|
+
const fromPubkey = url.searchParams.get("from") ?? undefined;
|
|
8959
|
+
const limitRaw = url.searchParams.get("limit");
|
|
8960
|
+
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : undefined;
|
|
8961
|
+
const rows = listInbox(opts.inboxDb, {
|
|
8962
|
+
since: Number.isFinite(since) ? since : undefined,
|
|
8963
|
+
topic,
|
|
8964
|
+
fromPubkey,
|
|
8965
|
+
limit: Number.isFinite(limit ?? NaN) ? limit : undefined
|
|
8966
|
+
});
|
|
8967
|
+
respond(res, 200, {
|
|
8968
|
+
items: rows.map((r) => ({
|
|
8969
|
+
id: r.id,
|
|
8970
|
+
client_message_id: r.client_message_id,
|
|
8971
|
+
broker_message_id: r.broker_message_id,
|
|
8972
|
+
mesh: r.mesh,
|
|
8973
|
+
topic: r.topic,
|
|
8974
|
+
sender_pubkey: r.sender_pubkey,
|
|
8975
|
+
sender_name: r.sender_name,
|
|
8976
|
+
body: r.body,
|
|
8977
|
+
received_at: new Date(r.received_at).toISOString(),
|
|
8978
|
+
reply_to_id: r.reply_to_id
|
|
8979
|
+
}))
|
|
8980
|
+
});
|
|
8981
|
+
return;
|
|
8982
|
+
}
|
|
8983
|
+
if (req.method === "GET" && url.pathname === "/v1/outbox") {
|
|
8984
|
+
if (!opts.outboxDb) {
|
|
8985
|
+
respond(res, 503, { error: "outbox not initialised" });
|
|
8986
|
+
return;
|
|
8987
|
+
}
|
|
8988
|
+
const statusParam = url.searchParams.get("status") ?? undefined;
|
|
8989
|
+
const allowed = ["pending", "inflight", "done", "dead", "aborted"];
|
|
8990
|
+
const status = statusParam && allowed.includes(statusParam) ? statusParam : undefined;
|
|
8991
|
+
const limitRaw = url.searchParams.get("limit");
|
|
8992
|
+
const limit = limitRaw ? Number.parseInt(limitRaw, 10) : undefined;
|
|
8993
|
+
const rows = listOutbox(opts.outboxDb, {
|
|
8994
|
+
status,
|
|
8995
|
+
limit: Number.isFinite(limit ?? NaN) ? limit : undefined
|
|
8996
|
+
});
|
|
8997
|
+
respond(res, 200, {
|
|
8998
|
+
items: rows.map((r) => ({
|
|
8999
|
+
id: r.id,
|
|
9000
|
+
client_message_id: r.client_message_id,
|
|
9001
|
+
status: r.status,
|
|
9002
|
+
attempts: r.attempts,
|
|
9003
|
+
enqueued_at: new Date(r.enqueued_at).toISOString(),
|
|
9004
|
+
next_attempt_at: new Date(r.next_attempt_at).toISOString(),
|
|
9005
|
+
delivered_at: r.delivered_at ? new Date(r.delivered_at).toISOString() : null,
|
|
9006
|
+
broker_message_id: r.broker_message_id,
|
|
9007
|
+
last_error: r.last_error,
|
|
9008
|
+
aborted_at: r.aborted_at ? new Date(r.aborted_at).toISOString() : null,
|
|
9009
|
+
aborted_by: r.aborted_by,
|
|
9010
|
+
superseded_by: r.superseded_by,
|
|
9011
|
+
payload_bytes: r.payload?.byteLength ?? 0
|
|
9012
|
+
}))
|
|
9013
|
+
});
|
|
9014
|
+
return;
|
|
9015
|
+
}
|
|
9016
|
+
if (req.method === "POST" && url.pathname === "/v1/outbox/requeue") {
|
|
9017
|
+
if (!opts.outboxDb) {
|
|
9018
|
+
respond(res, 503, { error: "outbox not initialised" });
|
|
9019
|
+
return;
|
|
9020
|
+
}
|
|
9021
|
+
try {
|
|
9022
|
+
const body = await readJsonBody(req, 4 * 1024);
|
|
9023
|
+
if (!body || typeof body.id !== "string") {
|
|
9024
|
+
respond(res, 400, { error: "missing 'id'" });
|
|
9025
|
+
return;
|
|
9026
|
+
}
|
|
9027
|
+
const newId = typeof body.new_client_message_id === "string" && body.new_client_message_id.trim() ? body.new_client_message_id.trim() : randomUUID4();
|
|
9028
|
+
const result = requeueDeadOrPending(opts.outboxDb, {
|
|
9029
|
+
id: body.id,
|
|
9030
|
+
newClientMessageId: newId,
|
|
9031
|
+
newRowId: randomUUID4(),
|
|
9032
|
+
now: Date.now(),
|
|
9033
|
+
abortedBy: typeof body.aborted_by === "string" ? body.aborted_by : "operator"
|
|
9034
|
+
});
|
|
9035
|
+
if (!result) {
|
|
9036
|
+
respond(res, 409, { error: "row not found, already aborted, or already done" });
|
|
9037
|
+
return;
|
|
9038
|
+
}
|
|
9039
|
+
respond(res, 200, {
|
|
9040
|
+
aborted_row_id: result.abortedRowId,
|
|
9041
|
+
new_row_id: result.newRowId,
|
|
9042
|
+
new_client_message_id: result.newClientMessageId
|
|
9043
|
+
});
|
|
9044
|
+
opts.onPendingInserted?.();
|
|
9045
|
+
} catch (e) {
|
|
9046
|
+
respond(res, 400, { error: String(e) });
|
|
9047
|
+
}
|
|
9048
|
+
return;
|
|
9049
|
+
}
|
|
9050
|
+
if (req.method === "POST" && url.pathname === "/v1/send") {
|
|
9051
|
+
if (!opts.outboxDb) {
|
|
9052
|
+
respond(res, 503, { error: "outbox not initialised" });
|
|
9053
|
+
return;
|
|
9054
|
+
}
|
|
9055
|
+
try {
|
|
9056
|
+
const body = await readJsonBody(req, 256 * 1024);
|
|
9057
|
+
const parsed = parseSendRequest(body, req.headers["idempotency-key"]);
|
|
9058
|
+
if ("error" in parsed) {
|
|
9059
|
+
respond(res, 400, { error: parsed.error });
|
|
9060
|
+
return;
|
|
9061
|
+
}
|
|
9062
|
+
const outcome = acceptSend(parsed.req, { db: opts.outboxDb });
|
|
9063
|
+
switch (outcome.kind) {
|
|
9064
|
+
case "accepted_pending":
|
|
9065
|
+
respond(res, outcome.status, {
|
|
9066
|
+
client_message_id: outcome.client_message_id,
|
|
9067
|
+
status: "queued"
|
|
9068
|
+
});
|
|
9069
|
+
opts.onPendingInserted?.();
|
|
9070
|
+
return;
|
|
9071
|
+
case "accepted_inflight":
|
|
9072
|
+
respond(res, outcome.status, {
|
|
9073
|
+
client_message_id: outcome.client_message_id,
|
|
9074
|
+
status: "inflight"
|
|
9075
|
+
});
|
|
9076
|
+
return;
|
|
9077
|
+
case "accepted_done":
|
|
9078
|
+
respond(res, outcome.status, {
|
|
9079
|
+
client_message_id: outcome.client_message_id,
|
|
9080
|
+
broker_message_id: outcome.broker_message_id,
|
|
9081
|
+
duplicate: true
|
|
9082
|
+
});
|
|
9083
|
+
return;
|
|
9084
|
+
case "conflict":
|
|
9085
|
+
respond(res, outcome.status, {
|
|
9086
|
+
error: "idempotency_key_reused",
|
|
9087
|
+
conflict: outcome.reason,
|
|
9088
|
+
daemon_fingerprint_prefix: outcome.daemon_fingerprint_prefix,
|
|
9089
|
+
broker_message_id: outcome.broker_message_id ?? null
|
|
9090
|
+
});
|
|
9091
|
+
return;
|
|
9092
|
+
}
|
|
9093
|
+
} catch (err) {
|
|
9094
|
+
opts.log("error", "ipc_send_failed", { err: String(err) });
|
|
9095
|
+
respond(res, 500, { error: "internal" });
|
|
9096
|
+
return;
|
|
9097
|
+
}
|
|
9098
|
+
}
|
|
9099
|
+
respond(res, 404, { error: "not found" });
|
|
9100
|
+
};
|
|
9101
|
+
}
|
|
9102
|
+
async function readJsonBody(req, maxBytes) {
|
|
9103
|
+
const chunks = [];
|
|
9104
|
+
let total = 0;
|
|
9105
|
+
for await (const chunk of req) {
|
|
9106
|
+
const buf = chunk;
|
|
9107
|
+
total += buf.length;
|
|
9108
|
+
if (total > maxBytes)
|
|
9109
|
+
throw new Error("payload_too_large");
|
|
9110
|
+
chunks.push(buf);
|
|
9111
|
+
}
|
|
9112
|
+
if (total === 0)
|
|
9113
|
+
return null;
|
|
9114
|
+
const text = Buffer.concat(chunks).toString("utf8");
|
|
9115
|
+
try {
|
|
9116
|
+
return JSON.parse(text);
|
|
9117
|
+
} catch {
|
|
9118
|
+
throw new Error("invalid_json");
|
|
9119
|
+
}
|
|
9120
|
+
}
|
|
9121
|
+
function parseSendRequest(body, idempotencyHeader) {
|
|
9122
|
+
if (!body || typeof body !== "object")
|
|
9123
|
+
return { error: "expected JSON object" };
|
|
9124
|
+
const b = body;
|
|
9125
|
+
const to = typeof b.to === "string" ? b.to.trim() : "";
|
|
9126
|
+
const message = typeof b.message === "string" ? b.message : "";
|
|
9127
|
+
if (!to)
|
|
9128
|
+
return { error: "missing 'to'" };
|
|
9129
|
+
if (!message)
|
|
9130
|
+
return { error: "missing 'message'" };
|
|
9131
|
+
const priority = b.priority;
|
|
9132
|
+
if (priority !== undefined && priority !== "now" && priority !== "next" && priority !== "low") {
|
|
9133
|
+
return { error: "priority must be 'now' | 'next' | 'low'" };
|
|
9134
|
+
}
|
|
9135
|
+
const meta = b.meta;
|
|
9136
|
+
if (meta !== undefined && meta !== null && (typeof meta !== "object" || Array.isArray(meta))) {
|
|
9137
|
+
return { error: "'meta' must be an object" };
|
|
9138
|
+
}
|
|
9139
|
+
let destination_kind;
|
|
9140
|
+
let destination_ref;
|
|
9141
|
+
if (to.startsWith("@")) {
|
|
9142
|
+
destination_kind = "topic";
|
|
9143
|
+
destination_ref = to.slice(1);
|
|
9144
|
+
} else if (to === "*") {
|
|
9145
|
+
destination_kind = "topic";
|
|
9146
|
+
destination_ref = "*";
|
|
9147
|
+
} else {
|
|
9148
|
+
destination_kind = "dm";
|
|
9149
|
+
destination_ref = to;
|
|
9150
|
+
}
|
|
9151
|
+
const headerId = Array.isArray(idempotencyHeader) ? idempotencyHeader[0] : idempotencyHeader;
|
|
9152
|
+
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;
|
|
9153
|
+
const reply_to_id = typeof b.reply_to_id === "string" ? b.reply_to_id : undefined;
|
|
9154
|
+
return {
|
|
9155
|
+
req: {
|
|
9156
|
+
to,
|
|
9157
|
+
message,
|
|
9158
|
+
priority,
|
|
9159
|
+
meta: meta ?? undefined,
|
|
9160
|
+
reply_to_id,
|
|
9161
|
+
client_message_id,
|
|
9162
|
+
destination_kind,
|
|
9163
|
+
destination_ref
|
|
9164
|
+
}
|
|
9165
|
+
};
|
|
9166
|
+
}
|
|
9167
|
+
function respond(res, status, body) {
|
|
9168
|
+
const json = JSON.stringify(body);
|
|
9169
|
+
res.statusCode = status;
|
|
9170
|
+
res.setHeader("Content-Type", "application/json");
|
|
9171
|
+
res.setHeader("Content-Length", Buffer.byteLength(json));
|
|
9172
|
+
res.end(json);
|
|
9173
|
+
}
|
|
9174
|
+
var init_server = __esm(() => {
|
|
9175
|
+
init_paths2();
|
|
9176
|
+
init_send2();
|
|
9177
|
+
init_urls();
|
|
9178
|
+
});
|
|
9179
|
+
|
|
9180
|
+
// src/daemon/broker.ts
|
|
9181
|
+
import WebSocket2 from "ws";
|
|
9182
|
+
|
|
9183
|
+
class DaemonBrokerClient {
|
|
9184
|
+
mesh;
|
|
9185
|
+
opts;
|
|
9186
|
+
ws = null;
|
|
9187
|
+
_status = "closed";
|
|
9188
|
+
closed = false;
|
|
9189
|
+
reconnectAttempt = 0;
|
|
9190
|
+
reconnectTimer = null;
|
|
9191
|
+
helloTimer = null;
|
|
9192
|
+
pendingAcks = new Map;
|
|
9193
|
+
peerListResolvers = new Map;
|
|
9194
|
+
sessionPubkey = null;
|
|
9195
|
+
sessionSecretKey = null;
|
|
9196
|
+
opens = [];
|
|
9197
|
+
reqCounter = 0;
|
|
9198
|
+
constructor(mesh, opts = {}) {
|
|
9199
|
+
this.mesh = mesh;
|
|
9200
|
+
this.opts = opts;
|
|
9201
|
+
}
|
|
9202
|
+
get status() {
|
|
9203
|
+
return this._status;
|
|
9204
|
+
}
|
|
9205
|
+
get meshSlug() {
|
|
9206
|
+
return this.mesh.slug;
|
|
9207
|
+
}
|
|
9208
|
+
get meshId() {
|
|
9209
|
+
return this.mesh.meshId;
|
|
9210
|
+
}
|
|
9211
|
+
log = (level, msg, meta) => {
|
|
9212
|
+
(this.opts.log ?? defaultLog)(level, msg, { mesh: this.mesh.slug, ...meta });
|
|
9213
|
+
};
|
|
9214
|
+
setConnStatus(s) {
|
|
9215
|
+
if (this._status === s)
|
|
9216
|
+
return;
|
|
9217
|
+
this._status = s;
|
|
9218
|
+
this.opts.onStatusChange?.(s);
|
|
9219
|
+
}
|
|
9220
|
+
async connect() {
|
|
9221
|
+
if (this.closed)
|
|
9222
|
+
throw new Error("client_closed");
|
|
9223
|
+
if (this._status === "connecting" || this._status === "open")
|
|
9224
|
+
return;
|
|
9225
|
+
this.setConnStatus("connecting");
|
|
9226
|
+
const ws = new WebSocket2(this.mesh.brokerUrl);
|
|
9227
|
+
this.ws = ws;
|
|
9228
|
+
return new Promise((resolve, reject) => {
|
|
9229
|
+
ws.on("open", async () => {
|
|
9230
|
+
try {
|
|
9231
|
+
if (!this.sessionPubkey) {
|
|
9232
|
+
const { generateKeypair: generateKeypair4 } = await Promise.resolve().then(() => (init_facade7(), exports_facade4));
|
|
9233
|
+
const kp = await generateKeypair4();
|
|
9234
|
+
this.sessionPubkey = kp.publicKey;
|
|
9235
|
+
this.sessionSecretKey = kp.secretKey;
|
|
9236
|
+
}
|
|
9237
|
+
const { timestamp: timestamp2, signature } = await signHello(this.mesh.meshId, this.mesh.memberId, this.mesh.pubkey, this.mesh.secretKey);
|
|
9238
|
+
ws.send(JSON.stringify({
|
|
9239
|
+
type: "hello",
|
|
9240
|
+
meshId: this.mesh.meshId,
|
|
9241
|
+
memberId: this.mesh.memberId,
|
|
9242
|
+
pubkey: this.mesh.pubkey,
|
|
9243
|
+
sessionPubkey: this.sessionPubkey,
|
|
9244
|
+
displayName: this.opts.displayName,
|
|
9245
|
+
sessionId: `daemon-${process.pid}`,
|
|
9246
|
+
pid: process.pid,
|
|
9247
|
+
cwd: process.cwd(),
|
|
9248
|
+
hostname: __require("node:os").hostname(),
|
|
9249
|
+
peerType: "ai",
|
|
9250
|
+
channel: "claudemesh-daemon",
|
|
9251
|
+
timestamp: timestamp2,
|
|
9252
|
+
signature
|
|
9253
|
+
}));
|
|
9254
|
+
this.helloTimer = setTimeout(() => {
|
|
9255
|
+
this.log("warn", "broker_hello_ack_timeout");
|
|
9256
|
+
try {
|
|
9257
|
+
ws.close();
|
|
9258
|
+
} catch {}
|
|
9259
|
+
reject(new Error("hello_ack_timeout"));
|
|
9260
|
+
}, HELLO_ACK_TIMEOUT_MS2);
|
|
9261
|
+
} catch (e) {
|
|
9262
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
9263
|
+
}
|
|
9264
|
+
});
|
|
9265
|
+
ws.on("message", (raw) => {
|
|
9266
|
+
let msg;
|
|
9267
|
+
try {
|
|
9268
|
+
msg = JSON.parse(raw.toString());
|
|
9269
|
+
} catch {
|
|
9270
|
+
return;
|
|
9271
|
+
}
|
|
9272
|
+
if (msg.type === "hello_ack") {
|
|
9273
|
+
if (this.helloTimer)
|
|
9274
|
+
clearTimeout(this.helloTimer);
|
|
9275
|
+
this.helloTimer = null;
|
|
9276
|
+
this.setConnStatus("open");
|
|
9277
|
+
this.reconnectAttempt = 0;
|
|
9278
|
+
const queued = this.opens.slice();
|
|
9279
|
+
this.opens.length = 0;
|
|
9280
|
+
for (const fn of queued) {
|
|
9281
|
+
try {
|
|
9282
|
+
fn();
|
|
9283
|
+
} catch (e) {
|
|
9284
|
+
this.log("warn", "open_handler_failed", { err: String(e) });
|
|
9285
|
+
}
|
|
9286
|
+
}
|
|
9287
|
+
resolve();
|
|
9288
|
+
return;
|
|
9289
|
+
}
|
|
9290
|
+
if (msg.type === "ack") {
|
|
9291
|
+
const id = String(msg.id ?? "");
|
|
9292
|
+
const ack = this.pendingAcks.get(id);
|
|
9293
|
+
if (ack) {
|
|
9294
|
+
this.pendingAcks.delete(id);
|
|
9295
|
+
clearTimeout(ack.timer);
|
|
9296
|
+
if (typeof msg.error === "string" && msg.error.length > 0) {
|
|
9297
|
+
ack.resolve({ ok: false, error: msg.error, permanent: classifyPermanent(msg.error) });
|
|
9298
|
+
} else {
|
|
9299
|
+
ack.resolve({ ok: true, messageId: String(msg.messageId ?? id) });
|
|
9300
|
+
}
|
|
9301
|
+
}
|
|
9302
|
+
return;
|
|
9303
|
+
}
|
|
9304
|
+
if (msg.type === "peers_list") {
|
|
9305
|
+
const reqId = String(msg._reqId ?? "");
|
|
9306
|
+
const pending = this.peerListResolvers.get(reqId);
|
|
9307
|
+
if (pending) {
|
|
9308
|
+
this.peerListResolvers.delete(reqId);
|
|
9309
|
+
clearTimeout(pending.timer);
|
|
9310
|
+
pending.resolve(Array.isArray(msg.peers) ? msg.peers : []);
|
|
9311
|
+
}
|
|
9312
|
+
return;
|
|
9313
|
+
}
|
|
9314
|
+
if (msg.type === "push" || msg.type === "inbound") {
|
|
9315
|
+
this.opts.onPush?.(msg);
|
|
9316
|
+
return;
|
|
9317
|
+
}
|
|
9318
|
+
});
|
|
9319
|
+
ws.on("close", (code, reason) => {
|
|
9320
|
+
if (this.helloTimer) {
|
|
9321
|
+
clearTimeout(this.helloTimer);
|
|
9322
|
+
this.helloTimer = null;
|
|
9323
|
+
}
|
|
9324
|
+
this.failPendingAcks(`broker_disconnected_${code}`);
|
|
9325
|
+
if (this.closed) {
|
|
9326
|
+
this.setConnStatus("closed");
|
|
9327
|
+
return;
|
|
9328
|
+
}
|
|
9329
|
+
this.setConnStatus("reconnecting");
|
|
9330
|
+
const wait = BACKOFF_CAPS_MS[Math.min(this.reconnectAttempt, BACKOFF_CAPS_MS.length - 1)] ?? 30000;
|
|
9331
|
+
this.reconnectAttempt++;
|
|
9332
|
+
this.log("info", "broker_reconnect_scheduled", { wait_ms: wait, code, reason: reason.toString("utf8") });
|
|
9333
|
+
this.reconnectTimer = setTimeout(() => this.connect().catch((err) => this.log("warn", "broker_reconnect_failed", { err: String(err) })), wait);
|
|
9334
|
+
if (this._status === "connecting")
|
|
9335
|
+
reject(new Error(`closed_before_hello_${code}`));
|
|
9336
|
+
});
|
|
9337
|
+
ws.on("error", (err) => this.log("warn", "broker_ws_error", { err: err.message }));
|
|
9338
|
+
});
|
|
9339
|
+
}
|
|
9340
|
+
send(req) {
|
|
9341
|
+
return new Promise((resolve) => {
|
|
9342
|
+
const dispatch = () => {
|
|
9343
|
+
if (!this.ws || this.ws.readyState !== this.ws.OPEN) {
|
|
9344
|
+
resolve({ ok: false, error: "broker_not_open", permanent: false });
|
|
9345
|
+
return;
|
|
9346
|
+
}
|
|
9347
|
+
const id = req.client_message_id;
|
|
9348
|
+
const timer = setTimeout(() => {
|
|
9349
|
+
if (this.pendingAcks.delete(id)) {
|
|
9350
|
+
resolve({ ok: false, error: "ack_timeout", permanent: false });
|
|
9351
|
+
}
|
|
9352
|
+
}, SEND_ACK_TIMEOUT_MS);
|
|
9353
|
+
this.pendingAcks.set(id, { resolve, timer });
|
|
9354
|
+
try {
|
|
9355
|
+
this.ws.send(JSON.stringify({
|
|
9356
|
+
type: "send",
|
|
9357
|
+
id,
|
|
9358
|
+
client_message_id: id,
|
|
9359
|
+
request_fingerprint: req.request_fingerprint_hex,
|
|
9360
|
+
targetSpec: req.targetSpec,
|
|
9361
|
+
priority: req.priority,
|
|
9362
|
+
nonce: req.nonce,
|
|
9363
|
+
ciphertext: req.ciphertext
|
|
9364
|
+
}));
|
|
9365
|
+
} catch (e) {
|
|
9366
|
+
this.pendingAcks.delete(id);
|
|
9367
|
+
clearTimeout(timer);
|
|
9368
|
+
resolve({ ok: false, error: `ws_write_failed: ${String(e)}`, permanent: false });
|
|
9369
|
+
}
|
|
9370
|
+
};
|
|
9371
|
+
if (this._status === "open")
|
|
9372
|
+
dispatch();
|
|
9373
|
+
else
|
|
9374
|
+
this.opens.push(dispatch);
|
|
9375
|
+
});
|
|
9376
|
+
}
|
|
9377
|
+
async listPeers(timeoutMs = 5000) {
|
|
9378
|
+
if (this._status !== "open" || !this.ws)
|
|
9379
|
+
return [];
|
|
9380
|
+
return new Promise((resolve) => {
|
|
9381
|
+
const reqId = `pl-${++this.reqCounter}`;
|
|
9382
|
+
const timer = setTimeout(() => {
|
|
9383
|
+
if (this.peerListResolvers.delete(reqId))
|
|
9384
|
+
resolve([]);
|
|
9385
|
+
}, timeoutMs);
|
|
9386
|
+
this.peerListResolvers.set(reqId, { resolve, timer });
|
|
9387
|
+
try {
|
|
9388
|
+
this.ws.send(JSON.stringify({ type: "list_peers", _reqId: reqId }));
|
|
9389
|
+
} catch {
|
|
9390
|
+
this.peerListResolvers.delete(reqId);
|
|
9391
|
+
clearTimeout(timer);
|
|
9392
|
+
resolve([]);
|
|
9393
|
+
}
|
|
9394
|
+
});
|
|
9395
|
+
}
|
|
9396
|
+
setProfile(profile) {
|
|
9397
|
+
if (this._status !== "open" || !this.ws)
|
|
9398
|
+
return;
|
|
9399
|
+
try {
|
|
9400
|
+
this.ws.send(JSON.stringify({ type: "set_profile", ...profile }));
|
|
9401
|
+
} catch {}
|
|
9402
|
+
}
|
|
9403
|
+
setSummary(summary) {
|
|
9404
|
+
if (this._status !== "open" || !this.ws)
|
|
9405
|
+
return;
|
|
9406
|
+
try {
|
|
9407
|
+
this.ws.send(JSON.stringify({ type: "set_summary", summary }));
|
|
9408
|
+
} catch {}
|
|
9409
|
+
}
|
|
9410
|
+
setStatus(status) {
|
|
9411
|
+
if (this._status !== "open" || !this.ws)
|
|
9412
|
+
return;
|
|
9413
|
+
try {
|
|
9414
|
+
this.ws.send(JSON.stringify({ type: "set_status", status }));
|
|
9415
|
+
} catch {}
|
|
9416
|
+
}
|
|
9417
|
+
setVisible(visible) {
|
|
9418
|
+
if (this._status !== "open" || !this.ws)
|
|
9419
|
+
return;
|
|
9420
|
+
try {
|
|
9421
|
+
this.ws.send(JSON.stringify({ type: "set_visible", visible }));
|
|
9422
|
+
} catch {}
|
|
9423
|
+
}
|
|
9424
|
+
async close() {
|
|
9425
|
+
this.closed = true;
|
|
9426
|
+
if (this.reconnectTimer) {
|
|
9427
|
+
clearTimeout(this.reconnectTimer);
|
|
9428
|
+
this.reconnectTimer = null;
|
|
9429
|
+
}
|
|
9430
|
+
if (this.helloTimer) {
|
|
9431
|
+
clearTimeout(this.helloTimer);
|
|
9432
|
+
this.helloTimer = null;
|
|
9433
|
+
}
|
|
9434
|
+
this.failPendingAcks("daemon_shutdown");
|
|
9435
|
+
try {
|
|
9436
|
+
this.ws?.close();
|
|
9437
|
+
} catch {}
|
|
9438
|
+
this.setConnStatus("closed");
|
|
9439
|
+
}
|
|
9440
|
+
getSessionKeys() {
|
|
9441
|
+
if (!this.sessionPubkey || !this.sessionSecretKey)
|
|
9442
|
+
return null;
|
|
9443
|
+
return { sessionPubkey: this.sessionPubkey, sessionSecretKey: this.sessionSecretKey };
|
|
9444
|
+
}
|
|
9445
|
+
failPendingAcks(reason) {
|
|
9446
|
+
for (const [id, ack] of this.pendingAcks) {
|
|
9447
|
+
clearTimeout(ack.timer);
|
|
9448
|
+
ack.resolve({ ok: false, error: reason, permanent: false });
|
|
9449
|
+
this.pendingAcks.delete(id);
|
|
9450
|
+
}
|
|
9451
|
+
}
|
|
9452
|
+
}
|
|
9453
|
+
function defaultLog(level, msg, meta) {
|
|
9454
|
+
const line = JSON.stringify({ level, msg, ...meta, ts: new Date().toISOString() });
|
|
9455
|
+
if (level === "info")
|
|
9456
|
+
process.stdout.write(line + `
|
|
9457
|
+
`);
|
|
9458
|
+
else
|
|
9459
|
+
process.stderr.write(line + `
|
|
9460
|
+
`);
|
|
9461
|
+
}
|
|
9462
|
+
function classifyPermanent(err) {
|
|
9463
|
+
return /payload_too_large|forbidden|not_found|invalid|schema|auth|signature/i.test(err);
|
|
9464
|
+
}
|
|
9465
|
+
var HELLO_ACK_TIMEOUT_MS2 = 5000, SEND_ACK_TIMEOUT_MS = 15000, BACKOFF_CAPS_MS;
|
|
9466
|
+
var init_broker = __esm(() => {
|
|
9467
|
+
init_hello_sig();
|
|
9468
|
+
BACKOFF_CAPS_MS = [1000, 2000, 4000, 8000, 16000, 30000];
|
|
9469
|
+
});
|
|
9470
|
+
|
|
9471
|
+
// src/daemon/drain.ts
|
|
9472
|
+
function startDrainWorker(opts) {
|
|
9473
|
+
const log2 = opts.log ?? defaultLog2;
|
|
9474
|
+
let stopped = false;
|
|
9475
|
+
let wakeResolve = null;
|
|
9476
|
+
let wakePromise = new Promise((r) => {
|
|
9477
|
+
wakeResolve = r;
|
|
9478
|
+
});
|
|
9479
|
+
const wake = () => {
|
|
9480
|
+
if (wakeResolve) {
|
|
9481
|
+
const r = wakeResolve;
|
|
9482
|
+
wakeResolve = null;
|
|
9483
|
+
r();
|
|
9484
|
+
}
|
|
9485
|
+
};
|
|
9486
|
+
const tick = async () => {
|
|
9487
|
+
while (!stopped) {
|
|
9488
|
+
try {
|
|
9489
|
+
await drainOnce(opts, log2);
|
|
9490
|
+
} catch (e) {
|
|
9491
|
+
log2("warn", "drain_tick_failed", { err: String(e) });
|
|
9492
|
+
}
|
|
9493
|
+
await Promise.race([
|
|
9494
|
+
wakePromise,
|
|
9495
|
+
new Promise((r) => setTimeout(r, POLL_INTERVAL_MS))
|
|
9496
|
+
]);
|
|
9497
|
+
wakePromise = new Promise((r) => {
|
|
9498
|
+
wakeResolve = r;
|
|
9499
|
+
});
|
|
9500
|
+
}
|
|
9501
|
+
};
|
|
9502
|
+
tick();
|
|
9503
|
+
return {
|
|
9504
|
+
wake,
|
|
9505
|
+
close: async () => {
|
|
9506
|
+
stopped = true;
|
|
9507
|
+
wake();
|
|
9508
|
+
}
|
|
9509
|
+
};
|
|
9510
|
+
}
|
|
9511
|
+
async function drainOnce(opts, log2) {
|
|
9512
|
+
const now = Date.now();
|
|
9513
|
+
const rows = opts.db.prepare(`
|
|
9514
|
+
SELECT id, client_message_id, request_fingerprint, payload, attempts
|
|
9515
|
+
FROM outbox
|
|
9516
|
+
WHERE status = 'pending' AND next_attempt_at <= ?
|
|
9517
|
+
ORDER BY enqueued_at
|
|
9518
|
+
LIMIT 32
|
|
9519
|
+
`).all(now);
|
|
9520
|
+
if (rows.length === 0)
|
|
9521
|
+
return;
|
|
9522
|
+
for (const row of rows) {
|
|
9523
|
+
if (markInflight(opts.db, row.id, now) === 0)
|
|
9524
|
+
continue;
|
|
9525
|
+
const fpHex = bufferToHex(row.request_fingerprint);
|
|
9526
|
+
const sessionKeys = opts.broker.getSessionKeys();
|
|
9527
|
+
const targetSpec = "*";
|
|
9528
|
+
const nonce = await randomNonce2();
|
|
9529
|
+
const ciphertext = Buffer.from(row.payload).toString("base64");
|
|
9530
|
+
let res;
|
|
9531
|
+
try {
|
|
9532
|
+
res = await opts.broker.send({
|
|
9533
|
+
targetSpec,
|
|
9534
|
+
priority: "next",
|
|
9535
|
+
nonce,
|
|
9536
|
+
ciphertext,
|
|
9537
|
+
client_message_id: row.client_message_id,
|
|
9538
|
+
request_fingerprint_hex: fpHex
|
|
9539
|
+
});
|
|
9540
|
+
} catch (e) {
|
|
9541
|
+
log2("warn", "drain_send_threw", { id: row.id, err: String(e) });
|
|
9542
|
+
backoffPending(opts.db, row.id, row.attempts + 1, "exception", String(e));
|
|
9543
|
+
continue;
|
|
9544
|
+
}
|
|
9545
|
+
if (res.ok) {
|
|
9546
|
+
markDone(opts.db, row.id, res.messageId, Date.now());
|
|
9547
|
+
} else if (res.permanent) {
|
|
9548
|
+
log2("warn", "drain_permanent_failure", { id: row.id, err: res.error });
|
|
9549
|
+
markDead(opts.db, row.id, res.error);
|
|
9550
|
+
} else if (row.attempts + 1 >= MAX_ATTEMPTS_PER_ROW) {
|
|
9551
|
+
log2("warn", "drain_max_attempts", { id: row.id, err: res.error });
|
|
9552
|
+
markDead(opts.db, row.id, `max_attempts: ${res.error}`);
|
|
9553
|
+
} else {
|
|
9554
|
+
backoffPending(opts.db, row.id, row.attempts + 1, "retry", res.error);
|
|
9555
|
+
}
|
|
9556
|
+
}
|
|
9557
|
+
}
|
|
9558
|
+
function markInflight(db, id, now) {
|
|
9559
|
+
return Number(db.prepare(`
|
|
9560
|
+
UPDATE outbox
|
|
9561
|
+
SET status = 'inflight', attempts = attempts + 1, next_attempt_at = ?
|
|
9562
|
+
WHERE id = ? AND status = 'pending'
|
|
9563
|
+
`).run(now + BACKOFF_CAP_MS, id).changes);
|
|
9564
|
+
}
|
|
9565
|
+
function markDone(db, id, brokerMessageId, now) {
|
|
9566
|
+
db.prepare(`
|
|
9567
|
+
UPDATE outbox
|
|
9568
|
+
SET status = 'done', delivered_at = ?, broker_message_id = ?, last_error = NULL
|
|
9569
|
+
WHERE id = ?
|
|
9570
|
+
`).run(now, brokerMessageId, id);
|
|
9571
|
+
}
|
|
9572
|
+
function markDead(db, id, err) {
|
|
9573
|
+
db.prepare(`UPDATE outbox SET status = 'dead', last_error = ? WHERE id = ?`).run(err, id);
|
|
9574
|
+
}
|
|
9575
|
+
function backoffPending(db, id, attempts, _kind, err) {
|
|
9576
|
+
const wait = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** Math.min(attempts, 12));
|
|
9577
|
+
const next = Date.now() + wait;
|
|
9578
|
+
db.prepare(`
|
|
9579
|
+
UPDATE outbox
|
|
9580
|
+
SET status = 'pending', attempts = ?, next_attempt_at = ?, last_error = ?
|
|
9581
|
+
WHERE id = ?
|
|
9582
|
+
`).run(attempts, next, err, id);
|
|
9583
|
+
}
|
|
9584
|
+
function bufferToHex(b) {
|
|
9585
|
+
let s = "";
|
|
9586
|
+
for (let i = 0;i < b.length; i++)
|
|
9587
|
+
s += b[i].toString(16).padStart(2, "0");
|
|
9588
|
+
return s;
|
|
9589
|
+
}
|
|
9590
|
+
async function randomNonce2() {
|
|
9591
|
+
const { randomBytes: randomBytes5 } = await import("node:crypto");
|
|
9592
|
+
return randomBytes5(24).toString("base64");
|
|
9593
|
+
}
|
|
9594
|
+
function defaultLog2(level, msg, meta) {
|
|
9595
|
+
const line = JSON.stringify({ level, msg, ...meta, ts: new Date().toISOString() });
|
|
9596
|
+
if (level === "info")
|
|
9597
|
+
process.stdout.write(line + `
|
|
9598
|
+
`);
|
|
9599
|
+
else
|
|
9600
|
+
process.stderr.write(line + `
|
|
9601
|
+
`);
|
|
9602
|
+
}
|
|
9603
|
+
var POLL_INTERVAL_MS = 500, MAX_ATTEMPTS_PER_ROW = 25, BACKOFF_BASE_MS = 500, BACKOFF_CAP_MS = 30000;
|
|
9604
|
+
var init_drain = () => {};
|
|
9605
|
+
|
|
9606
|
+
// src/daemon/inbound.ts
|
|
9607
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
9608
|
+
async function handleBrokerPush(msg, ctx) {
|
|
9609
|
+
if (msg.subtype === "system" && typeof msg.event === "string") {
|
|
9610
|
+
ctx.bus.publish(mapSystemEventKind(msg.event), {
|
|
9611
|
+
mesh: ctx.meshSlug,
|
|
9612
|
+
event: msg.event,
|
|
9613
|
+
...msg.eventData ?? {}
|
|
9614
|
+
});
|
|
9615
|
+
return;
|
|
9616
|
+
}
|
|
9617
|
+
if (msg.type !== "push")
|
|
9618
|
+
return;
|
|
9619
|
+
const brokerMessageId = stringOrNull(msg.messageId);
|
|
9620
|
+
const senderPubkey = stringOrNull(msg.senderPubkey) ?? "";
|
|
9621
|
+
const senderName = stringOrNull(msg.senderName) ?? senderPubkey.slice(0, 8);
|
|
9622
|
+
const topic = stringOrNull(msg.topic);
|
|
9623
|
+
const replyToId = stringOrNull(msg.replyToId);
|
|
9624
|
+
const ciphertext = stringOrNull(msg.ciphertext) ?? "";
|
|
9625
|
+
const nonce = stringOrNull(msg.nonce) ?? "";
|
|
9626
|
+
const createdAt = stringOrNull(msg.createdAt);
|
|
9627
|
+
const clientMessageId = stringOrNull(msg.client_message_id) ?? brokerMessageId ?? randomUUID5();
|
|
9628
|
+
const body = await decryptOrFallback({
|
|
9629
|
+
ciphertext,
|
|
9630
|
+
nonce,
|
|
9631
|
+
senderPubkey,
|
|
9632
|
+
ctx
|
|
9633
|
+
});
|
|
9634
|
+
const id = randomUUID5();
|
|
9635
|
+
const inserted = insertIfNew(ctx.db, {
|
|
9636
|
+
id,
|
|
9637
|
+
client_message_id: clientMessageId,
|
|
9638
|
+
broker_message_id: brokerMessageId,
|
|
9639
|
+
mesh: ctx.meshSlug,
|
|
9640
|
+
topic,
|
|
9641
|
+
sender_pubkey: senderPubkey,
|
|
9642
|
+
sender_name: senderName,
|
|
9643
|
+
body,
|
|
9644
|
+
meta: createdAt ? JSON.stringify({ created_at: createdAt }) : null,
|
|
9645
|
+
received_at: Date.now(),
|
|
9646
|
+
reply_to_id: replyToId
|
|
9647
|
+
});
|
|
9648
|
+
if (!inserted)
|
|
9649
|
+
return;
|
|
9650
|
+
ctx.bus.publish("message", {
|
|
9651
|
+
id,
|
|
9652
|
+
mesh: ctx.meshSlug,
|
|
9653
|
+
client_message_id: clientMessageId,
|
|
9654
|
+
broker_message_id: brokerMessageId,
|
|
9655
|
+
sender_pubkey: senderPubkey,
|
|
9656
|
+
sender_name: senderName,
|
|
9657
|
+
topic,
|
|
9658
|
+
reply_to_id: replyToId,
|
|
9659
|
+
body,
|
|
9660
|
+
created_at: createdAt
|
|
9661
|
+
});
|
|
9662
|
+
}
|
|
9663
|
+
async function decryptOrFallback(args) {
|
|
9664
|
+
const { ciphertext, nonce, senderPubkey, ctx } = args;
|
|
9665
|
+
if (!ciphertext)
|
|
9666
|
+
return null;
|
|
9667
|
+
if (nonce && senderPubkey) {
|
|
9668
|
+
const envelope = { nonce, ciphertext };
|
|
9669
|
+
if (ctx.sessionSecretKeyHex) {
|
|
9670
|
+
const pt = await decryptDirect(envelope, senderPubkey, ctx.sessionSecretKeyHex);
|
|
9671
|
+
if (pt !== null)
|
|
9672
|
+
return pt;
|
|
9673
|
+
}
|
|
9674
|
+
if (ctx.recipientSecretKeyHex) {
|
|
9675
|
+
const pt = await decryptDirect(envelope, senderPubkey, ctx.recipientSecretKeyHex);
|
|
9676
|
+
if (pt !== null)
|
|
9677
|
+
return pt;
|
|
9678
|
+
}
|
|
9679
|
+
}
|
|
9680
|
+
try {
|
|
9681
|
+
return Buffer.from(ciphertext, "base64").toString("utf8");
|
|
9682
|
+
} catch (e) {
|
|
9683
|
+
ctx.log?.("warn", "inbound_b64_decode_failed", { err: String(e) });
|
|
9684
|
+
return null;
|
|
9685
|
+
}
|
|
9686
|
+
}
|
|
9687
|
+
function stringOrNull(v) {
|
|
9688
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
9689
|
+
}
|
|
9690
|
+
function mapSystemEventKind(event) {
|
|
9691
|
+
if (event === "peer_joined")
|
|
9692
|
+
return "peer_join";
|
|
9693
|
+
if (event === "peer_left")
|
|
9694
|
+
return "peer_leave";
|
|
9695
|
+
return "system";
|
|
9696
|
+
}
|
|
9697
|
+
var init_inbound = __esm(() => {
|
|
9698
|
+
init_facade7();
|
|
9699
|
+
});
|
|
9700
|
+
|
|
9701
|
+
// src/daemon/identity.ts
|
|
9702
|
+
var exports_identity = {};
|
|
9703
|
+
__export(exports_identity, {
|
|
9704
|
+
computeCurrentFingerprint: () => computeCurrentFingerprint,
|
|
9705
|
+
checkFingerprint: () => checkFingerprint,
|
|
9706
|
+
acceptCurrentHost: () => acceptCurrentHost
|
|
9707
|
+
});
|
|
9708
|
+
import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
9709
|
+
import { join as join7 } from "node:path";
|
|
9710
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
9711
|
+
import { networkInterfaces } from "node:os";
|
|
9712
|
+
function path() {
|
|
9713
|
+
return join7(DAEMON_PATHS.DAEMON_DIR, FILE_NAME);
|
|
9714
|
+
}
|
|
9715
|
+
function computeCurrentFingerprint() {
|
|
9716
|
+
const host_id = readHostId() ?? "";
|
|
9717
|
+
const stable_mac = pickStableMac() ?? "";
|
|
9718
|
+
const fp = createHash2("sha256").update(host_id, "utf8").update("\x00").update(stable_mac, "utf8").digest("hex");
|
|
9719
|
+
return {
|
|
9720
|
+
schema_version: 1,
|
|
9721
|
+
fingerprint: fp,
|
|
9722
|
+
host_id,
|
|
9723
|
+
stable_mac,
|
|
9724
|
+
written_at: new Date().toISOString()
|
|
9725
|
+
};
|
|
9726
|
+
}
|
|
9727
|
+
function checkFingerprint() {
|
|
9728
|
+
const current = computeCurrentFingerprint();
|
|
9729
|
+
if (!existsSync10(path())) {
|
|
9730
|
+
writeFileSync8(path(), JSON.stringify(current, null, 2), { mode: 384 });
|
|
9731
|
+
return { result: "first_run", current };
|
|
9732
|
+
}
|
|
9733
|
+
let stored;
|
|
9734
|
+
try {
|
|
9735
|
+
stored = JSON.parse(readFileSync7(path(), "utf8"));
|
|
9736
|
+
} catch {
|
|
9737
|
+
return { result: "unavailable", current };
|
|
9738
|
+
}
|
|
9739
|
+
if (stored.fingerprint === current.fingerprint)
|
|
9740
|
+
return { result: "match", current, stored };
|
|
9741
|
+
return { result: "mismatch", current, stored };
|
|
9742
|
+
}
|
|
9743
|
+
function acceptCurrentHost() {
|
|
9744
|
+
const current = computeCurrentFingerprint();
|
|
9745
|
+
writeFileSync8(path(), JSON.stringify(current, null, 2), { mode: 384 });
|
|
9746
|
+
return current;
|
|
9747
|
+
}
|
|
9748
|
+
function readHostId() {
|
|
9749
|
+
if (process.platform === "linux") {
|
|
9750
|
+
for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
9751
|
+
try {
|
|
9752
|
+
const raw = readFileSync7(p, "utf8").trim();
|
|
9753
|
+
if (raw)
|
|
9754
|
+
return `linux:${raw}`;
|
|
9755
|
+
} catch {}
|
|
9756
|
+
}
|
|
9757
|
+
return null;
|
|
9758
|
+
}
|
|
9759
|
+
if (process.platform === "darwin") {
|
|
9760
|
+
return null;
|
|
9761
|
+
}
|
|
9762
|
+
return null;
|
|
9763
|
+
}
|
|
9764
|
+
function pickStableMac() {
|
|
9765
|
+
const ifs = networkInterfaces();
|
|
9766
|
+
const candidates = [];
|
|
9767
|
+
for (const [name, addrs] of Object.entries(ifs)) {
|
|
9768
|
+
if (!addrs)
|
|
9769
|
+
continue;
|
|
9770
|
+
if (isIgnoredInterface(name))
|
|
9771
|
+
continue;
|
|
9772
|
+
for (const a of addrs) {
|
|
9773
|
+
if (a.internal)
|
|
9774
|
+
continue;
|
|
9775
|
+
if (!a.mac || a.mac === "00:00:00:00:00:00")
|
|
9776
|
+
continue;
|
|
9777
|
+
candidates.push(`${name}::${a.mac}`);
|
|
9778
|
+
break;
|
|
9779
|
+
}
|
|
9780
|
+
}
|
|
9781
|
+
if (candidates.length === 0)
|
|
9782
|
+
return null;
|
|
9783
|
+
candidates.sort();
|
|
9784
|
+
const first = candidates[0];
|
|
9785
|
+
const idx = first.indexOf("::");
|
|
9786
|
+
return idx >= 0 ? first.slice(idx + 2) : first;
|
|
9787
|
+
}
|
|
9788
|
+
function isIgnoredInterface(name) {
|
|
9789
|
+
return /^(lo|docker|br-|veth|tap|tun|tailscale|wg|utun|ppp|vboxnet|vmnet|awdl|llw)/i.test(name);
|
|
9790
|
+
}
|
|
9791
|
+
var FILE_NAME = "host_fingerprint.json";
|
|
9792
|
+
var init_identity = __esm(() => {
|
|
9793
|
+
init_paths2();
|
|
9794
|
+
});
|
|
9795
|
+
|
|
9796
|
+
// src/daemon/run.ts
|
|
9797
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync8 } from "node:fs";
|
|
9798
|
+
function detectContainer() {
|
|
9799
|
+
if (process.env.KUBERNETES_SERVICE_HOST)
|
|
9800
|
+
return true;
|
|
9801
|
+
if (process.env.CONTAINER === "1")
|
|
9802
|
+
return true;
|
|
9803
|
+
try {
|
|
9804
|
+
if (existsSync11("/.dockerenv"))
|
|
9805
|
+
return true;
|
|
9806
|
+
const cg = readFileSync8("/proc/1/cgroup", "utf8");
|
|
9807
|
+
if (/(docker|kubepods|containerd)/.test(cg))
|
|
9808
|
+
return true;
|
|
9809
|
+
} catch {}
|
|
9810
|
+
return false;
|
|
9811
|
+
}
|
|
9812
|
+
async function runDaemon(opts = {}) {
|
|
9813
|
+
mkdirSync6(DAEMON_PATHS.DAEMON_DIR, { recursive: true, mode: 448 });
|
|
9814
|
+
const lock = acquireSingletonLock();
|
|
9815
|
+
if (lock.result === "already-running") {
|
|
9816
|
+
process.stderr.write(`daemon already running (pid ${lock.pid})
|
|
9817
|
+
`);
|
|
9818
|
+
return 1;
|
|
9819
|
+
}
|
|
9820
|
+
if (lock.result === "stale") {
|
|
9821
|
+
process.stderr.write(`recovered stale pid file; starting fresh
|
|
9822
|
+
`);
|
|
9823
|
+
}
|
|
9824
|
+
const fpCheck = checkFingerprint();
|
|
9825
|
+
const policy = opts.clonePolicy ?? "refuse";
|
|
9826
|
+
if (fpCheck.result === "mismatch") {
|
|
9827
|
+
const msg = `host_fingerprint mismatch: this daemon dir was started on a different host.`;
|
|
9828
|
+
if (policy === "refuse") {
|
|
9829
|
+
process.stderr.write(`${msg}
|
|
9830
|
+
`);
|
|
9831
|
+
process.stderr.write(` stored host_id: ${fpCheck.stored?.host_id}
|
|
9832
|
+
`);
|
|
9833
|
+
process.stderr.write(` current host_id: ${fpCheck.current.host_id}
|
|
9834
|
+
`);
|
|
9835
|
+
process.stderr.write(`Run \`claudemesh daemon accept-host\` to write a fresh fingerprint, or
|
|
9836
|
+
`);
|
|
9837
|
+
process.stderr.write(`run \`claudemesh daemon remint\` to mint a new keypair (Sprint 7+).
|
|
9838
|
+
`);
|
|
9839
|
+
releaseSingletonLock();
|
|
9840
|
+
return 4;
|
|
9841
|
+
}
|
|
9842
|
+
if (policy === "warn") {
|
|
9843
|
+
process.stderr.write(`WARN: ${msg} (continuing per [clone] policy=warn)
|
|
9844
|
+
`);
|
|
9845
|
+
}
|
|
9846
|
+
}
|
|
9847
|
+
if (fpCheck.result === "first_run") {
|
|
9848
|
+
process.stdout.write(JSON.stringify({
|
|
9849
|
+
msg: "host_fingerprint_written",
|
|
9850
|
+
fingerprint_prefix: fpCheck.current.fingerprint.slice(0, 16),
|
|
9851
|
+
ts: new Date().toISOString()
|
|
9852
|
+
}) + `
|
|
9853
|
+
`);
|
|
9854
|
+
}
|
|
9855
|
+
const localToken = ensureLocalToken();
|
|
9856
|
+
const tcpEnabled = opts.tcpEnabled ?? !detectContainer();
|
|
9857
|
+
let outboxDb;
|
|
9858
|
+
let inboxDb;
|
|
9859
|
+
try {
|
|
9860
|
+
outboxDb = await openSqlite(DAEMON_PATHS.OUTBOX_DB);
|
|
9861
|
+
migrateOutbox(outboxDb);
|
|
9862
|
+
inboxDb = await openSqlite(DAEMON_PATHS.INBOX_DB);
|
|
9863
|
+
migrateInbox(inboxDb);
|
|
9864
|
+
} catch (err) {
|
|
9865
|
+
process.stderr.write(`db open failed: ${String(err)}
|
|
9866
|
+
`);
|
|
9867
|
+
releaseSingletonLock();
|
|
9868
|
+
return 1;
|
|
9869
|
+
}
|
|
9870
|
+
const bus = new EventBus;
|
|
9871
|
+
const cfg = readConfig();
|
|
9872
|
+
let mesh = null;
|
|
9873
|
+
if (opts.mesh) {
|
|
9874
|
+
mesh = cfg.meshes.find((m) => m.slug === opts.mesh) ?? null;
|
|
9875
|
+
if (!mesh) {
|
|
9876
|
+
process.stderr.write(`mesh not found: ${opts.mesh}
|
|
9877
|
+
`);
|
|
9878
|
+
process.stderr.write(`joined meshes: ${cfg.meshes.map((m) => m.slug).join(", ") || "(none)"}
|
|
9879
|
+
`);
|
|
9880
|
+
releaseSingletonLock();
|
|
9881
|
+
try {
|
|
9882
|
+
outboxDb.close();
|
|
9883
|
+
} catch {}
|
|
9884
|
+
return 2;
|
|
9885
|
+
}
|
|
9886
|
+
} else if (cfg.meshes.length === 1) {
|
|
9887
|
+
mesh = cfg.meshes[0];
|
|
9888
|
+
} else if (cfg.meshes.length === 0) {
|
|
9889
|
+
process.stderr.write(`no mesh joined; run \`claudemesh join <invite-url>\` first
|
|
9890
|
+
`);
|
|
9891
|
+
releaseSingletonLock();
|
|
9892
|
+
try {
|
|
9893
|
+
outboxDb.close();
|
|
9894
|
+
} catch {}
|
|
9895
|
+
return 2;
|
|
9896
|
+
} else {
|
|
9897
|
+
process.stderr.write(`multiple meshes joined; pass --mesh <slug>
|
|
9898
|
+
`);
|
|
9899
|
+
process.stderr.write(`available: ${cfg.meshes.map((m) => m.slug).join(", ")}
|
|
9900
|
+
`);
|
|
9901
|
+
releaseSingletonLock();
|
|
9902
|
+
try {
|
|
9903
|
+
outboxDb.close();
|
|
9904
|
+
} catch {}
|
|
9905
|
+
return 2;
|
|
9906
|
+
}
|
|
9907
|
+
const broker = new DaemonBrokerClient(mesh, {
|
|
9908
|
+
displayName: opts.displayName,
|
|
9909
|
+
onStatusChange: (s) => {
|
|
9910
|
+
process.stdout.write(JSON.stringify({
|
|
9911
|
+
msg: "broker_status",
|
|
9912
|
+
status: s,
|
|
9913
|
+
mesh: mesh.slug,
|
|
9914
|
+
ts: new Date().toISOString()
|
|
9915
|
+
}) + `
|
|
9916
|
+
`);
|
|
9917
|
+
bus.publish("broker_status", { mesh: mesh.slug, status: s });
|
|
9918
|
+
},
|
|
9919
|
+
onPush: (m) => {
|
|
9920
|
+
const sessionKeys = broker.getSessionKeys();
|
|
9921
|
+
handleBrokerPush(m, {
|
|
9922
|
+
db: inboxDb,
|
|
9923
|
+
bus,
|
|
9924
|
+
meshSlug: mesh.slug,
|
|
9925
|
+
recipientSecretKeyHex: mesh.secretKey,
|
|
9926
|
+
sessionSecretKeyHex: sessionKeys?.sessionSecretKey
|
|
9927
|
+
});
|
|
9928
|
+
}
|
|
9929
|
+
});
|
|
9930
|
+
broker.connect().catch((err) => process.stderr.write(`broker connect failed: ${String(err)}
|
|
9931
|
+
`));
|
|
9932
|
+
let drain = null;
|
|
9933
|
+
drain = startDrainWorker({ db: outboxDb, broker });
|
|
9934
|
+
const ipc2 = startIpcServer({
|
|
9935
|
+
localToken,
|
|
9936
|
+
tcpEnabled,
|
|
9937
|
+
publicHealthCheck: opts.publicHealthCheck,
|
|
9938
|
+
outboxDb,
|
|
9939
|
+
inboxDb,
|
|
9940
|
+
bus,
|
|
9941
|
+
broker,
|
|
9942
|
+
onPendingInserted: () => drain?.wake()
|
|
9943
|
+
});
|
|
9944
|
+
try {
|
|
9945
|
+
await ipc2.ready;
|
|
9946
|
+
} catch (err) {
|
|
9947
|
+
process.stderr.write(`ipc listen failed: ${String(err)}
|
|
9948
|
+
`);
|
|
9949
|
+
releaseSingletonLock();
|
|
9950
|
+
return 1;
|
|
9951
|
+
}
|
|
9952
|
+
process.stdout.write(JSON.stringify({
|
|
9953
|
+
msg: "daemon_started",
|
|
9954
|
+
pid: process.pid,
|
|
9955
|
+
sock: DAEMON_PATHS.SOCK_FILE,
|
|
9956
|
+
tcp: tcpEnabled ? `127.0.0.1:47823` : null,
|
|
9957
|
+
mesh: mesh.slug,
|
|
9958
|
+
ts: new Date().toISOString()
|
|
9959
|
+
}) + `
|
|
9960
|
+
`);
|
|
9961
|
+
let shuttingDown = false;
|
|
9962
|
+
const shutdown = async (sig) => {
|
|
9963
|
+
if (shuttingDown)
|
|
9964
|
+
return;
|
|
9965
|
+
shuttingDown = true;
|
|
9966
|
+
process.stdout.write(JSON.stringify({ msg: "daemon_shutdown", signal: sig, ts: new Date().toISOString() }) + `
|
|
9967
|
+
`);
|
|
9968
|
+
if (drain)
|
|
9969
|
+
await drain.close();
|
|
9970
|
+
await broker.close();
|
|
9971
|
+
await ipc2.close();
|
|
9972
|
+
try {
|
|
9973
|
+
outboxDb.close();
|
|
9974
|
+
} catch {}
|
|
9975
|
+
try {
|
|
9976
|
+
inboxDb.close();
|
|
9977
|
+
} catch {}
|
|
9978
|
+
releaseSingletonLock();
|
|
9979
|
+
process.exit(0);
|
|
9980
|
+
};
|
|
9981
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
9982
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
9983
|
+
return new Promise(() => {});
|
|
9984
|
+
}
|
|
9985
|
+
var init_run = __esm(() => {
|
|
9986
|
+
init_paths2();
|
|
9987
|
+
init_lock();
|
|
9988
|
+
init_local_token();
|
|
9989
|
+
init_server();
|
|
9990
|
+
init_broker();
|
|
9991
|
+
init_drain();
|
|
9992
|
+
init_inbound();
|
|
9993
|
+
init_identity();
|
|
9994
|
+
init_facade();
|
|
9995
|
+
});
|
|
9996
|
+
|
|
9997
|
+
// src/daemon/service-install.ts
|
|
9998
|
+
var exports_service_install = {};
|
|
9999
|
+
__export(exports_service_install, {
|
|
10000
|
+
uninstallService: () => uninstallService,
|
|
10001
|
+
readInstalledUnit: () => readInstalledUnit,
|
|
10002
|
+
installService: () => installService,
|
|
10003
|
+
detectPlatform: () => detectPlatform
|
|
10004
|
+
});
|
|
10005
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync9, unlinkSync as unlinkSync4, readFileSync as readFileSync9 } from "node:fs";
|
|
10006
|
+
import { execSync as execSync2 } from "node:child_process";
|
|
10007
|
+
import { homedir as homedir6 } from "node:os";
|
|
10008
|
+
import { join as join8, dirname as dirname5 } from "node:path";
|
|
10009
|
+
function detectPlatform() {
|
|
10010
|
+
if (process.platform === "darwin")
|
|
10011
|
+
return "darwin";
|
|
10012
|
+
if (process.platform === "linux")
|
|
10013
|
+
return "linux";
|
|
10014
|
+
return null;
|
|
10015
|
+
}
|
|
10016
|
+
function isCi() {
|
|
10017
|
+
return !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.BUILDKITE || process.env.CIRCLECI || process.env.JENKINS_URL);
|
|
10018
|
+
}
|
|
10019
|
+
function installService(args) {
|
|
10020
|
+
const platform5 = detectPlatform();
|
|
10021
|
+
if (!platform5)
|
|
10022
|
+
throw new Error(`unsupported platform: ${process.platform}`);
|
|
10023
|
+
if (isCi() && !args.allowCi) {
|
|
10024
|
+
throw new Error("Refusing to install persistent service in CI; pass --allow-ci-persistent to override.");
|
|
10025
|
+
}
|
|
10026
|
+
if (!existsSync12(args.binaryPath)) {
|
|
10027
|
+
throw new Error(`binary not found at ${args.binaryPath}`);
|
|
10028
|
+
}
|
|
10029
|
+
mkdirSync7(DAEMON_PATHS.DAEMON_DIR, { recursive: true, mode: 448 });
|
|
10030
|
+
if (platform5 === "darwin")
|
|
10031
|
+
return installDarwin(args);
|
|
10032
|
+
return installLinux(args);
|
|
10033
|
+
}
|
|
10034
|
+
function uninstallService() {
|
|
10035
|
+
const platform5 = detectPlatform();
|
|
10036
|
+
const removed = [];
|
|
10037
|
+
if (platform5 === "darwin") {
|
|
10038
|
+
const p = darwinPlistPath();
|
|
10039
|
+
try {
|
|
10040
|
+
execSync2(`launchctl bootout gui/$(id -u)/${SERVICE_LABEL}`, { stdio: "ignore" });
|
|
10041
|
+
} catch {}
|
|
10042
|
+
if (existsSync12(p)) {
|
|
10043
|
+
unlinkSync4(p);
|
|
10044
|
+
removed.push(p);
|
|
10045
|
+
}
|
|
10046
|
+
} else if (platform5 === "linux") {
|
|
10047
|
+
const p = linuxUnitPath();
|
|
10048
|
+
try {
|
|
10049
|
+
execSync2(`systemctl --user disable --now ${SYSTEMD_UNIT}`, { stdio: "ignore" });
|
|
10050
|
+
} catch {}
|
|
10051
|
+
if (existsSync12(p)) {
|
|
10052
|
+
unlinkSync4(p);
|
|
10053
|
+
removed.push(p);
|
|
10054
|
+
}
|
|
10055
|
+
}
|
|
10056
|
+
return { platform: platform5, removed };
|
|
10057
|
+
}
|
|
10058
|
+
function darwinPlistPath() {
|
|
10059
|
+
return join8(homedir6(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
|
|
10060
|
+
}
|
|
10061
|
+
function installDarwin(args) {
|
|
10062
|
+
const plist = darwinPlistPath();
|
|
10063
|
+
mkdirSync7(dirname5(plist), { recursive: true });
|
|
10064
|
+
const log2 = DAEMON_PATHS.LOG_FILE;
|
|
10065
|
+
const meshArgs = [
|
|
10066
|
+
"<string>daemon</string>",
|
|
10067
|
+
"<string>up</string>",
|
|
10068
|
+
"<string>--mesh</string>",
|
|
10069
|
+
`<string>${escapeXml(args.meshSlug)}</string>`,
|
|
10070
|
+
...args.displayName ? ["<string>--name</string>", `<string>${escapeXml(args.displayName)}</string>`] : []
|
|
10071
|
+
].join(`
|
|
10072
|
+
`);
|
|
10073
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
10074
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
10075
|
+
<plist version="1.0">
|
|
10076
|
+
<dict>
|
|
10077
|
+
<key>Label</key>
|
|
10078
|
+
<string>${SERVICE_LABEL}</string>
|
|
10079
|
+
<key>ProgramArguments</key>
|
|
10080
|
+
<array>
|
|
10081
|
+
<string>${escapeXml(args.binaryPath)}</string>
|
|
10082
|
+
${meshArgs}
|
|
10083
|
+
</array>
|
|
10084
|
+
<key>RunAtLoad</key>
|
|
10085
|
+
<true/>
|
|
10086
|
+
<key>KeepAlive</key>
|
|
10087
|
+
<true/>
|
|
10088
|
+
<key>StandardOutPath</key>
|
|
10089
|
+
<string>${escapeXml(log2)}</string>
|
|
10090
|
+
<key>StandardErrorPath</key>
|
|
10091
|
+
<string>${escapeXml(log2)}</string>
|
|
10092
|
+
<key>WorkingDirectory</key>
|
|
10093
|
+
<string>${escapeXml(homedir6())}</string>
|
|
10094
|
+
<key>EnvironmentVariables</key>
|
|
10095
|
+
<dict>
|
|
10096
|
+
<key>HOME</key>
|
|
10097
|
+
<string>${escapeXml(homedir6())}</string>
|
|
10098
|
+
<key>PATH</key>
|
|
10099
|
+
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
|
|
10100
|
+
</dict>
|
|
10101
|
+
</dict>
|
|
10102
|
+
</plist>
|
|
10103
|
+
`;
|
|
10104
|
+
writeFileSync9(plist, xml, { mode: 420 });
|
|
10105
|
+
return {
|
|
10106
|
+
platform: "darwin",
|
|
10107
|
+
unitPath: plist,
|
|
10108
|
+
bootCommand: `launchctl bootstrap gui/$(id -u) ${shellQuote(plist)}`
|
|
10109
|
+
};
|
|
10110
|
+
}
|
|
10111
|
+
function linuxUnitPath() {
|
|
10112
|
+
return join8(homedir6(), ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
10113
|
+
}
|
|
10114
|
+
function installLinux(args) {
|
|
10115
|
+
const unit = linuxUnitPath();
|
|
10116
|
+
mkdirSync7(dirname5(unit), { recursive: true });
|
|
10117
|
+
const execArgs = [
|
|
10118
|
+
"daemon",
|
|
10119
|
+
"up",
|
|
10120
|
+
"--mesh",
|
|
10121
|
+
args.meshSlug,
|
|
10122
|
+
...args.displayName ? ["--name", args.displayName] : []
|
|
10123
|
+
].map(shellQuote).join(" ");
|
|
10124
|
+
const content = `[Unit]
|
|
10125
|
+
Description=claudemesh daemon (peer mesh runtime)
|
|
10126
|
+
After=network-online.target
|
|
10127
|
+
Wants=network-online.target
|
|
10128
|
+
|
|
10129
|
+
[Service]
|
|
10130
|
+
Type=simple
|
|
10131
|
+
ExecStart=${shellQuote(args.binaryPath)} ${execArgs}
|
|
10132
|
+
Restart=always
|
|
10133
|
+
RestartSec=3
|
|
10134
|
+
StandardOutput=append:${DAEMON_PATHS.LOG_FILE}
|
|
10135
|
+
StandardError=append:${DAEMON_PATHS.LOG_FILE}
|
|
10136
|
+
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
10137
|
+
|
|
10138
|
+
[Install]
|
|
10139
|
+
WantedBy=default.target
|
|
10140
|
+
`;
|
|
10141
|
+
writeFileSync9(unit, content, { mode: 420 });
|
|
10142
|
+
return {
|
|
10143
|
+
platform: "linux",
|
|
10144
|
+
unitPath: unit,
|
|
10145
|
+
bootCommand: `systemctl --user daemon-reload && systemctl --user enable --now ${SYSTEMD_UNIT}`
|
|
10146
|
+
};
|
|
10147
|
+
}
|
|
10148
|
+
function escapeXml(s) {
|
|
10149
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
10150
|
+
}
|
|
10151
|
+
function shellQuote(s) {
|
|
10152
|
+
if (/^[\w@%+=:,./-]+$/.test(s))
|
|
10153
|
+
return s;
|
|
10154
|
+
return "'" + s.replace(/'/g, `'"'"'`) + "'";
|
|
10155
|
+
}
|
|
10156
|
+
function readInstalledUnit() {
|
|
10157
|
+
const platform5 = detectPlatform();
|
|
10158
|
+
if (!platform5)
|
|
10159
|
+
return { platform: null, path: null, content: null };
|
|
10160
|
+
const path2 = platform5 === "darwin" ? darwinPlistPath() : linuxUnitPath();
|
|
10161
|
+
if (!existsSync12(path2))
|
|
10162
|
+
return { platform: platform5, path: null, content: null };
|
|
10163
|
+
try {
|
|
10164
|
+
return { platform: platform5, path: path2, content: readFileSync9(path2, "utf8") };
|
|
10165
|
+
} catch {
|
|
10166
|
+
return { platform: platform5, path: path2, content: null };
|
|
10167
|
+
}
|
|
10168
|
+
}
|
|
10169
|
+
var SERVICE_LABEL = "com.claudemesh.daemon", SYSTEMD_UNIT = "claudemesh-daemon.service";
|
|
10170
|
+
var init_service_install = __esm(() => {
|
|
10171
|
+
init_paths2();
|
|
10172
|
+
});
|
|
10173
|
+
|
|
10174
|
+
// src/commands/daemon.ts
|
|
10175
|
+
var exports_daemon = {};
|
|
10176
|
+
__export(exports_daemon, {
|
|
10177
|
+
runDaemonCommand: () => runDaemonCommand
|
|
10178
|
+
});
|
|
10179
|
+
async function runDaemonCommand(sub, opts, rest = []) {
|
|
10180
|
+
switch (sub) {
|
|
10181
|
+
case undefined:
|
|
10182
|
+
case "up":
|
|
10183
|
+
case "start":
|
|
10184
|
+
return runDaemon({
|
|
10185
|
+
tcpEnabled: !opts.noTcp,
|
|
10186
|
+
publicHealthCheck: opts.publicHealth,
|
|
10187
|
+
mesh: opts.mesh,
|
|
10188
|
+
displayName: opts.displayName
|
|
10189
|
+
});
|
|
10190
|
+
case "status":
|
|
10191
|
+
return runStatus(opts);
|
|
10192
|
+
case "version":
|
|
10193
|
+
return runVersion(opts);
|
|
10194
|
+
case "down":
|
|
10195
|
+
case "stop":
|
|
10196
|
+
return runStop(opts);
|
|
10197
|
+
case "accept-host":
|
|
10198
|
+
return runAcceptHost(opts);
|
|
10199
|
+
case "outbox":
|
|
10200
|
+
return runOutbox(rest, opts);
|
|
10201
|
+
case "install-service":
|
|
10202
|
+
return runInstallService(opts);
|
|
10203
|
+
case "uninstall-service":
|
|
10204
|
+
return runUninstallService(opts);
|
|
10205
|
+
default:
|
|
10206
|
+
process.stderr.write(`unknown daemon subcommand: ${sub}
|
|
10207
|
+
`);
|
|
10208
|
+
process.stderr.write(`usage: claudemesh daemon [up|status|version|down|accept-host|outbox|install-service|uninstall-service]
|
|
10209
|
+
`);
|
|
10210
|
+
return 2;
|
|
10211
|
+
}
|
|
10212
|
+
}
|
|
10213
|
+
async function runOutbox(rest, opts) {
|
|
10214
|
+
const sub = rest[0];
|
|
10215
|
+
switch (sub) {
|
|
10216
|
+
case undefined:
|
|
10217
|
+
case "list": {
|
|
10218
|
+
const status = opts.outboxStatus;
|
|
10219
|
+
const path2 = `/v1/outbox${status ? `?status=${status}` : ""}`;
|
|
10220
|
+
try {
|
|
10221
|
+
const res = await ipc({ path: path2 });
|
|
10222
|
+
if (opts.json) {
|
|
10223
|
+
process.stdout.write(JSON.stringify(res.body) + `
|
|
10224
|
+
`);
|
|
10225
|
+
return 0;
|
|
10226
|
+
}
|
|
10227
|
+
if (!res.body.items?.length) {
|
|
10228
|
+
process.stdout.write(`(empty)
|
|
10229
|
+
`);
|
|
10230
|
+
return 0;
|
|
10231
|
+
}
|
|
10232
|
+
for (const r of res.body.items) {
|
|
10233
|
+
const tag = r.status.padEnd(8);
|
|
10234
|
+
const bm = r.broker_message_id ? ` → ${r.broker_message_id}` : "";
|
|
10235
|
+
const err = r.last_error ? ` last_error="${r.last_error.slice(0, 60)}"` : "";
|
|
10236
|
+
process.stdout.write(`${tag} ${r.id} cid=${r.client_message_id} attempts=${r.attempts}${bm}${err}
|
|
10237
|
+
`);
|
|
10238
|
+
}
|
|
10239
|
+
return 0;
|
|
10240
|
+
} catch (err) {
|
|
10241
|
+
process.stderr.write(`daemon unreachable: ${String(err)}
|
|
10242
|
+
`);
|
|
10243
|
+
return 1;
|
|
10244
|
+
}
|
|
10245
|
+
}
|
|
10246
|
+
case "requeue": {
|
|
10247
|
+
const id = rest[1];
|
|
10248
|
+
if (!id) {
|
|
10249
|
+
process.stderr.write(`usage: claudemesh daemon outbox requeue <id> [--new-client-id <id>]
|
|
10250
|
+
`);
|
|
10251
|
+
return 2;
|
|
8007
10252
|
}
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
const
|
|
8011
|
-
|
|
8012
|
-
|
|
10253
|
+
const newClientMessageId = opts.newClientId;
|
|
10254
|
+
try {
|
|
10255
|
+
const res = await ipc({
|
|
10256
|
+
method: "POST",
|
|
10257
|
+
path: "/v1/outbox/requeue",
|
|
10258
|
+
body: { id, new_client_message_id: newClientMessageId }
|
|
10259
|
+
});
|
|
10260
|
+
if (res.status === 200) {
|
|
10261
|
+
if (opts.json)
|
|
10262
|
+
process.stdout.write(JSON.stringify(res.body) + `
|
|
8013
10263
|
`);
|
|
8014
|
-
|
|
8015
|
-
|
|
10264
|
+
else
|
|
10265
|
+
process.stdout.write(`requeued: aborted ${res.body.aborted_row_id} → new ${res.body.new_row_id} ` + `(client_message_id=${res.body.new_client_message_id})
|
|
10266
|
+
`);
|
|
10267
|
+
return 0;
|
|
10268
|
+
}
|
|
10269
|
+
process.stderr.write(`requeue failed (${res.status}): ${res.body.error ?? "unknown"}
|
|
10270
|
+
`);
|
|
10271
|
+
return 1;
|
|
10272
|
+
} catch (err) {
|
|
10273
|
+
process.stderr.write(`daemon unreachable: ${String(err)}
|
|
8016
10274
|
`);
|
|
10275
|
+
return 1;
|
|
8017
10276
|
}
|
|
8018
|
-
});
|
|
8019
|
-
return;
|
|
8020
|
-
}
|
|
8021
|
-
if (action === "cancel") {
|
|
8022
|
-
const id = positional[1];
|
|
8023
|
-
if (!id) {
|
|
8024
|
-
render.err("Usage: claudemesh remind cancel <id>");
|
|
8025
|
-
process.exit(1);
|
|
8026
10277
|
}
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
|
|
8032
|
-
|
|
8033
|
-
process.exit(1);
|
|
8034
|
-
}
|
|
8035
|
-
});
|
|
8036
|
-
return;
|
|
10278
|
+
default:
|
|
10279
|
+
process.stderr.write(`unknown outbox subcommand: ${sub}
|
|
10280
|
+
`);
|
|
10281
|
+
process.stderr.write(`usage: claudemesh daemon outbox [list|requeue <id>]
|
|
10282
|
+
`);
|
|
10283
|
+
return 2;
|
|
8037
10284
|
}
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
8044
|
-
|
|
8045
|
-
|
|
10285
|
+
}
|
|
10286
|
+
async function runInstallService(opts) {
|
|
10287
|
+
const { installService: installService2, detectPlatform: detectPlatform2 } = await Promise.resolve().then(() => (init_service_install(), exports_service_install));
|
|
10288
|
+
const platform5 = detectPlatform2();
|
|
10289
|
+
if (!platform5) {
|
|
10290
|
+
process.stderr.write(`unsupported platform: ${process.platform}
|
|
10291
|
+
`);
|
|
10292
|
+
return 2;
|
|
8046
10293
|
}
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
process.exit(1);
|
|
10294
|
+
if (!opts.mesh) {
|
|
10295
|
+
process.stderr.write(`pass --mesh <slug> so the service knows which mesh to attach to
|
|
10296
|
+
`);
|
|
10297
|
+
return 2;
|
|
8052
10298
|
}
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
render.err(`Peer "${flags.to}" not found`, `online: ${peers.map((p) => p.displayName).join(", ") || "(none)"}`);
|
|
8063
|
-
process.exit(1);
|
|
8064
|
-
}
|
|
8065
|
-
targetSpec = match.pubkey;
|
|
8066
|
-
}
|
|
8067
|
-
} else {
|
|
8068
|
-
targetSpec = client.getSessionPubkey() ?? "*";
|
|
8069
|
-
}
|
|
8070
|
-
const result = await client.scheduleMessage(targetSpec, message, deliverAt ?? 0, false, flags.cron);
|
|
8071
|
-
if (!result) {
|
|
8072
|
-
render.err("Broker did not acknowledge — check connection");
|
|
8073
|
-
process.exit(1);
|
|
8074
|
-
}
|
|
8075
|
-
if (flags.json) {
|
|
8076
|
-
console.log(JSON.stringify(result));
|
|
8077
|
-
return;
|
|
10299
|
+
let binary = process.argv[1] ?? "";
|
|
10300
|
+
if (!binary || /\.ts$/.test(binary) || /node_modules|src\/entrypoints/.test(binary)) {
|
|
10301
|
+
try {
|
|
10302
|
+
const { execSync: execSync3 } = await import("node:child_process");
|
|
10303
|
+
binary = execSync3("which claudemesh", { encoding: "utf8" }).trim();
|
|
10304
|
+
} catch {
|
|
10305
|
+
process.stderr.write(`couldn't resolve a 'claudemesh' binary on PATH; install via npm/homebrew first
|
|
10306
|
+
`);
|
|
10307
|
+
return 1;
|
|
8078
10308
|
}
|
|
8079
|
-
|
|
8080
|
-
|
|
8081
|
-
|
|
8082
|
-
|
|
10309
|
+
}
|
|
10310
|
+
try {
|
|
10311
|
+
const r = installService2({
|
|
10312
|
+
binaryPath: binary,
|
|
10313
|
+
meshSlug: opts.mesh,
|
|
10314
|
+
displayName: opts.displayName
|
|
10315
|
+
});
|
|
10316
|
+
if (opts.json) {
|
|
10317
|
+
process.stdout.write(JSON.stringify({ ok: true, ...r }) + `
|
|
10318
|
+
`);
|
|
8083
10319
|
} else {
|
|
8084
|
-
|
|
8085
|
-
|
|
10320
|
+
process.stdout.write(`installed ${r.platform} service unit: ${r.unitPath}
|
|
10321
|
+
`);
|
|
10322
|
+
process.stdout.write(`bring it up now: ${r.bootCommand}
|
|
10323
|
+
`);
|
|
8086
10324
|
}
|
|
8087
|
-
|
|
10325
|
+
return 0;
|
|
10326
|
+
} catch (err) {
|
|
10327
|
+
process.stderr.write(`install-service failed: ${String(err)}
|
|
10328
|
+
`);
|
|
10329
|
+
return 1;
|
|
10330
|
+
}
|
|
8088
10331
|
}
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
|
|
8098
|
-
|
|
8099
|
-
}
|
|
8100
|
-
|
|
8101
|
-
return
|
|
10332
|
+
async function runUninstallService(opts) {
|
|
10333
|
+
const { uninstallService: uninstallService2 } = await Promise.resolve().then(() => (init_service_install(), exports_service_install));
|
|
10334
|
+
const r = uninstallService2();
|
|
10335
|
+
if (opts.json)
|
|
10336
|
+
process.stdout.write(JSON.stringify(r) + `
|
|
10337
|
+
`);
|
|
10338
|
+
else if (r.removed.length === 0)
|
|
10339
|
+
process.stdout.write(`no service unit installed
|
|
10340
|
+
`);
|
|
10341
|
+
else
|
|
10342
|
+
process.stdout.write(`removed: ${r.removed.join(", ")}
|
|
10343
|
+
`);
|
|
10344
|
+
return 0;
|
|
8102
10345
|
}
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
10346
|
+
async function runAcceptHost(opts) {
|
|
10347
|
+
const { acceptCurrentHost: acceptCurrentHost2 } = await Promise.resolve().then(() => (init_identity(), exports_identity));
|
|
10348
|
+
const fp = acceptCurrentHost2();
|
|
10349
|
+
if (opts.json)
|
|
10350
|
+
process.stdout.write(JSON.stringify({ ok: true, fingerprint_prefix: fp.fingerprint.slice(0, 16) }) + `
|
|
10351
|
+
`);
|
|
10352
|
+
else
|
|
10353
|
+
process.stdout.write(`host fingerprint accepted: ${fp.fingerprint.slice(0, 16)}…
|
|
10354
|
+
`);
|
|
10355
|
+
return 0;
|
|
10356
|
+
}
|
|
10357
|
+
async function runStatus(opts) {
|
|
10358
|
+
const pid = readRunningPid();
|
|
10359
|
+
if (!pid) {
|
|
10360
|
+
if (opts.json)
|
|
10361
|
+
process.stdout.write(JSON.stringify({ running: false }) + `
|
|
10362
|
+
`);
|
|
10363
|
+
else
|
|
10364
|
+
process.stdout.write(`daemon: not running
|
|
10365
|
+
`);
|
|
10366
|
+
return 1;
|
|
10367
|
+
}
|
|
8113
10368
|
try {
|
|
8114
|
-
const
|
|
8115
|
-
if (
|
|
8116
|
-
|
|
10369
|
+
const res = await ipc({ path: "/v1/health" });
|
|
10370
|
+
if (opts.json) {
|
|
10371
|
+
process.stdout.write(JSON.stringify({ running: true, pid, health: res.body }) + `
|
|
10372
|
+
`);
|
|
8117
10373
|
} else {
|
|
8118
|
-
|
|
8119
|
-
|
|
10374
|
+
process.stdout.write(`daemon: running (pid ${pid})
|
|
10375
|
+
`);
|
|
10376
|
+
process.stdout.write(`socket: ${DAEMON_PATHS.SOCK_FILE}
|
|
10377
|
+
`);
|
|
8120
10378
|
}
|
|
8121
|
-
|
|
8122
|
-
return EXIT.SUCCESS;
|
|
10379
|
+
return 0;
|
|
8123
10380
|
} catch (err) {
|
|
8124
|
-
|
|
8125
|
-
|
|
10381
|
+
if (opts.json)
|
|
10382
|
+
process.stdout.write(JSON.stringify({ running: true, pid, ipc_error: String(err) }) + `
|
|
10383
|
+
`);
|
|
10384
|
+
else
|
|
10385
|
+
process.stdout.write(`daemon: pid ${pid} alive but IPC unreachable (${String(err)})
|
|
10386
|
+
`);
|
|
10387
|
+
return 1;
|
|
8126
10388
|
}
|
|
8127
10389
|
}
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
}
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
10390
|
+
async function runVersion(opts) {
|
|
10391
|
+
try {
|
|
10392
|
+
const res = await ipc({ path: "/v1/version" });
|
|
10393
|
+
if (opts.json)
|
|
10394
|
+
process.stdout.write(JSON.stringify(res.body) + `
|
|
10395
|
+
`);
|
|
10396
|
+
else {
|
|
10397
|
+
const v = res.body;
|
|
10398
|
+
process.stdout.write(`daemon ${v.daemon_version ?? "unknown"} (ipc ${v.ipc_api ?? "?"}, schema ${v.schema_version ?? "?"})
|
|
10399
|
+
`);
|
|
10400
|
+
}
|
|
10401
|
+
return 0;
|
|
10402
|
+
} catch (err) {
|
|
10403
|
+
if (err instanceof IpcError) {
|
|
10404
|
+
process.stderr.write(`${err.message}
|
|
10405
|
+
`);
|
|
10406
|
+
return err.status === 401 ? 3 : 1;
|
|
10407
|
+
}
|
|
10408
|
+
process.stderr.write(`daemon unreachable: ${String(err)}
|
|
10409
|
+
`);
|
|
10410
|
+
return 1;
|
|
8144
10411
|
}
|
|
8145
|
-
|
|
8146
|
-
|
|
8147
|
-
|
|
10412
|
+
}
|
|
10413
|
+
async function runStop(opts) {
|
|
10414
|
+
const pid = readRunningPid();
|
|
10415
|
+
if (!pid) {
|
|
10416
|
+
if (opts.json)
|
|
10417
|
+
process.stdout.write(JSON.stringify({ stopped: false, reason: "not_running" }) + `
|
|
10418
|
+
`);
|
|
10419
|
+
else
|
|
10420
|
+
process.stdout.write(`daemon: not running
|
|
10421
|
+
`);
|
|
10422
|
+
return 0;
|
|
8148
10423
|
}
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
|
|
8154
|
-
|
|
8155
|
-
]);
|
|
8156
|
-
} else {
|
|
8157
|
-
render.kv([
|
|
8158
|
-
["web", dim("not signed in · run `claudemesh login` for account features")]
|
|
8159
|
-
]);
|
|
10424
|
+
try {
|
|
10425
|
+
process.kill(pid, "SIGTERM");
|
|
10426
|
+
} catch (err) {
|
|
10427
|
+
process.stderr.write(`failed to signal pid ${pid}: ${String(err)}
|
|
10428
|
+
`);
|
|
10429
|
+
return 1;
|
|
8160
10430
|
}
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
10431
|
+
for (let i = 0;i < 50; i++) {
|
|
10432
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
10433
|
+
if (!readRunningPid()) {
|
|
10434
|
+
if (opts.json)
|
|
10435
|
+
process.stdout.write(JSON.stringify({ stopped: true, pid }) + `
|
|
10436
|
+
`);
|
|
10437
|
+
else
|
|
10438
|
+
process.stdout.write(`daemon: stopped (was pid ${pid})
|
|
10439
|
+
`);
|
|
10440
|
+
return 0;
|
|
8168
10441
|
}
|
|
8169
10442
|
}
|
|
8170
|
-
|
|
8171
|
-
|
|
10443
|
+
if (opts.json)
|
|
10444
|
+
process.stdout.write(JSON.stringify({ stopped: false, pid, reason: "shutdown_timeout" }) + `
|
|
10445
|
+
`);
|
|
10446
|
+
else
|
|
10447
|
+
process.stdout.write(`daemon: signaled but did not exit within 5s (pid ${pid})
|
|
10448
|
+
`);
|
|
10449
|
+
return 1;
|
|
8172
10450
|
}
|
|
8173
|
-
var
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
10451
|
+
var init_daemon = __esm(() => {
|
|
10452
|
+
init_run();
|
|
10453
|
+
init_client4();
|
|
10454
|
+
init_lock();
|
|
10455
|
+
init_paths2();
|
|
8178
10456
|
});
|
|
8179
10457
|
|
|
8180
10458
|
// src/commands/install.ts
|
|
@@ -8184,21 +10462,21 @@ __export(exports_install, {
|
|
|
8184
10462
|
runInstall: () => runInstall
|
|
8185
10463
|
});
|
|
8186
10464
|
import {
|
|
8187
|
-
chmodSync as
|
|
10465
|
+
chmodSync as chmodSync4,
|
|
8188
10466
|
copyFileSync,
|
|
8189
|
-
existsSync as
|
|
8190
|
-
mkdirSync as
|
|
8191
|
-
readFileSync as
|
|
8192
|
-
writeFileSync as
|
|
10467
|
+
existsSync as existsSync13,
|
|
10468
|
+
mkdirSync as mkdirSync8,
|
|
10469
|
+
readFileSync as readFileSync10,
|
|
10470
|
+
writeFileSync as writeFileSync10
|
|
8193
10471
|
} from "node:fs";
|
|
8194
|
-
import { homedir as
|
|
8195
|
-
import { dirname as
|
|
10472
|
+
import { homedir as homedir7, platform as platform5 } from "node:os";
|
|
10473
|
+
import { dirname as dirname6, join as join9, resolve } from "node:path";
|
|
8196
10474
|
import { fileURLToPath } from "node:url";
|
|
8197
10475
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
8198
10476
|
function readClaudeConfig() {
|
|
8199
|
-
if (!
|
|
10477
|
+
if (!existsSync13(CLAUDE_CONFIG))
|
|
8200
10478
|
return {};
|
|
8201
|
-
const text =
|
|
10479
|
+
const text = readFileSync10(CLAUDE_CONFIG, "utf-8").trim();
|
|
8202
10480
|
if (!text)
|
|
8203
10481
|
return {};
|
|
8204
10482
|
try {
|
|
@@ -8208,12 +10486,12 @@ function readClaudeConfig() {
|
|
|
8208
10486
|
}
|
|
8209
10487
|
}
|
|
8210
10488
|
function backupClaudeConfig() {
|
|
8211
|
-
if (!
|
|
10489
|
+
if (!existsSync13(CLAUDE_CONFIG))
|
|
8212
10490
|
return;
|
|
8213
|
-
const backupDir =
|
|
8214
|
-
|
|
10491
|
+
const backupDir = join9(dirname6(CLAUDE_CONFIG), ".claude", "backups");
|
|
10492
|
+
mkdirSync8(backupDir, { recursive: true });
|
|
8215
10493
|
const ts = Date.now();
|
|
8216
|
-
const dest =
|
|
10494
|
+
const dest = join9(backupDir, `.claude.json.pre-claudemesh.${ts}`);
|
|
8217
10495
|
copyFileSync(CLAUDE_CONFIG, dest);
|
|
8218
10496
|
}
|
|
8219
10497
|
function patchMcpServer(entry) {
|
|
@@ -8237,7 +10515,7 @@ function patchMcpServer(entry) {
|
|
|
8237
10515
|
return action;
|
|
8238
10516
|
}
|
|
8239
10517
|
function removeMcpServer() {
|
|
8240
|
-
if (!
|
|
10518
|
+
if (!existsSync13(CLAUDE_CONFIG))
|
|
8241
10519
|
return false;
|
|
8242
10520
|
backupClaudeConfig();
|
|
8243
10521
|
const cfg = readClaudeConfig();
|
|
@@ -8250,11 +10528,11 @@ function removeMcpServer() {
|
|
|
8250
10528
|
return true;
|
|
8251
10529
|
}
|
|
8252
10530
|
function flushClaudeConfig(obj) {
|
|
8253
|
-
|
|
8254
|
-
|
|
10531
|
+
mkdirSync8(dirname6(CLAUDE_CONFIG), { recursive: true });
|
|
10532
|
+
writeFileSync10(CLAUDE_CONFIG, JSON.stringify(obj, null, 2) + `
|
|
8255
10533
|
`, "utf-8");
|
|
8256
10534
|
try {
|
|
8257
|
-
|
|
10535
|
+
chmodSync4(CLAUDE_CONFIG, 384);
|
|
8258
10536
|
} catch {}
|
|
8259
10537
|
}
|
|
8260
10538
|
function bunAvailable() {
|
|
@@ -8268,13 +10546,13 @@ function resolveEntry() {
|
|
|
8268
10546
|
const here = fileURLToPath(import.meta.url);
|
|
8269
10547
|
if (isBundledFile(here))
|
|
8270
10548
|
return here;
|
|
8271
|
-
return resolve(
|
|
10549
|
+
return resolve(dirname6(here), "..", "index.ts");
|
|
8272
10550
|
}
|
|
8273
10551
|
function resolveBundledSkillsDir() {
|
|
8274
10552
|
const here = fileURLToPath(import.meta.url);
|
|
8275
|
-
const pkgRoot = resolve(
|
|
8276
|
-
const skillsDir =
|
|
8277
|
-
if (
|
|
10553
|
+
const pkgRoot = resolve(dirname6(here), "..", "..");
|
|
10554
|
+
const skillsDir = join9(pkgRoot, "skills");
|
|
10555
|
+
if (existsSync13(skillsDir))
|
|
8278
10556
|
return skillsDir;
|
|
8279
10557
|
return null;
|
|
8280
10558
|
}
|
|
@@ -8287,13 +10565,13 @@ function installSkills() {
|
|
|
8287
10565
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
8288
10566
|
if (!entry.isDirectory())
|
|
8289
10567
|
continue;
|
|
8290
|
-
const srcDir =
|
|
8291
|
-
const dstDir =
|
|
8292
|
-
|
|
10568
|
+
const srcDir = join9(src, entry.name);
|
|
10569
|
+
const dstDir = join9(CLAUDE_SKILLS_ROOT, entry.name);
|
|
10570
|
+
mkdirSync8(dstDir, { recursive: true });
|
|
8293
10571
|
for (const file of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
8294
10572
|
if (!file.isFile())
|
|
8295
10573
|
continue;
|
|
8296
|
-
copyFileSync(
|
|
10574
|
+
copyFileSync(join9(srcDir, file.name), join9(dstDir, file.name));
|
|
8297
10575
|
}
|
|
8298
10576
|
installed.push(entry.name);
|
|
8299
10577
|
}
|
|
@@ -8308,8 +10586,8 @@ function uninstallSkills() {
|
|
|
8308
10586
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
8309
10587
|
if (!entry.isDirectory())
|
|
8310
10588
|
continue;
|
|
8311
|
-
const dstDir =
|
|
8312
|
-
if (
|
|
10589
|
+
const dstDir = join9(CLAUDE_SKILLS_ROOT, entry.name);
|
|
10590
|
+
if (existsSync13(dstDir)) {
|
|
8313
10591
|
try {
|
|
8314
10592
|
fs.rmSync(dstDir, { recursive: true, force: true });
|
|
8315
10593
|
removed.push(entry.name);
|
|
@@ -8334,9 +10612,9 @@ function entriesEqual(a, b) {
|
|
|
8334
10612
|
return a.command === b.command && JSON.stringify(a.args ?? []) === JSON.stringify(b.args ?? []);
|
|
8335
10613
|
}
|
|
8336
10614
|
function readClaudeSettings() {
|
|
8337
|
-
if (!
|
|
10615
|
+
if (!existsSync13(CLAUDE_SETTINGS))
|
|
8338
10616
|
return {};
|
|
8339
|
-
const text =
|
|
10617
|
+
const text = readFileSync10(CLAUDE_SETTINGS, "utf-8").trim();
|
|
8340
10618
|
if (!text)
|
|
8341
10619
|
return {};
|
|
8342
10620
|
try {
|
|
@@ -8346,8 +10624,8 @@ function readClaudeSettings() {
|
|
|
8346
10624
|
}
|
|
8347
10625
|
}
|
|
8348
10626
|
function writeClaudeSettings(obj) {
|
|
8349
|
-
|
|
8350
|
-
|
|
10627
|
+
mkdirSync8(dirname6(CLAUDE_SETTINGS), { recursive: true });
|
|
10628
|
+
writeFileSync10(CLAUDE_SETTINGS, JSON.stringify(obj, null, 2) + `
|
|
8351
10629
|
`, "utf-8");
|
|
8352
10630
|
}
|
|
8353
10631
|
function installAllowedTools() {
|
|
@@ -8361,7 +10639,7 @@ function installAllowedTools() {
|
|
|
8361
10639
|
return { added: toAdd, unchanged: CLAUDEMESH_TOOLS.length - toAdd.length };
|
|
8362
10640
|
}
|
|
8363
10641
|
function uninstallAllowedTools() {
|
|
8364
|
-
if (!
|
|
10642
|
+
if (!existsSync13(CLAUDE_SETTINGS))
|
|
8365
10643
|
return 0;
|
|
8366
10644
|
const settings = readClaudeSettings();
|
|
8367
10645
|
const existing = settings.allowedTools ?? [];
|
|
@@ -8396,7 +10674,7 @@ function installHooks() {
|
|
|
8396
10674
|
return { added, unchanged };
|
|
8397
10675
|
}
|
|
8398
10676
|
function uninstallHooks() {
|
|
8399
|
-
if (!
|
|
10677
|
+
if (!existsSync13(CLAUDE_SETTINGS))
|
|
8400
10678
|
return 0;
|
|
8401
10679
|
const settings = readClaudeSettings();
|
|
8402
10680
|
const hooks = settings.hooks;
|
|
@@ -8445,7 +10723,7 @@ function runInstall(args = []) {
|
|
|
8445
10723
|
render.err("`bun` is not on PATH.", "Install Bun first: https://bun.com");
|
|
8446
10724
|
process.exit(1);
|
|
8447
10725
|
}
|
|
8448
|
-
if (!
|
|
10726
|
+
if (!existsSync13(entry)) {
|
|
8449
10727
|
render.err(`MCP entry not found at ${entry}`);
|
|
8450
10728
|
process.exit(1);
|
|
8451
10729
|
}
|
|
@@ -8496,7 +10774,7 @@ function runInstall(args = []) {
|
|
|
8496
10774
|
const installed = installSkills();
|
|
8497
10775
|
if (installed.length > 0) {
|
|
8498
10776
|
render.ok(`Claude skill${installed.length === 1 ? "" : "s"} installed`, installed.join(", "));
|
|
8499
|
-
render.info(dim(` ${
|
|
10777
|
+
render.info(dim(` ${join9(CLAUDE_SKILLS_ROOT, installed[0])}/SKILL.md`));
|
|
8500
10778
|
}
|
|
8501
10779
|
} catch (e) {
|
|
8502
10780
|
render.warn(`skill install failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -8584,9 +10862,9 @@ var init_install = __esm(() => {
|
|
|
8584
10862
|
init_facade();
|
|
8585
10863
|
init_render();
|
|
8586
10864
|
init_styles();
|
|
8587
|
-
CLAUDE_CONFIG =
|
|
8588
|
-
CLAUDE_SETTINGS =
|
|
8589
|
-
CLAUDE_SKILLS_ROOT =
|
|
10865
|
+
CLAUDE_CONFIG = join9(homedir7(), ".claude.json");
|
|
10866
|
+
CLAUDE_SETTINGS = join9(homedir7(), ".claude", "settings.json");
|
|
10867
|
+
CLAUDE_SKILLS_ROOT = join9(homedir7(), ".claude", "skills");
|
|
8590
10868
|
CLAUDEMESH_TOOLS = [
|
|
8591
10869
|
"mcp__claudemesh__cancel_scheduled",
|
|
8592
10870
|
"mcp__claudemesh__check_messages",
|
|
@@ -8641,35 +10919,35 @@ var exports_uninstall = {};
|
|
|
8641
10919
|
__export(exports_uninstall, {
|
|
8642
10920
|
uninstall: () => uninstall
|
|
8643
10921
|
});
|
|
8644
|
-
import { readFileSync as
|
|
8645
|
-
import { join as
|
|
8646
|
-
import { homedir as
|
|
10922
|
+
import { readFileSync as readFileSync11, writeFileSync as writeFileSync11, existsSync as existsSync14, rmSync as rmSync2, readdirSync as readdirSync2 } from "node:fs";
|
|
10923
|
+
import { join as join10, dirname as dirname7 } from "node:path";
|
|
10924
|
+
import { homedir as homedir8 } from "node:os";
|
|
8647
10925
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
8648
10926
|
function bundledSkillsDir() {
|
|
8649
10927
|
const here = fileURLToPath2(import.meta.url);
|
|
8650
|
-
const pkgRoot =
|
|
8651
|
-
const skillsDir =
|
|
8652
|
-
return
|
|
10928
|
+
const pkgRoot = join10(dirname7(here), "..", "..");
|
|
10929
|
+
const skillsDir = join10(pkgRoot, "skills");
|
|
10930
|
+
return existsSync14(skillsDir) ? skillsDir : null;
|
|
8653
10931
|
}
|
|
8654
10932
|
async function uninstall() {
|
|
8655
10933
|
let removed = 0;
|
|
8656
|
-
if (
|
|
10934
|
+
if (existsSync14(PATHS.CLAUDE_JSON)) {
|
|
8657
10935
|
try {
|
|
8658
|
-
const raw =
|
|
10936
|
+
const raw = readFileSync11(PATHS.CLAUDE_JSON, "utf-8");
|
|
8659
10937
|
const config = JSON.parse(raw);
|
|
8660
10938
|
const servers = config.mcpServers;
|
|
8661
10939
|
if (servers && "claudemesh" in servers) {
|
|
8662
10940
|
delete servers.claudemesh;
|
|
8663
|
-
|
|
10941
|
+
writeFileSync11(PATHS.CLAUDE_JSON, JSON.stringify(config, null, 2) + `
|
|
8664
10942
|
`, "utf-8");
|
|
8665
10943
|
render.ok("removed MCP server", dim("~/.claude.json"));
|
|
8666
10944
|
removed++;
|
|
8667
10945
|
}
|
|
8668
10946
|
} catch {}
|
|
8669
10947
|
}
|
|
8670
|
-
if (
|
|
10948
|
+
if (existsSync14(PATHS.CLAUDE_SETTINGS)) {
|
|
8671
10949
|
try {
|
|
8672
|
-
const raw =
|
|
10950
|
+
const raw = readFileSync11(PATHS.CLAUDE_SETTINGS, "utf-8");
|
|
8673
10951
|
const config = JSON.parse(raw);
|
|
8674
10952
|
const hooks = config.hooks;
|
|
8675
10953
|
if (hooks) {
|
|
@@ -8690,7 +10968,7 @@ async function uninstall() {
|
|
|
8690
10968
|
}
|
|
8691
10969
|
}
|
|
8692
10970
|
if (removedHooks > 0) {
|
|
8693
|
-
|
|
10971
|
+
writeFileSync11(PATHS.CLAUDE_SETTINGS, JSON.stringify(config, null, 2) + `
|
|
8694
10972
|
`, "utf-8");
|
|
8695
10973
|
render.ok(`removed ${removedHooks} claudemesh hook${removedHooks === 1 ? "" : "s"}`, dim("settings.json"));
|
|
8696
10974
|
removed++;
|
|
@@ -8705,8 +10983,8 @@ async function uninstall() {
|
|
|
8705
10983
|
for (const entry of readdirSync2(src, { withFileTypes: true })) {
|
|
8706
10984
|
if (!entry.isDirectory())
|
|
8707
10985
|
continue;
|
|
8708
|
-
const dst =
|
|
8709
|
-
if (
|
|
10986
|
+
const dst = join10(CLAUDE_SKILLS_ROOT2, entry.name);
|
|
10987
|
+
if (existsSync14(dst)) {
|
|
8710
10988
|
try {
|
|
8711
10989
|
rmSync2(dst, { recursive: true, force: true });
|
|
8712
10990
|
removedSkills.push(entry.name);
|
|
@@ -8730,7 +11008,7 @@ var init_uninstall = __esm(() => {
|
|
|
8730
11008
|
init_render();
|
|
8731
11009
|
init_styles();
|
|
8732
11010
|
init_exit_codes();
|
|
8733
|
-
CLAUDE_SKILLS_ROOT2 =
|
|
11011
|
+
CLAUDE_SKILLS_ROOT2 = join10(homedir8(), ".claude", "skills");
|
|
8734
11012
|
});
|
|
8735
11013
|
|
|
8736
11014
|
// src/commands/doctor.ts
|
|
@@ -8738,9 +11016,9 @@ var exports_doctor = {};
|
|
|
8738
11016
|
__export(exports_doctor, {
|
|
8739
11017
|
runDoctor: () => runDoctor
|
|
8740
11018
|
});
|
|
8741
|
-
import { existsSync as
|
|
8742
|
-
import { homedir as
|
|
8743
|
-
import { join as
|
|
11019
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12, statSync as statSync2 } from "node:fs";
|
|
11020
|
+
import { homedir as homedir9, platform as platform6 } from "node:os";
|
|
11021
|
+
import { join as join11 } from "node:path";
|
|
8744
11022
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
8745
11023
|
function checkNode() {
|
|
8746
11024
|
const major = Number(process.versions.node.split(".")[0]);
|
|
@@ -8764,8 +11042,8 @@ function checkClaudeOnPath() {
|
|
|
8764
11042
|
};
|
|
8765
11043
|
}
|
|
8766
11044
|
function checkMcpRegistered() {
|
|
8767
|
-
const claudeConfig =
|
|
8768
|
-
if (!
|
|
11045
|
+
const claudeConfig = join11(homedir9(), ".claude.json");
|
|
11046
|
+
if (!existsSync15(claudeConfig)) {
|
|
8769
11047
|
return {
|
|
8770
11048
|
name: "claudemesh MCP registered in ~/.claude.json",
|
|
8771
11049
|
pass: false,
|
|
@@ -8773,7 +11051,7 @@ function checkMcpRegistered() {
|
|
|
8773
11051
|
};
|
|
8774
11052
|
}
|
|
8775
11053
|
try {
|
|
8776
|
-
const cfg = JSON.parse(
|
|
11054
|
+
const cfg = JSON.parse(readFileSync12(claudeConfig, "utf-8"));
|
|
8777
11055
|
const registered = Boolean(cfg.mcpServers?.["claudemesh"]);
|
|
8778
11056
|
return {
|
|
8779
11057
|
name: "claudemesh MCP registered in ~/.claude.json",
|
|
@@ -8790,8 +11068,8 @@ function checkMcpRegistered() {
|
|
|
8790
11068
|
}
|
|
8791
11069
|
}
|
|
8792
11070
|
function checkHooksRegistered() {
|
|
8793
|
-
const settings =
|
|
8794
|
-
if (!
|
|
11071
|
+
const settings = join11(homedir9(), ".claude", "settings.json");
|
|
11072
|
+
if (!existsSync15(settings)) {
|
|
8795
11073
|
return {
|
|
8796
11074
|
name: "Status hooks registered in ~/.claude/settings.json",
|
|
8797
11075
|
pass: false,
|
|
@@ -8799,7 +11077,7 @@ function checkHooksRegistered() {
|
|
|
8799
11077
|
};
|
|
8800
11078
|
}
|
|
8801
11079
|
try {
|
|
8802
|
-
const raw =
|
|
11080
|
+
const raw = readFileSync12(settings, "utf-8");
|
|
8803
11081
|
const has = raw.includes("claudemesh hook ");
|
|
8804
11082
|
return {
|
|
8805
11083
|
name: "Status hooks registered in ~/.claude/settings.json",
|
|
@@ -8815,8 +11093,8 @@ function checkHooksRegistered() {
|
|
|
8815
11093
|
}
|
|
8816
11094
|
}
|
|
8817
11095
|
function checkConfigFile() {
|
|
8818
|
-
const
|
|
8819
|
-
if (!
|
|
11096
|
+
const path2 = getConfigPath();
|
|
11097
|
+
if (!existsSync15(path2)) {
|
|
8820
11098
|
return {
|
|
8821
11099
|
name: "~/.claudemesh/config.json exists and parses",
|
|
8822
11100
|
pass: true,
|
|
@@ -8825,14 +11103,14 @@ function checkConfigFile() {
|
|
|
8825
11103
|
}
|
|
8826
11104
|
try {
|
|
8827
11105
|
readConfig();
|
|
8828
|
-
const st = statSync2(
|
|
11106
|
+
const st = statSync2(path2);
|
|
8829
11107
|
const mode = (st.mode & 511).toString(8);
|
|
8830
11108
|
const secure = platform6() === "win32" || mode === "600";
|
|
8831
11109
|
return {
|
|
8832
11110
|
name: "~/.claudemesh/config.json parses + chmod 0600",
|
|
8833
11111
|
pass: secure,
|
|
8834
11112
|
detail: platform6() === "win32" ? "chmod skipped on Windows" : `0${mode}`,
|
|
8835
|
-
fix: secure ? undefined : `chmod 600 ${
|
|
11113
|
+
fix: secure ? undefined : `chmod 600 ${path2}`
|
|
8836
11114
|
};
|
|
8837
11115
|
} catch (e) {
|
|
8838
11116
|
return {
|
|
@@ -8888,8 +11166,8 @@ async function checkBrokerWs() {
|
|
|
8888
11166
|
const wsUrl = URLS.BROKER;
|
|
8889
11167
|
const start = Date.now();
|
|
8890
11168
|
try {
|
|
8891
|
-
const
|
|
8892
|
-
const ws = new
|
|
11169
|
+
const WebSocket3 = (await import("ws")).default;
|
|
11170
|
+
const ws = new WebSocket3(wsUrl);
|
|
8893
11171
|
const result = await new Promise((resolve2) => {
|
|
8894
11172
|
const timer = setTimeout(() => {
|
|
8895
11173
|
try {
|
|
@@ -8997,14 +11275,14 @@ var init_doctor = __esm(() => {
|
|
|
8997
11275
|
// src/commands/status.ts
|
|
8998
11276
|
var exports_status = {};
|
|
8999
11277
|
__export(exports_status, {
|
|
9000
|
-
runStatus: () =>
|
|
11278
|
+
runStatus: () => runStatus2
|
|
9001
11279
|
});
|
|
9002
|
-
import { statSync as statSync3, existsSync as
|
|
9003
|
-
import
|
|
11280
|
+
import { statSync as statSync3, existsSync as existsSync16 } from "node:fs";
|
|
11281
|
+
import WebSocket3 from "ws";
|
|
9004
11282
|
async function probeBroker(url, timeoutMs = 4000) {
|
|
9005
11283
|
return new Promise((resolve2) => {
|
|
9006
11284
|
const started = Date.now();
|
|
9007
|
-
const ws = new
|
|
11285
|
+
const ws = new WebSocket3(url);
|
|
9008
11286
|
const timer = setTimeout(() => {
|
|
9009
11287
|
try {
|
|
9010
11288
|
ws.terminate();
|
|
@@ -9025,11 +11303,11 @@ async function probeBroker(url, timeoutMs = 4000) {
|
|
|
9025
11303
|
});
|
|
9026
11304
|
});
|
|
9027
11305
|
}
|
|
9028
|
-
async function
|
|
11306
|
+
async function runStatus2() {
|
|
9029
11307
|
render.section(`status (v${VERSION})`);
|
|
9030
11308
|
const configPath = getConfigPath();
|
|
9031
11309
|
let configPermsNote = "missing";
|
|
9032
|
-
if (
|
|
11310
|
+
if (existsSync16(configPath)) {
|
|
9033
11311
|
const mode = (statSync3(configPath).mode & 511).toString(8).padStart(4, "0");
|
|
9034
11312
|
configPermsNote = mode === "0600" ? `${mode}` : `${mode} — expected 0600`;
|
|
9035
11313
|
}
|
|
@@ -9175,13 +11453,13 @@ var init_check_claude_binary = __esm(() => {
|
|
|
9175
11453
|
});
|
|
9176
11454
|
|
|
9177
11455
|
// src/services/health/check-mcp-registered.ts
|
|
9178
|
-
import { existsSync as
|
|
11456
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "node:fs";
|
|
9179
11457
|
function checkMcpRegistered2() {
|
|
9180
11458
|
try {
|
|
9181
|
-
if (!
|
|
11459
|
+
if (!existsSync17(PATHS.CLAUDE_JSON)) {
|
|
9182
11460
|
return { name: "mcp-registered", ok: false, message: "~/.claude.json not found" };
|
|
9183
11461
|
}
|
|
9184
|
-
const raw =
|
|
11462
|
+
const raw = readFileSync13(PATHS.CLAUDE_JSON, "utf-8");
|
|
9185
11463
|
const config = JSON.parse(raw);
|
|
9186
11464
|
if (config.mcpServers && "claudemesh" in config.mcpServers) {
|
|
9187
11465
|
return { name: "mcp-registered", ok: true, message: "MCP server registered" };
|
|
@@ -9196,13 +11474,13 @@ var init_check_mcp_registered = __esm(() => {
|
|
|
9196
11474
|
});
|
|
9197
11475
|
|
|
9198
11476
|
// src/services/health/check-hooks-registered.ts
|
|
9199
|
-
import { existsSync as
|
|
11477
|
+
import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
|
|
9200
11478
|
function checkHooksRegistered2() {
|
|
9201
11479
|
try {
|
|
9202
|
-
if (!
|
|
11480
|
+
if (!existsSync18(PATHS.CLAUDE_SETTINGS)) {
|
|
9203
11481
|
return { name: "hooks-registered", ok: false, message: "~/.claude/settings.json not found" };
|
|
9204
11482
|
}
|
|
9205
|
-
const raw =
|
|
11483
|
+
const raw = readFileSync14(PATHS.CLAUDE_SETTINGS, "utf-8");
|
|
9206
11484
|
const config = JSON.parse(raw);
|
|
9207
11485
|
if (config.hooks) {
|
|
9208
11486
|
return { name: "hooks-registered", ok: true, message: "Hooks configured" };
|
|
@@ -9217,10 +11495,10 @@ var init_check_hooks_registered = __esm(() => {
|
|
|
9217
11495
|
});
|
|
9218
11496
|
|
|
9219
11497
|
// src/services/health/check-config-perms.ts
|
|
9220
|
-
import { existsSync as
|
|
11498
|
+
import { existsSync as existsSync19, statSync as statSync4 } from "node:fs";
|
|
9221
11499
|
function checkConfigPerms() {
|
|
9222
11500
|
const configFile = PATHS.CONFIG_FILE;
|
|
9223
|
-
if (!
|
|
11501
|
+
if (!existsSync19(configFile)) {
|
|
9224
11502
|
return { name: "config-perms", ok: true, message: "No config file yet (first run)" };
|
|
9225
11503
|
}
|
|
9226
11504
|
try {
|
|
@@ -9238,13 +11516,13 @@ var init_check_config_perms = __esm(() => {
|
|
|
9238
11516
|
});
|
|
9239
11517
|
|
|
9240
11518
|
// src/services/health/check-keypairs-valid.ts
|
|
9241
|
-
import { existsSync as
|
|
11519
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15 } from "node:fs";
|
|
9242
11520
|
function checkKeypairsValid() {
|
|
9243
|
-
if (!
|
|
11521
|
+
if (!existsSync20(PATHS.CONFIG_FILE)) {
|
|
9244
11522
|
return { name: "keypairs-valid", ok: true, message: "No config (first run)" };
|
|
9245
11523
|
}
|
|
9246
11524
|
try {
|
|
9247
|
-
const raw =
|
|
11525
|
+
const raw = readFileSync15(PATHS.CONFIG_FILE, "utf-8");
|
|
9248
11526
|
const config = JSON.parse(raw);
|
|
9249
11527
|
const meshes = config.meshes ?? [];
|
|
9250
11528
|
if (meshes.length === 0) {
|
|
@@ -9645,12 +11923,12 @@ var exports_verify = {};
|
|
|
9645
11923
|
__export(exports_verify, {
|
|
9646
11924
|
runVerify: () => runVerify
|
|
9647
11925
|
});
|
|
9648
|
-
import { createHash } from "node:crypto";
|
|
11926
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
9649
11927
|
function safetyNumber(myPubkey, peerPubkey) {
|
|
9650
11928
|
const a = Buffer.from(myPubkey, "hex");
|
|
9651
11929
|
const b = Buffer.from(peerPubkey, "hex");
|
|
9652
11930
|
const [lo, hi] = Buffer.compare(a, b) < 0 ? [a, b] : [b, a];
|
|
9653
|
-
const hash =
|
|
11931
|
+
const hash = createHash3("sha256").update(lo).update(hi).digest();
|
|
9654
11932
|
const bits = [];
|
|
9655
11933
|
for (let i = 0;i < 15; i++) {
|
|
9656
11934
|
for (let b2 = 7;b2 >= 0; b2--) {
|
|
@@ -9724,19 +12002,19 @@ var exports_url_handler = {};
|
|
|
9724
12002
|
__export(exports_url_handler, {
|
|
9725
12003
|
runUrlHandler: () => runUrlHandler
|
|
9726
12004
|
});
|
|
9727
|
-
import { platform as platform7, homedir as
|
|
9728
|
-
import { existsSync as
|
|
9729
|
-
import { join as
|
|
12005
|
+
import { platform as platform7, homedir as homedir10 } from "node:os";
|
|
12006
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync9, writeFileSync as writeFileSync12, rmSync as rmSync3, chmodSync as chmodSync5 } from "node:fs";
|
|
12007
|
+
import { join as join12 } from "node:path";
|
|
9730
12008
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
9731
12009
|
function resolveClaudemeshBin() {
|
|
9732
12010
|
return process.argv[1] ?? "claudemesh";
|
|
9733
12011
|
}
|
|
9734
|
-
function
|
|
12012
|
+
function installDarwin2() {
|
|
9735
12013
|
const binPath = resolveClaudemeshBin();
|
|
9736
|
-
const appDir =
|
|
9737
|
-
const contents =
|
|
9738
|
-
const macOS =
|
|
9739
|
-
|
|
12014
|
+
const appDir = join12(homedir10(), "Library", "Application Support", "claudemesh", "ClaudemeshHandler.app");
|
|
12015
|
+
const contents = join12(appDir, "Contents");
|
|
12016
|
+
const macOS = join12(contents, "MacOS");
|
|
12017
|
+
mkdirSync9(macOS, { recursive: true });
|
|
9740
12018
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
9741
12019
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
9742
12020
|
<plist version="1.0">
|
|
@@ -9759,7 +12037,7 @@ function installDarwin() {
|
|
|
9759
12037
|
</array>
|
|
9760
12038
|
</dict>
|
|
9761
12039
|
</plist>`;
|
|
9762
|
-
|
|
12040
|
+
writeFileSync12(join12(contents, "Info.plist"), plist);
|
|
9763
12041
|
const shim = `#!/bin/sh
|
|
9764
12042
|
URL="$1"
|
|
9765
12043
|
CODE=\${URL#claudemesh://}
|
|
@@ -9773,9 +12051,9 @@ tell application "Terminal"
|
|
|
9773
12051
|
end tell
|
|
9774
12052
|
EOF
|
|
9775
12053
|
`;
|
|
9776
|
-
const shimPath =
|
|
9777
|
-
|
|
9778
|
-
|
|
12054
|
+
const shimPath = join12(macOS, "open-url");
|
|
12055
|
+
writeFileSync12(shimPath, shim);
|
|
12056
|
+
chmodSync5(shimPath, 493);
|
|
9779
12057
|
const lsreg = spawnSync5("/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", ["-f", appDir], { encoding: "utf-8" });
|
|
9780
12058
|
if (lsreg.status !== 0) {
|
|
9781
12059
|
render.warn("lsregister returned non-zero", "scheme may not activate until Finder rescans.");
|
|
@@ -9783,10 +12061,10 @@ EOF
|
|
|
9783
12061
|
render.ok("registered claudemesh:// scheme on macOS", dim(appDir));
|
|
9784
12062
|
return EXIT.SUCCESS;
|
|
9785
12063
|
}
|
|
9786
|
-
function
|
|
12064
|
+
function installLinux2() {
|
|
9787
12065
|
const binPath = resolveClaudemeshBin();
|
|
9788
|
-
const appsDir =
|
|
9789
|
-
|
|
12066
|
+
const appsDir = join12(homedir10(), ".local", "share", "applications");
|
|
12067
|
+
mkdirSync9(appsDir, { recursive: true });
|
|
9790
12068
|
const desktop = `[Desktop Entry]
|
|
9791
12069
|
Type=Application
|
|
9792
12070
|
Name=Claudemesh
|
|
@@ -9797,8 +12075,8 @@ Terminal=true
|
|
|
9797
12075
|
MimeType=x-scheme-handler/claudemesh;
|
|
9798
12076
|
NoDisplay=true
|
|
9799
12077
|
`;
|
|
9800
|
-
const desktopPath =
|
|
9801
|
-
|
|
12078
|
+
const desktopPath = join12(appsDir, "claudemesh.desktop");
|
|
12079
|
+
writeFileSync12(desktopPath, desktop);
|
|
9802
12080
|
const xdg1 = spawnSync5("xdg-mime", ["default", "claudemesh.desktop", "x-scheme-handler/claudemesh"], { encoding: "utf-8" });
|
|
9803
12081
|
if (xdg1.status !== 0) {
|
|
9804
12082
|
render.warn("xdg-mime not available — skipped mime default registration");
|
|
@@ -9820,8 +12098,8 @@ function installWindows() {
|
|
|
9820
12098
|
`[HKEY_CURRENT_USER\\Software\\Classes\\claudemesh\\shell\\open\\command]`,
|
|
9821
12099
|
`@="\\"${binPath.replace(/\\/g, "\\\\")}\\" \\"%1\\""`
|
|
9822
12100
|
];
|
|
9823
|
-
const regPath =
|
|
9824
|
-
|
|
12101
|
+
const regPath = join12(homedir10(), "claudemesh-handler.reg");
|
|
12102
|
+
writeFileSync12(regPath, lines.join(`\r
|
|
9825
12103
|
`));
|
|
9826
12104
|
const res = spawnSync5("reg.exe", ["import", regPath], { encoding: "utf-8" });
|
|
9827
12105
|
if (res.status !== 0) {
|
|
@@ -9832,15 +12110,15 @@ function installWindows() {
|
|
|
9832
12110
|
return EXIT.SUCCESS;
|
|
9833
12111
|
}
|
|
9834
12112
|
function uninstallDarwin() {
|
|
9835
|
-
const appDir =
|
|
9836
|
-
if (
|
|
12113
|
+
const appDir = join12(homedir10(), "Library", "Application Support", "claudemesh", "ClaudemeshHandler.app");
|
|
12114
|
+
if (existsSync21(appDir))
|
|
9837
12115
|
rmSync3(appDir, { recursive: true, force: true });
|
|
9838
12116
|
render.ok("removed claudemesh:// handler on macOS");
|
|
9839
12117
|
return EXIT.SUCCESS;
|
|
9840
12118
|
}
|
|
9841
12119
|
function uninstallLinux() {
|
|
9842
|
-
const desktopPath =
|
|
9843
|
-
if (
|
|
12120
|
+
const desktopPath = join12(homedir10(), ".local", "share", "applications", "claudemesh.desktop");
|
|
12121
|
+
if (existsSync21(desktopPath))
|
|
9844
12122
|
rmSync3(desktopPath, { force: true });
|
|
9845
12123
|
render.ok("removed claudemesh:// handler on Linux");
|
|
9846
12124
|
return EXIT.SUCCESS;
|
|
@@ -9855,9 +12133,9 @@ async function runUrlHandler(action) {
|
|
|
9855
12133
|
const p = platform7();
|
|
9856
12134
|
if (act === "install") {
|
|
9857
12135
|
if (p === "darwin")
|
|
9858
|
-
return
|
|
12136
|
+
return installDarwin2();
|
|
9859
12137
|
if (p === "linux")
|
|
9860
|
-
return
|
|
12138
|
+
return installLinux2();
|
|
9861
12139
|
if (p === "win32")
|
|
9862
12140
|
return installWindows();
|
|
9863
12141
|
} else if (act === "uninstall" || act === "remove") {
|
|
@@ -9885,9 +12163,9 @@ var exports_status_line = {};
|
|
|
9885
12163
|
__export(exports_status_line, {
|
|
9886
12164
|
runStatusLine: () => runStatusLine
|
|
9887
12165
|
});
|
|
9888
|
-
import { existsSync as
|
|
9889
|
-
import { join as
|
|
9890
|
-
import { homedir as
|
|
12166
|
+
import { existsSync as existsSync22, readFileSync as readFileSync16 } from "node:fs";
|
|
12167
|
+
import { join as join13 } from "node:path";
|
|
12168
|
+
import { homedir as homedir11 } from "node:os";
|
|
9891
12169
|
async function runStatusLine() {
|
|
9892
12170
|
try {
|
|
9893
12171
|
const config = readConfig();
|
|
@@ -9895,11 +12173,11 @@ async function runStatusLine() {
|
|
|
9895
12173
|
process.stdout.write("◇ claudemesh (not joined)");
|
|
9896
12174
|
return EXIT.SUCCESS;
|
|
9897
12175
|
}
|
|
9898
|
-
const cachePath =
|
|
12176
|
+
const cachePath = join13(homedir11(), ".claudemesh", "peer-cache.json");
|
|
9899
12177
|
let cache = {};
|
|
9900
|
-
if (
|
|
12178
|
+
if (existsSync22(cachePath)) {
|
|
9901
12179
|
try {
|
|
9902
|
-
cache = JSON.parse(
|
|
12180
|
+
cache = JSON.parse(readFileSync16(cachePath, "utf-8"));
|
|
9903
12181
|
} catch {}
|
|
9904
12182
|
}
|
|
9905
12183
|
const pick = config.meshes[0];
|
|
@@ -9930,7 +12208,7 @@ __export(exports_backup, {
|
|
|
9930
12208
|
runRestore: () => runRestore,
|
|
9931
12209
|
runBackup: () => runBackup
|
|
9932
12210
|
});
|
|
9933
|
-
import { readFileSync as
|
|
12211
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync13, existsSync as existsSync23 } from "node:fs";
|
|
9934
12212
|
import { createInterface as createInterface11 } from "node:readline";
|
|
9935
12213
|
function readHidden(prompt5) {
|
|
9936
12214
|
return new Promise((resolve2) => {
|
|
@@ -9972,11 +12250,11 @@ async function deriveKey(pass, salt, s) {
|
|
|
9972
12250
|
}
|
|
9973
12251
|
async function runBackup(outPath) {
|
|
9974
12252
|
const configPath = getConfigPath();
|
|
9975
|
-
if (!
|
|
12253
|
+
if (!existsSync23(configPath)) {
|
|
9976
12254
|
console.error(" No config found — nothing to back up. Join a mesh first.");
|
|
9977
12255
|
return EXIT.NOT_FOUND;
|
|
9978
12256
|
}
|
|
9979
|
-
const plaintext =
|
|
12257
|
+
const plaintext = readFileSync17(configPath);
|
|
9980
12258
|
const pass = await readHidden(" Passphrase (min 12 chars): ");
|
|
9981
12259
|
if (pass.length < 12) {
|
|
9982
12260
|
console.error(" ✗ Passphrase too short.");
|
|
@@ -9994,7 +12272,7 @@ async function runBackup(outPath) {
|
|
|
9994
12272
|
const ciphertext = Buffer.from(s.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, null, null, nonce, key));
|
|
9995
12273
|
const blob = Buffer.concat([MAGIC, salt, nonce, ciphertext]);
|
|
9996
12274
|
const file = outPath ?? `claudemesh-backup-${new Date().toISOString().replace(/[:.]/g, "-")}.cmb`;
|
|
9997
|
-
|
|
12275
|
+
writeFileSync13(file, blob, { mode: 384 });
|
|
9998
12276
|
console.log(`
|
|
9999
12277
|
✓ Backup saved: ${file}`);
|
|
10000
12278
|
console.log(` Size: ${blob.length} bytes. Guard the passphrase — there is no recovery.
|
|
@@ -10006,11 +12284,11 @@ async function runRestore(inPath) {
|
|
|
10006
12284
|
console.error(" Usage: claudemesh restore <backup-file>");
|
|
10007
12285
|
return EXIT.INVALID_ARGS;
|
|
10008
12286
|
}
|
|
10009
|
-
if (!
|
|
12287
|
+
if (!existsSync23(inPath)) {
|
|
10010
12288
|
console.error(` ✗ File not found: ${inPath}`);
|
|
10011
12289
|
return EXIT.NOT_FOUND;
|
|
10012
12290
|
}
|
|
10013
|
-
const blob =
|
|
12291
|
+
const blob = readFileSync17(inPath);
|
|
10014
12292
|
if (blob.length < 4 + 16 + 24 + 17 || !blob.subarray(0, 4).equals(MAGIC)) {
|
|
10015
12293
|
console.error(" ✗ Not a claudemesh backup file (bad magic).");
|
|
10016
12294
|
return EXIT.INVALID_ARGS;
|
|
@@ -10029,12 +12307,12 @@ async function runRestore(inPath) {
|
|
|
10029
12307
|
return EXIT.INTERNAL_ERROR;
|
|
10030
12308
|
}
|
|
10031
12309
|
const configPath = getConfigPath();
|
|
10032
|
-
if (
|
|
12310
|
+
if (existsSync23(configPath)) {
|
|
10033
12311
|
const backupOld = `${configPath}.before-restore.${Date.now()}`;
|
|
10034
|
-
|
|
12312
|
+
writeFileSync13(backupOld, readFileSync17(configPath), { mode: 384 });
|
|
10035
12313
|
console.log(` ↻ Existing config saved to ${backupOld}`);
|
|
10036
12314
|
}
|
|
10037
|
-
|
|
12315
|
+
writeFileSync13(configPath, Buffer.from(plaintext), { mode: 384 });
|
|
10038
12316
|
console.log(`
|
|
10039
12317
|
✓ Config restored to ${configPath}`);
|
|
10040
12318
|
console.log(" Run `claudemesh list` to verify your meshes.\n");
|
|
@@ -10054,8 +12332,8 @@ __export(exports_upgrade, {
|
|
|
10054
12332
|
runUpgrade: () => runUpgrade
|
|
10055
12333
|
});
|
|
10056
12334
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
10057
|
-
import { existsSync as
|
|
10058
|
-
import { dirname as
|
|
12335
|
+
import { existsSync as existsSync24 } from "node:fs";
|
|
12336
|
+
import { dirname as dirname8, join as join14, resolve as resolve2 } from "node:path";
|
|
10059
12337
|
async function latestVersion() {
|
|
10060
12338
|
try {
|
|
10061
12339
|
const res = await fetch(URLS.NPM_REGISTRY, { signal: AbortSignal.timeout(8000) });
|
|
@@ -10068,15 +12346,15 @@ async function latestVersion() {
|
|
|
10068
12346
|
}
|
|
10069
12347
|
}
|
|
10070
12348
|
function findNpm() {
|
|
10071
|
-
const portable =
|
|
10072
|
-
if (
|
|
10073
|
-
return { npm: portable, prefix:
|
|
12349
|
+
const portable = join14(process.env.HOME ?? "", ".claudemesh", "node", "bin", "npm");
|
|
12350
|
+
if (existsSync24(portable)) {
|
|
12351
|
+
return { npm: portable, prefix: join14(process.env.HOME ?? "", ".claudemesh") };
|
|
10074
12352
|
}
|
|
10075
12353
|
let cur = resolve2(process.argv[1] ?? ".");
|
|
10076
12354
|
for (let i = 0;i < 6; i++) {
|
|
10077
|
-
cur =
|
|
10078
|
-
const candidate =
|
|
10079
|
-
if (
|
|
12355
|
+
cur = dirname8(cur);
|
|
12356
|
+
const candidate = join14(cur, "bin", "npm");
|
|
12357
|
+
if (existsSync24(candidate))
|
|
10080
12358
|
return { npm: candidate };
|
|
10081
12359
|
}
|
|
10082
12360
|
return { npm: "npm" };
|
|
@@ -10138,9 +12416,9 @@ __export(exports_grants, {
|
|
|
10138
12416
|
runBlock: () => runBlock,
|
|
10139
12417
|
isAllowed: () => isAllowed
|
|
10140
12418
|
});
|
|
10141
|
-
import { existsSync as
|
|
10142
|
-
import { homedir as
|
|
10143
|
-
import { join as
|
|
12419
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync10, readFileSync as readFileSync18, writeFileSync as writeFileSync14 } from "node:fs";
|
|
12420
|
+
import { homedir as homedir12 } from "node:os";
|
|
12421
|
+
import { join as join15 } from "node:path";
|
|
10144
12422
|
async function syncToBroker(meshSlug, grants) {
|
|
10145
12423
|
const auth = getStoredToken();
|
|
10146
12424
|
if (!auth)
|
|
@@ -10158,19 +12436,19 @@ async function syncToBroker(meshSlug, grants) {
|
|
|
10158
12436
|
}
|
|
10159
12437
|
}
|
|
10160
12438
|
function readGrants() {
|
|
10161
|
-
if (!
|
|
12439
|
+
if (!existsSync25(GRANT_FILE))
|
|
10162
12440
|
return {};
|
|
10163
12441
|
try {
|
|
10164
|
-
return JSON.parse(
|
|
12442
|
+
return JSON.parse(readFileSync18(GRANT_FILE, "utf-8"));
|
|
10165
12443
|
} catch {
|
|
10166
12444
|
return {};
|
|
10167
12445
|
}
|
|
10168
12446
|
}
|
|
10169
12447
|
function writeGrants(g) {
|
|
10170
|
-
const dir =
|
|
10171
|
-
if (!
|
|
10172
|
-
|
|
10173
|
-
|
|
12448
|
+
const dir = join15(homedir12(), ".claudemesh");
|
|
12449
|
+
if (!existsSync25(dir))
|
|
12450
|
+
mkdirSync10(dir, { recursive: true });
|
|
12451
|
+
writeFileSync14(GRANT_FILE, JSON.stringify(g, null, 2), { mode: 384 });
|
|
10174
12452
|
}
|
|
10175
12453
|
function resolveCaps(input) {
|
|
10176
12454
|
if (input.includes("all"))
|
|
@@ -10326,7 +12604,7 @@ var init_grants = __esm(() => {
|
|
|
10326
12604
|
BROKER_HTTP7 = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
|
|
10327
12605
|
ALL_CAPS = ["read", "dm", "broadcast", "state-read", "state-write", "file-read"];
|
|
10328
12606
|
DEFAULT_CAPS = ["read", "dm", "broadcast", "state-read"];
|
|
10329
|
-
GRANT_FILE =
|
|
12607
|
+
GRANT_FILE = join15(homedir12(), ".claudemesh", "grants.json");
|
|
10330
12608
|
});
|
|
10331
12609
|
|
|
10332
12610
|
// src/commands/profile.ts
|
|
@@ -11805,8 +14083,8 @@ __export(exports_file, {
|
|
|
11805
14083
|
runFileGet: () => runFileGet
|
|
11806
14084
|
});
|
|
11807
14085
|
import { hostname as osHostname } from "node:os";
|
|
11808
|
-
import { resolve as resolvePath, basename, dirname as
|
|
11809
|
-
import { statSync as statSync6, existsSync as
|
|
14086
|
+
import { resolve as resolvePath, basename, dirname as dirname9 } from "node:path";
|
|
14087
|
+
import { statSync as statSync6, existsSync as existsSync26, writeFileSync as writeFileSync15, mkdirSync as mkdirSync11 } from "node:fs";
|
|
11810
14088
|
function emitJson2(data) {
|
|
11811
14089
|
console.log(JSON.stringify(data, null, 2));
|
|
11812
14090
|
}
|
|
@@ -11823,7 +14101,7 @@ async function runFileShare(filePath, opts) {
|
|
|
11823
14101
|
return EXIT.INVALID_ARGS;
|
|
11824
14102
|
}
|
|
11825
14103
|
const absPath = resolvePath(filePath);
|
|
11826
|
-
if (!
|
|
14104
|
+
if (!existsSync26(absPath)) {
|
|
11827
14105
|
render.err(`File not found: ${absPath}`);
|
|
11828
14106
|
return EXIT.INVALID_ARGS;
|
|
11829
14107
|
}
|
|
@@ -11902,8 +14180,8 @@ async function runFileGet(fileId, opts) {
|
|
|
11902
14180
|
}
|
|
11903
14181
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
11904
14182
|
const outPath = opts.out ? resolvePath(opts.out) : resolvePath(process.cwd(), meta.name);
|
|
11905
|
-
|
|
11906
|
-
|
|
14183
|
+
mkdirSync11(dirname9(outPath), { recursive: true });
|
|
14184
|
+
writeFileSync15(outPath, buf);
|
|
11907
14185
|
if (opts.json) {
|
|
11908
14186
|
emitJson2({ fileId, name: meta.name, savedTo: outPath, sizeBytes: buf.length });
|
|
11909
14187
|
} else {
|
|
@@ -11998,7 +14276,7 @@ var require_client = __commonJS((exports) => {
|
|
|
11998
14276
|
var ws_1 = __importDefault(__require("ws"));
|
|
11999
14277
|
var crypto_js_1 = require_crypto();
|
|
12000
14278
|
var MAX_QUEUED2 = 100;
|
|
12001
|
-
var
|
|
14279
|
+
var HELLO_ACK_TIMEOUT_MS3 = 5000;
|
|
12002
14280
|
var BACKOFF_CAPS2 = [1000, 2000, 4000, 8000, 16000, 30000];
|
|
12003
14281
|
|
|
12004
14282
|
class MeshClient extends node_events_1.EventEmitter {
|
|
@@ -12063,7 +14341,7 @@ var require_client = __commonJS((exports) => {
|
|
|
12063
14341
|
this.debug("hello_ack timeout");
|
|
12064
14342
|
ws.close();
|
|
12065
14343
|
reject(new Error("hello_ack timeout"));
|
|
12066
|
-
},
|
|
14344
|
+
}, HELLO_ACK_TIMEOUT_MS3);
|
|
12067
14345
|
};
|
|
12068
14346
|
const onMessage = (raw) => {
|
|
12069
14347
|
let msg;
|
|
@@ -12513,7 +14791,7 @@ __export(exports_bridge, {
|
|
|
12513
14791
|
runBridge: () => runBridge,
|
|
12514
14792
|
bridgeConfigTemplate: () => bridgeConfigTemplate
|
|
12515
14793
|
});
|
|
12516
|
-
import { readFileSync as
|
|
14794
|
+
import { readFileSync as readFileSync19, existsSync as existsSync27 } from "node:fs";
|
|
12517
14795
|
function parseConfig(text) {
|
|
12518
14796
|
const trimmed = text.trim();
|
|
12519
14797
|
if (trimmed.startsWith("{"))
|
|
@@ -12557,13 +14835,13 @@ async function runBridge(configPath) {
|
|
|
12557
14835
|
render.err("Usage: claudemesh bridge run <config.yaml>");
|
|
12558
14836
|
return EXIT.INVALID_ARGS;
|
|
12559
14837
|
}
|
|
12560
|
-
if (!
|
|
14838
|
+
if (!existsSync27(configPath)) {
|
|
12561
14839
|
render.err(`config file not found: ${configPath}`);
|
|
12562
14840
|
return EXIT.NOT_FOUND;
|
|
12563
14841
|
}
|
|
12564
14842
|
let cfg;
|
|
12565
14843
|
try {
|
|
12566
|
-
cfg = parseConfig(
|
|
14844
|
+
cfg = parseConfig(readFileSync19(configPath, "utf-8"));
|
|
12567
14845
|
} catch (e) {
|
|
12568
14846
|
render.err(`failed to parse ${configPath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
12569
14847
|
return EXIT.INVALID_ARGS;
|
|
@@ -12967,9 +15245,9 @@ function cacheKey(apiKeySecret, topicName) {
|
|
|
12967
15245
|
async function getTopicKey(args) {
|
|
12968
15246
|
const cacheId = cacheKey(args.apiKeySecret, args.topicName);
|
|
12969
15247
|
if (!args.fresh) {
|
|
12970
|
-
const
|
|
12971
|
-
if (
|
|
12972
|
-
return { ok: true, topicKey:
|
|
15248
|
+
const cached2 = cache.get(cacheId);
|
|
15249
|
+
if (cached2)
|
|
15250
|
+
return { ok: true, topicKey: cached2.topicKey };
|
|
12973
15251
|
}
|
|
12974
15252
|
let sealed;
|
|
12975
15253
|
try {
|
|
@@ -13533,8 +15811,8 @@ var init_definitions = __esm(() => {
|
|
|
13533
15811
|
});
|
|
13534
15812
|
|
|
13535
15813
|
// src/services/bridge/server.ts
|
|
13536
|
-
import { createServer as
|
|
13537
|
-
import { mkdirSync as
|
|
15814
|
+
import { createServer as createServer3 } from "node:net";
|
|
15815
|
+
import { mkdirSync as mkdirSync12, unlinkSync as unlinkSync5, existsSync as existsSync28, chmodSync as chmodSync6 } from "node:fs";
|
|
13538
15816
|
async function resolveTarget2(client, to) {
|
|
13539
15817
|
if (to.startsWith("@") || to === "*" || /^[0-9a-f]{64}$/i.test(to)) {
|
|
13540
15818
|
return { ok: true, spec: to };
|
|
@@ -13646,21 +15924,21 @@ function handleConnection(socket, client) {
|
|
|
13646
15924
|
socket.on("error", () => {});
|
|
13647
15925
|
}
|
|
13648
15926
|
function startBridgeServer(client) {
|
|
13649
|
-
const
|
|
15927
|
+
const path2 = socketPath(client.meshSlug);
|
|
13650
15928
|
const dir = socketDir();
|
|
13651
|
-
if (!
|
|
13652
|
-
|
|
15929
|
+
if (!existsSync28(dir)) {
|
|
15930
|
+
mkdirSync12(dir, { recursive: true, mode: 448 });
|
|
13653
15931
|
}
|
|
13654
|
-
if (
|
|
15932
|
+
if (existsSync28(path2)) {
|
|
13655
15933
|
try {
|
|
13656
|
-
|
|
15934
|
+
unlinkSync5(path2);
|
|
13657
15935
|
} catch {}
|
|
13658
15936
|
}
|
|
13659
|
-
const server =
|
|
15937
|
+
const server = createServer3((socket) => handleConnection(socket, client));
|
|
13660
15938
|
try {
|
|
13661
|
-
server.listen(
|
|
15939
|
+
server.listen(path2);
|
|
13662
15940
|
} catch (err) {
|
|
13663
|
-
process.stderr.write(`[claudemesh] bridge: failed to bind ${
|
|
15941
|
+
process.stderr.write(`[claudemesh] bridge: failed to bind ${path2}: ${String(err)}
|
|
13664
15942
|
`);
|
|
13665
15943
|
return null;
|
|
13666
15944
|
}
|
|
@@ -13669,11 +15947,11 @@ function startBridgeServer(client) {
|
|
|
13669
15947
|
`);
|
|
13670
15948
|
});
|
|
13671
15949
|
try {
|
|
13672
|
-
|
|
15950
|
+
chmodSync6(path2, 384);
|
|
13673
15951
|
} catch {}
|
|
13674
15952
|
let stopped = false;
|
|
13675
15953
|
return {
|
|
13676
|
-
path,
|
|
15954
|
+
path: path2,
|
|
13677
15955
|
stop() {
|
|
13678
15956
|
if (stopped)
|
|
13679
15957
|
return;
|
|
@@ -13682,12 +15960,12 @@ function startBridgeServer(client) {
|
|
|
13682
15960
|
server.close();
|
|
13683
15961
|
} catch {}
|
|
13684
15962
|
try {
|
|
13685
|
-
|
|
15963
|
+
unlinkSync5(path2);
|
|
13686
15964
|
} catch {}
|
|
13687
15965
|
}
|
|
13688
15966
|
};
|
|
13689
15967
|
}
|
|
13690
|
-
var
|
|
15968
|
+
var init_server2 = __esm(() => {
|
|
13691
15969
|
init_protocol();
|
|
13692
15970
|
});
|
|
13693
15971
|
|
|
@@ -14258,9 +16536,9 @@ async function startServiceProxy(serviceName) {
|
|
|
14258
16536
|
const fetched = await client.getServiceTools(serviceName);
|
|
14259
16537
|
tools = fetched;
|
|
14260
16538
|
} catch {
|
|
14261
|
-
const
|
|
14262
|
-
if (
|
|
14263
|
-
tools =
|
|
16539
|
+
const cached2 = client.serviceCatalog.find((s) => s.name === serviceName);
|
|
16540
|
+
if (cached2) {
|
|
16541
|
+
tools = cached2.tools;
|
|
14264
16542
|
}
|
|
14265
16543
|
}
|
|
14266
16544
|
if (tools.length === 0) {
|
|
@@ -14349,11 +16627,11 @@ async function startServiceProxy(serviceName) {
|
|
|
14349
16627
|
process.on("SIGINT", shutdown);
|
|
14350
16628
|
}
|
|
14351
16629
|
var peerNameCache, peerNameCacheAge = 0, CACHE_TTL_MS = 30000;
|
|
14352
|
-
var
|
|
16630
|
+
var init_server3 = __esm(() => {
|
|
14353
16631
|
init_definitions();
|
|
14354
16632
|
init_facade();
|
|
14355
16633
|
init_facade8();
|
|
14356
|
-
|
|
16634
|
+
init_server2();
|
|
14357
16635
|
peerNameCache = new Map;
|
|
14358
16636
|
});
|
|
14359
16637
|
|
|
@@ -14369,7 +16647,7 @@ async function runMcp() {
|
|
|
14369
16647
|
process.exit(0);
|
|
14370
16648
|
}
|
|
14371
16649
|
var init_mcp = __esm(() => {
|
|
14372
|
-
|
|
16650
|
+
init_server3();
|
|
14373
16651
|
});
|
|
14374
16652
|
|
|
14375
16653
|
// src/commands/hook.ts
|
|
@@ -15470,6 +17748,23 @@ async function main() {
|
|
|
15470
17748
|
process.exit(await whoami2({ json: !!flags.json }));
|
|
15471
17749
|
break;
|
|
15472
17750
|
}
|
|
17751
|
+
case "daemon": {
|
|
17752
|
+
const { runDaemonCommand: runDaemonCommand2 } = await Promise.resolve().then(() => (init_daemon(), exports_daemon));
|
|
17753
|
+
const sub = positionals[0];
|
|
17754
|
+
const rest = positionals.slice(1);
|
|
17755
|
+
const outboxStatus = flags.failed ? "dead" : flags.pending ? "pending" : flags.inflight ? "inflight" : flags.done ? "done" : flags.aborted ? "aborted" : undefined;
|
|
17756
|
+
const code = await runDaemonCommand2(sub, {
|
|
17757
|
+
json: !!flags.json,
|
|
17758
|
+
noTcp: !!flags["no-tcp"],
|
|
17759
|
+
publicHealth: !!flags["public-health"],
|
|
17760
|
+
mesh: flags.mesh,
|
|
17761
|
+
displayName: flags.name,
|
|
17762
|
+
outboxStatus,
|
|
17763
|
+
newClientId: flags["new-client-id"]
|
|
17764
|
+
}, rest);
|
|
17765
|
+
process.exit(code);
|
|
17766
|
+
break;
|
|
17767
|
+
}
|
|
15473
17768
|
case "install": {
|
|
15474
17769
|
const { runInstall: runInstall2 } = await Promise.resolve().then(() => (init_install(), exports_install));
|
|
15475
17770
|
runInstall2(positionals);
|
|
@@ -15490,8 +17785,8 @@ async function main() {
|
|
|
15490
17785
|
const { runStatusSet: runStatusSet2 } = await Promise.resolve().then(() => (init_broker_actions(), exports_broker_actions));
|
|
15491
17786
|
process.exit(await runStatusSet2(positionals[1] ?? "", { mesh: flags.mesh, json: !!flags.json }));
|
|
15492
17787
|
} else {
|
|
15493
|
-
const { runStatus:
|
|
15494
|
-
await
|
|
17788
|
+
const { runStatus: runStatus3 } = await Promise.resolve().then(() => (init_status(), exports_status));
|
|
17789
|
+
await runStatus3();
|
|
15495
17790
|
}
|
|
15496
17791
|
break;
|
|
15497
17792
|
}
|
|
@@ -15643,8 +17938,8 @@ async function main() {
|
|
|
15643
17938
|
const { runStatusSet: runStatusSet2 } = await Promise.resolve().then(() => (init_broker_actions(), exports_broker_actions));
|
|
15644
17939
|
process.exit(await runStatusSet2(positionals[2] ?? "", { mesh: flags.mesh, json: !!flags.json }));
|
|
15645
17940
|
} else {
|
|
15646
|
-
const { runStatus:
|
|
15647
|
-
await
|
|
17941
|
+
const { runStatus: runStatus3 } = await Promise.resolve().then(() => (init_status(), exports_status));
|
|
17942
|
+
await runStatus3();
|
|
15648
17943
|
}
|
|
15649
17944
|
} else {
|
|
15650
17945
|
console.error("Usage: claudemesh profile [summary|visible|status]");
|
|
@@ -16131,4 +18426,4 @@ main().catch((err) => {
|
|
|
16131
18426
|
process.exit(EXIT.INTERNAL_ERROR);
|
|
16132
18427
|
});
|
|
16133
18428
|
|
|
16134
|
-
//# debugId=
|
|
18429
|
+
//# debugId=BA4EDB138BD2E1AE64756E2164756E21
|