@clawops/cli 2.1.3 → 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 (51) hide show
  1. package/README.md +120 -2
  2. package/dist/agent-activity-25FAWLAI.js +16 -0
  3. package/dist/{apply-JARPVA5Y.js → apply-F5SQ2ZOS.js} +7 -7
  4. package/dist/backup-flows-NDP5JTR3.js +29 -0
  5. package/dist/{bootstrap-3PBVY7MA.js → bootstrap-32TZXVDF.js} +3 -3
  6. package/dist/bootstrap.sh.tmpl +4 -0
  7. package/dist/chunk-27335SIH.js +51 -0
  8. package/dist/{chunk-E4NJSJ54.js → chunk-4PY6PN4G.js} +51 -7
  9. package/dist/{chunk-PTCVFLZ3.js → chunk-66R52R7K.js} +7 -11
  10. package/dist/{chunk-7T4CIT7C.js → chunk-AX65JMDZ.js} +15 -15
  11. package/dist/{chunk-TYHTISI5.js → chunk-CQOYUWR7.js} +610 -104
  12. package/dist/chunk-E6BRQK67.js +289 -0
  13. package/dist/chunk-LV7UO5CG.js +82 -0
  14. package/dist/{chunk-GIEVAGEK.js → chunk-N5ZYUTZS.js} +16 -1
  15. package/dist/{chunk-CPCO6UJK.js → chunk-OUQVKIIA.js} +368 -30
  16. package/dist/{chunk-XFN3YDZE.js → chunk-PASAVVNS.js} +1 -1
  17. package/dist/chunk-PCSX6Y3U.js +297 -0
  18. package/dist/chunk-POYS3EVQ.js +14 -0
  19. package/dist/chunk-SX75L2T3.js +46 -0
  20. package/dist/{chunk-2Z7J3ERQ.js → chunk-TYNMOYVM.js} +1 -1
  21. package/dist/{chunk-2I7KCMXZ.js → chunk-UAJJEDCJ.js} +2 -2
  22. package/dist/{chunk-NUDIHZEH.js → chunk-WPZIJONN.js} +14 -12
  23. package/dist/{chunk-CHNSL3TC.js → chunk-YPVWF7IA.js} +1 -1
  24. package/dist/{chunk-3OLUJPCB.js → chunk-Z3XLWZWF.js} +1 -1
  25. package/dist/cli.js +303 -698
  26. package/dist/conn-M2V5HBPJ.js +9 -0
  27. package/dist/{context-WOKSKAIA.js → context-EHSLWKSH.js} +2 -2
  28. package/dist/{docker-3PDO4TS7.js → docker-IKM5GRMC.js} +2 -2
  29. package/dist/{flows-HGJIA6EL.js → flows-6B2I6TRW.js} +5 -5
  30. package/dist/{generate-NE54BATS.js → generate-4YYNW3IU.js} +5 -4
  31. package/dist/{harden-RUOUA5BV.js → harden-UHAOVQMZ.js} +3 -3
  32. package/dist/{mcp-wire-FIO5DFBY.js → mcp-wire-BXF4DJFT.js} +21 -14
  33. package/dist/migrate-flow-24XA4GZL.js +25 -0
  34. package/dist/{package-MF5LPITL.js → package-HYGHRX35.js} +3 -1
  35. package/dist/{pool-PIIKJ4L2.js → pool-NT4XBSUT.js} +2 -2
  36. package/dist/{privileged-JFA6LLTV.js → privileged-5Y6O4H7Z.js} +1 -1
  37. package/dist/{ready-KDNRBNSU.js → ready-IJEUBFVX.js} +2 -2
  38. package/dist/{remote-config-3HZLUWEA.js → remote-config-OZYBMBSK.js} +3 -3
  39. package/dist/{runtime-X3KW5TCP.js → runtime-VW53TU2W.js} +5 -1
  40. package/dist/{server-OXWHBTXK.js → server-E3YA32ND.js} +21 -14
  41. package/dist/ssh-HOM7GF3M.js +13 -0
  42. package/dist/stack-delete-NTIGKWUF.js +13 -0
  43. package/dist/{tailscale-cutover-EKAQSDXP.js → tailscale-cutover-V42HO5UM.js} +1 -1
  44. package/dist/{validate-JXKN5WD6.js → validate-DZE6LFEB.js} +1 -1
  45. package/dist/{wait-WO72TRRQ.js → wait-DOUNNJNN.js} +2 -2
  46. package/package.json +3 -1
  47. package/spec/mcp-tools.yaml +213 -7
  48. package/dist/logs-5A7YYA53.js +0 -15
  49. package/dist/ssh-VAI5DU5M.js +0 -11
  50. package/dist/upgrade-ULD4KFYC.js +0 -88
  51. /package/dist/{chunk-NUL5F6YV.js → chunk-SOZ62ZXW.js} +0 -0
package/dist/cli.js CHANGED
@@ -5,39 +5,51 @@ import {
5
5
  writeAppConfigs
6
6
  } from "./chunk-OIGTOLB3.js";
7
7
  import {
8
+ AUDIT_CLEAN_MESSAGE,
9
+ AUDIT_FIX_HINT,
10
+ NO_SECRETS_MESSAGE,
11
+ SET_SECRET_HINT,
12
+ auditIssuesHeadline,
13
+ auditSecrets,
14
+ deleteConfirmQuestion,
15
+ deletePreparedSecret,
16
+ dockerRunCmd,
17
+ formatAuditIssue,
18
+ gatewayStatus,
19
+ listSecrets,
8
20
  monitor_default,
9
- runDiagnostics
10
- } from "./chunk-CPCO6UJK.js";
21
+ prepareSecretDelete,
22
+ resolveSecretPath,
23
+ resolveUpdateVersion,
24
+ runDiagnostics,
25
+ secretsDir,
26
+ updateGateway
27
+ } from "./chunk-OUQVKIIA.js";
11
28
  import {
12
29
  GATEWAY_LOGS_PROBE,
13
30
  chooseLogSource,
14
31
  containerLogsCommand,
15
32
  gatewayLogsCommand
16
- } from "./chunk-NUL5F6YV.js";
17
- import "./chunk-3OLUJPCB.js";
33
+ } from "./chunk-SOZ62ZXW.js";
34
+ import "./chunk-6ZFIFDBJ.js";
35
+ import "./chunk-Z3XLWZWF.js";
18
36
  import {
19
37
  execPrivileged,
20
- execPrivilegedWithInput,
21
38
  streamPrivileged
22
- } from "./chunk-NUDIHZEH.js";
23
- import "./chunk-CHNSL3TC.js";
39
+ } from "./chunk-WPZIJONN.js";
40
+ import "./chunk-YPVWF7IA.js";
24
41
  import {
25
42
  resolveSpecDir
26
43
  } from "./chunk-JW5A7E3X.js";
27
- import {
28
- interpretProbe,
29
- probeCommand
30
- } from "./chunk-LT4XMO5D.js";
44
+ import "./chunk-LT4XMO5D.js";
31
45
  import "./chunk-VUFMZEIM.js";
