@clawops/cli 1.2.0 → 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/dist/cli.js CHANGED
@@ -1,12 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ MCP_APPS,
4
+ buildMcpEntry,
5
+ writeAppConfigs
6
+ } from "./chunk-OIGTOLB3.js";
2
7
  import {
3
8
  validateOpenclawConfig
4
- } from "./chunk-QJ6ERXHN.js";
5
- import "./chunk-ACYJBSLJ.js";
6
- import "./chunk-BRPU7AQC.js";
9
+ } from "./chunk-LVIIYY27.js";
10
+ import "./chunk-JDN6PLH2.js";
7
11
  import "./chunk-ZVOEQCNW.js";
8
12
  import "./chunk-4U3LTLWZ.js";
9
13
  import "./chunk-UDNZUSKA.js";
14
+ import "./chunk-A2I76FTA.js";
10
15
  import {
11
16
  getConfig,
12
17
  getConfigDir,
@@ -19,7 +24,7 @@ import {
19
24
  } from "./chunk-KGXPLI7W.js";
20
25
 
21
26
  // src/cli/index.ts
22
- import { defineCommand as defineCommand19, runMain } from "citty";
27
+ import { defineCommand as defineCommand21, runMain } from "citty";
23
28
 
24
29
  // src/cli/commands/init.ts
25
30
  import { defineCommand } from "citty";
@@ -212,7 +217,7 @@ var up_default = defineCommand2({
212
217
  config: { type: "string", description: "Path to openclaw config overlay JSON (local provider only)" }
213
218
  },
214
219
  async run({ args }) {
215
- const { buildContext } = await import("./context-VUAYRAJY.js");
220
+ const { buildContext } = await import("./context-M2RMDHX6.js");
216
221
  const ctx = buildContext(args);
217
222
  const openclawVersion = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : "stable";
218
223
  if (ctx.adapter.name === "local") {
@@ -223,7 +228,7 @@ var up_default = defineCommand2({
223
228
  );
224
229
  }
225
230
  const { localOpts } = stackConfig;
226
- const { localBootstrap } = await import("./bootstrap-UYCOEJQX.js");
231
+ const { localBootstrap } = await import("./bootstrap-G4UZ2FKH.js");
227
232
  const abortController = new AbortController();
228
233
  process3.on("SIGINT", () => abortController.abort());
229
234
  process3.on("SIGTERM", () => abortController.abort());
@@ -358,7 +363,7 @@ var down_default = defineCommand3({
358
363
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
359
364
  },
360
365
  async run({ args }) {
361
- const { buildContext } = await import("./context-VUAYRAJY.js");
366
+ const { buildContext } = await import("./context-M2RMDHX6.js");
362
367
  const ctx = buildContext(args);
363
368
  if (args["dry-run"]) {
364
369
  info(`Dry run \u2014 would destroy stack "${ctx.stackName}"`);
@@ -422,7 +427,7 @@ var status_default = defineCommand4({
422
427
  json: { type: "boolean", description: "Emit JSON" }
423
428
  },
424
429
  async run({ args }) {
425
- const { buildContext } = await import("./context-VUAYRAJY.js");
430
+ const { buildContext } = await import("./context-M2RMDHX6.js");
426
431
  const ctx = buildContext(args);
427
432
  if (ctx.adapter.name === "local") {
428
433
  const state = ctx.localState;
@@ -494,8 +499,8 @@ var plan_default = defineCommand5({
494
499
  out: { type: "string", description: "Write plan JSON to this absolute path (default: stdout)" }
495
500
  },
496
501
  async run({ args }) {
497
- const { buildContext } = await import("./context-VUAYRAJY.js");
498
- const { generatePlan } = await import("./generate-WG5VONSE.js");
502
+ const { buildContext } = await import("./context-M2RMDHX6.js");
503
+ const { generatePlan } = await import("./generate-U7PJ5LRG.js");
499
504
  const ctx = buildContext(args);
500
505
  if (ctx.adapter.name === "local") {
501
506
  throw new UsageError(
@@ -585,7 +590,7 @@ var apply_default = defineCommand6({
585
590
  },
586
591
  async run({ args }) {
587
592
  const { validatePlan } = await import("./validate-T5M5EHSJ.js");
588
- const { applyPlan } = await import("./apply-KBF7OPWL.js");
593
+ const { applyPlan } = await import("./apply-DLNE2JYZ.js");
589
594
  const planPath = args._?.[0];
590
595
  if (!planPath) {
591
596
  throw new UsageError("Usage: clawops apply <plan.json>");
@@ -701,7 +706,7 @@ var destroy_default = defineCommand7({
701
706
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
702
707
  },
703
708
  async run({ args }) {
704
- const { buildContext } = await import("./context-VUAYRAJY.js");
709
+ const { buildContext } = await import("./context-M2RMDHX6.js");
705
710
  const ctx = buildContext(args);
706
711
  if (ctx.adapter.name === "local") {
707
712
  throw new UsageError(
@@ -768,7 +773,7 @@ var ssh_default = defineCommand8({
768
773
  command: { type: "string", description: "Remote command to run (instead of interactive shell)" }
769
774
  },
770
775
  async run({ args }) {
771
- const { buildContext } = await import("./context-VUAYRAJY.js");
776
+ const { buildContext } = await import("./context-M2RMDHX6.js");
772
777
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
773
778
  const ctx = buildContext(args);
774
779
  let conn;
@@ -855,7 +860,7 @@ var tunnel_default = defineCommand9({
855
860
  "no-open": { type: "boolean", description: "Do not open browser after tunnel is ready" }
856
861
  },
857
862
  async run({ args }) {
858
- const { buildContext } = await import("./context-VUAYRAJY.js");
863
+ const { buildContext } = await import("./context-M2RMDHX6.js");
859
864
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
860
865
  const { acquireSession } = await import("./pool-FBFHATDG.js");
861
866
  const ctx = buildContext(args);
@@ -923,7 +928,7 @@ var logs_default = defineCommand10({
923
928
  since: { type: "string", description: "Show logs since duration (e.g. 5m, 1h)" }
924
929
  },
925
930
  async run({ args }) {
926
- const { buildContext } = await import("./context-VUAYRAJY.js");
931
+ const { buildContext } = await import("./context-M2RMDHX6.js");
927
932
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
928
933
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
929
934
  const ctx = buildContext(args);
@@ -1040,7 +1045,7 @@ var config_default = defineCommand11({
1040
1045
  "dry-run": { type: "boolean", description: "Show what would change without writing" }
1041
1046
  },
1042
1047
  async run({ args }) {
1043
- const { buildContext } = await import("./context-VUAYRAJY.js");
1048
+ const { buildContext } = await import("./context-M2RMDHX6.js");
1044
1049
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1045
1050
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1046
1051
  const [action, key, value] = args._ ?? [];
@@ -1166,7 +1171,7 @@ var agents_default = defineCommand12({
1166
1171
  json: { type: "boolean", description: "Emit JSON (for list)" }
1167
1172
  },
1168
1173
  async run({ args }) {
1169
- const { buildContext } = await import("./context-VUAYRAJY.js");
1174
+ const { buildContext } = await import("./context-M2RMDHX6.js");
1170
1175
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1171
1176
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1172
1177
  const [action, name] = args._ ?? [];
@@ -1270,7 +1275,7 @@ var gateway_default = defineCommand13({
1270
1275
  json: { type: "boolean", description: "Emit JSON (for status)" }
1271
1276
  },
1272
1277
  async run({ args }) {
1273
- const { buildContext } = await import("./context-VUAYRAJY.js");
1278
+ const { buildContext } = await import("./context-M2RMDHX6.js");
1274
1279
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1275
1280
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1276
1281
  const [action, versionArg] = args._ ?? [];
@@ -1383,7 +1388,7 @@ var backup_default = defineCommand14({
1383
1388
  yes: { type: "boolean", description: "[restore] Skip confirmation prompt" }
1384
1389
  },
1385
1390
  async run({ args }) {
1386
- const { buildContext } = await import("./context-VUAYRAJY.js");
1391
+ const { buildContext } = await import("./context-M2RMDHX6.js");
1387
1392
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1388
1393
  const action = args.action;
1389
1394
  if (action !== "create" && action !== "restore") {
@@ -1689,7 +1694,7 @@ var doctor_default = defineCommand16({
1689
1694
  info("Remote health skipped \u2014 no config");
1690
1695
  } else {
1691
1696
  try {
1692
- const { buildContext } = await import("./context-VUAYRAJY.js");
1697
+ const { buildContext } = await import("./context-M2RMDHX6.js");
1693
1698
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1694
1699
  const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
1695
1700
  const ctx = buildContext({ stack: args.stack });
@@ -1782,13 +1787,11 @@ var doctor_default = defineCommand16({
1782
1787
 
1783
1788
  // src/cli/commands/mcp.ts
1784
1789
  import { defineCommand as defineCommand17 } from "citty";
1785
- import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
1786
- import path3 from "path";
1787
- import os from "os";
1788
- var mcp_default = defineCommand17({
1790
+ import process19 from "process";
1791
+ var serveCmd = defineCommand17({
1789
1792
  meta: {
1790
- name: "mcp",
1791
- description: "MCP server operations (serve | install)"
1793
+ name: "serve",
1794
+ description: "Start the clawops MCP server"
1792
1795
  },
1793
1796
  args: {
1794
1797
  http: { type: "string", description: "HTTP port for standalone mode" },
@@ -1796,25 +1799,10 @@ var mcp_default = defineCommand17({
1796
1799
  "read-only": { type: "boolean", description: "Only register read toolset" },
1797
1800
  "no-destructive": { type: "boolean", description: "Filter out destructive tools" },
1798
1801
  toolsets: { type: "string", description: "Comma-separated toolsets to enable" },
1799
- inspector: { type: "boolean", description: "Enable MCP inspector" },
1800
- claude: { type: "boolean", description: "Install for Claude Desktop" },
1801
- cursor: { type: "boolean", description: "Install for Cursor" },
1802
- vscode: { type: "boolean", description: "Install for VS Code" },
1803
- windsurf: { type: "boolean", description: "Install for Windsurf" },
1804
- zed: { type: "boolean", description: "Install for Zed" }
1802
+ inspector: { type: "boolean", description: "Enable MCP inspector" }
1805
1803
  },
1806
1804
  async run({ args }) {
1807
- const installFlags = ["claude", "cursor", "vscode", "windsurf", "zed"];
1808
- const requestedClients = installFlags.filter((f) => Boolean(args[f]));
1809
- if (requestedClients.length > 0) {
1810
- for (const client of requestedClients) {
1811
- installMcp(client);
1812
- process.stderr.write(`Installed MCP config for: ${client}
1813
- `);
1814
- }
1815
- return;
1816
- }
1817
- const { serveMcp } = await import("./server-QJRWK2RV.js");
1805
+ const { serveMcp } = await import("./server-I62UES5V.js");
1818
1806
  await serveMcp({
1819
1807
  port: args.http ? Number(args.http) : void 0,
1820
1808
  bind: args.bind,
@@ -1825,60 +1813,80 @@ var mcp_default = defineCommand17({
1825
1813
  });
1826
1814
  }
1827
1815
  });
1828
- var CLAWOPS_ENTRY = {
1829
- command: "clawops",
1830
- args: ["mcp", "serve"],
1831
- type: "stdio"
1832
- };
1833
- function getConfigPath(client) {
1834
- const home = os.homedir();
1835
- const isLinux = process.platform === "linux";
1836
- switch (client) {
1837
- case "claude":
1838
- return isLinux ? path3.join(home, ".config", "Claude", "claude_desktop_config.json") : path3.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
1839
- case "cursor":
1840
- return path3.join(home, ".cursor", "mcp.json");
1841
- case "vscode":
1842
- return isLinux ? path3.join(home, ".config", "Code", "User", "mcp.json") : path3.join(home, "Library", "Application Support", "Code", "User", "mcp.json");
1843
- case "windsurf":
1844
- return path3.join(home, ".codeium", "windsurf", "mcp_config.json");
1845
- case "zed":
1846
- return path3.join(home, ".config", "zed", "settings.json");
1847
- }
1848
- }
1849
- function installMcp(client) {
1850
- const configPath = getConfigPath(client);
1851
- const dir = path3.dirname(configPath);
1852
- if (!existsSync2(dir)) {
1853
- mkdirSync3(dir, { recursive: true });
1854
- }
1855
- let config = {};
1856
- if (existsSync2(configPath)) {
1857
- try {
1858
- config = JSON.parse(readFileSync3(configPath, "utf-8"));
1859
- } catch {
1816
+ var installCmd = defineCommand17({
1817
+ meta: {
1818
+ name: "install",
1819
+ description: "Interactively wire clawops into AI editors (Claude Desktop, Claude Code, Cursor, \u2026)"
1820
+ },
1821
+ args: {},
1822
+ async run() {
1823
+ const inquirer = (await import("inquirer")).default;
1824
+ const { MCP_APPS: MCP_APPS2, buildMcpEntry: buildMcpEntry2, writeAppConfigs: writeAppConfigs2 } = await import("./mcp-apps-KY44ED3Y.js");
1825
+ const mcpEntry = buildMcpEntry2();
1826
+ if (!mcpEntry.resolved) {
1827
+ info("clawops is not installed globally \u2014 AI apps may not be able to start the MCP server.");
1828
+ info("Install it first: npm install -g @clawops/cli");
1829
+ info('The config will still be written now using "clawops" as the command name.\n');
1830
+ }
1831
+ info("Use \u2191\u2193 to move, Space to select/deselect, Enter to confirm.");
1832
+ const choices = MCP_APPS2.map((app) => ({
1833
+ name: app.isInstalled() ? app.name : `${app.name} (not detected \u2014 config will be written anyway)`,
1834
+ value: app.id,
1835
+ checked: app.isInstalled()
1836
+ }));
1837
+ const { selectedIds } = await inquirer.prompt([{
1838
+ type: "checkbox",
1839
+ name: "selectedIds",
1840
+ message: "Which AI editors should have access to clawops?",
1841
+ choices,
1842
+ pageSize: MCP_APPS2.length + 1
1843
+ }]);
1844
+ const selected = MCP_APPS2.filter((app) => selectedIds.includes(app.id));
1845
+ if (selected.length === 0) {
1846
+ info("No apps selected. Add this to an app's MCP config manually:");
1847
+ process19.stdout.write(JSON.stringify({
1848
+ mcpServers: { clawops: { command: mcpEntry.command, args: mcpEntry.args } }
1849
+ }, null, 2) + "\n\n");
1850
+ info("Config file locations:");
1851
+ for (const app of MCP_APPS2) {
1852
+ info(` ${app.name.padEnd(18)} ${app.configPath()}`);
1853
+ }
1854
+ return;
1855
+ }
1856
+ const results = writeAppConfigs2(selected, { command: mcpEntry.command, args: mcpEntry.args });
1857
+ for (const r of results) {
1858
+ if (r.ok) {
1859
+ success(`${r.app.name} configured (${r.configPath})`);
1860
+ } else {
1861
+ failure(`Could not configure ${r.app.name}: ${r.error ?? "unknown error"}`);
1862
+ }
1863
+ }
1864
+ const needsRestart = selected.filter((app) => app.id !== "claude-code");
1865
+ if (needsRestart.length > 0) {
1866
+ info(`Restart ${needsRestart.map((a) => a.name).join(", ")} to load the clawops tool.`);
1860
1867
  }
1861
1868
  }
1862
- if (client === "zed") {
1863
- const contextServers = config["context_servers"] ?? {};
1864
- contextServers["clawops"] = CLAWOPS_ENTRY;
1865
- config["context_servers"] = contextServers;
1866
- } else {
1867
- const mcpServers = config["mcpServers"] ?? {};
1868
- mcpServers["clawops"] = CLAWOPS_ENTRY;
1869
- config["mcpServers"] = mcpServers;
1869
+ });
1870
+ var mcp_default = defineCommand17({
1871
+ meta: {
1872
+ name: "mcp",
1873
+ description: "MCP server operations"
1874
+ },
1875
+ args: {},
1876
+ subCommands: {
1877
+ serve: serveCmd,
1878
+ install: installCmd
1870
1879
  }
1871
- writeFileSync3(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
1872
- }
1880
+ });
1873
1881
 
1874
1882
  // src/cli/commands/setup.ts
1875
1883
  import { defineCommand as defineCommand18 } from "citty";
1876
- import process19 from "process";
1877
- import os2 from "os";
1884
+ import process20 from "process";
1885
+ import os from "os";
1878
1886
  import { randomBytes } from "crypto";
1879
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync3, mkdirSync as mkdirSync4 } from "fs";
1887
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync2, mkdirSync as mkdirSync3 } from "fs";
1880
1888
  import { execSync, spawnSync, spawn as spawn2 } from "child_process";
1881
- import path4 from "path";
1889
+ import path3 from "path";
1882
1890
  import { fileURLToPath } from "url";
1883
1891
  var setup_default = defineCommand18({
1884
1892
  meta: {
@@ -1895,16 +1903,16 @@ var setup_default = defineCommand18({
1895
1903
  const catalogs = loadCatalogs(yaml);
1896
1904
  const dryRun = Boolean(args["dry-run"]);
1897
1905
  const ac = new AbortController();
1898
- process19.on("SIGINT", () => {
1906
+ process20.on("SIGINT", () => {
1899
1907
  ac.abort();
1900
- process19.exit(130);
1908
+ process20.exit(130);
1901
1909
  });
1902
- process19.on("SIGTERM", () => {
1910
+ process20.on("SIGTERM", () => {
1903
1911
  ac.abort();
1904
- process19.exit(143);
1912
+ process20.exit(143);
1905
1913
  });
1906
- process19.stdout.write("\nWelcome to clawops setup. This wizard will help you deploy OpenClaw\n");
1907
- process19.stdout.write("and connect it to your AI tools. It takes about 2 minutes.\n\n");
1914
+ process20.stdout.write("\nWelcome to clawops setup. This wizard will help you deploy OpenClaw\n");
1915
+ process20.stdout.write("and connect it to your AI tools. It takes about 2 minutes.\n\n");
1908
1916
  const { deploymentType } = await inquirer.prompt([{
1909
1917
  type: "list",
1910
1918
  name: "deploymentType",
@@ -1916,7 +1924,7 @@ var setup_default = defineCommand18({
1916
1924
  }]);
1917
1925
  let provider;
1918
1926
  let localHost = "";
1919
- let localUser = os2.userInfo().username;
1927
+ let localUser = os.userInfo().username;
1920
1928
  let localKeyPath = detectSshKey();
1921
1929
  let localPort = 22;
1922
1930
  let localSudoPassword = "";
@@ -1932,12 +1940,12 @@ var setup_default = defineCommand18({
1932
1940
  ]
1933
1941
  }]);
1934
1942
  provider = answer.provider;
1935
- process19.stdout.write("\n");
1943
+ process20.stdout.write("\n");
1936
1944
  await ensureCloudAuth(provider, inquirer);
1937
1945
  } else {
1938
1946
  provider = "local";
1939
1947
  info("We'll connect to your server over SSH to install and configure OpenClaw.\n");
1940
- if (!existsSync3(localKeyPath)) {
1948
+ if (!existsSync2(localKeyPath)) {
1941
1949
  info(`No SSH key found at ${localKeyPath}.`);
1942
1950
  const { genKey } = await inquirer.prompt([{
1943
1951
  type: "confirm",
@@ -1973,7 +1981,7 @@ var setup_default = defineCommand18({
1973
1981
  name: "keyPath",
1974
1982
  message: `SSH private key file: (the key file on your computer used to log in \u2014 e.g. ~/.ssh/id_ed25519)`,
1975
1983
  default: localKeyPath,
1976
- validate: (v) => existsSync3(v.trim()) || `File not found: ${v.trim()}`
1984
+ validate: (v) => existsSync2(v.trim()) || `File not found: ${v.trim()}`
1977
1985
  }
1978
1986
  ]);
1979
1987
  localHost = localAnswers.host;
@@ -1988,7 +1996,7 @@ var setup_default = defineCommand18({
1988
1996
  }]);
1989
1997
  localSudoPassword = sudoPass;
1990
1998
  }
1991
- process19.stdout.write("\n");
1999
+ process20.stdout.write("\n");
1992
2000
  const stackAnswers = await inquirer.prompt([
1993
2001
  {
1994
2002
  type: "input",
@@ -2036,7 +2044,7 @@ var setup_default = defineCommand18({
2036
2044
  default: "latest"
2037
2045
  }
2038
2046
  ]);
2039
- process19.stdout.write("\n");
2047
+ process20.stdout.write("\n");
2040
2048
  info("Choose the AI model that your OpenClaw agent will use.");
2041
2049
  info("You'll need an API key from your chosen provider (except Bedrock and Ollama).\n");
2042
2050
  const { modelProviderId } = await inquirer.prompt([{
@@ -2065,7 +2073,7 @@ var setup_default = defineCommand18({
2065
2073
  secrets.push(entry);
2066
2074
  modelConfig["apiKey"] = `$secret:${secretName}`;
2067
2075
  } else if (modelProvider.credentialSource === "aws-profile") {
2068
- process19.stdout.write("\n");
2076
+ process20.stdout.write("\n");
2069
2077
  info(modelProvider.iamNote ?? "Bedrock uses your AWS server's IAM role \u2014 no API key needed.");
2070
2078
  info("Make sure your EC2 instance has the AmazonBedrockFullAccess IAM policy attached.\n");
2071
2079
  } else if (modelProvider.id === "ollama") {
@@ -2082,7 +2090,7 @@ var setup_default = defineCommand18({
2082
2090
  ...selectedModel.modelId ? { modelId: selectedModel.modelId } : { model: selectedModel.id },
2083
2091
  ...modelConfig
2084
2092
  };
2085
- process19.stdout.write("\n");
2093
+ process20.stdout.write("\n");
2086
2094
  info("Use \u2191\u2193 to move, Space to select/deselect, Enter to confirm.");
2087
2095
  const { selectedIntegrationIds } = await inquirer.prompt([{
2088
2096
  type: "checkbox",
@@ -2098,7 +2106,7 @@ var setup_default = defineCommand18({
2098
2106
  const channelsConfig = {};
2099
2107
  const infraRequired = [];
2100
2108
  for (const integ of catalogs.integrations.filter((i) => selectedIntegrationIds.includes(i.id))) {
2101
- process19.stdout.write("\n");
2109
+ process20.stdout.write("\n");
2102
2110
  info(`Setting up ${integ.displayName}:`);
2103
2111
  const channelConfig = {};
2104
2112
  for (const field of integ.fields) {
@@ -2134,11 +2142,11 @@ var setup_default = defineCommand18({
2134
2142
  }
2135
2143
  }
2136
2144
  const gatewayToken = randomBytes(24).toString("hex");
2137
- const secretsDir = path4.join(os2.homedir(), ".clawops", "secrets");
2138
- mkdirSync4(secretsDir, { recursive: true });
2139
- spawnSync("chmod", ["700", secretsDir], { stdio: "ignore" });
2140
- const tokenPath = path4.join(secretsDir, `GATEWAY_TOKEN_${stackAnswers.stackName}`);
2141
- writeFileSync4(tokenPath, gatewayToken, { encoding: "utf-8", mode: 384 });
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}`);
2149
+ writeFileSync3(tokenPath, gatewayToken, { encoding: "utf-8", mode: 384 });
2142
2150
  const openclawConfigOverlay = {
2143
2151
  models: builtModelConfig,
2144
2152
  gateway: { auth: { mode: "token", token: gatewayToken } }
@@ -2148,17 +2156,17 @@ var setup_default = defineCommand18({
2148
2156
  }
2149
2157
  let outputPath;
2150
2158
  if (provider === "local") {
2151
- outputPath = path4.join(outDir, `openclaw-${stackAnswers.stackName}.json`);
2152
- writeFileSync4(outputPath, JSON.stringify(openclawConfigOverlay, null, 2), "utf-8");
2153
- process19.stdout.write("\n");
2159
+ outputPath = path3.join(outDir, `openclaw-${stackAnswers.stackName}.json`);
2160
+ writeFileSync3(outputPath, JSON.stringify(openclawConfigOverlay, null, 2), "utf-8");
2161
+ process20.stdout.write("\n");
2154
2162
  success(`Config file saved to ${outputPath}`);
2155
2163
  } else {
2156
2164
  let sshPublicKey = "";
2157
2165
  try {
2158
- sshPublicKey = readFileSync4(stackAnswers.sshKeyPath ?? "", "utf-8").trim();
2166
+ sshPublicKey = readFileSync3(stackAnswers.sshKeyPath ?? "", "utf-8").trim();
2159
2167
  } catch {
2160
2168
  failure(`Cannot read SSH public key at ${stackAnswers.sshKeyPath ?? ""}`);
2161
- process19.exit(1);
2169
+ process20.exit(1);
2162
2170
  }
2163
2171
  const plan = {
2164
2172
  apiVersion: "clawops.dev/v1",
@@ -2187,12 +2195,12 @@ var setup_default = defineCommand18({
2187
2195
  ...stackAnswers.stateBucket ? { stateBucket: stackAnswers.stateBucket } : {}
2188
2196
  }
2189
2197
  };
2190
- outputPath = path4.join(outDir, `clawops-${stackAnswers.stackName}-plan.json`);
2191
- writeFileSync4(outputPath, JSON.stringify(plan, null, 2), "utf-8");
2192
- process19.stdout.write("\n");
2198
+ outputPath = path3.join(outDir, `clawops-${stackAnswers.stackName}-plan.json`);
2199
+ writeFileSync3(outputPath, JSON.stringify(plan, null, 2), "utf-8");
2200
+ process20.stdout.write("\n");
2193
2201
  success(`Deployment plan saved to ${outputPath}`);
2194
2202
  }
2195
- process19.stdout.write("\n");
2203
+ process20.stdout.write("\n");
2196
2204
  const mcpEntry = buildMcpEntry();
2197
2205
  if (!mcpEntry.resolved) {
2198
2206
  info("Note: clawops is not installed globally \u2014 AI apps will not be able to start");
@@ -2215,21 +2223,12 @@ var setup_default = defineCommand18({
2215
2223
  }]);
2216
2224
  const selectedApps = MCP_APPS.filter((app) => selectedAppIds.includes(app.id));
2217
2225
  if (selectedApps.length > 0) {
2218
- for (const app of selectedApps) {
2219
- const cfgPath = app.configPath();
2220
- try {
2221
- let existing = {};
2222
- if (existsSync3(cfgPath)) {
2223
- existing = JSON.parse(readFileSync4(cfgPath, "utf-8"));
2224
- }
2225
- const mcpServers = existing["mcpServers"] ?? {};
2226
- mcpServers["clawops"] = { command: mcpEntry.command, args: mcpEntry.args };
2227
- existing["mcpServers"] = mcpServers;
2228
- mkdirSync4(path4.dirname(cfgPath), { recursive: true });
2229
- writeFileSync4(cfgPath, JSON.stringify(existing, null, 2), "utf-8");
2230
- success(`${app.name} configured (${cfgPath})`);
2231
- } catch (err) {
2232
- failure(`Could not configure ${app.name}: ${err.message}`);
2226
+ const results = writeAppConfigs(selectedApps, { command: mcpEntry.command, args: mcpEntry.args });
2227
+ for (const r of results) {
2228
+ if (r.ok) {
2229
+ success(`${r.app.name} configured (${r.configPath})`);
2230
+ } else {
2231
+ failure(`Could not configure ${r.app.name}: ${r.error ?? "unknown error"}`);
2233
2232
  info("Add this to its MCP config manually:");
2234
2233
  printMcpSnippet();
2235
2234
  }
@@ -2247,7 +2246,7 @@ var setup_default = defineCommand18({
2247
2246
  }
2248
2247
  }
2249
2248
  if (infraRequired.length > 0) {
2250
- process19.stdout.write("\n");
2249
+ process20.stdout.write("\n");
2251
2250
  info("\u2500\u2500 Action required: webhook registration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
2252
2251
  info("These integrations need a webhook URL set up in their developer portal:");
2253
2252
  for (const integ of infraRequired) {
@@ -2258,7 +2257,7 @@ var setup_default = defineCommand18({
2258
2257
  }
2259
2258
  }
2260
2259
  if (modelProvider.id === "ollama" && selectedModel.pullSuffix !== void 0) {
2261
- process19.stdout.write("\n");
2260
+ process20.stdout.write("\n");
2262
2261
  info("\u2500\u2500 Action required: download the Ollama model \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
2263
2262
  info("After deployment, run this command on the server (or locally if Ollama is local):");
2264
2263
  info(` ollama pull ${selectedModel.id}${selectedModel.pullSuffix}`);
@@ -2288,8 +2287,8 @@ var setup_default = defineCommand18({
2288
2287
  });
2289
2288
  } else {
2290
2289
  const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
2291
- const knownHostsPath = path4.join(getConfigDir2(), "known_hosts");
2292
- process19.stdout.write("\n");
2290
+ const knownHostsPath = path3.join(getConfigDir2(), "known_hosts");
2291
+ process20.stdout.write("\n");
2293
2292
  info("To deploy later, run these two commands:");
2294
2293
  info(` clawops init --provider local --host ${localHost} --port ${localPort} --user ${localUser} --key ${localKeyPath} --stack ${stackAnswers.stackName}`);
2295
2294
  info(` clawops up --stack ${stackAnswers.stackName} --config ${outputPath}`);
@@ -2304,18 +2303,18 @@ var setup_default = defineCommand18({
2304
2303
  default: false
2305
2304
  }]);
2306
2305
  if (applyNow) {
2307
- const { applyPlan } = await import("./apply-KBF7OPWL.js");
2308
- const plan = JSON.parse(readFileSync4(outputPath, "utf-8"));
2306
+ const { applyPlan } = await import("./apply-DLNE2JYZ.js");
2307
+ const plan = JSON.parse(readFileSync3(outputPath, "utf-8"));
2309
2308
  try {
2310
2309
  await applyPlan(plan, {
2311
- onOutput: (line) => process19.stdout.write(line),
2310
+ onOutput: (line) => process20.stdout.write(line),
2312
2311
  signal: ac.signal
2313
2312
  });
2314
2313
  success("Server provisioned and OpenClaw installed.");
2315
2314
  info(`Run: clawops doctor --stack ${stackAnswers.stackName} to verify everything is healthy`);
2316
2315
  } catch (err) {
2317
2316
  failure(err instanceof Error ? err.message : String(err));
2318
- process19.exit(1);
2317
+ process20.exit(1);
2319
2318
  }
2320
2319
  } else {
2321
2320
  info(`
@@ -2326,11 +2325,11 @@ To deploy later: clawops apply ${outputPath}`);
2326
2325
  });
2327
2326
  async function runLocalDeploy(opts) {
2328
2327
  const { setConfig: setConfig2, getConfig: getConfig2, getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
2329
- const { localBootstrap } = await import("./bootstrap-UYCOEJQX.js");
2328
+ const { localBootstrap } = await import("./bootstrap-G4UZ2FKH.js");
2330
2329
  const { connect } = await import("./ssh-IQXNME3D.js");
2331
2330
  const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-JQ77SRC2.js");
2332
2331
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
2333
- const knownHostsPath = path4.join(getConfigDir2(), "known_hosts");
2332
+ const knownHostsPath = path3.join(getConfigDir2(), "known_hosts");
2334
2333
  const existing = getConfig2();
2335
2334
  const stackEntry = {
2336
2335
  provider: "local",
@@ -2399,6 +2398,8 @@ async function runLocalDeploy(opts) {
2399
2398
  const merged = deepMerge(remote, resolved);
2400
2399
  await atomicWriteConfig(session, merged, opts.signal);
2401
2400
  await restartGateway(session, opts.signal);
2401
+ const { saveOverlay } = await import("./overlay-store-ADZPD7EU.js");
2402
+ saveOverlay(opts.stackName, opts.overlay, opts.secrets);
2402
2403
  } finally {
2403
2404
  session.close();
2404
2405
  }
@@ -2409,9 +2410,9 @@ async function runLocalDeploy(opts) {
2409
2410
  }
2410
2411
  const { drainPool } = await import("./pool-FBFHATDG.js");
2411
2412
  drainPool();
2412
- const tokenPath = path4.join(os2.homedir(), ".clawops", "secrets", `GATEWAY_TOKEN_${opts.stackName}`);
2413
+ const tokenPath = path3.join(os.homedir(), ".clawops", "secrets", `GATEWAY_TOKEN_${opts.stackName}`);
2413
2414
  const dashboardUrl = `${state.gatewayUrl}?token=${opts.gatewayToken}`;
2414
- process19.stdout.write("\n");
2415
+ process20.stdout.write("\n");
2415
2416
  success("All done! OpenClaw is running.");
2416
2417
  info(`Open dashboard: ${dashboardUrl}`);
2417
2418
  info(` (or enter the token manually in the "Gateway Token" field: ${opts.gatewayToken})`);
@@ -2465,7 +2466,7 @@ ${cfg.loginHint}`);
2465
2466
  default: true
2466
2467
  }]);
2467
2468
  if (runNow) {
2468
- process19.stdout.write("\n");
2469
+ process20.stdout.write("\n");
2469
2470
  const result = spawnSync(cfg.loginCmd[0], cfg.loginCmd.slice(1), { stdio: "inherit" });
2470
2471
  if (result.error) {
2471
2472
  failure(`Could not launch ${cliName} \u2014 is it installed? (${result.error.message})`);
@@ -2475,10 +2476,10 @@ ${cfg.loginHint}`);
2475
2476
  }
2476
2477
  try {
2477
2478
  const identity = cfg.check();
2478
- process19.stdout.write("\n");
2479
+ process20.stdout.write("\n");
2479
2480
  success(`Authenticated: ${cfg.identity(identity)}`);
2480
2481
  } catch {
2481
- process19.stdout.write("\n");
2482
+ process20.stdout.write("\n");
2482
2483
  failure(`Still not authenticated \u2014 the sign-in may have been cancelled or failed.`);
2483
2484
  info("Re-run `clawops setup` after signing in, or continue and authenticate before running `clawops up`.\n");
2484
2485
  }
@@ -2492,7 +2493,7 @@ function cliInstallUrl(provider) {
2492
2493
  return "https://learn.microsoft.com/en-us/cli/azure/install-azure-cli";
2493
2494
  }
2494
2495
  async function promptSecret(name, label, envDefault, inquirer) {
2495
- const secretsDir = path4.join(os2.homedir(), ".clawops", "secrets");
2496
+ const secretsDir2 = path3.join(os.homedir(), ".clawops", "secrets");
2496
2497
  const { secretSource } = await inquirer.prompt([{
2497
2498
  type: "list",
2498
2499
  name: "secretSource",
@@ -2510,12 +2511,12 @@ async function promptSecret(name, label, envDefault, inquirer) {
2510
2511
  message: `Paste your ${label}: (input is hidden)`,
2511
2512
  validate: (v) => v.trim() !== "" || "Value cannot be empty"
2512
2513
  }]);
2513
- mkdirSync4(secretsDir, { recursive: true });
2514
- spawnSync("chmod", ["700", secretsDir], { stdio: "ignore" });
2515
- const secretPath = path4.join(secretsDir, name);
2516
- writeFileSync4(secretPath, secretValue.trim(), { encoding: "utf-8", mode: 384 });
2517
- success(`Secret saved to ${secretPath} (chmod 600)`);
2518
- 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 };
2519
2520
  }
2520
2521
  if (secretSource === "env") {
2521
2522
  const { envVar } = await inquirer.prompt([{
@@ -2530,7 +2531,7 @@ async function promptSecret(name, label, envDefault, inquirer) {
2530
2531
  type: "input",
2531
2532
  name: "filePath",
2532
2533
  message: `File path: (full path to the file containing your ${label})`,
2533
- validate: (v) => existsSync3(v.trim()) || `File not found: ${v.trim()}`
2534
+ validate: (v) => existsSync2(v.trim()) || `File not found: ${v.trim()}`
2534
2535
  }]);
2535
2536
  return { name, source: "file", ref: filePath };
2536
2537
  }
@@ -2663,7 +2664,7 @@ async function pollUntilDockerReady(label) {
2663
2664
  throw new Error("Docker daemon did not become ready within timeout.");
2664
2665
  }
2665
2666
  async function startDockerAndWait(inquirer) {
2666
- const isLinux = process19.platform === "linux";
2667
+ const isLinux = process20.platform === "linux";
2667
2668
  const { choice } = await inquirer.prompt([{
2668
2669
  type: "list",
2669
2670
  name: "choice",
@@ -2727,12 +2728,12 @@ async function startDockerAndWait(inquirer) {
2727
2728
  var LOCALHOST_ALIASES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
2728
2729
  async function ensureAuthorizedKey(keyPath, host, inquirer) {
2729
2730
  const pubKeyPath = `${keyPath}.pub`;
2730
- if (!existsSync3(pubKeyPath)) return;
2731
- const pubKey = readFileSync4(pubKeyPath, "utf-8").trim();
2731
+ if (!existsSync2(pubKeyPath)) return;
2732
+ const pubKey = readFileSync3(pubKeyPath, "utf-8").trim();
2732
2733
  const isLocal = LOCALHOST_ALIASES.has(host.toLowerCase());
2733
- const authorizedKeysPath = path4.join(os2.homedir(), ".ssh", "authorized_keys");
2734
+ const authorizedKeysPath = path3.join(os.homedir(), ".ssh", "authorized_keys");
2734
2735
  if (isLocal) {
2735
- const existing = existsSync3(authorizedKeysPath) ? readFileSync4(authorizedKeysPath, "utf-8") : "";
2736
+ const existing = existsSync2(authorizedKeysPath) ? readFileSync3(authorizedKeysPath, "utf-8") : "";
2736
2737
  if (existing.includes(pubKey)) {
2737
2738
  success("SSH public key is already in authorized_keys.");
2738
2739
  return;
@@ -2745,9 +2746,9 @@ async function ensureAuthorizedKey(keyPath, host, inquirer) {
2745
2746
  default: true
2746
2747
  }]);
2747
2748
  if (addKey) {
2748
- mkdirSync4(path4.join(os2.homedir(), ".ssh"), { recursive: true });
2749
+ mkdirSync3(path3.join(os.homedir(), ".ssh"), { recursive: true });
2749
2750
  const entry = existing.endsWith("\n") || existing === "" ? pubKey + "\n" : "\n" + pubKey + "\n";
2750
- writeFileSync4(authorizedKeysPath, existing + entry, { encoding: "utf-8", flag: "a" });
2751
+ writeFileSync3(authorizedKeysPath, existing + entry, { encoding: "utf-8", flag: "a" });
2751
2752
  spawnSync("chmod", ["600", authorizedKeysPath], { stdio: "ignore" });
2752
2753
  success("Public key added to ~/.ssh/authorized_keys.");
2753
2754
  } else {
@@ -2755,15 +2756,15 @@ async function ensureAuthorizedKey(keyPath, host, inquirer) {
2755
2756
  info(` ${pubKey}`);
2756
2757
  }
2757
2758
  } else {
2758
- process19.stdout.write("\n");
2759
+ process20.stdout.write("\n");
2759
2760
  info("Make sure this public key is in ~/.ssh/authorized_keys on your server:");
2760
2761
  info(` ${pubKey}`);
2761
2762
  info("(If you just created the server, paste the line above into the server's ~/.ssh/authorized_keys file.)\n");
2762
2763
  }
2763
2764
  }
2764
2765
  async function generateSshKey() {
2765
- const keyPath = path4.join(os2.homedir(), ".ssh", "id_ed25519");
2766
- mkdirSync4(path4.join(os2.homedir(), ".ssh"), { recursive: true });
2766
+ const keyPath = path3.join(os.homedir(), ".ssh", "id_ed25519");
2767
+ mkdirSync3(path3.join(os.homedir(), ".ssh"), { recursive: true });
2767
2768
  const result = spawnSync("ssh-keygen", ["-t", "ed25519", "-f", keyPath, "-N", "", "-C", "clawops"], {
2768
2769
  stdio: ["ignore", "pipe", "pipe"]
2769
2770
  });
@@ -2772,77 +2773,30 @@ async function generateSshKey() {
2772
2773
  }
2773
2774
  success(`SSH key pair generated: ${keyPath}`);
2774
2775
  info(`Public key (add this to your server's ~/.ssh/authorized_keys if needed):
2775
- ${readFileSync4(`${keyPath}.pub`, "utf-8").trim()}`);
2776
+ ${readFileSync3(`${keyPath}.pub`, "utf-8").trim()}`);
2776
2777
  return keyPath;
2777
2778
  }
2778
2779
  function detectSshKey() {
2779
2780
  const candidates = ["id_ed25519", "id_ecdsa", "id_rsa", "id_dsa"];
2780
2781
  for (const name of candidates) {
2781
- const p = path4.join(os2.homedir(), ".ssh", name);
2782
- if (existsSync3(p)) return p;
2782
+ const p = path3.join(os.homedir(), ".ssh", name);
2783
+ if (existsSync2(p)) return p;
2783
2784
  }
2784
- return path4.join(os2.homedir(), ".ssh", "id_ed25519");
2785
+ return path3.join(os.homedir(), ".ssh", "id_ed25519");
2785
2786
  }
2786
- function buildMcpEntry() {
2787
- try {
2788
- const bin = execSync("which clawops", {
2789
- encoding: "utf-8",
2790
- stdio: ["ignore", "pipe", "ignore"]
2791
- }).trim();
2792
- if (bin) return { command: bin, args: ["mcp", "serve", "--read-only"], resolved: true };
2793
- } catch {
2794
- }
2795
- return { command: "clawops", args: ["mcp", "serve", "--read-only"], resolved: false };
2796
- }
2797
- var MCP_APPS = [
2798
- {
2799
- id: "claude-desktop",
2800
- name: "Claude Desktop",
2801
- configPath: () => process19.platform === "darwin" ? path4.join(os2.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json") : path4.join(os2.homedir(), ".config", "Claude", "claude_desktop_config.json"),
2802
- isInstalled: () => existsSync3(
2803
- process19.platform === "darwin" ? path4.join(os2.homedir(), "Library", "Application Support", "Claude") : path4.join(os2.homedir(), ".config", "Claude")
2804
- )
2805
- },
2806
- {
2807
- id: "claude-code",
2808
- name: "Claude Code",
2809
- configPath: () => path4.join(os2.homedir(), ".claude.json"),
2810
- isInstalled: () => {
2811
- if (existsSync3(path4.join(os2.homedir(), ".claude.json"))) return true;
2812
- try {
2813
- execSync("claude --version", { stdio: "ignore" });
2814
- return true;
2815
- } catch {
2816
- return false;
2817
- }
2818
- }
2819
- },
2820
- {
2821
- id: "cursor",
2822
- name: "Cursor",
2823
- configPath: () => path4.join(os2.homedir(), ".cursor", "mcp.json"),
2824
- isInstalled: () => existsSync3(path4.join(os2.homedir(), ".cursor"))
2825
- },
2826
- {
2827
- id: "windsurf",
2828
- name: "Windsurf",
2829
- configPath: () => path4.join(os2.homedir(), ".codeium", "windsurf", "mcp_config.json"),
2830
- isInstalled: () => existsSync3(path4.join(os2.homedir(), ".codeium", "windsurf"))
2831
- }
2832
- ];
2833
2787
  function printMcpSnippet() {
2834
- process19.stdout.write("\n");
2835
- process19.stdout.write(JSON.stringify({
2788
+ process20.stdout.write("\n");
2789
+ process20.stdout.write(JSON.stringify({
2836
2790
  mcpServers: {
2837
2791
  clawops: { command: "clawops", args: ["mcp", "serve", "--read-only"] }
2838
2792
  }
2839
2793
  }, null, 2) + "\n\n");
2840
2794
  }
2841
2795
  function loadCatalogs(yaml) {
2842
- const specDir = path4.join(path4.dirname(fileURLToPath(import.meta.url)), "../../../spec");
2796
+ const specDir = path3.join(path3.dirname(fileURLToPath(import.meta.url)), "../../../spec");
2843
2797
  try {
2844
- const modelsRaw = yaml.load(readFileSync4(path4.join(specDir, "models.yaml"), "utf-8"));
2845
- const integrationsRaw = yaml.load(readFileSync4(path4.join(specDir, "integrations.yaml"), "utf-8"));
2798
+ const modelsRaw = yaml.load(readFileSync3(path3.join(specDir, "models.yaml"), "utf-8"));
2799
+ const integrationsRaw = yaml.load(readFileSync3(path3.join(specDir, "integrations.yaml"), "utf-8"));
2846
2800
  return {
2847
2801
  models: modelsRaw.providers,
2848
2802
  integrations: integrationsRaw.integrations
@@ -2887,26 +2841,376 @@ function instanceChoices(provider) {
2887
2841
  return table[provider] ?? [];
2888
2842
  }
2889
2843
 
2844
+ // src/cli/commands/secret.ts
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";
3125
+ var COMMANDS = [
3126
+ ["setup", "Interactive first-run wizard \u2014 configure, deploy, and wire AI apps"],
3127
+ ["init", "Write config and generate SSH key pair (non-interactive)"],
3128
+ ["up", "Provision or update a stack"],
3129
+ ["down", "Destroy a local-provider stack (requires --yes)"],
3130
+ ["destroy", "Destroy a cloud-provider stack with confirmation prompt (requires --yes)"],
3131
+ ["status", "Show stack outputs: IP, gateway URL, region, provisioned time"],
3132
+ ["plan", "Generate a deploy-plan JSON artifact (dry-run safe)"],
3133
+ ["apply", "Apply a previously reviewed plan file"],
3134
+ ["ssh", "Interactive SSH session or run a remote command (--command)"],
3135
+ ["logs", "Stream OpenClaw logs (-f, --tail N, --since 5m)"],
3136
+ ["tunnel", "Port-forward gateway UI to localhost over SSH"],
3137
+ ["config", "Get / set / validate remote OpenClaw config values"],
3138
+ ["agents", "List or restart OpenClaw agents"],
3139
+ ["gateway", "Restart the OpenClaw gateway service"],
3140
+ ["backup", "Create or restore an OpenClaw state backup"],
3141
+ ["stacks", "List named stacks and their state"],
3142
+ ["doctor", "Check Node version, config, SSH key, provider credentials, and Pulumi home"],
3143
+ ["mcp", "MCP server operations (mcp serve | mcp install)"],
3144
+ ["secret", "Manage secrets (secret list | set | delete | rotate | audit)"],
3145
+ ["help", "Show this help message"]
3146
+ ];
3147
+ var GLOBAL_FLAGS = [
3148
+ ["--stack <name>", "Target named stack (overrides config default)"],
3149
+ ["--provider <name>", "Override provider (aws | gcp | azure | local)"],
3150
+ ["--json", "Emit JSON to stdout"],
3151
+ ["--quiet", "Suppress non-error output"],
3152
+ ["--dry-run", "Preview without applying (mutating commands)"],
3153
+ ["--yes", "Skip interactive confirmations (CI mode)"]
3154
+ ];
3155
+ var help_default = defineCommand20({
3156
+ meta: {
3157
+ name: "help",
3158
+ description: "Show available commands and global flags"
3159
+ },
3160
+ args: {},
3161
+ run() {
3162
+ const bold = chalk.bold;
3163
+ const dim = chalk.dim;
3164
+ const cyan = chalk.cyan;
3165
+ const cmdWidth = Math.max(...COMMANDS.map(([c]) => c.length));
3166
+ const flagWidth = Math.max(...GLOBAL_FLAGS.map(([f]) => f.length));
3167
+ process.stdout.write(`
3168
+ ${bold("clawops")} \u2014 Deploy and manage self-hosted OpenClaw instances
3169
+
3170
+ `);
3171
+ process.stdout.write(`${bold("Usage:")} clawops <command> [flags]
3172
+
3173
+ `);
3174
+ process.stdout.write(`${bold("Commands:")}
3175
+ `);
3176
+ for (const [cmd, desc] of COMMANDS) {
3177
+ process.stdout.write(` ${cyan(cmd.padEnd(cmdWidth))} ${desc}
3178
+ `);
3179
+ }
3180
+ process.stdout.write(`
3181
+ ${bold("Global flags:")}
3182
+ `);
3183
+ for (const [flag, desc] of GLOBAL_FLAGS) {
3184
+ process.stdout.write(` ${flag.padEnd(flagWidth)} ${dim(desc)}
3185
+ `);
3186
+ }
3187
+ process.stdout.write(`
3188
+ ${dim("Run `clawops <command> --help` for command-specific flags.")}
3189
+
3190
+ `);
3191
+ }
3192
+ });
3193
+
2890
3194
  // src/cli/error-handler.ts
2891
- import process20 from "process";
3195
+ import process22 from "process";
2892
3196
  function handleError(err) {
2893
3197
  if (err instanceof ClawopsError) {
2894
3198
  failure(err.message);
2895
- process20.exit(err.exitCode);
3199
+ process22.exit(err.exitCode);
2896
3200
  }
2897
3201
  if (err instanceof Error) {
2898
3202
  failure(`Unexpected error: ${err.message}`);
2899
- if (process20.env["DEBUG"]) {
2900
- process20.stderr.write(err.stack ?? "\n");
3203
+ if (process22.env["DEBUG"]) {
3204
+ process22.stderr.write(err.stack ?? "\n");
2901
3205
  }
2902
- process20.exit(1);
3206
+ process22.exit(1);
2903
3207
  }
2904
3208
  failure(`Unknown error: ${String(err)}`);
2905
- process20.exit(1);
3209
+ process22.exit(1);
2906
3210
  }
2907
3211
 
2908
3212
  // src/cli/index.ts
2909
- var main = defineCommand19({
3213
+ var main = defineCommand21({
2910
3214
  meta: {
2911
3215
  name: "clawops",
2912
3216
  description: "Deploy and manage self-hosted OpenClaw instances across clouds",
@@ -2939,7 +3243,9 @@ var main = defineCommand19({
2939
3243
  stacks: stacks_default,
2940
3244
  doctor: doctor_default,
2941
3245
  mcp: mcp_default,
2942
- setup: setup_default
3246
+ setup: setup_default,
3247
+ secret: secret_default,
3248
+ help: help_default
2943
3249
  }
2944
3250
  });
2945
3251
  try {