@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/README.md +113 -43
- package/dist/{apply-KBF7OPWL.js → apply-DLNE2JYZ.js} +4 -3
- package/dist/{bootstrap-UYCOEJQX.js → bootstrap-G4UZ2FKH.js} +3 -3
- package/dist/chunk-6ZFIFDBJ.js +41 -0
- package/dist/{chunk-BRPU7AQC.js → chunk-A2I76FTA.js} +13 -1
- package/dist/{chunk-LU63NZD3.js → chunk-JCUZU5BH.js} +5 -1
- package/dist/{chunk-ACYJBSLJ.js → chunk-JDN6PLH2.js} +1 -1
- package/dist/{chunk-QJ6ERXHN.js → chunk-LVIIYY27.js} +1 -1
- package/dist/chunk-OIGTOLB3.js +99 -0
- package/dist/{chunk-QMXM2SMO.js → chunk-ZDPBBYYV.js} +2 -2
- package/dist/cli.js +536 -230
- package/dist/{context-VUAYRAJY.js → context-M2RMDHX6.js} +2 -2
- package/dist/{generate-WG5VONSE.js → generate-U7PJ5LRG.js} +3 -3
- package/dist/mcp-apps-KY44ED3Y.js +11 -0
- package/dist/overlay-store-ADZPD7EU.js +11 -0
- package/dist/{package-3JZ767DN.js → package-AFUTH62F.js} +1 -1
- package/dist/{server-QJRWK2RV.js → server-I62UES5V.js} +11 -10
- package/dist/state-PRBIIN7I.js +17 -0
- package/package.json +1 -1
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-
|
|
5
|
-
import "./chunk-
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
498
|
-
const { generatePlan } = await import("./generate-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
1786
|
-
|
|
1787
|
-
import os from "os";
|
|
1788
|
-
var mcp_default = defineCommand17({
|
|
1790
|
+
import process19 from "process";
|
|
1791
|
+
var serveCmd = defineCommand17({
|
|
1789
1792
|
meta: {
|
|
1790
|
-
name: "
|
|
1791
|
-
description: "
|
|
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
|
|
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
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
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
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
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
|
-
|
|
1872
|
-
}
|
|
1880
|
+
});
|
|
1873
1881
|
|
|
1874
1882
|
// src/cli/commands/setup.ts
|
|
1875
1883
|
import { defineCommand as defineCommand18 } from "citty";
|
|
1876
|
-
import
|
|
1877
|
-
import
|
|
1884
|
+
import process20 from "process";
|
|
1885
|
+
import os from "os";
|
|
1878
1886
|
import { randomBytes } from "crypto";
|
|
1879
|
-
import { readFileSync as
|
|
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
|
|
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
|
-
|
|
1906
|
+
process20.on("SIGINT", () => {
|
|
1899
1907
|
ac.abort();
|
|
1900
|
-
|
|
1908
|
+
process20.exit(130);
|
|
1901
1909
|
});
|
|
1902
|
-
|
|
1910
|
+
process20.on("SIGTERM", () => {
|
|
1903
1911
|
ac.abort();
|
|
1904
|
-
|
|
1912
|
+
process20.exit(143);
|
|
1905
1913
|
});
|
|
1906
|
-
|
|
1907
|
-
|
|
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 =
|
|
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
|
-
|
|
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 (!
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2138
|
-
|
|
2139
|
-
spawnSync("chmod", ["700",
|
|
2140
|
-
const tokenPath =
|
|
2141
|
-
|
|
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 =
|
|
2152
|
-
|
|
2153
|
-
|
|
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 =
|
|
2166
|
+
sshPublicKey = readFileSync3(stackAnswers.sshKeyPath ?? "", "utf-8").trim();
|
|
2159
2167
|
} catch {
|
|
2160
2168
|
failure(`Cannot read SSH public key at ${stackAnswers.sshKeyPath ?? ""}`);
|
|
2161
|
-
|
|
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 =
|
|
2191
|
-
|
|
2192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
2292
|
-
|
|
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-
|
|
2308
|
-
const plan = JSON.parse(
|
|
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) =>
|
|
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
|
-
|
|
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-
|
|
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 =
|
|
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 =
|
|
2413
|
+
const tokenPath = path3.join(os.homedir(), ".clawops", "secrets", `GATEWAY_TOKEN_${opts.stackName}`);
|
|
2413
2414
|
const dashboardUrl = `${state.gatewayUrl}?token=${opts.gatewayToken}`;
|
|
2414
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2479
|
+
process20.stdout.write("\n");
|
|
2479
2480
|
success(`Authenticated: ${cfg.identity(identity)}`);
|
|
2480
2481
|
} catch {
|
|
2481
|
-
|
|
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
|
|
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
|
-
|
|
2514
|
-
spawnSync("chmod", ["700",
|
|
2515
|
-
const
|
|
2516
|
-
|
|
2517
|
-
success(`Secret saved to ${
|
|
2518
|
-
return { name, source: "file", ref:
|
|
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) =>
|
|
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 =
|
|
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 (!
|
|
2731
|
-
const pubKey =
|
|
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 =
|
|
2734
|
+
const authorizedKeysPath = path3.join(os.homedir(), ".ssh", "authorized_keys");
|
|
2734
2735
|
if (isLocal) {
|
|
2735
|
-
const existing =
|
|
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
|
-
|
|
2749
|
+
mkdirSync3(path3.join(os.homedir(), ".ssh"), { recursive: true });
|
|
2749
2750
|
const entry = existing.endsWith("\n") || existing === "" ? pubKey + "\n" : "\n" + pubKey + "\n";
|
|
2750
|
-
|
|
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
|
-
|
|
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 =
|
|
2766
|
-
|
|
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
|
-
${
|
|
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 =
|
|
2782
|
-
if (
|
|
2782
|
+
const p = path3.join(os.homedir(), ".ssh", name);
|
|
2783
|
+
if (existsSync2(p)) return p;
|
|
2783
2784
|
}
|
|
2784
|
-
return
|
|
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
|
-
|
|
2835
|
-
|
|
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 =
|
|
2796
|
+
const specDir = path3.join(path3.dirname(fileURLToPath(import.meta.url)), "../../../spec");
|
|
2843
2797
|
try {
|
|
2844
|
-
const modelsRaw = yaml.load(
|
|
2845
|
-
const integrationsRaw = yaml.load(
|
|
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
|
|
3195
|
+
import process22 from "process";
|
|
2892
3196
|
function handleError(err) {
|
|
2893
3197
|
if (err instanceof ClawopsError) {
|
|
2894
3198
|
failure(err.message);
|
|
2895
|
-
|
|
3199
|
+
process22.exit(err.exitCode);
|
|
2896
3200
|
}
|
|
2897
3201
|
if (err instanceof Error) {
|
|
2898
3202
|
failure(`Unexpected error: ${err.message}`);
|
|
2899
|
-
if (
|
|
2900
|
-
|
|
3203
|
+
if (process22.env["DEBUG"]) {
|
|
3204
|
+
process22.stderr.write(err.stack ?? "\n");
|
|
2901
3205
|
}
|
|
2902
|
-
|
|
3206
|
+
process22.exit(1);
|
|
2903
3207
|
}
|
|
2904
3208
|
failure(`Unknown error: ${String(err)}`);
|
|
2905
|
-
|
|
3209
|
+
process22.exit(1);
|
|
2906
3210
|
}
|
|
2907
3211
|
|
|
2908
3212
|
// src/cli/index.ts
|
|
2909
|
-
var main =
|
|
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 {
|