claudemesh-cli 1.21.1 → 1.22.1

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.
@@ -88,7 +88,7 @@ __export(exports_urls, {
88
88
  VERSION: () => VERSION,
89
89
  URLS: () => URLS
90
90
  });
91
- var URLS, VERSION = "1.21.1", env;
91
+ var URLS, VERSION = "1.22.1", env;
92
92
  var init_urls = __esm(() => {
93
93
  URLS = {
94
94
  BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
@@ -6834,6 +6834,154 @@ var init_peers = __esm(() => {
6834
6834
  };
6835
6835
  });
6836
6836
 
6837
+ // src/daemon/paths.ts
6838
+ import { join as join6 } from "node:path";
6839
+ var DAEMON_PATHS, DAEMON_TCP_HOST = "127.0.0.1", DAEMON_TCP_DEFAULT_PORT = 47823;
6840
+ var init_paths2 = __esm(() => {
6841
+ init_paths();
6842
+ DAEMON_PATHS = {
6843
+ get DAEMON_DIR() {
6844
+ return join6(PATHS.CONFIG_DIR, "daemon");
6845
+ },
6846
+ get PID_FILE() {
6847
+ return join6(this.DAEMON_DIR, "daemon.pid");
6848
+ },
6849
+ get SOCK_FILE() {
6850
+ return join6(this.DAEMON_DIR, "daemon.sock");
6851
+ },
6852
+ get TOKEN_FILE() {
6853
+ return join6(this.DAEMON_DIR, "local-token");
6854
+ },
6855
+ get OUTBOX_DB() {
6856
+ return join6(this.DAEMON_DIR, "outbox.db");
6857
+ },
6858
+ get INBOX_DB() {
6859
+ return join6(this.DAEMON_DIR, "inbox.db");
6860
+ },
6861
+ get LOG_FILE() {
6862
+ return join6(this.DAEMON_DIR, "daemon.log");
6863
+ }
6864
+ };
6865
+ });
6866
+
6867
+ // src/daemon/local-token.ts
6868
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "node:fs";
6869
+ import { dirname as dirname3 } from "node:path";
6870
+ import { randomBytes as randomBytes4 } from "node:crypto";
6871
+ function readLocalToken() {
6872
+ try {
6873
+ return readFileSync5(DAEMON_PATHS.TOKEN_FILE, "utf8").trim();
6874
+ } catch {
6875
+ return null;
6876
+ }
6877
+ }
6878
+ function ensureLocalToken() {
6879
+ const existing = readLocalToken();
6880
+ if (existing)
6881
+ return existing;
6882
+ mkdirSync4(dirname3(DAEMON_PATHS.TOKEN_FILE), { recursive: true, mode: 448 });
6883
+ const tok = randomBytes4(32).toString("base64url");
6884
+ writeFileSync6(DAEMON_PATHS.TOKEN_FILE, tok + `
6885
+ `, { mode: 384 });
6886
+ return tok;
6887
+ }
6888
+ var init_local_token = __esm(() => {
6889
+ init_paths2();
6890
+ });
6891
+
6892
+ // src/daemon/ipc/client.ts
6893
+ import { request as httpRequest } from "node:http";
6894
+ async function ipc(opts) {
6895
+ const useTcp = !!opts.preferTcp;
6896
+ const headers = {
6897
+ accept: "application/json",
6898
+ host: "localhost"
6899
+ };
6900
+ let bodyBuf;
6901
+ if (opts.body !== undefined) {
6902
+ bodyBuf = Buffer.from(JSON.stringify(opts.body), "utf8");
6903
+ headers["content-type"] = "application/json";
6904
+ headers["content-length"] = String(bodyBuf.length);
6905
+ }
6906
+ if (useTcp) {
6907
+ const tok = readLocalToken();
6908
+ if (!tok)
6909
+ throw new IpcError(0, null, "daemon local token not found; is the daemon running?");
6910
+ headers.authorization = `Bearer ${tok}`;
6911
+ }
6912
+ return new Promise((resolve, reject) => {
6913
+ const req = httpRequest(useTcp ? { host: DAEMON_TCP_HOST, port: DAEMON_TCP_DEFAULT_PORT, path: opts.path, method: opts.method ?? "GET", headers } : { socketPath: DAEMON_PATHS.SOCK_FILE, path: opts.path, method: opts.method ?? "GET", headers }, (res) => {
6914
+ const chunks = [];
6915
+ res.on("data", (c) => chunks.push(c));
6916
+ res.on("end", () => {
6917
+ const raw = Buffer.concat(chunks).toString("utf8");
6918
+ let parsed = raw;
6919
+ try {
6920
+ parsed = raw.length > 0 ? JSON.parse(raw) : null;
6921
+ } catch {}
6922
+ resolve({ status: res.statusCode ?? 0, body: parsed });
6923
+ });
6924
+ });
6925
+ req.setTimeout(opts.timeoutMs ?? 5000, () => req.destroy(new Error("ipc_timeout")));
6926
+ req.on("error", (err) => reject(err));
6927
+ if (bodyBuf)
6928
+ req.write(bodyBuf);
6929
+ req.end();
6930
+ });
6931
+ }
6932
+ var IpcError;
6933
+ var init_client4 = __esm(() => {
6934
+ init_paths2();
6935
+ init_local_token();
6936
+ IpcError = class IpcError extends Error {
6937
+ status;
6938
+ payload;
6939
+ constructor(status, payload, msg) {
6940
+ super(msg);
6941
+ this.status = status;
6942
+ this.payload = payload;
6943
+ }
6944
+ };
6945
+ });
6946
+
6947
+ // src/services/bridge/daemon-route.ts
6948
+ import { existsSync as existsSync7 } from "node:fs";
6949
+ async function trySendViaDaemon(args) {
6950
+ if (!existsSync7(DAEMON_PATHS.SOCK_FILE))
6951
+ return null;
6952
+ try {
6953
+ const res = await ipc({
6954
+ method: "POST",
6955
+ path: "/v1/send",
6956
+ timeoutMs: 3000,
6957
+ body: {
6958
+ to: args.to,
6959
+ message: args.message,
6960
+ priority: args.priority,
6961
+ ...args.idempotencyKey ? { client_message_id: args.idempotencyKey } : {}
6962
+ }
6963
+ });
6964
+ if (res.status === 202 || res.status === 200) {
6965
+ return {
6966
+ ok: true,
6967
+ messageId: res.body.broker_message_id ?? res.body.client_message_id ?? "",
6968
+ duplicate: res.body.duplicate,
6969
+ status: res.body.status
6970
+ };
6971
+ }
6972
+ return { ok: false, error: res.body.error ?? `daemon http ${res.status}` };
6973
+ } catch (err) {
6974
+ const msg = String(err);
6975
+ if (/ENOENT|ECONNREFUSED|ipc_timeout/.test(msg))
6976
+ return null;
6977
+ return { ok: false, error: msg };
6978
+ }
6979
+ }
6980
+ var init_daemon_route = __esm(() => {
6981
+ init_client4();
6982
+ init_paths2();
6983
+ });
6984
+
6837
6985
  // src/commands/send.ts
6838
6986
  var exports_send = {};
6839
6987
  __export(exports_send, {
@@ -6855,6 +7003,23 @@ async function runSend(flags, to, message) {
6855
7003
  process.exit(1);
6856
7004
  }
6857
7005
  }
7006
+ {
7007
+ const dr = await trySendViaDaemon({ to, message, priority, expectedMesh: meshSlug ?? undefined });
7008
+ if (dr !== null) {
7009
+ if (dr.ok) {
7010
+ if (flags.json)
7011
+ console.log(JSON.stringify({ ok: true, messageId: dr.messageId, target: to, via: "daemon", duplicate: !!dr.duplicate }));
7012
+ else
7013
+ render.ok(`sent to ${to} (daemon)`, dr.messageId ? dim(dr.messageId.slice(0, 8)) : undefined);
7014
+ return;
7015
+ }
7016
+ if (flags.json)
7017
+ console.log(JSON.stringify({ ok: false, error: dr.error, via: "daemon" }));
7018
+ else
7019
+ render.err(`send failed (daemon): ${dr.error}`);
7020
+ process.exit(1);
7021
+ }
7022
+ }
6858
7023
  if (meshSlug) {
6859
7024
  const bridged = await tryBridge(meshSlug, "send", { to, message, priority });
6860
7025
  if (bridged !== null) {
@@ -6918,6 +7083,7 @@ var init_send = __esm(() => {
6918
7083
  init_connect();
6919
7084
  init_facade();
6920
7085
  init_client3();
7086
+ init_daemon_route();
6921
7087
  init_render();
6922
7088
  init_styles();
6923
7089
  });
@@ -8001,180 +8167,2331 @@ async function runRemind(flags, positional) {
8001
8167
  console.log(JSON.stringify(scheduled, null, 2));
8002
8168
  return;
8003
8169
  }
8004
- if (scheduled.length === 0) {
8005
- render.info(dim("No pending reminders."));
8006
- return;
8170
+ if (scheduled.length === 0) {
8171
+ render.info(dim("No pending reminders."));
8172
+ return;
8173
+ }
8174
+ render.section(`reminders (${scheduled.length})`);
8175
+ for (const m of scheduled) {
8176
+ const when = new Date(m.deliverAt).toLocaleString();
8177
+ const to = m.to === client.getSessionPubkey() ? dim("(self)") : m.to;
8178
+ process.stdout.write(` ${bold(m.id.slice(0, 8))} ${dim("→")} ${to} ${dim("at")} ${when}
8179
+ `);
8180
+ process.stdout.write(` ${dim(m.message.slice(0, 80))}
8181
+
8182
+ `);
8183
+ }
8184
+ });
8185
+ return;
8186
+ }
8187
+ if (action === "cancel") {
8188
+ const id = positional[1];
8189
+ if (!id) {
8190
+ render.err("Usage: claudemesh remind cancel <id>");
8191
+ process.exit(1);
8192
+ }
8193
+ await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
8194
+ const ok = await client.cancelScheduled(id);
8195
+ if (ok)
8196
+ render.ok(`cancelled ${bold(id.slice(0, 8))}`);
8197
+ else {
8198
+ render.err(`not found or already fired: ${id}`);
8199
+ process.exit(1);
8200
+ }
8201
+ });
8202
+ return;
8203
+ }
8204
+ const message = action ?? positional.join(" ");
8205
+ if (!message) {
8206
+ render.err("Usage: claudemesh remind <message> --in <duration>");
8207
+ render.info(dim(" claudemesh remind <message> --at <time>"));
8208
+ render.info(dim(' claudemesh remind <message> --cron "0 */2 * * *"'));
8209
+ render.info(dim(" claudemesh remind list"));
8210
+ render.info(dim(" claudemesh remind cancel <id>"));
8211
+ process.exit(1);
8212
+ }
8213
+ const isCron = !!flags.cron;
8214
+ const deliverAt = isCron ? 0 : parseDeliverAt(flags);
8215
+ if (!isCron && deliverAt === null) {
8216
+ render.err("Specify when", 'use --in <duration> (e.g. "2h", "30m"), --at <time> (e.g. "15:00"), or --cron <expression>');
8217
+ process.exit(1);
8218
+ }
8219
+ await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
8220
+ let targetSpec;
8221
+ if (flags.to && flags.to !== "self") {
8222
+ if (flags.to.startsWith("@") || flags.to === "*" || /^[0-9a-f]{64}$/i.test(flags.to)) {
8223
+ targetSpec = flags.to;
8224
+ } else {
8225
+ const peers = await client.listPeers();
8226
+ const match = peers.find((p) => p.displayName.toLowerCase() === flags.to.toLowerCase());
8227
+ if (!match) {
8228
+ render.err(`Peer "${flags.to}" not found`, `online: ${peers.map((p) => p.displayName).join(", ") || "(none)"}`);
8229
+ process.exit(1);
8230
+ }
8231
+ targetSpec = match.pubkey;
8232
+ }
8233
+ } else {
8234
+ targetSpec = client.getSessionPubkey() ?? "*";
8235
+ }
8236
+ const result = await client.scheduleMessage(targetSpec, message, deliverAt ?? 0, false, flags.cron);
8237
+ if (!result) {
8238
+ render.err("Broker did not acknowledge — check connection");
8239
+ process.exit(1);
8240
+ }
8241
+ if (flags.json) {
8242
+ console.log(JSON.stringify(result));
8243
+ return;
8244
+ }
8245
+ const toLabel = !flags.to || flags.to === "self" ? "yourself" : flags.to;
8246
+ if (isCron) {
8247
+ const nextFire = new Date(result.deliverAt).toLocaleString();
8248
+ render.ok(`recurring reminder set`, `${result.scheduledId.slice(0, 8)} · ${clay(message)} → ${toLabel} · cron ${flags.cron} · next ${nextFire}`);
8249
+ } else {
8250
+ const when = new Date(result.deliverAt).toLocaleString();
8251
+ render.ok(`reminder set`, `${result.scheduledId.slice(0, 8)} · ${clay(message)} → ${toLabel} at ${when}`);
8252
+ }
8253
+ });
8254
+ }
8255
+ var init_remind = __esm(() => {
8256
+ init_connect();
8257
+ init_render();
8258
+ init_styles();
8259
+ });
8260
+
8261
+ // src/commands/register.ts
8262
+ var exports_register = {};
8263
+ __export(exports_register, {
8264
+ register: () => register2
8265
+ });
8266
+ async function register2() {
8267
+ return login();
8268
+ }
8269
+ var init_register = __esm(() => {
8270
+ init_login();
8271
+ });
8272
+
8273
+ // src/commands/logout.ts
8274
+ var exports_logout = {};
8275
+ __export(exports_logout, {
8276
+ logout: () => logout2
8277
+ });
8278
+ async function logout2() {
8279
+ try {
8280
+ const { revoked } = await logout();
8281
+ if (revoked) {
8282
+ console.log(` ${green(icons.check)} Revoked session on claudemesh.com`);
8283
+ } else {
8284
+ console.log(` ${yellow(icons.warn)} Could not revoke session on claudemesh.com.`);
8285
+ console.log(` Revoke manually at https://claudemesh.com/dashboard/settings/sessions`);
8286
+ }
8287
+ console.log(` ${green(icons.check)} Removed local credentials.`);
8288
+ return EXIT.SUCCESS;
8289
+ } catch (err) {
8290
+ console.error(` ${icons.cross} Logout failed: ${err instanceof Error ? err.message : err}`);
8291
+ return EXIT.AUTH_FAILED;
8292
+ }
8293
+ }
8294
+ var init_logout = __esm(() => {
8295
+ init_facade6();
8296
+ init_styles();
8297
+ init_exit_codes();
8298
+ });
8299
+
8300
+ // src/commands/whoami.ts
8301
+ var exports_whoami = {};
8302
+ __export(exports_whoami, {
8303
+ whoami: () => whoami
8304
+ });
8305
+ async function whoami(opts) {
8306
+ const result = await whoAmI();
8307
+ if (opts.json) {
8308
+ console.log(JSON.stringify({ schema_version: "1.0", ...result }, null, 2));
8309
+ return result.signed_in || result.local ? EXIT.SUCCESS : EXIT.AUTH_FAILED;
8310
+ }
8311
+ if (!result.signed_in && !result.local) {
8312
+ render.err("Not signed in", "Run `claudemesh login` to sign in or `claudemesh <invite>` to join.");
8313
+ return EXIT.AUTH_FAILED;
8314
+ }
8315
+ render.section("whoami");
8316
+ if (result.signed_in) {
8317
+ render.kv([
8318
+ ["user", `${bold(result.user.display_name)} ${dim(`(${result.user.email})`)}`],
8319
+ ["token", `${result.token_source} ${dim("(~/.claudemesh/auth.json)")}`],
8320
+ ...result.meshes ? [["meshes", `${result.meshes.owned} owned · ${result.meshes.guest} guest`]] : []
8321
+ ]);
8322
+ } else {
8323
+ render.kv([
8324
+ ["web", dim("not signed in · run `claudemesh login` for account features")]
8325
+ ]);
8326
+ }
8327
+ if (result.local) {
8328
+ render.blank();
8329
+ render.kv([
8330
+ ["local", `${result.local.meshes.length} mesh${result.local.meshes.length === 1 ? "" : "es"} · ${dim(result.local.config_path)}`]
8331
+ ]);
8332
+ for (const m of result.local.meshes) {
8333
+ console.log(` ${clay("●")} ${bold(m.slug)} ${dim(`member ${m.member_id.slice(0, 8)}… pk ${m.pubkey_prefix}…`)}`);
8334
+ }
8335
+ }
8336
+ render.blank();
8337
+ return EXIT.SUCCESS;
8338
+ }
8339
+ var init_whoami = __esm(() => {
8340
+ init_facade6();
8341
+ init_render();
8342
+ init_styles();
8343
+ init_exit_codes();
8344
+ });
8345
+
8346
+ // src/daemon/lock.ts
8347
+ import { existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync6, unlinkSync as unlinkSync2, writeFileSync as writeFileSync7 } from "node:fs";
8348
+ import { dirname as dirname4 } from "node:path";
8349
+ function acquireSingletonLock() {
8350
+ mkdirSync5(dirname4(DAEMON_PATHS.PID_FILE), { recursive: true, mode: 448 });
8351
+ if (existsSync8(DAEMON_PATHS.PID_FILE)) {
8352
+ const raw = readFileSync6(DAEMON_PATHS.PID_FILE, "utf8").trim();
8353
+ const oldPid = Number.parseInt(raw, 10);
8354
+ if (Number.isFinite(oldPid) && oldPid > 0 && isProcessAlive(oldPid)) {
8355
+ return { result: "already-running", pid: oldPid };
8356
+ }
8357
+ try {
8358
+ unlinkSync2(DAEMON_PATHS.PID_FILE);
8359
+ } catch {}
8360
+ writeFileSync7(DAEMON_PATHS.PID_FILE, String(process.pid), { mode: 384 });
8361
+ return { result: "stale", pid: process.pid };
8362
+ }
8363
+ writeFileSync7(DAEMON_PATHS.PID_FILE, String(process.pid), { mode: 384 });
8364
+ return { result: "acquired", pid: process.pid };
8365
+ }
8366
+ function releaseSingletonLock() {
8367
+ try {
8368
+ const raw = readFileSync6(DAEMON_PATHS.PID_FILE, "utf8").trim();
8369
+ if (Number.parseInt(raw, 10) === process.pid)
8370
+ unlinkSync2(DAEMON_PATHS.PID_FILE);
8371
+ } catch {}
8372
+ }
8373
+ function readRunningPid() {
8374
+ try {
8375
+ const raw = readFileSync6(DAEMON_PATHS.PID_FILE, "utf8").trim();
8376
+ const pid = Number.parseInt(raw, 10);
8377
+ if (Number.isFinite(pid) && pid > 0 && isProcessAlive(pid))
8378
+ return pid;
8379
+ } catch {}
8380
+ return null;
8381
+ }
8382
+ function isProcessAlive(pid) {
8383
+ try {
8384
+ process.kill(pid, 0);
8385
+ return true;
8386
+ } catch (err) {
8387
+ return err.code === "EPERM";
8388
+ }
8389
+ }
8390
+ var init_lock = __esm(() => {
8391
+ init_paths2();
8392
+ });
8393
+
8394
+ // src/daemon/db/outbox.ts
8395
+ function migrateOutbox(db) {
8396
+ db.exec(`
8397
+ CREATE TABLE IF NOT EXISTS outbox (
8398
+ id TEXT PRIMARY KEY,
8399
+ client_message_id TEXT NOT NULL UNIQUE,
8400
+ request_fingerprint BLOB NOT NULL,
8401
+ payload BLOB NOT NULL,
8402
+ enqueued_at INTEGER NOT NULL,
8403
+ attempts INTEGER NOT NULL DEFAULT 0,
8404
+ next_attempt_at INTEGER NOT NULL,
8405
+ status TEXT NOT NULL CHECK(status IN
8406
+ ('pending','inflight','done','dead','aborted')),
8407
+ last_error TEXT,
8408
+ delivered_at INTEGER,
8409
+ broker_message_id TEXT,
8410
+ aborted_at INTEGER,
8411
+ aborted_by TEXT,
8412
+ superseded_by TEXT
8413
+ );
8414
+ CREATE INDEX IF NOT EXISTS outbox_pending
8415
+ ON outbox(status, next_attempt_at);
8416
+ CREATE INDEX IF NOT EXISTS outbox_aborted
8417
+ ON outbox(status, aborted_at) WHERE status = 'aborted';
8418
+ `);
8419
+ }
8420
+ function findByClientId(db, clientMessageId) {
8421
+ const row = db.prepare(`
8422
+ SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
8423
+ attempts, next_attempt_at, status, last_error, delivered_at,
8424
+ broker_message_id, aborted_at, aborted_by, superseded_by
8425
+ FROM outbox WHERE client_message_id = ?
8426
+ `).get(clientMessageId);
8427
+ return row ?? null;
8428
+ }
8429
+ function insertPending(db, input) {
8430
+ db.prepare(`
8431
+ INSERT INTO outbox (
8432
+ id, client_message_id, request_fingerprint, payload,
8433
+ enqueued_at, attempts, next_attempt_at, status
8434
+ ) VALUES (?, ?, ?, ?, ?, 0, ?, 'pending')
8435
+ `).run(input.id, input.client_message_id, input.request_fingerprint, input.payload, input.now, input.now);
8436
+ }
8437
+ function fingerprintsEqual(a, b) {
8438
+ if (a.length !== b.length)
8439
+ return false;
8440
+ let diff = 0;
8441
+ for (let i = 0;i < a.length; i++)
8442
+ diff |= a[i] ^ b[i];
8443
+ return diff === 0;
8444
+ }
8445
+ function listOutbox(db, p = {}) {
8446
+ const where = [];
8447
+ const args = [];
8448
+ if (p.status) {
8449
+ where.push("status = ?");
8450
+ args.push(p.status);
8451
+ }
8452
+ const sql = `
8453
+ SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
8454
+ attempts, next_attempt_at, status, last_error, delivered_at,
8455
+ broker_message_id, aborted_at, aborted_by, superseded_by
8456
+ FROM outbox
8457
+ ${where.length ? "WHERE " + where.join(" AND ") : ""}
8458
+ ORDER BY enqueued_at DESC
8459
+ LIMIT ?
8460
+ `;
8461
+ args.push(Math.min(Math.max(p.limit ?? 50, 1), 500));
8462
+ return db.prepare(sql).all(...args);
8463
+ }
8464
+ function findById(db, id) {
8465
+ return db.prepare(`
8466
+ SELECT id, client_message_id, request_fingerprint, payload, enqueued_at,
8467
+ attempts, next_attempt_at, status, last_error, delivered_at,
8468
+ broker_message_id, aborted_at, aborted_by, superseded_by
8469
+ FROM outbox WHERE id = ?
8470
+ `).get(id) ?? null;
8471
+ }
8472
+ function requeueDeadOrPending(db, args) {
8473
+ const existing = findById(db, args.id);
8474
+ if (!existing)
8475
+ return null;
8476
+ if (existing.status === "aborted" || existing.status === "done")
8477
+ return null;
8478
+ db.prepare(`
8479
+ UPDATE outbox
8480
+ SET status = 'aborted', aborted_at = ?, aborted_by = ?, superseded_by = ?
8481
+ WHERE id = ? AND status IN ('pending','inflight','dead')
8482
+ `).run(args.now, args.abortedBy, args.newRowId, args.id);
8483
+ db.prepare(`
8484
+ INSERT INTO outbox (
8485
+ id, client_message_id, request_fingerprint, payload,
8486
+ enqueued_at, attempts, next_attempt_at, status
8487
+ ) VALUES (?, ?, ?, ?, ?, 0, ?, 'pending')
8488
+ `).run(args.newRowId, args.newClientMessageId, existing.request_fingerprint, existing.payload, args.now, args.now);
8489
+ return {
8490
+ abortedRowId: existing.id,
8491
+ newRowId: args.newRowId,
8492
+ newClientMessageId: args.newClientMessageId
8493
+ };
8494
+ }
8495
+
8496
+ // src/daemon/db/sqlite.ts
8497
+ async function loadSqlite() {
8498
+ if (cached)
8499
+ return cached;
8500
+ try {
8501
+ const mod = await import("node:sqlite");
8502
+ cached = mod.DatabaseSync;
8503
+ return cached;
8504
+ } catch (nodeErr) {
8505
+ try {
8506
+ const bunMod = await import("bun:sqlite");
8507
+ cached = bunMod.Database;
8508
+ return cached;
8509
+ } catch {
8510
+ const msg = `claudemesh daemon requires Node.js 22.5+ for the embedded SQLite store (node:sqlite), or Bun (bun:sqlite) for dev. Current: ${process.version}. Original error: ${String(nodeErr)}`;
8511
+ throw new Error(msg);
8512
+ }
8513
+ }
8514
+ }
8515
+ async function openSqlite(path) {
8516
+ const Database = await loadSqlite();
8517
+ const db = new Database(path);
8518
+ db.exec(`
8519
+ PRAGMA journal_mode = WAL;
8520
+ PRAGMA synchronous = NORMAL;
8521
+ PRAGMA foreign_keys = ON;
8522
+ PRAGMA busy_timeout = 5000;
8523
+ `);
8524
+ return db;
8525
+ }
8526
+ function inImmediateTx(db, fn) {
8527
+ db.exec("BEGIN IMMEDIATE");
8528
+ try {
8529
+ const out = fn();
8530
+ db.exec("COMMIT");
8531
+ return out;
8532
+ } catch (err) {
8533
+ try {
8534
+ db.exec("ROLLBACK");
8535
+ } catch {}
8536
+ throw err;
8537
+ }
8538
+ }
8539
+ var cached = null;
8540
+
8541
+ // src/daemon/fingerprint.ts
8542
+ import { createHash } from "node:crypto";
8543
+ function computeRequestFingerprint(req) {
8544
+ const h = createHash("sha256");
8545
+ h.update(String(req.envelope_version), "utf8");
8546
+ h.update(NUL);
8547
+ h.update(req.destination_kind, "utf8");
8548
+ h.update(NUL);
8549
+ h.update(req.destination_ref, "utf8");
8550
+ h.update(NUL);
8551
+ h.update(req.reply_to_id ?? "", "utf8");
8552
+ h.update(NUL);
8553
+ h.update(req.priority, "utf8");
8554
+ h.update(NUL);
8555
+ h.update(req.meta ? canonicalJson(req.meta) : "", "utf8");
8556
+ h.update(NUL);
8557
+ h.update(createHash("sha256").update(req.body).digest());
8558
+ return h.digest();
8559
+ }
8560
+ function canonicalJson(value) {
8561
+ return JSON.stringify(sortKeys(value));
8562
+ }
8563
+ function sortKeys(value) {
8564
+ if (Array.isArray(value))
8565
+ return value.map(sortKeys);
8566
+ if (value !== null && typeof value === "object") {
8567
+ const obj = value;
8568
+ const out = {};
8569
+ for (const k of Object.keys(obj).sort())
8570
+ out[k] = sortKeys(obj[k]);
8571
+ return out;
8572
+ }
8573
+ return value;
8574
+ }
8575
+ function fingerprintHexPrefix(fp, bytes = 8) {
8576
+ let s = "";
8577
+ for (let i = 0;i < bytes && i < fp.length; i++) {
8578
+ s += fp[i].toString(16).padStart(2, "0");
8579
+ }
8580
+ return s;
8581
+ }
8582
+ var NUL;
8583
+ var init_fingerprint = __esm(() => {
8584
+ NUL = Buffer.from([0]);
8585
+ });
8586
+
8587
+ // src/daemon/ipc/handlers/send.ts
8588
+ import { randomUUID as randomUUID3 } from "node:crypto";
8589
+ function acceptSend(req, deps) {
8590
+ const now = (deps.now ?? Date.now)();
8591
+ const newId = deps.newId ?? randomUUID3;
8592
+ const clientId = req.client_message_id?.trim() || ulidLike(newId);
8593
+ const body = Buffer.from(req.message, "utf8");
8594
+ const fingerprint = computeRequestFingerprint({
8595
+ envelope_version: ENVELOPE_VERSION,
8596
+ destination_kind: req.destination_kind,
8597
+ destination_ref: req.destination_ref,
8598
+ reply_to_id: req.reply_to_id ?? null,
8599
+ priority: req.priority ?? "next",
8600
+ meta: req.meta ?? null,
8601
+ body
8602
+ });
8603
+ return inImmediateTx(deps.db, () => {
8604
+ const existing = findByClientId(deps.db, clientId);
8605
+ if (!existing) {
8606
+ insertPending(deps.db, {
8607
+ id: newId(),
8608
+ client_message_id: clientId,
8609
+ request_fingerprint: fingerprint,
8610
+ payload: body,
8611
+ now
8612
+ });
8613
+ return { kind: "accepted_pending", status: 202, client_message_id: clientId };
8614
+ }
8615
+ return decideForExistingRow(existing, fingerprint);
8616
+ });
8617
+ }
8618
+ function decideForExistingRow(row, fp) {
8619
+ const match = fingerprintsEqual(fp, row.request_fingerprint);
8620
+ const fpPrefix = fingerprintHexPrefix(fp);
8621
+ switch (row.status) {
8622
+ case "pending":
8623
+ return match ? { kind: "accepted_pending", status: 202, client_message_id: row.client_message_id } : conflict("outbox_pending_fingerprint_mismatch", fpPrefix);
8624
+ case "inflight":
8625
+ return match ? { kind: "accepted_inflight", status: 202, client_message_id: row.client_message_id } : conflict("outbox_inflight_fingerprint_mismatch", fpPrefix);
8626
+ case "done":
8627
+ return match ? {
8628
+ kind: "accepted_done",
8629
+ status: 200,
8630
+ client_message_id: row.client_message_id,
8631
+ broker_message_id: row.broker_message_id
8632
+ } : conflict("outbox_done_fingerprint_mismatch", fpPrefix, row.broker_message_id);
8633
+ case "dead":
8634
+ return match ? conflict("outbox_dead_fingerprint_match", fpPrefix, row.broker_message_id) : conflict("outbox_dead_fingerprint_mismatch", fpPrefix);
8635
+ case "aborted":
8636
+ return match ? conflict("outbox_aborted_fingerprint_match", fpPrefix) : conflict("outbox_aborted_fingerprint_mismatch", fpPrefix);
8637
+ default: {
8638
+ const _ = row.status;
8639
+ throw new Error(`unknown outbox status: ${String(_)}`);
8640
+ }
8641
+ }
8642
+ }
8643
+ function conflict(reason, fpPrefix, brokerMessageId = null) {
8644
+ return {
8645
+ kind: "conflict",
8646
+ status: 409,
8647
+ reason,
8648
+ daemon_fingerprint_prefix: fpPrefix,
8649
+ broker_message_id: brokerMessageId
8650
+ };
8651
+ }
8652
+ function ulidLike(newId) {
8653
+ return newId();
8654
+ }
8655
+ var ENVELOPE_VERSION = 1;
8656
+ var init_send2 = __esm(() => {
8657
+ init_fingerprint();
8658
+ });
8659
+
8660
+ // src/daemon/db/inbox.ts
8661
+ function migrateInbox(db) {
8662
+ db.exec(`
8663
+ CREATE TABLE IF NOT EXISTS inbox (
8664
+ id TEXT PRIMARY KEY,
8665
+ client_message_id TEXT NOT NULL UNIQUE,
8666
+ broker_message_id TEXT,
8667
+ mesh TEXT NOT NULL,
8668
+ topic TEXT,
8669
+ sender_pubkey TEXT NOT NULL,
8670
+ sender_name TEXT NOT NULL,
8671
+ body TEXT,
8672
+ meta TEXT,
8673
+ received_at INTEGER NOT NULL,
8674
+ reply_to_id TEXT
8675
+ );
8676
+ CREATE INDEX IF NOT EXISTS inbox_received_at ON inbox(received_at);
8677
+ CREATE INDEX IF NOT EXISTS inbox_topic ON inbox(topic);
8678
+ CREATE INDEX IF NOT EXISTS inbox_sender ON inbox(sender_pubkey);
8679
+ `);
8680
+ }
8681
+ function insertIfNew(db, row) {
8682
+ const before = db.prepare(`SELECT id FROM inbox WHERE client_message_id = ?`).get(row.client_message_id);
8683
+ if (before)
8684
+ return null;
8685
+ db.prepare(`
8686
+ INSERT INTO inbox (
8687
+ id, client_message_id, broker_message_id, mesh, topic,
8688
+ sender_pubkey, sender_name, body, meta, received_at, reply_to_id
8689
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
8690
+ ON CONFLICT(client_message_id) DO NOTHING
8691
+ `).run(row.id, row.client_message_id, row.broker_message_id, row.mesh, row.topic, row.sender_pubkey, row.sender_name, row.body, row.meta, row.received_at, row.reply_to_id);
8692
+ const after = db.prepare(`SELECT id FROM inbox WHERE client_message_id = ?`).get(row.client_message_id);
8693
+ return after?.id === row.id ? row.id : null;
8694
+ }
8695
+ function listInbox(db, p) {
8696
+ const where = [];
8697
+ const args = [];
8698
+ if (p.since !== undefined) {
8699
+ where.push("received_at >= ?");
8700
+ args.push(p.since);
8701
+ }
8702
+ if (p.topic !== undefined) {
8703
+ where.push("topic = ?");
8704
+ args.push(p.topic);
8705
+ }
8706
+ if (p.fromPubkey !== undefined) {
8707
+ where.push("sender_pubkey = ?");
8708
+ args.push(p.fromPubkey);
8709
+ }
8710
+ const sql = `
8711
+ SELECT id, client_message_id, broker_message_id, mesh, topic,
8712
+ sender_pubkey, sender_name, body, meta, received_at, reply_to_id
8713
+ FROM inbox
8714
+ ${where.length ? "WHERE " + where.join(" AND ") : ""}
8715
+ ORDER BY received_at DESC
8716
+ LIMIT ?
8717
+ `;
8718
+ args.push(Math.min(Math.max(p.limit ?? 100, 1), 1000));
8719
+ return db.prepare(sql).all(...args);
8720
+ }
8721
+
8722
+ // src/daemon/events.ts
8723
+ class EventBus {
8724
+ subs = new Set;
8725
+ publish(kind, data) {
8726
+ const e = { kind, ts: new Date().toISOString(), data };
8727
+ for (const s of this.subs) {
8728
+ try {
8729
+ s(e);
8730
+ } catch {}
8731
+ }
8732
+ }
8733
+ subscribe(fn) {
8734
+ this.subs.add(fn);
8735
+ return () => this.subs.delete(fn);
8736
+ }
8737
+ }
8738
+ function writeSse(res, e, idCounter) {
8739
+ res.write(`id: ${idCounter}
8740
+ `);
8741
+ res.write(`event: ${e.kind}
8742
+ `);
8743
+ res.write(`data: ${JSON.stringify({ ts: e.ts, ...e.data })}
8744
+
8745
+ `);
8746
+ }
8747
+ function bindSseStream(res, bus) {
8748
+ res.statusCode = 200;
8749
+ res.setHeader("Content-Type", "text/event-stream");
8750
+ res.setHeader("Cache-Control", "no-cache, no-transform");
8751
+ res.setHeader("Connection", "keep-alive");
8752
+ res.setHeader("X-Accel-Buffering", "no");
8753
+ res.write(`: connected
8754
+
8755
+ `);
8756
+ let counter = 0;
8757
+ const unsubscribe = bus.subscribe((e) => writeSse(res, e, ++counter));
8758
+ const heartbeat = setInterval(() => {
8759
+ try {
8760
+ res.write(`: keepalive
8761
+
8762
+ `);
8763
+ } catch {}
8764
+ }, 15000);
8765
+ const cleanup = () => {
8766
+ clearInterval(heartbeat);
8767
+ unsubscribe();
8768
+ try {
8769
+ res.end();
8770
+ } catch {}
8771
+ };
8772
+ res.on("close", cleanup);
8773
+ res.on("error", cleanup);
8774
+ return cleanup;
8775
+ }
8776
+
8777
+ // src/daemon/ipc/server.ts
8778
+ import { createServer as createServer2 } from "node:http";
8779
+ import { chmodSync as chmodSync3, existsSync as existsSync9, unlinkSync as unlinkSync3 } from "node:fs";
8780
+ import { timingSafeEqual } from "node:crypto";
8781
+ import { randomUUID as randomUUID4 } from "node:crypto";
8782
+ function startIpcServer(opts) {
8783
+ const log2 = opts.log ?? defaultLogger;
8784
+ const handler = makeHandler({
8785
+ localToken: opts.localToken,
8786
+ publicHealthCheck: !!opts.publicHealthCheck,
8787
+ log: log2,
8788
+ outboxDb: opts.outboxDb,
8789
+ inboxDb: opts.inboxDb,
8790
+ bus: opts.bus,
8791
+ broker: opts.broker,
8792
+ onPendingInserted: opts.onPendingInserted
8793
+ });
8794
+ if (existsSync9(DAEMON_PATHS.SOCK_FILE)) {
8795
+ try {
8796
+ unlinkSync3(DAEMON_PATHS.SOCK_FILE);
8797
+ } catch {}
8798
+ }
8799
+ const uds = createServer2(handler);
8800
+ const udsReady = new Promise((resolve, reject) => {
8801
+ uds.once("error", reject);
8802
+ uds.listen(DAEMON_PATHS.SOCK_FILE, () => {
8803
+ try {
8804
+ chmodSync3(DAEMON_PATHS.SOCK_FILE, 384);
8805
+ } catch (err) {
8806
+ log2("warn", "uds_chmod_failed", { err: String(err) });
8807
+ }
8808
+ resolve();
8809
+ });
8810
+ });
8811
+ let tcp = null;
8812
+ let tcpReady = Promise.resolve();
8813
+ if (opts.tcpEnabled !== false) {
8814
+ tcp = createServer2(handler);
8815
+ tcpReady = new Promise((resolve, reject) => {
8816
+ tcp.once("error", reject);
8817
+ tcp.listen(opts.tcpPort ?? DAEMON_TCP_DEFAULT_PORT, DAEMON_TCP_HOST, () => resolve());
8818
+ });
8819
+ }
8820
+ return {
8821
+ uds,
8822
+ tcp,
8823
+ ready: Promise.all([udsReady, tcpReady]).then(() => {
8824
+ return;
8825
+ }),
8826
+ close: async () => {
8827
+ await Promise.allSettled([
8828
+ new Promise((res) => uds.close(() => res())),
8829
+ tcp ? new Promise((res) => tcp.close(() => res())) : Promise.resolve()
8830
+ ]);
8831
+ try {
8832
+ unlinkSync3(DAEMON_PATHS.SOCK_FILE);
8833
+ } catch {}
8834
+ }
8835
+ };
8836
+ }
8837
+ function defaultLogger(level, msg, meta) {
8838
+ const line = JSON.stringify({ level, msg, ...meta, ts: new Date().toISOString() });
8839
+ if (level === "info")
8840
+ process.stdout.write(line + `
8841
+ `);
8842
+ else
8843
+ process.stderr.write(line + `
8844
+ `);
8845
+ }
8846
+ function makeHandler(opts) {
8847
+ const tokenBytes = Buffer.from(opts.localToken, "utf8");
8848
+ return async (req, res) => {
8849
+ const url = new URL(req.url ?? "/", "http://daemon.local");
8850
+ if (url.searchParams.has("token")) {
8851
+ opts.log("warn", "ipc_token_in_query_string_rejected", { path: url.pathname });
8852
+ respond(res, 400, { error: "token must be in Authorization header, not query string" });
8853
+ return;
8854
+ }
8855
+ const host = (req.headers.host ?? "").toLowerCase().split(":")[0]?.trim() ?? "";
8856
+ if (host && host !== "localhost" && host !== "127.0.0.1" && host !== "[::1]" && host !== "::1") {
8857
+ respond(res, 403, { error: "forbidden host" });
8858
+ return;
8859
+ }
8860
+ if (req.headers.origin) {
8861
+ respond(res, 403, { error: "forbidden origin" });
8862
+ return;
8863
+ }
8864
+ const isUds = req.socket.remoteAddress === undefined;
8865
+ const isPublicHealth = opts.publicHealthCheck && url.pathname === "/v1/health";
8866
+ if (!isUds && !isPublicHealth) {
8867
+ const authz = req.headers.authorization ?? "";
8868
+ const m = /^Bearer\s+(.+)$/.exec(authz.trim());
8869
+ if (!m || !m[1]) {
8870
+ respond(res, 401, { error: "missing bearer token" });
8871
+ return;
8872
+ }
8873
+ const provided = Buffer.from(m[1], "utf8");
8874
+ if (provided.length !== tokenBytes.length || !timingSafeEqual(provided, tokenBytes)) {
8875
+ opts.log("warn", "ipc_bearer_mismatch", { path: url.pathname });
8876
+ respond(res, 401, { error: "invalid bearer token" });
8877
+ return;
8878
+ }
8879
+ }
8880
+ if (req.method === "GET" && url.pathname === "/v1/version") {
8881
+ respond(res, 200, {
8882
+ daemon_version: VERSION,
8883
+ ipc_api: "v1",
8884
+ ipc_features: ["version", "health", "send", "inbox", "events", "peers", "profile"],
8885
+ schema_version: 1
8886
+ });
8887
+ return;
8888
+ }
8889
+ if (req.method === "GET" && url.pathname === "/v1/health") {
8890
+ respond(res, 200, { ok: true, pid: process.pid });
8891
+ return;
8892
+ }
8893
+ if (req.method === "GET" && url.pathname === "/v1/events") {
8894
+ if (!opts.bus) {
8895
+ respond(res, 503, { error: "event bus not initialised" });
8896
+ return;
8897
+ }
8898
+ bindSseStream(res, opts.bus);
8899
+ return;
8900
+ }
8901
+ if (req.method === "GET" && url.pathname === "/v1/peers") {
8902
+ if (!opts.broker) {
8903
+ respond(res, 503, { error: "broker not initialised" });
8904
+ return;
8905
+ }
8906
+ try {
8907
+ const peers = await opts.broker.listPeers();
8908
+ respond(res, 200, { peers });
8909
+ } catch (e) {
8910
+ respond(res, 502, { error: "broker_unreachable", detail: String(e) });
8911
+ }
8912
+ return;
8913
+ }
8914
+ if (req.method === "POST" && url.pathname === "/v1/profile") {
8915
+ if (!opts.broker) {
8916
+ respond(res, 503, { error: "broker not initialised" });
8917
+ return;
8918
+ }
8919
+ try {
8920
+ const body = await readJsonBody(req, 16 * 1024);
8921
+ if (!body) {
8922
+ respond(res, 400, { error: "expected JSON object" });
8923
+ return;
8924
+ }
8925
+ const updates = {};
8926
+ if (typeof body.summary === "string")
8927
+ opts.broker.setSummary(body.summary);
8928
+ if (body.status === "idle" || body.status === "working" || body.status === "dnd")
8929
+ opts.broker.setStatus(body.status);
8930
+ if (typeof body.visible === "boolean")
8931
+ opts.broker.setVisible(body.visible);
8932
+ const profile = {};
8933
+ if (typeof body.avatar === "string")
8934
+ profile.avatar = body.avatar;
8935
+ if (typeof body.title === "string")
8936
+ profile.title = body.title;
8937
+ if (typeof body.bio === "string")
8938
+ profile.bio = body.bio;
8939
+ if (Array.isArray(body.capabilities))
8940
+ profile.capabilities = body.capabilities.filter((c) => typeof c === "string");
8941
+ if (Object.keys(profile).length > 0)
8942
+ opts.broker.setProfile(profile);
8943
+ Object.assign(updates, body);
8944
+ respond(res, 200, { ok: true, applied: Object.keys(updates) });
8945
+ } catch (e) {
8946
+ respond(res, 400, { error: String(e) });
8947
+ }
8948
+ return;
8949
+ }
8950
+ if (req.method === "GET" && url.pathname === "/v1/inbox") {
8951
+ if (!opts.inboxDb) {
8952
+ respond(res, 503, { error: "inbox not initialised" });
8953
+ return;
8954
+ }
8955
+ const sinceRaw = url.searchParams.get("since");
8956
+ const since = sinceRaw ? Date.parse(sinceRaw) : undefined;
8957
+ const topic = url.searchParams.get("topic") ?? undefined;
8958
+ const fromPubkey = url.searchParams.get("from") ?? undefined;
8959
+ const limitRaw = url.searchParams.get("limit");
8960
+ const limit = limitRaw ? Number.parseInt(limitRaw, 10) : undefined;
8961
+ const rows = listInbox(opts.inboxDb, {
8962
+ since: Number.isFinite(since) ? since : undefined,
8963
+ topic,
8964
+ fromPubkey,
8965
+ limit: Number.isFinite(limit ?? NaN) ? limit : undefined
8966
+ });
8967
+ respond(res, 200, {
8968
+ items: rows.map((r) => ({
8969
+ id: r.id,
8970
+ client_message_id: r.client_message_id,
8971
+ broker_message_id: r.broker_message_id,
8972
+ mesh: r.mesh,
8973
+ topic: r.topic,
8974
+ sender_pubkey: r.sender_pubkey,
8975
+ sender_name: r.sender_name,
8976
+ body: r.body,
8977
+ received_at: new Date(r.received_at).toISOString(),
8978
+ reply_to_id: r.reply_to_id
8979
+ }))
8980
+ });
8981
+ return;
8982
+ }
8983
+ if (req.method === "GET" && url.pathname === "/v1/outbox") {
8984
+ if (!opts.outboxDb) {
8985
+ respond(res, 503, { error: "outbox not initialised" });
8986
+ return;
8987
+ }
8988
+ const statusParam = url.searchParams.get("status") ?? undefined;
8989
+ const allowed = ["pending", "inflight", "done", "dead", "aborted"];
8990
+ const status = statusParam && allowed.includes(statusParam) ? statusParam : undefined;
8991
+ const limitRaw = url.searchParams.get("limit");
8992
+ const limit = limitRaw ? Number.parseInt(limitRaw, 10) : undefined;
8993
+ const rows = listOutbox(opts.outboxDb, {
8994
+ status,
8995
+ limit: Number.isFinite(limit ?? NaN) ? limit : undefined
8996
+ });
8997
+ respond(res, 200, {
8998
+ items: rows.map((r) => ({
8999
+ id: r.id,
9000
+ client_message_id: r.client_message_id,
9001
+ status: r.status,
9002
+ attempts: r.attempts,
9003
+ enqueued_at: new Date(r.enqueued_at).toISOString(),
9004
+ next_attempt_at: new Date(r.next_attempt_at).toISOString(),
9005
+ delivered_at: r.delivered_at ? new Date(r.delivered_at).toISOString() : null,
9006
+ broker_message_id: r.broker_message_id,
9007
+ last_error: r.last_error,
9008
+ aborted_at: r.aborted_at ? new Date(r.aborted_at).toISOString() : null,
9009
+ aborted_by: r.aborted_by,
9010
+ superseded_by: r.superseded_by,
9011
+ payload_bytes: r.payload?.byteLength ?? 0
9012
+ }))
9013
+ });
9014
+ return;
9015
+ }
9016
+ if (req.method === "POST" && url.pathname === "/v1/outbox/requeue") {
9017
+ if (!opts.outboxDb) {
9018
+ respond(res, 503, { error: "outbox not initialised" });
9019
+ return;
9020
+ }
9021
+ try {
9022
+ const body = await readJsonBody(req, 4 * 1024);
9023
+ if (!body || typeof body.id !== "string") {
9024
+ respond(res, 400, { error: "missing 'id'" });
9025
+ return;
9026
+ }
9027
+ const newId = typeof body.new_client_message_id === "string" && body.new_client_message_id.trim() ? body.new_client_message_id.trim() : randomUUID4();
9028
+ const result = requeueDeadOrPending(opts.outboxDb, {
9029
+ id: body.id,
9030
+ newClientMessageId: newId,
9031
+ newRowId: randomUUID4(),
9032
+ now: Date.now(),
9033
+ abortedBy: typeof body.aborted_by === "string" ? body.aborted_by : "operator"
9034
+ });
9035
+ if (!result) {
9036
+ respond(res, 409, { error: "row not found, already aborted, or already done" });
9037
+ return;
9038
+ }
9039
+ respond(res, 200, {
9040
+ aborted_row_id: result.abortedRowId,
9041
+ new_row_id: result.newRowId,
9042
+ new_client_message_id: result.newClientMessageId
9043
+ });
9044
+ opts.onPendingInserted?.();
9045
+ } catch (e) {
9046
+ respond(res, 400, { error: String(e) });
9047
+ }
9048
+ return;
9049
+ }
9050
+ if (req.method === "POST" && url.pathname === "/v1/send") {
9051
+ if (!opts.outboxDb) {
9052
+ respond(res, 503, { error: "outbox not initialised" });
9053
+ return;
9054
+ }
9055
+ try {
9056
+ const body = await readJsonBody(req, 256 * 1024);
9057
+ const parsed = parseSendRequest(body, req.headers["idempotency-key"]);
9058
+ if ("error" in parsed) {
9059
+ respond(res, 400, { error: parsed.error });
9060
+ return;
9061
+ }
9062
+ const outcome = acceptSend(parsed.req, { db: opts.outboxDb });
9063
+ switch (outcome.kind) {
9064
+ case "accepted_pending":
9065
+ respond(res, outcome.status, {
9066
+ client_message_id: outcome.client_message_id,
9067
+ status: "queued"
9068
+ });
9069
+ opts.onPendingInserted?.();
9070
+ return;
9071
+ case "accepted_inflight":
9072
+ respond(res, outcome.status, {
9073
+ client_message_id: outcome.client_message_id,
9074
+ status: "inflight"
9075
+ });
9076
+ return;
9077
+ case "accepted_done":
9078
+ respond(res, outcome.status, {
9079
+ client_message_id: outcome.client_message_id,
9080
+ broker_message_id: outcome.broker_message_id,
9081
+ duplicate: true
9082
+ });
9083
+ return;
9084
+ case "conflict":
9085
+ respond(res, outcome.status, {
9086
+ error: "idempotency_key_reused",
9087
+ conflict: outcome.reason,
9088
+ daemon_fingerprint_prefix: outcome.daemon_fingerprint_prefix,
9089
+ broker_message_id: outcome.broker_message_id ?? null
9090
+ });
9091
+ return;
9092
+ }
9093
+ } catch (err) {
9094
+ opts.log("error", "ipc_send_failed", { err: String(err) });
9095
+ respond(res, 500, { error: "internal" });
9096
+ return;
9097
+ }
9098
+ }
9099
+ respond(res, 404, { error: "not found" });
9100
+ };
9101
+ }
9102
+ async function readJsonBody(req, maxBytes) {
9103
+ const chunks = [];
9104
+ let total = 0;
9105
+ for await (const chunk of req) {
9106
+ const buf = chunk;
9107
+ total += buf.length;
9108
+ if (total > maxBytes)
9109
+ throw new Error("payload_too_large");
9110
+ chunks.push(buf);
9111
+ }
9112
+ if (total === 0)
9113
+ return null;
9114
+ const text = Buffer.concat(chunks).toString("utf8");
9115
+ try {
9116
+ return JSON.parse(text);
9117
+ } catch {
9118
+ throw new Error("invalid_json");
9119
+ }
9120
+ }
9121
+ function parseSendRequest(body, idempotencyHeader) {
9122
+ if (!body || typeof body !== "object")
9123
+ return { error: "expected JSON object" };
9124
+ const b = body;
9125
+ const to = typeof b.to === "string" ? b.to.trim() : "";
9126
+ const message = typeof b.message === "string" ? b.message : "";
9127
+ if (!to)
9128
+ return { error: "missing 'to'" };
9129
+ if (!message)
9130
+ return { error: "missing 'message'" };
9131
+ const priority = b.priority;
9132
+ if (priority !== undefined && priority !== "now" && priority !== "next" && priority !== "low") {
9133
+ return { error: "priority must be 'now' | 'next' | 'low'" };
9134
+ }
9135
+ const meta = b.meta;
9136
+ if (meta !== undefined && meta !== null && (typeof meta !== "object" || Array.isArray(meta))) {
9137
+ return { error: "'meta' must be an object" };
9138
+ }
9139
+ let destination_kind;
9140
+ let destination_ref;
9141
+ if (to.startsWith("@")) {
9142
+ destination_kind = "topic";
9143
+ destination_ref = to.slice(1);
9144
+ } else if (to === "*") {
9145
+ destination_kind = "topic";
9146
+ destination_ref = "*";
9147
+ } else {
9148
+ destination_kind = "dm";
9149
+ destination_ref = to;
9150
+ }
9151
+ const headerId = Array.isArray(idempotencyHeader) ? idempotencyHeader[0] : idempotencyHeader;
9152
+ const client_message_id = typeof b.client_message_id === "string" && b.client_message_id.trim() ? b.client_message_id.trim() : typeof headerId === "string" && headerId.trim() ? headerId.trim() : undefined;
9153
+ const reply_to_id = typeof b.reply_to_id === "string" ? b.reply_to_id : undefined;
9154
+ return {
9155
+ req: {
9156
+ to,
9157
+ message,
9158
+ priority,
9159
+ meta: meta ?? undefined,
9160
+ reply_to_id,
9161
+ client_message_id,
9162
+ destination_kind,
9163
+ destination_ref
9164
+ }
9165
+ };
9166
+ }
9167
+ function respond(res, status, body) {
9168
+ const json = JSON.stringify(body);
9169
+ res.statusCode = status;
9170
+ res.setHeader("Content-Type", "application/json");
9171
+ res.setHeader("Content-Length", Buffer.byteLength(json));
9172
+ res.end(json);
9173
+ }
9174
+ var init_server = __esm(() => {
9175
+ init_paths2();
9176
+ init_send2();
9177
+ init_urls();
9178
+ });
9179
+
9180
+ // src/daemon/broker.ts
9181
+ import WebSocket2 from "ws";
9182
+
9183
+ class DaemonBrokerClient {
9184
+ mesh;
9185
+ opts;
9186
+ ws = null;
9187
+ _status = "closed";
9188
+ closed = false;
9189
+ reconnectAttempt = 0;
9190
+ reconnectTimer = null;
9191
+ helloTimer = null;
9192
+ pendingAcks = new Map;
9193
+ peerListResolvers = new Map;
9194
+ sessionPubkey = null;
9195
+ sessionSecretKey = null;
9196
+ opens = [];
9197
+ reqCounter = 0;
9198
+ constructor(mesh, opts = {}) {
9199
+ this.mesh = mesh;
9200
+ this.opts = opts;
9201
+ }
9202
+ get status() {
9203
+ return this._status;
9204
+ }
9205
+ get meshSlug() {
9206
+ return this.mesh.slug;
9207
+ }
9208
+ get meshId() {
9209
+ return this.mesh.meshId;
9210
+ }
9211
+ log = (level, msg, meta) => {
9212
+ (this.opts.log ?? defaultLog)(level, msg, { mesh: this.mesh.slug, ...meta });
9213
+ };
9214
+ setConnStatus(s) {
9215
+ if (this._status === s)
9216
+ return;
9217
+ this._status = s;
9218
+ this.opts.onStatusChange?.(s);
9219
+ }
9220
+ async connect() {
9221
+ if (this.closed)
9222
+ throw new Error("client_closed");
9223
+ if (this._status === "connecting" || this._status === "open")
9224
+ return;
9225
+ this.setConnStatus("connecting");
9226
+ const ws = new WebSocket2(this.mesh.brokerUrl);
9227
+ this.ws = ws;
9228
+ return new Promise((resolve, reject) => {
9229
+ ws.on("open", async () => {
9230
+ try {
9231
+ if (!this.sessionPubkey) {
9232
+ const { generateKeypair: generateKeypair4 } = await Promise.resolve().then(() => (init_facade7(), exports_facade4));
9233
+ const kp = await generateKeypair4();
9234
+ this.sessionPubkey = kp.publicKey;
9235
+ this.sessionSecretKey = kp.secretKey;
9236
+ }
9237
+ const { timestamp: timestamp2, signature } = await signHello(this.mesh.meshId, this.mesh.memberId, this.mesh.pubkey, this.mesh.secretKey);
9238
+ ws.send(JSON.stringify({
9239
+ type: "hello",
9240
+ meshId: this.mesh.meshId,
9241
+ memberId: this.mesh.memberId,
9242
+ pubkey: this.mesh.pubkey,
9243
+ sessionPubkey: this.sessionPubkey,
9244
+ displayName: this.opts.displayName,
9245
+ sessionId: `daemon-${process.pid}`,
9246
+ pid: process.pid,
9247
+ cwd: process.cwd(),
9248
+ hostname: __require("node:os").hostname(),
9249
+ peerType: "ai",
9250
+ channel: "claudemesh-daemon",
9251
+ timestamp: timestamp2,
9252
+ signature
9253
+ }));
9254
+ this.helloTimer = setTimeout(() => {
9255
+ this.log("warn", "broker_hello_ack_timeout");
9256
+ try {
9257
+ ws.close();
9258
+ } catch {}
9259
+ reject(new Error("hello_ack_timeout"));
9260
+ }, HELLO_ACK_TIMEOUT_MS2);
9261
+ } catch (e) {
9262
+ reject(e instanceof Error ? e : new Error(String(e)));
9263
+ }
9264
+ });
9265
+ ws.on("message", (raw) => {
9266
+ let msg;
9267
+ try {
9268
+ msg = JSON.parse(raw.toString());
9269
+ } catch {
9270
+ return;
9271
+ }
9272
+ if (msg.type === "hello_ack") {
9273
+ if (this.helloTimer)
9274
+ clearTimeout(this.helloTimer);
9275
+ this.helloTimer = null;
9276
+ this.setConnStatus("open");
9277
+ this.reconnectAttempt = 0;
9278
+ const queued = this.opens.slice();
9279
+ this.opens.length = 0;
9280
+ for (const fn of queued) {
9281
+ try {
9282
+ fn();
9283
+ } catch (e) {
9284
+ this.log("warn", "open_handler_failed", { err: String(e) });
9285
+ }
9286
+ }
9287
+ resolve();
9288
+ return;
9289
+ }
9290
+ if (msg.type === "ack") {
9291
+ const id = String(msg.id ?? "");
9292
+ const ack = this.pendingAcks.get(id);
9293
+ if (ack) {
9294
+ this.pendingAcks.delete(id);
9295
+ clearTimeout(ack.timer);
9296
+ if (typeof msg.error === "string" && msg.error.length > 0) {
9297
+ ack.resolve({ ok: false, error: msg.error, permanent: classifyPermanent(msg.error) });
9298
+ } else {
9299
+ ack.resolve({ ok: true, messageId: String(msg.messageId ?? id) });
9300
+ }
9301
+ }
9302
+ return;
9303
+ }
9304
+ if (msg.type === "peers_list") {
9305
+ const reqId = String(msg._reqId ?? "");
9306
+ const pending = this.peerListResolvers.get(reqId);
9307
+ if (pending) {
9308
+ this.peerListResolvers.delete(reqId);
9309
+ clearTimeout(pending.timer);
9310
+ pending.resolve(Array.isArray(msg.peers) ? msg.peers : []);
9311
+ }
9312
+ return;
9313
+ }
9314
+ if (msg.type === "push" || msg.type === "inbound") {
9315
+ this.opts.onPush?.(msg);
9316
+ return;
9317
+ }
9318
+ });
9319
+ ws.on("close", (code, reason) => {
9320
+ if (this.helloTimer) {
9321
+ clearTimeout(this.helloTimer);
9322
+ this.helloTimer = null;
9323
+ }
9324
+ this.failPendingAcks(`broker_disconnected_${code}`);
9325
+ if (this.closed) {
9326
+ this.setConnStatus("closed");
9327
+ return;
9328
+ }
9329
+ this.setConnStatus("reconnecting");
9330
+ const wait = BACKOFF_CAPS_MS[Math.min(this.reconnectAttempt, BACKOFF_CAPS_MS.length - 1)] ?? 30000;
9331
+ this.reconnectAttempt++;
9332
+ this.log("info", "broker_reconnect_scheduled", { wait_ms: wait, code, reason: reason.toString("utf8") });
9333
+ this.reconnectTimer = setTimeout(() => this.connect().catch((err) => this.log("warn", "broker_reconnect_failed", { err: String(err) })), wait);
9334
+ if (this._status === "connecting")
9335
+ reject(new Error(`closed_before_hello_${code}`));
9336
+ });
9337
+ ws.on("error", (err) => this.log("warn", "broker_ws_error", { err: err.message }));
9338
+ });
9339
+ }
9340
+ send(req) {
9341
+ return new Promise((resolve) => {
9342
+ const dispatch = () => {
9343
+ if (!this.ws || this.ws.readyState !== this.ws.OPEN) {
9344
+ resolve({ ok: false, error: "broker_not_open", permanent: false });
9345
+ return;
9346
+ }
9347
+ const id = req.client_message_id;
9348
+ const timer = setTimeout(() => {
9349
+ if (this.pendingAcks.delete(id)) {
9350
+ resolve({ ok: false, error: "ack_timeout", permanent: false });
9351
+ }
9352
+ }, SEND_ACK_TIMEOUT_MS);
9353
+ this.pendingAcks.set(id, { resolve, timer });
9354
+ try {
9355
+ this.ws.send(JSON.stringify({
9356
+ type: "send",
9357
+ id,
9358
+ client_message_id: id,
9359
+ request_fingerprint: req.request_fingerprint_hex,
9360
+ targetSpec: req.targetSpec,
9361
+ priority: req.priority,
9362
+ nonce: req.nonce,
9363
+ ciphertext: req.ciphertext
9364
+ }));
9365
+ } catch (e) {
9366
+ this.pendingAcks.delete(id);
9367
+ clearTimeout(timer);
9368
+ resolve({ ok: false, error: `ws_write_failed: ${String(e)}`, permanent: false });
9369
+ }
9370
+ };
9371
+ if (this._status === "open")
9372
+ dispatch();
9373
+ else
9374
+ this.opens.push(dispatch);
9375
+ });
9376
+ }
9377
+ async listPeers(timeoutMs = 5000) {
9378
+ if (this._status !== "open" || !this.ws)
9379
+ return [];
9380
+ return new Promise((resolve) => {
9381
+ const reqId = `pl-${++this.reqCounter}`;
9382
+ const timer = setTimeout(() => {
9383
+ if (this.peerListResolvers.delete(reqId))
9384
+ resolve([]);
9385
+ }, timeoutMs);
9386
+ this.peerListResolvers.set(reqId, { resolve, timer });
9387
+ try {
9388
+ this.ws.send(JSON.stringify({ type: "list_peers", _reqId: reqId }));
9389
+ } catch {
9390
+ this.peerListResolvers.delete(reqId);
9391
+ clearTimeout(timer);
9392
+ resolve([]);
9393
+ }
9394
+ });
9395
+ }
9396
+ setProfile(profile) {
9397
+ if (this._status !== "open" || !this.ws)
9398
+ return;
9399
+ try {
9400
+ this.ws.send(JSON.stringify({ type: "set_profile", ...profile }));
9401
+ } catch {}
9402
+ }
9403
+ setSummary(summary) {
9404
+ if (this._status !== "open" || !this.ws)
9405
+ return;
9406
+ try {
9407
+ this.ws.send(JSON.stringify({ type: "set_summary", summary }));
9408
+ } catch {}
9409
+ }
9410
+ setStatus(status) {
9411
+ if (this._status !== "open" || !this.ws)
9412
+ return;
9413
+ try {
9414
+ this.ws.send(JSON.stringify({ type: "set_status", status }));
9415
+ } catch {}
9416
+ }
9417
+ setVisible(visible) {
9418
+ if (this._status !== "open" || !this.ws)
9419
+ return;
9420
+ try {
9421
+ this.ws.send(JSON.stringify({ type: "set_visible", visible }));
9422
+ } catch {}
9423
+ }
9424
+ async close() {
9425
+ this.closed = true;
9426
+ if (this.reconnectTimer) {
9427
+ clearTimeout(this.reconnectTimer);
9428
+ this.reconnectTimer = null;
9429
+ }
9430
+ if (this.helloTimer) {
9431
+ clearTimeout(this.helloTimer);
9432
+ this.helloTimer = null;
9433
+ }
9434
+ this.failPendingAcks("daemon_shutdown");
9435
+ try {
9436
+ this.ws?.close();
9437
+ } catch {}
9438
+ this.setConnStatus("closed");
9439
+ }
9440
+ getSessionKeys() {
9441
+ if (!this.sessionPubkey || !this.sessionSecretKey)
9442
+ return null;
9443
+ return { sessionPubkey: this.sessionPubkey, sessionSecretKey: this.sessionSecretKey };
9444
+ }
9445
+ failPendingAcks(reason) {
9446
+ for (const [id, ack] of this.pendingAcks) {
9447
+ clearTimeout(ack.timer);
9448
+ ack.resolve({ ok: false, error: reason, permanent: false });
9449
+ this.pendingAcks.delete(id);
9450
+ }
9451
+ }
9452
+ }
9453
+ function defaultLog(level, msg, meta) {
9454
+ const line = JSON.stringify({ level, msg, ...meta, ts: new Date().toISOString() });
9455
+ if (level === "info")
9456
+ process.stdout.write(line + `
9457
+ `);
9458
+ else
9459
+ process.stderr.write(line + `
9460
+ `);
9461
+ }
9462
+ function classifyPermanent(err) {
9463
+ return /payload_too_large|forbidden|not_found|invalid|schema|auth|signature/i.test(err);
9464
+ }
9465
+ var HELLO_ACK_TIMEOUT_MS2 = 5000, SEND_ACK_TIMEOUT_MS = 15000, BACKOFF_CAPS_MS;
9466
+ var init_broker = __esm(() => {
9467
+ init_hello_sig();
9468
+ BACKOFF_CAPS_MS = [1000, 2000, 4000, 8000, 16000, 30000];
9469
+ });
9470
+
9471
+ // src/daemon/drain.ts
9472
+ function startDrainWorker(opts) {
9473
+ const log2 = opts.log ?? defaultLog2;
9474
+ let stopped = false;
9475
+ let wakeResolve = null;
9476
+ let wakePromise = new Promise((r) => {
9477
+ wakeResolve = r;
9478
+ });
9479
+ const wake = () => {
9480
+ if (wakeResolve) {
9481
+ const r = wakeResolve;
9482
+ wakeResolve = null;
9483
+ r();
9484
+ }
9485
+ };
9486
+ const tick = async () => {
9487
+ while (!stopped) {
9488
+ try {
9489
+ await drainOnce(opts, log2);
9490
+ } catch (e) {
9491
+ log2("warn", "drain_tick_failed", { err: String(e) });
9492
+ }
9493
+ await Promise.race([
9494
+ wakePromise,
9495
+ new Promise((r) => setTimeout(r, POLL_INTERVAL_MS))
9496
+ ]);
9497
+ wakePromise = new Promise((r) => {
9498
+ wakeResolve = r;
9499
+ });
9500
+ }
9501
+ };
9502
+ tick();
9503
+ return {
9504
+ wake,
9505
+ close: async () => {
9506
+ stopped = true;
9507
+ wake();
9508
+ }
9509
+ };
9510
+ }
9511
+ async function drainOnce(opts, log2) {
9512
+ const now = Date.now();
9513
+ const rows = opts.db.prepare(`
9514
+ SELECT id, client_message_id, request_fingerprint, payload, attempts
9515
+ FROM outbox
9516
+ WHERE status = 'pending' AND next_attempt_at <= ?
9517
+ ORDER BY enqueued_at
9518
+ LIMIT 32
9519
+ `).all(now);
9520
+ if (rows.length === 0)
9521
+ return;
9522
+ for (const row of rows) {
9523
+ if (markInflight(opts.db, row.id, now) === 0)
9524
+ continue;
9525
+ const fpHex = bufferToHex(row.request_fingerprint);
9526
+ const sessionKeys = opts.broker.getSessionKeys();
9527
+ const targetSpec = "*";
9528
+ const nonce = await randomNonce2();
9529
+ const ciphertext = Buffer.from(row.payload).toString("base64");
9530
+ let res;
9531
+ try {
9532
+ res = await opts.broker.send({
9533
+ targetSpec,
9534
+ priority: "next",
9535
+ nonce,
9536
+ ciphertext,
9537
+ client_message_id: row.client_message_id,
9538
+ request_fingerprint_hex: fpHex
9539
+ });
9540
+ } catch (e) {
9541
+ log2("warn", "drain_send_threw", { id: row.id, err: String(e) });
9542
+ backoffPending(opts.db, row.id, row.attempts + 1, "exception", String(e));
9543
+ continue;
9544
+ }
9545
+ if (res.ok) {
9546
+ markDone(opts.db, row.id, res.messageId, Date.now());
9547
+ } else if (res.permanent) {
9548
+ log2("warn", "drain_permanent_failure", { id: row.id, err: res.error });
9549
+ markDead(opts.db, row.id, res.error);
9550
+ } else if (row.attempts + 1 >= MAX_ATTEMPTS_PER_ROW) {
9551
+ log2("warn", "drain_max_attempts", { id: row.id, err: res.error });
9552
+ markDead(opts.db, row.id, `max_attempts: ${res.error}`);
9553
+ } else {
9554
+ backoffPending(opts.db, row.id, row.attempts + 1, "retry", res.error);
9555
+ }
9556
+ }
9557
+ }
9558
+ function markInflight(db, id, now) {
9559
+ return Number(db.prepare(`
9560
+ UPDATE outbox
9561
+ SET status = 'inflight', attempts = attempts + 1, next_attempt_at = ?
9562
+ WHERE id = ? AND status = 'pending'
9563
+ `).run(now + BACKOFF_CAP_MS, id).changes);
9564
+ }
9565
+ function markDone(db, id, brokerMessageId, now) {
9566
+ db.prepare(`
9567
+ UPDATE outbox
9568
+ SET status = 'done', delivered_at = ?, broker_message_id = ?, last_error = NULL
9569
+ WHERE id = ?
9570
+ `).run(now, brokerMessageId, id);
9571
+ }
9572
+ function markDead(db, id, err) {
9573
+ db.prepare(`UPDATE outbox SET status = 'dead', last_error = ? WHERE id = ?`).run(err, id);
9574
+ }
9575
+ function backoffPending(db, id, attempts, _kind, err) {
9576
+ const wait = Math.min(BACKOFF_CAP_MS, BACKOFF_BASE_MS * 2 ** Math.min(attempts, 12));
9577
+ const next = Date.now() + wait;
9578
+ db.prepare(`
9579
+ UPDATE outbox
9580
+ SET status = 'pending', attempts = ?, next_attempt_at = ?, last_error = ?
9581
+ WHERE id = ?
9582
+ `).run(attempts, next, err, id);
9583
+ }
9584
+ function bufferToHex(b) {
9585
+ let s = "";
9586
+ for (let i = 0;i < b.length; i++)
9587
+ s += b[i].toString(16).padStart(2, "0");
9588
+ return s;
9589
+ }
9590
+ async function randomNonce2() {
9591
+ const { randomBytes: randomBytes5 } = await import("node:crypto");
9592
+ return randomBytes5(24).toString("base64");
9593
+ }
9594
+ function defaultLog2(level, msg, meta) {
9595
+ const line = JSON.stringify({ level, msg, ...meta, ts: new Date().toISOString() });
9596
+ if (level === "info")
9597
+ process.stdout.write(line + `
9598
+ `);
9599
+ else
9600
+ process.stderr.write(line + `
9601
+ `);
9602
+ }
9603
+ var POLL_INTERVAL_MS = 500, MAX_ATTEMPTS_PER_ROW = 25, BACKOFF_BASE_MS = 500, BACKOFF_CAP_MS = 30000;
9604
+ var init_drain = () => {};
9605
+
9606
+ // src/daemon/inbound.ts
9607
+ import { randomUUID as randomUUID5 } from "node:crypto";
9608
+ async function handleBrokerPush(msg, ctx) {
9609
+ if (msg.subtype === "system" && typeof msg.event === "string") {
9610
+ ctx.bus.publish(mapSystemEventKind(msg.event), {
9611
+ mesh: ctx.meshSlug,
9612
+ event: msg.event,
9613
+ ...msg.eventData ?? {}
9614
+ });
9615
+ return;
9616
+ }
9617
+ if (msg.type !== "push")
9618
+ return;
9619
+ const brokerMessageId = stringOrNull(msg.messageId);
9620
+ const senderPubkey = stringOrNull(msg.senderPubkey) ?? "";
9621
+ const senderName = stringOrNull(msg.senderName) ?? senderPubkey.slice(0, 8);
9622
+ const topic = stringOrNull(msg.topic);
9623
+ const replyToId = stringOrNull(msg.replyToId);
9624
+ const ciphertext = stringOrNull(msg.ciphertext) ?? "";
9625
+ const nonce = stringOrNull(msg.nonce) ?? "";
9626
+ const createdAt = stringOrNull(msg.createdAt);
9627
+ const clientMessageId = stringOrNull(msg.client_message_id) ?? brokerMessageId ?? randomUUID5();
9628
+ const body = await decryptOrFallback({
9629
+ ciphertext,
9630
+ nonce,
9631
+ senderPubkey,
9632
+ ctx
9633
+ });
9634
+ const id = randomUUID5();
9635
+ const inserted = insertIfNew(ctx.db, {
9636
+ id,
9637
+ client_message_id: clientMessageId,
9638
+ broker_message_id: brokerMessageId,
9639
+ mesh: ctx.meshSlug,
9640
+ topic,
9641
+ sender_pubkey: senderPubkey,
9642
+ sender_name: senderName,
9643
+ body,
9644
+ meta: createdAt ? JSON.stringify({ created_at: createdAt }) : null,
9645
+ received_at: Date.now(),
9646
+ reply_to_id: replyToId
9647
+ });
9648
+ if (!inserted)
9649
+ return;
9650
+ ctx.bus.publish("message", {
9651
+ id,
9652
+ mesh: ctx.meshSlug,
9653
+ client_message_id: clientMessageId,
9654
+ broker_message_id: brokerMessageId,
9655
+ sender_pubkey: senderPubkey,
9656
+ sender_name: senderName,
9657
+ topic,
9658
+ reply_to_id: replyToId,
9659
+ body,
9660
+ created_at: createdAt
9661
+ });
9662
+ }
9663
+ async function decryptOrFallback(args) {
9664
+ const { ciphertext, nonce, senderPubkey, ctx } = args;
9665
+ if (!ciphertext)
9666
+ return null;
9667
+ if (nonce && senderPubkey) {
9668
+ const envelope = { nonce, ciphertext };
9669
+ if (ctx.sessionSecretKeyHex) {
9670
+ const pt = await decryptDirect(envelope, senderPubkey, ctx.sessionSecretKeyHex);
9671
+ if (pt !== null)
9672
+ return pt;
9673
+ }
9674
+ if (ctx.recipientSecretKeyHex) {
9675
+ const pt = await decryptDirect(envelope, senderPubkey, ctx.recipientSecretKeyHex);
9676
+ if (pt !== null)
9677
+ return pt;
9678
+ }
9679
+ }
9680
+ try {
9681
+ return Buffer.from(ciphertext, "base64").toString("utf8");
9682
+ } catch (e) {
9683
+ ctx.log?.("warn", "inbound_b64_decode_failed", { err: String(e) });
9684
+ return null;
9685
+ }
9686
+ }
9687
+ function stringOrNull(v) {
9688
+ return typeof v === "string" && v.length > 0 ? v : null;
9689
+ }
9690
+ function mapSystemEventKind(event) {
9691
+ if (event === "peer_joined")
9692
+ return "peer_join";
9693
+ if (event === "peer_left")
9694
+ return "peer_leave";
9695
+ return "system";
9696
+ }
9697
+ var init_inbound = __esm(() => {
9698
+ init_facade7();
9699
+ });
9700
+
9701
+ // src/daemon/identity.ts
9702
+ var exports_identity = {};
9703
+ __export(exports_identity, {
9704
+ computeCurrentFingerprint: () => computeCurrentFingerprint,
9705
+ checkFingerprint: () => checkFingerprint,
9706
+ acceptCurrentHost: () => acceptCurrentHost
9707
+ });
9708
+ import { existsSync as existsSync10, readFileSync as readFileSync7, writeFileSync as writeFileSync8 } from "node:fs";
9709
+ import { join as join7 } from "node:path";
9710
+ import { createHash as createHash2 } from "node:crypto";
9711
+ import { networkInterfaces } from "node:os";
9712
+ function path() {
9713
+ return join7(DAEMON_PATHS.DAEMON_DIR, FILE_NAME);
9714
+ }
9715
+ function computeCurrentFingerprint() {
9716
+ const host_id = readHostId() ?? "";
9717
+ const stable_mac = pickStableMac() ?? "";
9718
+ const fp = createHash2("sha256").update(host_id, "utf8").update("\x00").update(stable_mac, "utf8").digest("hex");
9719
+ return {
9720
+ schema_version: 1,
9721
+ fingerprint: fp,
9722
+ host_id,
9723
+ stable_mac,
9724
+ written_at: new Date().toISOString()
9725
+ };
9726
+ }
9727
+ function checkFingerprint() {
9728
+ const current = computeCurrentFingerprint();
9729
+ if (!existsSync10(path())) {
9730
+ writeFileSync8(path(), JSON.stringify(current, null, 2), { mode: 384 });
9731
+ return { result: "first_run", current };
9732
+ }
9733
+ let stored;
9734
+ try {
9735
+ stored = JSON.parse(readFileSync7(path(), "utf8"));
9736
+ } catch {
9737
+ return { result: "unavailable", current };
9738
+ }
9739
+ if (stored.fingerprint === current.fingerprint)
9740
+ return { result: "match", current, stored };
9741
+ return { result: "mismatch", current, stored };
9742
+ }
9743
+ function acceptCurrentHost() {
9744
+ const current = computeCurrentFingerprint();
9745
+ writeFileSync8(path(), JSON.stringify(current, null, 2), { mode: 384 });
9746
+ return current;
9747
+ }
9748
+ function readHostId() {
9749
+ if (process.platform === "linux") {
9750
+ for (const p of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
9751
+ try {
9752
+ const raw = readFileSync7(p, "utf8").trim();
9753
+ if (raw)
9754
+ return `linux:${raw}`;
9755
+ } catch {}
9756
+ }
9757
+ return null;
9758
+ }
9759
+ if (process.platform === "darwin") {
9760
+ return null;
9761
+ }
9762
+ return null;
9763
+ }
9764
+ function pickStableMac() {
9765
+ const ifs = networkInterfaces();
9766
+ const candidates = [];
9767
+ for (const [name, addrs] of Object.entries(ifs)) {
9768
+ if (!addrs)
9769
+ continue;
9770
+ if (isIgnoredInterface(name))
9771
+ continue;
9772
+ for (const a of addrs) {
9773
+ if (a.internal)
9774
+ continue;
9775
+ if (!a.mac || a.mac === "00:00:00:00:00:00")
9776
+ continue;
9777
+ candidates.push(`${name}::${a.mac}`);
9778
+ break;
9779
+ }
9780
+ }
9781
+ if (candidates.length === 0)
9782
+ return null;
9783
+ candidates.sort();
9784
+ const first = candidates[0];
9785
+ const idx = first.indexOf("::");
9786
+ return idx >= 0 ? first.slice(idx + 2) : first;
9787
+ }
9788
+ function isIgnoredInterface(name) {
9789
+ return /^(lo|docker|br-|veth|tap|tun|tailscale|wg|utun|ppp|vboxnet|vmnet|awdl|llw)/i.test(name);
9790
+ }
9791
+ var FILE_NAME = "host_fingerprint.json";
9792
+ var init_identity = __esm(() => {
9793
+ init_paths2();
9794
+ });
9795
+
9796
+ // src/daemon/run.ts
9797
+ import { existsSync as existsSync11, mkdirSync as mkdirSync6, readFileSync as readFileSync8 } from "node:fs";
9798
+ function detectContainer() {
9799
+ if (process.env.KUBERNETES_SERVICE_HOST)
9800
+ return true;
9801
+ if (process.env.CONTAINER === "1")
9802
+ return true;
9803
+ try {
9804
+ if (existsSync11("/.dockerenv"))
9805
+ return true;
9806
+ const cg = readFileSync8("/proc/1/cgroup", "utf8");
9807
+ if (/(docker|kubepods|containerd)/.test(cg))
9808
+ return true;
9809
+ } catch {}
9810
+ return false;
9811
+ }
9812
+ async function runDaemon(opts = {}) {
9813
+ mkdirSync6(DAEMON_PATHS.DAEMON_DIR, { recursive: true, mode: 448 });
9814
+ const lock = acquireSingletonLock();
9815
+ if (lock.result === "already-running") {
9816
+ process.stderr.write(`daemon already running (pid ${lock.pid})
9817
+ `);
9818
+ return 1;
9819
+ }
9820
+ if (lock.result === "stale") {
9821
+ process.stderr.write(`recovered stale pid file; starting fresh
9822
+ `);
9823
+ }
9824
+ const fpCheck = checkFingerprint();
9825
+ const policy = opts.clonePolicy ?? "refuse";
9826
+ if (fpCheck.result === "mismatch") {
9827
+ const msg = `host_fingerprint mismatch: this daemon dir was started on a different host.`;
9828
+ if (policy === "refuse") {
9829
+ process.stderr.write(`${msg}
9830
+ `);
9831
+ process.stderr.write(` stored host_id: ${fpCheck.stored?.host_id}
9832
+ `);
9833
+ process.stderr.write(` current host_id: ${fpCheck.current.host_id}
9834
+ `);
9835
+ process.stderr.write(`Run \`claudemesh daemon accept-host\` to write a fresh fingerprint, or
9836
+ `);
9837
+ process.stderr.write(`run \`claudemesh daemon remint\` to mint a new keypair (Sprint 7+).
9838
+ `);
9839
+ releaseSingletonLock();
9840
+ return 4;
9841
+ }
9842
+ if (policy === "warn") {
9843
+ process.stderr.write(`WARN: ${msg} (continuing per [clone] policy=warn)
9844
+ `);
9845
+ }
9846
+ }
9847
+ if (fpCheck.result === "first_run") {
9848
+ process.stdout.write(JSON.stringify({
9849
+ msg: "host_fingerprint_written",
9850
+ fingerprint_prefix: fpCheck.current.fingerprint.slice(0, 16),
9851
+ ts: new Date().toISOString()
9852
+ }) + `
9853
+ `);
9854
+ }
9855
+ const localToken = ensureLocalToken();
9856
+ const tcpEnabled = opts.tcpEnabled ?? !detectContainer();
9857
+ let outboxDb;
9858
+ let inboxDb;
9859
+ try {
9860
+ outboxDb = await openSqlite(DAEMON_PATHS.OUTBOX_DB);
9861
+ migrateOutbox(outboxDb);
9862
+ inboxDb = await openSqlite(DAEMON_PATHS.INBOX_DB);
9863
+ migrateInbox(inboxDb);
9864
+ } catch (err) {
9865
+ process.stderr.write(`db open failed: ${String(err)}
9866
+ `);
9867
+ releaseSingletonLock();
9868
+ return 1;
9869
+ }
9870
+ const bus = new EventBus;
9871
+ const cfg = readConfig();
9872
+ let mesh = null;
9873
+ if (opts.mesh) {
9874
+ mesh = cfg.meshes.find((m) => m.slug === opts.mesh) ?? null;
9875
+ if (!mesh) {
9876
+ process.stderr.write(`mesh not found: ${opts.mesh}
9877
+ `);
9878
+ process.stderr.write(`joined meshes: ${cfg.meshes.map((m) => m.slug).join(", ") || "(none)"}
9879
+ `);
9880
+ releaseSingletonLock();
9881
+ try {
9882
+ outboxDb.close();
9883
+ } catch {}
9884
+ return 2;
9885
+ }
9886
+ } else if (cfg.meshes.length === 1) {
9887
+ mesh = cfg.meshes[0];
9888
+ } else if (cfg.meshes.length === 0) {
9889
+ process.stderr.write(`no mesh joined; run \`claudemesh join <invite-url>\` first
9890
+ `);
9891
+ releaseSingletonLock();
9892
+ try {
9893
+ outboxDb.close();
9894
+ } catch {}
9895
+ return 2;
9896
+ } else {
9897
+ process.stderr.write(`multiple meshes joined; pass --mesh <slug>
9898
+ `);
9899
+ process.stderr.write(`available: ${cfg.meshes.map((m) => m.slug).join(", ")}
9900
+ `);
9901
+ releaseSingletonLock();
9902
+ try {
9903
+ outboxDb.close();
9904
+ } catch {}
9905
+ return 2;
9906
+ }
9907
+ const broker = new DaemonBrokerClient(mesh, {
9908
+ displayName: opts.displayName,
9909
+ onStatusChange: (s) => {
9910
+ process.stdout.write(JSON.stringify({
9911
+ msg: "broker_status",
9912
+ status: s,
9913
+ mesh: mesh.slug,
9914
+ ts: new Date().toISOString()
9915
+ }) + `
9916
+ `);
9917
+ bus.publish("broker_status", { mesh: mesh.slug, status: s });
9918
+ },
9919
+ onPush: (m) => {
9920
+ const sessionKeys = broker.getSessionKeys();
9921
+ handleBrokerPush(m, {
9922
+ db: inboxDb,
9923
+ bus,
9924
+ meshSlug: mesh.slug,
9925
+ recipientSecretKeyHex: mesh.secretKey,
9926
+ sessionSecretKeyHex: sessionKeys?.sessionSecretKey
9927
+ });
9928
+ }
9929
+ });
9930
+ broker.connect().catch((err) => process.stderr.write(`broker connect failed: ${String(err)}
9931
+ `));
9932
+ let drain = null;
9933
+ drain = startDrainWorker({ db: outboxDb, broker });
9934
+ const ipc2 = startIpcServer({
9935
+ localToken,
9936
+ tcpEnabled,
9937
+ publicHealthCheck: opts.publicHealthCheck,
9938
+ outboxDb,
9939
+ inboxDb,
9940
+ bus,
9941
+ broker,
9942
+ onPendingInserted: () => drain?.wake()
9943
+ });
9944
+ try {
9945
+ await ipc2.ready;
9946
+ } catch (err) {
9947
+ process.stderr.write(`ipc listen failed: ${String(err)}
9948
+ `);
9949
+ releaseSingletonLock();
9950
+ return 1;
9951
+ }
9952
+ process.stdout.write(JSON.stringify({
9953
+ msg: "daemon_started",
9954
+ pid: process.pid,
9955
+ sock: DAEMON_PATHS.SOCK_FILE,
9956
+ tcp: tcpEnabled ? `127.0.0.1:47823` : null,
9957
+ mesh: mesh.slug,
9958
+ ts: new Date().toISOString()
9959
+ }) + `
9960
+ `);
9961
+ let shuttingDown = false;
9962
+ const shutdown = async (sig) => {
9963
+ if (shuttingDown)
9964
+ return;
9965
+ shuttingDown = true;
9966
+ process.stdout.write(JSON.stringify({ msg: "daemon_shutdown", signal: sig, ts: new Date().toISOString() }) + `
9967
+ `);
9968
+ if (drain)
9969
+ await drain.close();
9970
+ await broker.close();
9971
+ await ipc2.close();
9972
+ try {
9973
+ outboxDb.close();
9974
+ } catch {}
9975
+ try {
9976
+ inboxDb.close();
9977
+ } catch {}
9978
+ releaseSingletonLock();
9979
+ process.exit(0);
9980
+ };
9981
+ process.on("SIGINT", () => shutdown("SIGINT"));
9982
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
9983
+ return new Promise(() => {});
9984
+ }
9985
+ var init_run = __esm(() => {
9986
+ init_paths2();
9987
+ init_lock();
9988
+ init_local_token();
9989
+ init_server();
9990
+ init_broker();
9991
+ init_drain();
9992
+ init_inbound();
9993
+ init_identity();
9994
+ init_facade();
9995
+ });
9996
+
9997
+ // src/daemon/service-install.ts
9998
+ var exports_service_install = {};
9999
+ __export(exports_service_install, {
10000
+ uninstallService: () => uninstallService,
10001
+ readInstalledUnit: () => readInstalledUnit,
10002
+ installService: () => installService,
10003
+ detectPlatform: () => detectPlatform
10004
+ });
10005
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7, writeFileSync as writeFileSync9, unlinkSync as unlinkSync4, readFileSync as readFileSync9 } from "node:fs";
10006
+ import { execSync as execSync2 } from "node:child_process";
10007
+ import { homedir as homedir6 } from "node:os";
10008
+ import { join as join8, dirname as dirname5 } from "node:path";
10009
+ function detectPlatform() {
10010
+ if (process.platform === "darwin")
10011
+ return "darwin";
10012
+ if (process.platform === "linux")
10013
+ return "linux";
10014
+ return null;
10015
+ }
10016
+ function isCi() {
10017
+ return !!(process.env.CI || process.env.GITHUB_ACTIONS || process.env.GITLAB_CI || process.env.BUILDKITE || process.env.CIRCLECI || process.env.JENKINS_URL);
10018
+ }
10019
+ function installService(args) {
10020
+ const platform5 = detectPlatform();
10021
+ if (!platform5)
10022
+ throw new Error(`unsupported platform: ${process.platform}`);
10023
+ if (isCi() && !args.allowCi) {
10024
+ throw new Error("Refusing to install persistent service in CI; pass --allow-ci-persistent to override.");
10025
+ }
10026
+ if (!existsSync12(args.binaryPath)) {
10027
+ throw new Error(`binary not found at ${args.binaryPath}`);
10028
+ }
10029
+ mkdirSync7(DAEMON_PATHS.DAEMON_DIR, { recursive: true, mode: 448 });
10030
+ if (platform5 === "darwin")
10031
+ return installDarwin(args);
10032
+ return installLinux(args);
10033
+ }
10034
+ function uninstallService() {
10035
+ const platform5 = detectPlatform();
10036
+ const removed = [];
10037
+ if (platform5 === "darwin") {
10038
+ const p = darwinPlistPath();
10039
+ try {
10040
+ execSync2(`launchctl bootout gui/$(id -u)/${SERVICE_LABEL}`, { stdio: "ignore" });
10041
+ } catch {}
10042
+ if (existsSync12(p)) {
10043
+ unlinkSync4(p);
10044
+ removed.push(p);
10045
+ }
10046
+ } else if (platform5 === "linux") {
10047
+ const p = linuxUnitPath();
10048
+ try {
10049
+ execSync2(`systemctl --user disable --now ${SYSTEMD_UNIT}`, { stdio: "ignore" });
10050
+ } catch {}
10051
+ if (existsSync12(p)) {
10052
+ unlinkSync4(p);
10053
+ removed.push(p);
10054
+ }
10055
+ }
10056
+ return { platform: platform5, removed };
10057
+ }
10058
+ function darwinPlistPath() {
10059
+ return join8(homedir6(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
10060
+ }
10061
+ function installDarwin(args) {
10062
+ const plist = darwinPlistPath();
10063
+ mkdirSync7(dirname5(plist), { recursive: true });
10064
+ const log2 = DAEMON_PATHS.LOG_FILE;
10065
+ const meshArgs = [
10066
+ "<string>daemon</string>",
10067
+ "<string>up</string>",
10068
+ "<string>--mesh</string>",
10069
+ `<string>${escapeXml(args.meshSlug)}</string>`,
10070
+ ...args.displayName ? ["<string>--name</string>", `<string>${escapeXml(args.displayName)}</string>`] : []
10071
+ ].join(`
10072
+ `);
10073
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
10074
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
10075
+ <plist version="1.0">
10076
+ <dict>
10077
+ <key>Label</key>
10078
+ <string>${SERVICE_LABEL}</string>
10079
+ <key>ProgramArguments</key>
10080
+ <array>
10081
+ <string>${escapeXml(args.binaryPath)}</string>
10082
+ ${meshArgs}
10083
+ </array>
10084
+ <key>RunAtLoad</key>
10085
+ <true/>
10086
+ <key>KeepAlive</key>
10087
+ <true/>
10088
+ <key>StandardOutPath</key>
10089
+ <string>${escapeXml(log2)}</string>
10090
+ <key>StandardErrorPath</key>
10091
+ <string>${escapeXml(log2)}</string>
10092
+ <key>WorkingDirectory</key>
10093
+ <string>${escapeXml(homedir6())}</string>
10094
+ <key>EnvironmentVariables</key>
10095
+ <dict>
10096
+ <key>HOME</key>
10097
+ <string>${escapeXml(homedir6())}</string>
10098
+ <key>PATH</key>
10099
+ <string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
10100
+ </dict>
10101
+ </dict>
10102
+ </plist>
10103
+ `;
10104
+ writeFileSync9(plist, xml, { mode: 420 });
10105
+ return {
10106
+ platform: "darwin",
10107
+ unitPath: plist,
10108
+ bootCommand: `launchctl bootstrap gui/$(id -u) ${shellQuote(plist)}`
10109
+ };
10110
+ }
10111
+ function linuxUnitPath() {
10112
+ return join8(homedir6(), ".config", "systemd", "user", SYSTEMD_UNIT);
10113
+ }
10114
+ function installLinux(args) {
10115
+ const unit = linuxUnitPath();
10116
+ mkdirSync7(dirname5(unit), { recursive: true });
10117
+ const execArgs = [
10118
+ "daemon",
10119
+ "up",
10120
+ "--mesh",
10121
+ args.meshSlug,
10122
+ ...args.displayName ? ["--name", args.displayName] : []
10123
+ ].map(shellQuote).join(" ");
10124
+ const content = `[Unit]
10125
+ Description=claudemesh daemon (peer mesh runtime)
10126
+ After=network-online.target
10127
+ Wants=network-online.target
10128
+
10129
+ [Service]
10130
+ Type=simple
10131
+ ExecStart=${shellQuote(args.binaryPath)} ${execArgs}
10132
+ Restart=always
10133
+ RestartSec=3
10134
+ StandardOutput=append:${DAEMON_PATHS.LOG_FILE}
10135
+ StandardError=append:${DAEMON_PATHS.LOG_FILE}
10136
+ Environment=PATH=/usr/local/bin:/usr/bin:/bin
10137
+
10138
+ [Install]
10139
+ WantedBy=default.target
10140
+ `;
10141
+ writeFileSync9(unit, content, { mode: 420 });
10142
+ return {
10143
+ platform: "linux",
10144
+ unitPath: unit,
10145
+ bootCommand: `systemctl --user daemon-reload && systemctl --user enable --now ${SYSTEMD_UNIT}`
10146
+ };
10147
+ }
10148
+ function escapeXml(s) {
10149
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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
+ return printDaemonUsage();
10183
+ case "up":
10184
+ case "start":
10185
+ return runDaemon({
10186
+ tcpEnabled: !opts.noTcp,
10187
+ publicHealthCheck: opts.publicHealth,
10188
+ mesh: opts.mesh,
10189
+ displayName: opts.displayName
10190
+ });
10191
+ case "help":
10192
+ case "--help":
10193
+ case "-h":
10194
+ return printDaemonUsage();
10195
+ case "status":
10196
+ return runStatus(opts);
10197
+ case "version":
10198
+ return runVersion(opts);
10199
+ case "down":
10200
+ case "stop":
10201
+ return runStop(opts);
10202
+ case "accept-host":
10203
+ return runAcceptHost(opts);
10204
+ case "outbox":
10205
+ return runOutbox(rest, opts);
10206
+ case "install-service":
10207
+ return runInstallService(opts);
10208
+ case "uninstall-service":
10209
+ return runUninstallService(opts);
10210
+ default:
10211
+ process.stderr.write(`unknown daemon subcommand: ${sub}
10212
+
10213
+ `);
10214
+ printDaemonUsage(process.stderr);
10215
+ return 2;
10216
+ }
10217
+ }
10218
+ function printDaemonUsage(stream = process.stdout) {
10219
+ stream.write(`claudemesh daemon — long-lived peer mesh runtime (v0.9.0)
10220
+
10221
+ USAGE
10222
+ claudemesh daemon <command> [options]
10223
+
10224
+ COMMANDS
10225
+ up | start start the daemon in the foreground
10226
+ status show running pid + IPC health
10227
+ version ipc + schema version of the running daemon
10228
+ down | stop stop the running daemon (SIGTERM, then wait)
10229
+ accept-host pin the current host fingerprint
10230
+ outbox list list local outbox rows (newest first)
10231
+ outbox requeue <id> re-enqueue an aborted / dead outbox row
10232
+ install-service --mesh <s> write launchd (macOS) / systemd-user (Linux) unit
10233
+ uninstall-service remove the platform service unit
10234
+
10235
+ OPTIONS
10236
+ --mesh <slug> attach to / target this mesh
10237
+ --name <displayName> override CLAUDEMESH_DISPLAY_NAME
10238
+ --no-tcp disable the loopback TCP listener (UDS only)
10239
+ --public-health expose /v1/health unauthenticated on TCP
10240
+ --json machine-readable output where supported
10241
+
10242
+ OUTBOX FLAGS (for 'daemon outbox list')
10243
+ --pending --inflight --done --failed --aborted filter by status
10244
+
10245
+ OUTBOX FLAGS (for 'daemon outbox requeue')
10246
+ --new-client-id <id> mint the new row with this client_message_id
10247
+
10248
+ See ${"https://claudemesh.com/docs"} for the full daemon spec.
10249
+ `);
10250
+ return 0;
10251
+ }
10252
+ async function runOutbox(rest, opts) {
10253
+ const sub = rest[0];
10254
+ switch (sub) {
10255
+ case undefined:
10256
+ case "list": {
10257
+ const status = opts.outboxStatus;
10258
+ const path2 = `/v1/outbox${status ? `?status=${status}` : ""}`;
10259
+ try {
10260
+ const res = await ipc({ path: path2 });
10261
+ if (opts.json) {
10262
+ process.stdout.write(JSON.stringify(res.body) + `
10263
+ `);
10264
+ return 0;
10265
+ }
10266
+ if (!res.body.items?.length) {
10267
+ process.stdout.write(`(empty)
10268
+ `);
10269
+ return 0;
10270
+ }
10271
+ for (const r of res.body.items) {
10272
+ const tag = r.status.padEnd(8);
10273
+ const bm = r.broker_message_id ? ` → ${r.broker_message_id}` : "";
10274
+ const err = r.last_error ? ` last_error="${r.last_error.slice(0, 60)}"` : "";
10275
+ process.stdout.write(`${tag} ${r.id} cid=${r.client_message_id} attempts=${r.attempts}${bm}${err}
10276
+ `);
10277
+ }
10278
+ return 0;
10279
+ } catch (err) {
10280
+ process.stderr.write(`daemon unreachable: ${String(err)}
10281
+ `);
10282
+ return 1;
10283
+ }
10284
+ }
10285
+ case "requeue": {
10286
+ const id = rest[1];
10287
+ if (!id) {
10288
+ process.stderr.write(`usage: claudemesh daemon outbox requeue <id> [--new-client-id <id>]
10289
+ `);
10290
+ return 2;
8007
10291
  }
8008
- render.section(`reminders (${scheduled.length})`);
8009
- for (const m of scheduled) {
8010
- const when = new Date(m.deliverAt).toLocaleString();
8011
- const to = m.to === client.getSessionPubkey() ? dim("(self)") : m.to;
8012
- process.stdout.write(` ${bold(m.id.slice(0, 8))} ${dim("")} ${to} ${dim("at")} ${when}
10292
+ const newClientMessageId = opts.newClientId;
10293
+ try {
10294
+ const res = await ipc({
10295
+ method: "POST",
10296
+ path: "/v1/outbox/requeue",
10297
+ body: { id, new_client_message_id: newClientMessageId }
10298
+ });
10299
+ if (res.status === 200) {
10300
+ if (opts.json)
10301
+ process.stdout.write(JSON.stringify(res.body) + `
8013
10302
  `);
8014
- process.stdout.write(` ${dim(m.message.slice(0, 80))}
8015
-
10303
+ else
10304
+ 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})
10305
+ `);
10306
+ return 0;
10307
+ }
10308
+ process.stderr.write(`requeue failed (${res.status}): ${res.body.error ?? "unknown"}
10309
+ `);
10310
+ return 1;
10311
+ } catch (err) {
10312
+ process.stderr.write(`daemon unreachable: ${String(err)}
8016
10313
  `);
10314
+ return 1;
8017
10315
  }
8018
- });
8019
- return;
8020
- }
8021
- if (action === "cancel") {
8022
- const id = positional[1];
8023
- if (!id) {
8024
- render.err("Usage: claudemesh remind cancel <id>");
8025
- process.exit(1);
8026
10316
  }
8027
- await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
8028
- const ok = await client.cancelScheduled(id);
8029
- if (ok)
8030
- render.ok(`cancelled ${bold(id.slice(0, 8))}`);
8031
- else {
8032
- render.err(`not found or already fired: ${id}`);
8033
- process.exit(1);
8034
- }
8035
- });
8036
- return;
10317
+ default:
10318
+ process.stderr.write(`unknown outbox subcommand: ${sub}
10319
+ `);
10320
+ process.stderr.write(`usage: claudemesh daemon outbox [list|requeue <id>]
10321
+ `);
10322
+ return 2;
8037
10323
  }