32
46
  import {
33
- CONFIG_FILENAME,
34
- CONTAINER_UID,
35
47
  PUBLISH_INSPECT_CMD,
36
48
  STATE_DIR_HOST_LINUX,
37
49
  configPathForOS,
38
50
  gatewayRunCommand,
39
51
  publishForRestart
40
- } from "./chunk-GIEVAGEK.js";
52
+ } from "./chunk-N5ZYUTZS.js";
41
53
  import {
42
54
  GATEWAY_PORT,
43
55
  IMAGE_INSPECT_CMD,
@@ -67,8 +79,7 @@ import {
67
79
  } from "./chunk-755X5OGS.js";
68
80
  import {
69
81
  getConfigDir,
70
- requireConfig,
71
- setConfig
82
+ requireConfig
72
83
  } from "./chunk-GFIR2IIJ.js";
73
84
  import {
74
85
  ClawopsError,
@@ -192,7 +203,7 @@ var up_default = defineCommand2({
192
203
  "publish-gateway": { type: "string", description: 'loopback (default) or all. "all" serves plaintext HTTP \u2014 put TLS in front of it' }
193
204
  },
194
205
  async run({ args }) {
195
- const { buildContext } = await import("./context-WOKSKAIA.js");
206
+ const { buildContext } = await import("./context-EHSLWKSH.js");
196
207
  const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
197
208
  const ctx = buildContext(args);
198
209
  const requestedVersion = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : await defaultOpenclawVersion();
@@ -205,7 +216,7 @@ var up_default = defineCommand2({
205
216
  );
206
217
  }
207
218
  const { localOpts } = stackConfig;
208
- const { localBootstrap } = await import("./bootstrap-3PBVY7MA.js");
219
+ const { localBootstrap } = await import("./bootstrap-32TZXVDF.js");
209
220
  const gatewayPort = parseGatewayPort(args["gateway-port"]);
210
221
  const abortController2 = new AbortController();
211
222
  process3.on("SIGINT", () => abortController2.abort());
@@ -245,8 +256,8 @@ var up_default = defineCommand2({
245
256
  }
246
257
  return;
247
258
  }
248
- const { generatePlan } = await import("./generate-NE54BATS.js");
249
- const { applyPlan } = await import("./apply-JARPVA5Y.js");
259
+ const { generatePlan } = await import("./generate-4YYNW3IU.js");
260
+ const { applyPlan } = await import("./apply-F5SQ2ZOS.js");
250
261
  const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
251
262
  const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
252
263
  const isDryRun = Boolean(args["dry-run"]);
@@ -326,9 +337,9 @@ function strArg(value) {
326
337
  return typeof value === "string" ? value : void 0;
327
338
  }
328
339
  async function applyLocalConfigOverlay(opts) {
329
- const { connect } = await import("./ssh-VAI5DU5M.js");
340
+ const { connect } = await import("./ssh-HOM7GF3M.js");
330
341
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
331
- const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-3HZLUWEA.js");
342
+ const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-OZYBMBSK.js");
332
343
  let overlay;
333
344
  try {
334
345
  overlay = JSON.parse(readFileSync(opts.configPath, "utf-8"));
@@ -376,7 +387,7 @@ var down_default = defineCommand3({
376
387
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
377
388
  },
378
389
  async run({ args }) {
379
- const { buildContext } = await import("./context-WOKSKAIA.js");
390
+ const { buildContext } = await import("./context-EHSLWKSH.js");
380
391
  const ctx = buildContext(args);
381
392
  if (args["dry-run"]) {
382
393
  info(`Dry run \u2014 would destroy stack "${ctx.stackName}"`);
@@ -440,7 +451,7 @@ var status_default = defineCommand4({
440
451
  json: { type: "boolean", description: "Emit JSON" }
441
452
  },
442
453
  async run({ args }) {
443
- const { buildContext } = await import("./context-WOKSKAIA.js");
454
+ const { buildContext } = await import("./context-EHSLWKSH.js");
444
455
  const ctx = buildContext(args);
445
456
  if (ctx.adapter.name === "local") {
446
457
  const state = ctx.localState;
@@ -540,8 +551,8 @@ var plan_default = defineCommand5({
540
551
  "private-only": { type: "boolean", description: "Close public SSH and gateway access; reach the stack over its tailnet. Needs `clawops harden --tailscale` first, and checks the tailnet answers before planning" }
541
552
  },
542
553
  async run({ args }) {
543
- const { buildContext } = await import("./context-WOKSKAIA.js");
544
- const { generatePlan } = await import("./generate-NE54BATS.js");
554
+ const { buildContext } = await import("./context-EHSLWKSH.js");
555
+ const { generatePlan } = await import("./generate-4YYNW3IU.js");
545
556
  const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
546
557
  const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
547
558
  const ctx = buildContext(args);
@@ -566,7 +577,7 @@ var plan_default = defineCommand5({
566
577
  let network = resolved;
567
578
  if (args["private-only"]) {
568
579
  const { privateOnlyNetwork, assertTailnetReachable } = await import("./private-only-DIA26I2E.js");
569
- const { probeSsh } = await import("./tailscale-cutover-EKAQSDXP.js");
580
+ const { probeSsh } = await import("./tailscale-cutover-V42HO5UM.js");
570
581
  network = privateOnlyNetwork(flags, resolved, ctx.config.stacks[ctx.stackName]?.tailscale, ctx.stackName);
571
582
  await assertTailnetReachable(ctx, (conn) => probeSsh(conn));
572
583
  }
@@ -656,8 +667,8 @@ var apply_default = defineCommand6({
656
667
  "dry-run": { type: "boolean", description: "Validate plan and show diff without applying" }
657
668
  },
658
669
  async run({ args }) {
659
- const { validatePlan } = await import("./validate-JXKN5WD6.js");
660
- const { applyPlan } = await import("./apply-JARPVA5Y.js");
670
+ const { validatePlan } = await import("./validate-DZE6LFEB.js");
671
+ const { applyPlan } = await import("./apply-F5SQ2ZOS.js");
661
672
  const planPath = args._?.[0];
662
673
  if (!planPath) {
663
674
  throw new UsageError("Usage: clawops apply <plan.json>");
@@ -783,7 +794,7 @@ var destroy_default = defineCommand7({
783
794
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
784
795
  },
785
796
  async run({ args }) {
786
- const { buildContext } = await import("./context-WOKSKAIA.js");
797
+ const { buildContext } = await import("./context-EHSLWKSH.js");
787
798
  const ctx = buildContext(args);
788
799
  if (ctx.adapter.name === "local") {
789
800
  throw new UsageError(
@@ -876,8 +887,8 @@ var ssh_default = defineCommand8({
876
887
  command: { type: "string", description: "Remote command to run (instead of interactive shell)" }
877
888
  },
878
889
  async run({ args }) {
879
- const { buildContext } = await import("./context-WOKSKAIA.js");
880
- const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
890
+ const { buildContext } = await import("./context-EHSLWKSH.js");
891
+ const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
881
892
  const ctx = buildContext(args);
882
893
  let conn;
883
894
  if (ctx.adapter.name === "local") {
@@ -963,9 +974,9 @@ var tunnel_default = defineCommand9({
963
974
  "no-open": { type: "boolean", description: "Do not open browser after tunnel is ready" }
964
975
  },
965
976
  async run({ args }) {
966
- const { buildContext } = await import("./context-WOKSKAIA.js");
977
+ const { buildContext } = await import("./context-EHSLWKSH.js");
967
978
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
968
- const { acquireSession } = await import("./pool-PIIKJ4L2.js");
979
+ const { acquireSession } = await import("./pool-NT4XBSUT.js");
969
980
  const ctx = buildContext(args);
970
981
  const stack = await ctx.getStack();
971
982
  const outputMap = await stack.outputs();
@@ -1032,25 +1043,10 @@ var logs_default = defineCommand10({
1032
1043
  json: { type: "boolean", description: "Emit the gateway's structured JSON log lines" }
1033
1044
  },
1034
1045
  async run({ args }) {
1035
- const { buildContext } = await import("./context-WOKSKAIA.js");
1036
- const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1037
- const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1046
+ const { buildContext } = await import("./context-EHSLWKSH.js");
1047
+ const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
1038
1048
  const ctx = buildContext(args);
1039
- const stack = await ctx.getStack();
1040
- const outputMap = await stack.outputs();
1041
- const outputs = Object.fromEntries(
1042
- Object.entries(outputMap).map(([k, v]) => [k, v.value])
1043
- );
1044
- if (!outputs["publicIp"]) {
1045
- failure("Stack has no outputs. Run `clawops up` first.");
1046
- process12.exit(4);
1047
- }
1048
- const base = extractBaseOutputs(outputs);
1049
- const conn = ctx.adapter.getConnectionInfo({
1050
- ...base,
1051
- privateKeyPath: ctx.config.ssh.keyPath,
1052
- knownHostsPath: ctx.config.ssh.knownHostsPath
1053
- });
1049
+ const conn = await connFor(ctx);
1054
1050
  const tailLines = typeof args.tail === "string" ? parseInt(args.tail, 10) : 100;
1055
1051
  const follow = Boolean(args.follow);
1056
1052
  const since = typeof args.since === "string" ? args.since : void 0;
@@ -1092,12 +1088,33 @@ var logs_default = defineCommand10({
1092
1088
  }
1093
1089
  }
1094
1090
  });
1091
+ async function connFor(ctx) {
1092
+ if (ctx.adapter.name === "local") {
1093
+ const { resolveConn } = await import("./conn-M2V5HBPJ.js");
1094
+ return resolveConn(ctx);
1095
+ }
1096
+ const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1097
+ const stack = await ctx.getStack();
1098
+ const outputMap = await stack.outputs();
1099
+ const outputs = Object.fromEntries(
1100
+ Object.entries(outputMap).map(([k, v]) => [k, v.value])
1101
+ );
1102
+ if (!outputs["publicIp"]) {
1103
+ failure("Stack has no outputs. Run `clawops up` first.");
1104
+ process12.exit(4);
1105
+ }
1106
+ return ctx.adapter.getConnectionInfo({
1107
+ ...extractBaseOutputs(outputs),
1108
+ privateKeyPath: ctx.config.ssh.keyPath,
1109
+ knownHostsPath: ctx.config.ssh.knownHostsPath
1110
+ });
1111
+ }
1095
1112
 
1096
1113
  // src/cli/commands/config.ts
1097
1114
  import { defineCommand as defineCommand11 } from "citty";
1098
1115
  import process13 from "process";
1099
1116
  var OPENCLAW_CONFIG = configPathForOS("Linux");
1100
- function dockerRunCmd(image) {
1117
+ function dockerRunCmd2(image) {
1101
1118
  return gatewayRunCommand({ image, stateDir: STATE_DIR_HOST_LINUX });
1102
1119
  }
1103
1120
  function getPath(obj, dotKey) {
@@ -1138,9 +1155,8 @@ var config_default = defineCommand11({
1138
1155
  "dry-run": { type: "boolean", description: "Show what would change without writing" }
1139
1156
  },
1140
1157
  async run({ args }) {
1141
- const { buildContext } = await import("./context-WOKSKAIA.js");
1142
- const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1143
- const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1158
+ const { buildContext } = await import("./context-EHSLWKSH.js");
1159
+ const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
1144
1160
  const [action, key, value] = args._ ?? [];
1145
1161
  if (!action || !["get", "set", "unset", "validate"].includes(action)) {
1146
1162
  failure("Usage: clawops config <get [key] | set key value | unset key | validate>");
@@ -1155,17 +1171,8 @@ var config_default = defineCommand11({
1155
1171
  process13.exit(2);
1156
1172
  }
1157
1173
  const ctx = buildContext(args);
1158
- const stack = await ctx.getStack();
1159
- const outputMap = await stack.outputs();
1160
- const outputs = Object.fromEntries(
1161
- Object.entries(outputMap).map(([k, v]) => [k, v.value])
1162
- );
1163
- const base = extractBaseOutputs(outputs);
1164
- const conn = ctx.adapter.getConnectionInfo({
1165
- ...base,
1166
- privateKeyPath: ctx.config.ssh.keyPath,
1167
- knownHostsPath: ctx.config.ssh.knownHostsPath
1168
- });
1174
+ const { resolveConn } = await import("./conn-M2V5HBPJ.js");
1175
+ const conn = await resolveConn(ctx);
1169
1176
  const abortController = new AbortController();
1170
1177
  process13.on("SIGINT", () => abortController.abort());
1171
1178
  process13.on("SIGTERM", () => abortController.abort());
@@ -1228,7 +1235,7 @@ var config_default = defineCommand11({
1228
1235
  process13.stdout.write(JSON.stringify(cfg, null, 2) + "\n");
1229
1236
  return;
1230
1237
  }
1231
- const { atomicWriteConfig } = await import("./remote-config-3HZLUWEA.js");
1238
+ const { atomicWriteConfig } = await import("./remote-config-OZYBMBSK.js");
1232
1239
  try {
1233
1240
  await atomicWriteConfig(session, cfg, abortController.signal);
1234
1241
  } catch (err) {
@@ -1244,7 +1251,7 @@ var config_default = defineCommand11({
1244
1251
  abortController.signal
1245
1252
  );
1246
1253
  const image = imgResult.stdout.trim();
1247
- const restartResult = await execPrivileged(session, dockerRunCmd(image), abortController.signal);
1254
+ const restartResult = await execPrivileged(session, dockerRunCmd2(image), abortController.signal);
1248
1255
  if (restartResult.code !== 0) {
1249
1256
  failure(`Restart failed: ${restartResult.stderr}`);
1250
1257
  process13.exit(1);
@@ -1273,9 +1280,8 @@ var agents_default = defineCommand12({
1273
1280
  cursor: { type: "string", description: "Continue from a previous `logs` result cursor" }
1274
1281
  },
1275
1282
  async run({ args }) {
1276
- const { buildContext } = await import("./context-WOKSKAIA.js");
1277
- const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1278
- const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1283
+ const { buildContext } = await import("./context-EHSLWKSH.js");
1284
+ const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
1279
1285
  const [action, name] = args._ ?? [];
1280
1286
  if (action === "restart") {
1281
1287
  failure(
@@ -1292,17 +1298,8 @@ var agents_default = defineCommand12({
1292
1298
  process14.exit(2);
1293
1299
  }
1294
1300
  const ctx = buildContext(args);
1295
- const stack = await ctx.getStack();
1296
- const outputMap = await stack.outputs();
1297
- const outputs = Object.fromEntries(
1298
- Object.entries(outputMap).map(([k, v]) => [k, v.value])
1299
- );
1300
- const base = extractBaseOutputs(outputs);
1301
- const conn = ctx.adapter.getConnectionInfo({
1302
- ...base,
1303
- privateKeyPath: ctx.config.ssh.keyPath,
1304
- knownHostsPath: ctx.config.ssh.knownHostsPath
1305
- });
1301
+ const { resolveConn } = await import("./conn-M2V5HBPJ.js");
1302
+ const conn = await resolveConn(ctx);
1306
1303
  const abortController = new AbortController();
1307
1304
  process14.on("SIGINT", () => abortController.abort());
1308
1305
  process14.on("SIGTERM", () => abortController.abort());
@@ -1348,22 +1345,23 @@ var agents_default = defineCommand12({
1348
1345
  );
1349
1346
  }
1350
1347
  } else {
1351
- const { agentAuditCommand } = await import("./logs-5A7YYA53.js");
1352
- const limit = typeof args.limit === "string" ? parseInt(args.limit, 10) : 50;
1353
- const result = await execPrivileged(
1348
+ const { readAgentActivityRaw, DEFAULT_AGENT_LOG_LIMIT } = await import("./agent-activity-25FAWLAI.js");
1349
+ const limit = typeof args.limit === "string" ? parseInt(args.limit, 10) : DEFAULT_AGENT_LOG_LIMIT;
1350
+ const result = await readAgentActivityRaw(
1354
1351
  session,
1355
- agentAuditCommand({ agentId: name, limit, cursor: typeof args.cursor === "string" ? args.cursor : void 0 }),
1352
+ { agentId: name, limit, ...typeof args.cursor === "string" ? { cursor: args.cursor } : {} },
1356
1353
  abortController.signal
1357
1354
  );
1358
- if (result.code !== 0) {
1359
- failure(`Cannot read activity for "${name}": ${result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`}`);
1355
+ if (!result.ok) {
1356
+ failure(result.error);
1360
1357
  process14.exitCode = 1;
1361
1358
  return;
1362
1359
  }
1363
1360
  if (args.json) {
1364
- process14.stdout.write(result.stdout);
1361
+ process14.stdout.write(result.value);
1365
1362
  } else {
1366
- renderAgentRuns(name, result.stdout);
1363
+ const { parseAgentActivity } = await import("./agent-activity-25FAWLAI.js");
1364
+ renderAgentRuns(name, parseAgentActivity(name, result.value));
1367
1365
  }
1368
1366
  }
1369
1367
  } finally {
@@ -1372,16 +1370,14 @@ var agents_default = defineCommand12({
1372
1370
  }
1373
1371
  }
1374
1372
  });
1375
- function renderAgentRuns(agentId, stdout) {
1376
- let page;
1377
- try {
1378
- page = JSON.parse(stdout.trim() || "{}");
1379
- } catch {
1380
- failure(`Cannot read activity for "${agentId}": unexpected output: ${stdout.trim().slice(0, 200)}`);
1373
+ function renderAgentRuns(agentId, parsed) {
1374
+ if (!parsed.ok) {
1375
+ failure(parsed.error);
1381
1376
  process14.exitCode = 1;
1382
1377
  return;
1383
1378
  }
1384
- const records = page.records ?? [];
1379
+ const page = parsed.value;
1380
+ const records = page.records;
1385
1381
  if (records.length === 0) {
1386
1382
  info(`No recorded activity for agent "${agentId}".`);
1387
1383
  return;
@@ -1401,99 +1397,6 @@ function renderAgentRuns(agentId, stdout) {
1401
1397
  // src/cli/commands/migrate.ts
1402
1398
  import { defineCommand as defineCommand13 } from "citty";
1403
1399
  import process15 from "process";
1404
-
1405
- // src/openclaw/migrate.ts
1406
- async function migrate(steps) {
1407
- const sourceImage = await steps.inspectSource();
1408
- if (!sourceImage) {
1409
- return {
1410
- kind: "nothing-to-migrate",
1411
- reason: "No running OpenClaw container was found, so there is no 1.x state to extract. If this deployment was restarted on a clawops release before 2.0, its state was already lost \u2014 clawops mounted none, so every container replacement discarded it. Deploy fresh with `clawops up`."
1412
- };
1413
- }
1414
- const identityBefore = await steps.deviceId();
1415
- const backup = await steps.backup();
1416
- if (!backup.ok) {
1417
- return {
1418
- kind: "refused",
1419
- reason: `Refusing to migrate without a verified backup: ${backup.detail}
1420
- This replaces the running container, and the state it extracts is the only copy.`
1421
- };
1422
- }
1423
- const extracted = await steps.extract();
1424
- if (!extracted.ok) {
1425
- return {
1426
- kind: "failed",
1427
- reason: "Could not extract state from the running container; it has not been touched.",
1428
- backupDetail: backup.detail
1429
- };
1430
- }
1431
- await steps.chown();
1432
- await steps.removeSource();
1433
- await steps.writeConfig();
1434
- await steps.start();
1435
- let restarts = 0;
1436
- let gate = await steps.gate();
1437
- if (!gate.ok) {
1438
- await steps.start();
1439
- restarts = 1;
1440
- gate = await steps.gate();
1441
- }
1442
- if (!gate.ok) {
1443
- return {
1444
- kind: "failed",
1445
- reason: gate.reason ?? "the gateway did not start after migration",
1446
- backupDetail: backup.detail
1447
- };
1448
- }
1449
- const identityAfter = await steps.deviceId();
1450
- const identity = identityBefore === void 0 || identityAfter === void 0 ? "unknown" : identityBefore === identityAfter ? "preserved" : "changed";
1451
- return { kind: "migrated", entries: extracted.entries, identity, restarts };
1452
- }
1453
- function describeMigration(o) {
1454
- switch (o.kind) {
1455
- case "migrated": {
1456
- const lines = [
1457
- `Migrated to the 2.0 runtime contract. Carried over: ${o.entries.join(", ") || "(nothing)"}.`
1458
- ];
1459
- if (o.restarts > 0) {
1460
- lines.push(
1461
- "The gateway needed a second start to finish its state-schema migration, which is expected for a 1.x database."
1462
- );
1463
- }
1464
- if (o.identity === "preserved") {
1465
- lines.push("Device identity preserved \u2014 paired devices do not need re-pairing.");
1466
- } else if (o.identity === "changed") {
1467
- lines.push(
1468
- "Device identity CHANGED. Paired devices and nodes must be re-paired \u2014 check `openclaw devices list` on the host for what was paired."
1469
- );
1470
- } else {
1471
- lines.push(
1472
- "Device identity could not be compared, so pairings may or may not have carried over. Check `openclaw devices list` before relying on them."
1473
- );
1474
- }
1475
- lines.push("Your 1.x config was NOT applied \u2014 it never took effect on 1.x either. Review it");
1476
- lines.push("and set what you need with `clawops config set`.");
1477
- return lines.join("\n");
1478
- }
1479
- case "nothing-to-migrate":
1480
- return o.reason;
1481
- case "refused":
1482
- return o.reason;
1483
- case "failed":
1484
- return `Migration failed: ${o.reason}
1485
- A verified backup was taken first: ${o.backupDetail}
1486
- Restore it with \`clawops backup restore --file <archive>\` before retrying.`;
1487
- }
1488
- }
1489
-
1490
- // src/cli/commands/migrate.ts
1491
- var SYNTHESISED_CONFIG = JSON.stringify({
1492
- meta: { lastTouchedVersion: "2026.9.2" },
1493
- gateway: { mode: "local", port: GATEWAY_PORT, auth: { mode: "token" } },
1494
- models: {},
1495
- channels: {}
1496
- });
1497
1400
  var migrate_default = defineCommand13({
1498
1401
  meta: {
1499
1402
  name: "migrate",
@@ -1505,120 +1408,41 @@ var migrate_default = defineCommand13({
1505
1408
  yes: { type: "boolean", description: "Skip the confirmation prompt" }
1506
1409
  },
1507
1410
  async run({ args }) {
1508
- const { buildContext } = await import("./context-WOKSKAIA.js");
1509
- const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1510
- const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1511
- const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
1512
- const requested = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : void 0;
1513
- const target = requested ? await guardOpenclawVersion(requested) : await defaultOpenclawVersion();
1514
- const targetImage = `ghcr.io/openclaw/openclaw:${target}`;
1515
- const ctx = buildContext(args);
1516
- const stack = await ctx.getStack();
1517
- const outputs = Object.fromEntries(
1518
- Object.entries(await stack.outputs()).map(([k, v]) => [k, v.value])
1519
- );
1520
- const conn = ctx.adapter.getConnectionInfo({
1521
- ...extractBaseOutputs(outputs),
1522
- privateKeyPath: ctx.config.ssh.keyPath,
1523
- knownHostsPath: ctx.config.ssh.knownHostsPath
1411
+ const { prepareMigration, runMigration, migrationQuestion } = await import("./migrate-flow-24XA4GZL.js");
1412
+ const target = await prepareMigration({
1413
+ stack: typeof args.stack === "string" ? args.stack : void 0,
1414
+ openclawVersion: typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : void 0
1524
1415
  });
1416
+ if (!args.yes) {
1417
+ const inquirer = (await import("inquirer")).default;
1418
+ const { confirmed } = await inquirer.prompt([{
1419
+ type: "confirm",
1420
+ name: "confirmed",
1421
+ message: migrationQuestion(target),
1422
+ default: false
1423
+ }]);
1424
+ if (!confirmed) {
1425
+ info("Aborted. Nothing was changed.");
1426
+ return;
1427
+ }
1428
+ }
1525
1429
  const ac = new AbortController();
1526
1430
  process15.on("SIGINT", () => ac.abort());
1527
- const { session, release } = await acquireSession({
1528
- host: conn.host,
1529
- port: conn.port,
1530
- user: conn.user,
1531
- privateKeyPath: conn.privateKeyPath,
1532
- knownHostsPath: conn.knownHostsPath,
1533
- signal: ac.signal
1534
- });
1535
- const spin = spinner("Inspecting the deployment...");
1536
- const run = (cmd) => execPrivileged(session, cmd, ac.signal);
1537
- const archive = "/tmp/clawops-premigration.tar.gz";
1538
- const configPath = `${STATE_DIR_HOST_LINUX}/${CONFIG_FILENAME}`;
1539
- const steps = {
1540
- inspectSource: async () => {
1541
- const r = await run(`docker inspect openclaw --format '{{.Config.Image}}'`);
1542
- const image = r.stdout.trim();
1543
- return r.code === 0 && image.includes(":") ? image : void 0;
1544
- },
1545
- backup: async () => {
1546
- spin.text = "Taking a verified backup...";
1547
- const r = await run(
1548
- `docker exec openclaw openclaw backup create --output ${archive} --verify --json`
1549
- );
1550
- return r.code === 0 ? { ok: true, detail: `${archive} on the host (verified)` } : { ok: false, detail: (r.stderr || r.stdout).slice(0, 200) };
1551
- },
1552
- extract: async () => {
1553
- spin.text = "Extracting state from the running container...";
1554
- const mk = await run(`mkdir -p ${STATE_DIR_HOST_LINUX}`);
1555
- if (mk.code !== 0) return { ok: false, entries: [] };
1556
- const cp = await run(`docker cp openclaw:/home/node/.openclaw/. ${STATE_DIR_HOST_LINUX}/`);
1557
- if (cp.code !== 0) return { ok: false, entries: [] };
1558
- const ls = await run(`ls ${STATE_DIR_HOST_LINUX}`);
1559
- return { ok: true, entries: ls.stdout.trim().split(/\s+/).filter(Boolean) };
1560
- },
1561
- chown: async () => {
1562
- await run(`chown -R ${CONTAINER_UID}:${CONTAINER_UID} ${STATE_DIR_HOST_LINUX}`);
1563
- },
1564
- removeSource: async () => {
1565
- spin.text = "Stopping the 1.x container...";
1566
- await run("docker stop openclaw 2>/dev/null || true");
1567
- await run("docker rm openclaw 2>/dev/null || true");
1568
- },
1569
- writeConfig: async () => {
1570
- const b64 = Buffer.from(SYNTHESISED_CONFIG, "utf-8").toString("base64");
1571
- await run(
1572
- `echo '${b64}' | base64 -d > ${configPath} && chown ${CONTAINER_UID}:${CONTAINER_UID} ${configPath}`
1573
- );
1574
- },
1575
- start: async () => {
1576
- spin.text = "Starting the 2.0 gateway...";
1577
- await run(gatewayRunCommand({ image: targetImage, stateDir: STATE_DIR_HOST_LINUX }));
1578
- },
1579
- gate: async () => {
1580
- spin.text = "Waiting for the gateway to start...";
1581
- let last;
1582
- for (let i = 0; i < 15; i++) {
1583
- if (ac.signal.aborted) return { ok: false, reason: "aborted" };
1584
- const r = await run(probeCommand("started", GATEWAY_PORT));
1585
- const v = interpretProbe("started", r.stdout);
1586
- if (v.ok) return { ok: true };
1587
- last = v.reason;
1588
- await new Promise((res) => setTimeout(res, 2e3));
1589
- }
1590
- return { ok: false, reason: last ?? "no response" };
1591
- },
1592
- deviceId: async () => {
1593
- const file = await run(
1594
- `docker exec openclaw sh -c 'cat ~/.openclaw/identity/device.json 2>/dev/null' || cat ${STATE_DIR_HOST_LINUX}/identity/device.json 2>/dev/null || true`
1595
- );
1596
- try {
1597
- const parsed = JSON.parse(file.stdout.trim());
1598
- return parsed.deviceId;
1599
- } catch {
1600
- return void 0;
1601
- }
1602
- }
1603
- };
1431
+ let spin;
1604
1432
  try {
1605
- const outcome = await migrate(steps);
1606
- spin.stop();
1607
- const report = describeMigration(outcome);
1608
- if (outcome.kind === "migrated") {
1609
- success(report.split("\n")[0]);
1610
- for (const line2 of report.split("\n").slice(1)) {
1611
- if (/CHANGED|could not be compared/.test(line2)) warn(line2);
1612
- else info(line2);
1433
+ const result = await runMigration(target, {
1434
+ signal: ac.signal,
1435
+ onStep: (text) => {
1436
+ if (spin) spin.text = text;
1437
+ else spin = spinner(text);
1613
1438
  }
1614
- info(`Pre-migration backup: ${archive}`);
1615
- } else {
1616
- failure(report);
1617
- process15.exit(outcome.kind === "nothing-to-migrate" ? 0 : 1);
1618
- }
1439
+ });
1440
+ spin?.stop();
1441
+ const render2 = { success, info, warn, failure };
1442
+ for (const line2 of result.lines) render2[line2.level](line2.text);
1443
+ if (result.outcome.kind !== "migrated") process15.exit(result.exitCode);
1619
1444
  } finally {
1620
- release();
1621
- drainPool();
1445
+ spin?.stop();
1622
1446
  }
1623
1447
  }
1624
1448
  });
@@ -1626,13 +1450,6 @@ var migrate_default = defineCommand13({
1626
1450
  // src/cli/commands/gateway.ts
1627
1451
  import { defineCommand as defineCommand14 } from "citty";
1628
1452
  import process16 from "process";
1629
- function dockerRunCmd2(version, publish = "loopback") {
1630
- return gatewayRunCommand({
1631
- image: `ghcr.io/openclaw/openclaw:${version}`,
1632
- stateDir: STATE_DIR_HOST_LINUX,
1633
- publish
1634
- });
1635
- }
1636
1453
  var gateway_default = defineCommand14({
1637
1454
  meta: {
1638
1455
  name: "gateway",
@@ -1644,26 +1461,16 @@ var gateway_default = defineCommand14({
1644
1461
  json: { type: "boolean", description: "Emit JSON (for status)" }
1645
1462
  },
1646
1463
  async run({ args }) {
1647
- const { buildContext } = await import("./context-WOKSKAIA.js");
1648
- const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1649
- const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1464
+ const { buildContext } = await import("./context-EHSLWKSH.js");
1465
+ const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
1650
1466
  const [action, versionArg] = args._ ?? [];
1651
1467
  if (!action || !["status", "restart", "update"].includes(action)) {
1652
1468
  failure("Usage: clawops gateway <status | restart | update [version]>");
1653
1469
  process16.exit(2);
1654
1470
  }
1655
1471
  const ctx = buildContext(args);
1656
- const stack = await ctx.getStack();
1657
- const outputMap = await stack.outputs();
1658
- const outputs = Object.fromEntries(
1659
- Object.entries(outputMap).map(([k, v]) => [k, v.value])
1660
- );
1661
- const base = extractBaseOutputs(outputs);
1662
- const conn = ctx.adapter.getConnectionInfo({
1663
- ...base,
1664
- privateKeyPath: ctx.config.ssh.keyPath,
1665
- knownHostsPath: ctx.config.ssh.knownHostsPath
1666
- });
1472
+ const { resolveConn } = await import("./conn-M2V5HBPJ.js");
1473
+ const conn = await resolveConn(ctx);
1667
1474
  const abortController = new AbortController();
1668
1475
  process16.on("SIGINT", () => abortController.abort());
1669
1476
  process16.on("SIGTERM", () => abortController.abort());
@@ -1677,25 +1484,12 @@ var gateway_default = defineCommand14({
1677
1484
  });
1678
1485
  try {
1679
1486
  if (action === "status") {
1680
- const { inspectContainer } = await import("./docker-3PDO4TS7.js");
1681
- const inspected = await inspectContainer(
1682
- session,
1683
- "openclaw",
1684
- '{"status":"{{.State.Status}}","started":"{{.State.StartedAt}}","image":"{{.Config.Image}}"}',
1685
- abortController.signal
1686
- );
1687
- let status = { status: "unknown", started: "", image: "" };
1688
- if (inspected.kind === "ok") {
1689
- try {
1690
- status = JSON.parse(inspected.value);
1691
- } catch {
1692
- }
1693
- } else if (inspected.kind === "missing") {
1694
- status = { status: "not running", started: "", image: "" };
1695
- } else {
1696
- failure(`Could not ask docker about the gateway: ${inspected.detail}`);
1487
+ const inspected = await gatewayStatus(session, abortController.signal);
1488
+ if (!inspected.ok) {
1489
+ failure(inspected.error);
1697
1490
  process16.exit(1);
1698
1491
  }
1492
+ const status = inspected.value;
1699
1493
  if (args.json) {
1700
1494
  printJson(jsonOk(status));
1701
1495
  } else {
@@ -1723,7 +1517,7 @@ var gateway_default = defineCommand14({
1723
1517
  const spin = spinner("Restarting gateway...");
1724
1518
  const result = await execPrivileged(
1725
1519
  session,
1726
- dockerRunCmd2(version, publish),
1520
+ dockerRunCmd(version, publish),
1727
1521
  abortController.signal
1728
1522
  );
1729
1523
  spin.stop();
@@ -1733,121 +1527,23 @@ var gateway_default = defineCommand14({
1733
1527
  }
1734
1528
  success(`Gateway restarted (${version}).`);
1735
1529
  } else {
1736
- const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
1737
- const requested = versionArg ?? args.channel;
1738
- const version = requested ? await guardOpenclawVersion(requested) : await defaultOpenclawVersion();
1530
+ const target = await resolveUpdateVersion(versionArg ?? args.channel);
1531
+ if (!target.ok) throw new UsageError(target.error);
1532
+ const version = target.value;
1739
1533
  const spin = spinner(`Updating gateway to ${version}...`);
1740
- const targetImage = `ghcr.io/openclaw/openclaw:${version}`;
1741
- const pullResult = await execPrivileged(
1742
- session,
1743
- `docker pull ${targetImage}`,
1744
- abortController.signal
1745
- );
1746
- if (pullResult.code !== 0) {
1747
- spin.stop();
1748
- failure(`Pull failed: ${pullResult.stderr}`);
1749
- process16.exit(1);
1750
- }
1751
- spin.text = "Checking state compatibility...";
1752
- const {
1753
- snapshotCommand,
1754
- snapshotPathFrom,
1755
- preflightCommand,
1756
- parsePreflight,
1757
- judgePreflight
1758
- } = await import("./upgrade-ULD4KFYC.js");
1759
- const cur = imageForRestart(
1760
- (await execPrivileged(session, IMAGE_INSPECT_CMD, abortController.signal)).stdout
1761
- );
1762
- const snapRepo = `${STATE_DIR_HOST_LINUX}/snapshots`;
1763
- const snapOut = await execPrivileged(
1764
- session,
1765
- snapshotCommand(cur.ok ? cur.value : targetImage, STATE_DIR_HOST_LINUX, snapRepo),
1766
- abortController.signal
1767
- );
1768
- const snapPath = snapshotPathFrom(snapOut.stdout);
1769
- if (!snapPath) {
1770
- spin.stop();
1771
- failure(
1772
- "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, 300)
1773
- );
1774
- process16.exit(1);
1775
- }
1776
- const pre = await execPrivileged(
1777
- session,
1778
- preflightCommand(targetImage, STATE_DIR_HOST_LINUX, `${snapPath}/database.sqlite`),
1779
- abortController.signal
1780
- );
1781
- const report = parsePreflight(pre.stdout);
1782
- const verdict = report ? judgePreflight(report) : { ok: false, reason: `preflight produced no readable report: ${pre.stderr.slice(0, 200)}` };
1783
- if (!verdict.ok) {
1784
- spin.stop();
1785
- failure(`Refusing to upgrade to ${version}: ${verdict.reason}`);
1786
- info(`A snapshot of the current state was kept at ${snapPath}.`);
1787
- process16.exit(1);
1788
- }
1789
- if (verdict.ok && verdict.note) info(verdict.note);
1790
- spin.text = `Updating gateway to ${version}...`;
1791
- const pubU = await execPrivileged(session, PUBLISH_INSPECT_CMD, abortController.signal);
1792
- const runResult = await execPrivileged(
1793
- session,
1794
- dockerRunCmd2(version, publishForRestart(pubU.stdout)),
1795
- abortController.signal
1796
- );
1797
- if (runResult.code !== 0) {
1798
- spin.stop();
1799
- failure(`Start failed: ${runResult.stderr}`);
1800
- info(`State snapshot from before the upgrade: ${snapPath}`);
1801
- process16.exit(1);
1802
- }
1803
- spin.text = "Waiting for the gateway to start...";
1804
- const { probeCommand: probeCommand2, interpretProbe: interpretProbe2 } = await import("./health-5CCOXYGG.js");
1805
- const { repairCommand, describeOutcome } = await import("./upgrade-ULD4KFYC.js");
1806
- const publish = publishForRestart(pubU.stdout);
1807
- const gate = async () => {
1808
- let last;
1809
- for (let i = 0; i < 15; i++) {
1810
- if (abortController.signal.aborted) return { ok: false, reason: "aborted" };
1811
- const r = await execPrivileged(
1812
- session,
1813
- probeCommand2("started", GATEWAY_PORT),
1814
- abortController.signal
1815
- );
1816
- const v = interpretProbe2("started", r.stdout);
1817
- if (v.ok) return { ok: true };
1818
- last = v.reason;
1819
- await new Promise((res) => setTimeout(res, 2e3));
1820
- }
1821
- return { ok: false, reason: last ?? "no response" };
1822
- };
1823
- const { resolveUpgrade } = await import("./upgrade-ULD4KFYC.js");
1824
- const outcome = await resolveUpgrade(
1825
- {
1826
- gate,
1827
- repair: async () => {
1828
- spin.text = "Gateway did not start; attempting one-shot repair...";
1829
- await execPrivileged(
1830
- session,
1831
- repairCommand(targetImage, STATE_DIR_HOST_LINUX),
1832
- abortController.signal
1833
- );
1834
- },
1835
- run: async (v) => {
1836
- await execPrivileged(session, dockerRunCmd2(v, publish), abortController.signal);
1837
- }
1534
+ const result = await updateGateway(session, version, {
1535
+ signal: abortController.signal,
1536
+ onProgress: (text) => {
1537
+ spin.text = text;
1838
1538
  },
1839
- {
1840
- version,
1841
- previousVersion: cur.ok ? versionOf(cur.value) : void 0,
1842
- snapshotPath: snapPath
1843
- }
1844
- );
1539
+ onNote: (text) => info(text)
1540
+ });
1845
1541
  spin.stop();
1846
- const message = describeOutcome(outcome, version);
1847
- if (outcome.kind === "started" || outcome.kind === "repaired") {
1848
- success(message);
1542
+ if (result.ok) {
1543
+ success(result.message);
1849
1544
  } else {
1850
- failure(message);
1545
+ failure(result.message);
1546
+ if (result.hint) info(result.hint);
1851
1547
  process16.exit(1);
1852
1548
  }
1853
1549
  }
@@ -1860,24 +1556,23 @@ var gateway_default = defineCommand14({
1860
1556
 
1861
1557
  // src/cli/commands/backup.ts
1862
1558
  import { defineCommand as defineCommand15 } from "citty";
1863
- import { createWriteStream } from "fs";
1864
- import { pipeline } from "stream/promises";
1865
1559
  import process17 from "process";
1866
1560
  var backup_default = defineCommand15({
1867
1561
  meta: {
1868
1562
  name: "backup",
1869
- description: "Create an OpenClaw backup (restore is unavailable on this release line)"
1563
+ description: "Create an OpenClaw backup, or restore one into a staging directory"
1870
1564
  },
1871
1565
  args: {
1872
- action: { type: "positional", description: "Action: create (restore returns in clawops 2.x)", required: true },
1566
+ action: { type: "positional", description: "Action: create | restore", required: true },
1873
1567
  out: { type: "string", description: "[create] Local path to write the backup archive" },
1874
- file: { type: "string", description: "[restore] Local backup archive (restore is unavailable)" },
1568
+ file: { type: "string", description: "[restore] Local backup archive to verify and expand on the host" },
1875
1569
  stack: { type: "string", description: "Target stack name" },
1876
- yes: { type: "boolean", description: "[restore] Skip confirmation prompt" }
1570
+ yes: { type: "boolean", description: "[restore] Skip confirmation prompt" },
1571
+ activate: { type: "boolean", description: "[restore] Put the restored state into service: stop the gateway, swap it in, restart, and roll back if it does not come up" }
1877
1572
  },
1878
1573
  async run({ args }) {
1879
- const { buildContext } = await import("./context-WOKSKAIA.js");
1880
- const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1574
+ const { buildContext } = await import("./context-EHSLWKSH.js");
1575
+ const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
1881
1576
  const action = args.action;
1882
1577
  if (action !== "create" && action !== "restore") {
1883
1578
  throw new UsageError(`Unknown action: ${action}. Use "create" or "restore"`);
@@ -1927,86 +1622,91 @@ var backup_default = defineCommand15({
1927
1622
  signal: abortController.signal
1928
1623
  });
1929
1624
  try {
1625
+ const flows = await import("./backup-flows-NDP5JTR3.js");
1930
1626
  if (action === "create") {
1931
- const outPath = typeof args.out === "string" ? args.out : `openclaw-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.tar.gz`;
1627
+ const outPath = typeof args.out === "string" ? args.out : flows.defaultBackupFilename();
1932
1628
  info(`Writing backup to ${outPath}...`);
1933
1629
  const spin = spinner("Creating backup on remote host...");
1934
- const remoteArchive = "/tmp/clawops-backup.tar.gz";
1935
- const createResult = await execPrivileged(
1936
- session,
1937
- `docker exec openclaw sh -lc 'rm -f ${remoteArchive} && openclaw backup create --output ${remoteArchive} --verify --json'`,
1938
- abortController.signal
1939
- );
1940
- if (createResult.code !== 0) {
1941
- spin.stop();
1942
- throw new Error(`Backup failed on the remote host: ${createResult.stderr.slice(0, 300)}`);
1943
- }
1944
- const backupStream = await streamPrivileged(
1945
- session,
1946
- `docker exec openclaw cat ${remoteArchive}`,
1947
- abortController.signal
1948
- );
1630
+ const created = await flows.createBackup(session, {
1631
+ outPath,
1632
+ signal: abortController.signal,
1633
+ onPhase: (phase) => {
1634
+ if (phase === "downloading") spin.stop();
1635
+ }
1636
+ });
1949
1637
  spin.stop();
1950
- const fileStream = createWriteStream(outPath, { mode: 384 });
1951
- await pipeline(backupStream, fileStream);
1952
- await execPrivileged(
1953
- session,
1954
- `docker exec openclaw rm -f ${remoteArchive}`,
1955
- abortController.signal
1956
- );
1638
+ if (!created.ok) throw new Error(created.reason);
1957
1639
  success(`Backup saved to ${outPath}`);
1958
- info("Saved with mode 0600. The archive contains the state database \u2014 OAuth tokens,");
1959
- info("secrets and device credentials, unencrypted. Treat it as a credential.");
1640
+ for (const line2 of flows.ARCHIVE_IS_A_CREDENTIAL) info(line2);
1960
1641
  } else {
1961
1642
  const file = typeof args.file === "string" ? args.file : void 0;
1962
1643
  if (!file) {
1963
1644
  throw new UsageError("Usage: clawops backup restore --file <archive.tar.gz>");
1964
1645
  }
1965
- const { createReadStream } = await import("fs");
1966
- const remoteArchive = "/tmp/clawops-restore.tar.gz";
1967
- const staging = `/tmp/clawops-restored-${Date.now()}`;
1968
- const spin = spinner("Uploading archive...");
1969
- const uploadResult = await execPrivilegedWithInput(
1970
- session,
1971
- `docker exec -i openclaw sh -c 'cat > ${remoteArchive}'`,
1972
- createReadStream(file),
1973
- abortController.signal
1974
- );
1975
- if (uploadResult.code !== 0) {
1976
- spin.stop();
1977
- throw new Error(`Could not upload the archive: ${uploadResult.stderr.slice(0, 300)}`);
1646
+ let spin;
1647
+ const staged = await flows.stageRestore(session, {
1648
+ file,
1649
+ signal: abortController.signal,
1650
+ onPhase: (phase) => {
1651
+ if (phase === "uploading") spin = spinner("Uploading archive...");
1652
+ else if (spin) spin.text = "Verifying and restoring to a staging directory...";
1653
+ }
1654
+ });
1655
+ spin?.stop();
1656
+ if (staged.unparsedReport !== void 0) info(staged.unparsedReport);
1657
+ if (!staged.ok) {
1658
+ if (staged.kind === "refused") throw new UsageError(staged.reason);
1659
+ if (staged.kind === "failed") throw new Error(staged.reason);
1660
+ failure(staged.reason);
1661
+ if (staged.stagingOnHost) info(flows.nothingChanged(staged.stagingOnHost));
1662
+ process17.exit(1);
1978
1663
  }
1979
- spin.text = "Verifying and restoring to a staging directory...";
1980
- const restore = await execPrivileged(
1981
- session,
1982
- `docker exec openclaw openclaw backup restore ${remoteArchive} --target ${staging} --json`,
1983
- abortController.signal
1984
- );
1985
- await execPrivileged(
1986
- session,
1987
- `docker exec openclaw rm -f ${remoteArchive}`,
1988
- abortController.signal
1989
- );
1990
- spin.stop();
1991
- if (restore.code !== 0) {
1992
- throw new Error(`Restore failed: ${(restore.stderr || restore.stdout).slice(0, 400)}`);
1664
+ const { stagingOnHost, stateDir } = staged;
1665
+ success(`Archive verified and expanded to ${stagingOnHost} on the host.`);
1666
+ if (staged.entryCount !== void 0) info(`${staged.entryCount} entries restored.`);
1667
+ for (const w of staged.warnings) warn(w);
1668
+ if (!args.activate) {
1669
+ info("");
1670
+ info("Nothing has been activated. Re-run with --activate to put this state into");
1671
+ info("service, or adopt it by hand:");
1672
+ info(" 1. clawops ssh --command 'sudo docker stop openclaw'");
1673
+ info(` 2. replace the contents of ${stateDir} with those of`);
1674
+ info(` ${staged.statePath}`);
1675
+ info(` (the archive holds ${staged.stateDirInArchive} under payload/posix, not a`);
1676
+ info(" drop-in state directory \u2014 moving the bundle itself stops the gateway)");
1677
+ info(" 3. clawops gateway restart");
1678
+ for (const line2 of flows.PLUGINS_NOT_CARRIED) info(line2);
1679
+ return;
1993
1680
  }
1994
- let report = {};
1995
- try {
1996
- report = JSON.parse(restore.stdout.trim());
1997
- } catch {
1998
- info(restore.stdout);
1681
+ if (!args.yes) {
1682
+ const { createInterface: createInterface3 } = await import("readline/promises");
1683
+ const rl = createInterface3({ input: process17.stdin, output: process17.stdout });
1684
+ const answer = await rl.question(
1685
+ `Stop the gateway and put this restored state into service? The state it replaces is kept alongside it, and clawops puts it back if the gateway does not come up. [y/N] `
1686
+ );
1687
+ rl.close();
1688
+ if (!answer.toLowerCase().startsWith("y")) {
1689
+ info(`Aborted. The restored state is at ${stagingOnHost}; nothing was changed.`);
1690
+ return;
1691
+ }
1999
1692
  }
2000
- success(`Archive verified and restored to ${staging} on the host.`);
2001
- if (report.entryCount !== void 0) info(`${report.entryCount} entries restored.`);
2002
- for (const w of report.warnings ?? []) warn(w);
2003
- info("");
2004
- info("Nothing has been activated. To adopt the restored state:");
2005
- info(" 1. clawops ssh --command 'sudo docker stop openclaw'");
2006
- info(` 2. replace the state directory contents with ${staging}`);
2007
- info(" 3. clawops gateway restart");
2008
- info("Provider plugins are not carried in the archive; re-run `clawops apply` to");
2009
- info("reinstall them, or the gateway starts without its model providers.");
1693
+ const activating = spinner("Activating the restored state...");
1694
+ const outcome = await flows.activateStaged(session, staged, {
1695
+ signal: abortController.signal,
1696
+ onPhase: (phase) => {
1697
+ if (phase === "restarting") activating.text = "Restarting the gateway...";
1698
+ else if (phase === "waiting") activating.text = "Waiting for the gateway to answer...";
1699
+ else if (phase === "done") activating.stop();
1700
+ }
1701
+ });
1702
+ activating.stop();
1703
+ if (!outcome.ok) {
1704
+ failure(outcome.reason);
1705
+ for (const line2 of flows.activationFailureDetail(outcome)) info(line2);
1706
+ process17.exit(1);
1707
+ }
1708
+ success(flows.ACTIVATED);
1709
+ for (const line2 of flows.activatedLines(outcome.preserved)) info(line2);
2010
1710
  }
2011
1711
  } finally {
2012
1712
  release();
@@ -2035,16 +1735,16 @@ var stacks_default = defineCommand16({
2035
1735
  process18.exit(2);
2036
1736
  }
2037
1737
  if (action === "list") {
2038
- const config2 = requireConfig();
2039
- const defaultStack = config2.defaults.stack;
2040
- const rows = Object.entries(config2.stacks).map(([n, s]) => [
1738
+ const config = requireConfig();
1739
+ const defaultStack = config.defaults.stack;
1740
+ const rows = Object.entries(config.stacks).map(([n, s]) => [
2041
1741
  n === defaultStack ? `${n} *` : n,
2042
1742
  s.provider,
2043
1743
  s.region ?? "\u2014",
2044
1744
  s.stateUrl
2045
1745
  ]);
2046
1746
  if (args.json) {
2047
- const data = Object.entries(config2.stacks).map(([n, s]) => ({
1747
+ const data = Object.entries(config.stacks).map(([n, s]) => ({
2048
1748
  name: n,
2049
1749
  provider: s.provider,
2050
1750
  region: s.region ?? null,
@@ -2069,49 +1769,17 @@ var stacks_default = defineCommand16({
2069
1769
  failure("Usage: clawops stacks delete <name>");
2070
1770
  process18.exit(2);
2071
1771
  }
2072
- const config = requireConfig();
2073
- if (!(name in config.stacks)) {
2074
- throw new UsageError(`Stack "${name}" not found in config.`);
2075
- }
2076
- const stackNames = Object.keys(config.stacks);
2077
- if (stackNames.length === 1) {
2078
- throw new UsageError(
2079
- `Cannot delete the only remaining stack "${name}". Add another stack first or run \`clawops destroy\` to tear down resources.`
2080
- );
2081
- }
2082
- if (name === config.defaults.stack && !args.force) {
2083
- throw new UsageError(
2084
- `"${name}" is the default stack. Use --force to delete it (clawops will switch the default to another stack).`
2085
- );
2086
- }
2087
- let isDeployed = false;
2088
- if (!args.force) {
2089
- try {
2090
- const { buildContext } = await import("./context-WOKSKAIA.js");
2091
- const ctx = buildContext({ stack: name });
2092
- if (ctx.adapter.name === "local") {
2093
- isDeployed = !!ctx.localState;
2094
- } else {
2095
- const stack = await ctx.getStack();
2096
- const outputMap = await stack.outputs();
2097
- const outputs = Object.fromEntries(
2098
- Object.entries(outputMap).map(([k, v]) => [k, v.value])
2099
- );
2100
- isDeployed = !!outputs["publicIp"];
2101
- }
2102
- } catch {
2103
- warn(`Could not verify deployment status for "${name}" \u2014 proceeding anyway.`);
1772
+ const { checkStackDelete, deleteStackFromConfig, forgetNotice } = await import("./stack-delete-NTIGKWUF.js");
1773
+ const check = await checkStackDelete(name, { force: !!args.force });
1774
+ if (!check.ok) {
1775
+ if (check.refusal === "deployed") {
1776
+ failure(check.reason);
1777
+ process18.exit(1);
2104
1778
  }
1779
+ throw new UsageError(check.reason);
2105
1780
  }
2106
- if (isDeployed) {
2107
- failure(
2108
- `Stack "${name}" is still deployed. Run \`clawops down --stack ${name}\` first to destroy cloud resources, or pass --force to remove from registry only.`
2109
- );
2110
- process18.exit(1);
2111
- }
2112
- warn(
2113
- `This removes "${name}" from clawops config only. Cloud resources are NOT destroyed. Run \`clawops destroy --stack ` + name + "` first if you want to remove cloud resources."
2114
- );
1781
+ for (const w of check.warnings) warn(w);
1782
+ warn(forgetNotice(name));
2115
1783
  if (!args.yes) {
2116
1784
  const confirmed = await confirm(`Delete stack "${name}" from config?`);
2117
1785
  if (!confirmed) {
@@ -2119,18 +1787,9 @@ var stacks_default = defineCommand16({
2119
1787
  return;
2120
1788
  }
2121
1789
  }
2122
- const updated = { ...config };
2123
- const newStacks = { ...config.stacks };
2124
- delete newStacks[name];
2125
- updated.stacks = newStacks;
2126
- if (name === config.defaults.stack) {
2127
- updated.defaults = { ...config.defaults, stack: Object.keys(newStacks)[0] };
2128
- }
2129
- setConfig(updated);
2130
- success(`Stack "${name}" removed from config.`);
2131
- if (name === config.defaults.stack) {
2132
- info(`Default stack switched to "${updated.defaults.stack}".`);
2133
- }
1790
+ const deleted = deleteStackFromConfig(name);
1791
+ success(deleted.message);
1792
+ if (deleted.newDefaultMessage) info(deleted.newDefaultMessage);
2134
1793
  }
2135
1794
  });
2136
1795
  async function confirm(message) {
@@ -2228,7 +1887,7 @@ var serveCmd = defineCommand18({
2228
1887
  inspector: { type: "boolean", description: "Enable MCP inspector" }
2229
1888
  },
2230
1889
  async run({ args }) {
2231
- const { serveMcp } = await import("./server-OXWHBTXK.js");
1890
+ const { serveMcp } = await import("./server-E3YA32ND.js");
2232
1891
  await serveMcp({
2233
1892
  port: args.http ? Number(args.http) : void 0,
2234
1893
  bind: args.bind,
@@ -2306,11 +1965,11 @@ var wireCmd = defineCommand18({
2306
1965
  rewire: { type: "boolean", description: "Replace an existing clawops entry" }
2307
1966
  },
2308
1967
  async run({ args }) {
2309
- const { buildContext } = await import("./context-WOKSKAIA.js");
2310
- const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1968
+ const { buildContext } = await import("./context-EHSLWKSH.js");
1969
+ const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
2311
1970
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
2312
- const { wireGatewayMcp } = await import("./mcp-wire-FIO5DFBY.js");
2313
- const { MCP_HTTP_PORT } = await import("./server-OXWHBTXK.js");
1971
+ const { wireGatewayMcp } = await import("./mcp-wire-BXF4DJFT.js");
1972
+ const { MCP_HTTP_PORT } = await import("./server-E3YA32ND.js");
2314
1973
  const ac = new AbortController();
2315
1974
  process20.on("SIGINT", () => {
2316
1975
  ac.abort();
@@ -2910,7 +2569,7 @@ var setup_default = defineCommand19({
2910
2569
  default: false
2911
2570
  }]);
2912
2571
  if (applyNow) {
2913
- const { applyPlan } = await import("./apply-JARPVA5Y.js");
2572
+ const { applyPlan } = await import("./apply-F5SQ2ZOS.js");
2914
2573
  const plan = JSON.parse(readFileSync3(outputPath, "utf-8"));
2915
2574
  try {
2916
2575
  await applyPlan(plan, {
@@ -2938,10 +2597,10 @@ To deploy later: clawops apply ${outputPath}`);
2938
2597
  }
2939
2598
  });
2940
2599
  async function runLocalDeploy(opts) {
2941
- const { setConfig: setConfig2, getConfig, getConfigDir: getConfigDir2 } = await import("./store-YOLQYXMB.js");
2942
- const { localBootstrap } = await import("./bootstrap-3PBVY7MA.js");
2943
- const { connect } = await import("./ssh-VAI5DU5M.js");
2944
- const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-3HZLUWEA.js");
2600
+ const { setConfig, getConfig, getConfigDir: getConfigDir2 } = await import("./store-YOLQYXMB.js");
2601
+ const { localBootstrap } = await import("./bootstrap-32TZXVDF.js");
2602
+ const { connect } = await import("./ssh-HOM7GF3M.js");
2603
+ const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-OZYBMBSK.js");
2945
2604
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
2946
2605
  const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
2947
2606
  const existing = getConfig();
@@ -2957,7 +2616,7 @@ async function runLocalDeploy(opts) {
2957
2616
  stacks: { [opts.stackName]: stackEntry },
2958
2617
  ssh: { keyPath: opts.keyPath, knownHostsPath }
2959
2618
  };
2960
- setConfig2(newConfig);
2619
+ setConfig(newConfig);
2961
2620
  success(`Deployment "${opts.stackName}" registered (~/.clawops/config.json)`);
2962
2621
  await checkDockerPreflight({ host: opts.host, port: opts.port, user: opts.user, keyPath: opts.keyPath, knownHostsPath, signal: opts.signal, inquirer: opts.inquirer });
2963
2622
  const STAGES = [
@@ -3022,7 +2681,7 @@ async function runLocalDeploy(opts) {
3022
2681
  spin2.fail("Configuration failed");
3023
2682
  throw err;
3024
2683
  }
3025
- const { drainPool } = await import("./pool-PIIKJ4L2.js");
2684
+ const { drainPool } = await import("./pool-NT4XBSUT.js");
3026
2685
  drainPool();
3027
2686
  const tokenPath = path2.join(os.homedir(), ".clawops", "secrets", `GATEWAY_TOKEN_${opts.stackName}`);
3028
2687
  const dashboardUrl = `${state.gatewayUrl}?token=${opts.gatewayToken}`;
@@ -3128,10 +2787,10 @@ async function promptSecret(name, label, envDefault, inquirer) {
3128
2787
  }]);
3129
2788
  mkdirSync(secretsDir2, { recursive: true });
3130
2789
  spawnSync("chmod", ["700", secretsDir2], { stdio: "ignore" });
3131
- const secretPath2 = path2.join(secretsDir2, name);
3132
- writeFileSync2(secretPath2, secretValue.trim(), { encoding: "utf-8", mode: 384 });
3133
- success(`Secret saved to ${secretPath2} (chmod 600)`);
3134
- return { name, source: "file", ref: secretPath2 };
2790
+ const secretPath = path2.join(secretsDir2, name);
2791
+ writeFileSync2(secretPath, secretValue.trim(), { encoding: "utf-8", mode: 384 });
2792
+ success(`Secret saved to ${secretPath} (chmod 600)`);
2793
+ return { name, source: "file", ref: secretPath };
3135
2794
  }
3136
2795
  if (secretSource === "env") {
3137
2796
  const { envVar } = await inquirer.prompt([{
@@ -3160,7 +2819,7 @@ var DOCKER_CHECK_CMD = [
3160
2819
  "echo NOT_RUNNING"
3161
2820
  ].join("; ");
3162
2821
  async function checkDockerPreflight(opts) {
3163
- const { acquireSession } = await import("./pool-PIIKJ4L2.js");
2822
+ const { acquireSession } = await import("./pool-NT4XBSUT.js");
3164
2823
  const { ProviderError } = await import("./errors-OK47MQFD.js");
3165
2824
  const spin = spinner("Checking Docker on target host...");
3166
2825
  const timeoutSignal = AbortSignal.timeout(DOCKER_PREFLIGHT_TIMEOUT_MS);
@@ -3207,7 +2866,7 @@ async function checkDockerPreflight(opts) {
3207
2866
  var DOCKER_POLL_MS = 3e3;
3208
2867
  var DOCKER_TIMEOUT_MS = 9e4;
3209
2868
  async function startRemoteDocker(opts, spin) {
3210
- const { acquireSession } = await import("./pool-PIIKJ4L2.js");
2869
+ const { acquireSession } = await import("./pool-NT4XBSUT.js");
3211
2870
  const { ProviderError } = await import("./errors-OK47MQFD.js");
3212
2871
  spin.text = "Starting Docker on the remote server...";
3213
2872
  const { session, release } = await acquireSession({
@@ -3412,9 +3071,9 @@ async function maybeWireGatewayMcp(opts) {
3412
3071
  process21.stdout.write("\n");
3413
3072
  const { wireGateway } = await inquirerPromptWire(opts.inquirer);
3414
3073
  if (!wireGateway) return;
3415
- const { connect } = await import("./ssh-VAI5DU5M.js");
3074
+ const { connect } = await import("./ssh-HOM7GF3M.js");
3416
3075
  const { getConfigDir: getConfigDir2 } = await import("./store-YOLQYXMB.js");
3417
- const { wireGatewayMcp } = await import("./mcp-wire-FIO5DFBY.js");
3076
+ const { wireGatewayMcp } = await import("./mcp-wire-BXF4DJFT.js");
3418
3077
  const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
3419
3078
  const spin = spinner("Wiring gateway MCP client...");
3420
3079
  const session = await connect({
@@ -3533,7 +3192,7 @@ function instanceChoices(provider) {
3533
3192
  return table[provider] ?? [];
3534
3193
  }
3535
3194
  async function maybeHarden(opts) {
3536
- const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-RUOUA5BV.js");
3195
+ const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-UHAOVQMZ.js");
3537
3196
  const { getConfig } = await import("./store-YOLQYXMB.js");
3538
3197
  const config = getConfig();
3539
3198
  if (!config) return;
@@ -3557,7 +3216,7 @@ async function maybeHarden(opts) {
3557
3216
  const modules = availableModules.filter((m) => selectedIds.includes(m.id));
3558
3217
  process21.stdout.write("\nApplying hardening modules...\n\n");
3559
3218
  try {
3560
- const { buildContext } = await import("./context-WOKSKAIA.js");
3219
+ const { buildContext } = await import("./context-EHSLWKSH.js");
3561
3220
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
3562
3221
  const ctx = buildContext({ stack: opts.stackName });
3563
3222
  const stackObj = await ctx.getStack();
@@ -3680,21 +3339,16 @@ async function runAccountPreflight(opts) {
3680
3339
 
3681
3340
  // src/cli/commands/secret.ts
3682
3341
  import { defineCommand as defineCommand20 } from "citty";
3683
- import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync, mkdirSync as mkdirSync2, statSync } from "fs";
3342
+ import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2 } from "fs";
3684
3343
  import { spawnSync as spawnSync2 } from "child_process";
3685
- import path3 from "path";
3686
- import os2 from "os";
3687
3344
  import process22 from "process";
3688
- var SECRETS_DIR = path3.join(os2.homedir(), ".clawops", "secrets");
3689
- function secretsDir() {
3690
- return SECRETS_DIR;
3691
- }
3692
- function secretPath(name) {
3693
- return path3.join(secretsDir(), name);
3694
- }
3695
- function listSecretNames() {
3696
- if (!existsSync2(secretsDir())) return [];
3697
- return readdirSync(secretsDir()).filter((f) => !f.startsWith("."));
3345
+ function secretPathOrExit(name) {
3346
+ const resolved = resolveSecretPath(name);
3347
+ if (!resolved.ok) {
3348
+ failure(resolved.reason);
3349
+ process22.exit(2);
3350
+ }
3351
+ return resolved.path;
3698
3352
  }
3699
3353
  var listCmd = defineCommand20({
3700
3354
  meta: { name: "list", description: "List all stored secrets and their status" },
@@ -3702,24 +3356,12 @@ var listCmd = defineCommand20({
3702
3356
  json: { type: "boolean", description: "Emit JSON" }
3703
3357
  },
3704
3358
  run({ args }) {
3705
- const names = listSecretNames();
3706
- if (names.length === 0) {
3707
- info("No secrets stored in ~/.clawops/secrets/");
3708
- info("Run `clawops secret set <name>` to add one.");
3359
+ const rows = listSecrets();
3360
+ if (rows.length === 0) {
3361
+ info(NO_SECRETS_MESSAGE);
3362
+ info(SET_SECRET_HINT);
3709
3363
  return;
3710
3364
  }
3711
- const rows = names.map((name) => {
3712
- const p = secretPath(name);
3713
- try {
3714
- const stat = statSync(p);
3715
- const content = readFileSync4(p, "utf-8").trim();
3716
- const status = content.length > 0 ? "ok" : "empty";
3717
- const modified = stat.mtime.toISOString().slice(0, 10);
3718
- return { name, status, source: "file", path: p, modified, resolvable: content.length > 0 };
3719
- } catch {
3720
- return { name, status: "unreadable", source: "file", path: p, modified: "\u2014", resolvable: false };
3721
- }
3722
- });
3723
3365
  if (args.json) {
3724
3366
  process22.stdout.write(JSON.stringify(rows, null, 2) + "\n");
3725
3367
  return;
@@ -3744,6 +3386,7 @@ var setCmd = defineCommand20({
3744
3386
  failure("Usage: clawops secret set <name>");
3745
3387
  process22.exit(2);
3746
3388
  }
3389
+ const target = secretPathOrExit(name);
3747
3390
  const inquirer = (await import("inquirer")).default;
3748
3391
  let value;
3749
3392
  if (args.value) {
@@ -3759,8 +3402,8 @@ var setCmd = defineCommand20({
3759
3402
  }
3760
3403
  mkdirSync2(secretsDir(), { recursive: true });
3761
3404
  spawnSync2("chmod", ["700", secretsDir()], { stdio: "ignore" });
3762
- writeFileSync3(secretPath(name), value.trim(), { encoding: "utf-8", mode: 384 });
3763
- success(`Secret "${name}" saved to ${secretPath(name)} (chmod 600)`);
3405
+ writeFileSync3(target, value.trim(), { encoding: "utf-8", mode: 384 });
3406
+ success(`Secret "${name}" saved to ${target} (chmod 600)`);
3764
3407
  info("Run `clawops secret rotate <name>` to propagate it to a running stack.");
3765
3408
  }
3766
3409
  });
@@ -3775,23 +3418,18 @@ var deleteCmd = defineCommand20({
3775
3418
  failure("Usage: clawops secret delete <name>");
3776
3419
  process22.exit(2);
3777
3420
  }
3778
- const p = secretPath(name);
3779
- if (!existsSync2(p)) {
3780
- failure(`Secret "${name}" not found at ${p}`);
3781
- process22.exit(1);
3782
- }
3783
- const { listOverlays } = await import("./overlay-store-ADZPD7EU.js");
3784
- const referencingStacks = listOverlays().filter((o) => o.secrets.some((s) => s.name === name)).map((o) => o.stackName);
3785
- if (referencingStacks.length > 0) {
3786
- warn(`Secret "${name}" is referenced by stack(s): ${referencingStacks.join(", ")}`);
3787
- warn("Deleting it will leave those stacks with an unresolvable $secret: ref.");
3421
+ const prepared = prepareSecretDelete(name);
3422
+ if (!prepared.ok) {
3423
+ failure(prepared.reason);
3424
+ process22.exit(prepared.kind === "invalid-name" ? 2 : 1);
3788
3425
  }
3426
+ for (const line2 of prepared.warnings) warn(line2);
3789
3427
  if (!args.yes) {
3790
3428
  const inquirer = (await import("inquirer")).default;
3791
3429
  const { confirmed } = await inquirer.prompt([{
3792
3430
  type: "confirm",
3793
3431
  name: "confirmed",
3794
- message: `Delete secret "${name}"?`,
3432
+ message: deleteConfirmQuestion(name),
3795
3433
  default: false
3796
3434
  }]);
3797
3435
  if (!confirmed) {
@@ -3799,8 +3437,7 @@ var deleteCmd = defineCommand20({
3799
3437
  return;
3800
3438
  }
3801
3439
  }
3802
- unlinkSync(p);
3803
- success(`Secret "${name}" deleted.`);
3440
+ success(deletePreparedSecret(prepared));
3804
3441
  }
3805
3442
  });
3806
3443
  var rotateCmd = defineCommand20({
@@ -3815,6 +3452,7 @@ var rotateCmd = defineCommand20({
3815
3452
  failure("Usage: clawops secret rotate <name> [--stack <name>]");
3816
3453
  process22.exit(2);
3817
3454
  }
3455
+ const target = secretPathOrExit(name);
3818
3456
  const inquirer = (await import("inquirer")).default;
3819
3457
  let value;
3820
3458
  if (args.value) {
@@ -3830,7 +3468,7 @@ var rotateCmd = defineCommand20({
3830
3468
  }
3831
3469
  mkdirSync2(secretsDir(), { recursive: true });
3832
3470
  spawnSync2("chmod", ["700", secretsDir()], { stdio: "ignore" });
3833
- writeFileSync3(secretPath(name), value.trim(), { encoding: "utf-8", mode: 384 });
3471
+ writeFileSync3(target, value.trim(), { encoding: "utf-8", mode: 384 });
3834
3472
  success(`Secret "${name}" updated.`);
3835
3473
  const { getConfig } = await import("./store-YOLQYXMB.js");
3836
3474
  const cfg = getConfig();
@@ -3849,10 +3487,10 @@ var rotateCmd = defineCommand20({
3849
3487
  }
3850
3488
  info(`Re-applying config overlay to stack "${targetStack}"\u2026`);
3851
3489
  try {
3852
- const { buildContext } = await import("./context-WOKSKAIA.js");
3853
- const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-3HZLUWEA.js");
3490
+ const { buildContext } = await import("./context-EHSLWKSH.js");
3491
+ const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-OZYBMBSK.js");
3854
3492
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
3855
- const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
3493
+ const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
3856
3494
  const { localStateToConnectionInfo } = await import("./state-AKZP2S4T.js");
3857
3495
  const ctx = buildContext({ stack: targetStack });
3858
3496
  let conn;
@@ -3893,53 +3531,20 @@ var auditCmd = defineCommand20({
3893
3531
  json: { type: "boolean", description: "Emit JSON" }
3894
3532
  },
3895
3533
  async run({ args }) {
3896
- const { listOverlays } = await import("./overlay-store-ADZPD7EU.js");
3897
- const overlays = listOverlays();
3898
- const issues = [];
3899
- for (const name of listSecretNames()) {
3900
- const p = secretPath(name);
3901
- try {
3902
- const content = readFileSync4(p, "utf-8").trim();
3903
- if (content.length === 0) {
3904
- issues.push({ kind: "empty-secret", secret: name, detail: `File exists but is empty: ${p}` });
3905
- }
3906
- } catch {
3907
- issues.push({ kind: "unreadable-secret", secret: name, detail: `Cannot read: ${p}` });
3908
- }
3909
- }
3910
- for (const overlay of overlays) {
3911
- for (const s of overlay.secrets) {
3912
- if (s.source === "file") {
3913
- const ref = s.ref ?? secretPath(s.name);
3914
- if (!existsSync2(ref)) {
3915
- issues.push({ kind: "missing-file", stack: overlay.stackName, secret: s.name, detail: `File not found: ${ref}` });
3916
- }
3917
- } else if (s.source === "env") {
3918
- const envVar = s.ref ?? s.name;
3919
- if (!process22.env[envVar]) {
3920
- issues.push({ kind: "missing-env", stack: overlay.stackName, secret: s.name, detail: `Env var not set: ${envVar}` });
3921
- }
3922
- } else {
3923
- issues.push({ kind: "cloud-sm-unresolved", stack: overlay.stackName, secret: s.name, detail: `Source "${s.source}" is not auto-resolved \u2014 set manually` });
3924
- }
3925
- }
3926
- }
3534
+ const { issues } = auditSecrets();
3927
3535
  if (args.json) {
3928
3536
  process22.stdout.write(JSON.stringify({ issues, ok: issues.length === 0 }, null, 2) + "\n");
3929
3537
  return;
3930
3538
  }
3931
3539
  if (issues.length === 0) {
3932
- success("All secrets are resolvable. No issues found.");
3540
+ success(AUDIT_CLEAN_MESSAGE);
3933
3541
  return;
3934
3542
  }
3935
- failure(`${issues.length} issue${issues.length === 1 ? "" : "s"} found:
3543
+ failure(`${auditIssuesHeadline(issues.length)}
3936
3544
  `);
3937
- for (const issue of issues) {
3938
- const prefix = issue.stack ? `[${issue.stack}] ` : "";
3939
- warn(`${prefix}${issue.secret}: ${issue.detail}`);
3940
- }
3545
+ for (const issue of issues) warn(formatAuditIssue(issue));
3941
3546
  process22.stdout.write("\n");
3942
- info("Run `clawops secret set <name>` to update a missing or empty secret.");
3547
+ info(AUDIT_FIX_HINT);
3943
3548
  }
3944
3549
  });
3945
3550
  var secret_default = defineCommand20({
@@ -4036,10 +3641,10 @@ import { accessSync, constants } from "fs";
4036
3641
  import { execSync as execSync2 } from "child_process";
4037
3642
  var ISSUES_URL = `${REPO_URL}/issues/new`;
4038
3643
  var BODY_CAP = 2e3;
4039
- var VERSION = true ? "2.1.3" : "dev";
3644
+ var VERSION = true ? "2.2.0" : "dev";
4040
3645
  async function collectContext() {
4041
3646
  const nodeVersion = process23.version;
4042
- const os3 = `${process23.platform} ${process23.arch}`;
3647
+ const os2 = `${process23.platform} ${process23.arch}`;
4043
3648
  let provider = "unknown";
4044
3649
  let stackCount = 0;
4045
3650
  let sshKey = "unknown";
@@ -4066,7 +3671,7 @@ async function collectContext() {
4066
3671
  return [
4067
3672
  `**clawops version:** ${VERSION}`,
4068
3673
  `**Node:** ${nodeVersion}`,
4069
- `**OS:** ${os3}`,
3674
+ `**OS:** ${os2}`,
4070
3675
  `**Provider:** ${provider}`,
4071
3676
  `**Stacks:** ${stackCount}`,
4072
3677
  `**SSH key:** ${sshKey}`
@@ -4155,8 +3760,8 @@ var harden_default = defineCommand23({
4155
3760
  "tailscale-revert": { type: "boolean", description: "Take the host off its tailnet and go back to its public address. A private-only stack must be reopened with a plan first" }
4156
3761
  },
4157
3762
  async run({ args }) {
4158
- const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-RUOUA5BV.js");
4159
- const { buildContext } = await import("./context-WOKSKAIA.js");
3763
+ const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-UHAOVQMZ.js");
3764
+ const { buildContext } = await import("./context-EHSLWKSH.js");
4160
3765
  const { getConfig } = await import("./store-YOLQYXMB.js");
4161
3766
  if (args.list) {
4162
3767
  process24.stdout.write("\nAvailable hardening modules:\n\n");
@@ -4179,7 +3784,7 @@ var harden_default = defineCommand23({
4179
3784
  const ctx = buildContext({ stack: args.stack });
4180
3785
  const provider = ctx.adapter.name;
4181
3786
  if (args["tailscale-revert"]) {
4182
- const { revertTailnet } = await import("./flows-HGJIA6EL.js");
3787
+ const { revertTailnet } = await import("./flows-6B2I6TRW.js");
4183
3788
  const override = config.stacks[ctx.stackName]?.tailscale;
4184
3789
  if (override && args.yes !== true) {
4185
3790
  const { createInterface: createInterface3 } = await import("readline/promises");
@@ -4202,7 +3807,7 @@ var harden_default = defineCommand23({
4202
3807
  else success(outcome.message);
4203
3808
  return;
4204
3809
  }
4205
- const { makeTailscaleModule } = await import("./harden-RUOUA5BV.js");
3810
+ const { makeTailscaleModule } = await import("./harden-UHAOVQMZ.js");
4206
3811
  const forStack = makeTailscaleModule(ctx.stackName);
4207
3812
  const resolved = resolveModules(MODULE_CATALOG, args.options, provider).map(
4208
3813
  (m) => m.id === "tailscale" ? forStack : m
@@ -4286,7 +3891,7 @@ var harden_default = defineCommand23({
4286
3891
  success("Hardening complete.");
4287
3892
  }
4288
3893
  if (args.tailscale && !args["dry-run"]) {
4289
- const { cutOverToTailnet } = await import("./flows-HGJIA6EL.js");
3894
+ const { cutOverToTailnet } = await import("./flows-6B2I6TRW.js");
4290
3895
  process24.stdout.write("\nVerifying the tailnet address before using it...\n");
4291
3896
  const outcome = await cutOverToTailnet(ctx, conn, config);
4292
3897
  if (!outcome.ok) {
@@ -4323,7 +3928,7 @@ function resolveArgv(argv2, stdinIsTty) {
4323
3928
  }
4324
3929
 
4325
3930
  // src/cli/index.ts
4326
- var CLI_VERSION = true ? "2.1.3" : "dev";
3931
+ var CLI_VERSION = true ? "2.2.0" : "dev";
4327
3932
  var main = defineCommand24({
4328
3933
  meta: {
4329
3934
  name: "clawops",