@hasna/machines 0.0.45 → 0.0.46

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.
Files changed (109) hide show
  1. package/README.md +27 -4
  2. package/dist/agent/index.d.ts +0 -1
  3. package/dist/agent/index.js +249 -14
  4. package/dist/agent/runtime.d.ts +0 -1
  5. package/dist/cli/index.d.ts +0 -1
  6. package/dist/cli/index.js +1301 -210
  7. package/dist/cli-utils.d.ts +0 -1
  8. package/dist/commands/apps.d.ts +7 -5
  9. package/dist/commands/backup.d.ts +0 -1
  10. package/dist/commands/cert.d.ts +0 -1
  11. package/dist/commands/clipboard-daemon.d.ts +0 -1
  12. package/dist/commands/clipboard-server.d.ts +0 -1
  13. package/dist/commands/clipboard.d.ts +0 -1
  14. package/dist/commands/daemon.d.ts +0 -1
  15. package/dist/commands/diff.d.ts +0 -1
  16. package/dist/commands/dns.d.ts +0 -1
  17. package/dist/commands/doctor.d.ts +0 -1
  18. package/dist/commands/heal-daemon.d.ts +0 -1
  19. package/dist/commands/heal.d.ts +0 -1
  20. package/dist/commands/install-claude.d.ts +5 -3
  21. package/dist/commands/install-tailscale.d.ts +5 -3
  22. package/dist/commands/manifest.d.ts +0 -1
  23. package/dist/commands/mutation-approval.d.ts +54 -0
  24. package/dist/commands/notifications.d.ts +14 -2
  25. package/dist/commands/ports.d.ts +0 -1
  26. package/dist/commands/runtime.d.ts +15 -1
  27. package/dist/commands/screen.d.ts +4 -1
  28. package/dist/commands/self-test.d.ts +0 -1
  29. package/dist/commands/serve.d.ts +0 -1
  30. package/dist/commands/setup.d.ts +5 -3
  31. package/dist/commands/ssh.d.ts +8 -1
  32. package/dist/commands/status.d.ts +0 -1
  33. package/dist/commands/sync.d.ts +5 -3
  34. package/dist/commands/workspace.d.ts +0 -1
  35. package/dist/compatibility.d.ts +0 -1
  36. package/dist/consumer-schema.d.ts +0 -1
  37. package/dist/consumer.d.ts +0 -1
  38. package/dist/consumer.js +253 -12
  39. package/dist/cross-project-types.d.ts +0 -1
  40. package/dist/db.d.ts +0 -1
  41. package/dist/index.d.ts +2 -2
  42. package/dist/index.js +1091 -184
  43. package/dist/manifests.d.ts +0 -1
  44. package/dist/mcp/http.d.ts +26 -2
  45. package/dist/mcp/index.d.ts +0 -1
  46. package/dist/mcp/index.js +1004 -162
  47. package/dist/mcp/server.d.ts +5 -3
  48. package/dist/paths.d.ts +0 -1
  49. package/dist/pg-migrations.d.ts +0 -1
  50. package/dist/redaction.d.ts +0 -1
  51. package/dist/remote-storage.d.ts +0 -1
  52. package/dist/remote.d.ts +14 -5
  53. package/dist/storage-sync.d.ts +0 -1
  54. package/dist/storage.d.ts +0 -1
  55. package/dist/storage.js +18 -0
  56. package/dist/topology.d.ts +0 -1
  57. package/dist/types.d.ts +3 -1
  58. package/dist/version.d.ts +0 -1
  59. package/package.json +5 -3
  60. package/dist/agent/index.d.ts.map +0 -1
  61. package/dist/agent/runtime.d.ts.map +0 -1
  62. package/dist/cli/index.d.ts.map +0 -1
  63. package/dist/cli-utils.d.ts.map +0 -1
  64. package/dist/commands/apps.d.ts.map +0 -1
  65. package/dist/commands/backup.d.ts.map +0 -1
  66. package/dist/commands/cert.d.ts.map +0 -1
  67. package/dist/commands/clipboard-daemon.d.ts.map +0 -1
  68. package/dist/commands/clipboard-server.d.ts.map +0 -1
  69. package/dist/commands/clipboard.d.ts.map +0 -1
  70. package/dist/commands/daemon.d.ts.map +0 -1
  71. package/dist/commands/diff.d.ts.map +0 -1
  72. package/dist/commands/dns.d.ts.map +0 -1
  73. package/dist/commands/doctor.d.ts.map +0 -1
  74. package/dist/commands/heal-daemon.d.ts.map +0 -1
  75. package/dist/commands/heal.d.ts.map +0 -1
  76. package/dist/commands/install-claude.d.ts.map +0 -1
  77. package/dist/commands/install-tailscale.d.ts.map +0 -1
  78. package/dist/commands/manifest.d.ts.map +0 -1
  79. package/dist/commands/notifications.d.ts.map +0 -1
  80. package/dist/commands/ports.d.ts.map +0 -1
  81. package/dist/commands/runtime.d.ts.map +0 -1
  82. package/dist/commands/screen.d.ts.map +0 -1
  83. package/dist/commands/self-test.d.ts.map +0 -1
  84. package/dist/commands/serve.d.ts.map +0 -1
  85. package/dist/commands/setup.d.ts.map +0 -1
  86. package/dist/commands/ssh.d.ts.map +0 -1
  87. package/dist/commands/status.d.ts.map +0 -1
  88. package/dist/commands/sync.d.ts.map +0 -1
  89. package/dist/commands/workspace.d.ts.map +0 -1
  90. package/dist/compatibility.d.ts.map +0 -1
  91. package/dist/consumer-schema.d.ts.map +0 -1
  92. package/dist/consumer.d.ts.map +0 -1
  93. package/dist/cross-project-types.d.ts.map +0 -1
  94. package/dist/db.d.ts.map +0 -1
  95. package/dist/index.d.ts.map +0 -1
  96. package/dist/manifests.d.ts.map +0 -1
  97. package/dist/mcp/http.d.ts.map +0 -1
  98. package/dist/mcp/index.d.ts.map +0 -1
  99. package/dist/mcp/server.d.ts.map +0 -1
  100. package/dist/paths.d.ts.map +0 -1
  101. package/dist/pg-migrations.d.ts.map +0 -1
  102. package/dist/redaction.d.ts.map +0 -1
  103. package/dist/remote-storage.d.ts.map +0 -1
  104. package/dist/remote.d.ts.map +0 -1
  105. package/dist/storage-sync.d.ts.map +0 -1
  106. package/dist/storage.d.ts.map +0 -1
  107. package/dist/topology.d.ts.map +0 -1
  108. package/dist/types.d.ts.map +0 -1
  109. package/dist/version.d.ts.map +0 -1
