claudemesh-cli 1.21.0 → 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 +2630 -333
- package/dist/entrypoints/cli.js.map +25 -6
- 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",
|
|
@@ -5180,6 +5180,8 @@ async function rename(oldSlug, newSlug) {
|
|
|
5180
5180
|
return EXIT.PERMISSION_DENIED;
|
|
5181
5181
|
if (err.status === 404)
|
|
5182
5182
|
return EXIT.NOT_FOUND;
|
|
5183
|
+
if (err.status === 409)
|
|
5184
|
+
return EXIT.ALREADY_EXISTS;
|
|
5183
5185
|
if (err.status === 400)
|
|
5184
5186
|
return EXIT.INVALID_ARGS;
|
|
5185
5187
|
return EXIT.INTERNAL_ERROR;
|
|
@@ -6832,6 +6834,154 @@ var init_peers = __esm(() => {
|
|
|
6832
6834
|
};
|
|
6833
6835
|
});
|
|
6834
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
|
+
|
|
6835
6985
|
// src/commands/send.ts
|
|
6836
6986
|
var exports_send = {};
|
|
6837
6987
|
__export(exports_send, {
|
|
@@ -6853,6 +7003,23 @@ async function runSend(flags, to, message) {
|
|
|
6853
7003
|
process.exit(1);
|
|
6854
7004
|
}
|
|
6855
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
|
+
}
|
|
6856
7023
|
if (meshSlug) {
|
|
6857
7024
|
const bridged = await tryBridge(meshSlug, "send", { to, message, priority });
|
|
6858
7025
|
if (bridged !== null) {
|
|
@@ -6916,6 +7083,7 @@ var init_send = __esm(() => {
|
|
|
6916
7083
|
init_connect();
|
|
6917
7084
|
init_facade();
|
|
6918
7085
|
init_client3();
|
|
7086
|
+
init_daemon_route();
|
|
6919
7087
|
init_render();
|
|
6920
7088
|
init_styles();
|
|
6921
7089
|
});
|
|
@@ -7999,180 +8167,2292 @@ async function runRemind(flags, positional) {
|
|
|
7999
8167
|
console.log(JSON.stringify(scheduled, null, 2));
|
|
8000
8168
|
return;
|
|
8001
8169
|
}
|
|
8002
|
-
if (scheduled.length === 0) {
|
|
8003
|
-
render.info(dim("No pending reminders."));
|
|
8004
|
-
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;
|
|
8005
10252
|
}
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
const
|
|
8009
|
-
|
|
8010
|
-
|
|
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) + `
|
|
8011
10263
|
`);
|
|
8012
|
-
|
|
8013
|
-
|
|
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)}
|
|
8014
10274
|
`);
|
|
10275
|
+
return 1;
|
|
8015
10276
|
}
|
|
8016
|
-
});
|
|
8017
|
-
return;
|
|
8018
|
-
}
|
|
8019
|
-
if (action === "cancel") {
|
|
8020
|
-
const id = positional[1];
|
|
8021
|
-
if (!id) {
|
|
8022
|
-
render.err("Usage: claudemesh remind cancel <id>");
|
|
8023
|
-
process.exit(1);
|
|
8024
10277
|
}
|
|
8025
|
-
|
|
8026
|
-
|
|
8027
|
-
|
|
8028
|
-
|
|
8029
|
-
|
|
8030
|
-
|
|
8031
|
-
process.exit(1);
|
|
8032
|
-
}
|
|
8033
|
-
});
|
|
8034
|
-
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;
|
|
8035
10284
|
}
|
|
8036
|
-
|
|
8037
|
-
|
|
8038
|
-
|
|
8039
|
-
|
|
8040
|
-
|
|
8041
|
-
|
|
8042
|
-
|
|
8043
|
-
|
|
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;
|
|
8044
10293
|
}
|
|
8045
|
-
|
|
8046
|
-
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
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;
|
|
8050
10298
|
}
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
|
|
8059
|
-
|
|
8060
|
-
render.err(`Peer "${flags.to}" not found`, `online: ${peers.map((p) => p.displayName).join(", ") || "(none)"}`);
|
|
8061
|
-
process.exit(1);
|
|
8062
|
-
}
|
|
8063
|
-
targetSpec = match.pubkey;
|
|
8064
|
-
}
|
|
8065
|
-
} else {
|
|
8066
|
-
targetSpec = client.getSessionPubkey() ?? "*";
|
|
8067
|
-
}
|
|
8068
|
-
const result = await client.scheduleMessage(targetSpec, message, deliverAt ?? 0, false, flags.cron);
|
|
8069
|
-
if (!result) {
|
|
8070
|
-
render.err("Broker did not acknowledge — check connection");
|
|
8071
|
-
process.exit(1);
|
|
8072
|
-
}
|
|
8073
|
-
if (flags.json) {
|
|
8074
|
-
console.log(JSON.stringify(result));
|
|
8075
|
-
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;
|
|
8076
10308
|
}
|
|
8077
|
-
|
|
8078
|
-
|
|
8079
|
-
|
|
8080
|
-
|
|
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
|
+
`);
|
|
8081
10319
|
} else {
|
|
8082
|
-
|
|
8083
|
-
|
|
10320
|
+
process.stdout.write(`installed ${r.platform} service unit: ${r.unitPath}
|
|
10321
|
+
`);
|
|
10322
|
+
process.stdout.write(`bring it up now: ${r.bootCommand}
|
|
10323
|
+
`);
|
|
8084
10324
|
}
|
|
8085
|
-
|
|
10325
|
+
return 0;
|
|
10326
|
+
} catch (err) {
|
|
10327
|
+
process.stderr.write(`install-service failed: ${String(err)}
|
|
10328
|
+
`);
|
|
10329
|
+
return 1;
|
|
10330
|
+
}
|
|
8086
10331
|
}
|
|
8087
|
-
|
|
8088
|
-
|
|
8089
|
-
|
|
8090
|
-
|
|
8091
|
-
|
|
8092
|
-
|
|
8093
|
-
|
|
8094
|
-
|
|
8095
|
-
|
|
8096
|
-
|
|
8097
|
-
}
|
|
8098
|
-
|
|
8099
|
-
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;
|
|
8100
10345
|
}
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
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
|
+
}
|
|
8111
10368
|
try {
|
|
8112
|
-
const
|
|
8113
|
-
if (
|
|
8114
|
-
|
|
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
|
+
`);
|
|
8115
10373
|
} else {
|
|
8116
|
-
|
|
8117
|
-
|
|
10374
|
+
process.stdout.write(`daemon: running (pid ${pid})
|
|
10375
|
+
`);
|
|
10376
|
+
process.stdout.write(`socket: ${DAEMON_PATHS.SOCK_FILE}
|
|
10377
|
+
`);
|
|
8118
10378
|
}
|
|
8119
|
-
|
|
8120
|
-
return EXIT.SUCCESS;
|
|
10379
|
+
return 0;
|
|
8121
10380
|
} catch (err) {
|
|
8122
|
-
|
|
8123
|
-
|
|
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;
|
|
8124
10388
|
}
|
|
8125
10389
|
}
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
|
|
8130
|
-
|
|
8131
|
-
|
|
8132
|
-
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
}
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
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;
|
|
8142
10411
|
}
|
|
8143
|
-
|
|
8144
|
-
|
|
8145
|
-
|
|
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;
|
|
8146
10423
|
}
|
|
8147
|
-
|
|
8148
|
-
|
|
8149
|
-
|
|
8150
|
-
|
|
8151
|
-
|
|
8152
|
-
|
|
8153
|
-
]);
|
|
8154
|
-
} else {
|
|
8155
|
-
render.kv([
|
|
8156
|
-
["web", dim("not signed in · run `claudemesh login` for account features")]
|
|
8157
|
-
]);
|
|
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;
|
|
8158
10430
|
}
|
|
8159
|
-
|
|
8160
|
-
|
|
8161
|
-
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
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;
|
|
8166
10441
|
}
|
|
8167
10442
|
}
|
|
8168
|
-
|
|
8169
|
-
|
|
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;
|
|
8170
10450
|
}
|
|
8171
|
-
var
|
|
8172
|
-
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
10451
|
+
var init_daemon = __esm(() => {
|
|
10452
|
+
init_run();
|
|
10453
|
+
init_client4();
|
|
10454
|
+
init_lock();
|
|
10455
|
+
init_paths2();
|
|
8176
10456
|
});
|
|
8177
10457
|
|
|
8178
10458
|
// src/commands/install.ts
|
|
@@ -8182,21 +10462,21 @@ __export(exports_install, {
|
|
|
8182
10462
|
runInstall: () => runInstall
|
|
8183
10463
|
});
|
|
8184
10464
|
import {
|
|
8185
|
-
chmodSync as
|
|
10465
|
+
chmodSync as chmodSync4,
|
|
8186
10466
|
copyFileSync,
|
|
8187
|
-
existsSync as
|
|
8188
|
-
mkdirSync as
|
|
8189
|
-
readFileSync as
|
|
8190
|
-
writeFileSync as
|
|
10467
|
+
existsSync as existsSync13,
|
|
10468
|
+
mkdirSync as mkdirSync8,
|
|
10469
|
+
readFileSync as readFileSync10,
|
|
10470
|
+
writeFileSync as writeFileSync10
|
|
8191
10471
|
} from "node:fs";
|
|
8192
|
-
import { homedir as
|
|
8193
|
-
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";
|
|
8194
10474
|
import { fileURLToPath } from "node:url";
|
|
8195
10475
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
8196
10476
|
function readClaudeConfig() {
|
|
8197
|
-
if (!
|
|
10477
|
+
if (!existsSync13(CLAUDE_CONFIG))
|
|
8198
10478
|
return {};
|
|
8199
|
-
const text =
|
|
10479
|
+
const text = readFileSync10(CLAUDE_CONFIG, "utf-8").trim();
|
|
8200
10480
|
if (!text)
|
|
8201
10481
|
return {};
|
|
8202
10482
|
try {
|
|
@@ -8206,12 +10486,12 @@ function readClaudeConfig() {
|
|
|
8206
10486
|
}
|
|
8207
10487
|
}
|
|
8208
10488
|
function backupClaudeConfig() {
|
|
8209
|
-
if (!
|
|
10489
|
+
if (!existsSync13(CLAUDE_CONFIG))
|
|
8210
10490
|
return;
|
|
8211
|
-
const backupDir =
|
|
8212
|
-
|
|
10491
|
+
const backupDir = join9(dirname6(CLAUDE_CONFIG), ".claude", "backups");
|
|
10492
|
+
mkdirSync8(backupDir, { recursive: true });
|
|
8213
10493
|
const ts = Date.now();
|
|
8214
|
-
const dest =
|
|
10494
|
+
const dest = join9(backupDir, `.claude.json.pre-claudemesh.${ts}`);
|
|
8215
10495
|
copyFileSync(CLAUDE_CONFIG, dest);
|
|
8216
10496
|
}
|
|
8217
10497
|
function patchMcpServer(entry) {
|
|
@@ -8235,7 +10515,7 @@ function patchMcpServer(entry) {
|
|
|
8235
10515
|
return action;
|
|
8236
10516
|
}
|
|
8237
10517
|
function removeMcpServer() {
|
|
8238
|
-
if (!
|
|
10518
|
+
if (!existsSync13(CLAUDE_CONFIG))
|
|
8239
10519
|
return false;
|
|
8240
10520
|
backupClaudeConfig();
|
|
8241
10521
|
const cfg = readClaudeConfig();
|
|
@@ -8248,11 +10528,11 @@ function removeMcpServer() {
|
|
|
8248
10528
|
return true;
|
|
8249
10529
|
}
|
|
8250
10530
|
function flushClaudeConfig(obj) {
|
|
8251
|
-
|
|
8252
|
-
|
|
10531
|
+
mkdirSync8(dirname6(CLAUDE_CONFIG), { recursive: true });
|
|
10532
|
+
writeFileSync10(CLAUDE_CONFIG, JSON.stringify(obj, null, 2) + `
|
|
8253
10533
|
`, "utf-8");
|
|
8254
10534
|
try {
|
|
8255
|
-
|
|
10535
|
+
chmodSync4(CLAUDE_CONFIG, 384);
|
|
8256
10536
|
} catch {}
|
|
8257
10537
|
}
|
|
8258
10538
|
function bunAvailable() {
|
|
@@ -8266,13 +10546,13 @@ function resolveEntry() {
|
|
|
8266
10546
|
const here = fileURLToPath(import.meta.url);
|
|
8267
10547
|
if (isBundledFile(here))
|
|
8268
10548
|
return here;
|
|
8269
|
-
return resolve(
|
|
10549
|
+
return resolve(dirname6(here), "..", "index.ts");
|
|
8270
10550
|
}
|
|
8271
10551
|
function resolveBundledSkillsDir() {
|
|
8272
10552
|
const here = fileURLToPath(import.meta.url);
|
|
8273
|
-
const pkgRoot = resolve(
|
|
8274
|
-
const skillsDir =
|
|
8275
|
-
if (
|
|
10553
|
+
const pkgRoot = resolve(dirname6(here), "..", "..");
|
|
10554
|
+
const skillsDir = join9(pkgRoot, "skills");
|
|
10555
|
+
if (existsSync13(skillsDir))
|
|
8276
10556
|
return skillsDir;
|
|
8277
10557
|
return null;
|
|
8278
10558
|
}
|
|
@@ -8285,13 +10565,13 @@ function installSkills() {
|
|
|
8285
10565
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
8286
10566
|
if (!entry.isDirectory())
|
|
8287
10567
|
continue;
|
|
8288
|
-
const srcDir =
|
|
8289
|
-
const dstDir =
|
|
8290
|
-
|
|
10568
|
+
const srcDir = join9(src, entry.name);
|
|
10569
|
+
const dstDir = join9(CLAUDE_SKILLS_ROOT, entry.name);
|
|
10570
|
+
mkdirSync8(dstDir, { recursive: true });
|
|
8291
10571
|
for (const file of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
8292
10572
|
if (!file.isFile())
|
|
8293
10573
|
continue;
|
|
8294
|
-
copyFileSync(
|
|
10574
|
+
copyFileSync(join9(srcDir, file.name), join9(dstDir, file.name));
|
|
8295
10575
|
}
|
|
8296
10576
|
installed.push(entry.name);
|
|
8297
10577
|
}
|
|
@@ -8306,8 +10586,8 @@ function uninstallSkills() {
|
|
|
8306
10586
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
8307
10587
|
if (!entry.isDirectory())
|
|
8308
10588
|
continue;
|
|
8309
|
-
const dstDir =
|
|
8310
|
-
if (
|
|
10589
|
+
const dstDir = join9(CLAUDE_SKILLS_ROOT, entry.name);
|
|
10590
|
+
if (existsSync13(dstDir)) {
|
|
8311
10591
|
try {
|
|
8312
10592
|
fs.rmSync(dstDir, { recursive: true, force: true });
|
|
8313
10593
|
removed.push(entry.name);
|
|
@@ -8332,9 +10612,9 @@ function entriesEqual(a, b) {
|
|
|
8332
10612
|
return a.command === b.command && JSON.stringify(a.args ?? []) === JSON.stringify(b.args ?? []);
|
|
8333
10613
|
}
|
|
8334
10614
|
function readClaudeSettings() {
|
|
8335
|
-
if (!
|
|
10615
|
+
if (!existsSync13(CLAUDE_SETTINGS))
|
|
8336
10616
|
return {};
|
|
8337
|
-
const text =
|
|
10617
|
+
const text = readFileSync10(CLAUDE_SETTINGS, "utf-8").trim();
|
|
8338
10618
|
if (!text)
|
|
8339
10619
|
return {};
|
|
8340
10620
|
try {
|
|
@@ -8344,8 +10624,8 @@ function readClaudeSettings() {
|
|
|
8344
10624
|
}
|
|
8345
10625
|
}
|
|
8346
10626
|
function writeClaudeSettings(obj) {
|
|
8347
|
-
|
|
8348
|
-
|
|
10627
|
+
mkdirSync8(dirname6(CLAUDE_SETTINGS), { recursive: true });
|
|
10628
|
+
writeFileSync10(CLAUDE_SETTINGS, JSON.stringify(obj, null, 2) + `
|
|
8349
10629
|
`, "utf-8");
|
|
8350
10630
|
}
|
|
8351
10631
|
function installAllowedTools() {
|
|
@@ -8359,7 +10639,7 @@ function installAllowedTools() {
|
|
|
8359
10639
|
return { added: toAdd, unchanged: CLAUDEMESH_TOOLS.length - toAdd.length };
|
|
8360
10640
|
}
|
|
8361
10641
|
function uninstallAllowedTools() {
|
|
8362
|
-
if (!
|
|
10642
|
+
if (!existsSync13(CLAUDE_SETTINGS))
|
|
8363
10643
|
return 0;
|
|
8364
10644
|
const settings = readClaudeSettings();
|
|
8365
10645
|
const existing = settings.allowedTools ?? [];
|
|
@@ -8394,7 +10674,7 @@ function installHooks() {
|
|
|
8394
10674
|
return { added, unchanged };
|
|
8395
10675
|
}
|
|
8396
10676
|
function uninstallHooks() {
|
|
8397
|
-
if (!
|
|
10677
|
+
if (!existsSync13(CLAUDE_SETTINGS))
|
|
8398
10678
|
return 0;
|
|
8399
10679
|
const settings = readClaudeSettings();
|
|
8400
10680
|
const hooks = settings.hooks;
|
|
@@ -8443,7 +10723,7 @@ function runInstall(args = []) {
|
|
|
8443
10723
|
render.err("`bun` is not on PATH.", "Install Bun first: https://bun.com");
|
|
8444
10724
|
process.exit(1);
|
|
8445
10725
|
}
|
|
8446
|
-
if (!
|
|
10726
|
+
if (!existsSync13(entry)) {
|
|
8447
10727
|
render.err(`MCP entry not found at ${entry}`);
|
|
8448
10728
|
process.exit(1);
|
|
8449
10729
|
}
|
|
@@ -8494,7 +10774,7 @@ function runInstall(args = []) {
|
|
|
8494
10774
|
const installed = installSkills();
|
|
8495
10775
|
if (installed.length > 0) {
|
|
8496
10776
|
render.ok(`Claude skill${installed.length === 1 ? "" : "s"} installed`, installed.join(", "));
|
|
8497
|
-
render.info(dim(` ${
|
|
10777
|
+
render.info(dim(` ${join9(CLAUDE_SKILLS_ROOT, installed[0])}/SKILL.md`));
|
|
8498
10778
|
}
|
|
8499
10779
|
} catch (e) {
|
|
8500
10780
|
render.warn(`skill install failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -8582,9 +10862,9 @@ var init_install = __esm(() => {
|
|
|
8582
10862
|
init_facade();
|
|
8583
10863
|
init_render();
|
|
8584
10864
|
init_styles();
|
|
8585
|
-
CLAUDE_CONFIG =
|
|
8586
|
-
CLAUDE_SETTINGS =
|
|
8587
|
-
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");
|
|
8588
10868
|
CLAUDEMESH_TOOLS = [
|
|
8589
10869
|
"mcp__claudemesh__cancel_scheduled",
|
|
8590
10870
|
"mcp__claudemesh__check_messages",
|
|
@@ -8639,35 +10919,35 @@ var exports_uninstall = {};
|
|
|
8639
10919
|
__export(exports_uninstall, {
|
|
8640
10920
|
uninstall: () => uninstall
|
|
8641
10921
|
});
|
|
8642
|
-
import { readFileSync as
|
|
8643
|
-
import { join as
|
|
8644
|
-
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";
|
|
8645
10925
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
8646
10926
|
function bundledSkillsDir() {
|
|
8647
10927
|
const here = fileURLToPath2(import.meta.url);
|
|
8648
|
-
const pkgRoot =
|
|
8649
|
-
const skillsDir =
|
|
8650
|
-
return
|
|
10928
|
+
const pkgRoot = join10(dirname7(here), "..", "..");
|
|
10929
|
+
const skillsDir = join10(pkgRoot, "skills");
|
|
10930
|
+
return existsSync14(skillsDir) ? skillsDir : null;
|
|
8651
10931
|
}
|
|
8652
10932
|
async function uninstall() {
|
|
8653
10933
|
let removed = 0;
|
|
8654
|
-
if (
|
|
10934
|
+
if (existsSync14(PATHS.CLAUDE_JSON)) {
|
|
8655
10935
|
try {
|
|
8656
|
-
const raw =
|
|
10936
|
+
const raw = readFileSync11(PATHS.CLAUDE_JSON, "utf-8");
|
|
8657
10937
|
const config = JSON.parse(raw);
|
|
8658
10938
|
const servers = config.mcpServers;
|
|
8659
10939
|
if (servers && "claudemesh" in servers) {
|
|
8660
10940
|
delete servers.claudemesh;
|
|
8661
|
-
|
|
10941
|
+
writeFileSync11(PATHS.CLAUDE_JSON, JSON.stringify(config, null, 2) + `
|
|
8662
10942
|
`, "utf-8");
|
|
8663
10943
|
render.ok("removed MCP server", dim("~/.claude.json"));
|
|
8664
10944
|
removed++;
|
|
8665
10945
|
}
|
|
8666
10946
|
} catch {}
|
|
8667
10947
|
}
|
|
8668
|
-
if (
|
|
10948
|
+
if (existsSync14(PATHS.CLAUDE_SETTINGS)) {
|
|
8669
10949
|
try {
|
|
8670
|
-
const raw =
|
|
10950
|
+
const raw = readFileSync11(PATHS.CLAUDE_SETTINGS, "utf-8");
|
|
8671
10951
|
const config = JSON.parse(raw);
|
|
8672
10952
|
const hooks = config.hooks;
|
|
8673
10953
|
if (hooks) {
|
|
@@ -8688,7 +10968,7 @@ async function uninstall() {
|
|
|
8688
10968
|
}
|
|
8689
10969
|
}
|
|
8690
10970
|
if (removedHooks > 0) {
|
|
8691
|
-
|
|
10971
|
+
writeFileSync11(PATHS.CLAUDE_SETTINGS, JSON.stringify(config, null, 2) + `
|
|
8692
10972
|
`, "utf-8");
|
|
8693
10973
|
render.ok(`removed ${removedHooks} claudemesh hook${removedHooks === 1 ? "" : "s"}`, dim("settings.json"));
|
|
8694
10974
|
removed++;
|
|
@@ -8703,8 +10983,8 @@ async function uninstall() {
|
|
|
8703
10983
|
for (const entry of readdirSync2(src, { withFileTypes: true })) {
|
|
8704
10984
|
if (!entry.isDirectory())
|
|
8705
10985
|
continue;
|
|
8706
|
-
const dst =
|
|
8707
|
-
if (
|
|
10986
|
+
const dst = join10(CLAUDE_SKILLS_ROOT2, entry.name);
|
|
10987
|
+
if (existsSync14(dst)) {
|
|
8708
10988
|
try {
|
|
8709
10989
|
rmSync2(dst, { recursive: true, force: true });
|
|
8710
10990
|
removedSkills.push(entry.name);
|
|
@@ -8728,7 +11008,7 @@ var init_uninstall = __esm(() => {
|
|
|
8728
11008
|
init_render();
|
|
8729
11009
|
init_styles();
|
|
8730
11010
|
init_exit_codes();
|
|
8731
|
-
CLAUDE_SKILLS_ROOT2 =
|
|
11011
|
+
CLAUDE_SKILLS_ROOT2 = join10(homedir8(), ".claude", "skills");
|
|
8732
11012
|
});
|
|
8733
11013
|
|
|
8734
11014
|
// src/commands/doctor.ts
|
|
@@ -8736,9 +11016,9 @@ var exports_doctor = {};
|
|
|
8736
11016
|
__export(exports_doctor, {
|
|
8737
11017
|
runDoctor: () => runDoctor
|
|
8738
11018
|
});
|
|
8739
|
-
import { existsSync as
|
|
8740
|
-
import { homedir as
|
|
8741
|
-
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";
|
|
8742
11022
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
8743
11023
|
function checkNode() {
|
|
8744
11024
|
const major = Number(process.versions.node.split(".")[0]);
|
|
@@ -8762,8 +11042,8 @@ function checkClaudeOnPath() {
|
|
|
8762
11042
|
};
|
|
8763
11043
|
}
|
|
8764
11044
|
function checkMcpRegistered() {
|
|
8765
|
-
const claudeConfig =
|
|
8766
|
-
if (!
|
|
11045
|
+
const claudeConfig = join11(homedir9(), ".claude.json");
|
|
11046
|
+
if (!existsSync15(claudeConfig)) {
|
|
8767
11047
|
return {
|
|
8768
11048
|
name: "claudemesh MCP registered in ~/.claude.json",
|
|
8769
11049
|
pass: false,
|
|
@@ -8771,7 +11051,7 @@ function checkMcpRegistered() {
|
|
|
8771
11051
|
};
|
|
8772
11052
|
}
|
|
8773
11053
|
try {
|
|
8774
|
-
const cfg = JSON.parse(
|
|
11054
|
+
const cfg = JSON.parse(readFileSync12(claudeConfig, "utf-8"));
|
|
8775
11055
|
const registered = Boolean(cfg.mcpServers?.["claudemesh"]);
|
|
8776
11056
|
return {
|
|
8777
11057
|
name: "claudemesh MCP registered in ~/.claude.json",
|
|
@@ -8788,8 +11068,8 @@ function checkMcpRegistered() {
|
|
|
8788
11068
|
}
|
|
8789
11069
|
}
|
|
8790
11070
|
function checkHooksRegistered() {
|
|
8791
|
-
const settings =
|
|
8792
|
-
if (!
|
|
11071
|
+
const settings = join11(homedir9(), ".claude", "settings.json");
|
|
11072
|
+
if (!existsSync15(settings)) {
|
|
8793
11073
|
return {
|
|
8794
11074
|
name: "Status hooks registered in ~/.claude/settings.json",
|
|
8795
11075
|
pass: false,
|
|
@@ -8797,7 +11077,7 @@ function checkHooksRegistered() {
|
|
|
8797
11077
|
};
|
|
8798
11078
|
}
|
|
8799
11079
|
try {
|
|
8800
|
-
const raw =
|
|
11080
|
+
const raw = readFileSync12(settings, "utf-8");
|
|
8801
11081
|
const has = raw.includes("claudemesh hook ");
|
|
8802
11082
|
return {
|
|
8803
11083
|
name: "Status hooks registered in ~/.claude/settings.json",
|
|
@@ -8813,8 +11093,8 @@ function checkHooksRegistered() {
|
|
|
8813
11093
|
}
|
|
8814
11094
|
}
|
|
8815
11095
|
function checkConfigFile() {
|
|
8816
|
-
const
|
|
8817
|
-
if (!
|
|
11096
|
+
const path2 = getConfigPath();
|
|
11097
|
+
if (!existsSync15(path2)) {
|
|
8818
11098
|
return {
|
|
8819
11099
|
name: "~/.claudemesh/config.json exists and parses",
|
|
8820
11100
|
pass: true,
|
|
@@ -8823,14 +11103,14 @@ function checkConfigFile() {
|
|
|
8823
11103
|
}
|
|
8824
11104
|
try {
|
|
8825
11105
|
readConfig();
|
|
8826
|
-
const st = statSync2(
|
|
11106
|
+
const st = statSync2(path2);
|
|
8827
11107
|
const mode = (st.mode & 511).toString(8);
|
|
8828
11108
|
const secure = platform6() === "win32" || mode === "600";
|
|
8829
11109
|
return {
|
|
8830
11110
|
name: "~/.claudemesh/config.json parses + chmod 0600",
|
|
8831
11111
|
pass: secure,
|
|
8832
11112
|
detail: platform6() === "win32" ? "chmod skipped on Windows" : `0${mode}`,
|
|
8833
|
-
fix: secure ? undefined : `chmod 600 ${
|
|
11113
|
+
fix: secure ? undefined : `chmod 600 ${path2}`
|
|
8834
11114
|
};
|
|
8835
11115
|
} catch (e) {
|
|
8836
11116
|
return {
|
|
@@ -8886,8 +11166,8 @@ async function checkBrokerWs() {
|
|
|
8886
11166
|
const wsUrl = URLS.BROKER;
|
|
8887
11167
|
const start = Date.now();
|
|
8888
11168
|
try {
|
|
8889
|
-
const
|
|
8890
|
-
const ws = new
|
|
11169
|
+
const WebSocket3 = (await import("ws")).default;
|
|
11170
|
+
const ws = new WebSocket3(wsUrl);
|
|
8891
11171
|
const result = await new Promise((resolve2) => {
|
|
8892
11172
|
const timer = setTimeout(() => {
|
|
8893
11173
|
try {
|
|
@@ -8995,14 +11275,14 @@ var init_doctor = __esm(() => {
|
|
|
8995
11275
|
// src/commands/status.ts
|
|
8996
11276
|
var exports_status = {};
|
|
8997
11277
|
__export(exports_status, {
|
|
8998
|
-
runStatus: () =>
|
|
11278
|
+
runStatus: () => runStatus2
|
|
8999
11279
|
});
|
|
9000
|
-
import { statSync as statSync3, existsSync as
|
|
9001
|
-
import
|
|
11280
|
+
import { statSync as statSync3, existsSync as existsSync16 } from "node:fs";
|
|
11281
|
+
import WebSocket3 from "ws";
|
|
9002
11282
|
async function probeBroker(url, timeoutMs = 4000) {
|
|
9003
11283
|
return new Promise((resolve2) => {
|
|
9004
11284
|
const started = Date.now();
|
|
9005
|
-
const ws = new
|
|
11285
|
+
const ws = new WebSocket3(url);
|
|
9006
11286
|
const timer = setTimeout(() => {
|
|
9007
11287
|
try {
|
|
9008
11288
|
ws.terminate();
|
|
@@ -9023,11 +11303,11 @@ async function probeBroker(url, timeoutMs = 4000) {
|
|
|
9023
11303
|
});
|
|
9024
11304
|
});
|
|
9025
11305
|
}
|
|
9026
|
-
async function
|
|
11306
|
+
async function runStatus2() {
|
|
9027
11307
|
render.section(`status (v${VERSION})`);
|
|
9028
11308
|
const configPath = getConfigPath();
|
|
9029
11309
|
let configPermsNote = "missing";
|
|
9030
|
-
if (
|
|
11310
|
+
if (existsSync16(configPath)) {
|
|
9031
11311
|
const mode = (statSync3(configPath).mode & 511).toString(8).padStart(4, "0");
|
|
9032
11312
|
configPermsNote = mode === "0600" ? `${mode}` : `${mode} — expected 0600`;
|
|
9033
11313
|
}
|
|
@@ -9173,13 +11453,13 @@ var init_check_claude_binary = __esm(() => {
|
|
|
9173
11453
|
});
|
|
9174
11454
|
|
|
9175
11455
|
// src/services/health/check-mcp-registered.ts
|
|
9176
|
-
import { existsSync as
|
|
11456
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "node:fs";
|
|
9177
11457
|
function checkMcpRegistered2() {
|
|
9178
11458
|
try {
|
|
9179
|
-
if (!
|
|
11459
|
+
if (!existsSync17(PATHS.CLAUDE_JSON)) {
|
|
9180
11460
|
return { name: "mcp-registered", ok: false, message: "~/.claude.json not found" };
|
|
9181
11461
|
}
|
|
9182
|
-
const raw =
|
|
11462
|
+
const raw = readFileSync13(PATHS.CLAUDE_JSON, "utf-8");
|
|
9183
11463
|
const config = JSON.parse(raw);
|
|
9184
11464
|
if (config.mcpServers && "claudemesh" in config.mcpServers) {
|
|
9185
11465
|
return { name: "mcp-registered", ok: true, message: "MCP server registered" };
|
|
@@ -9194,13 +11474,13 @@ var init_check_mcp_registered = __esm(() => {
|
|
|
9194
11474
|
});
|
|
9195
11475
|
|
|
9196
11476
|
// src/services/health/check-hooks-registered.ts
|
|
9197
|
-
import { existsSync as
|
|
11477
|
+
import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
|
|
9198
11478
|
function checkHooksRegistered2() {
|
|
9199
11479
|
try {
|
|
9200
|
-
if (!
|
|
11480
|
+
if (!existsSync18(PATHS.CLAUDE_SETTINGS)) {
|
|
9201
11481
|
return { name: "hooks-registered", ok: false, message: "~/.claude/settings.json not found" };
|
|
9202
11482
|
}
|
|
9203
|
-
const raw =
|
|
11483
|
+
const raw = readFileSync14(PATHS.CLAUDE_SETTINGS, "utf-8");
|
|
9204
11484
|
const config = JSON.parse(raw);
|
|
9205
11485
|
if (config.hooks) {
|
|
9206
11486
|
return { name: "hooks-registered", ok: true, message: "Hooks configured" };
|
|
@@ -9215,10 +11495,10 @@ var init_check_hooks_registered = __esm(() => {
|
|
|
9215
11495
|
});
|
|
9216
11496
|
|
|
9217
11497
|
// src/services/health/check-config-perms.ts
|
|
9218
|
-
import { existsSync as
|
|
11498
|
+
import { existsSync as existsSync19, statSync as statSync4 } from "node:fs";
|
|
9219
11499
|
function checkConfigPerms() {
|
|
9220
11500
|
const configFile = PATHS.CONFIG_FILE;
|
|
9221
|
-
if (!
|
|
11501
|
+
if (!existsSync19(configFile)) {
|
|
9222
11502
|
return { name: "config-perms", ok: true, message: "No config file yet (first run)" };
|
|
9223
11503
|
}
|
|
9224
11504
|
try {
|
|
@@ -9236,13 +11516,13 @@ var init_check_config_perms = __esm(() => {
|
|
|
9236
11516
|
});
|
|
9237
11517
|
|
|
9238
11518
|
// src/services/health/check-keypairs-valid.ts
|
|
9239
|
-
import { existsSync as
|
|
11519
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15 } from "node:fs";
|
|
9240
11520
|
function checkKeypairsValid() {
|
|
9241
|
-
if (!
|
|
11521
|
+
if (!existsSync20(PATHS.CONFIG_FILE)) {
|
|
9242
11522
|
return { name: "keypairs-valid", ok: true, message: "No config (first run)" };
|
|
9243
11523
|
}
|
|
9244
11524
|
try {
|
|
9245
|
-
const raw =
|
|
11525
|
+
const raw = readFileSync15(PATHS.CONFIG_FILE, "utf-8");
|
|
9246
11526
|
const config = JSON.parse(raw);
|
|
9247
11527
|
const meshes = config.meshes ?? [];
|
|
9248
11528
|
if (meshes.length === 0) {
|
|
@@ -9643,12 +11923,12 @@ var exports_verify = {};
|
|
|
9643
11923
|
__export(exports_verify, {
|
|
9644
11924
|
runVerify: () => runVerify
|
|
9645
11925
|
});
|
|
9646
|
-
import { createHash } from "node:crypto";
|
|
11926
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
9647
11927
|
function safetyNumber(myPubkey, peerPubkey) {
|
|
9648
11928
|
const a = Buffer.from(myPubkey, "hex");
|
|
9649
11929
|
const b = Buffer.from(peerPubkey, "hex");
|
|
9650
11930
|
const [lo, hi] = Buffer.compare(a, b) < 0 ? [a, b] : [b, a];
|
|
9651
|
-
const hash =
|
|
11931
|
+
const hash = createHash3("sha256").update(lo).update(hi).digest();
|
|
9652
11932
|
const bits = [];
|
|
9653
11933
|
for (let i = 0;i < 15; i++) {
|
|
9654
11934
|
for (let b2 = 7;b2 >= 0; b2--) {
|
|
@@ -9722,19 +12002,19 @@ var exports_url_handler = {};
|
|
|
9722
12002
|
__export(exports_url_handler, {
|
|
9723
12003
|
runUrlHandler: () => runUrlHandler
|
|
9724
12004
|
});
|
|
9725
|
-
import { platform as platform7, homedir as
|
|
9726
|
-
import { existsSync as
|
|
9727
|
-
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";
|
|
9728
12008
|
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
9729
12009
|
function resolveClaudemeshBin() {
|
|
9730
12010
|
return process.argv[1] ?? "claudemesh";
|
|
9731
12011
|
}
|
|
9732
|
-
function
|
|
12012
|
+
function installDarwin2() {
|
|
9733
12013
|
const binPath = resolveClaudemeshBin();
|
|
9734
|
-
const appDir =
|
|
9735
|
-
const contents =
|
|
9736
|
-
const macOS =
|
|
9737
|
-
|
|
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 });
|
|
9738
12018
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
9739
12019
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
9740
12020
|
<plist version="1.0">
|
|
@@ -9757,7 +12037,7 @@ function installDarwin() {
|
|
|
9757
12037
|
</array>
|
|
9758
12038
|
</dict>
|
|
9759
12039
|
</plist>`;
|
|
9760
|
-
|
|
12040
|
+
writeFileSync12(join12(contents, "Info.plist"), plist);
|
|
9761
12041
|
const shim = `#!/bin/sh
|
|
9762
12042
|
URL="$1"
|
|
9763
12043
|
CODE=\${URL#claudemesh://}
|
|
@@ -9771,9 +12051,9 @@ tell application "Terminal"
|
|
|
9771
12051
|
end tell
|
|
9772
12052
|
EOF
|
|
9773
12053
|
`;
|
|
9774
|
-
const shimPath =
|
|
9775
|
-
|
|
9776
|
-
|
|
12054
|
+
const shimPath = join12(macOS, "open-url");
|
|
12055
|
+
writeFileSync12(shimPath, shim);
|
|
12056
|
+
chmodSync5(shimPath, 493);
|
|
9777
12057
|
const lsreg = spawnSync5("/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", ["-f", appDir], { encoding: "utf-8" });
|
|
9778
12058
|
if (lsreg.status !== 0) {
|
|
9779
12059
|
render.warn("lsregister returned non-zero", "scheme may not activate until Finder rescans.");
|
|
@@ -9781,10 +12061,10 @@ EOF
|
|
|
9781
12061
|
render.ok("registered claudemesh:// scheme on macOS", dim(appDir));
|
|
9782
12062
|
return EXIT.SUCCESS;
|
|
9783
12063
|
}
|
|
9784
|
-
function
|
|
12064
|
+
function installLinux2() {
|
|
9785
12065
|
const binPath = resolveClaudemeshBin();
|
|
9786
|
-
const appsDir =
|
|
9787
|
-
|
|
12066
|
+
const appsDir = join12(homedir10(), ".local", "share", "applications");
|
|
12067
|
+
mkdirSync9(appsDir, { recursive: true });
|
|
9788
12068
|
const desktop = `[Desktop Entry]
|
|
9789
12069
|
Type=Application
|
|
9790
12070
|
Name=Claudemesh
|
|
@@ -9795,8 +12075,8 @@ Terminal=true
|
|
|
9795
12075
|
MimeType=x-scheme-handler/claudemesh;
|
|
9796
12076
|
NoDisplay=true
|
|
9797
12077
|
`;
|
|
9798
|
-
const desktopPath =
|
|
9799
|
-
|
|
12078
|
+
const desktopPath = join12(appsDir, "claudemesh.desktop");
|
|
12079
|
+
writeFileSync12(desktopPath, desktop);
|
|
9800
12080
|
const xdg1 = spawnSync5("xdg-mime", ["default", "claudemesh.desktop", "x-scheme-handler/claudemesh"], { encoding: "utf-8" });
|
|
9801
12081
|
if (xdg1.status !== 0) {
|
|
9802
12082
|
render.warn("xdg-mime not available — skipped mime default registration");
|
|
@@ -9818,8 +12098,8 @@ function installWindows() {
|
|
|
9818
12098
|
`[HKEY_CURRENT_USER\\Software\\Classes\\claudemesh\\shell\\open\\command]`,
|
|
9819
12099
|
`@="\\"${binPath.replace(/\\/g, "\\\\")}\\" \\"%1\\""`
|
|
9820
12100
|
];
|
|
9821
|
-
const regPath =
|
|
9822
|
-
|
|
12101
|
+
const regPath = join12(homedir10(), "claudemesh-handler.reg");
|
|
12102
|
+
writeFileSync12(regPath, lines.join(`\r
|
|
9823
12103
|
`));
|
|
9824
12104
|
const res = spawnSync5("reg.exe", ["import", regPath], { encoding: "utf-8" });
|
|
9825
12105
|
if (res.status !== 0) {
|
|
@@ -9830,15 +12110,15 @@ function installWindows() {
|
|
|
9830
12110
|
return EXIT.SUCCESS;
|
|
9831
12111
|
}
|
|
9832
12112
|
function uninstallDarwin() {
|
|
9833
|
-
const appDir =
|
|
9834
|
-
if (
|
|
12113
|
+
const appDir = join12(homedir10(), "Library", "Application Support", "claudemesh", "ClaudemeshHandler.app");
|
|
12114
|
+
if (existsSync21(appDir))
|
|
9835
12115
|
rmSync3(appDir, { recursive: true, force: true });
|
|
9836
12116
|
render.ok("removed claudemesh:// handler on macOS");
|
|
9837
12117
|
return EXIT.SUCCESS;
|
|
9838
12118
|
}
|
|
9839
12119
|
function uninstallLinux() {
|
|
9840
|
-
const desktopPath =
|
|
9841
|
-
if (
|
|
12120
|
+
const desktopPath = join12(homedir10(), ".local", "share", "applications", "claudemesh.desktop");
|
|
12121
|
+
if (existsSync21(desktopPath))
|
|
9842
12122
|
rmSync3(desktopPath, { force: true });
|
|
9843
12123
|
render.ok("removed claudemesh:// handler on Linux");
|
|
9844
12124
|
return EXIT.SUCCESS;
|
|
@@ -9853,9 +12133,9 @@ async function runUrlHandler(action) {
|
|
|
9853
12133
|
const p = platform7();
|
|
9854
12134
|
if (act === "install") {
|
|
9855
12135
|
if (p === "darwin")
|
|
9856
|
-
return
|
|
12136
|
+
return installDarwin2();
|
|
9857
12137
|
if (p === "linux")
|
|
9858
|
-
return
|
|
12138
|
+
return installLinux2();
|
|
9859
12139
|
if (p === "win32")
|
|
9860
12140
|
return installWindows();
|
|
9861
12141
|
} else if (act === "uninstall" || act === "remove") {
|
|
@@ -9883,9 +12163,9 @@ var exports_status_line = {};
|
|
|
9883
12163
|
__export(exports_status_line, {
|
|
9884
12164
|
runStatusLine: () => runStatusLine
|
|
9885
12165
|
});
|
|
9886
|
-
import { existsSync as
|
|
9887
|
-
import { join as
|
|
9888
|
-
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";
|
|
9889
12169
|
async function runStatusLine() {
|
|
9890
12170
|
try {
|
|
9891
12171
|
const config = readConfig();
|
|
@@ -9893,11 +12173,11 @@ async function runStatusLine() {
|
|
|
9893
12173
|
process.stdout.write("◇ claudemesh (not joined)");
|
|
9894
12174
|
return EXIT.SUCCESS;
|
|
9895
12175
|
}
|
|
9896
|
-
const cachePath =
|
|
12176
|
+
const cachePath = join13(homedir11(), ".claudemesh", "peer-cache.json");
|
|
9897
12177
|
let cache = {};
|
|
9898
|
-
if (
|
|
12178
|
+
if (existsSync22(cachePath)) {
|
|
9899
12179
|
try {
|
|
9900
|
-
cache = JSON.parse(
|
|
12180
|
+
cache = JSON.parse(readFileSync16(cachePath, "utf-8"));
|
|
9901
12181
|
} catch {}
|
|
9902
12182
|
}
|
|
9903
12183
|
const pick = config.meshes[0];
|
|
@@ -9928,7 +12208,7 @@ __export(exports_backup, {
|
|
|
9928
12208
|
runRestore: () => runRestore,
|
|
9929
12209
|
runBackup: () => runBackup
|
|
9930
12210
|
});
|
|
9931
|
-
import { readFileSync as
|
|
12211
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync13, existsSync as existsSync23 } from "node:fs";
|
|
9932
12212
|
import { createInterface as createInterface11 } from "node:readline";
|
|
9933
12213
|
function readHidden(prompt5) {
|
|
9934
12214
|
return new Promise((resolve2) => {
|
|
@@ -9970,11 +12250,11 @@ async function deriveKey(pass, salt, s) {
|
|
|
9970
12250
|
}
|
|
9971
12251
|
async function runBackup(outPath) {
|
|
9972
12252
|
const configPath = getConfigPath();
|
|
9973
|
-
if (!
|
|
12253
|
+
if (!existsSync23(configPath)) {
|
|
9974
12254
|
console.error(" No config found — nothing to back up. Join a mesh first.");
|
|
9975
12255
|
return EXIT.NOT_FOUND;
|
|
9976
12256
|
}
|
|
9977
|
-
const plaintext =
|
|
12257
|
+
const plaintext = readFileSync17(configPath);
|
|
9978
12258
|
const pass = await readHidden(" Passphrase (min 12 chars): ");
|
|
9979
12259
|
if (pass.length < 12) {
|
|
9980
12260
|
console.error(" ✗ Passphrase too short.");
|
|
@@ -9992,7 +12272,7 @@ async function runBackup(outPath) {
|
|
|
9992
12272
|
const ciphertext = Buffer.from(s.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, null, null, nonce, key));
|
|
9993
12273
|
const blob = Buffer.concat([MAGIC, salt, nonce, ciphertext]);
|
|
9994
12274
|
const file = outPath ?? `claudemesh-backup-${new Date().toISOString().replace(/[:.]/g, "-")}.cmb`;
|
|
9995
|
-
|
|
12275
|
+
writeFileSync13(file, blob, { mode: 384 });
|
|
9996
12276
|
console.log(`
|
|
9997
12277
|
✓ Backup saved: ${file}`);
|
|
9998
12278
|
console.log(` Size: ${blob.length} bytes. Guard the passphrase — there is no recovery.
|
|
@@ -10004,11 +12284,11 @@ async function runRestore(inPath) {
|
|
|
10004
12284
|
console.error(" Usage: claudemesh restore <backup-file>");
|
|
10005
12285
|
return EXIT.INVALID_ARGS;
|
|
10006
12286
|
}
|
|
10007
|
-
if (!
|
|
12287
|
+
if (!existsSync23(inPath)) {
|
|
10008
12288
|
console.error(` ✗ File not found: ${inPath}`);
|
|
10009
12289
|
return EXIT.NOT_FOUND;
|
|
10010
12290
|
}
|
|
10011
|
-
const blob =
|
|
12291
|
+
const blob = readFileSync17(inPath);
|
|
10012
12292
|
if (blob.length < 4 + 16 + 24 + 17 || !blob.subarray(0, 4).equals(MAGIC)) {
|
|
10013
12293
|
console.error(" ✗ Not a claudemesh backup file (bad magic).");
|
|
10014
12294
|
return EXIT.INVALID_ARGS;
|
|
@@ -10027,12 +12307,12 @@ async function runRestore(inPath) {
|
|
|
10027
12307
|
return EXIT.INTERNAL_ERROR;
|
|
10028
12308
|
}
|
|
10029
12309
|
const configPath = getConfigPath();
|
|
10030
|
-
if (
|
|
12310
|
+
if (existsSync23(configPath)) {
|
|
10031
12311
|
const backupOld = `${configPath}.before-restore.${Date.now()}`;
|
|
10032
|
-
|
|
12312
|
+
writeFileSync13(backupOld, readFileSync17(configPath), { mode: 384 });
|
|
10033
12313
|
console.log(` ↻ Existing config saved to ${backupOld}`);
|
|
10034
12314
|
}
|
|
10035
|
-
|
|
12315
|
+
writeFileSync13(configPath, Buffer.from(plaintext), { mode: 384 });
|
|
10036
12316
|
console.log(`
|
|
10037
12317
|
✓ Config restored to ${configPath}`);
|
|
10038
12318
|
console.log(" Run `claudemesh list` to verify your meshes.\n");
|
|
@@ -10052,8 +12332,8 @@ __export(exports_upgrade, {
|
|
|
10052
12332
|
runUpgrade: () => runUpgrade
|
|
10053
12333
|
});
|
|
10054
12334
|
import { spawnSync as spawnSync6 } from "node:child_process";
|
|
10055
|
-
import { existsSync as
|
|
10056
|
-
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";
|
|
10057
12337
|
async function latestVersion() {
|
|
10058
12338
|
try {
|
|
10059
12339
|
const res = await fetch(URLS.NPM_REGISTRY, { signal: AbortSignal.timeout(8000) });
|
|
@@ -10066,15 +12346,15 @@ async function latestVersion() {
|
|
|
10066
12346
|
}
|
|
10067
12347
|
}
|
|
10068
12348
|
function findNpm() {
|
|
10069
|
-
const portable =
|
|
10070
|
-
if (
|
|
10071
|
-
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") };
|
|
10072
12352
|
}
|
|
10073
12353
|
let cur = resolve2(process.argv[1] ?? ".");
|
|
10074
12354
|
for (let i = 0;i < 6; i++) {
|
|
10075
|
-
cur =
|
|
10076
|
-
const candidate =
|
|
10077
|
-
if (
|
|
12355
|
+
cur = dirname8(cur);
|
|
12356
|
+
const candidate = join14(cur, "bin", "npm");
|
|
12357
|
+
if (existsSync24(candidate))
|
|
10078
12358
|
return { npm: candidate };
|
|
10079
12359
|
}
|
|
10080
12360
|
return { npm: "npm" };
|
|
@@ -10136,9 +12416,9 @@ __export(exports_grants, {
|
|
|
10136
12416
|
runBlock: () => runBlock,
|
|
10137
12417
|
isAllowed: () => isAllowed
|
|
10138
12418
|
});
|
|
10139
|
-
import { existsSync as
|
|
10140
|
-
import { homedir as
|
|
10141
|
-
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";
|
|
10142
12422
|
async function syncToBroker(meshSlug, grants) {
|
|
10143
12423
|
const auth = getStoredToken();
|
|
10144
12424
|
if (!auth)
|
|
@@ -10156,19 +12436,19 @@ async function syncToBroker(meshSlug, grants) {
|
|
|
10156
12436
|
}
|
|
10157
12437
|
}
|
|
10158
12438
|
function readGrants() {
|
|
10159
|
-
if (!
|
|
12439
|
+
if (!existsSync25(GRANT_FILE))
|
|
10160
12440
|
return {};
|
|
10161
12441
|
try {
|
|
10162
|
-
return JSON.parse(
|
|
12442
|
+
return JSON.parse(readFileSync18(GRANT_FILE, "utf-8"));
|
|
10163
12443
|
} catch {
|
|
10164
12444
|
return {};
|
|
10165
12445
|
}
|
|
10166
12446
|
}
|
|
10167
12447
|
function writeGrants(g) {
|
|
10168
|
-
const dir =
|
|
10169
|
-
if (!
|
|
10170
|
-
|
|
10171
|
-
|
|
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 });
|
|
10172
12452
|
}
|
|
10173
12453
|
function resolveCaps(input) {
|
|
10174
12454
|
if (input.includes("all"))
|
|
@@ -10324,7 +12604,7 @@ var init_grants = __esm(() => {
|
|
|
10324
12604
|
BROKER_HTTP7 = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
|
|
10325
12605
|
ALL_CAPS = ["read", "dm", "broadcast", "state-read", "state-write", "file-read"];
|
|
10326
12606
|
DEFAULT_CAPS = ["read", "dm", "broadcast", "state-read"];
|
|
10327
|
-
GRANT_FILE =
|
|
12607
|
+
GRANT_FILE = join15(homedir12(), ".claudemesh", "grants.json");
|
|
10328
12608
|
});
|
|
10329
12609
|
|
|
10330
12610
|
// src/commands/profile.ts
|
|
@@ -11803,8 +14083,8 @@ __export(exports_file, {
|
|
|
11803
14083
|
runFileGet: () => runFileGet
|
|
11804
14084
|
});
|
|
11805
14085
|
import { hostname as osHostname } from "node:os";
|
|
11806
|
-
import { resolve as resolvePath, basename, dirname as
|
|
11807
|
-
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";
|
|
11808
14088
|
function emitJson2(data) {
|
|
11809
14089
|
console.log(JSON.stringify(data, null, 2));
|
|
11810
14090
|
}
|
|
@@ -11821,7 +14101,7 @@ async function runFileShare(filePath, opts) {
|
|
|
11821
14101
|
return EXIT.INVALID_ARGS;
|
|
11822
14102
|
}
|
|
11823
14103
|
const absPath = resolvePath(filePath);
|
|
11824
|
-
if (!
|
|
14104
|
+
if (!existsSync26(absPath)) {
|
|
11825
14105
|
render.err(`File not found: ${absPath}`);
|
|
11826
14106
|
return EXIT.INVALID_ARGS;
|
|
11827
14107
|
}
|
|
@@ -11900,8 +14180,8 @@ async function runFileGet(fileId, opts) {
|
|
|
11900
14180
|
}
|
|
11901
14181
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
11902
14182
|
const outPath = opts.out ? resolvePath(opts.out) : resolvePath(process.cwd(), meta.name);
|
|
11903
|
-
|
|
11904
|
-
|
|
14183
|
+
mkdirSync11(dirname9(outPath), { recursive: true });
|
|
14184
|
+
writeFileSync15(outPath, buf);
|
|
11905
14185
|
if (opts.json) {
|
|
11906
14186
|
emitJson2({ fileId, name: meta.name, savedTo: outPath, sizeBytes: buf.length });
|
|
11907
14187
|
} else {
|
|
@@ -11996,7 +14276,7 @@ var require_client = __commonJS((exports) => {
|
|
|
11996
14276
|
var ws_1 = __importDefault(__require("ws"));
|
|
11997
14277
|
var crypto_js_1 = require_crypto();
|
|
11998
14278
|
var MAX_QUEUED2 = 100;
|
|
11999
|
-
var
|
|
14279
|
+
var HELLO_ACK_TIMEOUT_MS3 = 5000;
|
|
12000
14280
|
var BACKOFF_CAPS2 = [1000, 2000, 4000, 8000, 16000, 30000];
|
|
12001
14281
|
|
|
12002
14282
|
class MeshClient extends node_events_1.EventEmitter {
|
|
@@ -12061,7 +14341,7 @@ var require_client = __commonJS((exports) => {
|
|
|
12061
14341
|
this.debug("hello_ack timeout");
|
|
12062
14342
|
ws.close();
|
|
12063
14343
|
reject(new Error("hello_ack timeout"));
|
|
12064
|
-
},
|
|
14344
|
+
}, HELLO_ACK_TIMEOUT_MS3);
|
|
12065
14345
|
};
|
|
12066
14346
|
const onMessage = (raw) => {
|
|
12067
14347
|
let msg;
|
|
@@ -12511,7 +14791,7 @@ __export(exports_bridge, {
|
|
|
12511
14791
|
runBridge: () => runBridge,
|
|
12512
14792
|
bridgeConfigTemplate: () => bridgeConfigTemplate
|
|
12513
14793
|
});
|
|
12514
|
-
import { readFileSync as
|
|
14794
|
+
import { readFileSync as readFileSync19, existsSync as existsSync27 } from "node:fs";
|
|
12515
14795
|
function parseConfig(text) {
|
|
12516
14796
|
const trimmed = text.trim();
|
|
12517
14797
|
if (trimmed.startsWith("{"))
|
|
@@ -12555,13 +14835,13 @@ async function runBridge(configPath) {
|
|
|
12555
14835
|
render.err("Usage: claudemesh bridge run <config.yaml>");
|
|
12556
14836
|
return EXIT.INVALID_ARGS;
|
|
12557
14837
|
}
|
|
12558
|
-
if (!
|
|
14838
|
+
if (!existsSync27(configPath)) {
|
|
12559
14839
|
render.err(`config file not found: ${configPath}`);
|
|
12560
14840
|
return EXIT.NOT_FOUND;
|
|
12561
14841
|
}
|
|
12562
14842
|
let cfg;
|
|
12563
14843
|
try {
|
|
12564
|
-
cfg = parseConfig(
|
|
14844
|
+
cfg = parseConfig(readFileSync19(configPath, "utf-8"));
|
|
12565
14845
|
} catch (e) {
|
|
12566
14846
|
render.err(`failed to parse ${configPath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
12567
14847
|
return EXIT.INVALID_ARGS;
|
|
@@ -12965,9 +15245,9 @@ function cacheKey(apiKeySecret, topicName) {
|
|
|
12965
15245
|
async function getTopicKey(args) {
|
|
12966
15246
|
const cacheId = cacheKey(args.apiKeySecret, args.topicName);
|
|
12967
15247
|
if (!args.fresh) {
|
|
12968
|
-
const
|
|
12969
|
-
if (
|
|
12970
|
-
return { ok: true, topicKey:
|
|
15248
|
+
const cached2 = cache.get(cacheId);
|
|
15249
|
+
if (cached2)
|
|
15250
|
+
return { ok: true, topicKey: cached2.topicKey };
|
|
12971
15251
|
}
|
|
12972
15252
|
let sealed;
|
|
12973
15253
|
try {
|
|
@@ -13531,8 +15811,8 @@ var init_definitions = __esm(() => {
|
|
|
13531
15811
|
});
|
|
13532
15812
|
|
|
13533
15813
|
// src/services/bridge/server.ts
|
|
13534
|
-
import { createServer as
|
|
13535
|
-
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";
|
|
13536
15816
|
async function resolveTarget2(client, to) {
|
|
13537
15817
|
if (to.startsWith("@") || to === "*" || /^[0-9a-f]{64}$/i.test(to)) {
|
|
13538
15818
|
return { ok: true, spec: to };
|
|
@@ -13644,21 +15924,21 @@ function handleConnection(socket, client) {
|
|
|
13644
15924
|
socket.on("error", () => {});
|
|
13645
15925
|
}
|
|
13646
15926
|
function startBridgeServer(client) {
|
|
13647
|
-
const
|
|
15927
|
+
const path2 = socketPath(client.meshSlug);
|
|
13648
15928
|
const dir = socketDir();
|
|
13649
|
-
if (!
|
|
13650
|
-
|
|
15929
|
+
if (!existsSync28(dir)) {
|
|
15930
|
+
mkdirSync12(dir, { recursive: true, mode: 448 });
|
|
13651
15931
|
}
|
|
13652
|
-
if (
|
|
15932
|
+
if (existsSync28(path2)) {
|
|
13653
15933
|
try {
|
|
13654
|
-
|
|
15934
|
+
unlinkSync5(path2);
|
|
13655
15935
|
} catch {}
|
|
13656
15936
|
}
|
|
13657
|
-
const server =
|
|
15937
|
+
const server = createServer3((socket) => handleConnection(socket, client));
|
|
13658
15938
|
try {
|
|
13659
|
-
server.listen(
|
|
15939
|
+
server.listen(path2);
|
|
13660
15940
|
} catch (err) {
|
|
13661
|
-
process.stderr.write(`[claudemesh] bridge: failed to bind ${
|
|
15941
|
+
process.stderr.write(`[claudemesh] bridge: failed to bind ${path2}: ${String(err)}
|
|
13662
15942
|
`);
|
|
13663
15943
|
return null;
|
|
13664
15944
|
}
|
|
@@ -13667,11 +15947,11 @@ function startBridgeServer(client) {
|
|
|
13667
15947
|
`);
|
|
13668
15948
|
});
|
|
13669
15949
|
try {
|
|
13670
|
-
|
|
15950
|
+
chmodSync6(path2, 384);
|
|
13671
15951
|
} catch {}
|
|
13672
15952
|
let stopped = false;
|
|
13673
15953
|
return {
|
|
13674
|
-
path,
|
|
15954
|
+
path: path2,
|
|
13675
15955
|
stop() {
|
|
13676
15956
|
if (stopped)
|
|
13677
15957
|
return;
|
|
@@ -13680,12 +15960,12 @@ function startBridgeServer(client) {
|
|
|
13680
15960
|
server.close();
|
|
13681
15961
|
} catch {}
|
|
13682
15962
|
try {
|
|
13683
|
-
|
|
15963
|
+
unlinkSync5(path2);
|
|
13684
15964
|
} catch {}
|
|
13685
15965
|
}
|
|
13686
15966
|
};
|
|
13687
15967
|
}
|
|
13688
|
-
var
|
|
15968
|
+
var init_server2 = __esm(() => {
|
|
13689
15969
|
init_protocol();
|
|
13690
15970
|
});
|
|
13691
15971
|
|
|
@@ -14256,9 +16536,9 @@ async function startServiceProxy(serviceName) {
|
|
|
14256
16536
|
const fetched = await client.getServiceTools(serviceName);
|
|
14257
16537
|
tools = fetched;
|
|
14258
16538
|
} catch {
|
|
14259
|
-
const
|
|
14260
|
-
if (
|
|
14261
|
-
tools =
|
|
16539
|
+
const cached2 = client.serviceCatalog.find((s) => s.name === serviceName);
|
|
16540
|
+
if (cached2) {
|
|
16541
|
+
tools = cached2.tools;
|
|
14262
16542
|
}
|
|
14263
16543
|
}
|
|
14264
16544
|
if (tools.length === 0) {
|
|
@@ -14347,11 +16627,11 @@ async function startServiceProxy(serviceName) {
|
|
|
14347
16627
|
process.on("SIGINT", shutdown);
|
|
14348
16628
|
}
|
|
14349
16629
|
var peerNameCache, peerNameCacheAge = 0, CACHE_TTL_MS = 30000;
|
|
14350
|
-
var
|
|
16630
|
+
var init_server3 = __esm(() => {
|
|
14351
16631
|
init_definitions();
|
|
14352
16632
|
init_facade();
|
|
14353
16633
|
init_facade8();
|
|
14354
|
-
|
|
16634
|
+
init_server2();
|
|
14355
16635
|
peerNameCache = new Map;
|
|
14356
16636
|
});
|
|
14357
16637
|
|
|
@@ -14367,7 +16647,7 @@ async function runMcp() {
|
|
|
14367
16647
|
process.exit(0);
|
|
14368
16648
|
}
|
|
14369
16649
|
var init_mcp = __esm(() => {
|
|
14370
|
-
|
|
16650
|
+
init_server3();
|
|
14371
16651
|
});
|
|
14372
16652
|
|
|
14373
16653
|
// src/commands/hook.ts
|
|
@@ -15468,6 +17748,23 @@ async function main() {
|
|
|
15468
17748
|
process.exit(await whoami2({ json: !!flags.json }));
|
|
15469
17749
|
break;
|
|
15470
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
|
+
}
|
|
15471
17768
|
case "install": {
|
|
15472
17769
|
const { runInstall: runInstall2 } = await Promise.resolve().then(() => (init_install(), exports_install));
|
|
15473
17770
|
runInstall2(positionals);
|
|
@@ -15488,8 +17785,8 @@ async function main() {
|
|
|
15488
17785
|
const { runStatusSet: runStatusSet2 } = await Promise.resolve().then(() => (init_broker_actions(), exports_broker_actions));
|
|
15489
17786
|
process.exit(await runStatusSet2(positionals[1] ?? "", { mesh: flags.mesh, json: !!flags.json }));
|
|
15490
17787
|
} else {
|
|
15491
|
-
const { runStatus:
|
|
15492
|
-
await
|
|
17788
|
+
const { runStatus: runStatus3 } = await Promise.resolve().then(() => (init_status(), exports_status));
|
|
17789
|
+
await runStatus3();
|
|
15493
17790
|
}
|
|
15494
17791
|
break;
|
|
15495
17792
|
}
|
|
@@ -15641,8 +17938,8 @@ async function main() {
|
|
|
15641
17938
|
const { runStatusSet: runStatusSet2 } = await Promise.resolve().then(() => (init_broker_actions(), exports_broker_actions));
|
|
15642
17939
|
process.exit(await runStatusSet2(positionals[2] ?? "", { mesh: flags.mesh, json: !!flags.json }));
|
|
15643
17940
|
} else {
|
|
15644
|
-
const { runStatus:
|
|
15645
|
-
await
|
|
17941
|
+
const { runStatus: runStatus3 } = await Promise.resolve().then(() => (init_status(), exports_status));
|
|
17942
|
+
await runStatus3();
|
|
15646
17943
|
}
|
|
15647
17944
|
} else {
|
|
15648
17945
|
console.error("Usage: claudemesh profile [summary|visible|status]");
|
|
@@ -16129,4 +18426,4 @@ main().catch((err) => {
|
|
|
16129
18426
|
process.exit(EXIT.INTERNAL_ERROR);
|
|
16130
18427
|
});
|
|
16131
18428
|
|
|
16132
|
-
//# debugId=
|
|
18429
|
+
//# debugId=BA4EDB138BD2E1AE64756E2164756E21
|