claudemesh-cli 1.29.0 → 1.30.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.
@@ -92,7 +92,8 @@ var init_exit_codes = __esm(() => {
92
92
  ALREADY_EXISTS: 6,
93
93
  PERMISSION_DENIED: 7,
94
94
  INTERNAL_ERROR: 8,
95
- CLAUDE_MISSING: 9
95
+ CLAUDE_MISSING: 9,
96
+ IO_ERROR: 10
96
97
  };
97
98
  });
98
99
 
@@ -103,7 +104,7 @@ __export(exports_urls, {
103
104
  VERSION: () => VERSION,
104
105
  URLS: () => URLS
105
106
  });
106
- var URLS, VERSION = "1.29.0", env;
107
+ var URLS, VERSION = "1.30.1", env;
107
108
  var init_urls = __esm(() => {
108
109
  URLS = {
109
110
  BROKER: process.env.CLAUDEMESH_BROKER_URL ?? "wss://ic.claudemesh.com/ws",
@@ -3941,6 +3942,38 @@ var init_lifecycle = __esm(() => {
3941
3942
  init_paths2();
3942
3943
  });
3943
3944
 
3945
+ // src/services/broker/session-hello-sig.ts
3946
+ var exports_session_hello_sig = {};
3947
+ __export(exports_session_hello_sig, {
3948
+ signSessionHello: () => signSessionHello,
3949
+ signParentAttestation: () => signParentAttestation,
3950
+ DEFAULT_ATTESTATION_TTL_MS: () => DEFAULT_ATTESTATION_TTL_MS
3951
+ });
3952
+ async function signParentAttestation(args) {
3953
+ const s = await ensureSodium();
3954
+ const expiresAt = (args.now ?? Date.now()) + (args.ttlMs ?? DEFAULT_ATTESTATION_TTL_MS);
3955
+ const canonical = `claudemesh-session-attest|${args.parentMemberPubkey}|${args.sessionPubkey}|${expiresAt}`;
3956
+ const sig = s.crypto_sign_detached(s.from_string(canonical), s.from_hex(args.parentSecretKey));
3957
+ return {
3958
+ sessionPubkey: args.sessionPubkey,
3959
+ parentMemberPubkey: args.parentMemberPubkey,
3960
+ expiresAt,
3961
+ signature: s.to_hex(sig)
3962
+ };
3963
+ }
3964
+ async function signSessionHello(args) {
3965
+ const s = await ensureSodium();
3966
+ const timestamp2 = args.now ?? Date.now();
3967
+ const canonical = `claudemesh-session-hello|${args.meshId}|${args.parentMemberPubkey}|${args.sessionPubkey}|${timestamp2}`;
3968
+ const sig = s.crypto_sign_detached(s.from_string(canonical), s.from_hex(args.sessionSecretKey));
3969
+ return { timestamp: timestamp2, signature: s.to_hex(sig) };
3970
+ }
3971
+ var DEFAULT_ATTESTATION_TTL_MS;
3972
+ var init_session_hello_sig = __esm(() => {
3973
+ init_keypair();
3974
+ DEFAULT_ATTESTATION_TTL_MS = 12 * 60 * 60 * 1000;
3975
+ });
3976
+
3944
3977
  // src/commands/launch.ts
3945
3978
  var exports_launch = {};
3946
3979
  __export(exports_launch, {
@@ -4344,6 +4377,8 @@ async function runLaunch(flags, rawArgs) {
4344
4377
  };
4345
4378
  writeFileSync7(join5(tmpDir, "config.json"), JSON.stringify(sessionConfig, null, 2) + `
4346
4379
  `, "utf-8");
4380
+ const isResume = args.resume !== null || args.continueSession;
4381
+ const claudeSessionId = isResume ? undefined : randomUUID();
4347
4382
  let sessionTokenFilePath = null;
4348
4383
  let sessionTokenForCleanup = null;
4349
4384
  try {
@@ -4351,6 +4386,27 @@ async function runLaunch(flags, rawArgs) {
4351
4386
  const minted = mintSessionToken2(tmpDir);
4352
4387
  sessionTokenFilePath = minted.filePath;
4353
4388
  sessionTokenForCleanup = minted.token;
4389
+ let presencePayload;
4390
+ try {
4391
+ const { generateKeypair: generateKeypair4 } = await Promise.resolve().then(() => (init_facade7(), exports_facade4));
4392
+ const { signParentAttestation: signParentAttestation2 } = await Promise.resolve().then(() => (init_session_hello_sig(), exports_session_hello_sig));
4393
+ const sessionKp = await generateKeypair4();
4394
+ const att = await signParentAttestation2({
4395
+ parentMemberPubkey: mesh.pubkey,
4396
+ parentSecretKey: mesh.secretKey,
4397
+ sessionPubkey: sessionKp.publicKey
4398
+ });
4399
+ presencePayload = {
4400
+ session_pubkey: sessionKp.publicKey,
4401
+ session_secret_key: sessionKp.secretKey,
4402
+ parent_attestation: {
4403
+ session_pubkey: att.sessionPubkey,
4404
+ parent_member_pubkey: att.parentMemberPubkey,
4405
+ expires_at: att.expiresAt,
4406
+ signature: att.signature
4407
+ }
4408
+ };
4409
+ } catch {}
4354
4410
  const { ipc: ipc2 } = await Promise.resolve().then(() => (init_client3(), exports_client));
4355
4411
  const sessionIdForRegister = claudeSessionId ?? randomUUID();
4356
4412
  await ipc2({
@@ -4365,7 +4421,8 @@ async function runLaunch(flags, rawArgs) {
4365
4421
  pid: process.pid,
4366
4422
  cwd: process.cwd(),
4367
4423
  ...role ? { role } : {},
4368
- ...parsedGroups.length > 0 ? { groups: parsedGroups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`) } : {}
4424
+ ...parsedGroups.length > 0 ? { groups: parsedGroups.map((g) => `@${g.name}${g.role ? `:${g.role}` : ""}`) } : {},
4425
+ ...presencePayload ? { presence: presencePayload } : {}
4369
4426
  }
4370
4427
  }).catch(() => null);
4371
4428
  process._claudemeshTokenEnv = {
@@ -4429,8 +4486,6 @@ async function runLaunch(flags, rawArgs) {
4429
4486
  }
4430
4487
  filtered.push(args.claudeArgs[i]);
4431
4488
  }
4432
- const isResume = args.resume !== null || args.continueSession;
4433
- const claudeSessionId = isResume ? undefined : randomUUID();
4434
4489
  const claudeArgs = [
4435
4490
  "--dangerously-load-development-channels",
4436
4491
  "server:claudemesh",
@@ -8440,7 +8495,7 @@ async function runMsgStatus(id, opts) {
8440
8495
  console.log(JSON.stringify(result, null, 2));
8441
8496
  return EXIT.SUCCESS;
8442
8497
  }
8443
- render.section(`message ${id.slice(0, 12)}…`);
8498
+ render.section(`message ${lookupId.slice(0, 12)}…`);
8444
8499
  render.kv([
8445
8500
  ["target", result.targetSpec],
8446
8501
  ["delivered", result.delivered ? "yes" : "no"],
@@ -9258,13 +9313,27 @@ function startReaper() {
9258
9313
  return;
9259
9314
  reaperHandle = setInterval(reapDead, REAPER_INTERVAL_MS).unref?.() ?? reaperHandle;
9260
9315
  }
9316
+ function setRegistryHooks(next) {
9317
+ hooks.onRegister = next.onRegister;
9318
+ hooks.onDeregister = next.onDeregister;
9319
+ }
9261
9320
  function registerSession(info) {
9262
9321
  const priorToken = bySessionId.get(info.sessionId);
9263
- if (priorToken && priorToken !== info.token)
9264
- byToken.delete(priorToken);
9322
+ if (priorToken && priorToken !== info.token) {
9323
+ const prior = byToken.get(priorToken);
9324
+ if (prior) {
9325
+ byToken.delete(priorToken);
9326
+ try {
9327
+ hooks.onDeregister?.(prior);
9328
+ } catch {}
9329
+ }
9330
+ }
9265
9331
  const stored = { ...info, registeredAt: Date.now() };
9266
9332
  byToken.set(info.token, stored);
9267
9333
  bySessionId.set(info.sessionId, info.token);
9334
+ try {
9335
+ hooks.onRegister?.(stored);
9336
+ } catch {}
9268
9337
  return stored;
9269
9338
  }
9270
9339
  function deregisterByToken(token) {
@@ -9274,6 +9343,9 @@ function deregisterByToken(token) {
9274
9343
  byToken.delete(token);
9275
9344
  if (bySessionId.get(entry.sessionId) === token)
9276
9345
  bySessionId.delete(entry.sessionId);
9346
+ try {
9347
+ hooks.onDeregister?.(entry);
9348
+ } catch {}
9277
9349
  return true;
9278
9350
  }
9279
9351
  function resolveToken(token) {
@@ -9305,12 +9377,13 @@ function reapDead() {
9305
9377
  for (const t of dead)
9306
9378
  deregisterByToken(t);
9307
9379
  }
9308
- var TTL_MS, REAPER_INTERVAL_MS, byToken, bySessionId, reaperHandle = null;
9380
+ var TTL_MS, REAPER_INTERVAL_MS, byToken, bySessionId, hooks, reaperHandle = null;
9309
9381
  var init_session_registry = __esm(() => {
9310
9382
  TTL_MS = 24 * 60 * 60 * 1000;
9311
9383
  REAPER_INTERVAL_MS = 30 * 1000;
9312
9384
  byToken = new Map;
9313
9385
  bySessionId = new Map;
9386
+ hooks = {};
9314
9387
  });
9315
9388
 
9316
9389
  // src/daemon/ipc/server.ts
@@ -9461,6 +9534,28 @@ function makeHandler(opts) {
9461
9534
  const cwd = typeof body.cwd === "string" ? body.cwd : undefined;
9462
9535
  const role = typeof body.role === "string" ? body.role : undefined;
9463
9536
  const groups = Array.isArray(body.groups) ? body.groups.filter((g) => typeof g === "string") : undefined;
9537
+ let presence;
9538
+ const rawPresence = body.presence;
9539
+ if (rawPresence && typeof rawPresence === "object") {
9540
+ const p = rawPresence;
9541
+ const sessionPubkey = typeof p.session_pubkey === "string" ? p.session_pubkey.toLowerCase() : "";
9542
+ const sessionSecretKey = typeof p.session_secret_key === "string" ? p.session_secret_key.toLowerCase() : "";
9543
+ const att = p.parent_attestation;
9544
+ if (/^[0-9a-f]{64}$/.test(sessionPubkey) && /^[0-9a-f]{128}$/.test(sessionSecretKey) && att && typeof att === "object" && typeof att.session_pubkey === "string" && typeof att.parent_member_pubkey === "string" && typeof att.expires_at === "number" && typeof att.signature === "string") {
9545
+ presence = {
9546
+ sessionPubkey,
9547
+ sessionSecretKey,
9548
+ parentAttestation: {
9549
+ sessionPubkey: att.session_pubkey.toLowerCase(),
9550
+ parentMemberPubkey: att.parent_member_pubkey.toLowerCase(),
9551
+ expiresAt: att.expires_at,
9552
+ signature: att.signature.toLowerCase()
9553
+ }
9554
+ };
9555
+ } else {
9556
+ opts.log("warn", "session_register_presence_malformed", { mesh });
9557
+ }
9558
+ }
9464
9559
  const stored = registerSession({
9465
9560
  token: token.toLowerCase(),
9466
9561
  sessionId,
@@ -9469,10 +9564,20 @@ function makeHandler(opts) {
9469
9564
  pid,
9470
9565
  cwd,
9471
9566
  role,
9472
- groups
9567
+ groups,
9568
+ ...presence ? { presence } : {}
9569
+ });
9570
+ opts.log("info", "session_registered", {
9571
+ sessionId,
9572
+ mesh,
9573
+ pid,
9574
+ presence: presence ? "yes" : "no"
9575
+ });
9576
+ respond(res, 200, {
9577
+ ok: true,
9578
+ registered_at: stored.registeredAt,
9579
+ presence_accepted: !!presence
9473
9580
  });
9474
- opts.log("info", "session_registered", { sessionId, mesh, pid });
9475
- respond(res, 200, { ok: true, registered_at: stored.registeredAt });
9476
9581
  } catch (e) {
9477
9582
  respond(res, 400, { error: String(e) });
9478
9583
  }
@@ -10573,9 +10678,168 @@ var init_broker = __esm(() => {
10573
10678
  BACKOFF_CAPS_MS = [1000, 2000, 4000, 8000, 16000, 30000];
10574
10679
  });
10575
10680
 
10681
+ // src/daemon/session-broker.ts
10682
+ import { hostname as osHostname } from "node:os";
10683
+ import WebSocket3 from "ws";
10684
+
10685
+ class SessionBrokerClient {
10686
+ opts;
10687
+ ws = null;
10688
+ _status = "closed";
10689
+ closed = false;
10690
+ reconnectAttempt = 0;
10691
+ reconnectTimer = null;
10692
+ helloTimer = null;
10693
+ constructor(opts) {
10694
+ this.opts = opts;
10695
+ }
10696
+ get status() {
10697
+ return this._status;
10698
+ }
10699
+ get meshSlug() {
10700
+ return this.opts.mesh.slug;
10701
+ }
10702
+ get sessionPubkey() {
10703
+ return this.opts.sessionPubkey;
10704
+ }
10705
+ log = (level, msg, meta) => {
10706
+ (this.opts.log ?? defaultLog2)(level, msg, {
10707
+ mesh: this.opts.mesh.slug,
10708
+ session_pubkey: this.opts.sessionPubkey.slice(0, 12),
10709
+ ...meta
10710
+ });
10711
+ };
10712
+ setStatus(s) {
10713
+ if (this._status === s)
10714
+ return;
10715
+ this._status = s;
10716
+ this.opts.onStatusChange?.(s);
10717
+ }
10718
+ async connect() {
10719
+ if (this.closed)
10720
+ throw new Error("client_closed");
10721
+ if (this._status === "connecting" || this._status === "open")
10722
+ return;
10723
+ this.setStatus("connecting");
10724
+ const ws = new WebSocket3(this.opts.mesh.brokerUrl);
10725
+ this.ws = ws;
10726
+ return new Promise((resolve, reject) => {
10727
+ ws.on("open", async () => {
10728
+ try {
10729
+ const { timestamp: timestamp2, signature } = await signSessionHello({
10730
+ meshId: this.opts.mesh.meshId,
10731
+ parentMemberPubkey: this.opts.mesh.pubkey,
10732
+ sessionPubkey: this.opts.sessionPubkey,
10733
+ sessionSecretKey: this.opts.sessionSecretKey
10734
+ });
10735
+ ws.send(JSON.stringify({
10736
+ type: "session_hello",
10737
+ meshId: this.opts.mesh.meshId,
10738
+ parentMemberId: this.opts.mesh.memberId,
10739
+ parentMemberPubkey: this.opts.mesh.pubkey,
10740
+ sessionPubkey: this.opts.sessionPubkey,
10741
+ parentAttestation: this.opts.parentAttestation,
10742
+ displayName: this.opts.displayName,
10743
+ sessionId: this.opts.sessionId,
10744
+ pid: this.opts.pid,
10745
+ cwd: this.opts.cwd ?? process.cwd(),
10746
+ hostname: osHostname(),
10747
+ peerType: "ai",
10748
+ channel: "claudemesh-session",
10749
+ ...this.opts.groups && this.opts.groups.length > 0 ? { groups: this.opts.groups } : {},
10750
+ ...this.opts.role ? { role: this.opts.role } : {},
10751
+ timestamp: timestamp2,
10752
+ signature
10753
+ }));
10754
+ this.helloTimer = setTimeout(() => {
10755
+ this.log("warn", "session_hello_ack_timeout");
10756
+ try {
10757
+ ws.close();
10758
+ } catch {}
10759
+ reject(new Error("session_hello_ack_timeout"));
10760
+ }, HELLO_ACK_TIMEOUT_MS3);
10761
+ } catch (e) {
10762
+ reject(e instanceof Error ? e : new Error(String(e)));
10763
+ }
10764
+ });
10765
+ ws.on("message", (raw) => {
10766
+ let msg;
10767
+ try {
10768
+ msg = JSON.parse(raw.toString());
10769
+ } catch {
10770
+ return;
10771
+ }
10772
+ if (msg.type === "hello_ack") {
10773
+ if (this.helloTimer)
10774
+ clearTimeout(this.helloTimer);
10775
+ this.helloTimer = null;
10776
+ this.setStatus("open");
10777
+ this.reconnectAttempt = 0;
10778
+ resolve();
10779
+ return;
10780
+ }
10781
+ if (msg.type === "error") {
10782
+ this.log("warn", "broker_error", { code: msg.code, message: msg.message });
10783
+ if (msg.code === "unknown_message_type") {
10784
+ this.closed = true;
10785
+ }
10786
+ return;
10787
+ }
10788
+ });
10789
+ ws.on("close", (code, reason) => {
10790
+ if (this.helloTimer) {
10791
+ clearTimeout(this.helloTimer);
10792
+ this.helloTimer = null;
10793
+ }
10794
+ if (this.closed) {
10795
+ this.setStatus("closed");
10796
+ return;
10797
+ }
10798
+ this.setStatus("reconnecting");
10799
+ const wait = BACKOFF_CAPS_MS2[Math.min(this.reconnectAttempt, BACKOFF_CAPS_MS2.length - 1)] ?? 30000;
10800
+ this.reconnectAttempt++;
10801
+ this.log("info", "session_broker_reconnect_scheduled", { wait_ms: wait, code, reason: reason.toString("utf8") });
10802
+ this.reconnectTimer = setTimeout(() => this.connect().catch((err) => this.log("warn", "session_broker_reconnect_failed", { err: String(err) })), wait);
10803
+ if (this._status === "connecting")
10804
+ reject(new Error(`closed_before_hello_${code}`));
10805
+ });
10806
+ ws.on("error", (err) => this.log("warn", "session_broker_ws_error", { err: err.message }));
10807
+ });
10808
+ }
10809
+ async close() {
10810
+ this.closed = true;
10811
+ if (this.reconnectTimer) {
10812
+ clearTimeout(this.reconnectTimer);
10813
+ this.reconnectTimer = null;
10814
+ }
10815
+ if (this.helloTimer) {
10816
+ clearTimeout(this.helloTimer);
10817
+ this.helloTimer = null;
10818
+ }
10819
+ try {
10820
+ this.ws?.close();
10821
+ } catch {}
10822
+ this.setStatus("closed");
10823
+ }
10824
+ }
10825
+ function defaultLog2(level, msg, meta) {
10826
+ const line = JSON.stringify({ level, msg, ...meta, ts: new Date().toISOString() });
10827
+ if (level === "info")
10828
+ process.stdout.write(line + `
10829
+ `);
10830
+ else
10831
+ process.stderr.write(line + `
10832
+ `);
10833
+ }
10834
+ var HELLO_ACK_TIMEOUT_MS3 = 5000, BACKOFF_CAPS_MS2;
10835
+ var init_session_broker = __esm(() => {
10836
+ init_session_hello_sig();
10837
+ BACKOFF_CAPS_MS2 = [1000, 2000, 4000, 8000, 16000, 30000];
10838
+ });
10839
+
10576
10840
  // src/daemon/drain.ts
10577
10841
  function startDrainWorker(opts) {
10578
- const log2 = opts.log ?? defaultLog2;
10842
+ const log2 = opts.log ?? defaultLog3;
10579
10843
  let stopped = false;
10580
10844
  let wakeResolve = null;
10581
10845
  let wakePromise = new Promise((r) => {
@@ -10719,7 +10983,7 @@ async function randomNonce2() {
10719
10983
  const { randomBytes: randomBytes6 } = await import("node:crypto");
10720
10984
  return randomBytes6(24).toString("base64");
10721
10985
  }
10722
- function defaultLog2(level, msg, meta) {
10986
+ function defaultLog3(level, msg, meta) {
10723
10987
  const line = JSON.stringify({ level, msg, ...meta, ts: new Date().toISOString() });
10724
10988
  if (level === "info")
10725
10989
  process.stdout.write(line + `
@@ -11062,6 +11326,56 @@ async function runDaemon(opts = {}) {
11062
11326
  }
11063
11327
  let drain = null;
11064
11328
  drain = startDrainWorker({ db: outboxDb, brokers });
11329
+ const sessionBrokers = new Map;
11330
+ setRegistryHooks({
11331
+ onRegister: (info) => {
11332
+ if (!info.presence)
11333
+ return;
11334
+ const meshConfig = meshConfigs.get(info.mesh);
11335
+ if (!meshConfig) {
11336
+ process.stderr.write(JSON.stringify({
11337
+ level: "warn",
11338
+ msg: "session_broker_no_mesh_config",
11339
+ mesh: info.mesh,
11340
+ ts: new Date().toISOString()
11341
+ }) + `
11342
+ `);
11343
+ return;
11344
+ }
11345
+ const prior = sessionBrokers.get(info.token);
11346
+ if (prior) {
11347
+ sessionBrokers.delete(info.token);
11348
+ prior.close().catch(() => {});
11349
+ }
11350
+ const client = new SessionBrokerClient({
11351
+ mesh: meshConfig,
11352
+ sessionPubkey: info.presence.sessionPubkey,
11353
+ sessionSecretKey: info.presence.sessionSecretKey,
11354
+ parentAttestation: info.presence.parentAttestation,
11355
+ sessionId: info.sessionId,
11356
+ displayName: info.displayName,
11357
+ ...info.role ? { role: info.role } : {},
11358
+ ...info.cwd ? { cwd: info.cwd } : {},
11359
+ pid: info.pid
11360
+ });
11361
+ sessionBrokers.set(info.token, client);
11362
+ client.connect().catch((err) => process.stderr.write(JSON.stringify({
11363
+ level: "warn",
11364
+ msg: "session_broker_connect_failed",
11365
+ mesh: info.mesh,
11366
+ err: String(err),
11367
+ ts: new Date().toISOString()
11368
+ }) + `
11369
+ `));
11370
+ },
11371
+ onDeregister: (info) => {
11372
+ const client = sessionBrokers.get(info.token);
11373
+ if (!client)
11374
+ return;
11375
+ sessionBrokers.delete(info.token);
11376
+ client.close().catch(() => {});
11377
+ }
11378
+ });
11065
11379
  startReaper();
11066
11380
  const ipc2 = startIpcServer({
11067
11381
  localToken,
@@ -11105,6 +11419,12 @@ async function runDaemon(opts = {}) {
11105
11419
  await b.close();
11106
11420
  } catch {}
11107
11421
  }
11422
+ for (const b of sessionBrokers.values()) {
11423
+ try {
11424
+ await b.close();
11425
+ } catch {}
11426
+ }
11427
+ sessionBrokers.clear();
11108
11428
  await ipc2.close();
11109
11429
  try {
11110
11430
  outboxDb.close();
@@ -11126,6 +11446,7 @@ var init_run = __esm(() => {
11126
11446
  init_server();
11127
11447
  init_session_registry();
11128
11448
  init_broker();
11449
+ init_session_broker();
11129
11450
  init_drain();
11130
11451
  init_inbound();
11131
11452
  init_identity();
@@ -11200,7 +11521,9 @@ function installDarwin(args) {
11200
11521
  const plist = darwinPlistPath();
11201
11522
  mkdirSync7(dirname5(plist), { recursive: true });
11202
11523
  const log2 = DAEMON_PATHS.LOG_FILE;
11524
+ const nodeBin = process.execPath;
11203
11525
  const meshArgs = [
11526
+ `<string>${escapeXml(args.binaryPath)}</string>`,
11204
11527
  "<string>daemon</string>",
11205
11528
  "<string>up</string>",
11206
11529
  "<string>--mesh</string>",
@@ -11216,7 +11539,7 @@ function installDarwin(args) {
11216
11539
  <string>${SERVICE_LABEL}</string>
11217
11540
  <key>ProgramArguments</key>
11218
11541
  <array>
11219
- <string>${escapeXml(args.binaryPath)}</string>
11542
+ <string>${escapeXml(nodeBin)}</string>
11220
11543
  ${meshArgs}
11221
11544
  </array>
11222
11545
  <key>RunAtLoad</key>
@@ -11240,6 +11563,20 @@ function installDarwin(args) {
11240
11563
  </plist>
11241
11564
  `;
11242
11565
  writeFileSync11(plist, xml, { mode: 420 });
11566
+ try {
11567
+ execSync2(`launchctl bootout gui/$(id -u)/${SERVICE_LABEL}`, { stdio: "ignore" });
11568
+ } catch {}
11569
+ try {
11570
+ const pidPath = DAEMON_PATHS.PID_FILE;
11571
+ if (existsSync12(pidPath)) {
11572
+ const pid = parseInt(readFileSync11(pidPath, "utf8").trim(), 10);
11573
+ if (Number.isFinite(pid) && pid > 0) {
11574
+ try {
11575
+ process.kill(pid, "SIGTERM");
11576
+ } catch {}
11577
+ }
11578
+ }
11579
+ } catch {}
11243
11580
  return {
11244
11581
  platform: "darwin",
11245
11582
  unitPath: plist,
@@ -11252,6 +11589,7 @@ function linuxUnitPath() {
11252
11589
  function installLinux(args) {
11253
11590
  const unit = linuxUnitPath();
11254
11591
  mkdirSync7(dirname5(unit), { recursive: true });
11592
+ const nodeBin = process.execPath;
11255
11593
  const execArgs = [
11256
11594
  "daemon",
11257
11595
  "up",
@@ -11266,7 +11604,7 @@ Wants=network-online.target
11266
11604
 
11267
11605
  [Service]
11268
11606
  Type=simple
11269
- ExecStart=${shellQuote(args.binaryPath)} ${execArgs}
11607
+ ExecStart=${shellQuote(nodeBin)} ${shellQuote(args.binaryPath)} ${execArgs}
11270
11608
  Restart=always
11271
11609
  RestartSec=3
11272
11610
  StandardOutput=append:${DAEMON_PATHS.LOG_FILE}
@@ -11277,6 +11615,20 @@ Environment=PATH=/usr/local/bin:/usr/bin:/bin
11277
11615
  WantedBy=default.target
11278
11616
  `;
11279
11617
  writeFileSync11(unit, content, { mode: 420 });
11618
+ try {
11619
+ execSync2(`systemctl --user stop ${SYSTEMD_UNIT}`, { stdio: "ignore" });
11620
+ } catch {}
11621
+ try {
11622
+ const pidPath = DAEMON_PATHS.PID_FILE;
11623
+ if (existsSync12(pidPath)) {
11624
+ const pid = parseInt(readFileSync11(pidPath, "utf8").trim(), 10);
11625
+ if (Number.isFinite(pid) && pid > 0) {
11626
+ try {
11627
+ process.kill(pid, "SIGTERM");
11628
+ } catch {}
11629
+ }
11630
+ }
11631
+ } catch {}
11280
11632
  return {
11281
11633
  platform: "linux",
11282
11634
  unitPath: unit,
@@ -11831,11 +12183,11 @@ function uninstallAllowedTools() {
11831
12183
  }
11832
12184
  function installHooks() {
11833
12185
  const settings = readClaudeSettings();
11834
- const hooks = (settings.hooks ??= {}) ?? {};
12186
+ const hooks2 = (settings.hooks ??= {}) ?? {};
11835
12187
  let added = 0;
11836
12188
  let unchanged = 0;
11837
12189
  const ensure = (event, command) => {
11838
- const list = hooks[event] ??= [];
12190
+ const list = hooks2[event] ??= [];
11839
12191
  const alreadyPresent = list.some((entry) => (entry.hooks ?? []).some((h) => h.command === command));
11840
12192
  if (alreadyPresent) {
11841
12193
  unchanged += 1;
@@ -11846,7 +12198,7 @@ function installHooks() {
11846
12198
  };
11847
12199
  ensure("Stop", HOOK_COMMAND_STOP);
11848
12200
  ensure("UserPromptSubmit", HOOK_COMMAND_USER_PROMPT);
11849
- settings.hooks = hooks;
12201
+ settings.hooks = hooks2;
11850
12202
  writeClaudeSettings(settings);
11851
12203
  return { added, unchanged };
11852
12204
  }
@@ -11854,24 +12206,24 @@ function uninstallHooks() {
11854
12206
  if (!existsSync13(CLAUDE_SETTINGS))
11855
12207
  return 0;
11856
12208
  const settings = readClaudeSettings();
11857
- const hooks = settings.hooks;
11858
- if (!hooks)
12209
+ const hooks2 = settings.hooks;
12210
+ if (!hooks2)
11859
12211
  return 0;
11860
12212
  let removed = 0;
11861
- for (const event of Object.keys(hooks)) {
12213
+ for (const event of Object.keys(hooks2)) {
11862
12214
  const kept = [];
11863
- for (const entry of hooks[event] ?? []) {
12215
+ for (const entry of hooks2[event] ?? []) {
11864
12216
  const filtered = (entry.hooks ?? []).filter((h) => !(h.command ?? "").includes(HOOK_MARKER));
11865
12217
  removed += (entry.hooks ?? []).length - filtered.length;
11866
12218
  if (filtered.length > 0)
11867
12219
  kept.push({ ...entry, hooks: filtered });
11868
12220
  }
11869
12221
  if (kept.length === 0)
11870
- delete hooks[event];
12222
+ delete hooks2[event];
11871
12223
  else
11872
- hooks[event] = kept;
12224
+ hooks2[event] = kept;
11873
12225
  }
11874
- settings.hooks = hooks;
12226
+ settings.hooks = hooks2;
11875
12227
  writeClaudeSettings(settings);
11876
12228
  return removed;
11877
12229
  }
@@ -12176,10 +12528,10 @@ async function uninstall() {
12176
12528
  try {
12177
12529
  const raw = readFileSync13(PATHS.CLAUDE_SETTINGS, "utf-8");
12178
12530
  const config = JSON.parse(raw);
12179
- const hooks = config.hooks;
12180
- if (hooks) {
12531
+ const hooks2 = config.hooks;
12532
+ if (hooks2) {
12181
12533
  let removedHooks = 0;
12182
- for (const [event, entries] of Object.entries(hooks)) {
12534
+ for (const [event, entries] of Object.entries(hooks2)) {
12183
12535
  if (!Array.isArray(entries))
12184
12536
  continue;
12185
12537
  const filtered = entries.filter((h) => {
@@ -12189,9 +12541,9 @@ async function uninstall() {
12189
12541
  if (filtered.length < entries.length) {
12190
12542
  removedHooks += entries.length - filtered.length;
12191
12543
  if (filtered.length === 0)
12192
- delete hooks[event];
12544
+ delete hooks2[event];
12193
12545
  else
12194
- hooks[event] = filtered;
12546
+ hooks2[event] = filtered;
12195
12547
  }
12196
12548
  }
12197
12549
  if (removedHooks > 0) {
@@ -12393,8 +12745,8 @@ async function checkBrokerWs() {
12393
12745
  const wsUrl = URLS.BROKER;
12394
12746
  const start = Date.now();
12395
12747
  try {
12396
- const WebSocket3 = (await import("ws")).default;
12397
- const ws = new WebSocket3(wsUrl);
12748
+ const WebSocket4 = (await import("ws")).default;
12749
+ const ws = new WebSocket4(wsUrl);
12398
12750
  const result = await new Promise((resolve2) => {
12399
12751
  const timer = setTimeout(() => {
12400
12752
  try {
@@ -12505,11 +12857,11 @@ __export(exports_status, {
12505
12857
  runStatus: () => runStatus2
12506
12858
  });
12507
12859
  import { statSync as statSync4, existsSync as existsSync16 } from "node:fs";
12508
- import WebSocket3 from "ws";
12860
+ import WebSocket4 from "ws";
12509
12861
  async function probeBroker(url, timeoutMs = 4000) {
12510
12862
  return new Promise((resolve2) => {
12511
12863
  const started = Date.now();
12512
- const ws = new WebSocket3(url);
12864
+ const ws = new WebSocket4(url);
12513
12865
  const timer = setTimeout(() => {
12514
12866
  try {
12515
12867
  ws.terminate();
@@ -14530,17 +14882,17 @@ async function runUnwatch(id, opts) {
14530
14882
  }
14531
14883
  async function runWebhookList(opts) {
14532
14884
  return await withMesh({ meshSlug: opts.mesh ?? null }, async (client) => {
14533
- const hooks = await client.listWebhooks();
14885
+ const hooks2 = await client.listWebhooks();
14534
14886
  if (opts.json) {
14535
- emitJson(hooks);
14887
+ emitJson(hooks2);
14536
14888
  return EXIT.SUCCESS;
14537
14889
  }
14538
- if (hooks.length === 0) {
14890
+ if (hooks2.length === 0) {
14539
14891
  render.info(dim("(no webhooks)"));
14540
14892
  return EXIT.SUCCESS;
14541
14893
  }
14542
- render.section(`webhooks (${hooks.length})`);
14543
- for (const h of hooks) {
14894
+ render.section(`webhooks (${hooks2.length})`);
14895
+ for (const h of hooks2) {
14544
14896
  const dot = h.active ? "●" : dim("○");
14545
14897
  process.stdout.write(` ${dot} ${bold(h.name)} ${dim("· " + h.url)}
14546
14898
  `);
@@ -15511,7 +15863,7 @@ __export(exports_file, {
15511
15863
  runFileShare: () => runFileShare,
15512
15864
  runFileGet: () => runFileGet
15513
15865
  });
15514
- import { hostname as osHostname } from "node:os";
15866
+ import { hostname as osHostname2 } from "node:os";
15515
15867
  import { resolve as resolvePath, basename, dirname as dirname9 } from "node:path";
15516
15868
  import { statSync as statSync7, existsSync as existsSync26, writeFileSync as writeFileSync17, mkdirSync as mkdirSync11 } from "node:fs";
15517
15869
  function emitJson2(data) {
@@ -15543,7 +15895,7 @@ async function runFileShare(filePath, opts) {
15543
15895
  return await withMesh({ meshSlug: opts.mesh ?? null }, async (client, mesh) => {
15544
15896
  if (opts.to && !opts.upload) {
15545
15897
  const peers = await client.listPeers();
15546
- const myHost = osHostname();
15898
+ const myHost = osHostname2();
15547
15899
  const target = peers.find((p) => {
15548
15900
  if (!p.hostname || p.hostname !== myHost)
15549
15901
  return false;
@@ -15705,7 +16057,7 @@ var require_client = __commonJS((exports) => {
15705
16057
  var ws_1 = __importDefault(__require("ws"));
15706
16058
  var crypto_js_1 = require_crypto();
15707
16059
  var MAX_QUEUED2 = 100;
15708
- var HELLO_ACK_TIMEOUT_MS3 = 5000;
16060
+ var HELLO_ACK_TIMEOUT_MS4 = 5000;
15709
16061
  var BACKOFF_CAPS2 = [1000, 2000, 4000, 8000, 16000, 30000];
15710
16062
 
15711
16063
  class MeshClient extends node_events_1.EventEmitter {
@@ -15770,7 +16122,7 @@ var require_client = __commonJS((exports) => {
15770
16122
  this.debug("hello_ack timeout");
15771
16123
  ws.close();
15772
16124
  reject(new Error("hello_ack timeout"));
15773
- }, HELLO_ACK_TIMEOUT_MS3);
16125
+ }, HELLO_ACK_TIMEOUT_MS4);
15774
16126
  };
15775
16127
  const onMessage = (raw) => {
15776
16128
  let msg;
@@ -19550,4 +19902,4 @@ main().catch((err) => {
19550
19902
  process.exit(EXIT.INTERNAL_ERROR);
19551
19903
  });
19552
19904
 
19553
- //# debugId=FBD494B01BF81C7164756E2164756E21
19905
+ //# debugId=6DFC8CA65CBA4E0364756E2164756E21