package/README.md CHANGED
@@ -23,6 +23,12 @@ Endpoints on `127.0.0.1` only:
23
23
  - `GET /health` → `{"status":"ok","name":"machines"}`
24
24
  - `POST /mcp` → MCP Streamable HTTP
25
25
 
26
+ HTTP mode rejects browser requests with untrusted `Origin` headers, caps JSON
27
+ bodies at `MACHINES_HTTP_MAX_BODY_BYTES` (default 1 MiB), and requires either
28
+ `MACHINES_API_KEY` or loopback-only `MACHINES_ALLOW_UNAUTHENTICATED=1`. Use
29
+ `MACHINES_HTTP_ALLOWED_ORIGINS=https://ops.example` for an explicit browser
30
+ origin allowlist.
31
+
26
32
  ## Manifest
27
33
 
28
34
  `machines.json` is the desired fleet declaration.
@@ -373,6 +379,7 @@ machines install-tailscale --machine mac-lab-01 --json
373
379
 
374
380
  ```bash
375
381
  machines notifications add --id ops --type webhook --target https://example.com/hook --event sync_failed
382
+ machines notifications add --id cmd --type command --target /bin/sh --arg -c --arg 'printf "%s\n" "$HASNA_MACHINES_NOTIFICATION_EVENT"'
376
383
  machines notifications list
377
384
  machines notifications test --channel ops
378
385
  machines notifications test --channel ops --apply --yes
@@ -381,7 +388,9 @@ machines notifications dispatch --event manual.test --message "hello fleet"
381
388
 
382
389
  - `email` channels deliver through local `sendmail` or `mail` when available
383
390
  - `webhook` channels deliver JSON via HTTP POST
384
- - `command` channels execute the configured command with `HASNA_MACHINES_NOTIFICATION_*` env vars
391
+ - `command` channels execute an explicit command executable plus optional `--arg`
392
+ values with `HASNA_MACHINES_NOTIFICATION_*` env vars; use `/bin/sh -c ...`
393
+ explicitly if a shell is required
385
394
 
386
395
  ## Runtime Events
387
396
 
@@ -390,19 +399,31 @@ events without sending keys, killing panes, or changing tmux state.
390
399
 
391
400
  ```bash
