@clawops/cli 2.1.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +122 -2
  2. package/dist/agent-activity-25FAWLAI.js +16 -0
  3. package/dist/{apply-HJ5GAKHW.js → apply-F5SQ2ZOS.js} +12 -12
  4. package/dist/{automation-MKCJ46I6.js → automation-NBYQ6MFZ.js} +1 -1
  5. package/dist/backup-flows-NDP5JTR3.js +29 -0
  6. package/dist/{bootstrap-NN647RCY.js → bootstrap-32TZXVDF.js} +5 -5
  7. package/dist/bootstrap.sh.tmpl +4 -0
  8. package/dist/chunk-27335SIH.js +51 -0
  9. package/dist/{chunk-X56CQ6GB.js → chunk-4MHDW2NB.js} +1 -1
  10. package/dist/{chunk-E4NJSJ54.js → chunk-4PY6PN4G.js} +51 -7
  11. package/dist/{chunk-3Z5TDXCJ.js → chunk-55UMO37O.js} +1 -1
  12. package/dist/{chunk-IQQTB7ER.js → chunk-66R52R7K.js} +10 -14
  13. package/dist/chunk-755X5OGS.js +213 -0
  14. package/dist/{chunk-QDNFADJI.js → chunk-AVERNLUY.js} +6 -6
  15. package/dist/{chunk-R3DTA35B.js → chunk-AX65JMDZ.js} +20 -20
  16. package/dist/{chunk-T5XCMYAV.js → chunk-CQOYUWR7.js} +766 -210
  17. package/dist/chunk-E6BRQK67.js +289 -0
  18. package/dist/{chunk-64PONSA5.js → chunk-GFIR2IIJ.js} +3 -1
  19. package/dist/chunk-LV7UO5CG.js +82 -0
  20. package/dist/{chunk-GIEVAGEK.js → chunk-N5ZYUTZS.js} +16 -1
  21. package/dist/{chunk-GFUK6KDR.js → chunk-OUQVKIIA.js} +372 -34
  22. package/dist/{chunk-XFN3YDZE.js → chunk-PASAVVNS.js} +1 -1
  23. package/dist/chunk-PCSX6Y3U.js +297 -0
  24. package/dist/chunk-POYS3EVQ.js +14 -0
  25. package/dist/{chunk-ELU3XQEA.js → chunk-RDUKRIRC.js} +1 -1
  26. package/dist/chunk-SX75L2T3.js +46 -0
  27. package/dist/{chunk-2Z7J3ERQ.js → chunk-TYNMOYVM.js} +1 -1
  28. package/dist/{chunk-2I7KCMXZ.js → chunk-UAJJEDCJ.js} +2 -2
  29. package/dist/{chunk-NUDIHZEH.js → chunk-WPZIJONN.js} +14 -12
  30. package/dist/{chunk-BELTQGHT.js → chunk-YPVWF7IA.js} +4 -4
  31. package/dist/{chunk-3OLUJPCB.js → chunk-Z3XLWZWF.js} +1 -1
  32. package/dist/cli.js +383 -925
  33. package/dist/conn-M2V5HBPJ.js +9 -0
  34. package/dist/{context-7VI3R6EK.js → context-EHSLWKSH.js} +6 -6
  35. package/dist/{docker-3PDO4TS7.js → docker-IKM5GRMC.js} +2 -2
  36. package/dist/{flows-RI7FGHAM.js → flows-6B2I6TRW.js} +7 -7
  37. package/dist/{generate-Q2BGVMH7.js → generate-4YYNW3IU.js} +9 -8
  38. package/dist/{harden-3KKCR7BL.js → harden-UHAOVQMZ.js} +5 -5
  39. package/dist/init-HOPXDNWY.js +13 -0
  40. package/dist/{mcp-wire-FQOMM7MK.js → mcp-wire-BXF4DJFT.js} +26 -19
  41. package/dist/migrate-flow-24XA4GZL.js +25 -0
  42. package/dist/{package-JFVGCNKU.js → package-HYGHRX35.js} +3 -1
  43. package/dist/{pool-PIIKJ4L2.js → pool-NT4XBSUT.js} +2 -2
  44. package/dist/{privileged-JFA6LLTV.js → privileged-5Y6O4H7Z.js} +1 -1
  45. package/dist/{ready-KDNRBNSU.js → ready-IJEUBFVX.js} +2 -2
  46. package/dist/{remote-config-3HZLUWEA.js → remote-config-OZYBMBSK.js} +3 -3
  47. package/dist/{runtime-X3KW5TCP.js → runtime-VW53TU2W.js} +5 -1
  48. package/dist/server-E3YA32ND.js +53 -0
  49. package/dist/ssh-HOM7GF3M.js +13 -0
  50. package/dist/{stack-config-LUTS2XAD.js → stack-config-ICQ5YHUL.js} +2 -2
  51. package/dist/stack-delete-NTIGKWUF.js +13 -0
  52. package/dist/{state-AOAYLYY5.js → state-AKZP2S4T.js} +2 -2
  53. package/dist/{store-OVB3DAJR.js → store-YOLQYXMB.js} +1 -1
  54. package/dist/{tailscale-cutover-6YP2QKFM.js → tailscale-cutover-V42HO5UM.js} +3 -3
  55. package/dist/{validate-JXKN5WD6.js → validate-DZE6LFEB.js} +1 -1
  56. package/dist/{wait-WO72TRRQ.js → wait-DOUNNJNN.js} +2 -2
  57. package/package.json +3 -1
  58. package/spec/mcp-tools.yaml +282 -35
  59. package/dist/logs-5A7YYA53.js +0 -15
  60. package/dist/server-B7I6HBQH.js +0 -46
  61. package/dist/ssh-VAI5DU5M.js +0 -11
  62. package/dist/upgrade-ULD4KFYC.js +0 -88
  63. /package/dist/{chunk-NUL5F6YV.js → chunk-SOZ62ZXW.js} +0 -0
