@clawops/cli 1.2.1 → 1.3.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # clawops
2
2
 
3
- Deploy OpenClaw once. Operate it from your terminal or Claude Code.
3
+ MCP-native infrastructure ops for OpenClaw with read-only mode, destructive-action confirmation, and audit logs built in.
4
4
 
5
5
  **clawops** is a CLI and [MCP](https://modelcontextprotocol.io/) server for deploying and operating
6
6
  self-hosted [OpenClaw](https://github.com/openclaw/openclaw) instances. Provision on AWS, GCP,
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  applyPlan
4
- } from "./chunk-LU63NZD3.js";
4
+ } from "./chunk-JCUZU5BH.js";
5
5
  import "./chunk-YTH4L2GN.js";
6
+ import "./chunk-6ZFIFDBJ.js";
6
7
  import "./chunk-BOPSG2LI.js";
7
- import "./chunk-ACYJBSLJ.js";
8
- import "./chunk-BRPU7AQC.js";
8
+ import "./chunk-JDN6PLH2.js";
9
9
  import "./chunk-UDNZUSKA.js";
10
+ import "./chunk-A2I76FTA.js";
10
11
  import "./chunk-CX5SL5HP.js";
11
12
  import "./chunk-KGXPLI7W.js";
12
13
  export {
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- writeLocalState
4
- } from "./chunk-BRPU7AQC.js";
5
2
  import {
6
3
  acquireSession
7
4
  } from "./chunk-ZVOEQCNW.js";
8
5
  import "./chunk-4U3LTLWZ.js";
6
+ import {
7
+ writeLocalState
8
+ } from "./chunk-A2I76FTA.js";
9
9
  import "./chunk-CX5SL5HP.js";
10
10
  import {
11
11
  ProviderError
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/plan/overlay-store.ts
4
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
5
+ import path from "path";
6
+ import os from "os";
7
+ function overlayPath(stackName) {
8
+ return path.join(os.homedir(), ".clawops", "overlays", `${stackName}.json`);
9
+ }
10
+ function saveOverlay(stackName, overlay, secrets) {
11
+ const dir = path.join(os.homedir(), ".clawops", "overlays");
12
+ mkdirSync(dir, { recursive: true });
13
+ const data = { stackName, savedAt: (/* @__PURE__ */ new Date()).toISOString(), overlay, secrets };
14
+ writeFileSync(overlayPath(stackName), JSON.stringify(data, null, 2) + "\n", "utf-8");
15
+ }
16
+ function loadOverlay(stackName) {
17
+ const p = overlayPath(stackName);
18
+ if (!existsSync(p)) return null;
19
+ try {
20
+ return JSON.parse(readFileSync(p, "utf-8"));
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+ function listOverlays() {
26
+ const dir = path.join(os.homedir(), ".clawops", "overlays");
27
+ if (!existsSync(dir)) return [];
28
+ return readdirSync(dir).filter((f) => f.endsWith(".json")).flatMap((f) => {
29
+ try {
30
+ return [JSON.parse(readFileSync(path.join(dir, f), "utf-8"))];
31
+ } catch {
32
+ return [];
33
+ }
34
+ });
35
+ }
36
+
37
+ export {
38
+ saveOverlay,
39
+ loadOverlay,
40
+ listOverlays
41
+ };
@@ -30,8 +30,20 @@ function writeLocalState(stackName, state) {
30
30
  writeFileSync(tmp, JSON.stringify(state, null, 2) + "\n", "utf-8");
31
31
  renameSync(tmp, dest);
32
32
  }
33
+ function localStateToConnectionInfo(state) {
34
+ return {
35
+ host: state.sshHost,
36
+ port: state.sshPort,
37
+ user: state.sshUser,
38
+ privateKeyPath: state.privateKeyPath,
39
+ knownHostsPath: state.knownHostsPath
40
+ };
41
+ }
33
42
 
34
43
  export {
44
+ stateDir,
45
+ statePath,
35
46
  readLocalState,
36
- writeLocalState
47
+ writeLocalState,
48
+ localStateToConnectionInfo
37
49
  };
@@ -2,12 +2,15 @@
2
2
  import {
3
3
  validatePlan
4
4
  } from "./chunk-YTH4L2GN.js";
5
+ import {
6
+ saveOverlay
7
+ } from "./chunk-6ZFIFDBJ.js";
5
8
  import {
6
9
  resolveSecrets
7
10
  } from "./chunk-BOPSG2LI.js";
8
11
  import {
9
12
  buildContext
10
- } from "./chunk-ACYJBSLJ.js";
13
+ } from "./chunk-JDN6PLH2.js";
11
14
  import {
12
15
  atomicWriteConfig,
13
16
  deepMerge,
@@ -106,6 +109,7 @@ async function applyConfigOverlay(plan, outputs, ctx, signal) {
106
109
  });
107
110
  await atomicWriteConfig(session, merged, signal);
108
111
  await restartGateway(session, signal);
112
+ saveOverlay(plan.spec.stackName, configOverlay, plan.spec.secrets ?? []);
109
113
  } finally {
110
114
  session.close();
111
115
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readLocalState
4
- } from "./chunk-BRPU7AQC.js";
4
+ } from "./chunk-A2I76FTA.js";
5
5
  import {
6
6
  getConfigDir,
7
7
  requireConfig
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildContext
4
- } from "./chunk-ACYJBSLJ.js";
4
+ } from "./chunk-JDN6PLH2.js";
5
5
  import {
6
6
  acquireSession,
7
7
  drainPool
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-YTH4L2GN.js";
5
5
  import {
6
6
  buildContext
7
- } from "./chunk-ACYJBSLJ.js";
7
+ } from "./chunk-JDN6PLH2.js";
8
8
  import {
9
9
  getConfig
10
10
  } from "./chunk-CX5SL5HP.js";
