@clawops/cli 1.5.0 → 1.7.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,24 +1,27 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ monitor_default,
4
+ validateOpenclawConfig
5
+ } from "./chunk-CG3Y7Y2R.js";
2
6
  import {
3
7
  MCP_APPS,
4
8
  buildMcpEntry,
5
9
  writeAppConfigs
6
10
  } from "./chunk-OIGTOLB3.js";
11
+ import "./chunk-ZFNPM2WG.js";
12
+ import "./chunk-3MFZ7E74.js";
13
+ import "./chunk-LCKD7L7X.js";
14
+ import "./chunk-GJEF6UQA.js";
7
15
  import {
16
+ REPO_URL,
8
17
  chalk,
9
18
  failure,
10
19
  info,
11
- monitor_default,
12
20
  printCta,
13
21
  spinner,
14
22
  success,
15
- validateOpenclawConfig,
16
23
  warn
17
- } from "./chunk-R5S6IXBE.js";
18
- import "./chunk-T5EX55GP.js";
19
- import "./chunk-ZVOEQCNW.js";
20
- import "./chunk-4U3LTLWZ.js";
21
- import "./chunk-ZFNPM2WG.js";
24
+ } from "./chunk-Q7NQY5HV.js";
22
25
  import "./chunk-A2I76FTA.js";