@@ -1,23 +1,35 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ listOverlays
4
+ } from "./chunk-6ZFIFDBJ.js";
2
5
  import {
3
6
  inspectContainer
4
- } from "./chunk-3OLUJPCB.js";
7
+ } from "./chunk-Z3XLWZWF.js";
5
8
  import {
6
9
  execPrivileged
7
- } from "./chunk-NUDIHZEH.js";
10
+ } from "./chunk-WPZIJONN.js";
8
11
  import {
9
12
  interpretProbe,
10
13
  probeCommand
11
14
  } from "./chunk-LT4XMO5D.js";
12
15
  import {
13
16
  PUBLISH_INSPECT_CMD,
17
+ STATE_DIR_CONTAINER,
14
18
  STATE_DIR_HOST_LINUX,
15
19
  configPathForOS,
20
+ gatewayRunCommand,
16
21
  publishForRestart
17
- } from "./chunk-GIEVAGEK.js";
22
+ } from "./chunk-N5ZYUTZS.js";
18
23
  import {
19
- GATEWAY_PORT
24
+ GATEWAY_PORT,
25
+ IMAGE_INSPECT_CMD,
26
+ imageForRestart,
27
+ versionOf
20
28
  } from "./chunk-VOEJYRIK.js";
29
+ import {
30
+ err,
31
+ ok
32
+ } from "./chunk-EI7CQQ7D.js";
21
33
  import {
22
34
  chalk,
23
35
  failure
@@ -146,8 +158,8 @@ function renderSnapshot(snap, opts) {
146
158
  return lines.join("\n");
147
159
  }
148
160
  async function probeEntries() {
149
- const { buildContext } = await import("./context-7VI3R6EK.js");
150
- const { getConfig } = await import("./store-OVB3DAJR.js");
161
+ const { buildContext } = await import("./context-EHSLWKSH.js");
162
+ const { getConfig } = await import("./store-YOLQYXMB.js");
151
163
  const config = getConfig();
152
164
  if (!config) return [];
153
165
  return Promise.all(
@@ -215,7 +227,7 @@ function renderMenu(entries, selectedIdx, showAll, noColor, confirmDelete) {
215
227
  return lines.join("\n");
216
228
  }
217
229
  async function deleteFromRegistry(name) {
218
- const { requireConfig, setConfig } = await import("./store-OVB3DAJR.js");
230
+ const { requireConfig, setConfig } = await import("./store-YOLQYXMB.js");
219
231
  const config = requireConfig();
220
232
  const newStacks = { ...config.stacks };
221
233
  delete newStacks[name];
@@ -323,9 +335,9 @@ async function runDashboard(session, stackName, opts, ac, setKeyHandler, menuMod
323
335
  try {
324
336
  lastSnapshot = await gatherSnapshot(session, ac.signal, opts.tailLines);
325
337
  redraw();
326
- } catch (err) {
338
+ } catch (err2) {
327
339
  if (!ac.signal.aborted) {
328
- process.stdout.write("\x1B[2J\x1B[H Error gathering snapshot: " + (err instanceof Error ? err.message : String(err)) + "\n");
340
+ process.stdout.write("\x1B[2J\x1B[H Error gathering snapshot: " + (err2 instanceof Error ? err2.message : String(err2)) + "\n");
329
341
  }
330
342
  }
331
343
  if (!ac.signal.aborted) scheduleRefresh();
@@ -373,8 +385,8 @@ var monitor_default = defineCommand({
373
385
  const tailLines = Math.max(1, parseInt(String(args.tail ?? "10"), 10) || 10);
374
386
  const isTTY = Boolean(process.stdout.isTTY);
375
387
  const noColor = Boolean(args["no-color"]) || !isTTY;
376
- const { buildContext } = await import("./context-7VI3R6EK.js");
377
- const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
388
+ const { buildContext } = await import("./context-EHSLWKSH.js");
389
+ const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
378
390
  const ac = new AbortController();
379
391
  process.on("SIGINT", () => ac.abort());
380
392
  process.on("SIGTERM", () => ac.abort());
@@ -502,13 +514,211 @@ var monitor_default = defineCommand({
502
514
  }
503
515
  });
504
516
 
517
+ // src/openclaw/upgrade.ts
518
+ function judgePreflight(report) {
519
+ const { targetVersion, foundVersion, status, reason } = report;
520
+ if (status === "indeterminate") {
521
+ return {
522
+ ok: false,
523
+ reason: reason ?? "the database schema could not be determined, so compatibility with the target release is unknown"
524
+ };
525
+ }
526
+ if (typeof foundVersion === "number" && typeof targetVersion === "number") {
527
+ if (foundVersion > targetVersion) {
528
+ return {
529
+ ok: false,
530
+ reason: `the state database is at schema ${foundVersion}, but the target release understands ${targetVersion}. This is a downgrade across a schema boundary: the older release would read a database written by a newer one. Restore a backup taken before the upgrade instead.`
531
+ };
532
+ }
533
+ if (foundVersion < targetVersion) {
534
+ return { ok: true, note: `state will migrate from schema ${foundVersion} to ${targetVersion}` };
535
+ }
536
+ return { ok: true };
537
+ }
538
+ return {
539
+ ok: false,
540
+ reason: reason ?? `preflight returned no schema version (status: ${status ?? "unknown"})`
541
+ };
542
+ }
543
+ function parsePreflight(stdout) {
544
+ try {
545
+ const parsed = JSON.parse(stdout.trim());
546
+ if (parsed !== null && typeof parsed === "object") return parsed;
547
+ } catch {
548
+ }
549
+ return void 0;
550
+ }
551
+ function snapshotCommand(image, stateDir, repo) {
552
+ return `docker run --rm -v ${stateDir}:/home/node/.openclaw ${image} openclaw backup sqlite create --global --repository ${repo} --json`;
553
+ }
554
+ function preflightCommand(targetImage, stateDir, snapshotDb) {
555
+ return `docker run --rm -v ${stateDir}:/home/node/.openclaw ${targetImage} openclaw database preflight ${snapshotDb} --json`;
556
+ }
557
+ function snapshotPathFrom(stdout) {
558
+ try {
559
+ const parsed = JSON.parse(stdout.trim());
560
+ if (parsed.ok === true && typeof parsed.snapshotPath === "string") return parsed.snapshotPath;
561
+ } catch {
562
+ }
563
+ return void 0;
564
+ }
565
+ function repairCommand(image, stateDir) {
566
+ return `docker run --rm -v ${stateDir}:/home/node/.openclaw ${image} openclaw doctor --fix --non-interactive --yes`;
567
+ }
568
+ function describeOutcome(o, version) {
569
+ switch (o.kind) {
570
+ case "started":
571
+ return `Gateway updated to ${version}.`;
572
+ case "repaired":
573
+ return `Gateway updated to ${version} after a one-shot repair (doctor --fix).`;
574
+ case "rolled-back":
575
+ return `Rolled back to ${o.to}: the gateway did not start on ${version} \u2014 ${o.reason}. The previous image is running again. If it also fails, restore the snapshot taken before the upgrade.`;
576
+ case "failed":
577
+ return `Upgrade to ${version} failed and the rollback did not come up either \u2014 ${o.reason}. The state snapshot taken before the upgrade is at ${o.snapshotPath}; restore it with \`openclaw backup sqlite restore\` before retrying.`;
578
+ }
579
+ }
580
+ async function resolveUpgrade(steps, ctx) {
581
+ const first = await steps.gate();
582
+ if (first.ok) return { kind: "started" };
583
+ await steps.repair();
584
+ await steps.run(ctx.version);
585
+ const second = await steps.gate();
586
+ if (second.ok) return { kind: "repaired" };
587
+ const reason = second.reason ?? first.reason ?? "unknown";
588
+ if (!ctx.previousVersion) return { kind: "failed", reason, snapshotPath: ctx.snapshotPath };
589
+ await steps.run(ctx.previousVersion);
590
+ const back = await steps.gate();
591
+ return back.ok ? { kind: "rolled-back", to: ctx.previousVersion, reason } : { kind: "failed", reason, snapshotPath: ctx.snapshotPath };
592
+ }
593
+
594
+ // src/openclaw/gateway-ops.ts
595
+ function dockerRunCmd(version, publish = "loopback") {
596
+ return gatewayRunCommand({
597
+ image: `ghcr.io/openclaw/openclaw:${version}`,
598
+ stateDir: STATE_DIR_HOST_LINUX,
599
+ publish
600
+ });
601
+ }
602
+ var STATUS_FORMAT = '{"status":"{{.State.Status}}","started":"{{.State.StartedAt}}","image":"{{.Config.Image}}"}';
603
+ async function gatewayStatus(session, signal) {
604
+ const inspected = await inspectContainer(session, "openclaw", STATUS_FORMAT, signal);
605
+ if (inspected.kind === "missing") return ok({ status: "not running", started: "", image: "" });
606
+ if (inspected.kind === "error") {
607
+ return err(`Could not ask docker about the gateway: ${inspected.detail}`);
608
+ }
609
+ try {
610
+ return ok(JSON.parse(inspected.value));
611
+ } catch {
612
+ return ok({ status: "unknown", started: "", image: "" });
613
+ }
614
+ }
615
+ async function resolveUpdateVersion(requested) {
616
+ const yaml = await import("./js-yaml-PTEEG4FO.js");
617
+ const { assertSupportedVersion, loadVersionSpec } = await import("./versions-W5LRQCAJ.js");
618
+ if (!requested) return ok(loadVersionSpec(yaml).support.recommended);
619
+ const result = await assertSupportedVersion(requested, yaml);
620
+ return result.ok ? ok(result.value) : err(result.error.message);
621
+ }
622
+ async function currentGatewayVersion(session, signal) {
623
+ const cur = imageForRestart((await execPrivileged(session, IMAGE_INSPECT_CMD, signal)).stdout);
624
+ return cur.ok ? versionOf(cur.value) : void 0;
625
+ }
626
+ async function updateGateway(session, version, hooks = {}) {
627
+ const { signal } = hooks;
628
+ const progress = hooks.onProgress ?? (() => {
629
+ });
630
+ const sleep = hooks.sleep ?? ((ms) => new Promise((res) => setTimeout(res, ms)));
631
+ const targetImage = `ghcr.io/openclaw/openclaw:${version}`;
632
+ const pullResult = await execPrivileged(session, `docker pull ${targetImage}`, signal);
633
+ if (pullResult.code !== 0) {
634
+ return { ok: false, version, message: `Pull failed: ${pullResult.stderr}` };
635
+ }
636
+ progress("Checking state compatibility...");
637
+ const cur = imageForRestart((await execPrivileged(session, IMAGE_INSPECT_CMD, signal)).stdout);
638
+ const previousVersion = cur.ok ? versionOf(cur.value) : void 0;
639
+ const snapRepo = `${STATE_DIR_CONTAINER}/snapshots`;
640
+ const snapOut = await execPrivileged(
641
+ session,
642
+ snapshotCommand(cur.ok ? cur.value : targetImage, STATE_DIR_HOST_LINUX, snapRepo),
643
+ signal
644
+ );
645
+ const snapPath = snapshotPathFrom(snapOut.stdout);
646
+ const hostSnapPath = snapPath?.startsWith(STATE_DIR_CONTAINER) ? STATE_DIR_HOST_LINUX + snapPath.slice(STATE_DIR_CONTAINER.length) : snapPath;
647
+ if (!snapPath) {
648
+ return {
649
+ ok: false,
650
+ version,
651
+ message: "Could not snapshot the state database before upgrading, so neither the compatibility check nor a rollback point is available.\n" + (snapOut.stderr || snapOut.stdout).slice(0, 2e3)
652
+ };
653
+ }
654
+ const pre = await execPrivileged(
655
+ session,
656
+ preflightCommand(targetImage, STATE_DIR_HOST_LINUX, `${snapPath}/database.sqlite`),
657
+ signal
658
+ );
659
+ const report = parsePreflight(pre.stdout);
660
+ const verdict = report ? judgePreflight(report) : { ok: false, reason: `preflight produced no readable report: ${pre.stderr.slice(0, 200)}` };
661
+ if (!verdict.ok) {
662
+ return {
663
+ ok: false,
664
+ version,
665
+ message: `Refusing to upgrade to ${version}: ${verdict.reason}`,
666
+ hint: `A snapshot of the current state was kept at ${hostSnapPath}.`
667
+ };
668
+ }
669
+ if (verdict.note) hooks.onNote?.(verdict.note);
670
+ progress(`Updating gateway to ${version}...`);
671
+ const pubU = await execPrivileged(session, PUBLISH_INSPECT_CMD, signal);
672
+ const publish = publishForRestart(pubU.stdout);
673
+ const runResult = await execPrivileged(session, dockerRunCmd(version, publish), signal);
674
+ if (runResult.code !== 0) {
675
+ return {
676
+ ok: false,
677
+ version,
678
+ message: `Start failed: ${runResult.stderr}`,
679
+ hint: `State snapshot from before the upgrade: ${hostSnapPath}`
680
+ };
681
+ }
682
+ progress("Waiting for the gateway to start...");
683
+ const gate = async () => {
684
+ let last;
685
+ for (let i = 0; i < 15; i++) {
686
+ if (signal?.aborted) return { ok: false, reason: "aborted" };
687
+ const r = await execPrivileged(session, probeCommand("started", GATEWAY_PORT), signal);
688
+ const v = interpretProbe("started", r.stdout);
689
+ if (v.ok) return { ok: true };
690
+ last = v.reason;
691
+ await sleep(2e3);
692
+ }
693
+ return { ok: false, reason: last ?? "no response" };
694
+ };
695
+ const outcome = await resolveUpgrade(
696
+ {
697
+ gate,
698
+ repair: async () => {
699
+ progress("Gateway did not start; attempting one-shot repair...");
700
+ await execPrivileged(session, repairCommand(targetImage, STATE_DIR_HOST_LINUX), signal);
701
+ },
702
+ run: async (v) => {
703
+ await execPrivileged(session, dockerRunCmd(v, publish), signal);
704
+ }
705
+ },
706
+ { version, previousVersion, snapshotPath: hostSnapPath ?? snapPath }
707
+ );
708
+ const message = describeOutcome(outcome, version);
709
+ if (outcome.kind === "started" || outcome.kind === "repaired") {
710
+ return { ok: true, version, ...previousVersion ? { previousVersion } : {}, outcome, message };
711
+ }
712
+ return { ok: false, version, message, outcome };
713
+ }
714
+
505
715
  // src/diagnostics/index.ts
506
716
  import process2 from "process";
507
717
  import { accessSync, mkdirSync, readFileSync, constants } from "fs";
508
718
  import ssh2 from "ssh2";
509
719
  import path from "path";
510
720
  async function runDiagnostics(opts = {}, deps = {}) {
511
- const { getConfig, getConfigDir } = await import("./store-OVB3DAJR.js");
721
+ const { getConfig, getConfigDir } = await import("./store-YOLQYXMB.js");
512
722
  const config = getConfig();
513
723
  const sections = [];
514
724
  sections.push({ title: "Runtime", checks: await runtimeChecks(getConfigDir()) });
@@ -528,10 +738,10 @@ async function runDiagnostics(opts = {}, deps = {}) {
528
738
  let handle;
529
739
  try {
530
740
  handle = await open(opts.stack, opts.signal);
531
- } catch (err) {
741
+ } catch (err2) {
532
742
  sections.push({
533
743
  title: "Remote health",
534
- checks: [{ name: "Connection", status: "fail", detail: messageOf(err) }]
744
+ checks: [connectionCheck(messageOf(err2))]
535
745
  });
536
746
  }
537
747
  if (handle) {
@@ -546,7 +756,7 @@ async function runDiagnostics(opts = {}, deps = {}) {
546
756
  });
547
757
  } finally {
548
758
  handle.release();
549
- const { drainPool } = await import("./pool-PIIKJ4L2.js");
759
+ const { drainPool } = await import("./pool-NT4XBSUT.js");
550
760
  drainPool();
551
761
  }
552
762
  }
@@ -654,8 +864,8 @@ async function providerCredentialCheck(provider, why) {
654
864
  const adapter = getProvider(provider);
655
865
  const result = await adapter.validateConfig();
656
866
  return result.ok ? { name: provider, status: "pass", detail: why } : { name: provider, status: "fail", detail: result.errors.join("; ") };
657
- } catch (err) {
658
- return { name: provider, status: "fail", detail: messageOf(err) };
867
+ } catch (err2) {
868
+ return { name: provider, status: "fail", detail: messageOf(err2) };
659
869
  }
660
870
  }
661
871
  async function credentialChecks(config, provider) {
@@ -682,15 +892,15 @@ async function credentialChecks(config, provider) {
682
892
  if (result.ok) {
683
893
  checks.push({ name: providerName, status: "pass", detail: `stack "${stackName}"` });
684
894
  } else {
685
- for (const err of result.errors) {
686
- checks.push({ name: providerName, status: "fail", detail: `stack "${stackName}" \u2014 ${err}` });
895
+ for (const err2 of result.errors) {
896
+ checks.push({ name: providerName, status: "fail", detail: `stack "${stackName}" \u2014 ${err2}` });
687
897
  }
688
898
  }
689
- } catch (err) {
899
+ } catch (err2) {
690
900
  checks.push({
691
901
  name: providerName,
692
902
  status: "fail",
693
- detail: `stack "${stackName}" \u2014 ${messageOf(err)}`
903
+ detail: `stack "${stackName}" \u2014 ${messageOf(err2)}`
694
904
  });
695
905
  }
696
906
  }
@@ -725,7 +935,7 @@ async function versionChecks() {
725
935
  }
726
936
  async function remoteChecks(session, signal) {
727
937
  const checks = [];
728
- const { containerStatus } = await import("./docker-3PDO4TS7.js");
938
+ const { containerStatus } = await import("./docker-IKM5GRMC.js");
729
939
  const container = await containerStatus(session, "openclaw", signal);
730
940
  checks.push(
731
941
  container.error ? {
@@ -808,8 +1018,8 @@ async function deployedVersionCheck(image, deployedVersion) {
808
1018
  }
809
1019
  async function hardeningChecks(conn, stack, signal) {
810
1020
  try {
811
- const { MODULE_CATALOG, resolveModules, withRemoteExec } = await import("./harden-3KKCR7BL.js");
812
- const { buildContext } = await import("./context-7VI3R6EK.js");
1021
+ const { MODULE_CATALOG, resolveModules, withRemoteExec } = await import("./harden-UHAOVQMZ.js");
1022
+ const { buildContext } = await import("./context-EHSLWKSH.js");
813
1023
  const provider = buildContext({ stack }).adapter.name;
814
1024
  const modules = resolveModules(MODULE_CATALOG, void 0, provider);
815
1025
  const checks = [];
@@ -826,15 +1036,15 @@ async function hardeningChecks(conn, stack, signal) {
826
1036
  }
827
1037
  });
828
1038
  return checks;
829
- } catch (err) {
830
- return [{ name: "Hardening checks", status: "fail", detail: messageOf(err) }];
1039
+ } catch (err2) {
1040
+ return [{ name: "Hardening checks", status: "fail", detail: messageOf(err2) }];
831
1041
  }
832
1042
  }
833
1043
  async function defaultOpenSession(stack, signal) {
834
- const { getConfig } = await import("./store-OVB3DAJR.js");
835
- const { buildContext } = await import("./context-7VI3R6EK.js");
1044
+ const { getConfig } = await import("./store-YOLQYXMB.js");
1045
+ const { buildContext } = await import("./context-EHSLWKSH.js");
836
1046
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
837
- const { acquireSession } = await import("./pool-PIIKJ4L2.js");
1047
+ const { acquireSession } = await import("./pool-NT4XBSUT.js");
838
1048
  const config = getConfig();
839
1049
  if (!config) throw new Error("no config \u2014 run `clawops init`");
840
1050
  const ctx = buildContext({ stack });
@@ -859,8 +1069,18 @@ async function defaultOpenSession(stack, signal) {
859
1069
  });
860
1070
  return { session, release, conn };
861
1071
  }
862
- function messageOf(err) {
863
- return err instanceof Error ? err.message : String(err);
1072
+ function messageOf(err2) {
1073
+ return err2 instanceof Error ? err2.message : String(err2);
1074
+ }
1075
+ function connectionCheck(message) {
1076
+ const [summary, ...rest] = message.split("\n");
1077
+ const remedy = rest.join("\n ").trim();
1078
+ return {
1079
+ name: "Connection",
1080
+ status: "fail",
1081
+ detail: summary,
1082
+ ...remedy ? { remedy } : {}
1083
+ };
864
1084
  }
865
1085
  async function accountChecks(config, stack, provider, instanceType) {
866
1086
  if (!config && !provider) return [];
@@ -883,8 +1103,8 @@ async function accountChecks(config, stack, provider, instanceType) {
883
1103
  detail: r.detail,
884
1104
  remedy: r.mutates ? `clawops setup can fix this: ${r.mutates}` : void 0
885
1105
  }));
886
- } catch (err) {
887
- return [{ name: "Account preflight", status: "warn", detail: messageOf(err) }];
1106
+ } catch (err2) {
1107
+ return [{ name: "Account preflight", status: "warn", detail: messageOf(err2) }];
888
1108
  }
889
1109
  }
890
1110
  function bucketFromStateUrl(stateUrl) {
@@ -893,9 +1113,127 @@ function bucketFromStateUrl(stateUrl) {
893
1113
  return match?.[1];
894
1114
  }
895
1115
 
1116
+ // src/secrets/store.ts
1117
+ import { existsSync, readdirSync, readFileSync as readFileSync2, statSync, unlinkSync } from "fs";
1118
+ import path2 from "path";
1119
+ import os from "os";
1120
+ import process3 from "process";
1121
+ function secretsDir() {
1122
+ return path2.join(os.homedir(), ".clawops", "secrets");
1123
+ }
1124
+ function resolveSecretPath(name) {
1125
+ const dir = secretsDir();
1126
+ const invalid = name.length === 0 || name === "." || name === ".." || name.includes("/") || name.includes("\\") || name.includes("\0") || path2.dirname(path2.resolve(dir, name)) !== path2.resolve(dir);
1127
+ if (invalid) {
1128
+ return {
1129
+ ok: false,
1130
+ reason: `Secret name "${name}" is not valid: a secret name is a single file name inside ${dir} (no "/" or "\\", and not "." or "..").`
1131
+ };
1132
+ }
1133
+ return { ok: true, path: path2.join(dir, name) };
1134
+ }
1135
+ function listSecretNames() {
1136
+ if (!existsSync(secretsDir())) return [];
1137
+ return readdirSync(secretsDir()).filter((f) => !f.startsWith("."));
1138
+ }
1139
+ function listSecrets() {
1140
+ return listSecretNames().map((name) => {
1141
+ const p = path2.join(secretsDir(), name);
1142
+ try {
1143
+ const stat = statSync(p);
1144
+ const nonEmpty = readFileSync2(p, "utf-8").trim().length > 0;
1145
+ const modified = stat.mtime.toISOString().slice(0, 10);
1146
+ return { name, status: nonEmpty ? "ok" : "empty", source: "file", path: p, modified, resolvable: nonEmpty };
1147
+ } catch {
1148
+ return { name, status: "unreadable", source: "file", path: p, modified: "\u2014", resolvable: false };
1149
+ }
1150
+ });
1151
+ }
1152
+ var NO_SECRETS_MESSAGE = "No secrets stored in ~/.clawops/secrets/";
1153
+ var SET_SECRET_HINT = "Run `clawops secret set <name>` to add one.";
1154
+ function auditSecrets() {
1155
+ const issues = [];
1156
+ for (const name of listSecretNames()) {
1157
+ const p = path2.join(secretsDir(), name);
1158
+ try {
1159
+ if (readFileSync2(p, "utf-8").trim().length === 0) {
1160
+ issues.push({ kind: "empty-secret", secret: name, detail: `File exists but is empty: ${p}` });
1161
+ }
1162
+ } catch {
1163
+ issues.push({ kind: "unreadable-secret", secret: name, detail: `Cannot read: ${p}` });
1164
+ }
1165
+ }
1166
+ for (const overlay of listOverlays()) {
1167
+ for (const s of overlay.secrets) {
1168
+ if (s.source === "file") {
1169
+ const ref = s.ref ?? path2.join(secretsDir(), s.name);
1170
+ if (!existsSync(ref)) {
1171
+ issues.push({ kind: "missing-file", stack: overlay.stackName, secret: s.name, detail: `File not found: ${ref}` });
1172
+ }
1173
+ } else if (s.source === "env") {
1174
+ const envVar = s.ref ?? s.name;
1175
+ if (!process3.env[envVar]) {
1176
+ issues.push({ kind: "missing-env", stack: overlay.stackName, secret: s.name, detail: `Env var not set: ${envVar}` });
1177
+ }
1178
+ } else {
1179
+ issues.push({ kind: "cloud-sm-unresolved", stack: overlay.stackName, secret: s.name, detail: `Source "${s.source}" is not auto-resolved \u2014 set manually` });
1180
+ }
1181
+ }
1182
+ }
1183
+ return { issues, ok: issues.length === 0 };
1184
+ }
1185
+ var AUDIT_CLEAN_MESSAGE = "All secrets are resolvable. No issues found.";
1186
+ var AUDIT_FIX_HINT = "Run `clawops secret set <name>` to update a missing or empty secret.";
1187
+ function auditIssuesHeadline(count) {
1188
+ return `${count} issue${count === 1 ? "" : "s"} found:`;
1189
+ }
1190
+ function formatAuditIssue(issue) {
1191
+ const prefix = issue.stack ? `[${issue.stack}] ` : "";
1192
+ return `${prefix}${issue.secret}: ${issue.detail}`;
1193
+ }
1194
+ function prepareSecretDelete(name) {
1195
+ const resolved = resolveSecretPath(name);
1196
+ if (!resolved.ok) return { ok: false, kind: "invalid-name", reason: resolved.reason };
1197
+ const p = resolved.path;
1198
+ if (!existsSync(p)) {
1199
+ return { ok: false, kind: "not-found", reason: `Secret "${name}" not found at ${p}` };
1200
+ }
1201
+ const referencingStacks = listOverlays().filter((o) => o.secrets.some((s) => s.name === name)).map((o) => o.stackName);
1202
+ const warnings = referencingStacks.length > 0 ? [
1203
+ `Secret "${name}" is referenced by stack(s): ${referencingStacks.join(", ")}`,
1204
+ "Deleting it will leave those stacks with an unresolvable $secret: ref."
1205
+ ] : [];
1206
+ return { ok: true, name, path: p, referencingStacks, warnings };
1207
+ }
1208
+ function deleteConfirmQuestion(name) {
1209
+ return `Delete secret "${name}"?`;
1210
+ }
1211
+ function deletePreparedSecret(prepared) {
1212
+ unlinkSync(prepared.path);
1213
+ return `Secret "${prepared.name}" deleted.`;
1214
+ }
1215
+
896
1216
  export {
1217
+ dockerRunCmd,
1218
+ gatewayStatus,
1219
+ resolveUpdateVersion,
1220
+ currentGatewayVersion,
1221
+ updateGateway,
897
1222
  runDiagnostics,
898
1223
  formatUptime,
899
1224
  gatherSnapshot,
900
- monitor_default
1225
+ monitor_default,
1226
+ secretsDir,
1227
+ resolveSecretPath,
1228
+ listSecrets,
1229
+ NO_SECRETS_MESSAGE,
1230
+ SET_SECRET_HINT,
1231
+ auditSecrets,
1232
+ AUDIT_CLEAN_MESSAGE,
1233
+ AUDIT_FIX_HINT,
1234
+ auditIssuesHeadline,
1235
+ formatAuditIssue,
1236
+ prepareSecretDelete,
1237
+ deleteConfirmQuestion,
1238
+ deletePreparedSecret
901
1239
  };
@@ -44,7 +44,7 @@ async function validatePlanConfig(plan) {
44
44
  ]);
45
45
  const { loadVersionSpec } = await import("./versions-W5LRQCAJ.js");
46
46
  const spec = loadVersionSpec(yaml);
47
- const { deepMerge } = await import("./remote-config-3HZLUWEA.js");
47
+ const { deepMerge } = await import("./remote-config-OZYBMBSK.js");
48
48
  const merged = deepMerge(
49
49
  { gateway: { mode: "local" } },
50
50
  overlay