@@ -45,7 +45,7 @@ async function generatePlan(intent, _opts) {
45
45
  "plan/apply is not supported for the local provider. Use `clawops up` directly."
46
46
  );
47
47
  }
48
- const { version } = await import("./package-SE3M4NJA.js");
48
+ const { version } = await import("./package-AFUTH62F.js");
49
49
  const config = getConfig();
50
50
  const instanceType = intent.instanceType ?? "small";
51
51
  const openclawVersion = intent.openclawVersion ?? "latest";
package/dist/cli.js CHANGED
@@ -1,17 +1,17 @@
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
+ validateOpenclawConfig
9
+ } from "./chunk-LVIIYY27.js";
10
+ import "./chunk-JDN6PLH2.js";
12
11
  import "./chunk-ZVOEQCNW.js";
13
12
  import "./chunk-4U3LTLWZ.js";
14
13
  import "./chunk-UDNZUSKA.js";
14
+ import "./chunk-A2I76FTA.js";
15
15
  import {
16
16
  getConfig,
17
17
  getConfigDir,
@@ -24,7 +24,7 @@ import {
24
24
  } from "./chunk-KGXPLI7W.js";
25
25
 
26
26
  // src/cli/index.ts
27
- import { defineCommand as defineCommand20, runMain } from "citty";
27
+ import { defineCommand as defineCommand21, runMain } from "citty";
28
28
 
29
29
  // src/cli/commands/init.ts
30
30
  import { defineCommand } from "citty";
@@ -217,7 +217,7 @@ var up_default = defineCommand2({
217
217
  config: { type: "string", description: "Path to openclaw config overlay JSON (local provider only)" }
218
218
  },
219
219
  async run({ args }) {
220
- const { buildContext } = await import("./context-VUAYRAJY.js");
220
+ const { buildContext } = await import("./context-M2RMDHX6.js");
221
221
  const ctx = buildContext(args);
222
222
  const openclawVersion = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : "stable";
223
223
  if (ctx.adapter.name === "local") {
@@ -228,7 +228,7 @@ var up_default = defineCommand2({
228
228
  );
229
229
  }
230
230
  const { localOpts } = stackConfig;
231
- const { localBootstrap } = await import("./bootstrap-UYCOEJQX.js");
231
+ const { localBootstrap } = await import("./bootstrap-G4UZ2FKH.js");
232
232
  const abortController = new AbortController();
233
233
  process3.on("SIGINT", () => abortController.abort());
234
234
  process3.on("SIGTERM", () => abortController.abort());
@@ -363,7 +363,7 @@ var down_default = defineCommand3({
363
363
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
364
364
  },
365
365
  async run({ args }) {
366
- const { buildContext } = await import("./context-VUAYRAJY.js");
366
+ const { buildContext } = await import("./context-M2RMDHX6.js");
367
367
  const ctx = buildContext(args);
368
368
  if (args["dry-run"]) {
369
369
  info(`Dry run \u2014 would destroy stack "${ctx.stackName}"`);
@@ -427,7 +427,7 @@ var status_default = defineCommand4({
427
427
  json: { type: "boolean", description: "Emit JSON" }
428
428
  },
429
429
  async run({ args }) {
430
- const { buildContext } = await import("./context-VUAYRAJY.js");
430
+ const { buildContext } = await import("./context-M2RMDHX6.js");
431
431
  const ctx = buildContext(args);
432
432
  if (ctx.adapter.name === "local") {
433
433
  const state = ctx.localState;
@@ -499,8 +499,8 @@ var plan_default = defineCommand5({
499
499
  out: { type: "string", description: "Write plan JSON to this absolute path (default: stdout)" }
500
500
  },
501
501
  async run({ args }) {
502
- const { buildContext } = await import("./context-VUAYRAJY.js");
503
- const { generatePlan } = await import("./generate-PZPLG3ZM.js");
502
+ const { buildContext } = await import("./context-M2RMDHX6.js");
503
+ const { generatePlan } = await import("./generate-U7PJ5LRG.js");
504
504
  const ctx = buildContext(args);
505
505
  if (ctx.adapter.name === "local") {
506
506
  throw new UsageError(
@@ -590,7 +590,7 @@ var apply_default = defineCommand6({
590
590
  },
591
591
  async run({ args }) {
592
592
  const { validatePlan } = await import("./validate-T5M5EHSJ.js");
593
- const { applyPlan } = await import("./apply-KBF7OPWL.js");
593
+ const { applyPlan } = await import("./apply-DLNE2JYZ.js");
594
594
  const planPath = args._?.[0];
595
595
  if (!planPath) {
596
596
  throw new UsageError("Usage: clawops apply <plan.json>");
@@ -706,7 +706,7 @@ var destroy_default = defineCommand7({
706
706
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
707
707
  },
708
708
  async run({ args }) {
709
- const { buildContext } = await import("./context-VUAYRAJY.js");
709
+ const { buildContext } = await import("./context-M2RMDHX6.js");
710
710
  const ctx = buildContext(args);
711
711
  if (ctx.adapter.name === "local") {
712
712
  throw new UsageError(
@@ -773,7 +773,7 @@ var ssh_default = defineCommand8({
773
773
  command: { type: "string", description: "Remote command to run (instead of interactive shell)" }
774
774
  },
775
775
  async run({ args }) {
776
- const { buildContext } = await import("./context-VUAYRAJY.js");
776
+ const { buildContext } = await import("./context-M2RMDHX6.js");
777
777
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
778
778
  const ctx = buildContext(args);
779
779
  let conn;
@@ -860,7 +860,7 @@ var tunnel_default = defineCommand9({
860
860
  "no-open": { type: "boolean", description: "Do not open browser after tunnel is ready" }
861
861
  },
862
862
  async run({ args }) {
863
- const { buildContext } = await import("./context-VUAYRAJY.js");
863
+ const { buildContext } = await import("./context-M2RMDHX6.js");
864
864
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
865
865
  const { acquireSession } = await import("./pool-FBFHATDG.js");
866
866
  const ctx = buildContext(args);
@@ -928,7 +928,7 @@ var logs_default = defineCommand10({
928
928
  since: { type: "string", description: "Show logs since duration (e.g. 5m, 1h)" }
929
929
  },
930
930
  async run({ args }) {
931
- const { buildContext } = await import("./context-VUAYRAJY.js");
931
+ const { buildContext } = await import("./context-M2RMDHX6.js");
932
932
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
933
933
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
934
934
  const ctx = buildContext(args);
@@ -1045,7 +1045,7 @@ var config_default = defineCommand11({
1045
1045
  "dry-run": { type: "boolean", description: "Show what would change without writing" }
1046
1046
  },
1047
1047
  async run({ args }) {
1048
- const { buildContext } = await import("./context-VUAYRAJY.js");
1048
+ const { buildContext } = await import("./context-M2RMDHX6.js");
1049
1049
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1050
1050
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1051
1051
  const [action, key, value] = args._ ?? [];
@@ -1171,7 +1171,7 @@ var agents_default = defineCommand12({
1171
1171
  json: { type: "boolean", description: "Emit JSON (for list)" }
1172
1172
  },
1173
1173
  async run({ args }) {
1174
- const { buildContext } = await import("./context-VUAYRAJY.js");
1174
+ const { buildContext } = await import("./context-M2RMDHX6.js");
1175
1175
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1176
1176
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1177
1177
  const [action, name] = args._ ?? [];
@@ -1275,7 +1275,7 @@ var gateway_default = defineCommand13({
1275
1275
  json: { type: "boolean", description: "Emit JSON (for status)" }
1276
1276
  },
1277
1277
  async run({ args }) {
1278
- const { buildContext } = await import("./context-VUAYRAJY.js");
1278
+ const { buildContext } = await import("./context-M2RMDHX6.js");
1279
1279
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1280
1280
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1281
1281
  const [action, versionArg] = args._ ?? [];
@@ -1388,7 +1388,7 @@ var backup_default = defineCommand14({
1388
1388
  yes: { type: "boolean", description: "[restore] Skip confirmation prompt" }
1389
1389
  },
1390
1390
  async run({ args }) {
1391
- const { buildContext } = await import("./context-VUAYRAJY.js");
1391
+ const { buildContext } = await import("./context-M2RMDHX6.js");
1392
1392
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1393
1393
  const action = args.action;
1394
1394
  if (action !== "create" && action !== "restore") {
@@ -1694,7 +1694,7 @@ var doctor_default = defineCommand16({
1694
1694
  info("Remote health skipped \u2014 no config");
1695
1695
  } else {
1696
1696
  try {
1697
- const { buildContext } = await import("./context-VUAYRAJY.js");
1697
+ const { buildContext } = await import("./context-M2RMDHX6.js");
1698
1698
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1699
1699
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1700
1700
  const ctx = buildContext({ stack: args.stack });
@@ -1802,7 +1802,7 @@ var serveCmd = defineCommand17({
1802
1802
  inspector: { type: "boolean", description: "Enable MCP inspector" }
1803
1803
  },
1804
1804
  async run({ args }) {
1805
- const { serveMcp } = await import("./server-4DNHLGEG.js");
1805
+ const { serveMcp } = await import("./server-I62UES5V.js");
1806
1806
  await serveMcp({
1807
1807
  port: args.http ? Number(args.http) : void 0,
1808
1808
  bind: args.bind,
@@ -1876,12 +1876,6 @@ var mcp_default = defineCommand17({
1876
1876
  subCommands: {
1877
1877
  serve: serveCmd,
1878
1878
  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
1879
  }
1886
1880
  });
1887
1881
 
@@ -2148,10 +2142,10 @@ var setup_default = defineCommand18({
2148
2142
  }
2149
2143
  }
2150
2144
  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}`);
2145
+ const secretsDir2 = path3.join(os.homedir(), ".clawops", "secrets");
2146
+ mkdirSync3(secretsDir2, { recursive: true });
2147
+ spawnSync("chmod", ["700", secretsDir2], { stdio: "ignore" });
2148
+ const tokenPath = path3.join(secretsDir2, `GATEWAY_TOKEN_${stackAnswers.stackName}`);
2155
2149
  writeFileSync3(tokenPath, gatewayToken, { encoding: "utf-8", mode: 384 });
2156
2150
  const openclawConfigOverlay = {
2157
2151
  models: builtModelConfig,
@@ -2309,7 +2303,7 @@ var setup_default = defineCommand18({
2309
2303
  default: false
2310
2304
  }]);
2311
2305
  if (applyNow) {
2312
- const { applyPlan } = await import("./apply-KBF7OPWL.js");
2306
+ const { applyPlan } = await import("./apply-DLNE2JYZ.js");
2313
2307
  const plan = JSON.parse(readFileSync3(outputPath, "utf-8"));
2314
2308
  try {
2315
2309
  await applyPlan(plan, {
@@ -2331,7 +2325,7 @@ To deploy later: clawops apply ${outputPath}`);
2331
2325
  });
2332
2326
  async function runLocalDeploy(opts) {
2333
2327
  const { setConfig: setConfig2, getConfig: getConfig2, getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
2334
- const { localBootstrap } = await import("./bootstrap-UYCOEJQX.js");
2328
+ const { localBootstrap } = await import("./bootstrap-G4UZ2FKH.js");
2335
2329
  const { connect } = await import("./ssh-IQXNME3D.js");
2336
2330
  const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-JQ77SRC2.js");
2337
2331
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
@@ -2404,6 +2398,8 @@ async function runLocalDeploy(opts) {
2404
2398
  const merged = deepMerge(remote, resolved);
2405
2399
  await atomicWriteConfig(session, merged, opts.signal);
2406
2400
  await restartGateway(session, opts.signal);
2401
+ const { saveOverlay } = await import("./overlay-store-ADZPD7EU.js");
2402
+ saveOverlay(opts.stackName, opts.overlay, opts.secrets);
2407
2403
  } finally {
2408
2404
  session.close();
2409
2405
  }
@@ -2497,7 +2493,7 @@ function cliInstallUrl(provider) {
2497
2493
  return "https://learn.microsoft.com/en-us/cli/azure/install-azure-cli";
2498
2494
  }
2499
2495
  async function promptSecret(name, label, envDefault, inquirer) {
2500
- const secretsDir = path3.join(os.homedir(), ".clawops", "secrets");
2496
+ const secretsDir2 = path3.join(os.homedir(), ".clawops", "secrets");
2501
2497
  const { secretSource } = await inquirer.prompt([{
2502
2498
  type: "list",
2503
2499
  name: "secretSource",
@@ -2515,12 +2511,12 @@ async function promptSecret(name, label, envDefault, inquirer) {
2515
2511
  message: `Paste your ${label}: (input is hidden)`,
2516
2512
  validate: (v) => v.trim() !== "" || "Value cannot be empty"
2517
2513
  }]);
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 };
2514
+ mkdirSync3(secretsDir2, { recursive: true });
2515
+ spawnSync("chmod", ["700", secretsDir2], { stdio: "ignore" });
2516
+ const secretPath2 = path3.join(secretsDir2, name);
2517
+ writeFileSync3(secretPath2, secretValue.trim(), { encoding: "utf-8", mode: 384 });
2518
+ success(`Secret saved to ${secretPath2} (chmod 600)`);
2519
+ return { name, source: "file", ref: secretPath2 };
2524
2520
  }
2525
2521
  if (secretSource === "env") {
2526
2522
  const { envVar } = await inquirer.prompt([{
@@ -2845,8 +2841,287 @@ function instanceChoices(provider) {
2845
2841
  return table[provider] ?? [];
2846
2842
  }
2847
2843
 
2848
- // src/cli/commands/help.ts
2844
+ // src/cli/commands/secret.ts
2849
2845
  import { defineCommand as defineCommand19 } from "citty";
2846
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync4, unlinkSync, mkdirSync as mkdirSync4, statSync } from "fs";
2847
+ import { spawnSync as spawnSync2 } from "child_process";
2848
+ import path4 from "path";
2849
+ import os2 from "os";
2850
+ import process21 from "process";
2851
+ var SECRETS_DIR = path4.join(os2.homedir(), ".clawops", "secrets");
2852
+ function secretsDir() {
2853
+ return SECRETS_DIR;
2854
+ }
2855
+ function secretPath(name) {
2856
+ return path4.join(secretsDir(), name);
2857
+ }
2858
+ function listSecretNames() {
2859
+ if (!existsSync3(secretsDir())) return [];
2860
+ return readdirSync(secretsDir()).filter((f) => !f.startsWith("."));
2861
+ }
2862
+ var listCmd = defineCommand19({
2863
+ meta: { name: "list", description: "List all stored secrets and their status" },
2864
+ args: {
2865
+ json: { type: "boolean", description: "Emit JSON" }
2866
+ },
2867
+ run({ args }) {
2868
+ const names = listSecretNames();
2869
+ if (names.length === 0) {
2870
+ info("No secrets stored in ~/.clawops/secrets/");
2871
+ info("Run `clawops secret set <name>` to add one.");
2872
+ return;
2873
+ }
2874
+ const rows = names.map((name) => {
2875
+ const p = secretPath(name);
2876
+ try {
2877
+ const stat = statSync(p);
2878
+ const content = readFileSync4(p, "utf-8").trim();
2879
+ const status = content.length > 0 ? "ok" : "empty";
2880
+ const modified = stat.mtime.toISOString().slice(0, 10);
2881
+ return { name, status, source: "file", path: p, modified, resolvable: content.length > 0 };
2882
+ } catch {
2883
+ return { name, status: "unreadable", source: "file", path: p, modified: "\u2014", resolvable: false };
2884
+ }
2885
+ });
2886
+ if (args.json) {
2887
+ process21.stdout.write(JSON.stringify(rows, null, 2) + "\n");
2888
+ return;
2889
+ }
2890
+ const tableRows = rows.map((r) => [
2891
+ r.name,
2892
+ r.resolvable ? chalk.green("ok") : chalk.red(r.status),
2893
+ r.modified,
2894
+ r.path
2895
+ ]);
2896
+ process21.stdout.write("\n" + renderTable(["Name", "Status", "Modified", "Path"], tableRows) + "\n\n");
2897
+ }
2898
+ });
2899
+ var setCmd = defineCommand19({
2900
+ meta: { name: "set", description: "Create or update a secret" },
2901
+ args: {
2902
+ value: { type: "string", description: "Secret value (skips interactive prompt)" }
2903
+ },
2904
+ async run({ args }) {
2905
+ const [name] = args._ ?? [];
2906
+ if (!name) {
2907
+ failure("Usage: clawops secret set <name>");
2908
+ process21.exit(2);
2909
+ }
2910
+ const inquirer = (await import("inquirer")).default;
2911
+ let value;
2912
+ if (args.value) {
2913
+ value = args.value;
2914
+ } else {
2915
+ const { secretValue } = await inquirer.prompt([{
2916
+ type: "password",
2917
+ name: "secretValue",
2918
+ message: `Value for secret "${name}": (input is hidden)`,
2919
+ validate: (v) => typeof v === "string" && v.trim() !== "" || "Value cannot be empty"
2920
+ }]);
2921
+ value = secretValue;
2922
+ }
2923
+ mkdirSync4(secretsDir(), { recursive: true });
2924
+ spawnSync2("chmod", ["700", secretsDir()], { stdio: "ignore" });
2925
+ writeFileSync4(secretPath(name), value.trim(), { encoding: "utf-8", mode: 384 });
2926
+ success(`Secret "${name}" saved to ${secretPath(name)} (chmod 600)`);
2927
+ info("Run `clawops secret rotate <name>` to propagate it to a running stack.");
2928
+ }
2929
+ });
2930
+ var deleteCmd = defineCommand19({
2931
+ meta: { name: "delete", description: "Remove a stored secret" },
2932
+ args: {
2933
+ yes: { type: "boolean", description: "Skip confirmation prompt" }
2934
+ },
2935
+ async run({ args }) {
2936
+ const [name] = args._ ?? [];
2937
+ if (!name) {
2938
+ failure("Usage: clawops secret delete <name>");
2939
+ process21.exit(2);
2940
+ }
2941
+ const p = secretPath(name);
2942
+ if (!existsSync3(p)) {
2943
+ failure(`Secret "${name}" not found at ${p}`);
2944
+ process21.exit(1);
2945
+ }
2946
+ const { listOverlays } = await import("./overlay-store-ADZPD7EU.js");
2947
+ const referencingStacks = listOverlays().filter((o) => o.secrets.some((s) => s.name === name)).map((o) => o.stackName);
2948
+ if (referencingStacks.length > 0) {
2949
+ warn(`Secret "${name}" is referenced by stack(s): ${referencingStacks.join(", ")}`);
2950
+ warn("Deleting it will leave those stacks with an unresolvable $secret: ref.");
2951
+ }
2952
+ if (!args.yes) {
2953
+ const inquirer = (await import("inquirer")).default;
2954
+ const { confirmed } = await inquirer.prompt([{
2955
+ type: "confirm",
2956
+ name: "confirmed",
2957
+ message: `Delete secret "${name}"?`,
2958
+ default: false
2959
+ }]);
2960
+ if (!confirmed) {
2961
+ info("Aborted.");
2962
+ return;
2963
+ }
2964
+ }
2965
+ unlinkSync(p);
2966
+ success(`Secret "${name}" deleted.`);
2967
+ }
2968
+ });
2969
+ var rotateCmd = defineCommand19({
2970
+ meta: { name: "rotate", description: "Update a secret and re-apply config to a running stack" },
2971
+ args: {
2972
+ stack: { type: "string", description: "Stack name to re-apply (defaults to config default)" },
2973
+ value: { type: "string", description: "New secret value (skips interactive prompt)" }
2974
+ },
2975
+ async run({ args }) {
2976
+ const [name] = args._ ?? [];
2977
+ if (!name) {
2978
+ failure("Usage: clawops secret rotate <name> [--stack <name>]");
2979
+ process21.exit(2);
2980
+ }
2981
+ const inquirer = (await import("inquirer")).default;
2982
+ let value;
2983
+ if (args.value) {
2984
+ value = args.value;
2985
+ } else {
2986
+ const { secretValue } = await inquirer.prompt([{
2987
+ type: "password",
2988
+ name: "secretValue",
2989
+ message: `New value for secret "${name}": (input is hidden)`,
2990
+ validate: (v) => typeof v === "string" && v.trim() !== "" || "Value cannot be empty"
2991
+ }]);
2992
+ value = secretValue;
2993
+ }
2994
+ mkdirSync4(secretsDir(), { recursive: true });
2995
+ spawnSync2("chmod", ["700", secretsDir()], { stdio: "ignore" });
2996
+ writeFileSync4(secretPath(name), value.trim(), { encoding: "utf-8", mode: 384 });
2997
+ success(`Secret "${name}" updated.`);
2998
+ const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
2999
+ const cfg = getConfig2();
3000
+ const targetStack = args.stack ?? cfg?.defaults?.stack;
3001
+ if (!targetStack) {
3002
+ warn("No stack specified and no default stack in config \u2014 skipping re-apply.");
3003
+ info("Run `clawops secret rotate <name> --stack <name>` to re-apply to a specific stack.");
3004
+ return;
3005
+ }
3006
+ const { loadOverlay } = await import("./overlay-store-ADZPD7EU.js");
3007
+ const stored = loadOverlay(targetStack);
3008
+ if (!stored) {
3009
+ warn(`No stored overlay for stack "${targetStack}" \u2014 cannot re-apply automatically.`);
3010
+ info("Re-run `clawops setup` or `clawops apply` to propagate the new secret.");
3011
+ return;
3012
+ }
3013
+ info(`Re-applying config overlay to stack "${targetStack}"\u2026`);
3014
+ try {
3015
+ const { buildContext } = await import("./context-M2RMDHX6.js");
3016
+ const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-JQ77SRC2.js");
3017
+ const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
3018
+ const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
3019
+ const { localStateToConnectionInfo } = await import("./state-PRBIIN7I.js");
3020
+ const ctx = buildContext({ stack: targetStack });
3021
+ let conn;
3022
+ if (ctx.adapter.name === "local") {
3023
+ if (!ctx.localState) throw new Error(`Stack "${targetStack}" has not been bootstrapped yet.`);
3024
+ conn = localStateToConnectionInfo(ctx.localState);
3025
+ } else {
3026
+ const stack = await ctx.getStack();
3027
+ const outputMap = await stack.outputs();
3028
+ const outputs = Object.fromEntries(
3029
+ Object.entries(outputMap).map(([k, v]) => [k, v.value])
3030
+ );
3031
+ const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
3032
+ const base = extractBaseOutputs(outputs);
3033
+ conn = ctx.adapter.getConnectionInfo({ ...base, privateKeyPath: ctx.config.ssh.keyPath, knownHostsPath: ctx.config.ssh.knownHostsPath });
3034
+ }
3035
+ const { session, release } = await acquireSession(conn);
3036
+ try {
3037
+ const remote = await readRemoteConfig(session);
3038
+ const resolved = resolveSecrets(stored.overlay, stored.secrets);
3039
+ const merged = deepMerge(remote, resolved);
3040
+ await atomicWriteConfig(session, merged);
3041
+ await restartGateway(session);
3042
+ } finally {
3043
+ release();
3044
+ drainPool();
3045
+ }
3046
+ success(`Config overlay re-applied and gateway restarted on "${targetStack}".`);
3047
+ } catch (err) {
3048
+ failure(`Re-apply failed: ${err.message}`);
3049
+ info("The secret file has been updated. Re-run `clawops setup` or `clawops apply` to propagate.");
3050
+ }
3051
+ }
3052
+ });
3053
+ var auditCmd = defineCommand19({
3054
+ meta: { name: "audit", description: "Report missing secrets and unresolvable $secret: refs" },
3055
+ args: {
3056
+ json: { type: "boolean", description: "Emit JSON" }
3057
+ },
3058
+ async run({ args }) {
3059
+ const { listOverlays } = await import("./overlay-store-ADZPD7EU.js");
3060
+ const overlays = listOverlays();
3061
+ const issues = [];
3062
+ for (const name of listSecretNames()) {
3063
+ const p = secretPath(name);
3064
+ try {
3065
+ const content = readFileSync4(p, "utf-8").trim();
3066
+ if (content.length === 0) {
3067
+ issues.push({ kind: "empty-secret", secret: name, detail: `File exists but is empty: ${p}` });
3068
+ }
3069
+ } catch {
3070
+ issues.push({ kind: "unreadable-secret", secret: name, detail: `Cannot read: ${p}` });
3071
+ }
3072
+ }
3073
+ for (const overlay of overlays) {
3074
+ for (const s of overlay.secrets) {
3075
+ if (s.source === "file") {
3076
+ const ref = s.ref ?? secretPath(s.name);
3077
+ if (!existsSync3(ref)) {
3078
+ issues.push({ kind: "missing-file", stack: overlay.stackName, secret: s.name, detail: `File not found: ${ref}` });
3079
+ }
3080
+ } else if (s.source === "env") {
3081
+ const envVar = s.ref ?? s.name;
3082
+ if (!process21.env[envVar]) {
3083
+ issues.push({ kind: "missing-env", stack: overlay.stackName, secret: s.name, detail: `Env var not set: ${envVar}` });
3084
+ }
3085
+ } else {
3086
+ issues.push({ kind: "cloud-sm-unresolved", stack: overlay.stackName, secret: s.name, detail: `Source "${s.source}" is not auto-resolved \u2014 set manually` });
3087
+ }
3088
+ }
3089
+ }
3090
+ if (args.json) {
3091
+ process21.stdout.write(JSON.stringify({ issues, ok: issues.length === 0 }, null, 2) + "\n");
3092
+ return;
3093
+ }
3094
+ if (issues.length === 0) {
3095
+ success("All secrets are resolvable. No issues found.");
3096
+ return;
3097
+ }
3098
+ failure(`${issues.length} issue${issues.length === 1 ? "" : "s"} found:
3099
+ `);
3100
+ for (const issue of issues) {
3101
+ const prefix = issue.stack ? `[${issue.stack}] ` : "";
3102
+ warn(`${prefix}${issue.secret}: ${issue.detail}`);
3103
+ }
3104
+ process21.stdout.write("\n");
3105
+ info("Run `clawops secret set <name>` to update a missing or empty secret.");
3106
+ }
3107
+ });
3108
+ var secret_default = defineCommand19({
3109
+ meta: {
3110
+ name: "secret",
3111
+ description: "Manage clawops secrets (list | set | delete | rotate | audit)"
3112
+ },
3113
+ args: {},
3114
+ subCommands: {
3115
+ list: listCmd,
3116
+ set: setCmd,
3117
+ delete: deleteCmd,
3118
+ rotate: rotateCmd,
3119
+ audit: auditCmd
3120
+ }
3121
+ });
3122
+
3123
+ // src/cli/commands/help.ts
3124
+ import { defineCommand as defineCommand20 } from "citty";
2850
3125
  var COMMANDS = [
2851
3126
  ["setup", "Interactive first-run wizard \u2014 configure, deploy, and wire AI apps"],
2852
3127
  ["init", "Write config and generate SSH key pair (non-interactive)"],
@@ -2866,6 +3141,7 @@ var COMMANDS = [
2866
3141
  ["stacks", "List named stacks and their state"],
2867
3142
  ["doctor", "Check Node version, config, SSH key, provider credentials, and Pulumi home"],
2868
3143
  ["mcp", "MCP server operations (mcp serve | mcp install)"],
3144
+ ["secret", "Manage secrets (secret list | set | delete | rotate | audit)"],
2869
3145
  ["help", "Show this help message"]
2870
3146
  ];
2871
3147
  var GLOBAL_FLAGS = [
@@ -2876,7 +3152,7 @@ var GLOBAL_FLAGS = [
2876
3152
  ["--dry-run", "Preview without applying (mutating commands)"],
2877
3153
  ["--yes", "Skip interactive confirmations (CI mode)"]
2878
3154
  ];
2879
- var help_default = defineCommand19({
3155
+ var help_default = defineCommand20({
2880
3156
  meta: {
2881
3157
  name: "help",
2882
3158
  description: "Show available commands and global flags"
@@ -2916,25 +3192,25 @@ ${dim("Run `clawops <command> --help` for command-specific flags.")}
2916
3192
  });
2917
3193
 
2918
3194
  // src/cli/error-handler.ts
2919
- import process21 from "process";
3195
+ import process22 from "process";
2920
3196
  function handleError(err) {
2921
3197
  if (err instanceof ClawopsError) {
2922
3198
  failure(err.message);
2923
- process21.exit(err.exitCode);
3199
+ process22.exit(err.exitCode);
2924
3200
  }
2925
3201
  if (err instanceof Error) {
2926
3202
  failure(`Unexpected error: ${err.message}`);
2927
- if (process21.env["DEBUG"]) {
2928
- process21.stderr.write(err.stack ?? "\n");
3203
+ if (process22.env["DEBUG"]) {
3204
+ process22.stderr.write(err.stack ?? "\n");
2929
3205
  }
2930
- process21.exit(1);
3206
+ process22.exit(1);
2931
3207
  }
2932
3208
  failure(`Unknown error: ${String(err)}`);
2933
- process21.exit(1);
3209
+ process22.exit(1);
2934
3210
  }
2935
3211
 
2936
3212
  // src/cli/index.ts
2937
- var main = defineCommand20({
3213
+ var main = defineCommand21({
2938
3214
  meta: {
2939
3215
  name: "clawops",
2940
3216
  description: "Deploy and manage self-hosted OpenClaw instances across clouds",
@@ -2968,6 +3244,7 @@ var main = defineCommand20({
2968
3244
  doctor: doctor_default,
2969
3245
  mcp: mcp_default,
2970
3246
  setup: setup_default,
3247
+ secret: secret_default,
2971
3248
  help: help_default
2972
3249
  }
2973
3250
  });
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildContext
4
- } from "./chunk-ACYJBSLJ.js";
5
- import "./chunk-BRPU7AQC.js";
4
+ } from "./chunk-JDN6PLH2.js";
5
+ import "./chunk-A2I76FTA.js";
6
6
  import "./chunk-CX5SL5HP.js";
7
7
  import "./chunk-KGXPLI7W.js";
8
8
  export {
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  generatePlan,
4
4
  planId
5
- } from "./chunk-SSB6SGPQ.js";
5
+ } from "./chunk-ZDPBBYYV.js";
6
6
  import "./chunk-YTH4L2GN.js";
7
- import "./chunk-ACYJBSLJ.js";
8
- import "./chunk-BRPU7AQC.js";
7
+ import "./chunk-JDN6PLH2.js";
8
+ import "./chunk-A2I76FTA.js";
9
9
  import "./chunk-CX5SL5HP.js";
10
10
  import "./chunk-KGXPLI7W.js";
11
11
  export {
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ listOverlays,
4
+ loadOverlay,
5
+ saveOverlay
6
+ } from "./chunk-6ZFIFDBJ.js";
7
+ export {
8
+ listOverlays,
9
+ loadOverlay,
10
+ saveOverlay
11
+ };
@@ -2,7 +2,7 @@
2
2
 
3
3
  // package.json
4
4
  var name = "@clawops/cli";
5
- var version = "1.2.1";
5
+ var version = "1.3.0";
6
6
  var description = "Deploy and manage self-hosted OpenClaw instances across clouds";
7
7
  var type = "module";
8
8
  var bin = {
@@ -1,17 +1,18 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ extractBaseOutputs
4
+ } from "./chunk-3QJBNAHW.js";
2
5
  import {
3
6
  generatePlan
4
- } from "./chunk-SSB6SGPQ.js";
7
+ } from "./chunk-ZDPBBYYV.js";
5
8
  import {
6
9
  applyPlan
7
- } from "./chunk-LU63NZD3.js";
10
+ } from "./chunk-JCUZU5BH.js";
8
11
  import {
9
12
  validatePlan
10
13
  } from "./chunk-YTH4L2GN.js";
14
+ import "./chunk-6ZFIFDBJ.js";
11
15
  import "./chunk-BOPSG2LI.js";
12
- import {
13
- extractBaseOutputs
14
- } from "./chunk-3QJBNAHW.js";
15
16
  import {
16
17
  errText,
17
18
  handleConfigGet,
@@ -20,17 +21,17 @@ import {
20
21
  handleConfigValidate,
21
22
  okText,
22
23
  resolveConn
23
- } from "./chunk-QJ6ERXHN.js";
24
+ } from "./chunk-LVIIYY27.js";
24
25
  import {
25
26
  buildContext
26
- } from "./chunk-ACYJBSLJ.js";
27
- import "./chunk-BRPU7AQC.js";
27
+ } from "./chunk-JDN6PLH2.js";
28
28
  import {
29
29
  acquireSession,
30
30
  drainPool
31
31
  } from "./chunk-ZVOEQCNW.js";
32
32
  import "./chunk-4U3LTLWZ.js";
33
33
  import "./chunk-UDNZUSKA.js";
34
+ import "./chunk-A2I76FTA.js";
34
35
  import {
35
36
  getConfig,
36
37
  getConfigDir
@@ -613,7 +614,7 @@ async function handleUp(input, server) {
613
614
  );
614
615
  }
615
616
  const { localOpts } = stackConfig;
616
- const { localBootstrap } = await import("./bootstrap-UYCOEJQX.js");
617
+ const { localBootstrap } = await import("./bootstrap-G4UZ2FKH.js");
617
618
  const ac = new AbortController();
618
619
  const state = await localBootstrap({
619
620
  host: localOpts.host,
@@ -1231,7 +1232,7 @@ If none of the above resolves the issue:
1231
1232
 
1232
1233
  // src/mcp/server.ts
1233
1234
  async function serveMcp(opts) {
1234
- const { version } = await import("./package-SE3M4NJA.js");
1235
+ const { version } = await import("./package-AFUTH62F.js");
1235
1236
  const server = new McpServer({ name: "clawops", version });
1236
1237
  registerTools(server, opts);
1237
1238
  registerResources(server);
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ localStateToConnectionInfo,
4
+ readLocalState,
5
+ stateDir,
6
+ statePath,
7
+ writeLocalState
8
+ } from "./chunk-A2I76FTA.js";
9
+ import "./chunk-CX5SL5HP.js";
10
+ import "./chunk-KGXPLI7W.js";
11
+ export {
12
+ localStateToConnectionInfo,
13
+ readLocalState,
14
+ stateDir,
15
+ statePath,
16
+ writeLocalState
17
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawops/cli",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "Deploy and manage self-hosted OpenClaw instances across clouds",
5
5
  "type": "module",
6
6
  "bin": {