23
26
  import {
24
27
  getConfig,
@@ -32,7 +35,7 @@ import {
32
35
  } from "./chunk-KGXPLI7W.js";
33
36
 
34
37
  // src/cli/index.ts
35
- import { defineCommand as defineCommand21, runMain } from "citty";
38
+ import { defineCommand as defineCommand23, runMain } from "citty";
36
39
 
37
40
  // src/cli/commands/init.ts
38
41
  import { defineCommand } from "citty";
@@ -204,7 +207,7 @@ var up_default = defineCommand2({
204
207
  config: { type: "string", description: "Path to openclaw config overlay JSON (local provider only)" }
205
208
  },
206
209
  async run({ args }) {
207
- const { buildContext } = await import("./context-PSGEX2D7.js");
210
+ const { buildContext } = await import("./context-ALSJMTHE.js");
208
211
  const ctx = buildContext(args);
209
212
  const openclawVersion = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : "stable";
210
213
  if (ctx.adapter.name === "local") {
@@ -215,7 +218,7 @@ var up_default = defineCommand2({
215
218
  );
216
219
  }
217
220
  const { localOpts } = stackConfig;
218
- const { localBootstrap } = await import("./bootstrap-G4UZ2FKH.js");
221
+ const { localBootstrap } = await import("./bootstrap-RY4VOSDJ.js");
219
222
  const abortController = new AbortController();
220
223
  process3.on("SIGINT", () => abortController.abort());
221
224
  process3.on("SIGTERM", () => abortController.abort());
@@ -308,7 +311,7 @@ var up_default = defineCommand2({
308
311
  }
309
312
  });
310
313
  async function applyLocalConfigOverlay(opts) {
311
- const { connect } = await import("./ssh-IQXNME3D.js");
314
+ const { connect } = await import("./ssh-E2AMBPZY.js");
312
315
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
313
316
  const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-QF5TT7GU.js");
314
317
  let overlay;
@@ -350,7 +353,7 @@ var down_default = defineCommand3({
350
353
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
351
354
  },
352
355
  async run({ args }) {
353
- const { buildContext } = await import("./context-PSGEX2D7.js");
356
+ const { buildContext } = await import("./context-ALSJMTHE.js");
354
357
  const ctx = buildContext(args);
355
358
  if (args["dry-run"]) {
356
359
  info(`Dry run \u2014 would destroy stack "${ctx.stackName}"`);
@@ -414,7 +417,7 @@ var status_default = defineCommand4({
414
417
  json: { type: "boolean", description: "Emit JSON" }
415
418
  },
416
419
  async run({ args }) {
417
- const { buildContext } = await import("./context-PSGEX2D7.js");
420
+ const { buildContext } = await import("./context-ALSJMTHE.js");
418
421
  const ctx = buildContext(args);
419
422
  if (ctx.adapter.name === "local") {
420
423
  const state = ctx.localState;
@@ -486,8 +489,8 @@ var plan_default = defineCommand5({
486
489
  out: { type: "string", description: "Write plan JSON to this absolute path (default: stdout)" }
487
490
  },
488
491
  async run({ args }) {
489
- const { buildContext } = await import("./context-PSGEX2D7.js");
490
- const { generatePlan } = await import("./generate-2QGKYR42.js");
492
+ const { buildContext } = await import("./context-ALSJMTHE.js");
493
+ const { generatePlan } = await import("./generate-ZTD4BVVI.js");
491
494
  const ctx = buildContext(args);
492
495
  if (ctx.adapter.name === "local") {
493
496
  throw new UsageError(
@@ -577,7 +580,7 @@ var apply_default = defineCommand6({
577
580
  },
578
581
  async run({ args }) {
579
582
  const { validatePlan } = await import("./validate-T5M5EHSJ.js");
580
- const { applyPlan } = await import("./apply-5RREMN3L.js");
583
+ const { applyPlan } = await import("./apply-BTZFFGAD.js");
581
584
  const planPath = args._?.[0];
582
585
  if (!planPath) {
583
586
  throw new UsageError("Usage: clawops apply <plan.json>");
@@ -693,7 +696,7 @@ var destroy_default = defineCommand7({
693
696
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
694
697
  },
695
698
  async run({ args }) {
696
- const { buildContext } = await import("./context-PSGEX2D7.js");
699
+ const { buildContext } = await import("./context-ALSJMTHE.js");
697
700
  const ctx = buildContext(args);
698
701
  if (ctx.adapter.name === "local") {
699
702
  throw new UsageError(
@@ -760,8 +763,8 @@ var ssh_default = defineCommand8({
760
763
  command: { type: "string", description: "Remote command to run (instead of interactive shell)" }
761
764
  },
762
765
  async run({ args }) {
763
- const { buildContext } = await import("./context-PSGEX2D7.js");
764
- const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
766
+ const { buildContext } = await import("./context-ALSJMTHE.js");
767
+ const { acquireSession, drainPool } = await import("./pool-JZGKPP6K.js");
765
768
  const ctx = buildContext(args);
766
769
  let conn;
767
770
  if (ctx.adapter.name === "local") {
@@ -847,9 +850,9 @@ var tunnel_default = defineCommand9({
847
850
  "no-open": { type: "boolean", description: "Do not open browser after tunnel is ready" }
848
851
  },
849
852
  async run({ args }) {
850
- const { buildContext } = await import("./context-PSGEX2D7.js");
853
+ const { buildContext } = await import("./context-ALSJMTHE.js");
851
854
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
852
- const { acquireSession } = await import("./pool-FBFHATDG.js");
855
+ const { acquireSession } = await import("./pool-JZGKPP6K.js");
853
856
  const ctx = buildContext(args);
854
857
  const stack = await ctx.getStack();
855
858
  const outputMap = await stack.outputs();
@@ -915,9 +918,9 @@ var logs_default = defineCommand10({
915
918
  since: { type: "string", description: "Show logs since duration (e.g. 5m, 1h)" }
916
919
  },
917
920
  async run({ args }) {
918
- const { buildContext } = await import("./context-PSGEX2D7.js");
921
+ const { buildContext } = await import("./context-ALSJMTHE.js");
919
922
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
920
- const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
923
+ const { acquireSession, drainPool } = await import("./pool-JZGKPP6K.js");
921
924
  const ctx = buildContext(args);
922
925
  const stack = await ctx.getStack();
923
926
  const outputMap = await stack.outputs();
@@ -1032,9 +1035,9 @@ var config_default = defineCommand11({
1032
1035
  "dry-run": { type: "boolean", description: "Show what would change without writing" }
1033
1036
  },
1034
1037
  async run({ args }) {
1035
- const { buildContext } = await import("./context-PSGEX2D7.js");
1038
+ const { buildContext } = await import("./context-ALSJMTHE.js");
1036
1039
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1037
- const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1040
+ const { acquireSession, drainPool } = await import("./pool-JZGKPP6K.js");
1038
1041
  const [action, key, value] = args._ ?? [];
1039
1042
  if (!action || !["get", "set", "unset", "validate"].includes(action)) {
1040
1043
  failure("Usage: clawops config <get [key] | set key value | unset key | validate>");
@@ -1158,9 +1161,9 @@ var agents_default = defineCommand12({
1158
1161
  json: { type: "boolean", description: "Emit JSON (for list)" }
1159
1162
  },
1160
1163
  async run({ args }) {
1161
- const { buildContext } = await import("./context-PSGEX2D7.js");
1164
+ const { buildContext } = await import("./context-ALSJMTHE.js");
1162
1165
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1163
- const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1166
+ const { acquireSession, drainPool } = await import("./pool-JZGKPP6K.js");
1164
1167
  const [action, name] = args._ ?? [];
1165
1168
  if (!action || !["list", "restart", "logs"].includes(action)) {
1166
1169
  failure("Usage: clawops agents <list | restart [name] | logs <name>>");
@@ -1262,9 +1265,9 @@ var gateway_default = defineCommand13({
1262
1265
  json: { type: "boolean", description: "Emit JSON (for status)" }
1263
1266
  },
1264
1267
  async run({ args }) {
1265
- const { buildContext } = await import("./context-PSGEX2D7.js");
1268
+ const { buildContext } = await import("./context-ALSJMTHE.js");
1266
1269
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1267
- const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1270
+ const { acquireSession, drainPool } = await import("./pool-JZGKPP6K.js");
1268
1271
  const [action, versionArg] = args._ ?? [];
1269
1272
  if (!action || !["status", "restart", "update"].includes(action)) {
1270
1273
  failure("Usage: clawops gateway <status | restart | update [version]>");
@@ -1375,8 +1378,8 @@ var backup_default = defineCommand14({
1375
1378
  yes: { type: "boolean", description: "[restore] Skip confirmation prompt" }
1376
1379
  },
1377
1380
  async run({ args }) {
1378
- const { buildContext } = await import("./context-PSGEX2D7.js");
1379
- const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1381
+ const { buildContext } = await import("./context-ALSJMTHE.js");
1382
+ const { acquireSession, drainPool } = await import("./pool-JZGKPP6K.js");
1380
1383
  const action = args.action;
1381
1384
  if (action !== "create" && action !== "restore") {
1382
1385
  throw new UsageError(`Unknown action: ${action}. Use "create" or "restore"`);
@@ -1548,7 +1551,7 @@ var stacks_default = defineCommand15({
1548
1551
  let isDeployed = false;
1549
1552
  if (!args.force) {
1550
1553
  try {
1551
- const { buildContext } = await import("./context-PSGEX2D7.js");
1554
+ const { buildContext } = await import("./context-ALSJMTHE.js");
1552
1555
  const ctx = buildContext({ stack: name });
1553
1556
  if (ctx.adapter.name === "local") {
1554
1557
  isDeployed = !!ctx.localState;
@@ -1669,9 +1672,9 @@ var doctor_default = defineCommand16({
1669
1672
  info("Credential checks skipped \u2014 no config");
1670
1673
  } else {
1671
1674
  const { getProvider } = await import("./providers-2OABPW2E.js");
1672
- await import("./aws-FRE2JVAZ.js");
1673
- await import("./gcp-BXDTC6EK.js");
1674
- await import("./azure-PVC3AQVC.js");
1675
+ await import("./aws-D7Y6LCGK.js");
1676
+ await import("./gcp-6FT2A45S.js");
1677
+ await import("./azure-JQAMNVHN.js");
1675
1678
  await import("./local-DXBEVZ5C.js");
1676
1679
  const checkedProviders = /* @__PURE__ */ new Set();
1677
1680
  for (const [stackName, stackCfg] of Object.entries(config.stacks)) {
@@ -1706,9 +1709,9 @@ var doctor_default = defineCommand16({
1706
1709
  info("Remote health skipped \u2014 no config");
1707
1710
  } else {
1708
1711
  try {
1709
- const { buildContext } = await import("./context-PSGEX2D7.js");
1712
+ const { buildContext } = await import("./context-ALSJMTHE.js");
1710
1713
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1711
- const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1714
+ const { acquireSession, drainPool } = await import("./pool-JZGKPP6K.js");
1712
1715
  const ctx = buildContext({ stack: args.stack });
1713
1716
  const stack = await ctx.getStack();
1714
1717
  const outputMap = await stack.outputs();
@@ -1792,8 +1795,53 @@ var doctor_default = defineCommand16({
1792
1795
  }
1793
1796
  }
1794
1797
  }
1798
+ if (args.stack && config) {
1799
+ process18.stdout.write("\nHardening\n");
1800
+ try {
1801
+ const { MODULE_CATALOG, resolveModules, withRemoteExec } = await import("./harden-BDVYW567.js");
1802
+ const { buildContext } = await import("./context-ALSJMTHE.js");
1803
+ const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1804
+ const ctx = buildContext({ stack: args.stack });
1805
+ const stackObj = await ctx.getStack();
1806
+ const outputMap = await stackObj.outputs();
1807
+ const outputs = Object.fromEntries(
1808
+ Object.entries(outputMap).map(([k, v]) => [k, v.value])
1809
+ );
1810
+ const base = extractBaseOutputs(outputs);
1811
+ const conn = ctx.adapter.getConnectionInfo({
1812
+ ...base,
1813
+ privateKeyPath: config.ssh.keyPath,
1814
+ knownHostsPath: config.ssh.knownHostsPath
1815
+ });
1816
+ const hardenAc = new AbortController();
1817
+ process18.on("SIGINT", () => hardenAc.abort());
1818
+ const provider = ctx.adapter.name;
1819
+ const modules = resolveModules(MODULE_CATALOG, void 0, provider);
1820
+ await withRemoteExec(conn, hardenAc.signal, async (exec) => {
1821
+ for (const mod of modules) {
1822
+ const result = await mod.check(exec);
1823
+ if (result.status === "applied") {
1824
+ success(`${mod.label.padEnd(32)} applied`);
1825
+ } else if (result.status === "drifted") {
1826
+ warn(`${mod.label.padEnd(32)} drifted \u2014 ${result.detail}`);
1827
+ } else if (result.status === "missing") {
1828
+ info(`${mod.label.padEnd(32)} not applied \u2014 run \`clawops harden\``);
1829
+ }
1830
+ }
1831
+ });
1832
+ } catch (err) {
1833
+ failure(`Hardening checks failed: ${err instanceof Error ? err.message : String(err)}`);
1834
+ }
1835
+ }
1795
1836
  process18.stdout.write("\n");
1796
- if (!nodeOk) process18.exit(1);
1837
+ if (!nodeOk) {
1838
+ process18.stdout.write(
1839
+ `Run \`clawops bug\` to open a pre-filled issue at ${REPO_URL}/issues
1840
+
1841
+ `
1842
+ );
1843
+ process18.exit(1);
1844
+ }
1797
1845
  }
1798
1846
  });
1799
1847
 
@@ -1814,7 +1862,7 @@ var serveCmd = defineCommand17({
1814
1862
  inspector: { type: "boolean", description: "Enable MCP inspector" }
1815
1863
  },
1816
1864
  async run({ args }) {
1817
- const { serveMcp } = await import("./server-LOL2NUJ2.js");
1865
+ const { serveMcp } = await import("./server-EXBOOIEP.js");
1818
1866
  await serveMcp({
1819
1867
  port: args.http ? Number(args.http) : void 0,
1820
1868
  bind: args.bind,
@@ -1889,8 +1937,8 @@ var wireCmd = defineCommand17({
1889
1937
  force: { type: "boolean", description: "Apply even if gateway version is below minimum" }
1890
1938
  },
1891
1939
  async run({ args }) {
1892
- const { buildContext } = await import("./context-PSGEX2D7.js");
1893
- const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1940
+ const { buildContext } = await import("./context-ALSJMTHE.js");
1941
+ const { acquireSession, drainPool } = await import("./pool-JZGKPP6K.js");
1894
1942
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1895
1943
  const { wireGatewayMcp } = await import("./mcp-wire-ZWIYMLEW.js");
1896
1944
  const ac = new AbortController();
@@ -2386,6 +2434,12 @@ var setup_default = defineCommand18({
2386
2434
  signal: ac.signal,
2387
2435
  inquirer
2388
2436
  });
2437
+ await maybeHarden({
2438
+ stackName: stackAnswers.stackName,
2439
+ provider: "local",
2440
+ signal: ac.signal,
2441
+ inquirer
2442
+ });
2389
2443
  } else {
2390
2444
  const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
2391
2445
  const knownHostsPath = path3.join(getConfigDir2(), "known_hosts");
@@ -2404,7 +2458,7 @@ var setup_default = defineCommand18({
2404
2458
  default: false
2405
2459
  }]);
2406
2460
  if (applyNow) {
2407
- const { applyPlan } = await import("./apply-5RREMN3L.js");
2461
+ const { applyPlan } = await import("./apply-BTZFFGAD.js");
2408
2462
  const plan = JSON.parse(readFileSync3(outputPath, "utf-8"));
2409
2463
  try {
2410
2464
  await applyPlan(plan, {
@@ -2418,6 +2472,12 @@ var setup_default = defineCommand18({
2418
2472
  process20.exit(1);
2419
2473
  }
2420
2474
  await maybeWireGatewayMcpCloud({ stackName: stackAnswers.stackName, signal: ac.signal, inquirer });
2475
+ await maybeHarden({
2476
+ stackName: stackAnswers.stackName,
2477
+ provider,
2478
+ signal: ac.signal,
2479
+ inquirer
2480
+ });
2421
2481
  } else {
2422
2482
  info(`
2423
2483
  To deploy later: clawops apply ${outputPath}`);
@@ -2427,8 +2487,8 @@ To deploy later: clawops apply ${outputPath}`);
2427
2487
  });
2428
2488
  async function runLocalDeploy(opts) {
2429
2489
  const { setConfig: setConfig2, getConfig: getConfig2, getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
2430
- const { localBootstrap } = await import("./bootstrap-G4UZ2FKH.js");
2431
- const { connect } = await import("./ssh-IQXNME3D.js");
2490
+ const { localBootstrap } = await import("./bootstrap-RY4VOSDJ.js");
2491
+ const { connect } = await import("./ssh-E2AMBPZY.js");
2432
2492
  const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-QF5TT7GU.js");
2433
2493
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
2434
2494
  const knownHostsPath = path3.join(getConfigDir2(), "known_hosts");
@@ -2510,7 +2570,7 @@ async function runLocalDeploy(opts) {
2510
2570
  spin2.fail("Configuration failed");
2511
2571
  throw err;
2512
2572
  }
2513
- const { drainPool } = await import("./pool-FBFHATDG.js");
2573
+ const { drainPool } = await import("./pool-JZGKPP6K.js");
2514
2574
  drainPool();
2515
2575
  const tokenPath = path3.join(os.homedir(), ".clawops", "secrets", `GATEWAY_TOKEN_${opts.stackName}`);
2516
2576
  const dashboardUrl = `${state.gatewayUrl}?token=${opts.gatewayToken}`;
@@ -2648,7 +2708,7 @@ var DOCKER_CHECK_CMD = [
2648
2708
  "echo NOT_RUNNING"
2649
2709
  ].join("; ");
2650
2710
  async function checkDockerPreflight(opts) {
2651
- const { acquireSession } = await import("./pool-FBFHATDG.js");
2711
+ const { acquireSession } = await import("./pool-JZGKPP6K.js");
2652
2712
  const { ProviderError } = await import("./errors-OK47MQFD.js");
2653
2713
  const spin = spinner("Checking Docker on target host...");
2654
2714
  const timeoutSignal = AbortSignal.timeout(DOCKER_PREFLIGHT_TIMEOUT_MS);
@@ -2695,7 +2755,7 @@ async function checkDockerPreflight(opts) {
2695
2755
  var DOCKER_POLL_MS = 3e3;
2696
2756
  var DOCKER_TIMEOUT_MS = 9e4;
2697
2757
  async function startRemoteDocker(opts, spin) {
2698
- const { acquireSession } = await import("./pool-FBFHATDG.js");
2758
+ const { acquireSession } = await import("./pool-JZGKPP6K.js");
2699
2759
  const { ProviderError } = await import("./errors-OK47MQFD.js");
2700
2760
  spin.text = "Starting Docker on the remote server...";
2701
2761
  const { session, release } = await acquireSession({
@@ -2899,7 +2959,7 @@ async function maybeWireGatewayMcp(opts) {
2899
2959
  process20.stdout.write("\n");
2900
2960
  const { wireGateway } = await inquirerPromptWire(opts.inquirer);
2901
2961
  if (!wireGateway) return;
2902
- const { connect } = await import("./ssh-IQXNME3D.js");
2962
+ const { connect } = await import("./ssh-E2AMBPZY.js");
2903
2963
  const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
2904
2964
  const { wireGatewayMcp } = await import("./mcp-wire-ZWIYMLEW.js");
2905
2965
  const knownHostsPath = path3.join(getConfigDir2(), "known_hosts");
@@ -2992,6 +3052,68 @@ function instanceChoices(provider) {
2992
3052
  };
2993
3053
  return table[provider] ?? [];
2994
3054
  }
3055
+ async function maybeHarden(opts) {
3056
+ const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-BDVYW567.js");
3057
+ const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
3058
+ const config = getConfig2();
3059
+ if (!config) return;
3060
+ const availableModules = resolveModules(MODULE_CATALOG, void 0, opts.provider);
3061
+ if (availableModules.length === 0) return;
3062
+ process20.stdout.write("\n");
3063
+ const { selectedIds } = await opts.inquirer.prompt([{
3064
+ type: "checkbox",
3065
+ name: "selectedIds",
3066
+ message: "Apply security hardening? (Space to toggle, Enter to confirm)",
3067
+ choices: availableModules.map((m) => ({
3068
+ name: `${m.label}${m.defaultOn ? "" : " (opt-in)"}`,
3069
+ value: m.id,
3070
+ checked: m.defaultOn
3071
+ }))
3072
+ }]);
3073
+ if (selectedIds.length === 0) {
3074
+ info("Hardening skipped. Run `clawops harden --stack " + opts.stackName + "` later.");
3075
+ return;
3076
+ }
3077
+ const modules = availableModules.filter((m) => selectedIds.includes(m.id));
3078
+ process20.stdout.write("\nApplying hardening modules...\n\n");
3079
+ try {
3080
+ const { buildContext } = await import("./context-ALSJMTHE.js");
3081
+ const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
3082
+ const ctx = buildContext({ stack: opts.stackName });
3083
+ const stackObj = await ctx.getStack();
3084
+ const outputMap = await stackObj.outputs();
3085
+ const outputs = Object.fromEntries(
3086
+ Object.entries(outputMap).map(([k, v]) => [k, v.value])
3087
+ );
3088
+ const base = extractBaseOutputs(outputs);
3089
+ const conn = ctx.adapter.getConnectionInfo({
3090
+ ...base,
3091
+ privateKeyPath: config.ssh.keyPath,
3092
+ knownHostsPath: config.ssh.knownHostsPath
3093
+ });
3094
+ const { success: ok, failure: fail } = await import("./human-Z44CW7TR.js");
3095
+ const results = await runHardening(conn, {
3096
+ modules,
3097
+ signal: opts.signal,
3098
+ onProgress: (r) => {
3099
+ process20.stdout.write(` \xB7 ${r.module.label}
3100
+ `);
3101
+ }
3102
+ });
3103
+ process20.stdout.write("\n" + formatHardenSummary(results));
3104
+ const errors = results.filter((r) => r.error);
3105
+ if (errors.length > 0) {
3106
+ for (const r of errors) fail(`${r.module.label}: ${r.error}`);
3107
+ info("Some modules failed. Run `clawops harden --stack " + opts.stackName + "` to retry.");
3108
+ } else {
3109
+ ok("Hardening complete.");
3110
+ }
3111
+ } catch (err) {
3112
+ const { failure: fail } = await import("./human-Z44CW7TR.js");
3113
+ fail(`Hardening failed: ${err instanceof Error ? err.message : String(err)}`);
3114
+ info("Run `clawops harden --stack " + opts.stackName + "` to retry.");
3115
+ }
3116
+ }
2995
3117
 
2996
3118
  // src/cli/commands/secret.ts
2997
3119
  import { defineCommand as defineCommand19 } from "citty";
@@ -3164,10 +3286,10 @@ var rotateCmd = defineCommand19({
3164
3286
  }
3165
3287
  info(`Re-applying config overlay to stack "${targetStack}"\u2026`);
3166
3288
  try {
3167
- const { buildContext } = await import("./context-PSGEX2D7.js");
3289
+ const { buildContext } = await import("./context-ALSJMTHE.js");
3168
3290
  const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-QF5TT7GU.js");
3169
3291
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
3170
- const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
3292
+ const { acquireSession, drainPool } = await import("./pool-JZGKPP6K.js");
3171
3293
  const { localStateToConnectionInfo } = await import("./state-PRBIIN7I.js");
3172
3294
  const ctx = buildContext({ stack: targetStack });
3173
3295
  let conn;
@@ -3344,26 +3466,254 @@ ${dim("Run `clawops <command> --help` for command-specific flags.")}
3344
3466
  }
3345
3467
  });
3346
3468
 
3347
- // src/cli/error-handler.ts
3469
+ // src/cli/commands/bug.ts
3470
+ import { defineCommand as defineCommand21 } from "citty";
3348
3471
  import process22 from "process";
3472
+ import { accessSync as accessSync2, constants as constants2 } from "fs";
3473
+ import { execSync as execSync2 } from "child_process";
3474
+ var ISSUES_URL = `${REPO_URL}/issues/new`;
3475
+ var BODY_CAP = 2e3;
3476
+ var VERSION = true ? "1.7.0" : "dev";
3477
+ async function collectContext() {
3478
+ const nodeVersion = process22.version;
3479
+ const os3 = `${process22.platform} ${process22.arch}`;
3480
+ let provider = "unknown";
3481
+ let stackCount = 0;
3482
+ let sshKey = "unknown";
3483
+ try {
3484
+ const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
3485
+ const config = getConfig2();
3486
+ if (config) {
3487
+ const stacks = Object.values(config.stacks);
3488
+ stackCount = stacks.length;
3489
+ provider = stacks[0]?.provider ?? "none";
3490
+ const keyPath = config.ssh.keyPath.replace(/^~/, process22.env["HOME"] ?? "~");
3491
+ try {
3492
+ accessSync2(keyPath, constants2.R_OK);
3493
+ sshKey = "present";
3494
+ } catch {
3495
+ sshKey = "missing";
3496
+ }
3497
+ } else {
3498
+ provider = "not configured";
3499
+ sshKey = "not configured";
3500
+ }
3501
+ } catch {
3502
+ }
3503
+ return [
3504
+ `**clawops version:** ${VERSION}`,
3505
+ `**Node:** ${nodeVersion}`,
3506
+ `**OS:** ${os3}`,
3507
+ `**Provider:** ${provider}`,
3508
+ `**Stacks:** ${stackCount}`,
3509
+ `**SSH key:** ${sshKey}`
3510
+ ].join("\n");
3511
+ }
3512
+ function buildIssueUrl(title, command, context) {
3513
+ const body = [
3514
+ context,
3515
+ "",
3516
+ "**Description:**",
3517
+ title,
3518
+ "",
3519
+ command ? `**Command:**
3520
+ \`${command}\`` : "",
3521
+ "",
3522
+ "**Steps to reproduce:**",
3523
+ "<!-- Please describe what you did -->\n"
3524
+ ].filter((l) => l !== void 0).join("\n").slice(0, BODY_CAP);
3525
+ const params = new URLSearchParams({ title, body });
3526
+ return `${ISSUES_URL}?${params.toString()}`;
3527
+ }
3528
+ function openBrowser2(url) {
3529
+ try {
3530
+ const cmd = process22.platform === "darwin" ? "open" : "xdg-open";
3531
+ execSync2(`${cmd} "${url}"`, { stdio: "ignore" });
3532
+ return true;
3533
+ } catch {
3534
+ return false;
3535
+ }
3536
+ }
3537
+ var bug_default = defineCommand21({
3538
+ meta: {
3539
+ name: "bug",
3540
+ description: "Open a pre-filled GitHub issue with system context from clawops doctor"
3541
+ },
3542
+ args: {
3543
+ json: { type: "boolean", description: "Emit issue URL as JSON without opening a browser" }
3544
+ },
3545
+ async run({ args }) {
3546
+ const context = await collectContext();
3547
+ let title = "";
3548
+ let command = "";
3549
+ if (!args.json) {
3550
+ const { createInterface: createInterface3 } = await import("readline/promises");
3551
+ const rl = createInterface3({ input: process22.stdin, output: process22.stdout });
3552
+ try {
3553
+ title = (await rl.question("Describe the issue in one line: ")).trim();
3554
+ command = (await rl.question("Which command triggered it? (optional): ")).trim();
3555
+ } finally {
3556
+ rl.close();
3557
+ }
3558
+ }
3559
+ const url = buildIssueUrl(title, command, context);
3560
+ if (args.json) {
3561
+ printJson(jsonOk({ url }));
3562
+ return;
3563
+ }
3564
+ process22.stdout.write("\n");
3565
+ info(`Opening: ${url}`);
3566
+ process22.stdout.write("\n");
3567
+ const opened = openBrowser2(url);
3568
+ if (!opened) {
3569
+ process22.stdout.write(`Open this URL to file the issue:
3570
+ ${url}
3571
+ `);
3572
+ }
3573
+ }
3574
+ });
3575
+
3576
+ // src/cli/commands/harden.ts
3577
+ import { defineCommand as defineCommand22 } from "citty";
3578
+ import process23 from "process";
3579
+ var harden_default = defineCommand22({
3580
+ meta: {
3581
+ name: "harden",
3582
+ description: "Apply security hardening modules to a deployed stack"
3583
+ },
3584
+ args: {
3585
+ stack: { type: "string", description: "Target stack name" },
3586
+ options: { type: "string", description: "Comma-separated module IDs to run (default: all defaultOn modules)" },
3587
+ list: { type: "boolean", description: "List available modules and exit" },
3588
+ "dry-run": { type: "boolean", description: "Check current state without applying changes" },
3589
+ yes: { type: "boolean", description: "Skip confirmation prompt" },
3590
+ json: { type: "boolean", description: "Emit structured JSON output" }
3591
+ },
3592
+ async run({ args }) {
3593
+ const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-BDVYW567.js");
3594
+ const { buildContext } = await import("./context-ALSJMTHE.js");
3595
+ const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
3596
+ if (args.list) {
3597
+ process23.stdout.write("\nAvailable hardening modules:\n\n");
3598
+ for (const mod of MODULE_CATALOG) {
3599
+ const on = mod.defaultOn ? chalk.green("ON ") : chalk.dim("off");
3600
+ const providers = mod.providers === "all" ? "all" : mod.providers.join(", ");
3601
+ process23.stdout.write(
3602
+ ` ${on} ${mod.id.padEnd(28)} ${mod.label} [${providers}]
3603
+ `
3604
+ );
3605
+ }
3606
+ process23.stdout.write("\n");
3607
+ return;
3608
+ }
3609
+ const config = getConfig2();
3610
+ if (!config) {
3611
+ failure("No clawops config found. Run `clawops init` first.");
3612
+ process23.exit(1);
3613
+ }
3614
+ const ctx = buildContext({ stack: args.stack });
3615
+ const provider = ctx.adapter.name;
3616
+ const modules = resolveModules(MODULE_CATALOG, args.options, provider);
3617
+ if (modules.length === 0) {
3618
+ warn("No modules selected for this provider. Use --options to specify modules or --list to see all.");
3619
+ return;
3620
+ }
3621
+ if (!args.json) {
3622
+ process23.stdout.write("\nModules to run:\n");
3623
+ for (const mod of modules) {
3624
+ const flag = mod.defaultOn ? "" : chalk.yellow(" [opt-in]");
3625
+ process23.stdout.write(` \xB7 ${mod.label}${flag}
3626
+ `);
3627
+ }
3628
+ process23.stdout.write("\n");
3629
+ }
3630
+ if (!args["dry-run"] && !args.yes && !args.json) {
3631
+ const { createInterface: createInterface3 } = await import("readline/promises");
3632
+ const rl = createInterface3({ input: process23.stdin, output: process23.stdout });
3633
+ const answer = await rl.question("Apply these hardening modules? [y/N] ");
3634
+ rl.close();
3635
+ if (!answer.toLowerCase().startsWith("y")) {
3636
+ info("Aborted.");
3637
+ return;
3638
+ }
3639
+ }
3640
+ const ac = new AbortController();
3641
+ process23.on("SIGINT", () => ac.abort());
3642
+ process23.on("SIGTERM", () => ac.abort());
3643
+ const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
3644
+ const stackObj = await ctx.getStack();
3645
+ const outputMap = await stackObj.outputs();
3646
+ const outputs = Object.fromEntries(
3647
+ Object.entries(outputMap).map(([k, v]) => [k, v.value])
3648
+ );
3649
+ const base = extractBaseOutputs(outputs);
3650
+ const conn = ctx.adapter.getConnectionInfo({
3651
+ ...base,
3652
+ privateKeyPath: config.ssh.keyPath,
3653
+ knownHostsPath: config.ssh.knownHostsPath
3654
+ });
3655
+ if (!args.json) {
3656
+ process23.stdout.write(args["dry-run"] ? "Checking hardening state...\n\n" : "Applying hardening...\n\n");
3657
+ }
3658
+ const results = await runHardening(conn, {
3659
+ modules,
3660
+ dryRun: args["dry-run"],
3661
+ signal: ac.signal,
3662
+ onProgress: args.json ? void 0 : (r) => {
3663
+ const icon = r.error ? chalk.red("\u2717") : r.applyResult?.changed ? chalk.green("\u2713") : chalk.dim("\xB7");
3664
+ process23.stdout.write(` ${icon} ${r.module.label}
3665
+ `);
3666
+ }
3667
+ });
3668
+ if (args.json) {
3669
+ printJson(jsonOk({
3670
+ dryRun: args["dry-run"] ?? false,
3671
+ results: results.map((r) => ({
3672
+ id: r.module.id,
3673
+ status: r.checkResult.status,
3674
+ changed: r.applyResult?.changed ?? false,
3675
+ detail: r.applyResult?.detail ?? r.checkResult.detail,
3676
+ error: r.error,
3677
+ durationMs: r.durationMs
3678
+ }))
3679
+ }));
3680
+ return;
3681
+ }
3682
+ process23.stdout.write("\n" + formatHardenSummary(results));
3683
+ const errors = results.filter((r) => r.error);
3684
+ if (errors.length > 0) {
3685
+ process23.stdout.write("\n");
3686
+ for (const r of errors) {
3687
+ failure(`${r.module.label}: ${r.error}`);
3688
+ }
3689
+ process23.exit(1);
3690
+ }
3691
+ if (!args["dry-run"]) {
3692
+ success("Hardening complete.");
3693
+ }
3694
+ }
3695
+ });
3696
+
3697
+ // src/cli/error-handler.ts
3698
+ import process24 from "process";
3349
3699
  function handleError(err) {
3350
3700
  if (err instanceof ClawopsError) {
3351
3701
  failure(err.message);
3352
- process22.exit(err.exitCode);
3702
+ process24.exit(err.exitCode);
3353
3703
  }
3354
3704
  if (err instanceof Error) {
3355
3705
  failure(`Unexpected error: ${err.message}`);
3356
- if (process22.env["DEBUG"]) {
3357
- process22.stderr.write(err.stack ?? "\n");
3706
+ if (process24.env["DEBUG"]) {
3707
+ process24.stderr.write(err.stack ?? "\n");
3358
3708
  }
3359
- process22.exit(1);
3709
+ process24.exit(1);
3360
3710
  }
3361
3711
  failure(`Unknown error: ${String(err)}`);
3362
- process22.exit(1);
3712
+ process24.exit(1);
3363
3713
  }
3364
3714
 
3365
3715
  // src/cli/index.ts
3366
- var main = defineCommand21({
3716
+ var main = defineCommand23({
3367
3717
  meta: {
3368
3718
  name: "clawops",
3369
3719
  description: "Deploy and manage self-hosted OpenClaw instances across clouds",
@@ -3399,7 +3749,9 @@ var main = defineCommand21({
3399
3749
  setup: setup_default,
3400
3750
  secret: secret_default,
3401
3751
  monitor: monitor_default,
3402
- help: help_default
3752
+ help: help_default,
3753
+ bug: bug_default,
3754
+ harden: harden_default
3403
3755
  }
3404
3756
  });
3405
3757
  try {