8038
- const message = action ?? positional.join(" ");
8039
- if (!message) {
8040
- render.err("Usage: claudemesh remind <message> --in <duration>");
8041
- render.info(dim(" claudemesh remind <message> --at <time>"));
8042
- render.info(dim(' claudemesh remind <message> --cron "0 */2 * * *"'));
8043
- render.info(dim(" claudemesh remind list"));
8044
- render.info(dim(" claudemesh remind cancel <id>"));
8045
- process.exit(1);
10324
+ }
10325
+ async function runInstallService(opts) {
10326
+ const { installService: installService2, detectPlatform: detectPlatform2 } = await Promise.resolve().then(() => (init_service_install(), exports_service_install));
10327
+ const platform5 = detectPlatform2();
10328
+ if (!platform5) {
10329
+ process.stderr.write(`unsupported platform: ${process.platform}
10330
+ `);
10331
+ return 2;
8046
10332
  }
8047
- const isCron = !!flags.cron;
8048
- const deliverAt = isCron ? 0 : parseDeliverAt(flags);
8049
- if (!isCron && deliverAt === null) {
8050
- render.err("Specify when", 'use --in <duration> (e.g. "2h", "30m"), --at <time> (e.g. "15:00"), or --cron <expression>');
8051
- process.exit(1);
10333
+ if (!opts.mesh) {
10334
+ process.stderr.write(`pass --mesh <slug> so the service knows which mesh to attach to
10335
+ `);
10336
+ return 2;
8052
10337
  }
8053
- await withMesh({ meshSlug: flags.mesh ?? null }, async (client) => {
8054
- let targetSpec;
8055
- if (flags.to && flags.to !== "self") {
8056
- if (flags.to.startsWith("@") || flags.to === "*" || /^[0-9a-f]{64}$/i.test(flags.to)) {
8057
- targetSpec = flags.to;
8058
- } else {
8059
- const peers = await client.listPeers();
8060
- const match = peers.find((p) => p.displayName.toLowerCase() === flags.to.toLowerCase());
8061
- if (!match) {
8062
- render.err(`Peer "${flags.to}" not found`, `online: ${peers.map((p) => p.displayName).join(", ") || "(none)"}`);
8063
- process.exit(1);
8064
- }
8065
- targetSpec = match.pubkey;
8066
- }
8067
- } else {
8068
- targetSpec = client.getSessionPubkey() ?? "*";
8069
- }
8070
- const result = await client.scheduleMessage(targetSpec, message, deliverAt ?? 0, false, flags.cron);
8071
- if (!result) {
8072
- render.err("Broker did not acknowledge — check connection");
8073
- process.exit(1);
8074
- }
8075
- if (flags.json) {
8076
- console.log(JSON.stringify(result));
8077
- return;
10338
+ let binary = process.argv[1] ?? "";
10339
+ if (!binary || /\.ts$/.test(binary) || /node_modules|src\/entrypoints/.test(binary)) {
10340
+ try {
10341
+ const { execSync: execSync3 } = await import("node:child_process");
10342
+ binary = execSync3("which claudemesh", { encoding: "utf8" }).trim();
10343
+ } catch {
10344
+ process.stderr.write(`couldn't resolve a 'claudemesh' binary on PATH; install via npm/homebrew first
10345
+ `);
10346
+ return 1;
8078
10347
  }
8079
- const toLabel = !flags.to || flags.to === "self" ? "yourself" : flags.to;
8080
- if (isCron) {
8081
- const nextFire = new Date(result.deliverAt).toLocaleString();
8082
- render.ok(`recurring reminder set`, `${result.scheduledId.slice(0, 8)} · ${clay(message)} → ${toLabel} · cron ${flags.cron} · next ${nextFire}`);
10348
+ }
10349
+ try {
10350
+ const r = installService2({
10351
+ binaryPath: binary,
10352
+ meshSlug: opts.mesh,
10353
+ displayName: opts.displayName
10354
+ });
10355
+ if (opts.json) {
10356
+ process.stdout.write(JSON.stringify({ ok: true, ...r }) + `
10357
+ `);
8083
10358
  } else {
8084
- const when = new Date(result.deliverAt).toLocaleString();
8085
- render.ok(`reminder set`, `${result.scheduledId.slice(0, 8)} · ${clay(message)} → ${toLabel} at ${when}`);
10359
+ process.stdout.write(`installed ${r.platform} service unit: ${r.unitPath}
10360
+ `);
10361
+ process.stdout.write(`bring it up now: ${r.bootCommand}
10362
+ `);
8086
10363
  }
8087
- });
10364
+ return 0;
10365
+ } catch (err) {
10366
+ process.stderr.write(`install-service failed: ${String(err)}
10367
+ `);
10368
+ return 1;
10369
+ }
8088
10370
  }
8089
- var init_remind = __esm(() => {
8090
- init_connect();
8091
- init_render();
8092
- init_styles();
8093
- });
8094
-
8095
- // src/commands/register.ts
8096
- var exports_register = {};
8097
- __export(exports_register, {
8098
- register: () => register2
8099
- });
8100
- async function register2() {
8101
- return login();
10371
+ async function runUninstallService(opts) {
10372
+ const { uninstallService: uninstallService2 } = await Promise.resolve().then(() => (init_service_install(), exports_service_install));
10373
+ const r = uninstallService2();
10374
+ if (opts.json)
10375
+ process.stdout.write(JSON.stringify(r) + `
10376
+ `);
10377
+ else if (r.removed.length === 0)
10378
+ process.stdout.write(`no service unit installed
10379
+ `);
10380
+ else
10381
+ process.stdout.write(`removed: ${r.removed.join(", ")}
10382
+ `);
10383
+ return 0;
8102
10384
  }
8103
- var init_register = __esm(() => {
8104
- init_login();
8105
- });
8106
-
8107
- // src/commands/logout.ts
8108
- var exports_logout = {};
8109
- __export(exports_logout, {
8110
- logout: () => logout2
8111
- });
8112
- async function logout2() {
10385
+ async function runAcceptHost(opts) {
10386
+ const { acceptCurrentHost: acceptCurrentHost2 } = await Promise.resolve().then(() => (init_identity(), exports_identity));
10387
+ const fp = acceptCurrentHost2();
10388
+ if (opts.json)
10389
+ process.stdout.write(JSON.stringify({ ok: true, fingerprint_prefix: fp.fingerprint.slice(0, 16) }) + `
10390
+ `);
10391
+ else
10392
+ process.stdout.write(`host fingerprint accepted: ${fp.fingerprint.slice(0, 16)}…
10393
+ `);
10394
+ return 0;
10395
+ }
10396
+ async function runStatus(opts) {
10397
+ const pid = readRunningPid();
10398
+ if (!pid) {
10399
+ if (opts.json)
10400
+ process.stdout.write(JSON.stringify({ running: false }) + `
10401
+ `);
10402
+ else
10403
+ process.stdout.write(`daemon: not running
10404
+ `);
10405
+ return 1;
10406
+ }
8113
10407
  try {
8114
- const { revoked } = await logout();
8115
- if (revoked) {
8116
- console.log(` ${green(icons.check)} Revoked session on claudemesh.com`);
10408
+ const res = await ipc({ path: "/v1/health" });
10409
+ if (opts.json) {
10410
+ process.stdout.write(JSON.stringify({ running: true, pid, health: res.body }) + `
10411
+ `);
8117
10412
  } else {
8118
- console.log(` ${yellow(icons.warn)} Could not revoke session on claudemesh.com.`);
8119
- console.log(` Revoke manually at https://claudemesh.com/dashboard/settings/sessions`);
10413
+ process.stdout.write(`daemon: running (pid ${pid})
10414
+ `);
10415
+ process.stdout.write(`socket: ${DAEMON_PATHS.SOCK_FILE}
10416
+ `);
8120
10417
  }
8121
- console.log(` ${green(icons.check)} Removed local credentials.`);
8122
- return EXIT.SUCCESS;
10418
+ return 0;
8123
10419
  } catch (err) {
8124
- console.error(` ${icons.cross} Logout failed: ${err instanceof Error ? err.message : err}`);
8125
- return EXIT.AUTH_FAILED;
10420
+ if (opts.json)
10421
+ process.stdout.write(JSON.stringify({ running: true, pid, ipc_error: String(err) }) + `
10422
+ `);
10423
+ else
10424
+ process.stdout.write(`daemon: pid ${pid} alive but IPC unreachable (${String(err)})
10425
+ `);
10426
+ return 1;
8126
10427
  }
8127
10428
  }
8128
- var init_logout = __esm(() => {
8129
- init_facade6();
8130
- init_styles();
8131
- init_exit_codes();
8132
- });
8133
-
8134
- // src/commands/whoami.ts
8135
- var exports_whoami = {};
8136
- __export(exports_whoami, {
8137
- whoami: () => whoami
8138
- });
8139
- async function whoami(opts) {
8140
- const result = await whoAmI();
8141
- if (opts.json) {
8142
- console.log(JSON.stringify({ schema_version: "1.0", ...result }, null, 2));
8143
- return result.signed_in || result.local ? EXIT.SUCCESS : EXIT.AUTH_FAILED;
10429
+ async function runVersion(opts) {
10430
+ try {
10431
+ const res = await ipc({ path: "/v1/version" });
10432
+ if (opts.json)
10433
+ process.stdout.write(JSON.stringify(res.body) + `
10434
+ `);
10435
+ else {
10436
+ const v = res.body;
10437
+ process.stdout.write(`daemon ${v.daemon_version ?? "unknown"} (ipc ${v.ipc_api ?? "?"}, schema ${v.schema_version ?? "?"})
10438
+ `);
10439
+ }
10440
+ return 0;
10441
+ } catch (err) {
10442
+ if (err instanceof IpcError) {
10443
+ process.stderr.write(`${err.message}
10444
+ `);
10445
+ return err.status === 401 ? 3 : 1;
10446
+ }
10447
+ process.stderr.write(`daemon unreachable: ${String(err)}
10448
+ `);
10449
+ return 1;
8144
10450
  }
8145
- if (!result.signed_in && !result.local) {
8146
- render.err("Not signed in", "Run `claudemesh login` to sign in or `claudemesh <invite>` to join.");
8147
- return EXIT.AUTH_FAILED;
10451
+ }
10452
+ async function runStop(opts) {
10453
+ const pid = readRunningPid();
10454
+ if (!pid) {
10455
+ if (opts.json)
10456
+ process.stdout.write(JSON.stringify({ stopped: false, reason: "not_running" }) + `
10457
+ `);
10458
+ else
10459
+ process.stdout.write(`daemon: not running
10460
+ `);
10461
+ return 0;
8148
10462
  }
8149
- render.section("whoami");
8150
- if (result.signed_in) {
8151
- render.kv([
8152
- ["user", `${bold(result.user.display_name)} ${dim(`(${result.user.email})`)}`],
8153
- ["token", `${result.token_source} ${dim("(~/.claudemesh/auth.json)")}`],
8154
- ...result.meshes ? [["meshes", `${result.meshes.owned} owned · ${result.meshes.guest} guest`]] : []
8155
- ]);
8156
- } else {
8157
- render.kv([
8158
- ["web", dim("not signed in · run `claudemesh login` for account features")]
8159
- ]);
10463
+ try {
10464
+ process.kill(pid, "SIGTERM");
10465
+ } catch (err) {
10466
+ process.stderr.write(`failed to signal pid ${pid}: ${String(err)}
10467
+ `);
10468
+ return 1;
8160
10469
  }
8161
- if (result.local) {
8162
- render.blank();
8163
- render.kv([
8164
- ["local", `${result.local.meshes.length} mesh${result.local.meshes.length === 1 ? "" : "es"} · ${dim(result.local.config_path)}`]
8165
- ]);
8166
- for (const m of result.local.meshes) {
8167
- console.log(` ${clay("●")} ${bold(m.slug)} ${dim(`member ${m.member_id.slice(0, 8)}… pk ${m.pubkey_prefix}…`)}`);
10470
+ for (let i = 0;i < 50; i++) {
10471
+ await new Promise((r) => setTimeout(r, 100));
10472
+ if (!readRunningPid()) {
10473
+ if (opts.json)
10474
+ process.stdout.write(JSON.stringify({ stopped: true, pid }) + `
10475
+ `);
10476
+ else
10477
+ process.stdout.write(`daemon: stopped (was pid ${pid})
10478
+ `);
10479
+ return 0;
8168
10480
  }
8169
10481
  }
8170
- render.blank();
8171
- return EXIT.SUCCESS;
10482
+ if (opts.json)
10483
+ process.stdout.write(JSON.stringify({ stopped: false, pid, reason: "shutdown_timeout" }) + `
10484
+ `);
10485
+ else
10486
+ process.stdout.write(`daemon: signaled but did not exit within 5s (pid ${pid})
10487
+ `);
10488
+ return 1;
8172
10489
  }
8173
- var init_whoami = __esm(() => {
8174
- init_facade6();
8175
- init_render();
8176
- init_styles();
8177
- init_exit_codes();
10490
+ var init_daemon = __esm(() => {
10491
+ init_run();
10492
+ init_client4();
10493
+ init_lock();
10494
+ init_paths2();
8178
10495
  });
8179
10496
 
8180
10497
  // src/commands/install.ts
@@ -8184,21 +10501,21 @@ __export(exports_install, {
8184
10501
  runInstall: () => runInstall
8185
10502
  });
8186
10503
  import {
8187
- chmodSync as chmodSync3,
10504
+ chmodSync as chmodSync4,
8188
10505
  copyFileSync,
8189
- existsSync as existsSync7,
8190
- mkdirSync as mkdirSync4,
8191
- readFileSync as readFileSync5,
8192
- writeFileSync as writeFileSync6
10506
+ existsSync as existsSync13,
10507
+ mkdirSync as mkdirSync8,
10508
+ readFileSync as readFileSync10,
10509
+ writeFileSync as writeFileSync10
8193
10510
  } from "node:fs";
8194
- import { homedir as homedir6, platform as platform5 } from "node:os";
8195
- import { dirname as dirname3, join as join6, resolve } from "node:path";
10511
+ import { homedir as homedir7, platform as platform5 } from "node:os";
10512
+ import { dirname as dirname6, join as join9, resolve } from "node:path";
8196
10513
  import { fileURLToPath } from "node:url";
8197
10514
  import { spawnSync as spawnSync3 } from "node:child_process";
8198
10515
  function readClaudeConfig() {
8199
- if (!existsSync7(CLAUDE_CONFIG))
10516
+ if (!existsSync13(CLAUDE_CONFIG))
8200
10517
  return {};
8201
- const text = readFileSync5(CLAUDE_CONFIG, "utf-8").trim();
10518
+ const text = readFileSync10(CLAUDE_CONFIG, "utf-8").trim();
8202
10519
  if (!text)
8203
10520
  return {};
8204
10521
  try {
@@ -8208,12 +10525,12 @@ function readClaudeConfig() {
8208
10525
  }
8209
10526
  }
8210
10527
  function backupClaudeConfig() {
8211
- if (!existsSync7(CLAUDE_CONFIG))
10528
+ if (!existsSync13(CLAUDE_CONFIG))
8212
10529
  return;
8213
- const backupDir = join6(dirname3(CLAUDE_CONFIG), ".claude", "backups");
8214
- mkdirSync4(backupDir, { recursive: true });
10530
+ const backupDir = join9(dirname6(CLAUDE_CONFIG), ".claude", "backups");
10531
+ mkdirSync8(backupDir, { recursive: true });
8215
10532
  const ts = Date.now();
8216
- const dest = join6(backupDir, `.claude.json.pre-claudemesh.${ts}`);
10533
+ const dest = join9(backupDir, `.claude.json.pre-claudemesh.${ts}`);
8217
10534
  copyFileSync(CLAUDE_CONFIG, dest);
8218
10535
  }
8219
10536
  function patchMcpServer(entry) {
@@ -8237,7 +10554,7 @@ function patchMcpServer(entry) {
8237
10554
  return action;
8238
10555
  }
8239
10556
  function removeMcpServer() {
8240
- if (!existsSync7(CLAUDE_CONFIG))
10557
+ if (!existsSync13(CLAUDE_CONFIG))
8241
10558
  return false;
8242
10559
  backupClaudeConfig();
8243
10560
  const cfg = readClaudeConfig();
@@ -8250,11 +10567,11 @@ function removeMcpServer() {
8250
10567
  return true;
8251
10568
  }
8252
10569
  function flushClaudeConfig(obj) {
8253
- mkdirSync4(dirname3(CLAUDE_CONFIG), { recursive: true });
8254
- writeFileSync6(CLAUDE_CONFIG, JSON.stringify(obj, null, 2) + `
10570
+ mkdirSync8(dirname6(CLAUDE_CONFIG), { recursive: true });
10571
+ writeFileSync10(CLAUDE_CONFIG, JSON.stringify(obj, null, 2) + `
8255
10572
  `, "utf-8");
8256
10573
  try {
8257
- chmodSync3(CLAUDE_CONFIG, 384);
10574
+ chmodSync4(CLAUDE_CONFIG, 384);
8258
10575
  } catch {}
8259
10576
  }
8260
10577
  function bunAvailable() {
@@ -8268,13 +10585,13 @@ function resolveEntry() {
8268
10585
  const here = fileURLToPath(import.meta.url);
8269
10586
  if (isBundledFile(here))
8270
10587
  return here;
8271
- return resolve(dirname3(here), "..", "index.ts");
10588
+ return resolve(dirname6(here), "..", "index.ts");
8272
10589
  }
8273
10590
  function resolveBundledSkillsDir() {
8274
10591
  const here = fileURLToPath(import.meta.url);
8275
- const pkgRoot = resolve(dirname3(here), "..", "..");
8276
- const skillsDir = join6(pkgRoot, "skills");
8277
- if (existsSync7(skillsDir))
10592
+ const pkgRoot = resolve(dirname6(here), "..", "..");
10593
+ const skillsDir = join9(pkgRoot, "skills");
10594
+ if (existsSync13(skillsDir))
8278
10595
  return skillsDir;
8279
10596
  return null;
8280
10597
  }
@@ -8287,13 +10604,13 @@ function installSkills() {
8287
10604
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
8288
10605
  if (!entry.isDirectory())
8289
10606
  continue;
8290
- const srcDir = join6(src, entry.name);
8291
- const dstDir = join6(CLAUDE_SKILLS_ROOT, entry.name);
8292
- mkdirSync4(dstDir, { recursive: true });
10607
+ const srcDir = join9(src, entry.name);
10608
+ const dstDir = join9(CLAUDE_SKILLS_ROOT, entry.name);
10609
+ mkdirSync8(dstDir, { recursive: true });
8293
10610
  for (const file of fs.readdirSync(srcDir, { withFileTypes: true })) {
8294
10611
  if (!file.isFile())
8295
10612
  continue;
8296
- copyFileSync(join6(srcDir, file.name), join6(dstDir, file.name));
10613
+ copyFileSync(join9(srcDir, file.name), join9(dstDir, file.name));
8297
10614
  }
8298
10615
  installed.push(entry.name);
8299
10616
  }
@@ -8308,8 +10625,8 @@ function uninstallSkills() {
8308
10625
  for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
8309
10626
  if (!entry.isDirectory())
8310
10627
  continue;
8311
- const dstDir = join6(CLAUDE_SKILLS_ROOT, entry.name);
8312
- if (existsSync7(dstDir)) {
10628
+ const dstDir = join9(CLAUDE_SKILLS_ROOT, entry.name);
10629
+ if (existsSync13(dstDir)) {
8313
10630
  try {
8314
10631
  fs.rmSync(dstDir, { recursive: true, force: true });
8315
10632
  removed.push(entry.name);
@@ -8334,9 +10651,9 @@ function entriesEqual(a, b) {
8334
10651
  return a.command === b.command && JSON.stringify(a.args ?? []) === JSON.stringify(b.args ?? []);
8335
10652
  }
8336
10653
  function readClaudeSettings() {
8337
- if (!existsSync7(CLAUDE_SETTINGS))
10654
+ if (!existsSync13(CLAUDE_SETTINGS))
8338
10655
  return {};
8339
- const text = readFileSync5(CLAUDE_SETTINGS, "utf-8").trim();
10656
+ const text = readFileSync10(CLAUDE_SETTINGS, "utf-8").trim();
8340
10657
  if (!text)
8341
10658
  return {};
8342
10659
  try {
@@ -8346,8 +10663,8 @@ function readClaudeSettings() {
8346
10663
  }
8347
10664
  }
8348
10665
  function writeClaudeSettings(obj) {
8349
- mkdirSync4(dirname3(CLAUDE_SETTINGS), { recursive: true });
8350
- writeFileSync6(CLAUDE_SETTINGS, JSON.stringify(obj, null, 2) + `
10666
+ mkdirSync8(dirname6(CLAUDE_SETTINGS), { recursive: true });
10667
+ writeFileSync10(CLAUDE_SETTINGS, JSON.stringify(obj, null, 2) + `
8351
10668
  `, "utf-8");
8352
10669
  }
8353
10670
  function installAllowedTools() {
@@ -8361,7 +10678,7 @@ function installAllowedTools() {
8361
10678
  return { added: toAdd, unchanged: CLAUDEMESH_TOOLS.length - toAdd.length };
8362
10679
  }
8363
10680
  function uninstallAllowedTools() {
8364
- if (!existsSync7(CLAUDE_SETTINGS))
10681
+ if (!existsSync13(CLAUDE_SETTINGS))
8365
10682
  return 0;
8366
10683
  const settings = readClaudeSettings();
8367
10684
  const existing = settings.allowedTools ?? [];
@@ -8396,7 +10713,7 @@ function installHooks() {
8396
10713
  return { added, unchanged };
8397
10714
  }
8398
10715
  function uninstallHooks() {
8399
- if (!existsSync7(CLAUDE_SETTINGS))
10716
+ if (!existsSync13(CLAUDE_SETTINGS))
8400
10717
  return 0;
8401
10718
  const settings = readClaudeSettings();
8402
10719
  const hooks = settings.hooks;
@@ -8445,7 +10762,7 @@ function runInstall(args = []) {
8445
10762
  render.err("`bun` is not on PATH.", "Install Bun first: https://bun.com");
8446
10763
  process.exit(1);
8447
10764
  }
8448
- if (!existsSync7(entry)) {
10765
+ if (!existsSync13(entry)) {
8449
10766
  render.err(`MCP entry not found at ${entry}`);
8450
10767
  process.exit(1);
8451
10768
  }
@@ -8496,7 +10813,7 @@ function runInstall(args = []) {
8496
10813
  const installed = installSkills();
8497
10814
  if (installed.length > 0) {
8498
10815
  render.ok(`Claude skill${installed.length === 1 ? "" : "s"} installed`, installed.join(", "));
8499
- render.info(dim(` ${join6(CLAUDE_SKILLS_ROOT, installed[0])}/SKILL.md`));
10816
+ render.info(dim(` ${join9(CLAUDE_SKILLS_ROOT, installed[0])}/SKILL.md`));
8500
10817
  }
8501
10818
  } catch (e) {
8502
10819
  render.warn(`skill install failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -8584,9 +10901,9 @@ var init_install = __esm(() => {
8584
10901
  init_facade();
8585
10902
  init_render();
8586
10903
  init_styles();
8587
- CLAUDE_CONFIG = join6(homedir6(), ".claude.json");
8588
- CLAUDE_SETTINGS = join6(homedir6(), ".claude", "settings.json");
8589
- CLAUDE_SKILLS_ROOT = join6(homedir6(), ".claude", "skills");
10904
+ CLAUDE_CONFIG = join9(homedir7(), ".claude.json");
10905
+ CLAUDE_SETTINGS = join9(homedir7(), ".claude", "settings.json");
10906
+ CLAUDE_SKILLS_ROOT = join9(homedir7(), ".claude", "skills");
8590
10907
  CLAUDEMESH_TOOLS = [
8591
10908
  "mcp__claudemesh__cancel_scheduled",
8592
10909
  "mcp__claudemesh__check_messages",
@@ -8641,35 +10958,35 @@ var exports_uninstall = {};
8641
10958
  __export(exports_uninstall, {
8642
10959
  uninstall: () => uninstall
8643
10960
  });
8644
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync7, existsSync as existsSync8, rmSync as rmSync2, readdirSync as readdirSync2 } from "node:fs";
8645
- import { join as join7, dirname as dirname4 } from "node:path";
8646
- import { homedir as homedir7 } from "node:os";
10961
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync11, existsSync as existsSync14, rmSync as rmSync2, readdirSync as readdirSync2 } from "node:fs";
10962
+ import { join as join10, dirname as dirname7 } from "node:path";
10963
+ import { homedir as homedir8 } from "node:os";
8647
10964
  import { fileURLToPath as fileURLToPath2 } from "node:url";
8648
10965
  function bundledSkillsDir() {
8649
10966
  const here = fileURLToPath2(import.meta.url);
8650
- const pkgRoot = join7(dirname4(here), "..", "..");
8651
- const skillsDir = join7(pkgRoot, "skills");
8652
- return existsSync8(skillsDir) ? skillsDir : null;
10967
+ const pkgRoot = join10(dirname7(here), "..", "..");
10968
+ const skillsDir = join10(pkgRoot, "skills");
10969
+ return existsSync14(skillsDir) ? skillsDir : null;
8653
10970
  }
8654
10971
  async function uninstall() {
8655
10972
  let removed = 0;
8656
- if (existsSync8(PATHS.CLAUDE_JSON)) {
10973
+ if (existsSync14(PATHS.CLAUDE_JSON)) {
8657
10974
  try {
8658
- const raw = readFileSync6(PATHS.CLAUDE_JSON, "utf-8");
10975
+ const raw = readFileSync11(PATHS.CLAUDE_JSON, "utf-8");
8659
10976
  const config = JSON.parse(raw);
8660
10977
  const servers = config.mcpServers;
8661
10978
  if (servers && "claudemesh" in servers) {
8662
10979
  delete servers.claudemesh;
8663
- writeFileSync7(PATHS.CLAUDE_JSON, JSON.stringify(config, null, 2) + `
10980
+ writeFileSync11(PATHS.CLAUDE_JSON, JSON.stringify(config, null, 2) + `
8664
10981
  `, "utf-8");
8665
10982
  render.ok("removed MCP server", dim("~/.claude.json"));
8666
10983
  removed++;
8667
10984
  }
8668
10985
  } catch {}
8669
10986
  }
8670
- if (existsSync8(PATHS.CLAUDE_SETTINGS)) {
10987
+ if (existsSync14(PATHS.CLAUDE_SETTINGS)) {
8671
10988
  try {
8672
- const raw = readFileSync6(PATHS.CLAUDE_SETTINGS, "utf-8");
10989
+ const raw = readFileSync11(PATHS.CLAUDE_SETTINGS, "utf-8");
8673
10990
  const config = JSON.parse(raw);
8674
10991
  const hooks = config.hooks;
8675
10992
  if (hooks) {
@@ -8690,7 +11007,7 @@ async function uninstall() {
8690
11007
  }
8691
11008
  }
8692
11009
  if (removedHooks > 0) {
8693
- writeFileSync7(PATHS.CLAUDE_SETTINGS, JSON.stringify(config, null, 2) + `
11010
+ writeFileSync11(PATHS.CLAUDE_SETTINGS, JSON.stringify(config, null, 2) + `
8694
11011
  `, "utf-8");
8695
11012
  render.ok(`removed ${removedHooks} claudemesh hook${removedHooks === 1 ? "" : "s"}`, dim("settings.json"));
8696
11013
  removed++;
@@ -8705,8 +11022,8 @@ async function uninstall() {
8705
11022
  for (const entry of readdirSync2(src, { withFileTypes: true })) {
8706
11023
  if (!entry.isDirectory())
8707
11024
  continue;
8708
- const dst = join7(CLAUDE_SKILLS_ROOT2, entry.name);
8709
- if (existsSync8(dst)) {
11025
+ const dst = join10(CLAUDE_SKILLS_ROOT2, entry.name);
11026
+ if (existsSync14(dst)) {
8710
11027
  try {
8711
11028
  rmSync2(dst, { recursive: true, force: true });
8712
11029
  removedSkills.push(entry.name);
@@ -8730,7 +11047,7 @@ var init_uninstall = __esm(() => {
8730
11047
  init_render();
8731
11048
  init_styles();
8732
11049
  init_exit_codes();
8733
- CLAUDE_SKILLS_ROOT2 = join7(homedir7(), ".claude", "skills");
11050
+ CLAUDE_SKILLS_ROOT2 = join10(homedir8(), ".claude", "skills");
8734
11051
  });
8735
11052
 
8736
11053
  // src/commands/doctor.ts
@@ -8738,9 +11055,9 @@ var exports_doctor = {};
8738
11055
  __export(exports_doctor, {
8739
11056
  runDoctor: () => runDoctor
8740
11057
  });
8741
- import { existsSync as existsSync9, readFileSync as readFileSync7, statSync as statSync2 } from "node:fs";
8742
- import { homedir as homedir8, platform as platform6 } from "node:os";
8743
- import { join as join8 } from "node:path";
11058
+ import { existsSync as existsSync15, readFileSync as readFileSync12, statSync as statSync2 } from "node:fs";
11059
+ import { homedir as homedir9, platform as platform6 } from "node:os";
11060
+ import { join as join11 } from "node:path";
8744
11061
  import { spawnSync as spawnSync4 } from "node:child_process";
8745
11062
  function checkNode() {
8746
11063
  const major = Number(process.versions.node.split(".")[0]);
@@ -8764,8 +11081,8 @@ function checkClaudeOnPath() {
8764
11081
  };
8765
11082
  }
8766
11083
  function checkMcpRegistered() {
8767
- const claudeConfig = join8(homedir8(), ".claude.json");
8768
- if (!existsSync9(claudeConfig)) {
11084
+ const claudeConfig = join11(homedir9(), ".claude.json");
11085
+ if (!existsSync15(claudeConfig)) {
8769
11086
  return {
8770
11087
  name: "claudemesh MCP registered in ~/.claude.json",
8771
11088
  pass: false,
@@ -8773,7 +11090,7 @@ function checkMcpRegistered() {
8773
11090
  };
8774
11091
  }
8775
11092
  try {
8776
- const cfg = JSON.parse(readFileSync7(claudeConfig, "utf-8"));
11093
+ const cfg = JSON.parse(readFileSync12(claudeConfig, "utf-8"));
8777
11094
  const registered = Boolean(cfg.mcpServers?.["claudemesh"]);
8778
11095
  return {
8779
11096
  name: "claudemesh MCP registered in ~/.claude.json",
@@ -8790,8 +11107,8 @@ function checkMcpRegistered() {
8790
11107
  }
8791
11108
  }
8792
11109
  function checkHooksRegistered() {
8793
- const settings = join8(homedir8(), ".claude", "settings.json");
8794
- if (!existsSync9(settings)) {
11110
+ const settings = join11(homedir9(), ".claude", "settings.json");
11111
+ if (!existsSync15(settings)) {
8795
11112
  return {
8796
11113
  name: "Status hooks registered in ~/.claude/settings.json",
8797
11114
  pass: false,
@@ -8799,7 +11116,7 @@ function checkHooksRegistered() {
8799
11116
  };
8800
11117
  }
8801
11118
  try {
8802
- const raw = readFileSync7(settings, "utf-8");
11119
+ const raw = readFileSync12(settings, "utf-8");
8803
11120
  const has = raw.includes("claudemesh hook ");
8804
11121
  return {
8805
11122
  name: "Status hooks registered in ~/.claude/settings.json",
@@ -8815,8 +11132,8 @@ function checkHooksRegistered() {
8815
11132
  }
8816
11133
  }
8817
11134
  function checkConfigFile() {
8818
- const path = getConfigPath();
8819
- if (!existsSync9(path)) {
11135
+ const path2 = getConfigPath();
11136
+ if (!existsSync15(path2)) {
8820
11137
  return {
8821
11138
  name: "~/.claudemesh/config.json exists and parses",
8822
11139
  pass: true,
@@ -8825,14 +11142,14 @@ function checkConfigFile() {
8825
11142
  }
8826
11143
  try {
8827
11144
  readConfig();
8828
- const st = statSync2(path);
11145
+ const st = statSync2(path2);
8829
11146
  const mode = (st.mode & 511).toString(8);
8830
11147
  const secure = platform6() === "win32" || mode === "600";
8831
11148
  return {
8832
11149
  name: "~/.claudemesh/config.json parses + chmod 0600",
8833
11150
  pass: secure,
8834
11151
  detail: platform6() === "win32" ? "chmod skipped on Windows" : `0${mode}`,
8835
- fix: secure ? undefined : `chmod 600 ${path}`
11152
+ fix: secure ? undefined : `chmod 600 ${path2}`
8836
11153
  };
8837
11154
  } catch (e) {
8838
11155
  return {
@@ -8888,8 +11205,8 @@ async function checkBrokerWs() {
8888
11205
  const wsUrl = URLS.BROKER;
8889
11206
  const start = Date.now();
8890
11207
  try {
8891
- const WebSocket2 = (await import("ws")).default;
8892
- const ws = new WebSocket2(wsUrl);
11208
+ const WebSocket3 = (await import("ws")).default;
11209
+ const ws = new WebSocket3(wsUrl);
8893
11210
  const result = await new Promise((resolve2) => {
8894
11211
  const timer = setTimeout(() => {
8895
11212
  try {
@@ -8997,14 +11314,14 @@ var init_doctor = __esm(() => {
8997
11314
  // src/commands/status.ts
8998
11315
  var exports_status = {};
8999
11316
  __export(exports_status, {
9000
- runStatus: () => runStatus
11317
+ runStatus: () => runStatus2
9001
11318
  });
9002
- import { statSync as statSync3, existsSync as existsSync10 } from "node:fs";
9003
- import WebSocket2 from "ws";
11319
+ import { statSync as statSync3, existsSync as existsSync16 } from "node:fs";
11320
+ import WebSocket3 from "ws";
9004
11321
  async function probeBroker(url, timeoutMs = 4000) {
9005
11322
  return new Promise((resolve2) => {
9006
11323
  const started = Date.now();
9007
- const ws = new WebSocket2(url);
11324
+ const ws = new WebSocket3(url);
9008
11325
  const timer = setTimeout(() => {
9009
11326
  try {
9010
11327
  ws.terminate();
@@ -9025,11 +11342,11 @@ async function probeBroker(url, timeoutMs = 4000) {
9025
11342
  });
9026
11343
  });
9027
11344
  }
9028
- async function runStatus() {
11345
+ async function runStatus2() {
9029
11346
  render.section(`status (v${VERSION})`);
9030
11347
  const configPath = getConfigPath();
9031
11348
  let configPermsNote = "missing";
9032
- if (existsSync10(configPath)) {
11349
+ if (existsSync16(configPath)) {
9033
11350
  const mode = (statSync3(configPath).mode & 511).toString(8).padStart(4, "0");
9034
11351
  configPermsNote = mode === "0600" ? `${mode}` : `${mode} — expected 0600`;
9035
11352
  }
@@ -9175,13 +11492,13 @@ var init_check_claude_binary = __esm(() => {
9175
11492
  });
9176
11493
 
9177
11494
  // src/services/health/check-mcp-registered.ts
9178
- import { existsSync as existsSync11, readFileSync as readFileSync8 } from "node:fs";
11495
+ import { existsSync as existsSync17, readFileSync as readFileSync13 } from "node:fs";
9179
11496
  function checkMcpRegistered2() {
9180
11497
  try {
9181
- if (!existsSync11(PATHS.CLAUDE_JSON)) {
11498
+ if (!existsSync17(PATHS.CLAUDE_JSON)) {
9182
11499
  return { name: "mcp-registered", ok: false, message: "~/.claude.json not found" };
9183
11500
  }
9184
- const raw = readFileSync8(PATHS.CLAUDE_JSON, "utf-8");
11501
+ const raw = readFileSync13(PATHS.CLAUDE_JSON, "utf-8");
9185
11502
  const config = JSON.parse(raw);
9186
11503
  if (config.mcpServers && "claudemesh" in config.mcpServers) {
9187
11504
  return { name: "mcp-registered", ok: true, message: "MCP server registered" };
@@ -9196,13 +11513,13 @@ var init_check_mcp_registered = __esm(() => {
9196
11513
  });
9197
11514
 
9198
11515
  // src/services/health/check-hooks-registered.ts
9199
- import { existsSync as existsSync12, readFileSync as readFileSync9 } from "node:fs";
11516
+ import { existsSync as existsSync18, readFileSync as readFileSync14 } from "node:fs";
9200
11517
  function checkHooksRegistered2() {
9201
11518
  try {
9202
- if (!existsSync12(PATHS.CLAUDE_SETTINGS)) {
11519
+ if (!existsSync18(PATHS.CLAUDE_SETTINGS)) {
9203
11520
  return { name: "hooks-registered", ok: false, message: "~/.claude/settings.json not found" };
9204
11521
  }
9205
- const raw = readFileSync9(PATHS.CLAUDE_SETTINGS, "utf-8");
11522
+ const raw = readFileSync14(PATHS.CLAUDE_SETTINGS, "utf-8");
9206
11523
  const config = JSON.parse(raw);
9207
11524
  if (config.hooks) {
9208
11525
  return { name: "hooks-registered", ok: true, message: "Hooks configured" };
@@ -9217,10 +11534,10 @@ var init_check_hooks_registered = __esm(() => {
9217
11534
  });
9218
11535
 
9219
11536
  // src/services/health/check-config-perms.ts
9220
- import { existsSync as existsSync13, statSync as statSync4 } from "node:fs";
11537
+ import { existsSync as existsSync19, statSync as statSync4 } from "node:fs";
9221
11538
  function checkConfigPerms() {
9222
11539
  const configFile = PATHS.CONFIG_FILE;
9223
- if (!existsSync13(configFile)) {
11540
+ if (!existsSync19(configFile)) {
9224
11541
  return { name: "config-perms", ok: true, message: "No config file yet (first run)" };
9225
11542
  }
9226
11543
  try {
@@ -9238,13 +11555,13 @@ var init_check_config_perms = __esm(() => {
9238
11555
  });
9239
11556
 
9240
11557
  // src/services/health/check-keypairs-valid.ts
9241
- import { existsSync as existsSync14, readFileSync as readFileSync10 } from "node:fs";
11558
+ import { existsSync as existsSync20, readFileSync as readFileSync15 } from "node:fs";
9242
11559
  function checkKeypairsValid() {
9243
- if (!existsSync14(PATHS.CONFIG_FILE)) {
11560
+ if (!existsSync20(PATHS.CONFIG_FILE)) {
9244
11561
  return { name: "keypairs-valid", ok: true, message: "No config (first run)" };
9245
11562
  }
9246
11563
  try {
9247
- const raw = readFileSync10(PATHS.CONFIG_FILE, "utf-8");
11564
+ const raw = readFileSync15(PATHS.CONFIG_FILE, "utf-8");
9248
11565
  const config = JSON.parse(raw);
9249
11566
  const meshes = config.meshes ?? [];
9250
11567
  if (meshes.length === 0) {
@@ -9645,12 +11962,12 @@ var exports_verify = {};
9645
11962
  __export(exports_verify, {
9646
11963
  runVerify: () => runVerify
9647
11964
  });
9648
- import { createHash } from "node:crypto";
11965
+ import { createHash as createHash3 } from "node:crypto";
9649
11966
  function safetyNumber(myPubkey, peerPubkey) {
9650
11967
  const a = Buffer.from(myPubkey, "hex");
9651
11968
  const b = Buffer.from(peerPubkey, "hex");
9652
11969
  const [lo, hi] = Buffer.compare(a, b) < 0 ? [a, b] : [b, a];
9653
- const hash = createHash("sha256").update(lo).update(hi).digest();
11970
+ const hash = createHash3("sha256").update(lo).update(hi).digest();
9654
11971
  const bits = [];
9655
11972
  for (let i = 0;i < 15; i++) {
9656
11973
  for (let b2 = 7;b2 >= 0; b2--) {
@@ -9724,19 +12041,19 @@ var exports_url_handler = {};
9724
12041
  __export(exports_url_handler, {
9725
12042
  runUrlHandler: () => runUrlHandler
9726
12043
  });
9727
- import { platform as platform7, homedir as homedir9 } from "node:os";
9728
- import { existsSync as existsSync15, mkdirSync as mkdirSync5, writeFileSync as writeFileSync8, rmSync as rmSync3, chmodSync as chmodSync4 } from "node:fs";
9729
- import { join as join9 } from "node:path";
12044
+ import { platform as platform7, homedir as homedir10 } from "node:os";
12045
+ import { existsSync as existsSync21, mkdirSync as mkdirSync9, writeFileSync as writeFileSync12, rmSync as rmSync3, chmodSync as chmodSync5 } from "node:fs";
12046
+ import { join as join12 } from "node:path";
9730
12047
  import { spawnSync as spawnSync5 } from "node:child_process";
9731
12048
  function resolveClaudemeshBin() {
9732
12049
  return process.argv[1] ?? "claudemesh";
9733
12050
  }
9734
- function installDarwin() {
12051
+ function installDarwin2() {
9735
12052
  const binPath = resolveClaudemeshBin();
9736
- const appDir = join9(homedir9(), "Library", "Application Support", "claudemesh", "ClaudemeshHandler.app");
9737
- const contents = join9(appDir, "Contents");
9738
- const macOS = join9(contents, "MacOS");
9739
- mkdirSync5(macOS, { recursive: true });
12053
+ const appDir = join12(homedir10(), "Library", "Application Support", "claudemesh", "ClaudemeshHandler.app");
12054
+ const contents = join12(appDir, "Contents");
12055
+ const macOS = join12(contents, "MacOS");
12056
+ mkdirSync9(macOS, { recursive: true });
9740
12057
  const plist = `<?xml version="1.0" encoding="UTF-8"?>
9741
12058
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
9742
12059
  <plist version="1.0">
@@ -9759,7 +12076,7 @@ function installDarwin() {
9759
12076
  </array>
9760
12077
  </dict>
9761
12078
  </plist>`;
9762
- writeFileSync8(join9(contents, "Info.plist"), plist);
12079
+ writeFileSync12(join12(contents, "Info.plist"), plist);
9763
12080
  const shim = `#!/bin/sh
9764
12081
  URL="$1"
9765
12082
  CODE=\${URL#claudemesh://}
@@ -9773,9 +12090,9 @@ tell application "Terminal"
9773
12090
  end tell
9774
12091
  EOF
9775
12092
  `;
9776
- const shimPath = join9(macOS, "open-url");
9777
- writeFileSync8(shimPath, shim);
9778
- chmodSync4(shimPath, 493);
12093
+ const shimPath = join12(macOS, "open-url");
12094
+ writeFileSync12(shimPath, shim);
12095
+ chmodSync5(shimPath, 493);
9779
12096
  const lsreg = spawnSync5("/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister", ["-f", appDir], { encoding: "utf-8" });
9780
12097
  if (lsreg.status !== 0) {
9781
12098
  render.warn("lsregister returned non-zero", "scheme may not activate until Finder rescans.");
@@ -9783,10 +12100,10 @@ EOF
9783
12100
  render.ok("registered claudemesh:// scheme on macOS", dim(appDir));
9784
12101
  return EXIT.SUCCESS;
9785
12102
  }
9786
- function installLinux() {
12103
+ function installLinux2() {
9787
12104
  const binPath = resolveClaudemeshBin();
9788
- const appsDir = join9(homedir9(), ".local", "share", "applications");
9789
- mkdirSync5(appsDir, { recursive: true });
12105
+ const appsDir = join12(homedir10(), ".local", "share", "applications");
12106
+ mkdirSync9(appsDir, { recursive: true });
9790
12107
  const desktop = `[Desktop Entry]
9791
12108
  Type=Application
9792
12109
  Name=Claudemesh
@@ -9797,8 +12114,8 @@ Terminal=true
9797
12114
  MimeType=x-scheme-handler/claudemesh;
9798
12115
  NoDisplay=true
9799
12116
  `;
9800
- const desktopPath = join9(appsDir, "claudemesh.desktop");
9801
- writeFileSync8(desktopPath, desktop);
12117
+ const desktopPath = join12(appsDir, "claudemesh.desktop");
12118
+ writeFileSync12(desktopPath, desktop);
9802
12119
  const xdg1 = spawnSync5("xdg-mime", ["default", "claudemesh.desktop", "x-scheme-handler/claudemesh"], { encoding: "utf-8" });
9803
12120
  if (xdg1.status !== 0) {
9804
12121
  render.warn("xdg-mime not available — skipped mime default registration");
@@ -9820,8 +12137,8 @@ function installWindows() {
9820
12137
  `[HKEY_CURRENT_USER\\Software\\Classes\\claudemesh\\shell\\open\\command]`,
9821
12138
  `@="\\"${binPath.replace(/\\/g, "\\\\")}\\" \\"%1\\""`
9822
12139
  ];
9823
- const regPath = join9(homedir9(), "claudemesh-handler.reg");
9824
- writeFileSync8(regPath, lines.join(`\r
12140
+ const regPath = join12(homedir10(), "claudemesh-handler.reg");
12141
+ writeFileSync12(regPath, lines.join(`\r
9825
12142
  `));
9826
12143
  const res = spawnSync5("reg.exe", ["import", regPath], { encoding: "utf-8" });
9827
12144
  if (res.status !== 0) {
@@ -9832,15 +12149,15 @@ function installWindows() {
9832
12149
  return EXIT.SUCCESS;
9833
12150
  }
9834
12151
  function uninstallDarwin() {
9835
- const appDir = join9(homedir9(), "Library", "Application Support", "claudemesh", "ClaudemeshHandler.app");
9836
- if (existsSync15(appDir))
12152
+ const appDir = join12(homedir10(), "Library", "Application Support", "claudemesh", "ClaudemeshHandler.app");
12153
+ if (existsSync21(appDir))
9837
12154
  rmSync3(appDir, { recursive: true, force: true });
9838
12155
  render.ok("removed claudemesh:// handler on macOS");
9839
12156
  return EXIT.SUCCESS;
9840
12157
  }
9841
12158
  function uninstallLinux() {
9842
- const desktopPath = join9(homedir9(), ".local", "share", "applications", "claudemesh.desktop");
9843
- if (existsSync15(desktopPath))
12159
+ const desktopPath = join12(homedir10(), ".local", "share", "applications", "claudemesh.desktop");
12160
+ if (existsSync21(desktopPath))
9844
12161
  rmSync3(desktopPath, { force: true });
9845
12162
  render.ok("removed claudemesh:// handler on Linux");
9846
12163
  return EXIT.SUCCESS;
@@ -9855,9 +12172,9 @@ async function runUrlHandler(action) {
9855
12172
  const p = platform7();
9856
12173
  if (act === "install") {
9857
12174
  if (p === "darwin")
9858
- return installDarwin();
12175
+ return installDarwin2();
9859
12176
  if (p === "linux")
9860
- return installLinux();
12177
+ return installLinux2();
9861
12178
  if (p === "win32")
9862
12179
  return installWindows();
9863
12180
  } else if (act === "uninstall" || act === "remove") {
@@ -9885,9 +12202,9 @@ var exports_status_line = {};
9885
12202
  __export(exports_status_line, {
9886
12203
  runStatusLine: () => runStatusLine
9887
12204
  });
9888
- import { existsSync as existsSync16, readFileSync as readFileSync11 } from "node:fs";
9889
- import { join as join10 } from "node:path";
9890
- import { homedir as homedir10 } from "node:os";
12205
+ import { existsSync as existsSync22, readFileSync as readFileSync16 } from "node:fs";
12206
+ import { join as join13 } from "node:path";
12207
+ import { homedir as homedir11 } from "node:os";
9891
12208
  async function runStatusLine() {
9892
12209
  try {
9893
12210
  const config = readConfig();
@@ -9895,11 +12212,11 @@ async function runStatusLine() {
9895
12212
  process.stdout.write("◇ claudemesh (not joined)");
9896
12213
  return EXIT.SUCCESS;
9897
12214
  }
9898
- const cachePath = join10(homedir10(), ".claudemesh", "peer-cache.json");
12215
+ const cachePath = join13(homedir11(), ".claudemesh", "peer-cache.json");
9899
12216
  let cache = {};
9900
- if (existsSync16(cachePath)) {
12217
+ if (existsSync22(cachePath)) {
9901
12218
  try {
9902
- cache = JSON.parse(readFileSync11(cachePath, "utf-8"));
12219
+ cache = JSON.parse(readFileSync16(cachePath, "utf-8"));
9903
12220
  } catch {}
9904
12221
  }
9905
12222
  const pick = config.meshes[0];
@@ -9930,7 +12247,7 @@ __export(exports_backup, {
9930
12247
  runRestore: () => runRestore,
9931
12248
  runBackup: () => runBackup
9932
12249
  });
9933
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync17 } from "node:fs";
12250
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync13, existsSync as existsSync23 } from "node:fs";
9934
12251
  import { createInterface as createInterface11 } from "node:readline";
9935
12252
  function readHidden(prompt5) {
9936
12253
  return new Promise((resolve2) => {
@@ -9972,11 +12289,11 @@ async function deriveKey(pass, salt, s) {
9972
12289
  }
9973
12290
  async function runBackup(outPath) {
9974
12291
  const configPath = getConfigPath();
9975
- if (!existsSync17(configPath)) {
12292
+ if (!existsSync23(configPath)) {
9976
12293
  console.error(" No config found — nothing to back up. Join a mesh first.");
9977
12294
  return EXIT.NOT_FOUND;
9978
12295
  }
9979
- const plaintext = readFileSync12(configPath);
12296
+ const plaintext = readFileSync17(configPath);
9980
12297
  const pass = await readHidden(" Passphrase (min 12 chars): ");
9981
12298
  if (pass.length < 12) {
9982
12299
  console.error(" ✗ Passphrase too short.");
@@ -9994,7 +12311,7 @@ async function runBackup(outPath) {
9994
12311
  const ciphertext = Buffer.from(s.crypto_aead_xchacha20poly1305_ietf_encrypt(plaintext, null, null, nonce, key));
9995
12312
  const blob = Buffer.concat([MAGIC, salt, nonce, ciphertext]);
9996
12313
  const file = outPath ?? `claudemesh-backup-${new Date().toISOString().replace(/[:.]/g, "-")}.cmb`;
9997
- writeFileSync9(file, blob, { mode: 384 });
12314
+ writeFileSync13(file, blob, { mode: 384 });
9998
12315
  console.log(`
9999
12316
  ✓ Backup saved: ${file}`);
10000
12317
  console.log(` Size: ${blob.length} bytes. Guard the passphrase — there is no recovery.
@@ -10006,11 +12323,11 @@ async function runRestore(inPath) {
10006
12323
  console.error(" Usage: claudemesh restore <backup-file>");
10007
12324
  return EXIT.INVALID_ARGS;
10008
12325
  }
10009
- if (!existsSync17(inPath)) {
12326
+ if (!existsSync23(inPath)) {
10010
12327
  console.error(` ✗ File not found: ${inPath}`);
10011
12328
  return EXIT.NOT_FOUND;
10012
12329
  }
10013
- const blob = readFileSync12(inPath);
12330
+ const blob = readFileSync17(inPath);
10014
12331
  if (blob.length < 4 + 16 + 24 + 17 || !blob.subarray(0, 4).equals(MAGIC)) {
10015
12332
  console.error(" ✗ Not a claudemesh backup file (bad magic).");
10016
12333
  return EXIT.INVALID_ARGS;
@@ -10029,12 +12346,12 @@ async function runRestore(inPath) {
10029
12346
  return EXIT.INTERNAL_ERROR;
10030
12347
  }
10031
12348
  const configPath = getConfigPath();
10032
- if (existsSync17(configPath)) {
12349
+ if (existsSync23(configPath)) {
10033
12350
  const backupOld = `${configPath}.before-restore.${Date.now()}`;
10034
- writeFileSync9(backupOld, readFileSync12(configPath), { mode: 384 });
12351
+ writeFileSync13(backupOld, readFileSync17(configPath), { mode: 384 });
10035
12352
  console.log(` ↻ Existing config saved to ${backupOld}`);
10036
12353
  }
10037
- writeFileSync9(configPath, Buffer.from(plaintext), { mode: 384 });
12354
+ writeFileSync13(configPath, Buffer.from(plaintext), { mode: 384 });
10038
12355
  console.log(`
10039
12356
  ✓ Config restored to ${configPath}`);
10040
12357
  console.log(" Run `claudemesh list` to verify your meshes.\n");
@@ -10054,8 +12371,8 @@ __export(exports_upgrade, {
10054
12371
  runUpgrade: () => runUpgrade
10055
12372
  });
10056
12373
  import { spawnSync as spawnSync6 } from "node:child_process";
10057
- import { existsSync as existsSync18 } from "node:fs";
10058
- import { dirname as dirname5, join as join11, resolve as resolve2 } from "node:path";
12374
+ import { existsSync as existsSync24 } from "node:fs";
12375
+ import { dirname as dirname8, join as join14, resolve as resolve2 } from "node:path";
10059
12376
  async function latestVersion() {
10060
12377
  try {
10061
12378
  const res = await fetch(URLS.NPM_REGISTRY, { signal: AbortSignal.timeout(8000) });
@@ -10068,15 +12385,15 @@ async function latestVersion() {
10068
12385
  }
10069
12386
  }
10070
12387
  function findNpm() {
10071
- const portable = join11(process.env.HOME ?? "", ".claudemesh", "node", "bin", "npm");
10072
- if (existsSync18(portable)) {
10073
- return { npm: portable, prefix: join11(process.env.HOME ?? "", ".claudemesh") };
12388
+ const portable = join14(process.env.HOME ?? "", ".claudemesh", "node", "bin", "npm");
12389
+ if (existsSync24(portable)) {
12390
+ return { npm: portable, prefix: join14(process.env.HOME ?? "", ".claudemesh") };
10074
12391
  }
10075
12392
  let cur = resolve2(process.argv[1] ?? ".");
10076
12393
  for (let i = 0;i < 6; i++) {
10077
- cur = dirname5(cur);
10078
- const candidate = join11(cur, "bin", "npm");
10079
- if (existsSync18(candidate))
12394
+ cur = dirname8(cur);
12395
+ const candidate = join14(cur, "bin", "npm");
12396
+ if (existsSync24(candidate))
10080
12397
  return { npm: candidate };
10081
12398
  }
10082
12399
  return { npm: "npm" };
@@ -10138,9 +12455,9 @@ __export(exports_grants, {
10138
12455
  runBlock: () => runBlock,
10139
12456
  isAllowed: () => isAllowed
10140
12457
  });
10141
- import { existsSync as existsSync19, mkdirSync as mkdirSync6, readFileSync as readFileSync13, writeFileSync as writeFileSync10 } from "node:fs";
10142
- import { homedir as homedir11 } from "node:os";
10143
- import { join as join12 } from "node:path";
12458
+ import { existsSync as existsSync25, mkdirSync as mkdirSync10, readFileSync as readFileSync18, writeFileSync as writeFileSync14 } from "node:fs";
12459
+ import { homedir as homedir12 } from "node:os";
12460
+ import { join as join15 } from "node:path";
10144
12461
  async function syncToBroker(meshSlug, grants) {
10145
12462
  const auth = getStoredToken();
10146
12463
  if (!auth)
@@ -10158,19 +12475,19 @@ async function syncToBroker(meshSlug, grants) {
10158
12475
  }
10159
12476
  }
10160
12477
  function readGrants() {
10161
- if (!existsSync19(GRANT_FILE))
12478
+ if (!existsSync25(GRANT_FILE))
10162
12479
  return {};
10163
12480
  try {
10164
- return JSON.parse(readFileSync13(GRANT_FILE, "utf-8"));
12481
+ return JSON.parse(readFileSync18(GRANT_FILE, "utf-8"));
10165
12482
  } catch {
10166
12483
  return {};
10167
12484
  }
10168
12485
  }
10169
12486
  function writeGrants(g) {
10170
- const dir = join12(homedir11(), ".claudemesh");
10171
- if (!existsSync19(dir))
10172
- mkdirSync6(dir, { recursive: true });
10173
- writeFileSync10(GRANT_FILE, JSON.stringify(g, null, 2), { mode: 384 });
12487
+ const dir = join15(homedir12(), ".claudemesh");
12488
+ if (!existsSync25(dir))
12489
+ mkdirSync10(dir, { recursive: true });
12490
+ writeFileSync14(GRANT_FILE, JSON.stringify(g, null, 2), { mode: 384 });
10174
12491
  }
10175
12492
  function resolveCaps(input) {
10176
12493
  if (input.includes("all"))
@@ -10326,7 +12643,7 @@ var init_grants = __esm(() => {
10326
12643
  BROKER_HTTP7 = URLS.BROKER.replace("wss://", "https://").replace("ws://", "http://").replace("/ws", "");
10327
12644
  ALL_CAPS = ["read", "dm", "broadcast", "state-read", "state-write", "file-read"];
10328
12645
  DEFAULT_CAPS = ["read", "dm", "broadcast", "state-read"];
10329
- GRANT_FILE = join12(homedir11(), ".claudemesh", "grants.json");
12646
+ GRANT_FILE = join15(homedir12(), ".claudemesh", "grants.json");
10330
12647
  });
10331
12648
 
10332
12649
  // src/commands/profile.ts
@@ -11223,6 +13540,23 @@ claudemesh send "<from_name>" "..." --mesh "<mesh_slug>"
11223
13540
 
11224
13541
  If the parent Claude session was launched via \`claudemesh launch\`, an MCP push-pipe is running and holds the per-mesh WS connection. CLI invocations dial \`~/.claudemesh/sockets/<mesh-slug>.sock\` and reuse that warm connection (~200ms total round-trip including Node.js startup). If no push-pipe is running (cron, scripts, hooks fired outside a session), the CLI opens its own WS, which takes ~500-700ms cold. **You don't manage this** — every verb auto-detects and falls through.
11225
13542
 
13543
+ ### Daemon path (v0.9.0, opt-in, fastest)
13544
+
13545
+ \`claudemesh daemon up [--mesh <slug>]\` starts a persistent per-user runtime that holds the broker WS, a durable SQLite outbox/inbox, and listens on \`~/.claudemesh/daemon/daemon.sock\` (UDS) plus an optional loopback TCP. When the daemon socket is present, every verb routes through it first (~1ms IPC) before falling back to bridge / cold paths. The send envelope carries a caller-stable \`client_message_id\`, so a \`claudemesh send\` that started before a daemon crash survives the restart via the on-disk outbox.
13546
+
13547
+ Lifecycle:
13548
+
13549
+ \`\`\`bash
13550
+ claudemesh daemon up --mesh <slug> # foreground
13551
+ claudemesh daemon install-service --mesh <slug> # macOS launchd / Linux systemd-user
13552
+ claudemesh daemon status [--json] # health + pid
13553
+ claudemesh daemon outbox list [--failed|--pending|...] # local queue inspection
13554
+ claudemesh daemon outbox requeue <id> # re-enqueue an aborted/dead row
13555
+ claudemesh daemon down # SIGTERM + wait
13556
+ \`\`\`
13557
+
13558
+ \`claudemesh install\` (MCP + hooks registration) and the daemon are independent — install does not start the daemon, and the daemon does not require install. Run both for the warmest path: install gives you the in-session push-pipe, daemon gives you cross-invocation persistence and a survivable outbox.
13559
+
11226
13560
  ## Spawning new sessions (no wizard)
11227
13561
 
11228
13562
  \`claudemesh launch\` is the canonical way to start a new Claude Code session connected to claudemesh. Pass every required flag up front so no interactive prompt fires — that's what makes the verb scriptable from tmux send-keys, AppleScript/iTerm spawn helpers, hooks, cron, and the \`claudemesh launch\` you call from inside another session. **Always use this verb, never \`claude\` directly with hand-rolled flags** — it sets up the per-session ed25519 keypair, exports \`CLAUDEMESH_DISPLAY_NAME\`, isolates the mesh config in a tmpdir, and passes the \`--dangerously-load-development-channels server:claudemesh\` plumbing that the MCP push-pipe needs.
@@ -11805,8 +14139,8 @@ __export(exports_file, {
11805
14139
  runFileGet: () => runFileGet
11806
14140
  });
11807
14141
  import { hostname as osHostname } from "node:os";
11808
- import { resolve as resolvePath, basename, dirname as dirname6 } from "node:path";
11809
- import { statSync as statSync6, existsSync as existsSync20, writeFileSync as writeFileSync11, mkdirSync as mkdirSync7 } from "node:fs";
14142
+ import { resolve as resolvePath, basename, dirname as dirname9 } from "node:path";
14143
+ import { statSync as statSync6, existsSync as existsSync26, writeFileSync as writeFileSync15, mkdirSync as mkdirSync11 } from "node:fs";
11810
14144
  function emitJson2(data) {
11811
14145
  console.log(JSON.stringify(data, null, 2));
11812
14146
  }
@@ -11823,7 +14157,7 @@ async function runFileShare(filePath, opts) {
11823
14157
  return EXIT.INVALID_ARGS;
11824
14158
  }
11825
14159
  const absPath = resolvePath(filePath);
11826
- if (!existsSync20(absPath)) {
14160
+ if (!existsSync26(absPath)) {
11827
14161
  render.err(`File not found: ${absPath}`);
11828
14162
  return EXIT.INVALID_ARGS;
11829
14163
  }
@@ -11902,8 +14236,8 @@ async function runFileGet(fileId, opts) {
11902
14236
  }
11903
14237
  const buf = Buffer.from(await res.arrayBuffer());
11904
14238
  const outPath = opts.out ? resolvePath(opts.out) : resolvePath(process.cwd(), meta.name);
11905
- mkdirSync7(dirname6(outPath), { recursive: true });
11906
- writeFileSync11(outPath, buf);
14239
+ mkdirSync11(dirname9(outPath), { recursive: true });
14240
+ writeFileSync15(outPath, buf);
11907
14241
  if (opts.json) {
11908
14242
  emitJson2({ fileId, name: meta.name, savedTo: outPath, sizeBytes: buf.length });
11909
14243
  } else {
@@ -11998,7 +14332,7 @@ var require_client = __commonJS((exports) => {
11998
14332
  var ws_1 = __importDefault(__require("ws"));
11999
14333
  var crypto_js_1 = require_crypto();
12000
14334
  var MAX_QUEUED2 = 100;
12001
- var HELLO_ACK_TIMEOUT_MS2 = 5000;
14335
+ var HELLO_ACK_TIMEOUT_MS3 = 5000;
12002
14336
  var BACKOFF_CAPS2 = [1000, 2000, 4000, 8000, 16000, 30000];
12003
14337
 
12004
14338
  class MeshClient extends node_events_1.EventEmitter {
@@ -12063,7 +14397,7 @@ var require_client = __commonJS((exports) => {
12063
14397
  this.debug("hello_ack timeout");
12064
14398
  ws.close();
12065
14399
  reject(new Error("hello_ack timeout"));
12066
- }, HELLO_ACK_TIMEOUT_MS2);
14400
+ }, HELLO_ACK_TIMEOUT_MS3);
12067
14401
  };
12068
14402
  const onMessage = (raw) => {
12069
14403
  let msg;
@@ -12513,7 +14847,7 @@ __export(exports_bridge, {
12513
14847
  runBridge: () => runBridge,
12514
14848
  bridgeConfigTemplate: () => bridgeConfigTemplate
12515
14849
  });
12516
- import { readFileSync as readFileSync14, existsSync as existsSync21 } from "node:fs";
14850
+ import { readFileSync as readFileSync19, existsSync as existsSync27 } from "node:fs";
12517
14851
  function parseConfig(text) {
12518
14852
  const trimmed = text.trim();
12519
14853
  if (trimmed.startsWith("{"))
@@ -12557,13 +14891,13 @@ async function runBridge(configPath) {
12557
14891
  render.err("Usage: claudemesh bridge run <config.yaml>");
12558
14892
  return EXIT.INVALID_ARGS;
12559
14893
  }
12560
- if (!existsSync21(configPath)) {
14894
+ if (!existsSync27(configPath)) {
12561
14895
  render.err(`config file not found: ${configPath}`);
12562
14896
  return EXIT.NOT_FOUND;
12563
14897
  }
12564
14898
  let cfg;
12565
14899
  try {
12566
- cfg = parseConfig(readFileSync14(configPath, "utf-8"));
14900
+ cfg = parseConfig(readFileSync19(configPath, "utf-8"));
12567
14901
  } catch (e) {
12568
14902
  render.err(`failed to parse ${configPath}: ${e instanceof Error ? e.message : String(e)}`);
12569
14903
  return EXIT.INVALID_ARGS;
@@ -12967,9 +15301,9 @@ function cacheKey(apiKeySecret, topicName) {
12967
15301
  async function getTopicKey(args) {
12968
15302
  const cacheId = cacheKey(args.apiKeySecret, args.topicName);
12969
15303
  if (!args.fresh) {
12970
- const cached = cache.get(cacheId);
12971
- if (cached)
12972
- return { ok: true, topicKey: cached.topicKey };
15304
+ const cached2 = cache.get(cacheId);
15305
+ if (cached2)
15306
+ return { ok: true, topicKey: cached2.topicKey };
12973
15307
  }
12974
15308
  let sealed;
12975
15309
  try {
@@ -13533,8 +15867,8 @@ var init_definitions = __esm(() => {
13533
15867
  });
13534
15868
 
13535
15869
  // src/services/bridge/server.ts
13536
- import { createServer as createServer2 } from "node:net";
13537
- import { mkdirSync as mkdirSync8, unlinkSync as unlinkSync2, existsSync as existsSync22, chmodSync as chmodSync5 } from "node:fs";
15870
+ import { createServer as createServer3 } from "node:net";
15871
+ import { mkdirSync as mkdirSync12, unlinkSync as unlinkSync5, existsSync as existsSync28, chmodSync as chmodSync6 } from "node:fs";
13538
15872
  async function resolveTarget2(client, to) {
13539
15873
  if (to.startsWith("@") || to === "*" || /^[0-9a-f]{64}$/i.test(to)) {
13540
15874
  return { ok: true, spec: to };
@@ -13646,21 +15980,21 @@ function handleConnection(socket, client) {
13646
15980
  socket.on("error", () => {});
13647
15981
  }
13648
15982
  function startBridgeServer(client) {
13649
- const path = socketPath(client.meshSlug);
15983
+ const path2 = socketPath(client.meshSlug);
13650
15984
  const dir = socketDir();
13651
- if (!existsSync22(dir)) {
13652
- mkdirSync8(dir, { recursive: true, mode: 448 });
15985
+ if (!existsSync28(dir)) {
15986
+ mkdirSync12(dir, { recursive: true, mode: 448 });
13653
15987
  }
13654
- if (existsSync22(path)) {
15988
+ if (existsSync28(path2)) {
13655
15989
  try {
13656
- unlinkSync2(path);
15990
+ unlinkSync5(path2);
13657
15991
  } catch {}
13658
15992
  }
13659
- const server = createServer2((socket) => handleConnection(socket, client));
15993
+ const server = createServer3((socket) => handleConnection(socket, client));
13660
15994
  try {
13661
- server.listen(path);
15995
+ server.listen(path2);
13662
15996
  } catch (err) {
13663
- process.stderr.write(`[claudemesh] bridge: failed to bind ${path}: ${String(err)}
15997
+ process.stderr.write(`[claudemesh] bridge: failed to bind ${path2}: ${String(err)}
13664
15998
  `);
13665
15999
  return null;
13666
16000
  }
@@ -13669,11 +16003,11 @@ function startBridgeServer(client) {
13669
16003
  `);
13670
16004
  });
13671
16005
  try {
13672
- chmodSync5(path, 384);
16006
+ chmodSync6(path2, 384);
13673
16007
  } catch {}
13674
16008
  let stopped = false;
13675
16009
  return {
13676
- path,
16010
+ path: path2,
13677
16011
  stop() {
13678
16012
  if (stopped)
13679
16013
  return;
@@ -13682,12 +16016,12 @@ function startBridgeServer(client) {
13682
16016
  server.close();
13683
16017
  } catch {}
13684
16018
  try {
13685
- unlinkSync2(path);
16019
+ unlinkSync5(path2);
13686
16020
  } catch {}
13687
16021
  }
13688
16022
  };
13689
16023
  }
13690
- var init_server = __esm(() => {
16024
+ var init_server2 = __esm(() => {
13691
16025
  init_protocol();
13692
16026
  });
13693
16027
 
@@ -14258,9 +16592,9 @@ async function startServiceProxy(serviceName) {
14258
16592
  const fetched = await client.getServiceTools(serviceName);
14259
16593
  tools = fetched;
14260
16594
  } catch {
14261
- const cached = client.serviceCatalog.find((s) => s.name === serviceName);
14262
- if (cached) {
14263
- tools = cached.tools;
16595
+ const cached2 = client.serviceCatalog.find((s) => s.name === serviceName);
16596
+ if (cached2) {
16597
+ tools = cached2.tools;
14264
16598
  }
14265
16599
  }
14266
16600
  if (tools.length === 0) {
@@ -14349,11 +16683,11 @@ async function startServiceProxy(serviceName) {
14349
16683
  process.on("SIGINT", shutdown);
14350
16684
  }
14351
16685
  var peerNameCache, peerNameCacheAge = 0, CACHE_TTL_MS = 30000;
14352
- var init_server2 = __esm(() => {
16686
+ var init_server3 = __esm(() => {
14353
16687
  init_definitions();
14354
16688
  init_facade();
14355
16689
  init_facade8();
14356
- init_server();
16690
+ init_server2();
14357
16691
  peerNameCache = new Map;
14358
16692
  });
14359
16693
 
@@ -14369,7 +16703,7 @@ async function runMcp() {
14369
16703
  process.exit(0);
14370
16704
  }
14371
16705
  var init_mcp = __esm(() => {
14372
- init_server2();
16706
+ init_server3();
14373
16707
  });
14374
16708
 
14375
16709
  // src/commands/hook.ts
@@ -15174,6 +17508,17 @@ Security
15174
17508
  claudemesh backup [file] encrypt config → portable recovery file
15175
17509
  claudemesh restore <file> restore config from a backup file
15176
17510
 
17511
+ Daemon (long-lived peer mesh runtime, v0.9.0)
17512
+ claudemesh daemon up start daemon (alias: start) [--mesh <slug>] [--no-tcp]
17513
+ claudemesh daemon status show running pid + IPC health [--json]
17514
+ claudemesh daemon down stop daemon (alias: stop)
17515
+ claudemesh daemon version ipc + schema version of running daemon
17516
+ claudemesh daemon outbox list list local outbox rows [--failed|--pending|--inflight|--done]
17517
+ claudemesh daemon outbox requeue <id> re-enqueue an aborted/dead row [--new-client-id <id>]
17518
+ claudemesh daemon accept-host pin current host fingerprint
17519
+ claudemesh daemon install-service --mesh <slug> write launchd / systemd-user unit
17520
+ claudemesh daemon uninstall-service remove the unit
17521
+
15177
17522
  Setup
15178
17523
  claudemesh install register MCP server + hooks
15179
17524
  claudemesh uninstall remove MCP server + hooks
@@ -15470,6 +17815,23 @@ async function main() {
15470
17815
  process.exit(await whoami2({ json: !!flags.json }));
15471
17816
  break;
15472
17817
  }
17818
+ case "daemon": {
17819
+ const { runDaemonCommand: runDaemonCommand2 } = await Promise.resolve().then(() => (init_daemon(), exports_daemon));
17820
+ const sub = positionals[0];
17821
+ const rest = positionals.slice(1);
17822
+ const outboxStatus = flags.failed ? "dead" : flags.pending ? "pending" : flags.inflight ? "inflight" : flags.done ? "done" : flags.aborted ? "aborted" : undefined;
17823
+ const code = await runDaemonCommand2(sub, {
17824
+ json: !!flags.json,
17825
+ noTcp: !!flags["no-tcp"],
17826
+ publicHealth: !!flags["public-health"],
17827
+ mesh: flags.mesh,
17828
+ displayName: flags.name,
17829
+ outboxStatus,
17830
+ newClientId: flags["new-client-id"]
17831
+ }, rest);
17832
+ process.exit(code);
17833
+ break;
17834
+ }
15473
17835
  case "install": {
15474
17836
  const { runInstall: runInstall2 } = await Promise.resolve().then(() => (init_install(), exports_install));
15475
17837
  runInstall2(positionals);
@@ -15490,8 +17852,8 @@ async function main() {
15490
17852
  const { runStatusSet: runStatusSet2 } = await Promise.resolve().then(() => (init_broker_actions(), exports_broker_actions));
15491
17853
  process.exit(await runStatusSet2(positionals[1] ?? "", { mesh: flags.mesh, json: !!flags.json }));
15492
17854
  } else {
15493
- const { runStatus: runStatus2 } = await Promise.resolve().then(() => (init_status(), exports_status));
15494
- await runStatus2();
17855
+ const { runStatus: runStatus3 } = await Promise.resolve().then(() => (init_status(), exports_status));
17856
+ await runStatus3();
15495
17857
  }
15496
17858
  break;
15497
17859
  }
@@ -15643,8 +18005,8 @@ async function main() {
15643
18005
  const { runStatusSet: runStatusSet2 } = await Promise.resolve().then(() => (init_broker_actions(), exports_broker_actions));
15644
18006
  process.exit(await runStatusSet2(positionals[2] ?? "", { mesh: flags.mesh, json: !!flags.json }));
15645
18007
  } else {
15646
- const { runStatus: runStatus2 } = await Promise.resolve().then(() => (init_status(), exports_status));
15647
- await runStatus2();
18008
+ const { runStatus: runStatus3 } = await Promise.resolve().then(() => (init_status(), exports_status));
18009
+ await runStatus3();
15648
18010
  }
15649
18011
  } else {
15650
18012
  console.error("Usage: claudemesh profile [summary|visible|status]");
@@ -16131,4 +18493,4 @@ main().catch((err) => {
16131
18493
  process.exit(EXIT.INTERNAL_ERROR);
16132
18494
  });
16133
18495
 
16134
- //# debugId=5139AEA096A0916B64756E2164756E21
18496
+ //# debugId=7D9AD4B3EA7DF68364756E2164756E21