392
401
  machines runtime tmux-watch %11 --once --json
393
- machines runtime tmux-watch session:0.1 --interval-ms 5000
402
+ machines runtime tmux-watch session:0.1 --interval-ms 5000 --approval-token "$TOKEN"
403
+ machines runtime tmux-hook-plan --trusted-local-mutation --json
404
+ machines runtime tmux-hook-plan --approval-token "$TOKEN"
394
405
  machines webhooks add https://example.com/hook --id tmux-alerts --type machines.tmux.pane_died
395
406
  ```
396
407
 
397
408
  When a pane was present and later disappears, the command records
398
409
  `machines.tmux.pane_died`. With `--once`, a missing pane records
399
410
  `machines.tmux.pane_missing`; add `--no-deliver` to record without webhook
400
- delivery.
411
+ delivery. Runtime event delivery requires a scoped mutation approval token; local
412
+ no-deliver recording remains available for diagnostics.
413
+
414
+ `machines runtime tmux-hook-plan` prints a native tmux `pane-died` hook command
415
+ for operators that prefer tmux hooks over polling. It is read-only and does not
416
+ install hooks. Pass `--approval-token` when you want the generated hook command
417
+ to be scoped to a short-lived approval token, or pass
418
+ `--trusted-local-mutation` to generate a process-local
419
+ `HASNA_MACHINES_ALLOW_MUTATIONS=1` prefix for local event recording.
401
420
 
402
421
  ## Dashboard
403
422
 
404
423
  ```bash
405
424
  machines serve --json
425
+ machines serve --port 7676
426
+ # Explicitly expose beyond loopback only on a trusted network:
406
427
  machines serve --host 0.0.0.0 --port 7676
407
428
  ```
408
429
 
@@ -416,13 +437,15 @@ The dashboard exposes:
416
437
  - `/api/daemon/status` daemon heartbeat rows
417
438
  - `/api/manifest` current manifest JSON
418
439
  - `/api/notifications` notification channel JSON
440
+ - `/api/webhooks` shared event webhook channel JSON
441
+ - `/api/events` shared event JSON
419
442
  - `/api/doctor` doctor report JSON
420
443
  - `/api/self-test` smoke-check JSON
421
444
  - `/api/apps/status` app inventory JSON
422
445
  - `/api/apps/diff` app drift JSON
423
446
  - `/api/install-claude/status` CLI inventory JSON
424
447
  - `/api/install-claude/diff` CLI drift JSON
425
- - `/api/notifications/test` POST endpoint for test delivery
448
+ - `/api/events`, `/api/notifications/test`, `/api/webhooks/test` POST mutation routes require scoped dashboard mutation approval tokens
426
449
 
427
450
  ## Local development
428
451
 
@@ -1,3 +1,2 @@
1
1
  #!/usr/bin/env bun
2
2
  export {};
3
- //# sourceMappingURL=index.d.ts.map
@@ -6999,6 +6999,7 @@ class SqliteAdapter {
6999
6999
  raw;
7000
7000
  constructor(path) {
7001
7001
  this.raw = new Database(path);
7002
+ this.raw.exec("PRAGMA busy_timeout = 5000");
7002
7003
  }
7003
7004
  close() {
7004
7005
  this.raw.close();
@@ -7064,6 +7065,23 @@ function createTables(db) {
7064
7065
  updated_at TEXT NOT NULL
7065
7066
  )
7066
7067
  `);
