@clawops/cli 1.2.1 → 1.4.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.
package/dist/cli.js CHANGED
@@ -1,17 +1,24 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- validateOpenclawConfig
4
- } from "./chunk-QJ6ERXHN.js";
5
2
  import {
6
3
  MCP_APPS,
7
4
  buildMcpEntry,
8
5
  writeAppConfigs
9
6
  } from "./chunk-OIGTOLB3.js";
10
- import "./chunk-ACYJBSLJ.js";
11
- import "./chunk-BRPU7AQC.js";
7
+ import {
8
+ chalk,
9
+ failure,
10
+ info,
11
+ monitor_default,
12
+ spinner,
13
+ success,
14
+ validateOpenclawConfig,
15
+ warn
16
+ } from "./chunk-JYQZJMD3.js";
17
+ import "./chunk-T5EX55GP.js";
12
18
  import "./chunk-ZVOEQCNW.js";
13
19
  import "./chunk-4U3LTLWZ.js";
14
- import "./chunk-UDNZUSKA.js";
20
+ import "./chunk-ZFNPM2WG.js";
21
+ import "./chunk-A2I76FTA.js";
15
22
  import {
16
23
  getConfig,
17
24
  getConfigDir,
@@ -24,7 +31,7 @@ import {
24
31
  } from "./chunk-KGXPLI7W.js";
25
32
 
26
33
  // src/cli/index.ts
27
- import { defineCommand as defineCommand20, runMain } from "citty";
34
+ import { defineCommand as defineCommand21, runMain } from "citty";
28
35
 
29
36
  // src/cli/commands/init.ts
30
37
  import { defineCommand } from "citty";
@@ -32,27 +39,6 @@ import { generateKeyPairSync } from "crypto";
32
39
  import { writeFileSync, mkdirSync, existsSync } from "fs";
33
40
  import path from "path";
34
41
  import process2 from "process";
35
-
36
- // src/output/human.ts
37
- import chalk from "chalk";
38
- import ora from "ora";
39
- function success(msg) {
40
- console.log(chalk.green("\u2713") + " " + msg);
41
- }
42
- function failure(msg) {
43
- console.error(chalk.red("\u2717") + " " + msg);
44
- }
45
- function warn(msg) {
46
- console.warn(chalk.yellow("\u26A0") + " " + msg);
47
- }
48
- function info(msg) {
49
- console.log(chalk.blue("\u2139") + " " + msg);
50
- }
51
- function spinner(text) {
52
- return ora(text).start();
53
- }
54
-
55
- // src/cli/commands/init.ts
56
42
  var SUPPORTED_PROVIDERS = ["gcp", "aws", "azure", "local"];
57
43
  var PROVIDER_DEFAULTS = {
58
44
  gcp: { region: "us-central1", credEnv: "GOOGLE_APPLICATION_CREDENTIALS", stateScheme: "gs://" },
@@ -217,7 +203,7 @@ var up_default = defineCommand2({
217
203
  config: { type: "string", description: "Path to openclaw config overlay JSON (local provider only)" }
218
204
  },
219
205
  async run({ args }) {
220
- const { buildContext } = await import("./context-VUAYRAJY.js");
206
+ const { buildContext } = await import("./context-PSGEX2D7.js");
221
207
  const ctx = buildContext(args);
222
208
  const openclawVersion = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : "stable";
223
209
  if (ctx.adapter.name === "local") {
@@ -228,7 +214,7 @@ var up_default = defineCommand2({
228
214
  );
229
215
  }
230
216
  const { localOpts } = stackConfig;
231
- const { localBootstrap } = await import("./bootstrap-UYCOEJQX.js");
217
+ const { localBootstrap } = await import("./bootstrap-G4UZ2FKH.js");
232
218
  const abortController = new AbortController();
233
219
  process3.on("SIGINT", () => abortController.abort());
234
220
  process3.on("SIGTERM", () => abortController.abort());
@@ -323,7 +309,7 @@ var up_default = defineCommand2({
323
309
  async function applyLocalConfigOverlay(opts) {
324
310
  const { connect } = await import("./ssh-IQXNME3D.js");
325
311
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
326
- const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-JQ77SRC2.js");
312
+ const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-QF5TT7GU.js");
327
313
  let overlay;
328
314
  try {
329
315
  overlay = JSON.parse(readFileSync(opts.configPath, "utf-8"));
@@ -363,7 +349,7 @@ var down_default = defineCommand3({
363
349
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
364
350
  },
365
351
  async run({ args }) {
366
- const { buildContext } = await import("./context-VUAYRAJY.js");
352
+ const { buildContext } = await import("./context-PSGEX2D7.js");
367
353
  const ctx = buildContext(args);
368
354
  if (args["dry-run"]) {
369
355
  info(`Dry run \u2014 would destroy stack "${ctx.stackName}"`);
@@ -427,7 +413,7 @@ var status_default = defineCommand4({
427
413
  json: { type: "boolean", description: "Emit JSON" }
428
414
  },
429
415
  async run({ args }) {
430
- const { buildContext } = await import("./context-VUAYRAJY.js");
416
+ const { buildContext } = await import("./context-PSGEX2D7.js");
431
417
  const ctx = buildContext(args);
432
418
  if (ctx.adapter.name === "local") {
433
419
  const state = ctx.localState;
@@ -499,8 +485,8 @@ var plan_default = defineCommand5({
499
485
  out: { type: "string", description: "Write plan JSON to this absolute path (default: stdout)" }
500
486
  },
501
487
  async run({ args }) {
502
- const { buildContext } = await import("./context-VUAYRAJY.js");
503
- const { generatePlan } = await import("./generate-PZPLG3ZM.js");
488
+ const { buildContext } = await import("./context-PSGEX2D7.js");
489
+ const { generatePlan } = await import("./generate-Z7DBMANX.js");
504
490
  const ctx = buildContext(args);
505
491
  if (ctx.adapter.name === "local") {
506
492
  throw new UsageError(
@@ -590,7 +576,7 @@ var apply_default = defineCommand6({
590
576
  },
591
577
  async run({ args }) {
592
578
  const { validatePlan } = await import("./validate-T5M5EHSJ.js");
593
- const { applyPlan } = await import("./apply-KBF7OPWL.js");
579
+ const { applyPlan } = await import("./apply-5RREMN3L.js");
594
580
  const planPath = args._?.[0];
595
581
  if (!planPath) {
596
582
  throw new UsageError("Usage: clawops apply <plan.json>");
@@ -706,7 +692,7 @@ var destroy_default = defineCommand7({
706
692
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
707
693
  },
708
694
  async run({ args }) {
709
- const { buildContext } = await import("./context-VUAYRAJY.js");
695
+ const { buildContext } = await import("./context-PSGEX2D7.js");
710
696
  const ctx = buildContext(args);
711
697
  if (ctx.adapter.name === "local") {
712
698
  throw new UsageError(
@@ -773,7 +759,7 @@ var ssh_default = defineCommand8({
773
759
  command: { type: "string", description: "Remote command to run (instead of interactive shell)" }
774
760
  },
775
761
  async run({ args }) {
776
- const { buildContext } = await import("./context-VUAYRAJY.js");
762
+ const { buildContext } = await import("./context-PSGEX2D7.js");
777
763
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
778
764
  const ctx = buildContext(args);
779
765
  let conn;
@@ -860,7 +846,7 @@ var tunnel_default = defineCommand9({
860
846
  "no-open": { type: "boolean", description: "Do not open browser after tunnel is ready" }
861
847
  },
862
848
  async run({ args }) {
863
- const { buildContext } = await import("./context-VUAYRAJY.js");
849
+ const { buildContext } = await import("./context-PSGEX2D7.js");
864
850
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
865
851
  const { acquireSession } = await import("./pool-FBFHATDG.js");
866
852
  const ctx = buildContext(args);
@@ -928,7 +914,7 @@ var logs_default = defineCommand10({
928
914
  since: { type: "string", description: "Show logs since duration (e.g. 5m, 1h)" }
929
915
  },
930
916
  async run({ args }) {
931
- const { buildContext } = await import("./context-VUAYRAJY.js");
917
+ const { buildContext } = await import("./context-PSGEX2D7.js");
932
918
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
933
919
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
934
920
  const ctx = buildContext(args);
@@ -1045,7 +1031,7 @@ var config_default = defineCommand11({
1045
1031
  "dry-run": { type: "boolean", description: "Show what would change without writing" }
1046
1032
  },
1047
1033
  async run({ args }) {
1048
- const { buildContext } = await import("./context-VUAYRAJY.js");
1034
+ const { buildContext } = await import("./context-PSGEX2D7.js");
1049
1035
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1050
1036
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1051
1037
  const [action, key, value] = args._ ?? [];
@@ -1171,7 +1157,7 @@ var agents_default = defineCommand12({
1171
1157
  json: { type: "boolean", description: "Emit JSON (for list)" }
1172
1158
  },
1173
1159
  async run({ args }) {
1174
- const { buildContext } = await import("./context-VUAYRAJY.js");
1160
+ const { buildContext } = await import("./context-PSGEX2D7.js");
1175
1161
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1176
1162
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1177
1163
  const [action, name] = args._ ?? [];
@@ -1275,7 +1261,7 @@ var gateway_default = defineCommand13({
1275
1261
  json: { type: "boolean", description: "Emit JSON (for status)" }
1276
1262
  },
1277
1263
  async run({ args }) {
1278
- const { buildContext } = await import("./context-VUAYRAJY.js");
1264
+ const { buildContext } = await import("./context-PSGEX2D7.js");
1279
1265
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1280
1266
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1281
1267
  const [action, versionArg] = args._ ?? [];
@@ -1388,7 +1374,7 @@ var backup_default = defineCommand14({
1388
1374
  yes: { type: "boolean", description: "[restore] Skip confirmation prompt" }
1389
1375
  },
1390
1376
  async run({ args }) {
1391
- const { buildContext } = await import("./context-VUAYRAJY.js");
1377
+ const { buildContext } = await import("./context-PSGEX2D7.js");
1392
1378
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1393
1379
  const action = args.action;
1394
1380
  if (action !== "create" && action !== "restore") {
@@ -1500,7 +1486,7 @@ var stacks_default = defineCommand15({
1500
1486
  args: {
1501
1487
  json: { type: "boolean", description: "Emit JSON (for list)" },
1502
1488
  yes: { type: "boolean", description: "Skip confirmation prompt on delete" },
1503
- force: { type: "boolean", description: "Allow deleting the default stack" }
1489
+ force: { type: "boolean", description: "Skip safety checks (allow deleting default or still-deployed stacks)" }
1504
1490
  },
1505
1491
  async run({ args }) {
1506
1492
  const [action, name] = args._ ?? [];
@@ -1558,6 +1544,31 @@ var stacks_default = defineCommand15({
1558
1544
  `"${name}" is the default stack. Use --force to delete it (clawops will switch the default to another stack).`
1559
1545
  );
1560
1546
  }
1547
+ let isDeployed = false;
1548
+ if (!args.force) {
1549
+ try {
1550
+ const { buildContext } = await import("./context-PSGEX2D7.js");
1551
+ const ctx = buildContext({ stack: name });
1552
+ if (ctx.adapter.name === "local") {
1553
+ isDeployed = !!ctx.localState;
1554
+ } else {
1555
+ const stack = await ctx.getStack();
1556
+ const outputMap = await stack.outputs();
1557
+ const outputs = Object.fromEntries(
1558
+ Object.entries(outputMap).map(([k, v]) => [k, v.value])
1559
+ );
1560
+ isDeployed = !!outputs["publicIp"];
1561
+ }
1562
+ } catch {
1563
+ warn(`Could not verify deployment status for "${name}" \u2014 proceeding anyway.`);
1564
+ }
1565
+ }
1566
+ if (isDeployed) {
1567
+ failure(
1568
+ `Stack "${name}" is still deployed. Run \`clawops down --stack ${name}\` first to destroy cloud resources, or pass --force to remove from registry only.`
1569
+ );
1570
+ process17.exit(1);
1571
+ }
1561
1572
  warn(
1562
1573
  `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."
1563
1574
  );
@@ -1657,9 +1668,9 @@ var doctor_default = defineCommand16({
1657
1668
  info("Credential checks skipped \u2014 no config");
1658
1669
  } else {
1659
1670
  const { getProvider } = await import("./providers-2OABPW2E.js");
1660
- await import("./aws-KQBWY43W.js");
1661
- await import("./gcp-SKURG3L6.js");
1662
- await import("./azure-4EJFVJZL.js");
1671
+ await import("./aws-FRE2JVAZ.js");
1672
+ await import("./gcp-BXDTC6EK.js");
1673
+ await import("./azure-PVC3AQVC.js");
1663
1674
  await import("./local-DXBEVZ5C.js");
1664
1675
  const checkedProviders = /* @__PURE__ */ new Set();
1665
1676
  for (const [stackName, stackCfg] of Object.entries(config.stacks)) {
@@ -1694,7 +1705,7 @@ var doctor_default = defineCommand16({
1694
1705
  info("Remote health skipped \u2014 no config");
1695
1706
  } else {
1696
1707
  try {
1697
- const { buildContext } = await import("./context-VUAYRAJY.js");
1708
+ const { buildContext } = await import("./context-PSGEX2D7.js");
1698
1709
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1699
1710
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1700
1711
  const ctx = buildContext({ stack: args.stack });
@@ -1802,7 +1813,7 @@ var serveCmd = defineCommand17({
1802
1813
  inspector: { type: "boolean", description: "Enable MCP inspector" }
1803
1814
  },
1804
1815
  async run({ args }) {
1805
- const { serveMcp } = await import("./server-4DNHLGEG.js");
1816
+ const { serveMcp } = await import("./server-IWLZJ4EU.js");
1806
1817
  await serveMcp({
1807
1818
  port: args.http ? Number(args.http) : void 0,
1808
1819
  bind: args.bind,
@@ -1876,12 +1887,6 @@ var mcp_default = defineCommand17({
1876
1887
  subCommands: {
1877
1888
  serve: serveCmd,
1878
1889
  install: installCmd
1879
- },
1880
- run() {
1881
- process19.stdout.write("Usage: clawops mcp <serve | install>\n\n");
1882
- process19.stdout.write(" serve Start the MCP server (stdio or HTTP)\n");
1883
- process19.stdout.write(" install Wire clawops into AI editors interactively\n\n");
1884
- process19.stdout.write("Run `clawops mcp <command> --help` for flags.\n");
1885
1890
  }
1886
1891
  });
1887
1892
 
@@ -2148,10 +2153,10 @@ var setup_default = defineCommand18({
2148
2153
  }
2149
2154
  }
2150
2155
  const gatewayToken = randomBytes(24).toString("hex");
2151
- const secretsDir = path3.join(os.homedir(), ".clawops", "secrets");
2152
- mkdirSync3(secretsDir, { recursive: true });
2153
- spawnSync("chmod", ["700", secretsDir], { stdio: "ignore" });
2154
- const tokenPath = path3.join(secretsDir, `GATEWAY_TOKEN_${stackAnswers.stackName}`);
2156
+ const secretsDir2 = path3.join(os.homedir(), ".clawops", "secrets");
2157
+ mkdirSync3(secretsDir2, { recursive: true });
2158
+ spawnSync("chmod", ["700", secretsDir2], { stdio: "ignore" });
2159
+ const tokenPath = path3.join(secretsDir2, `GATEWAY_TOKEN_${stackAnswers.stackName}`);
2155
2160
  writeFileSync3(tokenPath, gatewayToken, { encoding: "utf-8", mode: 384 });
2156
2161
  const openclawConfigOverlay = {
2157
2162
  models: builtModelConfig,
@@ -2309,7 +2314,7 @@ var setup_default = defineCommand18({
2309
2314
  default: false
2310
2315
  }]);
2311
2316
  if (applyNow) {
2312
- const { applyPlan } = await import("./apply-KBF7OPWL.js");
2317
+ const { applyPlan } = await import("./apply-5RREMN3L.js");
2313
2318
  const plan = JSON.parse(readFileSync3(outputPath, "utf-8"));
2314
2319
  try {
2315
2320
  await applyPlan(plan, {
@@ -2331,9 +2336,9 @@ To deploy later: clawops apply ${outputPath}`);
2331
2336
  });
2332
2337
  async function runLocalDeploy(opts) {
2333
2338
  const { setConfig: setConfig2, getConfig: getConfig2, getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
2334
- const { localBootstrap } = await import("./bootstrap-UYCOEJQX.js");
2339
+ const { localBootstrap } = await import("./bootstrap-G4UZ2FKH.js");
2335
2340
  const { connect } = await import("./ssh-IQXNME3D.js");
2336
- const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-JQ77SRC2.js");
2341
+ const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-QF5TT7GU.js");
2337
2342
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
2338
2343
  const knownHostsPath = path3.join(getConfigDir2(), "known_hosts");
2339
2344
  const existing = getConfig2();
@@ -2404,6 +2409,8 @@ async function runLocalDeploy(opts) {
2404
2409
  const merged = deepMerge(remote, resolved);
2405
2410
  await atomicWriteConfig(session, merged, opts.signal);
2406
2411
  await restartGateway(session, opts.signal);
2412
+ const { saveOverlay } = await import("./overlay-store-ADZPD7EU.js");
2413
+ saveOverlay(opts.stackName, opts.overlay, opts.secrets);
2407
2414
  } finally {
2408
2415
  session.close();
2409
2416
  }
@@ -2497,7 +2504,7 @@ function cliInstallUrl(provider) {
2497
2504
  return "https://learn.microsoft.com/en-us/cli/azure/install-azure-cli";
2498
2505
  }
2499
2506
  async function promptSecret(name, label, envDefault, inquirer) {
2500
- const secretsDir = path3.join(os.homedir(), ".clawops", "secrets");
2507
+ const secretsDir2 = path3.join(os.homedir(), ".clawops", "secrets");
2501
2508
  const { secretSource } = await inquirer.prompt([{
2502
2509
  type: "list",
2503
2510
  name: "secretSource",
@@ -2515,12 +2522,12 @@ async function promptSecret(name, label, envDefault, inquirer) {
2515
2522
  message: `Paste your ${label}: (input is hidden)`,
2516
2523
  validate: (v) => v.trim() !== "" || "Value cannot be empty"
2517
2524
  }]);
2518
- mkdirSync3(secretsDir, { recursive: true });
2519
- spawnSync("chmod", ["700", secretsDir], { stdio: "ignore" });
2520
- const secretPath = path3.join(secretsDir, name);
2521
- writeFileSync3(secretPath, secretValue.trim(), { encoding: "utf-8", mode: 384 });
2522
- success(`Secret saved to ${secretPath} (chmod 600)`);
2523
- return { name, source: "file", ref: secretPath };
2525
+ mkdirSync3(secretsDir2, { recursive: true });
2526
+ spawnSync("chmod", ["700", secretsDir2], { stdio: "ignore" });
2527
+ const secretPath2 = path3.join(secretsDir2, name);
2528
+ writeFileSync3(secretPath2, secretValue.trim(), { encoding: "utf-8", mode: 384 });
2529
+ success(`Secret saved to ${secretPath2} (chmod 600)`);
2530
+ return { name, source: "file", ref: secretPath2 };
2524
2531
  }
2525
2532
  if (secretSource === "env") {
2526
2533
  const { envVar } = await inquirer.prompt([{
@@ -2845,8 +2852,287 @@ function instanceChoices(provider) {
2845
2852
  return table[provider] ?? [];
2846
2853
  }
2847
2854
 
2848
- // src/cli/commands/help.ts
2855
+ // src/cli/commands/secret.ts
2849
2856
  import { defineCommand as defineCommand19 } from "citty";
2857
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync4, unlinkSync, mkdirSync as mkdirSync4, statSync } from "fs";
2858
+ import { spawnSync as spawnSync2 } from "child_process";
2859
+ import path4 from "path";
2860
+ import os2 from "os";
2861
+ import process21 from "process";
2862
+ var SECRETS_DIR = path4.join(os2.homedir(), ".clawops", "secrets");
2863
+ function secretsDir() {
2864
+ return SECRETS_DIR;
2865
+ }
2866
+ function secretPath(name) {
2867
+ return path4.join(secretsDir(), name);
2868
+ }
2869
+ function listSecretNames() {
2870
+ if (!existsSync3(secretsDir())) return [];
2871
+ return readdirSync(secretsDir()).filter((f) => !f.startsWith("."));
2872
+ }
2873
+ var listCmd = defineCommand19({
2874
+ meta: { name: "list", description: "List all stored secrets and their status" },
2875
+ args: {
2876
+ json: { type: "boolean", description: "Emit JSON" }
2877
+ },
2878
+ run({ args }) {
2879
+ const names = listSecretNames();
2880
+ if (names.length === 0) {
2881
+ info("No secrets stored in ~/.clawops/secrets/");
2882
+ info("Run `clawops secret set <name>` to add one.");
2883
+ return;
2884
+ }
2885
+ const rows = names.map((name) => {
2886
+ const p = secretPath(name);
2887
+ try {
2888
+ const stat = statSync(p);
2889
+ const content = readFileSync4(p, "utf-8").trim();
2890
+ const status = content.length > 0 ? "ok" : "empty";
2891
+ const modified = stat.mtime.toISOString().slice(0, 10);
2892
+ return { name, status, source: "file", path: p, modified, resolvable: content.length > 0 };
2893
+ } catch {
2894
+ return { name, status: "unreadable", source: "file", path: p, modified: "\u2014", resolvable: false };
2895
+ }
2896
+ });
2897
+ if (args.json) {
2898
+ process21.stdout.write(JSON.stringify(rows, null, 2) + "\n");
2899
+ return;
2900
+ }
2901
+ const tableRows = rows.map((r) => [
2902
+ r.name,
2903
+ r.resolvable ? chalk.green("ok") : chalk.red(r.status),
2904
+ r.modified,
2905
+ r.path
2906
+ ]);
2907
+ process21.stdout.write("\n" + renderTable(["Name", "Status", "Modified", "Path"], tableRows) + "\n\n");
2908
+ }
2909
+ });
2910
+ var setCmd = defineCommand19({
2911
+ meta: { name: "set", description: "Create or update a secret" },
2912
+ args: {
2913
+ value: { type: "string", description: "Secret value (skips interactive prompt)" }
2914
+ },
2915
+ async run({ args }) {
2916
+ const [name] = args._ ?? [];
2917
+ if (!name) {
2918
+ failure("Usage: clawops secret set <name>");
2919
+ process21.exit(2);
2920
+ }
2921
+ const inquirer = (await import("inquirer")).default;
2922
+ let value;
2923
+ if (args.value) {
2924
+ value = args.value;
2925
+ } else {
2926
+ const { secretValue } = await inquirer.prompt([{
2927
+ type: "password",
2928
+ name: "secretValue",
2929
+ message: `Value for secret "${name}": (input is hidden)`,
2930
+ validate: (v) => typeof v === "string" && v.trim() !== "" || "Value cannot be empty"
2931
+ }]);
2932
+ value = secretValue;
2933
+ }
2934
+ mkdirSync4(secretsDir(), { recursive: true });
2935
+ spawnSync2("chmod", ["700", secretsDir()], { stdio: "ignore" });
2936
+ writeFileSync4(secretPath(name), value.trim(), { encoding: "utf-8", mode: 384 });
2937
+ success(`Secret "${name}" saved to ${secretPath(name)} (chmod 600)`);
2938
+ info("Run `clawops secret rotate <name>` to propagate it to a running stack.");
2939
+ }
2940
+ });
2941
+ var deleteCmd = defineCommand19({
2942
+ meta: { name: "delete", description: "Remove a stored secret" },
2943
+ args: {
2944
+ yes: { type: "boolean", description: "Skip confirmation prompt" }
2945
+ },
2946
+ async run({ args }) {
2947
+ const [name] = args._ ?? [];
2948
+ if (!name) {
2949
+ failure("Usage: clawops secret delete <name>");
2950
+ process21.exit(2);
2951
+ }
2952
+ const p = secretPath(name);
2953
+ if (!existsSync3(p)) {
2954
+ failure(`Secret "${name}" not found at ${p}`);
2955
+ process21.exit(1);
2956
+ }
2957
+ const { listOverlays } = await import("./overlay-store-ADZPD7EU.js");
2958
+ const referencingStacks = listOverlays().filter((o) => o.secrets.some((s) => s.name === name)).map((o) => o.stackName);
2959
+ if (referencingStacks.length > 0) {
2960
+ warn(`Secret "${name}" is referenced by stack(s): ${referencingStacks.join(", ")}`);
2961
+ warn("Deleting it will leave those stacks with an unresolvable $secret: ref.");
2962
+ }
2963
+ if (!args.yes) {
2964
+ const inquirer = (await import("inquirer")).default;
2965
+ const { confirmed } = await inquirer.prompt([{
2966
+ type: "confirm",
2967
+ name: "confirmed",
2968
+ message: `Delete secret "${name}"?`,
2969
+ default: false
2970
+ }]);
2971
+ if (!confirmed) {
2972
+ info("Aborted.");
2973
+ return;
2974
+ }
2975
+ }
2976
+ unlinkSync(p);
2977
+ success(`Secret "${name}" deleted.`);
2978
+ }
2979
+ });
2980
+ var rotateCmd = defineCommand19({
2981
+ meta: { name: "rotate", description: "Update a secret and re-apply config to a running stack" },
2982
+ args: {
2983
+ stack: { type: "string", description: "Stack name to re-apply (defaults to config default)" },
2984
+ value: { type: "string", description: "New secret value (skips interactive prompt)" }
2985
+ },
2986
+ async run({ args }) {
2987
+ const [name] = args._ ?? [];
2988
+ if (!name) {
2989
+ failure("Usage: clawops secret rotate <name> [--stack <name>]");
2990
+ process21.exit(2);
2991
+ }
2992
+ const inquirer = (await import("inquirer")).default;
2993
+ let value;
2994
+ if (args.value) {
2995
+ value = args.value;
2996
+ } else {
2997
+ const { secretValue } = await inquirer.prompt([{
2998
+ type: "password",
2999
+ name: "secretValue",
3000
+ message: `New value for secret "${name}": (input is hidden)`,
3001
+ validate: (v) => typeof v === "string" && v.trim() !== "" || "Value cannot be empty"
3002
+ }]);
3003
+ value = secretValue;
3004
+ }
3005
+ mkdirSync4(secretsDir(), { recursive: true });
3006
+ spawnSync2("chmod", ["700", secretsDir()], { stdio: "ignore" });
3007
+ writeFileSync4(secretPath(name), value.trim(), { encoding: "utf-8", mode: 384 });
3008
+ success(`Secret "${name}" updated.`);
3009
+ const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
3010
+ const cfg = getConfig2();
3011
+ const targetStack = args.stack ?? cfg?.defaults?.stack;
3012
+ if (!targetStack) {
3013
+ warn("No stack specified and no default stack in config \u2014 skipping re-apply.");
3014
+ info("Run `clawops secret rotate <name> --stack <name>` to re-apply to a specific stack.");
3015
+ return;
3016
+ }
3017
+ const { loadOverlay } = await import("./overlay-store-ADZPD7EU.js");
3018
+ const stored = loadOverlay(targetStack);
3019
+ if (!stored) {
3020
+ warn(`No stored overlay for stack "${targetStack}" \u2014 cannot re-apply automatically.`);
3021
+ info("Re-run `clawops setup` or `clawops apply` to propagate the new secret.");
3022
+ return;
3023
+ }
3024
+ info(`Re-applying config overlay to stack "${targetStack}"\u2026`);
3025
+ try {
3026
+ const { buildContext } = await import("./context-PSGEX2D7.js");
3027
+ const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-QF5TT7GU.js");
3028
+ const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
3029
+ const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
3030
+ const { localStateToConnectionInfo } = await import("./state-PRBIIN7I.js");
3031
+ const ctx = buildContext({ stack: targetStack });
3032
+ let conn;
3033
+ if (ctx.adapter.name === "local") {
3034
+ if (!ctx.localState) throw new Error(`Stack "${targetStack}" has not been bootstrapped yet.`);
3035
+ conn = localStateToConnectionInfo(ctx.localState);
3036
+ } else {
3037
+ const stack = await ctx.getStack();
3038
+ const outputMap = await stack.outputs();
3039
+ const outputs = Object.fromEntries(
3040
+ Object.entries(outputMap).map(([k, v]) => [k, v.value])
3041
+ );
3042
+ const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
3043
+ const base = extractBaseOutputs(outputs);
3044
+ conn = ctx.adapter.getConnectionInfo({ ...base, privateKeyPath: ctx.config.ssh.keyPath, knownHostsPath: ctx.config.ssh.knownHostsPath });
3045
+ }
3046
+ const { session, release } = await acquireSession(conn);
3047
+ try {
3048
+ const remote = await readRemoteConfig(session);
3049
+ const resolved = resolveSecrets(stored.overlay, stored.secrets);
3050
+ const merged = deepMerge(remote, resolved);
3051
+ await atomicWriteConfig(session, merged);
3052
+ await restartGateway(session);
3053
+ } finally {
3054
+ release();
3055
+ drainPool();
3056
+ }
3057
+ success(`Config overlay re-applied and gateway restarted on "${targetStack}".`);
3058
+ } catch (err) {
3059
+ failure(`Re-apply failed: ${err.message}`);
3060
+ info("The secret file has been updated. Re-run `clawops setup` or `clawops apply` to propagate.");
3061
+ }
3062
+ }
3063
+ });
3064
+ var auditCmd = defineCommand19({
3065
+ meta: { name: "audit", description: "Report missing secrets and unresolvable $secret: refs" },
3066
+ args: {
3067
+ json: { type: "boolean", description: "Emit JSON" }
3068
+ },
3069
+ async run({ args }) {
3070
+ const { listOverlays } = await import("./overlay-store-ADZPD7EU.js");
3071
+ const overlays = listOverlays();
3072
+ const issues = [];
3073
+ for (const name of listSecretNames()) {
3074
+ const p = secretPath(name);
3075
+ try {
3076
+ const content = readFileSync4(p, "utf-8").trim();
3077
+ if (content.length === 0) {
3078
+ issues.push({ kind: "empty-secret", secret: name, detail: `File exists but is empty: ${p}` });
3079
+ }
3080
+ } catch {
3081
+ issues.push({ kind: "unreadable-secret", secret: name, detail: `Cannot read: ${p}` });
3082
+ }
3083
+ }
3084
+ for (const overlay of overlays) {
3085
+ for (const s of overlay.secrets) {
3086
+ if (s.source === "file") {
3087
+ const ref = s.ref ?? secretPath(s.name);
3088
+ if (!existsSync3(ref)) {
3089
+ issues.push({ kind: "missing-file", stack: overlay.stackName, secret: s.name, detail: `File not found: ${ref}` });
3090
+ }
3091
+ } else if (s.source === "env") {
3092
+ const envVar = s.ref ?? s.name;
3093
+ if (!process21.env[envVar]) {
3094
+ issues.push({ kind: "missing-env", stack: overlay.stackName, secret: s.name, detail: `Env var not set: ${envVar}` });
3095
+ }
3096
+ } else {
3097
+ issues.push({ kind: "cloud-sm-unresolved", stack: overlay.stackName, secret: s.name, detail: `Source "${s.source}" is not auto-resolved \u2014 set manually` });
3098
+ }
3099
+ }
3100
+ }
3101
+ if (args.json) {
3102
+ process21.stdout.write(JSON.stringify({ issues, ok: issues.length === 0 }, null, 2) + "\n");
3103
+ return;
3104
+ }
3105
+ if (issues.length === 0) {
3106
+ success("All secrets are resolvable. No issues found.");
3107
+ return;
3108
+ }
3109
+ failure(`${issues.length} issue${issues.length === 1 ? "" : "s"} found:
3110
+ `);
3111
+ for (const issue of issues) {
3112
+ const prefix = issue.stack ? `[${issue.stack}] ` : "";
3113
+ warn(`${prefix}${issue.secret}: ${issue.detail}`);
3114
+ }
3115
+ process21.stdout.write("\n");
3116
+ info("Run `clawops secret set <name>` to update a missing or empty secret.");
3117
+ }
3118
+ });
3119
+ var secret_default = defineCommand19({
3120
+ meta: {
3121
+ name: "secret",
3122
+ description: "Manage clawops secrets (list | set | delete | rotate | audit)"
3123
+ },
3124
+ args: {},
3125
+ subCommands: {
3126
+ list: listCmd,
3127
+ set: setCmd,
3128
+ delete: deleteCmd,
3129
+ rotate: rotateCmd,
3130
+ audit: auditCmd
3131
+ }
3132
+ });
3133
+
3134
+ // src/cli/commands/help.ts
3135
+ import { defineCommand as defineCommand20 } from "citty";
2850
3136
  var COMMANDS = [
2851
3137
  ["setup", "Interactive first-run wizard \u2014 configure, deploy, and wire AI apps"],
2852
3138
  ["init", "Write config and generate SSH key pair (non-interactive)"],
@@ -2866,6 +3152,8 @@ var COMMANDS = [
2866
3152
  ["stacks", "List named stacks and their state"],
2867
3153
  ["doctor", "Check Node version, config, SSH key, provider credentials, and Pulumi home"],
2868
3154
  ["mcp", "MCP server operations (mcp serve | mcp install)"],
3155
+ ["secret", "Manage secrets (secret list | set | delete | rotate | audit)"],
3156
+ ["monitor", "Live dashboard: gateway health, container status, resource usage, log tail"],
2869
3157
  ["help", "Show this help message"]
2870
3158
  ];
2871
3159
  var GLOBAL_FLAGS = [
@@ -2876,7 +3164,7 @@ var GLOBAL_FLAGS = [
2876
3164
  ["--dry-run", "Preview without applying (mutating commands)"],
2877
3165
  ["--yes", "Skip interactive confirmations (CI mode)"]
2878
3166
  ];
2879
- var help_default = defineCommand19({
3167
+ var help_default = defineCommand20({
2880
3168
  meta: {
2881
3169
  name: "help",
2882
3170
  description: "Show available commands and global flags"
@@ -2916,25 +3204,25 @@ ${dim("Run `clawops <command> --help` for command-specific flags.")}
2916
3204
  });
2917
3205
 
2918
3206
  // src/cli/error-handler.ts
2919
- import process21 from "process";
3207
+ import process22 from "process";
2920
3208
  function handleError(err) {
2921
3209
  if (err instanceof ClawopsError) {
2922
3210
  failure(err.message);
2923
- process21.exit(err.exitCode);
3211
+ process22.exit(err.exitCode);
2924
3212
  }
2925
3213
  if (err instanceof Error) {
2926
3214
  failure(`Unexpected error: ${err.message}`);
2927
- if (process21.env["DEBUG"]) {
2928
- process21.stderr.write(err.stack ?? "\n");
3215
+ if (process22.env["DEBUG"]) {
3216
+ process22.stderr.write(err.stack ?? "\n");
2929
3217
  }
2930
- process21.exit(1);
3218
+ process22.exit(1);
2931
3219
  }
2932
3220
  failure(`Unknown error: ${String(err)}`);
2933
- process21.exit(1);
3221
+ process22.exit(1);
2934
3222
  }
2935
3223
 
2936
3224
  // src/cli/index.ts
2937
- var main = defineCommand20({
3225
+ var main = defineCommand21({
2938
3226
  meta: {
2939
3227
  name: "clawops",
2940
3228
  description: "Deploy and manage self-hosted OpenClaw instances across clouds",
@@ -2968,6 +3256,8 @@ var main = defineCommand20({
2968
3256
  doctor: doctor_default,
2969
3257
  mcp: mcp_default,
2970
3258
  setup: setup_default,
3259
+ secret: secret_default,
3260
+ monitor: monitor_default,
2971
3261
  help: help_default
2972
3262
  }
2973
3263
  });