7068
+ db.exec(`
7069
+ CREATE TABLE IF NOT EXISTS mutation_approval_nonces (
7070
+ nonce_sha256 TEXT PRIMARY KEY,
7071
+ token_sha256 TEXT NOT NULL,
7072
+ surface TEXT NOT NULL,
7073
+ operation TEXT NOT NULL,
7074
+ caller_id TEXT NOT NULL,
7075
+ run_id TEXT NOT NULL,
7076
+ transport TEXT NOT NULL,
7077
+ expires_at INTEGER NOT NULL,
7078
+ used_at INTEGER NOT NULL
7079
+ )
7080
+ `);
7081
+ db.exec(`
7082
+ CREATE INDEX IF NOT EXISTS mutation_approval_nonces_expires_at_idx
7083
+ ON mutation_approval_nonces (expires_at)
7084
+ `);
7067
7085
  }
7068
7086
  function migrateAgentHeartbeats(db) {
7069
7087
  const columns = db.query("PRAGMA table_info(agent_heartbeats)").all();
@@ -11387,7 +11405,9 @@ function readManifestWithSource(options = {}) {
11387
11405
 
11388
11406
  // src/remote.ts
11389
11407
  import { spawnSync as spawnSync2 } from "child_process";
11390
- import { hostname as hostname3 } from "os";
11408
+ import { existsSync as existsSync5, mkdtempSync, readFileSync as readFileSync3, rmSync } from "fs";
11409
+ import { hostname as hostname3, tmpdir } from "os";
11410
+ import { join as join3 } from "path";
11391
11411
 
11392
11412
  // src/topology.ts
11393
11413
  import { existsSync as existsSync4 } from "fs";
@@ -11845,6 +11865,16 @@ function resolveMachineRoute(machineId, options = {}) {
11845
11865
  function shellQuote(value) {
11846
11866
  return `'${value.replace(/'/g, "'\\''")}'`;
11847
11867
  }
11868
+ function validateSshTarget(target) {
11869
+ const trimmed = target.trim();
11870
+ if (!trimmed || trimmed.startsWith("-") || /[\s"'`$\\;&|<>()[\]{}]/.test(trimmed)) {
11871
+ throw new Error(`Unsafe SSH target: ${target}`);
11872
+ }
11873
+ if (!/^(?:[A-Za-z0-9._%+-]+@)?[A-Za-z0-9._:-]+$/.test(trimmed)) {
11874
+ throw new Error(`Unsafe SSH target: ${target}`);
11875
+ }
11876
+ return trimmed;
11877
+ }
11848
11878
  function resolveSshTarget(machineId, options = {}) {
11849
11879
  const resolved = resolveMachineRoute(machineId, options);
11850
11880
  if (!resolved.ok || !resolved.target) {
@@ -11855,15 +11885,22 @@ function resolveSshTarget(machineId, options = {}) {
11855
11885
  }
11856
11886
  return {
11857
11887
  machineId: resolved.machine_id ?? machineId,
11858
- target: resolved.command_target ?? resolved.target,
11888
+ target: validateSshTarget(resolved.command_target ?? resolved.target),
11859
11889
  route: resolved.route,
11860
11890
  confidence: resolved.confidence,
11861
11891
  warnings: resolved.warnings
11862
11892
  };
11863
11893
  }
11864
- function buildSshCommand(machineId, remoteCommand, options = {}) {
11894
+ function buildSshCommandPlan(machineId, remoteCommand, options = {}) {
11865
11895
  const resolved = resolveSshTarget(machineId, options);
11866
- return remoteCommand ? `ssh ${resolved.target} ${shellQuote(remoteCommand)}` : `ssh ${resolved.target}`;
11896
+ const args = remoteCommand ? [resolved.target, remoteCommand] : [resolved.target];
11897
+ const shellCommand = `ssh ${args.map(shellQuote).join(" ")}`;
11898
+ return {
11899
+ ...resolved,
11900
+ command: "ssh",
11901
+ args,
11902
+ shellCommand
11903
+ };
11867
11904
  }
11868
11905
 
11869
11906
  // src/remote.ts
@@ -11875,35 +11912,233 @@ function machineIsLocal(machineId, localMachineId) {
11875
11912
  }
11876
11913
  function resolveMachineCommand(machineId, command, localMachineId = getLocalMachineId()) {
11877
11914
  if (machineIsLocal(machineId, localMachineId)) {
11878
- return { source: "local", shellCommand: command };
11915
+ return { source: "local", command: "bash", args: ["-c", command], shellCommand: command, usesShell: true };
11879
11916
  }
11880
11917
  try {
11918
+ const plan = buildSshCommandPlan(machineId, command);
11881
11919
  return {
11882
- source: resolveSshTarget(machineId).route,
11883
- shellCommand: buildSshCommand(machineId, command)
11920
+ source: plan.route,
11921
+ command: plan.command,
11922
+ args: plan.args,
11923
+ shellCommand: plan.shellCommand,
11924
+ usesShell: false
11884
11925
  };
11885
11926
  } catch (error) {
11886
11927
  const message = String(error.message ?? error);
11887
11928
  if (message.includes("Machine route not found") || message.includes("Machine not found in manifest")) {
11888
- return { source: "ssh", shellCommand: `ssh ${shellQuote2(machineId)} ${shellQuote2(command)}` };
11929
+ const target = validateSshTarget(machineId);
11930
+ return {
11931
+ source: "ssh",
11932
+ command: "ssh",
11933
+ args: [target, command],
11934
+ shellCommand: `ssh ${shellQuote2(target)} ${shellQuote2(command)}`,
11935
+ usesShell: false
11936
+ };
11889
11937
  }
11890
11938
  throw error;
11891
11939
  }
11892
11940
  }
11893
- function runMachineCommand(machineId, command) {
11941
+ function runMachineCommand(machineId, command, options = {}) {
11894
11942
  const resolved = resolveMachineCommand(machineId, command);
11895
- const result = spawnSync2("bash", ["-c", resolved.shellCommand], {
11943
+ if (options.timeoutMs && options.timeoutMs > 0 && process.platform !== "win32") {
11944
+ return runMachineCommandWithProcessGroupTimeout(machineId, resolved, options);
11945
+ }
11946
+ const result = spawnSync2(resolved.command, resolved.args, {
11896
11947
  encoding: "utf8",
11897
- env: process.env
11948
+ env: process.env,
11949
+ timeout: options.timeoutMs,
11950
+ killSignal: "SIGTERM"
11898
11951
  });
11952
+ const timedOut = Boolean(result.error && "code" in result.error && result.error.code === "ETIMEDOUT");
11953
+ const timeoutMessage = timedOut ? `Command timed out after ${options.timeoutMs}ms.` : "";
11954
+ const stderr = [result.stderr || "", timeoutMessage].filter(Boolean).join(result.stderr ? `
11955
+ ` : "");
11899
11956
  return {
11900
11957
  machineId,
11901
11958
  source: resolved.source,
11902
11959
  stdout: result.stdout || "",
11903
- stderr: result.stderr || "",
11904
- exitCode: result.status ?? 1
11905
- };
11960
+ stderr,
11961
+ exitCode: timedOut ? 124 : result.status ?? 1,
11962
+ timedOut,
11963
+ signal: result.signal
11964
+ };
11965
+ }
11966
+ function runMachineCommandWithProcessGroupTimeout(machineId, resolved, options) {
11967
+ const timeoutMs = Math.max(1, options.timeoutMs ?? 1);
11968
+ const killGraceMs = Math.max(1, options.killGraceMs ?? 1000);
11969
+ const helperDir = mkdtempSync(join3(tmpdir(), "machines-timeout-helper-"));
11970
+ const pgidFile = join3(helperDir, "pgid");
11971
+ const helper = spawnSync2(process.execPath, ["--eval", PROCESS_GROUP_TIMEOUT_HELPER], {
11972
+ input: JSON.stringify({ command: resolved.command, args: resolved.args }),
11973
+ encoding: "utf8",
11974
+ env: {
11975
+ ...process.env,
11976
+ HASNA_MACHINES_COMMAND_TIMEOUT_MS: String(timeoutMs),
11977
+ HASNA_MACHINES_COMMAND_KILL_GRACE_MS: String(killGraceMs),
11978
+ HASNA_MACHINES_COMMAND_PGID_FILE: pgidFile
11979
+ },
11980
+ timeout: timeoutMs + killGraceMs + 2000,
11981
+ killSignal: "SIGKILL",
11982
+ maxBuffer: 64 * 1024 * 1024
11983
+ });
11984
+ try {
11985
+ const parsed = parseHelperResult(helper.stdout);
11986
+ if (parsed) {
11987
+ return {
11988
+ machineId,
11989
+ source: resolved.source,
11990
+ stdout: parsed.stdout,
11991
+ stderr: parsed.stderr,
11992
+ exitCode: parsed.exitCode,
11993
+ timedOut: parsed.timedOut,
11994
+ signal: parsed.signal
11995
+ };
11996
+ }
11997
+ const helperTimedOut = Boolean(helper.error && "code" in helper.error && helper.error.code === "ETIMEDOUT");
11998
+ if (helperTimedOut)
11999
+ killPublishedProcessGroup(pgidFile);
12000
+ const timeoutMessage = helperTimedOut ? `Command timed out after ${timeoutMs}ms; timeout helper exceeded cleanup grace ${killGraceMs}ms.` : "";
12001
+ const stderr = [helper.stderr || "", timeoutMessage].filter(Boolean).join(helper.stderr ? `
12002
+ ` : "");
12003
+ return {
12004
+ machineId,
12005
+ source: resolved.source,
12006
+ stdout: "",
12007
+ stderr,
12008
+ exitCode: helperTimedOut ? 124 : helper.status ?? 1,
12009
+ timedOut: helperTimedOut,
12010
+ signal: helper.signal
12011
+ };
12012
+ } finally {
12013
+ rmSync(helperDir, { recursive: true, force: true });
12014
+ }
12015
+ }
12016
+ function killPublishedProcessGroup(pgidFile) {
12017
+ if (!existsSync5(pgidFile))
12018
+ return;
12019
+ try {
12020
+ const pid = Number.parseInt(readFileSync3(pgidFile, "utf8").trim(), 10);
12021
+ if (!Number.isInteger(pid) || pid <= 1)
12022
+ return;
12023
+ process.kill(-pid, "SIGKILL");
12024
+ } catch {}
11906
12025
  }
12026
+ function parseHelperResult(stdout) {
12027
+ if (!stdout)
12028
+ return null;
12029
+ try {
12030
+ const parsed = JSON.parse(stdout);
12031
+ if (typeof parsed.stdout !== "string" || typeof parsed.stderr !== "string" || typeof parsed.exitCode !== "number")
12032
+ return null;
12033
+ return {
12034
+ machineId: "",
12035
+ source: "local",
12036
+ stdout: parsed.stdout,
12037
+ stderr: parsed.stderr,
12038
+ exitCode: parsed.exitCode,
12039
+ timedOut: parsed.timedOut === true,
12040
+ signal: typeof parsed.signal === "string" ? parsed.signal : null
12041
+ };
12042
+ } catch {
12043
+ return null;
12044
+ }
12045
+ }
12046
+ var PROCESS_GROUP_TIMEOUT_HELPER = `
12047
+ const { spawn } = require("node:child_process");
12048
+ const { readFileSync, writeFileSync } = require("node:fs");
12049
+
12050
+ const plan = JSON.parse(readFileSync(0, "utf8"));
12051
+ const command = String(plan.command || "");
12052
+ const args = Array.isArray(plan.args) ? plan.args.map(String) : [];
12053
+ const timeoutMs = Math.max(1, Number.parseInt(process.env.HASNA_MACHINES_COMMAND_TIMEOUT_MS || "1", 10));
12054
+ const killGraceMs = Math.max(1, Number.parseInt(process.env.HASNA_MACHINES_COMMAND_KILL_GRACE_MS || "1000", 10));
12055
+ const pgidFile = process.env.HASNA_MACHINES_COMMAND_PGID_FILE || "";
12056
+ let stdout = "";
12057
+ let stderr = "";
12058
+ let timedOut = false;
12059
+ let finished = false;
12060
+ let timeoutTimer;
12061
+ let killTimer;
12062
+ let sigkillSent = false;
12063
+ let pendingExit = null;
12064
+
12065
+ const child = spawn(command, args, {
12066
+ detached: true,
12067
+ stdio: ["ignore", "pipe", "pipe"],
12068
+ env: process.env,
12069
+ });
12070
+
12071
+ if (pgidFile && child.pid) {
12072
+ try {
12073
+ writeFileSync(pgidFile, String(child.pid), { mode: 0o600 });
12074
+ } catch {}
12075
+ }
12076
+
12077
+ function appendText(target, chunk) {
12078
+ return target + String(chunk);
12079
+ }
12080
+
12081
+ function killTarget(signal) {
12082
+ if (!child.pid) return;
12083
+ if (process.platform === "win32") {
12084
+ try {
12085
+ process.kill(child.pid, signal);
12086
+ } catch {}
12087
+ return;
12088
+ }
12089
+ try {
12090
+ process.kill(-child.pid, signal);
12091
+ } catch {}
12092
+ }
12093
+
12094
+ function finish(code, signal) {
12095
+ if (finished) return;
12096
+ if (timedOut && !sigkillSent) {
12097
+ pendingExit = { code, signal };
12098
+ return;
12099
+ }
12100
+ finished = true;
12101
+ if (timeoutTimer) clearTimeout(timeoutTimer);
12102
+ if (killTimer) clearTimeout(killTimer);
12103
+ if (timedOut) {
12104
+ stderr = [stderr, "Command timed out after " + timeoutMs + "ms."].filter(Boolean).join(stderr ? "\\n" : "");
12105
+ }
12106
+ const exitCode = timedOut ? 124 : code ?? 1;
12107
+ process.stdout.write(JSON.stringify({
12108
+ stdout,
12109
+ stderr,
12110
+ exitCode,
12111
+ timedOut,
12112
+ signal: signal ?? null,
12113
+ }), () => process.exit(exitCode));
12114
+ }
12115
+
12116
+ child.stdout.setEncoding("utf8");
12117
+ child.stderr.setEncoding("utf8");
12118
+ child.stdout.on("data", (chunk) => { stdout = appendText(stdout, chunk); });
12119
+ child.stderr.on("data", (chunk) => { stderr = appendText(stderr, chunk); });
12120
+ let childExit = { code: null, signal: null };
12121
+ child.on("error", (error) => {
12122
+ stderr = [stderr, error instanceof Error ? error.message : String(error)].filter(Boolean).join(stderr ? "\\n" : "");
12123
+ finish(1, null);
12124
+ });
12125
+ child.on("exit", (code, signal) => {
12126
+ childExit = { code, signal };
12127
+ });
12128
+ child.on("close", (code, signal) => {
12129
+ finish(code ?? childExit.code, signal ?? childExit.signal);
12130
+ });
12131
+
12132
+ timeoutTimer = setTimeout(() => {
12133
+ timedOut = true;
12134
+ killTarget("SIGTERM");
12135
+ killTimer = setTimeout(() => {
12136
+ sigkillSent = true;
12137
+ killTarget("SIGKILL");
12138
+ if (pendingExit) finish(pendingExit.code, pendingExit.signal);
12139
+ }, killGraceMs);
12140
+ }, timeoutMs);
12141
+ `;
11907
12142
 
11908
12143
  // src/commands/doctor.ts
11909
12144
  var DOCTOR_OPTIONAL_ADAPTER_DOMAINS = ["secrets", "configs", "monitor", "repos", "mcps", "shield"];
@@ -37,4 +37,3 @@ export declare function writeHeartbeat(status?: "online" | "offline", options?:
37
37
  export declare function writeHeartbeatTick(status?: "online" | "offline", options?: AgentTickOptions): Promise<AgentRuntimeStatus>;
38
38
  export declare function markOffline(options?: AgentRuntimeOptions): AgentRuntimeStatus;
39
39
  export declare function getAgentStatus(machineId?: string, options?: AgentStatusOptions): AgentRuntimeStatus[];
40
- //# sourceMappingURL=runtime.d.ts.map
@@ -1,3 +1,2 @@
1
1
  #!/usr/bin/env bun
2
2
  export {};
3
- //# sourceMappingURL=index.d.ts.map