@clawops/cli 1.7.9 → 2.0.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 +412 -70
- package/dist/{apply-F3KY2HTB.js → apply-AM5HERE7.js} +8 -6
- package/dist/{aws-L4RR2LSE.js → aws-AMON7DA7.js} +20 -9
- package/dist/{azure-7OXQENQL.js → azure-T4G6OBJQ.js} +19 -8
- package/dist/{bootstrap-HL4DNUNG.js → bootstrap-DMRPWJGN.js} +28 -19
- package/dist/bootstrap.sh.tmpl +51 -31
- package/dist/channels-KBUR42IU.js +53 -0
- package/dist/{chunk-DQDAWNGB.js → chunk-3WYA2QDM.js} +1 -1
- package/dist/chunk-6TIYVHPX.js +152 -0
- package/dist/{chunk-3XK66DKK.js → chunk-AHAAEXWC.js} +41 -0
- package/dist/{chunk-GZN7PKP4.js → chunk-AX74VXTV.js} +292 -94
- package/dist/{chunk-C5X375RO.js → chunk-CKSPROD5.js} +59 -15
- package/dist/{chunk-UKTP332Z.js → chunk-GAZPPSKU.js} +6 -7
- package/dist/{chunk-FNS56UQY.js → chunk-GYV4KNW7.js} +319 -252
- package/dist/{chunk-PVUVI35Y.js → chunk-JW5A7E3X.js} +5 -1
- package/dist/chunk-LT4XMO5D.js +50 -0
- package/dist/{chunk-P5LKWRPL.js → chunk-LU2V3LXA.js} +3 -3
- package/dist/chunk-N44K3LHE.js +140 -0
- package/dist/chunk-NUDIHZEH.js +55 -0
- package/dist/chunk-SBLQH2TJ.js +56 -0
- package/dist/chunk-UFRO4H5W.js +228 -0
- package/dist/{chunk-A4QR4TXW.js → chunk-WHJMKDV7.js} +24 -4
- package/dist/chunk-YR3O2I3S.js +101 -0
- package/dist/cli.js +866 -577
- package/dist/config-validate-K2N6XLDX.js +76 -0
- package/dist/{context-5LCUG2QR.js → context-KHKWJJMC.js} +1 -1
- package/dist/{firewall-XQGO7HWK.js → firewall-T3NTPZJD.js} +10 -0
- package/dist/{gcp-UG6AWPIZ.js → gcp-2JBTD7ZM.js} +19 -8
- package/dist/{generate-OAGYT3VH.js → generate-2XV2GSP2.js} +4 -4
- package/dist/{harden-URPXLSVF.js → harden-PCDK3UQ5.js} +48 -11
- package/dist/health-5CCOXYGG.js +11 -0
- package/dist/logs-P7YCKVF4.js +15 -0
- package/dist/{mcp-wire-7E5JQY47.js → mcp-wire-BFEY2Y6H.js} +23 -17
- package/dist/{package-5AR4ZPK4.js → package-3HUW57XH.js} +3 -3
- package/dist/plugins-DTST3OER.js +49 -0
- package/dist/{pool-QGFQRMGU.js → pool-EX4ZTA5P.js} +2 -2
- package/dist/privileged-JFA6LLTV.js +17 -0
- package/dist/{remote-config-QDLN4UMG.js → remote-config-Z6IIZR3D.js} +3 -3
- package/dist/runtime-LZ7NA53V.js +42 -0
- package/dist/{server-FIZCOLHL.js → server-A5AB67GQ.js} +16 -12
- package/dist/spec-path-KQOOIQCA.js +9 -0
- package/dist/{ssh-CNJNLM3E.js → ssh-B3NZAU4I.js} +1 -1
- package/dist/upgrade-ULD4KFYC.js +88 -0
- package/dist/validate-BOQRWX4F.js +14 -0
- package/dist/{version-guard-VUCNZXQS.js → version-guard-GPSTCPT7.js} +3 -3
- package/dist/{versions-2UVAT6PQ.js → versions-W5LRQCAJ.js} +2 -2
- package/package.json +3 -3
- package/spec/deploy-plan.schema.json +114 -25
- package/spec/integrations.yaml +135 -28
- package/spec/mcp-tools.yaml +68 -35
- package/spec/models.yaml +9 -0
- package/spec/openclaw-2.0.config.schema.json +1 -0
- package/spec/openclaw-versions.yaml +116 -27
- package/dist/chunk-35WVQY3X.js +0 -122
- package/dist/chunk-6X45DDGH.js +0 -42
- package/dist/chunk-SEZMVVAS.js +0 -156
- package/dist/validate-CFQ4FH2T.js +0 -10
package/dist/cli.js
CHANGED
|
@@ -6,21 +6,26 @@ import {
|
|
|
6
6
|
} from "./chunk-OIGTOLB3.js";
|
|
7
7
|
import {
|
|
8
8
|
monitor_default,
|
|
9
|
-
|
|
10
|
-
} from "./chunk-
|
|
11
|
-
import "./chunk-DQDAWNGB.js";
|
|
9
|
+
runDiagnostics
|
|
10
|
+
} from "./chunk-GYV4KNW7.js";
|
|
12
11
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import
|
|
19
|
-
|
|
12
|
+
GATEWAY_LOGS_PROBE,
|
|
13
|
+
chooseLogSource,
|
|
14
|
+
containerLogsCommand,
|
|
15
|
+
gatewayLogsCommand
|
|
16
|
+
} from "./chunk-SBLQH2TJ.js";
|
|
17
|
+
import {
|
|
18
|
+
interpretProbe,
|
|
19
|
+
probeCommand
|
|
20
|
+
} from "./chunk-LT4XMO5D.js";
|
|
21
|
+
import {
|
|
22
|
+
execPrivileged,
|
|
23
|
+
execPrivilegedWithInput,
|
|
24
|
+
streamPrivileged
|
|
25
|
+
} from "./chunk-NUDIHZEH.js";
|
|
20
26
|
import {
|
|
21
27
|
resolveSpecDir
|
|
22
|
-
} from "./chunk-
|
|
23
|
-
import "./chunk-3XK66DKK.js";
|
|
28
|
+
} from "./chunk-JW5A7E3X.js";
|
|
24
29
|
import {
|
|
25
30
|
REPO_URL,
|
|
26
31
|
chalk,
|
|
@@ -31,7 +36,6 @@ import {
|
|
|
31
36
|
success,
|
|
32
37
|
warn
|
|
33
38
|
} from "./chunk-Q7NQY5HV.js";
|
|
34
|
-
import "./chunk-A2I76FTA.js";
|
|
35
39
|
import {
|
|
36
40
|
getConfig,
|
|
37
41
|
getConfigDir,
|
|
@@ -42,9 +46,23 @@ import {
|
|
|
42
46
|
ClawopsError,
|
|
43
47
|
UsageError
|
|
44
48
|
} from "./chunk-KGXPLI7W.js";
|
|
49
|
+
import {
|
|
50
|
+
CONFIG_FILENAME,
|
|
51
|
+
CONTAINER_UID,
|
|
52
|
+
GATEWAY_PORT,
|
|
53
|
+
IMAGE_INSPECT_CMD,
|
|
54
|
+
PUBLISH_INSPECT_CMD,
|
|
55
|
+
STATE_DIR_HOST_LINUX,
|
|
56
|
+
configPathForOS,
|
|
57
|
+
gatewayRunCommand,
|
|
58
|
+
imageForRestart,
|
|
59
|
+
publishForRestart,
|
|
60
|
+
versionOf
|
|
61
|
+
} from "./chunk-6TIYVHPX.js";
|
|
62
|
+
import "./chunk-EI7CQQ7D.js";
|
|
45
63
|
|
|
46
64
|
// src/cli/index.ts
|
|
47
|
-
import { defineCommand as
|
|
65
|
+
import { defineCommand as defineCommand24, runMain } from "citty";
|
|
48
66
|
|
|
49
67
|
// src/cli/commands/init.ts
|
|
50
68
|
import { defineCommand } from "citty";
|
|
@@ -213,11 +231,12 @@ var up_default = defineCommand2({
|
|
|
213
231
|
"no-wait": { type: "boolean", description: "Return immediately without waiting for healthy state" },
|
|
214
232
|
"openclaw-version": { type: "string", description: "OpenClaw release (e.g. 2026.7.1-2). Moving tags are resolved and range-checked" },
|
|
215
233
|
stack: { type: "string", description: "Target stack name" },
|
|
216
|
-
config: { type: "string", description: "Path to openclaw config overlay JSON (local provider only)" }
|
|
234
|
+
config: { type: "string", description: "Path to openclaw config overlay JSON (local provider only)" },
|
|
235
|
+
"gateway-port": { type: "string", description: `Host port to publish the gateway on (default ${GATEWAY_PORT}, local provider only \u2014 cloud stacks set spec.network.gatewayPort in the plan)` }
|
|
217
236
|
},
|
|
218
237
|
async run({ args }) {
|
|
219
|
-
const { buildContext } = await import("./context-
|
|
220
|
-
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-
|
|
238
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
239
|
+
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-GPSTCPT7.js");
|
|
221
240
|
const ctx = buildContext(args);
|
|
222
241
|
const requestedVersion = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : await defaultOpenclawVersion();
|
|
223
242
|
const openclawVersion = await guardOpenclawVersion(requestedVersion);
|
|
@@ -229,7 +248,8 @@ var up_default = defineCommand2({
|
|
|
229
248
|
);
|
|
230
249
|
}
|
|
231
250
|
const { localOpts } = stackConfig;
|
|
232
|
-
const { localBootstrap } = await import("./bootstrap-
|
|
251
|
+
const { localBootstrap } = await import("./bootstrap-DMRPWJGN.js");
|
|
252
|
+
const gatewayPort = parseGatewayPort(args["gateway-port"]);
|
|
233
253
|
const abortController = new AbortController();
|
|
234
254
|
process3.on("SIGINT", () => abortController.abort());
|
|
235
255
|
process3.on("SIGTERM", () => abortController.abort());
|
|
@@ -243,6 +263,7 @@ var up_default = defineCommand2({
|
|
|
243
263
|
knownHostsPath: ctx.config.ssh.knownHostsPath,
|
|
244
264
|
openclawVersion,
|
|
245
265
|
stackName: ctx.stackName,
|
|
266
|
+
gatewayPort,
|
|
246
267
|
noWait: Boolean(args["no-wait"]),
|
|
247
268
|
signal: abortController.signal
|
|
248
269
|
});
|
|
@@ -322,9 +343,9 @@ var up_default = defineCommand2({
|
|
|
322
343
|
}
|
|
323
344
|
});
|
|
324
345
|
async function applyLocalConfigOverlay(opts) {
|
|
325
|
-
const { connect } = await import("./ssh-
|
|
346
|
+
const { connect } = await import("./ssh-B3NZAU4I.js");
|
|
326
347
|
const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
|
|
327
|
-
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-
|
|
348
|
+
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-Z6IIZR3D.js");
|
|
328
349
|
let overlay;
|
|
329
350
|
try {
|
|
330
351
|
overlay = JSON.parse(readFileSync(opts.configPath, "utf-8"));
|
|
@@ -349,6 +370,14 @@ async function applyLocalConfigOverlay(opts) {
|
|
|
349
370
|
session.close();
|
|
350
371
|
}
|
|
351
372
|
}
|
|
373
|
+
function parseGatewayPort(raw) {
|
|
374
|
+
if (typeof raw !== "string" || raw.trim() === "") return void 0;
|
|
375
|
+
const port = Number(raw);
|
|
376
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
377
|
+
throw new UsageError(`--gateway-port must be a port number between 1 and 65535, got "${raw}"`);
|
|
378
|
+
}
|
|
379
|
+
return port;
|
|
380
|
+
}
|
|
352
381
|
|
|
353
382
|
// src/cli/commands/down.ts
|
|
354
383
|
import { defineCommand as defineCommand3 } from "citty";
|
|
@@ -364,7 +393,7 @@ var down_default = defineCommand3({
|
|
|
364
393
|
"dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
|
|
365
394
|
},
|
|
366
395
|
async run({ args }) {
|
|
367
|
-
const { buildContext } = await import("./context-
|
|
396
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
368
397
|
const ctx = buildContext(args);
|
|
369
398
|
if (args["dry-run"]) {
|
|
370
399
|
info(`Dry run \u2014 would destroy stack "${ctx.stackName}"`);
|
|
@@ -428,7 +457,7 @@ var status_default = defineCommand4({
|
|
|
428
457
|
json: { type: "boolean", description: "Emit JSON" }
|
|
429
458
|
},
|
|
430
459
|
async run({ args }) {
|
|
431
|
-
const { buildContext } = await import("./context-
|
|
460
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
432
461
|
const ctx = buildContext(args);
|
|
433
462
|
if (ctx.adapter.name === "local") {
|
|
434
463
|
const state = ctx.localState;
|
|
@@ -500,8 +529,8 @@ var plan_default = defineCommand5({
|
|
|
500
529
|
out: { type: "string", description: "Write plan JSON to this absolute path (default: stdout)" }
|
|
501
530
|
},
|
|
502
531
|
async run({ args }) {
|
|
503
|
-
const { buildContext } = await import("./context-
|
|
504
|
-
const { generatePlan } = await import("./generate-
|
|
532
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
533
|
+
const { generatePlan } = await import("./generate-2XV2GSP2.js");
|
|
505
534
|
const ctx = buildContext(args);
|
|
506
535
|
if (ctx.adapter.name === "local") {
|
|
507
536
|
throw new UsageError(
|
|
@@ -544,6 +573,7 @@ var plan_default = defineCommand5({
|
|
|
544
573
|
const { spec, metadata } = plan;
|
|
545
574
|
const sshCidrs = spec.network.allowedSshCidrs.join(", ") || "(none)";
|
|
546
575
|
const gatewayCidrs = spec.network.allowedGatewayCidrs.join(", ") || "(none)";
|
|
576
|
+
const publish = spec.network.publishGateway ?? "loopback";
|
|
547
577
|
process7.stderr.write(
|
|
548
578
|
`
|
|
549
579
|
Plan: ${metadata.name} (${spec.provider}${spec.region ? ` / ${spec.region}` : ""})
|
|
@@ -551,6 +581,7 @@ Plan: ${metadata.name} (${spec.provider}${spec.region ? ` / ${spec.region}` : "
|
|
|
551
581
|
OpenClaw: ${spec.openclaw.version}
|
|
552
582
|
SSH CIDRs: ${sshCidrs}
|
|
553
583
|
Gateway: ${gatewayCidrs}
|
|
584
|
+
Published: ${publish === "all" ? "0.0.0.0 \u2014 reachable from the network; plaintext HTTP unless you add TLS" : "127.0.0.1 only \u2014 use `clawops tunnel` or a proxy on the host"}
|
|
554
585
|
`
|
|
555
586
|
);
|
|
556
587
|
if (plan.diff) {
|
|
@@ -590,8 +621,8 @@ var apply_default = defineCommand6({
|
|
|
590
621
|
"dry-run": { type: "boolean", description: "Validate plan and show diff without applying" }
|
|
591
622
|
},
|
|
592
623
|
async run({ args }) {
|
|
593
|
-
const { validatePlan } = await import("./validate-
|
|
594
|
-
const { applyPlan } = await import("./apply-
|
|
624
|
+
const { validatePlan } = await import("./validate-BOQRWX4F.js");
|
|
625
|
+
const { applyPlan } = await import("./apply-AM5HERE7.js");
|
|
595
626
|
const planPath = args._?.[0];
|
|
596
627
|
if (!planPath) {
|
|
597
628
|
throw new UsageError("Usage: clawops apply <plan.json>");
|
|
@@ -657,8 +688,8 @@ ${validation.errors.join("\n")}`);
|
|
|
657
688
|
const spin = spinner(`Applying plan for stack "${typedPlan.spec.stackName}"\u2026`);
|
|
658
689
|
try {
|
|
659
690
|
const result = await applyPlan(typedPlan, {
|
|
660
|
-
onOutput: (
|
|
661
|
-
spin.text =
|
|
691
|
+
onOutput: (line2) => {
|
|
692
|
+
spin.text = line2.trim() || spin.text;
|
|
662
693
|
},
|
|
663
694
|
signal: abortController.signal,
|
|
664
695
|
confirmDrift: args.yes ? void 0 : async () => {
|
|
@@ -707,7 +738,7 @@ var destroy_default = defineCommand7({
|
|
|
707
738
|
"dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
|
|
708
739
|
},
|
|
709
740
|
async run({ args }) {
|
|
710
|
-
const { buildContext } = await import("./context-
|
|
741
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
711
742
|
const ctx = buildContext(args);
|
|
712
743
|
if (ctx.adapter.name === "local") {
|
|
713
744
|
throw new UsageError(
|
|
@@ -774,8 +805,8 @@ var ssh_default = defineCommand8({
|
|
|
774
805
|
command: { type: "string", description: "Remote command to run (instead of interactive shell)" }
|
|
775
806
|
},
|
|
776
807
|
async run({ args }) {
|
|
777
|
-
const { buildContext } = await import("./context-
|
|
778
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
808
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
809
|
+
const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
|
|
779
810
|
const ctx = buildContext(args);
|
|
780
811
|
let conn;
|
|
781
812
|
if (ctx.adapter.name === "local") {
|
|
@@ -861,9 +892,9 @@ var tunnel_default = defineCommand9({
|
|
|
861
892
|
"no-open": { type: "boolean", description: "Do not open browser after tunnel is ready" }
|
|
862
893
|
},
|
|
863
894
|
async run({ args }) {
|
|
864
|
-
const { buildContext } = await import("./context-
|
|
895
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
865
896
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
866
|
-
const { acquireSession } = await import("./pool-
|
|
897
|
+
const { acquireSession } = await import("./pool-EX4ZTA5P.js");
|
|
867
898
|
const ctx = buildContext(args);
|
|
868
899
|
const stack = await ctx.getStack();
|
|
869
900
|
const outputMap = await stack.outputs();
|
|
@@ -926,12 +957,13 @@ var logs_default = defineCommand10({
|
|
|
926
957
|
stack: { type: "string", description: "Target stack name" },
|
|
927
958
|
follow: { type: "boolean", alias: "f", description: "Follow log output" },
|
|
928
959
|
tail: { type: "string", description: "Number of lines to show from end (default: 100)" },
|
|
929
|
-
since: { type: "string", description: "Show logs since duration (e.g. 5m, 1h)" }
|
|
960
|
+
since: { type: "string", description: "Show logs since duration (e.g. 5m, 1h). Reads container output \u2014 the gateway log command has no time filter" },
|
|
961
|
+
json: { type: "boolean", description: "Emit the gateway's structured JSON log lines" }
|
|
930
962
|
},
|
|
931
963
|
async run({ args }) {
|
|
932
|
-
const { buildContext } = await import("./context-
|
|
964
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
933
965
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
934
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
966
|
+
const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
|
|
935
967
|
const ctx = buildContext(args);
|
|
936
968
|
const stack = await ctx.getStack();
|
|
937
969
|
const outputMap = await stack.outputs();
|
|
@@ -950,18 +982,7 @@ var logs_default = defineCommand10({
|
|
|
950
982
|
});
|
|
951
983
|
const tailLines = typeof args.tail === "string" ? parseInt(args.tail, 10) : 100;
|
|
952
984
|
const follow = Boolean(args.follow);
|
|
953
|
-
const
|
|
954
|
-
const followFlag = follow ? "-f" : "";
|
|
955
|
-
const command = [
|
|
956
|
-
"journalctl -u openclaw",
|
|
957
|
-
`-n ${tailLines}`,
|
|
958
|
-
followFlag,
|
|
959
|
-
sinceFlag,
|
|
960
|
-
"2>/dev/null",
|
|
961
|
-
"|| docker logs openclaw",
|
|
962
|
-
`-n ${tailLines}`,
|
|
963
|
-
follow ? "-f" : ""
|
|
964
|
-
].filter(Boolean).join(" ");
|
|
985
|
+
const since = typeof args.since === "string" ? args.since : void 0;
|
|
965
986
|
const abortController = new AbortController();
|
|
966
987
|
process12.on("SIGINT", () => abortController.abort());
|
|
967
988
|
process12.on("SIGTERM", () => abortController.abort());
|
|
@@ -976,8 +997,13 @@ var logs_default = defineCommand10({
|
|
|
976
997
|
});
|
|
977
998
|
spin.stop();
|
|
978
999
|
try {
|
|
1000
|
+
const probe = since ? { stdout: "" } : await execPrivileged(session, GATEWAY_LOGS_PROBE, abortController.signal);
|
|
1001
|
+
const choice = chooseLogSource({ since, gatewayReachable: probe.stdout.trim() === "ok" });
|
|
1002
|
+
const opts = { tail: tailLines, follow, since, json: Boolean(args.json) };
|
|
1003
|
+
const command = choice.source === "gateway" ? gatewayLogsCommand(opts) : containerLogsCommand(opts);
|
|
1004
|
+
info(`Logs: ${choice.source} \u2014 ${choice.reason}`);
|
|
979
1005
|
if (follow) {
|
|
980
|
-
const logStream = await session
|
|
1006
|
+
const logStream = await streamPrivileged(session, command, abortController.signal);
|
|
981
1007
|
logStream.pipe(process12.stdout);
|
|
982
1008
|
await new Promise((resolve) => {
|
|
983
1009
|
logStream.on("end", resolve);
|
|
@@ -985,7 +1011,7 @@ var logs_default = defineCommand10({
|
|
|
985
1011
|
abortController.signal.addEventListener("abort", () => resolve(), { once: true });
|
|
986
1012
|
});
|
|
987
1013
|
} else {
|
|
988
|
-
const result = await session
|
|
1014
|
+
const result = await execPrivileged(session, command, abortController.signal);
|
|
989
1015
|
process12.stdout.write(result.stdout);
|
|
990
1016
|
if (result.stderr) process12.stderr.write(result.stderr);
|
|
991
1017
|
}
|
|
@@ -999,10 +1025,9 @@ var logs_default = defineCommand10({
|
|
|
999
1025
|
// src/cli/commands/config.ts
|
|
1000
1026
|
import { defineCommand as defineCommand11 } from "citty";
|
|
1001
1027
|
import process13 from "process";
|
|
1002
|
-
var OPENCLAW_CONFIG = "
|
|
1003
|
-
var OPENCLAW_TMP = "/tmp/clawops-config.json.tmp";
|
|
1028
|
+
var OPENCLAW_CONFIG = configPathForOS("Linux");
|
|
1004
1029
|
function dockerRunCmd(image) {
|
|
1005
|
-
return gatewayRunCommand({ image,
|
|
1030
|
+
return gatewayRunCommand({ image, stateDir: STATE_DIR_HOST_LINUX });
|
|
1006
1031
|
}
|
|
1007
1032
|
function getPath(obj, dotKey) {
|
|
1008
1033
|
return dotKey.split(".").reduce((cur, k) => {
|
|
@@ -1042,9 +1067,9 @@ var config_default = defineCommand11({
|
|
|
1042
1067
|
"dry-run": { type: "boolean", description: "Show what would change without writing" }
|
|
1043
1068
|
},
|
|
1044
1069
|
async run({ args }) {
|
|
1045
|
-
const { buildContext } = await import("./context-
|
|
1070
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
1046
1071
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1047
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
1072
|
+
const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
|
|
1048
1073
|
const [action, key, value] = args._ ?? [];
|
|
1049
1074
|
if (!action || !["get", "set", "unset", "validate"].includes(action)) {
|
|
1050
1075
|
failure("Usage: clawops config <get [key] | set key value | unset key | validate>");
|
|
@@ -1100,12 +1125,19 @@ var config_default = defineCommand11({
|
|
|
1100
1125
|
return;
|
|
1101
1126
|
}
|
|
1102
1127
|
if (action === "validate") {
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1128
|
+
const { validateConfig } = await import("./config-validate-K2N6XLDX.js");
|
|
1129
|
+
const yaml = await import("./js-yaml-PTEEG4FO.js");
|
|
1130
|
+
const { loadVersionSpec } = await import("./versions-W5LRQCAJ.js");
|
|
1131
|
+
const spec = loadVersionSpec(yaml);
|
|
1132
|
+
const { errors, warnings } = await validateConfig(cfg, {
|
|
1133
|
+
schemaCapturedFrom: spec.runtime?.configSchemaCapturedFrom
|
|
1134
|
+
});
|
|
1135
|
+
for (const w of warnings) warn(w);
|
|
1136
|
+
if (errors.length === 0) {
|
|
1137
|
+
success(warnings.length === 0 ? "Config is valid." : "Config is valid, with notes.");
|
|
1106
1138
|
} else {
|
|
1107
|
-
for (const
|
|
1108
|
-
failure(`Config has ${
|
|
1139
|
+
for (const e of errors) failure(e);
|
|
1140
|
+
failure(`Config has ${errors.length} error(s).`);
|
|
1109
1141
|
process13.exit(1);
|
|
1110
1142
|
}
|
|
1111
1143
|
return;
|
|
@@ -1125,23 +1157,23 @@ var config_default = defineCommand11({
|
|
|
1125
1157
|
process13.stdout.write(JSON.stringify(cfg, null, 2) + "\n");
|
|
1126
1158
|
return;
|
|
1127
1159
|
}
|
|
1128
|
-
const
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
failure(`Failed to write config: ${writeResult.stderr}`);
|
|
1160
|
+
const { atomicWriteConfig } = await import("./remote-config-Z6IIZR3D.js");
|
|
1161
|
+
try {
|
|
1162
|
+
await atomicWriteConfig(session, cfg, abortController.signal);
|
|
1163
|
+
} catch (err) {
|
|
1164
|
+
failure(err.message);
|
|
1134
1165
|
process13.exit(1);
|
|
1135
1166
|
}
|
|
1136
1167
|
success(`config ${action}: ${key ?? "(all)"}`);
|
|
1137
1168
|
if (args.restart) {
|
|
1138
1169
|
info("Restarting gateway...");
|
|
1139
|
-
const imgResult = await
|
|
1170
|
+
const imgResult = await execPrivileged(
|
|
1171
|
+
session,
|
|
1140
1172
|
`docker inspect openclaw --format '{{.Config.Image}}' 2>/dev/null || echo 'ghcr.io/openclaw/openclaw:stable'`,
|
|
1141
1173
|
abortController.signal
|
|
1142
1174
|
);
|
|
1143
1175
|
const image = imgResult.stdout.trim();
|
|
1144
|
-
const restartResult = await session
|
|
1176
|
+
const restartResult = await execPrivileged(session, dockerRunCmd(image), abortController.signal);
|
|
1145
1177
|
if (restartResult.code !== 0) {
|
|
1146
1178
|
failure(`Restart failed: ${restartResult.stderr}`);
|
|
1147
1179
|
process13.exit(1);
|
|
@@ -1161,19 +1193,27 @@ import process14 from "process";
|
|
|
1161
1193
|
var agents_default = defineCommand12({
|
|
1162
1194
|
meta: {
|
|
1163
1195
|
name: "agents",
|
|
1164
|
-
description: "Manage OpenClaw agents (list |
|
|
1196
|
+
description: "Manage OpenClaw agents (list | logs <name>)"
|
|
1165
1197
|
},
|
|
1166
1198
|
args: {
|
|
1167
1199
|
stack: { type: "string", description: "Target stack name" },
|
|
1168
|
-
json: { type: "boolean", description: "Emit JSON (for list)" }
|
|
1200
|
+
json: { type: "boolean", description: "Emit JSON (for list and logs)" },
|
|
1201
|
+
limit: { type: "string", description: "Max activity records for `logs` (default 50)" },
|
|
1202
|
+
cursor: { type: "string", description: "Continue from a previous `logs` result cursor" }
|
|
1169
1203
|
},
|
|
1170
1204
|
async run({ args }) {
|
|
1171
|
-
const { buildContext } = await import("./context-
|
|
1205
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
1172
1206
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1173
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
1207
|
+
const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
|
|
1174
1208
|
const [action, name] = args._ ?? [];
|
|
1175
|
-
if (
|
|
1176
|
-
failure(
|
|
1209
|
+
if (action === "restart") {
|
|
1210
|
+
failure(
|
|
1211
|
+
"clawops agents restart was removed in clawops 2.0.\n OpenClaw 2.0 has no per-agent restart; the only restart it offers is\n gateway-wide and interrupts every agent on the host.\n Run `clawops gateway restart` if that is what you want."
|
|
1212
|
+
);
|
|
1213
|
+
process14.exit(2);
|
|
1214
|
+
}
|
|
1215
|
+
if (!action || !["list", "logs"].includes(action)) {
|
|
1216
|
+
failure("Usage: clawops agents <list | logs <name>>");
|
|
1177
1217
|
process14.exit(2);
|
|
1178
1218
|
}
|
|
1179
1219
|
if (action === "logs" && !name) {
|
|
@@ -1205,15 +1245,24 @@ var agents_default = defineCommand12({
|
|
|
1205
1245
|
});
|
|
1206
1246
|
try {
|
|
1207
1247
|
if (action === "list") {
|
|
1208
|
-
const result = await
|
|
1209
|
-
|
|
1248
|
+
const result = await execPrivileged(
|
|
1249
|
+
session,
|
|
1250
|
+
"docker exec openclaw openclaw agents list --json",
|
|
1210
1251
|
abortController.signal
|
|
1211
1252
|
);
|
|
1212
|
-
|
|
1253
|
+
if (result.code !== 0) {
|
|
1254
|
+
const why = result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`;
|
|
1255
|
+
failure(`Cannot list agents: ${why}`);
|
|
1256
|
+
process14.exitCode = 1;
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
let agents;
|
|
1213
1260
|
try {
|
|
1214
|
-
agents = JSON.parse(result.stdout.trim());
|
|
1261
|
+
agents = JSON.parse(result.stdout.trim() || "[]");
|
|
1215
1262
|
} catch {
|
|
1216
|
-
agents
|
|
1263
|
+
failure(`Cannot list agents: unexpected output from OpenClaw: ${result.stdout.trim().slice(0, 200)}`);
|
|
1264
|
+
process14.exitCode = 1;
|
|
1265
|
+
return;
|
|
1217
1266
|
}
|
|
1218
1267
|
if (args.json) {
|
|
1219
1268
|
printJson(jsonOk(agents));
|
|
@@ -1227,25 +1276,24 @@ var agents_default = defineCommand12({
|
|
|
1227
1276
|
) + "\n\n"
|
|
1228
1277
|
);
|
|
1229
1278
|
}
|
|
1230
|
-
} else if (action === "restart") {
|
|
1231
|
-
const cmd = name ? `docker exec openclaw openclaw agents restart ${name}` : "docker exec openclaw openclaw agents restart";
|
|
1232
|
-
const result = await session.exec(cmd, abortController.signal);
|
|
1233
|
-
if (result.code !== 0) {
|
|
1234
|
-
failure(`Restart failed: ${result.stderr}`);
|
|
1235
|
-
process14.exit(1);
|
|
1236
|
-
}
|
|
1237
|
-
success(name ? `Agent '${name}' restarted.` : "All agents restarted.");
|
|
1238
1279
|
} else {
|
|
1239
|
-
const
|
|
1240
|
-
|
|
1280
|
+
const { agentAuditCommand } = await import("./logs-P7YCKVF4.js");
|
|
1281
|
+
const limit = typeof args.limit === "string" ? parseInt(args.limit, 10) : 50;
|
|
1282
|
+
const result = await execPrivileged(
|
|
1283
|
+
session,
|
|
1284
|
+
agentAuditCommand({ agentId: name, limit, cursor: typeof args.cursor === "string" ? args.cursor : void 0 }),
|
|
1241
1285
|
abortController.signal
|
|
1242
1286
|
);
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1287
|
+
if (result.code !== 0) {
|
|
1288
|
+
failure(`Cannot read activity for "${name}": ${result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`}`);
|
|
1289
|
+
process14.exitCode = 1;
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
if (args.json) {
|
|
1293
|
+
process14.stdout.write(result.stdout);
|
|
1294
|
+
} else {
|
|
1295
|
+
renderAgentRuns(name, result.stdout);
|
|
1296
|
+
}
|
|
1249
1297
|
}
|
|
1250
1298
|
} finally {
|
|
1251
1299
|
release();
|
|
@@ -1253,35 +1301,285 @@ var agents_default = defineCommand12({
|
|
|
1253
1301
|
}
|
|
1254
1302
|
}
|
|
1255
1303
|
});
|
|
1304
|
+
function renderAgentRuns(agentId, stdout) {
|
|
1305
|
+
let page;
|
|
1306
|
+
try {
|
|
1307
|
+
page = JSON.parse(stdout.trim() || "{}");
|
|
1308
|
+
} catch {
|
|
1309
|
+
failure(`Cannot read activity for "${agentId}": unexpected output: ${stdout.trim().slice(0, 200)}`);
|
|
1310
|
+
process14.exitCode = 1;
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
const records = page.records ?? [];
|
|
1314
|
+
if (records.length === 0) {
|
|
1315
|
+
info(`No recorded activity for agent "${agentId}".`);
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
process14.stdout.write(
|
|
1319
|
+
"\n" + renderTable(
|
|
1320
|
+
["When", "Status", "Detail"],
|
|
1321
|
+
records.map((r) => [r.at ?? "\u2014", r.status ?? "\u2014", r.summary ?? r.message ?? "\u2014"])
|
|
1322
|
+
) + "\n"
|
|
1323
|
+
);
|
|
1324
|
+
if (page.cursor) {
|
|
1325
|
+
info(`More records: clawops agents logs ${agentId} --cursor ${page.cursor}`);
|
|
1326
|
+
}
|
|
1327
|
+
process14.stdout.write("\n");
|
|
1328
|
+
}
|
|
1256
1329
|
|
|
1257
|
-
// src/cli/commands/
|
|
1330
|
+
// src/cli/commands/migrate.ts
|
|
1258
1331
|
import { defineCommand as defineCommand13 } from "citty";
|
|
1259
1332
|
import process15 from "process";
|
|
1260
|
-
|
|
1261
|
-
|
|
1333
|
+
|
|
1334
|
+
// src/openclaw/migrate.ts
|
|
1335
|
+
async function migrate(steps) {
|
|
1336
|
+
const sourceImage = await steps.inspectSource();
|
|
1337
|
+
if (!sourceImage) {
|
|
1338
|
+
return {
|
|
1339
|
+
kind: "nothing-to-migrate",
|
|
1340
|
+
reason: "No running OpenClaw container was found, so there is no 1.x state to extract. If this deployment was restarted on a clawops release before 2.0, its state was already lost \u2014 clawops mounted none, so every container replacement discarded it. Deploy fresh with `clawops up`."
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
const identityBefore = await steps.deviceId();
|
|
1344
|
+
const backup = await steps.backup();
|
|
1345
|
+
if (!backup.ok) {
|
|
1346
|
+
return {
|
|
1347
|
+
kind: "refused",
|
|
1348
|
+
reason: `Refusing to migrate without a verified backup: ${backup.detail}
|
|
1349
|
+
This replaces the running container, and the state it extracts is the only copy.`
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
const extracted = await steps.extract();
|
|
1353
|
+
if (!extracted.ok) {
|
|
1354
|
+
return {
|
|
1355
|
+
kind: "failed",
|
|
1356
|
+
reason: "Could not extract state from the running container; it has not been touched.",
|
|
1357
|
+
backupDetail: backup.detail
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
await steps.chown();
|
|
1361
|
+
await steps.removeSource();
|
|
1362
|
+
await steps.writeConfig();
|
|
1363
|
+
await steps.start();
|
|
1364
|
+
let restarts = 0;
|
|
1365
|
+
let gate = await steps.gate();
|
|
1366
|
+
if (!gate.ok) {
|
|
1367
|
+
await steps.start();
|
|
1368
|
+
restarts = 1;
|
|
1369
|
+
gate = await steps.gate();
|
|
1370
|
+
}
|
|
1371
|
+
if (!gate.ok) {
|
|
1372
|
+
return {
|
|
1373
|
+
kind: "failed",
|
|
1374
|
+
reason: gate.reason ?? "the gateway did not start after migration",
|
|
1375
|
+
backupDetail: backup.detail
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
const identityAfter = await steps.deviceId();
|
|
1379
|
+
const identity = identityBefore === void 0 || identityAfter === void 0 ? "unknown" : identityBefore === identityAfter ? "preserved" : "changed";
|
|
1380
|
+
return { kind: "migrated", entries: extracted.entries, identity, restarts };
|
|
1381
|
+
}
|
|
1382
|
+
function describeMigration(o) {
|
|
1383
|
+
switch (o.kind) {
|
|
1384
|
+
case "migrated": {
|
|
1385
|
+
const lines = [
|
|
1386
|
+
`Migrated to the 2.0 runtime contract. Carried over: ${o.entries.join(", ") || "(nothing)"}.`
|
|
1387
|
+
];
|
|
1388
|
+
if (o.restarts > 0) {
|
|
1389
|
+
lines.push(
|
|
1390
|
+
"The gateway needed a second start to finish its state-schema migration, which is expected for a 1.x database."
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
if (o.identity === "preserved") {
|
|
1394
|
+
lines.push("Device identity preserved \u2014 paired devices do not need re-pairing.");
|
|
1395
|
+
} else if (o.identity === "changed") {
|
|
1396
|
+
lines.push(
|
|
1397
|
+
"Device identity CHANGED. Paired devices and nodes must be re-paired \u2014 check `openclaw devices list` on the host for what was paired."
|
|
1398
|
+
);
|
|
1399
|
+
} else {
|
|
1400
|
+
lines.push(
|
|
1401
|
+
"Device identity could not be compared, so pairings may or may not have carried over. Check `openclaw devices list` before relying on them."
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
lines.push("Your 1.x config was NOT applied \u2014 it never took effect on 1.x either. Review it");
|
|
1405
|
+
lines.push("and set what you need with `clawops config set`.");
|
|
1406
|
+
return lines.join("\n");
|
|
1407
|
+
}
|
|
1408
|
+
case "nothing-to-migrate":
|
|
1409
|
+
return o.reason;
|
|
1410
|
+
case "refused":
|
|
1411
|
+
return o.reason;
|
|
1412
|
+
case "failed":
|
|
1413
|
+
return `Migration failed: ${o.reason}
|
|
1414
|
+
A verified backup was taken first: ${o.backupDetail}
|
|
1415
|
+
Restore it with \`clawops backup restore --file <archive>\` before retrying.`;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// src/cli/commands/migrate.ts
|
|
1420
|
+
var SYNTHESISED_CONFIG = JSON.stringify({
|
|
1421
|
+
meta: { lastTouchedVersion: "2026.9.2" },
|
|
1422
|
+
gateway: { mode: "local", port: GATEWAY_PORT, auth: { mode: "token" } },
|
|
1423
|
+
models: {},
|
|
1424
|
+
channels: {}
|
|
1425
|
+
});
|
|
1426
|
+
var migrate_default = defineCommand13({
|
|
1427
|
+
meta: {
|
|
1428
|
+
name: "migrate",
|
|
1429
|
+
description: "Move a 1.x OpenClaw deployment onto the 2.0 runtime contract"
|
|
1430
|
+
},
|
|
1431
|
+
args: {
|
|
1432
|
+
stack: { type: "string", description: "Target stack name" },
|
|
1433
|
+
"openclaw-version": { type: "string", description: "OpenClaw 2.x version to migrate to" },
|
|
1434
|
+
yes: { type: "boolean", description: "Skip the confirmation prompt" }
|
|
1435
|
+
},
|
|
1436
|
+
async run({ args }) {
|
|
1437
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
1438
|
+
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1439
|
+
const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
|
|
1440
|
+
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-GPSTCPT7.js");
|
|
1441
|
+
const requested = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : void 0;
|
|
1442
|
+
const target = requested ? await guardOpenclawVersion(requested) : await defaultOpenclawVersion();
|
|
1443
|
+
const targetImage = `ghcr.io/openclaw/openclaw:${target}`;
|
|
1444
|
+
const ctx = buildContext(args);
|
|
1445
|
+
const stack = await ctx.getStack();
|
|
1446
|
+
const outputs = Object.fromEntries(
|
|
1447
|
+
Object.entries(await stack.outputs()).map(([k, v]) => [k, v.value])
|
|
1448
|
+
);
|
|
1449
|
+
const conn = ctx.adapter.getConnectionInfo({
|
|
1450
|
+
...extractBaseOutputs(outputs),
|
|
1451
|
+
privateKeyPath: ctx.config.ssh.keyPath,
|
|
1452
|
+
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
1453
|
+
});
|
|
1454
|
+
const ac = new AbortController();
|
|
1455
|
+
process15.on("SIGINT", () => ac.abort());
|
|
1456
|
+
const { session, release } = await acquireSession({
|
|
1457
|
+
host: conn.host,
|
|
1458
|
+
port: conn.port,
|
|
1459
|
+
user: conn.user,
|
|
1460
|
+
privateKeyPath: conn.privateKeyPath,
|
|
1461
|
+
knownHostsPath: conn.knownHostsPath,
|
|
1462
|
+
signal: ac.signal
|
|
1463
|
+
});
|
|
1464
|
+
const spin = spinner("Inspecting the deployment...");
|
|
1465
|
+
const run = (cmd) => execPrivileged(session, cmd, ac.signal);
|
|
1466
|
+
const archive = "/tmp/clawops-premigration.tar.gz";
|
|
1467
|
+
const configPath = `${STATE_DIR_HOST_LINUX}/${CONFIG_FILENAME}`;
|
|
1468
|
+
const steps = {
|
|
1469
|
+
inspectSource: async () => {
|
|
1470
|
+
const r = await run(`docker inspect openclaw --format '{{.Config.Image}}'`);
|
|
1471
|
+
const image = r.stdout.trim();
|
|
1472
|
+
return r.code === 0 && image.includes(":") ? image : void 0;
|
|
1473
|
+
},
|
|
1474
|
+
backup: async () => {
|
|
1475
|
+
spin.text = "Taking a verified backup...";
|
|
1476
|
+
const r = await run(
|
|
1477
|
+
`docker exec openclaw openclaw backup create --output ${archive} --verify --json`
|
|
1478
|
+
);
|
|
1479
|
+
return r.code === 0 ? { ok: true, detail: `${archive} on the host (verified)` } : { ok: false, detail: (r.stderr || r.stdout).slice(0, 200) };
|
|
1480
|
+
},
|
|
1481
|
+
extract: async () => {
|
|
1482
|
+
spin.text = "Extracting state from the running container...";
|
|
1483
|
+
const mk = await run(`mkdir -p ${STATE_DIR_HOST_LINUX}`);
|
|
1484
|
+
if (mk.code !== 0) return { ok: false, entries: [] };
|
|
1485
|
+
const cp = await run(`docker cp openclaw:/home/node/.openclaw/. ${STATE_DIR_HOST_LINUX}/`);
|
|
1486
|
+
if (cp.code !== 0) return { ok: false, entries: [] };
|
|
1487
|
+
const ls = await run(`ls ${STATE_DIR_HOST_LINUX}`);
|
|
1488
|
+
return { ok: true, entries: ls.stdout.trim().split(/\s+/).filter(Boolean) };
|
|
1489
|
+
},
|
|
1490
|
+
chown: async () => {
|
|
1491
|
+
await run(`chown -R ${CONTAINER_UID}:${CONTAINER_UID} ${STATE_DIR_HOST_LINUX}`);
|
|
1492
|
+
},
|
|
1493
|
+
removeSource: async () => {
|
|
1494
|
+
spin.text = "Stopping the 1.x container...";
|
|
1495
|
+
await run("docker stop openclaw 2>/dev/null || true");
|
|
1496
|
+
await run("docker rm openclaw 2>/dev/null || true");
|
|
1497
|
+
},
|
|
1498
|
+
writeConfig: async () => {
|
|
1499
|
+
const b64 = Buffer.from(SYNTHESISED_CONFIG, "utf-8").toString("base64");
|
|
1500
|
+
await run(
|
|
1501
|
+
`echo '${b64}' | base64 -d > ${configPath} && chown ${CONTAINER_UID}:${CONTAINER_UID} ${configPath}`
|
|
1502
|
+
);
|
|
1503
|
+
},
|
|
1504
|
+
start: async () => {
|
|
1505
|
+
spin.text = "Starting the 2.0 gateway...";
|
|
1506
|
+
await run(gatewayRunCommand({ image: targetImage, stateDir: STATE_DIR_HOST_LINUX }));
|
|
1507
|
+
},
|
|
1508
|
+
gate: async () => {
|
|
1509
|
+
spin.text = "Waiting for the gateway to start...";
|
|
1510
|
+
let last;
|
|
1511
|
+
for (let i = 0; i < 15; i++) {
|
|
1512
|
+
if (ac.signal.aborted) return { ok: false, reason: "aborted" };
|
|
1513
|
+
const r = await run(probeCommand("started", GATEWAY_PORT));
|
|
1514
|
+
const v = interpretProbe("started", r.stdout);
|
|
1515
|
+
if (v.ok) return { ok: true };
|
|
1516
|
+
last = v.reason;
|
|
1517
|
+
await new Promise((res) => setTimeout(res, 2e3));
|
|
1518
|
+
}
|
|
1519
|
+
return { ok: false, reason: last ?? "no response" };
|
|
1520
|
+
},
|
|
1521
|
+
deviceId: async () => {
|
|
1522
|
+
const file = await run(
|
|
1523
|
+
`docker exec openclaw sh -c 'cat ~/.openclaw/identity/device.json 2>/dev/null' || cat ${STATE_DIR_HOST_LINUX}/identity/device.json 2>/dev/null || true`
|
|
1524
|
+
);
|
|
1525
|
+
try {
|
|
1526
|
+
const parsed = JSON.parse(file.stdout.trim());
|
|
1527
|
+
return parsed.deviceId;
|
|
1528
|
+
} catch {
|
|
1529
|
+
return void 0;
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
try {
|
|
1534
|
+
const outcome = await migrate(steps);
|
|
1535
|
+
spin.stop();
|
|
1536
|
+
const report = describeMigration(outcome);
|
|
1537
|
+
if (outcome.kind === "migrated") {
|
|
1538
|
+
success(report.split("\n")[0]);
|
|
1539
|
+
for (const line2 of report.split("\n").slice(1)) {
|
|
1540
|
+
if (/CHANGED|could not be compared/.test(line2)) warn(line2);
|
|
1541
|
+
else info(line2);
|
|
1542
|
+
}
|
|
1543
|
+
info(`Pre-migration backup: ${archive}`);
|
|
1544
|
+
} else {
|
|
1545
|
+
failure(report);
|
|
1546
|
+
process15.exit(outcome.kind === "nothing-to-migrate" ? 0 : 1);
|
|
1547
|
+
}
|
|
1548
|
+
} finally {
|
|
1549
|
+
release();
|
|
1550
|
+
drainPool();
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
|
|
1555
|
+
// src/cli/commands/gateway.ts
|
|
1556
|
+
import { defineCommand as defineCommand14 } from "citty";
|
|
1557
|
+
import process16 from "process";
|
|
1558
|
+
function dockerRunCmd2(version, publish = "loopback") {
|
|
1262
1559
|
return gatewayRunCommand({
|
|
1263
1560
|
image: `ghcr.io/openclaw/openclaw:${version}`,
|
|
1264
|
-
|
|
1561
|
+
stateDir: STATE_DIR_HOST_LINUX,
|
|
1562
|
+
publish
|
|
1265
1563
|
});
|
|
1266
1564
|
}
|
|
1267
|
-
var gateway_default =
|
|
1565
|
+
var gateway_default = defineCommand14({
|
|
1268
1566
|
meta: {
|
|
1269
1567
|
name: "gateway",
|
|
1270
1568
|
description: "Manage the OpenClaw gateway daemon (status | restart | update [version])"
|
|
1271
1569
|
},
|
|
1272
1570
|
args: {
|
|
1273
1571
|
stack: { type: "string", description: "Target stack name" },
|
|
1274
|
-
channel: { type: "string", description: "
|
|
1572
|
+
channel: { type: "string", description: "Version for update (a concrete pin; moving tags are refused)" },
|
|
1275
1573
|
json: { type: "boolean", description: "Emit JSON (for status)" }
|
|
1276
1574
|
},
|
|
1277
1575
|
async run({ args }) {
|
|
1278
|
-
const { buildContext } = await import("./context-
|
|
1576
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
1279
1577
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1280
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
1578
|
+
const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
|
|
1281
1579
|
const [action, versionArg] = args._ ?? [];
|
|
1282
1580
|
if (!action || !["status", "restart", "update"].includes(action)) {
|
|
1283
1581
|
failure("Usage: clawops gateway <status | restart | update [version]>");
|
|
1284
|
-
|
|
1582
|
+
process16.exit(2);
|
|
1285
1583
|
}
|
|
1286
1584
|
const ctx = buildContext(args);
|
|
1287
1585
|
const stack = await ctx.getStack();
|
|
@@ -1296,8 +1594,8 @@ var gateway_default = defineCommand13({
|
|
|
1296
1594
|
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
1297
1595
|
});
|
|
1298
1596
|
const abortController = new AbortController();
|
|
1299
|
-
|
|
1300
|
-
|
|
1597
|
+
process16.on("SIGINT", () => abortController.abort());
|
|
1598
|
+
process16.on("SIGTERM", () => abortController.abort());
|
|
1301
1599
|
const { session, release } = await acquireSession({
|
|
1302
1600
|
host: conn.host,
|
|
1303
1601
|
port: conn.port,
|
|
@@ -1318,7 +1616,7 @@ var gateway_default = defineCommand13({
|
|
|
1318
1616
|
if (args.json) {
|
|
1319
1617
|
printJson(jsonOk(status));
|
|
1320
1618
|
} else {
|
|
1321
|
-
|
|
1619
|
+
process16.stdout.write(
|
|
1322
1620
|
"\n" + renderTable(
|
|
1323
1621
|
["Field", "Value"],
|
|
1324
1622
|
[
|
|
@@ -1330,40 +1628,145 @@ var gateway_default = defineCommand13({
|
|
|
1330
1628
|
);
|
|
1331
1629
|
}
|
|
1332
1630
|
} else if (action === "restart") {
|
|
1333
|
-
const imgResult = await session
|
|
1631
|
+
const imgResult = await execPrivileged(session, IMAGE_INSPECT_CMD, abortController.signal);
|
|
1334
1632
|
const image = imageForRestart(imgResult.stdout);
|
|
1335
1633
|
if (!image.ok) {
|
|
1336
1634
|
failure(image.error);
|
|
1337
|
-
|
|
1635
|
+
process16.exit(1);
|
|
1338
1636
|
}
|
|
1339
1637
|
const version = versionOf(image.value);
|
|
1638
|
+
const pub = await execPrivileged(session, PUBLISH_INSPECT_CMD, abortController.signal);
|
|
1639
|
+
const publish = publishForRestart(pub.stdout);
|
|
1340
1640
|
const spin = spinner("Restarting gateway...");
|
|
1341
|
-
const result = await
|
|
1641
|
+
const result = await execPrivileged(
|
|
1642
|
+
session,
|
|
1643
|
+
dockerRunCmd2(version, publish),
|
|
1644
|
+
abortController.signal
|
|
1645
|
+
);
|
|
1342
1646
|
spin.stop();
|
|
1343
1647
|
if (result.code !== 0) {
|
|
1344
1648
|
failure(`Restart failed: ${result.stderr}`);
|
|
1345
|
-
|
|
1649
|
+
process16.exit(1);
|
|
1346
1650
|
}
|
|
1347
1651
|
success(`Gateway restarted (${version}).`);
|
|
1348
1652
|
} else {
|
|
1349
|
-
const
|
|
1653
|
+
const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-GPSTCPT7.js");
|
|
1654
|
+
const requested = versionArg ?? args.channel;
|
|
1655
|
+
const version = requested ? await guardOpenclawVersion(requested) : await defaultOpenclawVersion();
|
|
1350
1656
|
const spin = spinner(`Updating gateway to ${version}...`);
|
|
1351
|
-
const
|
|
1352
|
-
|
|
1657
|
+
const targetImage = `ghcr.io/openclaw/openclaw:${version}`;
|
|
1658
|
+
const pullResult = await execPrivileged(
|
|
1659
|
+
session,
|
|
1660
|
+
`docker pull ${targetImage}`,
|
|
1353
1661
|
abortController.signal
|
|
1354
1662
|
);
|
|
1355
1663
|
if (pullResult.code !== 0) {
|
|
1356
1664
|
spin.stop();
|
|
1357
1665
|
failure(`Pull failed: ${pullResult.stderr}`);
|
|
1358
|
-
|
|
1666
|
+
process16.exit(1);
|
|
1359
1667
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1668
|
+
spin.text = "Checking state compatibility...";
|
|
1669
|
+
const {
|
|
1670
|
+
snapshotCommand,
|
|
1671
|
+
snapshotPathFrom,
|
|
1672
|
+
preflightCommand,
|
|
1673
|
+
parsePreflight,
|
|
1674
|
+
judgePreflight
|
|
1675
|
+
} = await import("./upgrade-ULD4KFYC.js");
|
|
1676
|
+
const cur = imageForRestart(
|
|
1677
|
+
(await execPrivileged(session, IMAGE_INSPECT_CMD, abortController.signal)).stdout
|
|
1678
|
+
);
|
|
1679
|
+
const snapRepo = `${STATE_DIR_HOST_LINUX}/snapshots`;
|
|
1680
|
+
const snapOut = await execPrivileged(
|
|
1681
|
+
session,
|
|
1682
|
+
snapshotCommand(cur.ok ? cur.value : targetImage, STATE_DIR_HOST_LINUX, snapRepo),
|
|
1683
|
+
abortController.signal
|
|
1684
|
+
);
|
|
1685
|
+
const snapPath = snapshotPathFrom(snapOut.stdout);
|
|
1686
|
+
if (!snapPath) {
|
|
1687
|
+
spin.stop();
|
|
1688
|
+
failure(
|
|
1689
|
+
"Could not snapshot the state database before upgrading, so neither the compatibility check nor a rollback point is available.\n" + (snapOut.stderr || snapOut.stdout).slice(0, 300)
|
|
1690
|
+
);
|
|
1691
|
+
process16.exit(1);
|
|
1692
|
+
}
|
|
1693
|
+
const pre = await execPrivileged(
|
|
1694
|
+
session,
|
|
1695
|
+
preflightCommand(targetImage, STATE_DIR_HOST_LINUX, `${snapPath}/database.sqlite`),
|
|
1696
|
+
abortController.signal
|
|
1697
|
+
);
|
|
1698
|
+
const report = parsePreflight(pre.stdout);
|
|
1699
|
+
const verdict = report ? judgePreflight(report) : { ok: false, reason: `preflight produced no readable report: ${pre.stderr.slice(0, 200)}` };
|
|
1700
|
+
if (!verdict.ok) {
|
|
1701
|
+
spin.stop();
|
|
1702
|
+
failure(`Refusing to upgrade to ${version}: ${verdict.reason}`);
|
|
1703
|
+
info(`A snapshot of the current state was kept at ${snapPath}.`);
|
|
1704
|
+
process16.exit(1);
|
|
1705
|
+
}
|
|
1706
|
+
if (verdict.ok && verdict.note) info(verdict.note);
|
|
1707
|
+
spin.text = `Updating gateway to ${version}...`;
|
|
1708
|
+
const pubU = await execPrivileged(session, PUBLISH_INSPECT_CMD, abortController.signal);
|
|
1709
|
+
const runResult = await execPrivileged(
|
|
1710
|
+
session,
|
|
1711
|
+
dockerRunCmd2(version, publishForRestart(pubU.stdout)),
|
|
1712
|
+
abortController.signal
|
|
1713
|
+
);
|
|
1362
1714
|
if (runResult.code !== 0) {
|
|
1715
|
+
spin.stop();
|
|
1363
1716
|
failure(`Start failed: ${runResult.stderr}`);
|
|
1364
|
-
|
|
1717
|
+
info(`State snapshot from before the upgrade: ${snapPath}`);
|
|
1718
|
+
process16.exit(1);
|
|
1719
|
+
}
|
|
1720
|
+
spin.text = "Waiting for the gateway to start...";
|
|
1721
|
+
const { probeCommand: probeCommand2, interpretProbe: interpretProbe2 } = await import("./health-5CCOXYGG.js");
|
|
1722
|
+
const { repairCommand, describeOutcome } = await import("./upgrade-ULD4KFYC.js");
|
|
1723
|
+
const publish = publishForRestart(pubU.stdout);
|
|
1724
|
+
const gate = async () => {
|
|
1725
|
+
let last;
|
|
1726
|
+
for (let i = 0; i < 15; i++) {
|
|
1727
|
+
if (abortController.signal.aborted) return { ok: false, reason: "aborted" };
|
|
1728
|
+
const r = await execPrivileged(
|
|
1729
|
+
session,
|
|
1730
|
+
probeCommand2("started", GATEWAY_PORT),
|
|
1731
|
+
abortController.signal
|
|
1732
|
+
);
|
|
1733
|
+
const v = interpretProbe2("started", r.stdout);
|
|
1734
|
+
if (v.ok) return { ok: true };
|
|
1735
|
+
last = v.reason;
|
|
1736
|
+
await new Promise((res) => setTimeout(res, 2e3));
|
|
1737
|
+
}
|
|
1738
|
+
return { ok: false, reason: last ?? "no response" };
|
|
1739
|
+
};
|
|
1740
|
+
const { resolveUpgrade } = await import("./upgrade-ULD4KFYC.js");
|
|
1741
|
+
const outcome = await resolveUpgrade(
|
|
1742
|
+
{
|
|
1743
|
+
gate,
|
|
1744
|
+
repair: async () => {
|
|
1745
|
+
spin.text = "Gateway did not start; attempting one-shot repair...";
|
|
1746
|
+
await execPrivileged(
|
|
1747
|
+
session,
|
|
1748
|
+
repairCommand(targetImage, STATE_DIR_HOST_LINUX),
|
|
1749
|
+
abortController.signal
|
|
1750
|
+
);
|
|
1751
|
+
},
|
|
1752
|
+
run: async (v) => {
|
|
1753
|
+
await execPrivileged(session, dockerRunCmd2(v, publish), abortController.signal);
|
|
1754
|
+
}
|
|
1755
|
+
},
|
|
1756
|
+
{
|
|
1757
|
+
version,
|
|
1758
|
+
previousVersion: cur.ok ? versionOf(cur.value) : void 0,
|
|
1759
|
+
snapshotPath: snapPath
|
|
1760
|
+
}
|
|
1761
|
+
);
|
|
1762
|
+
spin.stop();
|
|
1763
|
+
const message = describeOutcome(outcome, version);
|
|
1764
|
+
if (outcome.kind === "started" || outcome.kind === "repaired") {
|
|
1765
|
+
success(message);
|
|
1766
|
+
} else {
|
|
1767
|
+
failure(message);
|
|
1768
|
+
process16.exit(1);
|
|
1365
1769
|
}
|
|
1366
|
-
success(`Gateway updated to ${version}.`);
|
|
1367
1770
|
}
|
|
1368
1771
|
} finally {
|
|
1369
1772
|
release();
|
|
@@ -1373,11 +1776,11 @@ var gateway_default = defineCommand13({
|
|
|
1373
1776
|
});
|
|
1374
1777
|
|
|
1375
1778
|
// src/cli/commands/backup.ts
|
|
1376
|
-
import { defineCommand as
|
|
1779
|
+
import { defineCommand as defineCommand15 } from "citty";
|
|
1377
1780
|
import { createWriteStream } from "fs";
|
|
1378
1781
|
import { pipeline } from "stream/promises";
|
|
1379
|
-
import
|
|
1380
|
-
var backup_default =
|
|
1782
|
+
import process17 from "process";
|
|
1783
|
+
var backup_default = defineCommand15({
|
|
1381
1784
|
meta: {
|
|
1382
1785
|
name: "backup",
|
|
1383
1786
|
description: "Create an OpenClaw backup (restore is unavailable on this release line)"
|
|
@@ -1390,8 +1793,8 @@ var backup_default = defineCommand14({
|
|
|
1390
1793
|
yes: { type: "boolean", description: "[restore] Skip confirmation prompt" }
|
|
1391
1794
|
},
|
|
1392
1795
|
async run({ args }) {
|
|
1393
|
-
const { buildContext } = await import("./context-
|
|
1394
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
1796
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
1797
|
+
const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
|
|
1395
1798
|
const action = args.action;
|
|
1396
1799
|
if (action !== "create" && action !== "restore") {
|
|
1397
1800
|
throw new UsageError(`Unknown action: ${action}. Use "create" or "restore"`);
|
|
@@ -1402,7 +1805,7 @@ var backup_default = defineCommand14({
|
|
|
1402
1805
|
const state = ctx.localState;
|
|
1403
1806
|
if (!state) {
|
|
1404
1807
|
failure("Stack has no state. Run `clawops up` first.");
|
|
1405
|
-
|
|
1808
|
+
process17.exit(4);
|
|
1406
1809
|
}
|
|
1407
1810
|
conn = {
|
|
1408
1811
|
host: state.sshHost,
|
|
@@ -1420,7 +1823,7 @@ var backup_default = defineCommand14({
|
|
|
1420
1823
|
);
|
|
1421
1824
|
if (!outputs["publicIp"]) {
|
|
1422
1825
|
failure("Stack has no outputs. Run `clawops up` first.");
|
|
1423
|
-
|
|
1826
|
+
process17.exit(4);
|
|
1424
1827
|
}
|
|
1425
1828
|
const base = extractBaseOutputs(outputs);
|
|
1426
1829
|
conn = ctx.adapter.getConnectionInfo({
|
|
@@ -1430,8 +1833,8 @@ var backup_default = defineCommand14({
|
|
|
1430
1833
|
});
|
|
1431
1834
|
}
|
|
1432
1835
|
const abortController = new AbortController();
|
|
1433
|
-
|
|
1434
|
-
|
|
1836
|
+
process17.on("SIGINT", () => abortController.abort());
|
|
1837
|
+
process17.on("SIGTERM", () => abortController.abort());
|
|
1435
1838
|
const { session, release } = await acquireSession({
|
|
1436
1839
|
host: conn.host,
|
|
1437
1840
|
port: conn.port,
|
|
@@ -1446,7 +1849,8 @@ var backup_default = defineCommand14({
|
|
|
1446
1849
|
info(`Writing backup to ${outPath}...`);
|
|
1447
1850
|
const spin = spinner("Creating backup on remote host...");
|
|
1448
1851
|
const remoteArchive = "/tmp/clawops-backup.tar.gz";
|
|
1449
|
-
const createResult = await
|
|
1852
|
+
const createResult = await execPrivileged(
|
|
1853
|
+
session,
|
|
1450
1854
|
`docker exec openclaw sh -lc 'rm -f ${remoteArchive} && openclaw backup create --output ${remoteArchive} --verify --json'`,
|
|
1451
1855
|
abortController.signal
|
|
1452
1856
|
);
|
|
@@ -1454,23 +1858,72 @@ var backup_default = defineCommand14({
|
|
|
1454
1858
|
spin.stop();
|
|
1455
1859
|
throw new Error(`Backup failed on the remote host: ${createResult.stderr.slice(0, 300)}`);
|
|
1456
1860
|
}
|
|
1457
|
-
const backupStream = await
|
|
1861
|
+
const backupStream = await streamPrivileged(
|
|
1862
|
+
session,
|
|
1458
1863
|
`docker exec openclaw cat ${remoteArchive}`,
|
|
1459
1864
|
abortController.signal
|
|
1460
1865
|
);
|
|
1461
1866
|
spin.stop();
|
|
1462
|
-
const fileStream = createWriteStream(outPath);
|
|
1867
|
+
const fileStream = createWriteStream(outPath, { mode: 384 });
|
|
1463
1868
|
await pipeline(backupStream, fileStream);
|
|
1464
|
-
await
|
|
1869
|
+
await execPrivileged(
|
|
1870
|
+
session,
|
|
1465
1871
|
`docker exec openclaw rm -f ${remoteArchive}`,
|
|
1466
1872
|
abortController.signal
|
|
1467
1873
|
);
|
|
1468
1874
|
success(`Backup saved to ${outPath}`);
|
|
1469
|
-
info("The archive contains
|
|
1875
|
+
info("Saved with mode 0600. The archive contains the state database \u2014 OAuth tokens,");
|
|
1876
|
+
info("secrets and device credentials, unencrypted. Treat it as a credential.");
|
|
1470
1877
|
} else {
|
|
1471
|
-
|
|
1472
|
-
|
|
1878
|
+
const file = typeof args.file === "string" ? args.file : void 0;
|
|
1879
|
+
if (!file) {
|
|
1880
|
+
throw new UsageError("Usage: clawops backup restore --file <archive.tar.gz>");
|
|
1881
|
+
}
|
|
1882
|
+
const { createReadStream } = await import("fs");
|
|
1883
|
+
const remoteArchive = "/tmp/clawops-restore.tar.gz";
|
|
1884
|
+
const staging = `/tmp/clawops-restored-${Date.now()}`;
|
|
1885
|
+
const spin = spinner("Uploading archive...");
|
|
1886
|
+
const uploadResult = await execPrivilegedWithInput(
|
|
1887
|
+
session,
|
|
1888
|
+
`docker exec -i openclaw sh -c 'cat > ${remoteArchive}'`,
|
|
1889
|
+
createReadStream(file),
|
|
1890
|
+
abortController.signal
|
|
1891
|
+
);
|
|
1892
|
+
if (uploadResult.code !== 0) {
|
|
1893
|
+
spin.stop();
|
|
1894
|
+
throw new Error(`Could not upload the archive: ${uploadResult.stderr.slice(0, 300)}`);
|
|
1895
|
+
}
|
|
1896
|
+
spin.text = "Verifying and restoring to a staging directory...";
|
|
1897
|
+
const restore = await execPrivileged(
|
|
1898
|
+
session,
|
|
1899
|
+
`docker exec openclaw openclaw backup restore ${remoteArchive} --target ${staging} --json`,
|
|
1900
|
+
abortController.signal
|
|
1473
1901
|
);
|
|
1902
|
+
await execPrivileged(
|
|
1903
|
+
session,
|
|
1904
|
+
`docker exec openclaw rm -f ${remoteArchive}`,
|
|
1905
|
+
abortController.signal
|
|
1906
|
+
);
|
|
1907
|
+
spin.stop();
|
|
1908
|
+
if (restore.code !== 0) {
|
|
1909
|
+
throw new Error(`Restore failed: ${(restore.stderr || restore.stdout).slice(0, 400)}`);
|
|
1910
|
+
}
|
|
1911
|
+
let report = {};
|
|
1912
|
+
try {
|
|
1913
|
+
report = JSON.parse(restore.stdout.trim());
|
|
1914
|
+
} catch {
|
|
1915
|
+
info(restore.stdout);
|
|
1916
|
+
}
|
|
1917
|
+
success(`Archive verified and restored to ${staging} on the host.`);
|
|
1918
|
+
if (report.entryCount !== void 0) info(`${report.entryCount} entries restored.`);
|
|
1919
|
+
for (const w of report.warnings ?? []) warn(w);
|
|
1920
|
+
info("");
|
|
1921
|
+
info("Nothing has been activated. To adopt the restored state:");
|
|
1922
|
+
info(" 1. clawops ssh --command 'sudo docker stop openclaw'");
|
|
1923
|
+
info(` 2. replace the state directory contents with ${staging}`);
|
|
1924
|
+
info(" 3. clawops gateway restart");
|
|
1925
|
+
info("Provider plugins are not carried in the archive; re-run `clawops apply` to");
|
|
1926
|
+
info("reinstall them, or the gateway starts without its model providers.");
|
|
1474
1927
|
}
|
|
1475
1928
|
} finally {
|
|
1476
1929
|
release();
|
|
@@ -1480,9 +1933,9 @@ var backup_default = defineCommand14({
|
|
|
1480
1933
|
});
|
|
1481
1934
|
|
|
1482
1935
|
// src/cli/commands/stacks.ts
|
|
1483
|
-
import { defineCommand as
|
|
1484
|
-
import
|
|
1485
|
-
var stacks_default =
|
|
1936
|
+
import { defineCommand as defineCommand16 } from "citty";
|
|
1937
|
+
import process18 from "process";
|
|
1938
|
+
var stacks_default = defineCommand16({
|
|
1486
1939
|
meta: {
|
|
1487
1940
|
name: "stacks",
|
|
1488
1941
|
description: "Manage clawops stacks (list | delete <name>)"
|
|
@@ -1496,7 +1949,7 @@ var stacks_default = defineCommand15({
|
|
|
1496
1949
|
const [action, name] = args._ ?? [];
|
|
1497
1950
|
if (!action || !["list", "delete"].includes(action)) {
|
|
1498
1951
|
failure("Usage: clawops stacks <list | delete <name>>");
|
|
1499
|
-
|
|
1952
|
+
process18.exit(2);
|
|
1500
1953
|
}
|
|
1501
1954
|
if (action === "list") {
|
|
1502
1955
|
const config2 = requireConfig();
|
|
@@ -1519,7 +1972,7 @@ var stacks_default = defineCommand15({
|
|
|
1519
1972
|
} else if (rows.length === 0) {
|
|
1520
1973
|
info("No stacks configured.");
|
|
1521
1974
|
} else {
|
|
1522
|
-
|
|
1975
|
+
process18.stdout.write(
|
|
1523
1976
|
"\n" + renderTable(
|
|
1524
1977
|
["Name", "Provider", "Region", "State URL"],
|
|
1525
1978
|
rows
|
|
@@ -1531,7 +1984,7 @@ var stacks_default = defineCommand15({
|
|
|
1531
1984
|
}
|
|
1532
1985
|
if (!name) {
|
|
1533
1986
|
failure("Usage: clawops stacks delete <name>");
|
|
1534
|
-
|
|
1987
|
+
process18.exit(2);
|
|
1535
1988
|
}
|
|
1536
1989
|
const config = requireConfig();
|
|
1537
1990
|
if (!(name in config.stacks)) {
|
|
@@ -1551,7 +2004,7 @@ var stacks_default = defineCommand15({
|
|
|
1551
2004
|
let isDeployed = false;
|
|
1552
2005
|
if (!args.force) {
|
|
1553
2006
|
try {
|
|
1554
|
-
const { buildContext } = await import("./context-
|
|
2007
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
1555
2008
|
const ctx = buildContext({ stack: name });
|
|
1556
2009
|
if (ctx.adapter.name === "local") {
|
|
1557
2010
|
isDeployed = !!ctx.localState;
|
|
@@ -1571,7 +2024,7 @@ var stacks_default = defineCommand15({
|
|
|
1571
2024
|
failure(
|
|
1572
2025
|
`Stack "${name}" is still deployed. Run \`clawops down --stack ${name}\` first to destroy cloud resources, or pass --force to remove from registry only.`
|
|
1573
2026
|
);
|
|
1574
|
-
|
|
2027
|
+
process18.exit(1);
|
|
1575
2028
|
}
|
|
1576
2029
|
warn(
|
|
1577
2030
|
`This removes "${name}" from clawops config only. Cloud resources are NOT destroyed. Run \`clawops destroy --stack ` + name + "` first if you want to remove cloud resources."
|
|
@@ -1599,7 +2052,7 @@ var stacks_default = defineCommand15({
|
|
|
1599
2052
|
});
|
|
1600
2053
|
async function confirm(message) {
|
|
1601
2054
|
const { createInterface: createInterface3 } = await import("readline/promises");
|
|
1602
|
-
const rl = createInterface3({ input:
|
|
2055
|
+
const rl = createInterface3({ input: process18.stdin, output: process18.stdout });
|
|
1603
2056
|
try {
|
|
1604
2057
|
const answer = await rl.question(`${message} (y/N) `);
|
|
1605
2058
|
return answer.trim().toLowerCase() === "y";
|
|
@@ -1609,286 +2062,68 @@ async function confirm(message) {
|
|
|
1609
2062
|
}
|
|
1610
2063
|
|
|
1611
2064
|
// src/cli/commands/doctor.ts
|
|
1612
|
-
import { defineCommand as
|
|
1613
|
-
import
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
var doctor_default = defineCommand16({
|
|
2065
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
2066
|
+
import process19 from "process";
|
|
2067
|
+
var NAME_COLUMN = 13;
|
|
2068
|
+
var doctor_default = defineCommand17({
|
|
1617
2069
|
meta: {
|
|
1618
2070
|
name: "doctor",
|
|
1619
2071
|
description: "Check system prerequisites, config, SSH keys, and cloud credentials"
|
|
1620
2072
|
},
|
|
1621
2073
|
args: {
|
|
1622
|
-
stack: { type: "string", description: "Stack name to include remote health checks" }
|
|
2074
|
+
stack: { type: "string", description: "Stack name to include remote health checks" },
|
|
2075
|
+
json: { type: "boolean", description: "Emit the report as JSON" }
|
|
1623
2076
|
},
|
|
1624
2077
|
async run({ args }) {
|
|
1625
|
-
const
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
const nodeOk = nodeMajor >= 22;
|
|
1631
|
-
if (nodeOk) {
|
|
1632
|
-
success(`Node.js ${nodeVersion}`);
|
|
1633
|
-
} else {
|
|
1634
|
-
failure(`Node.js ${nodeVersion} (requires >=22)`);
|
|
1635
|
-
}
|
|
1636
|
-
const configDir = getConfigDir2();
|
|
1637
|
-
const pulumiHome = path2.join(configDir, ".pulumi");
|
|
2078
|
+
const ac = new AbortController();
|
|
2079
|
+
const abort = () => ac.abort();
|
|
2080
|
+
process19.on("SIGINT", abort);
|
|
2081
|
+
process19.on("SIGTERM", abort);
|
|
2082
|
+
let report;
|
|
1638
2083
|
try {
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
}
|
|
1644
|
-
process18.stdout.write("\nConfig\n");
|
|
1645
|
-
const config = getConfig2();
|
|
1646
|
-
if (!config) {
|
|
1647
|
-
warn("No config file found \u2014 run `clawops init` to create one");
|
|
1648
|
-
} else {
|
|
1649
|
-
success(`Config file ${path2.join(configDir, "config.json")}`);
|
|
1650
|
-
}
|
|
1651
|
-
process18.stdout.write("\nSSH\n");
|
|
1652
|
-
if (!config) {
|
|
1653
|
-
info("SSH checks skipped \u2014 no config");
|
|
1654
|
-
} else {
|
|
1655
|
-
const keyPath = config.ssh.keyPath.replace(/^~/, process18.env["HOME"] ?? "~");
|
|
1656
|
-
try {
|
|
1657
|
-
accessSync(keyPath, constants.R_OK);
|
|
1658
|
-
success(`SSH key ${keyPath}`);
|
|
1659
|
-
} catch {
|
|
1660
|
-
failure(`SSH key ${keyPath} (not found or not readable)`);
|
|
1661
|
-
}
|
|
1662
|
-
const knownHostsPath = config.ssh.knownHostsPath.replace(/^~/, process18.env["HOME"] ?? "~");
|
|
1663
|
-
try {
|
|
1664
|
-
accessSync(knownHostsPath, constants.F_OK);
|
|
1665
|
-
success(`known_hosts ${knownHostsPath}`);
|
|
1666
|
-
} catch {
|
|
1667
|
-
warn(`known_hosts ${knownHostsPath} (does not exist \u2014 will be created on first connect)`);
|
|
1668
|
-
}
|
|
2084
|
+
report = await runDiagnostics({ stack: args.stack, signal: ac.signal });
|
|
2085
|
+
} finally {
|
|
2086
|
+
process19.off("SIGINT", abort);
|
|
2087
|
+
process19.off("SIGTERM", abort);
|
|
1669
2088
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
info("Credential checks skipped \u2014 no config");
|
|
2089
|
+
if (args.json) {
|
|
2090
|
+
printJson(jsonOk(report));
|
|
1673
2091
|
} else {
|
|
1674
|
-
|
|
1675
|
-
await import("./aws-L4RR2LSE.js");
|
|
1676
|
-
await import("./gcp-UG6AWPIZ.js");
|
|
1677
|
-
await import("./azure-7OXQENQL.js");
|
|
1678
|
-
await import("./local-DXBEVZ5C.js");
|
|
1679
|
-
const checkedProviders = /* @__PURE__ */ new Set();
|
|
1680
|
-
for (const [stackName, stackCfg] of Object.entries(config.stacks)) {
|
|
1681
|
-
const providerName = stackCfg.provider;
|
|
1682
|
-
if (checkedProviders.has(providerName)) continue;
|
|
1683
|
-
checkedProviders.add(providerName);
|
|
1684
|
-
if (providerName === "local") {
|
|
1685
|
-
success(`local stack "${stackName}" (SSH-only, no cloud credentials required)`);
|
|
1686
|
-
continue;
|
|
1687
|
-
}
|
|
1688
|
-
try {
|
|
1689
|
-
const adapter = getProvider(providerName);
|
|
1690
|
-
const result = await adapter.validateConfig();
|
|
1691
|
-
if (result.ok) {
|
|
1692
|
-
success(`${providerName} stack "${stackName}"`);
|
|
1693
|
-
} else {
|
|
1694
|
-
for (const err of result.errors) {
|
|
1695
|
-
failure(`${providerName} stack "${stackName}" \u2014 ${err}`);
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
} catch (err) {
|
|
1699
|
-
failure(`${providerName} stack "${stackName}" \u2014 ${err instanceof Error ? err.message : String(err)}`);
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
if (checkedProviders.size === 0) {
|
|
1703
|
-
warn("No stacks configured \u2014 run `clawops init`");
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
process18.stdout.write("\nOpenClaw\n");
|
|
1707
|
-
try {
|
|
1708
|
-
const yaml = await import("./js-yaml-PTEEG4FO.js");
|
|
1709
|
-
const { loadVersionSpec, describeRange } = await import("./versions-2UVAT6PQ.js");
|
|
1710
|
-
const support = loadVersionSpec(yaml).support;
|
|
1711
|
-
success(`Supported range ${describeRange(support)} (recommended ${support.recommended})`);
|
|
1712
|
-
if (!support.max) {
|
|
1713
|
-
warn("No upper bound declared \u2014 this line would accept any OpenClaw release");
|
|
1714
|
-
}
|
|
1715
|
-
} catch (e) {
|
|
1716
|
-
failure(`Version matrix ${e.message}`);
|
|
1717
|
-
}
|
|
1718
|
-
if (args.stack) {
|
|
1719
|
-
process18.stdout.write("\nRemote health\n");
|
|
1720
|
-
if (!config) {
|
|
1721
|
-
info("Remote health skipped \u2014 no config");
|
|
1722
|
-
} else {
|
|
1723
|
-
try {
|
|
1724
|
-
const { buildContext } = await import("./context-5LCUG2QR.js");
|
|
1725
|
-
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1726
|
-
const { acquireSession, drainPool } = await import("./pool-QGFQRMGU.js");
|
|
1727
|
-
const ctx = buildContext({ stack: args.stack });
|
|
1728
|
-
const stack = await ctx.getStack();
|
|
1729
|
-
const outputMap = await stack.outputs();
|
|
1730
|
-
const outputs = Object.fromEntries(
|
|
1731
|
-
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
1732
|
-
);
|
|
1733
|
-
const base = extractBaseOutputs(outputs);
|
|
1734
|
-
const conn = ctx.adapter.getConnectionInfo({
|
|
1735
|
-
...base,
|
|
1736
|
-
privateKeyPath: config.ssh.keyPath,
|
|
1737
|
-
knownHostsPath: config.ssh.knownHostsPath
|
|
1738
|
-
});
|
|
1739
|
-
const ac = new AbortController();
|
|
1740
|
-
process18.on("SIGINT", () => ac.abort());
|
|
1741
|
-
process18.on("SIGTERM", () => ac.abort());
|
|
1742
|
-
const { session, release } = await acquireSession({
|
|
1743
|
-
host: conn.host,
|
|
1744
|
-
port: conn.port,
|
|
1745
|
-
user: conn.user,
|
|
1746
|
-
privateKeyPath: conn.privateKeyPath,
|
|
1747
|
-
knownHostsPath: conn.knownHostsPath,
|
|
1748
|
-
signal: ac.signal
|
|
1749
|
-
});
|
|
1750
|
-
try {
|
|
1751
|
-
const containerResult = await session.exec(
|
|
1752
|
-
`docker inspect openclaw --format '{{.State.Status}}' 2>/dev/null || echo 'not found'`,
|
|
1753
|
-
ac.signal
|
|
1754
|
-
);
|
|
1755
|
-
const containerStatus = containerResult.stdout.trim();
|
|
1756
|
-
if (containerStatus === "running") {
|
|
1757
|
-
success(`Container running`);
|
|
1758
|
-
} else {
|
|
1759
|
-
failure(`Container ${containerStatus || "unknown"}`);
|
|
1760
|
-
}
|
|
1761
|
-
const imageResult = await session.exec(
|
|
1762
|
-
`docker inspect openclaw --format '{{.Config.Image}}' 2>/dev/null || echo ''`,
|
|
1763
|
-
ac.signal
|
|
1764
|
-
);
|
|
1765
|
-
const image = imageResult.stdout.trim();
|
|
1766
|
-
const deployedVersion = image.includes(":") ? image.slice(image.lastIndexOf(":") + 1) : "";
|
|
1767
|
-
if (deployedVersion) {
|
|
1768
|
-
const yaml = await import("./js-yaml-PTEEG4FO.js");
|
|
1769
|
-
const { loadVersionSpec, checkVersion, isMovingTag } = await import("./versions-2UVAT6PQ.js");
|
|
1770
|
-
const support = loadVersionSpec(yaml).support;
|
|
1771
|
-
if (isMovingTag(deployedVersion)) {
|
|
1772
|
-
warn(
|
|
1773
|
-
`Deployed ${image} (moving tag \u2014 pin an explicit version; "${deployedVersion}" now resolves to OpenClaw 2.0)`
|
|
1774
|
-
);
|
|
1775
|
-
} else {
|
|
1776
|
-
const check = checkVersion(deployedVersion, support);
|
|
1777
|
-
if (check.ok) {
|
|
1778
|
-
success(`Deployed OpenClaw ${deployedVersion}`);
|
|
1779
|
-
} else {
|
|
1780
|
-
failure(`Deployed OpenClaw ${deployedVersion} is not supported by this clawops line`);
|
|
1781
|
-
if (check.error.reason === "too-new") {
|
|
1782
|
-
warn(
|
|
1783
|
-
" This gateway is running OpenClaw 2.0 or later. Session state may already have been lost: 2.0 stores sessions and credentials in SQLite under a directory this clawops line does not mount, so every container restart discards them. Upgrade to clawops 2.x before making changes."
|
|
1784
|
-
);
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
|
-
const healthResult = await session.exec(
|
|
1790
|
-
`docker inspect openclaw --format '{{.State.Health.Status}}' 2>/dev/null || echo 'none'`,
|
|
1791
|
-
ac.signal
|
|
1792
|
-
);
|
|
1793
|
-
const healthStatus = healthResult.stdout.trim();
|
|
1794
|
-
if (healthStatus === "healthy") {
|
|
1795
|
-
success(`Healthcheck healthy`);
|
|
1796
|
-
} else if (healthStatus === "none" || healthStatus === "<no value>") {
|
|
1797
|
-
info(`Healthcheck no healthcheck configured`);
|
|
1798
|
-
} else {
|
|
1799
|
-
warn(`Healthcheck ${healthStatus}`);
|
|
1800
|
-
}
|
|
1801
|
-
const diskResult = await session.exec(
|
|
1802
|
-
`df -h /home/clawops 2>/dev/null | awk 'NR==2{print $5" used ("$3" of "$2")"}'`,
|
|
1803
|
-
ac.signal
|
|
1804
|
-
);
|
|
1805
|
-
const diskUsage = diskResult.stdout.trim();
|
|
1806
|
-
if (diskUsage) {
|
|
1807
|
-
const pctMatch = diskUsage.match(/^(\d+)%/);
|
|
1808
|
-
const pct = pctMatch ? parseInt(pctMatch[1], 10) : 0;
|
|
1809
|
-
if (pct >= 90) {
|
|
1810
|
-
failure(`Disk ${diskUsage}`);
|
|
1811
|
-
} else if (pct >= 75) {
|
|
1812
|
-
warn(`Disk ${diskUsage}`);
|
|
1813
|
-
} else {
|
|
1814
|
-
success(`Disk ${diskUsage}`);
|
|
1815
|
-
}
|
|
1816
|
-
} else {
|
|
1817
|
-
warn("Disk unable to determine disk usage");
|
|
1818
|
-
}
|
|
1819
|
-
const logrotateResult = await session.exec(
|
|
1820
|
-
`test -f /etc/logrotate.d/openclaw && echo 'configured' || echo 'not configured'`,
|
|
1821
|
-
ac.signal
|
|
1822
|
-
);
|
|
1823
|
-
const logrotate = logrotateResult.stdout.trim();
|
|
1824
|
-
if (logrotate === "configured") {
|
|
1825
|
-
success(`Log rotation configured`);
|
|
1826
|
-
} else {
|
|
1827
|
-
warn(`Log rotation not configured \u2014 logs may grow unbounded`);
|
|
1828
|
-
}
|
|
1829
|
-
} finally {
|
|
1830
|
-
release();
|
|
1831
|
-
drainPool();
|
|
1832
|
-
}
|
|
1833
|
-
} catch (err) {
|
|
1834
|
-
failure(`Remote health checks failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
if (args.stack && config) {
|
|
1839
|
-
process18.stdout.write("\nHardening\n");
|
|
1840
|
-
try {
|
|
1841
|
-
const { MODULE_CATALOG, resolveModules, withRemoteExec } = await import("./harden-URPXLSVF.js");
|
|
1842
|
-
const { buildContext } = await import("./context-5LCUG2QR.js");
|
|
1843
|
-
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1844
|
-
const ctx = buildContext({ stack: args.stack });
|
|
1845
|
-
const stackObj = await ctx.getStack();
|
|
1846
|
-
const outputMap = await stackObj.outputs();
|
|
1847
|
-
const outputs = Object.fromEntries(
|
|
1848
|
-
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
1849
|
-
);
|
|
1850
|
-
const base = extractBaseOutputs(outputs);
|
|
1851
|
-
const conn = ctx.adapter.getConnectionInfo({
|
|
1852
|
-
...base,
|
|
1853
|
-
privateKeyPath: config.ssh.keyPath,
|
|
1854
|
-
knownHostsPath: config.ssh.knownHostsPath
|
|
1855
|
-
});
|
|
1856
|
-
const hardenAc = new AbortController();
|
|
1857
|
-
process18.on("SIGINT", () => hardenAc.abort());
|
|
1858
|
-
const provider = ctx.adapter.name;
|
|
1859
|
-
const modules = resolveModules(MODULE_CATALOG, void 0, provider);
|
|
1860
|
-
await withRemoteExec(conn, hardenAc.signal, async (exec) => {
|
|
1861
|
-
for (const mod of modules) {
|
|
1862
|
-
const result = await mod.check(exec);
|
|
1863
|
-
if (result.status === "applied") {
|
|
1864
|
-
success(`${mod.label.padEnd(32)} applied`);
|
|
1865
|
-
} else if (result.status === "drifted") {
|
|
1866
|
-
warn(`${mod.label.padEnd(32)} drifted \u2014 ${result.detail}`);
|
|
1867
|
-
} else if (result.status === "missing") {
|
|
1868
|
-
info(`${mod.label.padEnd(32)} not applied \u2014 run \`clawops harden\``);
|
|
1869
|
-
}
|
|
1870
|
-
}
|
|
1871
|
-
});
|
|
1872
|
-
} catch (err) {
|
|
1873
|
-
failure(`Hardening checks failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1874
|
-
}
|
|
2092
|
+
render(report);
|
|
1875
2093
|
}
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2094
|
+
if (!report.ok) {
|
|
2095
|
+
if (!args.json) {
|
|
2096
|
+
process19.stdout.write(
|
|
2097
|
+
`Run \`clawops bug\` to open a pre-filled issue at ${REPO_URL}/issues
|
|
1880
2098
|
|
|
1881
2099
|
`
|
|
1882
|
-
|
|
1883
|
-
|
|
2100
|
+
);
|
|
2101
|
+
}
|
|
2102
|
+
process19.exit(1);
|
|
1884
2103
|
}
|
|
1885
2104
|
}
|
|
1886
2105
|
});
|
|
2106
|
+
function render(report) {
|
|
2107
|
+
process19.stdout.write("\nclawops doctor\n");
|
|
2108
|
+
for (const section of report.sections) {
|
|
2109
|
+
process19.stdout.write(`
|
|
2110
|
+
${section.title}
|
|
2111
|
+
`);
|
|
2112
|
+
for (const check of section.checks) line(check);
|
|
2113
|
+
}
|
|
2114
|
+
process19.stdout.write("\n");
|
|
2115
|
+
}
|
|
2116
|
+
function line(check) {
|
|
2117
|
+
const label = check.detail ? `${check.name.padEnd(NAME_COLUMN)}${check.detail}` : check.name;
|
|
2118
|
+
const emit = { pass: success, fail: failure, warn, info }[check.status];
|
|
2119
|
+
emit(label);
|
|
2120
|
+
if (check.remedy) info(` ${check.remedy}`);
|
|
2121
|
+
}
|
|
1887
2122
|
|
|
1888
2123
|
// src/cli/commands/mcp.ts
|
|
1889
|
-
import { defineCommand as
|
|
1890
|
-
import
|
|
1891
|
-
var serveCmd =
|
|
2124
|
+
import { defineCommand as defineCommand18 } from "citty";
|
|
2125
|
+
import process20 from "process";
|
|
2126
|
+
var serveCmd = defineCommand18({
|
|
1892
2127
|
meta: {
|
|
1893
2128
|
name: "serve",
|
|
1894
2129
|
description: "Start the clawops MCP server"
|
|
@@ -1903,7 +2138,7 @@ var serveCmd = defineCommand17({
|
|
|
1903
2138
|
inspector: { type: "boolean", description: "Enable MCP inspector" }
|
|
1904
2139
|
},
|
|
1905
2140
|
async run({ args }) {
|
|
1906
|
-
const { serveMcp } = await import("./server-
|
|
2141
|
+
const { serveMcp } = await import("./server-A5AB67GQ.js");
|
|
1907
2142
|
await serveMcp({
|
|
1908
2143
|
port: args.http ? Number(args.http) : void 0,
|
|
1909
2144
|
bind: args.bind,
|
|
@@ -1915,7 +2150,7 @@ var serveCmd = defineCommand17({
|
|
|
1915
2150
|
});
|
|
1916
2151
|
}
|
|
1917
2152
|
});
|
|
1918
|
-
var installCmd =
|
|
2153
|
+
var installCmd = defineCommand18({
|
|
1919
2154
|
meta: {
|
|
1920
2155
|
name: "install",
|
|
1921
2156
|
description: "Interactively wire clawops into AI editors (Claude Desktop, Claude Code, Cursor, \u2026)"
|
|
@@ -1946,7 +2181,7 @@ var installCmd = defineCommand17({
|
|
|
1946
2181
|
const selected = MCP_APPS2.filter((app) => selectedIds.includes(app.id));
|
|
1947
2182
|
if (selected.length === 0) {
|
|
1948
2183
|
info("No apps selected. Add this to an app's MCP config manually:");
|
|
1949
|
-
|
|
2184
|
+
process20.stdout.write(JSON.stringify({
|
|
1950
2185
|
mcpServers: { clawops: { command: mcpEntry.command, args: mcpEntry.args } }
|
|
1951
2186
|
}, null, 2) + "\n\n");
|
|
1952
2187
|
info("Config file locations:");
|
|
@@ -1969,7 +2204,7 @@ var installCmd = defineCommand17({
|
|
|
1969
2204
|
}
|
|
1970
2205
|
}
|
|
1971
2206
|
});
|
|
1972
|
-
var wireCmd =
|
|
2207
|
+
var wireCmd = defineCommand18({
|
|
1973
2208
|
meta: {
|
|
1974
2209
|
name: "wire",
|
|
1975
2210
|
description: "Wire the gateway AI as an MCP client of clawops (WO-28)"
|
|
@@ -1981,26 +2216,26 @@ var wireCmd = defineCommand17({
|
|
|
1981
2216
|
rewire: { type: "boolean", description: "Replace an existing clawops entry" }
|
|
1982
2217
|
},
|
|
1983
2218
|
async run({ args }) {
|
|
1984
|
-
const { buildContext } = await import("./context-
|
|
1985
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
2219
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
2220
|
+
const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
|
|
1986
2221
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1987
|
-
const { wireGatewayMcp } = await import("./mcp-wire-
|
|
1988
|
-
const { MCP_HTTP_PORT } = await import("./server-
|
|
2222
|
+
const { wireGatewayMcp } = await import("./mcp-wire-BFEY2Y6H.js");
|
|
2223
|
+
const { MCP_HTTP_PORT } = await import("./server-A5AB67GQ.js");
|
|
1989
2224
|
const ac = new AbortController();
|
|
1990
|
-
|
|
2225
|
+
process20.on("SIGINT", () => {
|
|
1991
2226
|
ac.abort();
|
|
1992
|
-
|
|
2227
|
+
process20.exit(130);
|
|
1993
2228
|
});
|
|
1994
|
-
|
|
2229
|
+
process20.on("SIGTERM", () => {
|
|
1995
2230
|
ac.abort();
|
|
1996
|
-
|
|
2231
|
+
process20.exit(143);
|
|
1997
2232
|
});
|
|
1998
2233
|
const ctx = buildContext({ stack: args.stack });
|
|
1999
2234
|
let conn;
|
|
2000
2235
|
if (ctx.adapter.name === "local") {
|
|
2001
2236
|
if (!ctx.localState) {
|
|
2002
2237
|
failure("Stack is not deployed. Run `clawops up` first.");
|
|
2003
|
-
|
|
2238
|
+
process20.exit(1);
|
|
2004
2239
|
}
|
|
2005
2240
|
const ls = ctx.localState;
|
|
2006
2241
|
conn = { host: ls.sshHost, port: ls.sshPort, user: ls.sshUser, privateKeyPath: ls.privateKeyPath, knownHostsPath: ls.knownHostsPath };
|
|
@@ -2012,7 +2247,7 @@ var wireCmd = defineCommand17({
|
|
|
2012
2247
|
);
|
|
2013
2248
|
if (!outputs["publicIp"]) {
|
|
2014
2249
|
failure("Stack has no outputs. Run `clawops up` first.");
|
|
2015
|
-
|
|
2250
|
+
process20.exit(1);
|
|
2016
2251
|
}
|
|
2017
2252
|
const base = extractBaseOutputs(outputs);
|
|
2018
2253
|
conn = {
|
|
@@ -2036,7 +2271,7 @@ var wireCmd = defineCommand17({
|
|
|
2036
2271
|
spin.fail("This gateway has no `openclaw mcp add`, so clawops cannot wire it.");
|
|
2037
2272
|
info("OpenClaw 2026.4.5 ships `openclaw mcp` with only `list` and `serve`.");
|
|
2038
2273
|
info("Upgrade the gateway to 2026.7.1-2 or later, then re-run this command.");
|
|
2039
|
-
|
|
2274
|
+
process20.exit(1);
|
|
2040
2275
|
}
|
|
2041
2276
|
if (result.status === "exists") {
|
|
2042
2277
|
spin.info("The gateway already has a clawops MCP server configured.");
|
|
@@ -2049,7 +2284,7 @@ var wireCmd = defineCommand17({
|
|
|
2049
2284
|
warn("clawops does not run on the gateway host. Start it where the gateway can reach it:");
|
|
2050
2285
|
info(` clawops mcp serve --http ${MCP_HTTP_PORT} --bind 0.0.0.0 --token <token>`);
|
|
2051
2286
|
info(`Then re-run: clawops mcp wire --url ${result.url} --token <token>`);
|
|
2052
|
-
|
|
2287
|
+
process20.exit(1);
|
|
2053
2288
|
}
|
|
2054
2289
|
spin.succeed(
|
|
2055
2290
|
result.rewired ? "Re-wired the gateway to clawops \u2014 previous entry replaced." : "Gateway wired to clawops, and the connection was verified."
|
|
@@ -2060,14 +2295,14 @@ var wireCmd = defineCommand17({
|
|
|
2060
2295
|
} catch (err) {
|
|
2061
2296
|
spin.fail("Failed to wire gateway MCP client.");
|
|
2062
2297
|
failure(err instanceof Error ? err.message : String(err));
|
|
2063
|
-
|
|
2298
|
+
process20.exit(1);
|
|
2064
2299
|
} finally {
|
|
2065
2300
|
release();
|
|
2066
2301
|
drainPool();
|
|
2067
2302
|
}
|
|
2068
2303
|
}
|
|
2069
2304
|
});
|
|
2070
|
-
var mcp_default =
|
|
2305
|
+
var mcp_default = defineCommand18({
|
|
2071
2306
|
meta: {
|
|
2072
2307
|
name: "mcp",
|
|
2073
2308
|
description: "MCP server operations"
|
|
@@ -2081,14 +2316,14 @@ var mcp_default = defineCommand17({
|
|
|
2081
2316
|
});
|
|
2082
2317
|
|
|
2083
2318
|
// src/cli/commands/setup.ts
|
|
2084
|
-
import { defineCommand as
|
|
2085
|
-
import
|
|
2319
|
+
import { defineCommand as defineCommand19 } from "citty";
|
|
2320
|
+
import process21 from "process";
|
|
2086
2321
|
import os from "os";
|
|
2087
2322
|
import { randomBytes } from "crypto";
|
|
2088
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync2, mkdirSync as
|
|
2323
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
2089
2324
|
import { execSync, spawnSync, spawn as spawn2 } from "child_process";
|
|
2090
|
-
import
|
|
2091
|
-
var setup_default =
|
|
2325
|
+
import path2 from "path";
|
|
2326
|
+
var setup_default = defineCommand19({
|
|
2092
2327
|
meta: {
|
|
2093
2328
|
name: "setup",
|
|
2094
2329
|
description: "Interactive wizard: configure and deploy an OpenClaw stack"
|
|
@@ -2103,16 +2338,16 @@ var setup_default = defineCommand18({
|
|
|
2103
2338
|
const catalogs = loadCatalogs(yaml);
|
|
2104
2339
|
const dryRun = Boolean(args["dry-run"]);
|
|
2105
2340
|
const ac = new AbortController();
|
|
2106
|
-
|
|
2341
|
+
process21.on("SIGINT", () => {
|
|
2107
2342
|
ac.abort();
|
|
2108
|
-
|
|
2343
|
+
process21.exit(130);
|
|
2109
2344
|
});
|
|
2110
|
-
|
|
2345
|
+
process21.on("SIGTERM", () => {
|
|
2111
2346
|
ac.abort();
|
|
2112
|
-
|
|
2347
|
+
process21.exit(143);
|
|
2113
2348
|
});
|
|
2114
|
-
|
|
2115
|
-
|
|
2349
|
+
process21.stdout.write("\nWelcome to clawops setup. This wizard will help you deploy OpenClaw\n");
|
|
2350
|
+
process21.stdout.write("and connect it to your AI tools. It takes about 2 minutes.\n\n");
|
|
2116
2351
|
const { deploymentType } = await inquirer.prompt([{
|
|
2117
2352
|
type: "list",
|
|
2118
2353
|
name: "deploymentType",
|
|
@@ -2140,7 +2375,7 @@ var setup_default = defineCommand18({
|
|
|
2140
2375
|
]
|
|
2141
2376
|
}]);
|
|
2142
2377
|
provider = answer.provider;
|
|
2143
|
-
|
|
2378
|
+
process21.stdout.write("\n");
|
|
2144
2379
|
await ensureCloudAuth(provider, inquirer);
|
|
2145
2380
|
} else {
|
|
2146
2381
|
provider = "local";
|
|
@@ -2196,7 +2431,8 @@ var setup_default = defineCommand18({
|
|
|
2196
2431
|
}]);
|
|
2197
2432
|
localSudoPassword = sudoPass;
|
|
2198
2433
|
}
|
|
2199
|
-
|
|
2434
|
+
process21.stdout.write("\n");
|
|
2435
|
+
const sshCidrDefault = provider === "local" ? void 0 : await detectSshCidrDefault();
|
|
2200
2436
|
const stackAnswers = await inquirer.prompt([
|
|
2201
2437
|
{
|
|
2202
2438
|
type: "input",
|
|
@@ -2233,8 +2469,13 @@ var setup_default = defineCommand18({
|
|
|
2233
2469
|
{
|
|
2234
2470
|
type: "input",
|
|
2235
2471
|
name: "sshCidr",
|
|
2236
|
-
message: "Restrict SSH access to: (your IP
|
|
2237
|
-
|
|
2472
|
+
message: sshCidrDefault ? "Restrict SSH access to: (detected your current IP \u2014 press Enter to accept, or enter another CIDR)" : "Restrict SSH access to: (a CIDR such as 203.0.113.4/32 \u2014 your own IP is the usual answer)",
|
|
2473
|
+
// Defaulted to 0.0.0.0/0 until 2.0, so pressing Enter opened SSH to the whole
|
|
2474
|
+
// internet on the path most first-time users take. N10 forbids exactly that
|
|
2475
|
+
// default. When the egress IP cannot be detected there is no default at all and
|
|
2476
|
+
// the answer is required — an unanswerable prompt is better than a wide one.
|
|
2477
|
+
...sshCidrDefault ? { default: sshCidrDefault } : {},
|
|
2478
|
+
validate: validateCidrAnswer
|
|
2238
2479
|
}
|
|
2239
2480
|
] : [],
|
|
2240
2481
|
{
|
|
@@ -2244,7 +2485,12 @@ var setup_default = defineCommand18({
|
|
|
2244
2485
|
default: "latest"
|
|
2245
2486
|
}
|
|
2246
2487
|
]);
|
|
2247
|
-
|
|
2488
|
+
if (provider !== "local" && admitsInternet(stackAnswers.sshCidr)) {
|
|
2489
|
+
warn(
|
|
2490
|
+
"SSH will be reachable from anywhere on the internet. `clawops harden` reports this as a finding; narrowing it to your own IP is the usual fix."
|
|
2491
|
+
);
|
|
2492
|
+
}
|
|
2493
|
+
process21.stdout.write("\n");
|
|
2248
2494
|
info("Choose the AI model that your OpenClaw agent will use.");
|
|
2249
2495
|
info("You'll need an API key from your chosen provider (except Bedrock and Ollama).\n");
|
|
2250
2496
|
const { modelProviderId } = await inquirer.prompt([{
|
|
@@ -2273,7 +2519,7 @@ var setup_default = defineCommand18({
|
|
|
2273
2519
|
secrets.push(entry);
|
|
2274
2520
|
modelConfig["apiKey"] = `$secret:${secretName}`;
|
|
2275
2521
|
} else if (modelProvider.credentialSource === "aws-profile") {
|
|
2276
|
-
|
|
2522
|
+
process21.stdout.write("\n");
|
|
2277
2523
|
info(modelProvider.iamNote ?? "Bedrock uses your AWS server's IAM role \u2014 no API key needed.");
|
|
2278
2524
|
info("Make sure your EC2 instance has the AmazonBedrockFullAccess IAM policy attached.\n");
|
|
2279
2525
|
} else if (modelProvider.id === "ollama") {
|
|
@@ -2294,13 +2540,13 @@ var setup_default = defineCommand18({
|
|
|
2294
2540
|
...selectedModel.modelId ? { modelId: selectedModel.modelId } : { model: selectedModel.id },
|
|
2295
2541
|
...modelConfig
|
|
2296
2542
|
};
|
|
2297
|
-
|
|
2543
|
+
process21.stdout.write("\n");
|
|
2298
2544
|
info("Use \u2191\u2193 to move, Space to select/deselect, Enter to confirm.");
|
|
2299
2545
|
const { selectedIntegrationIds } = await inquirer.prompt([{
|
|
2300
2546
|
type: "checkbox",
|
|
2301
2547
|
name: "selectedIntegrationIds",
|
|
2302
2548
|
message: "Which chat integrations would you like to enable?",
|
|
2303
|
-
choices: catalogs.integrations.map((integ) => ({
|
|
2549
|
+
choices: wizardChannels(catalogs.integrations).map((integ) => ({
|
|
2304
2550
|
name: `${integ.displayName} \u2014 ${integ.description}`,
|
|
2305
2551
|
value: integ.id,
|
|
2306
2552
|
checked: false
|
|
@@ -2310,9 +2556,9 @@ var setup_default = defineCommand18({
|
|
|
2310
2556
|
const channelsConfig = {};
|
|
2311
2557
|
const infraRequired = [];
|
|
2312
2558
|
for (const integ of catalogs.integrations.filter((i) => selectedIntegrationIds.includes(i.id))) {
|
|
2313
|
-
|
|
2559
|
+
process21.stdout.write("\n");
|
|
2314
2560
|
info(`Setting up ${integ.displayName}:`);
|
|
2315
|
-
const channelConfig =
|
|
2561
|
+
const channelConfig = startChannelConfig(integ);
|
|
2316
2562
|
for (const field of integ.fields) {
|
|
2317
2563
|
if (field.sensitive && field.envDefault) {
|
|
2318
2564
|
const entry = await promptSecret(field.envDefault, `${integ.displayName} ${field.label}`, field.envDefault, inquirer);
|
|
@@ -2328,6 +2574,15 @@ var setup_default = defineCommand18({
|
|
|
2328
2574
|
channelConfig[field.name] = value;
|
|
2329
2575
|
}
|
|
2330
2576
|
}
|
|
2577
|
+
if (integ.useEnvSupported === false) {
|
|
2578
|
+
warn(`${integ.displayName} has no non-interactive setup in OpenClaw.`);
|
|
2579
|
+
info(` Run \`openclaw channels add --channel ${integ.channelKey}\` on the host, interactively.`);
|
|
2580
|
+
} else if (integ.plugin?.source === "bundled") {
|
|
2581
|
+
info(`${integ.displayName} ships in the image. Activate it on the host with:`);
|
|
2582
|
+
info(` openclaw channels add --channel ${integ.channelKey} --use-env`);
|
|
2583
|
+
} else if (integ.plugin?.package) {
|
|
2584
|
+
info(`${integ.displayName} needs ${integ.plugin.package}; clawops installs it during apply.`);
|
|
2585
|
+
}
|
|
2331
2586
|
channelsConfig[integ.channelKey] = channelConfig;
|
|
2332
2587
|
if (integ.infraRequired) infraRequired.push(integ);
|
|
2333
2588
|
}
|
|
@@ -2346,10 +2601,10 @@ var setup_default = defineCommand18({
|
|
|
2346
2601
|
}
|
|
2347
2602
|
}
|
|
2348
2603
|
const gatewayToken = randomBytes(24).toString("hex");
|
|
2349
|
-
const secretsDir2 =
|
|
2350
|
-
|
|
2604
|
+
const secretsDir2 = path2.join(os.homedir(), ".clawops", "secrets");
|
|
2605
|
+
mkdirSync2(secretsDir2, { recursive: true });
|
|
2351
2606
|
spawnSync("chmod", ["700", secretsDir2], { stdio: "ignore" });
|
|
2352
|
-
const tokenPath =
|
|
2607
|
+
const tokenPath = path2.join(secretsDir2, `GATEWAY_TOKEN_${stackAnswers.stackName}`);
|
|
2353
2608
|
writeFileSync3(tokenPath, gatewayToken, { encoding: "utf-8", mode: 384 });
|
|
2354
2609
|
const openclawConfigOverlay = {
|
|
2355
2610
|
models: builtModelConfig,
|
|
@@ -2360,9 +2615,9 @@ var setup_default = defineCommand18({
|
|
|
2360
2615
|
}
|
|
2361
2616
|
let outputPath;
|
|
2362
2617
|
if (provider === "local") {
|
|
2363
|
-
outputPath =
|
|
2618
|
+
outputPath = path2.join(outDir, `openclaw-${stackAnswers.stackName}.json`);
|
|
2364
2619
|
writeFileSync3(outputPath, JSON.stringify(openclawConfigOverlay, null, 2), "utf-8");
|
|
2365
|
-
|
|
2620
|
+
process21.stdout.write("\n");
|
|
2366
2621
|
success(`Config file saved to ${outputPath}`);
|
|
2367
2622
|
} else {
|
|
2368
2623
|
let sshPublicKey = "";
|
|
@@ -2370,7 +2625,7 @@ var setup_default = defineCommand18({
|
|
|
2370
2625
|
sshPublicKey = readFileSync3(stackAnswers.sshKeyPath ?? "", "utf-8").trim();
|
|
2371
2626
|
} catch {
|
|
2372
2627
|
failure(`Cannot read SSH public key at ${stackAnswers.sshKeyPath ?? ""}`);
|
|
2373
|
-
|
|
2628
|
+
process21.exit(1);
|
|
2374
2629
|
}
|
|
2375
2630
|
const plan = {
|
|
2376
2631
|
apiVersion: "clawops.dev/v1",
|
|
@@ -2392,19 +2647,26 @@ var setup_default = defineCommand18({
|
|
|
2392
2647
|
},
|
|
2393
2648
|
secrets,
|
|
2394
2649
|
network: {
|
|
2395
|
-
|
|
2396
|
-
|
|
2650
|
+
// No `?? '0.0.0.0/0'`. The prompt requires an answer now, and a fallback that
|
|
2651
|
+
// opened the internet would reintroduce the default it replaced.
|
|
2652
|
+
allowedSshCidrs: [stackAnswers.sshCidr.trim()],
|
|
2653
|
+
// Empty, not the SSH CIDR. Reusing it opened a plaintext HTTP dashboard to
|
|
2654
|
+
// whatever network the operator picked for shell access — two very different
|
|
2655
|
+
// risks answered by one question. The gateway is reached with
|
|
2656
|
+
// `clawops tunnel`; exposing it is an explicit choice (network.publishGateway).
|
|
2657
|
+
allowedGatewayCidrs: [],
|
|
2658
|
+
publishGateway: "loopback"
|
|
2397
2659
|
},
|
|
2398
2660
|
ssh: { publicKey: sshPublicKey },
|
|
2399
2661
|
...stackAnswers.stateBucket ? { stateBucket: stackAnswers.stateBucket } : {}
|
|
2400
2662
|
}
|
|
2401
2663
|
};
|
|
2402
|
-
outputPath =
|
|
2664
|
+
outputPath = path2.join(outDir, `clawops-${stackAnswers.stackName}-plan.json`);
|
|
2403
2665
|
writeFileSync3(outputPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
2404
|
-
|
|
2666
|
+
process21.stdout.write("\n");
|
|
2405
2667
|
success(`Deployment plan saved to ${outputPath}`);
|
|
2406
2668
|
}
|
|
2407
|
-
|
|
2669
|
+
process21.stdout.write("\n");
|
|
2408
2670
|
const mcpEntry = buildMcpEntry();
|
|
2409
2671
|
if (!mcpEntry.resolved) {
|
|
2410
2672
|
info("Note: clawops is not installed globally \u2014 AI apps will not be able to start");
|
|
@@ -2450,7 +2712,7 @@ var setup_default = defineCommand18({
|
|
|
2450
2712
|
}
|
|
2451
2713
|
}
|
|
2452
2714
|
if (infraRequired.length > 0) {
|
|
2453
|
-
|
|
2715
|
+
process21.stdout.write("\n");
|
|
2454
2716
|
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");
|
|
2455
2717
|
info("These integrations need a webhook URL set up in their developer portal:");
|
|
2456
2718
|
for (const integ of infraRequired) {
|
|
@@ -2461,7 +2723,7 @@ var setup_default = defineCommand18({
|
|
|
2461
2723
|
}
|
|
2462
2724
|
}
|
|
2463
2725
|
if (modelProvider.id === "ollama" && selectedModel.pullSuffix !== void 0) {
|
|
2464
|
-
|
|
2726
|
+
process21.stdout.write("\n");
|
|
2465
2727
|
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");
|
|
2466
2728
|
info("After deployment, run this command on the server (or locally if Ollama is local):");
|
|
2467
2729
|
info(` ollama pull ${selectedModel.id}${selectedModel.pullSuffix}`);
|
|
@@ -2506,8 +2768,8 @@ var setup_default = defineCommand18({
|
|
|
2506
2768
|
});
|
|
2507
2769
|
} else {
|
|
2508
2770
|
const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
|
|
2509
|
-
const knownHostsPath =
|
|
2510
|
-
|
|
2771
|
+
const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
|
|
2772
|
+
process21.stdout.write("\n");
|
|
2511
2773
|
info("To deploy later, run these two commands:");
|
|
2512
2774
|
info(` clawops init --provider local --host ${localHost} --port ${localPort} --user ${localUser} --key ${localKeyPath} --stack ${stackAnswers.stackName}`);
|
|
2513
2775
|
info(` clawops up --stack ${stackAnswers.stackName} --config ${outputPath}`);
|
|
@@ -2522,18 +2784,18 @@ var setup_default = defineCommand18({
|
|
|
2522
2784
|
default: false
|
|
2523
2785
|
}]);
|
|
2524
2786
|
if (applyNow) {
|
|
2525
|
-
const { applyPlan } = await import("./apply-
|
|
2787
|
+
const { applyPlan } = await import("./apply-AM5HERE7.js");
|
|
2526
2788
|
const plan = JSON.parse(readFileSync3(outputPath, "utf-8"));
|
|
2527
2789
|
try {
|
|
2528
2790
|
await applyPlan(plan, {
|
|
2529
|
-
onOutput: (
|
|
2791
|
+
onOutput: (line2) => process21.stdout.write(line2),
|
|
2530
2792
|
signal: ac.signal
|
|
2531
2793
|
});
|
|
2532
2794
|
success("Server provisioned and OpenClaw installed.");
|
|
2533
2795
|
info(`Run: clawops doctor --stack ${stackAnswers.stackName} to verify everything is healthy`);
|
|
2534
2796
|
} catch (err) {
|
|
2535
2797
|
failure(err instanceof Error ? err.message : String(err));
|
|
2536
|
-
|
|
2798
|
+
process21.exit(1);
|
|
2537
2799
|
}
|
|
2538
2800
|
await maybeWireGatewayMcpCloud({ stackName: stackAnswers.stackName, signal: ac.signal, inquirer });
|
|
2539
2801
|
await maybeHarden({
|
|
@@ -2551,11 +2813,11 @@ To deploy later: clawops apply ${outputPath}`);
|
|
|
2551
2813
|
});
|
|
2552
2814
|
async function runLocalDeploy(opts) {
|
|
2553
2815
|
const { setConfig: setConfig2, getConfig: getConfig2, getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
|
|
2554
|
-
const { localBootstrap } = await import("./bootstrap-
|
|
2555
|
-
const { connect } = await import("./ssh-
|
|
2556
|
-
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-
|
|
2816
|
+
const { localBootstrap } = await import("./bootstrap-DMRPWJGN.js");
|
|
2817
|
+
const { connect } = await import("./ssh-B3NZAU4I.js");
|
|
2818
|
+
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-Z6IIZR3D.js");
|
|
2557
2819
|
const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
|
|
2558
|
-
const knownHostsPath =
|
|
2820
|
+
const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
|
|
2559
2821
|
const existing = getConfig2();
|
|
2560
2822
|
const stackEntry = {
|
|
2561
2823
|
provider: "local",
|
|
@@ -2593,9 +2855,9 @@ async function runLocalDeploy(opts) {
|
|
|
2593
2855
|
stackName: opts.stackName,
|
|
2594
2856
|
sudoPassword: opts.sudoPassword,
|
|
2595
2857
|
noWait: false,
|
|
2596
|
-
onOutput: (
|
|
2858
|
+
onOutput: (line2) => {
|
|
2597
2859
|
for (const [pattern, label] of STAGES) {
|
|
2598
|
-
if (pattern.test(
|
|
2860
|
+
if (pattern.test(line2)) {
|
|
2599
2861
|
spin.text = label;
|
|
2600
2862
|
break;
|
|
2601
2863
|
}
|
|
@@ -2634,11 +2896,11 @@ async function runLocalDeploy(opts) {
|
|
|
2634
2896
|
spin2.fail("Configuration failed");
|
|
2635
2897
|
throw err;
|
|
2636
2898
|
}
|
|
2637
|
-
const { drainPool } = await import("./pool-
|
|
2899
|
+
const { drainPool } = await import("./pool-EX4ZTA5P.js");
|
|
2638
2900
|
drainPool();
|
|
2639
|
-
const tokenPath =
|
|
2901
|
+
const tokenPath = path2.join(os.homedir(), ".clawops", "secrets", `GATEWAY_TOKEN_${opts.stackName}`);
|
|
2640
2902
|
const dashboardUrl = `${state.gatewayUrl}?token=${opts.gatewayToken}`;
|
|
2641
|
-
|
|
2903
|
+
process21.stdout.write("\n");
|
|
2642
2904
|
success("All done! OpenClaw is running.");
|
|
2643
2905
|
info(`Open dashboard: ${dashboardUrl}`);
|
|
2644
2906
|
info(` (or enter the token manually in the "Gateway Token" field: ${opts.gatewayToken})`);
|
|
@@ -2693,7 +2955,7 @@ ${cfg.loginHint}`);
|
|
|
2693
2955
|
default: true
|
|
2694
2956
|
}]);
|
|
2695
2957
|
if (runNow) {
|
|
2696
|
-
|
|
2958
|
+
process21.stdout.write("\n");
|
|
2697
2959
|
const result = spawnSync(cfg.loginCmd[0], cfg.loginCmd.slice(1), { stdio: "inherit" });
|
|
2698
2960
|
if (result.error) {
|
|
2699
2961
|
failure(`Could not launch ${cliName} \u2014 is it installed? (${result.error.message})`);
|
|
@@ -2703,10 +2965,10 @@ ${cfg.loginHint}`);
|
|
|
2703
2965
|
}
|
|
2704
2966
|
try {
|
|
2705
2967
|
const identity = cfg.check();
|
|
2706
|
-
|
|
2968
|
+
process21.stdout.write("\n");
|
|
2707
2969
|
success(`Authenticated: ${cfg.identity(identity)}`);
|
|
2708
2970
|
} catch {
|
|
2709
|
-
|
|
2971
|
+
process21.stdout.write("\n");
|
|
2710
2972
|
failure(`Still not authenticated \u2014 the sign-in may have been cancelled or failed.`);
|
|
2711
2973
|
info("Re-run `clawops setup` after signing in, or continue and authenticate before running `clawops up`.\n");
|
|
2712
2974
|
}
|
|
@@ -2720,7 +2982,7 @@ function cliInstallUrl(provider) {
|
|
|
2720
2982
|
return "https://learn.microsoft.com/en-us/cli/azure/install-azure-cli";
|
|
2721
2983
|
}
|
|
2722
2984
|
async function promptSecret(name, label, envDefault, inquirer) {
|
|
2723
|
-
const secretsDir2 =
|
|
2985
|
+
const secretsDir2 = path2.join(os.homedir(), ".clawops", "secrets");
|
|
2724
2986
|
const { secretSource } = await inquirer.prompt([{
|
|
2725
2987
|
type: "list",
|
|
2726
2988
|
name: "secretSource",
|
|
@@ -2738,9 +3000,9 @@ async function promptSecret(name, label, envDefault, inquirer) {
|
|
|
2738
3000
|
message: `Paste your ${label}: (input is hidden)`,
|
|
2739
3001
|
validate: (v) => v.trim() !== "" || "Value cannot be empty"
|
|
2740
3002
|
}]);
|
|
2741
|
-
|
|
3003
|
+
mkdirSync2(secretsDir2, { recursive: true });
|
|
2742
3004
|
spawnSync("chmod", ["700", secretsDir2], { stdio: "ignore" });
|
|
2743
|
-
const secretPath2 =
|
|
3005
|
+
const secretPath2 = path2.join(secretsDir2, name);
|
|
2744
3006
|
writeFileSync3(secretPath2, secretValue.trim(), { encoding: "utf-8", mode: 384 });
|
|
2745
3007
|
success(`Secret saved to ${secretPath2} (chmod 600)`);
|
|
2746
3008
|
return { name, source: "file", ref: secretPath2 };
|
|
@@ -2772,7 +3034,7 @@ var DOCKER_CHECK_CMD = [
|
|
|
2772
3034
|
"echo NOT_RUNNING"
|
|
2773
3035
|
].join("; ");
|
|
2774
3036
|
async function checkDockerPreflight(opts) {
|
|
2775
|
-
const { acquireSession } = await import("./pool-
|
|
3037
|
+
const { acquireSession } = await import("./pool-EX4ZTA5P.js");
|
|
2776
3038
|
const { ProviderError } = await import("./errors-OK47MQFD.js");
|
|
2777
3039
|
const spin = spinner("Checking Docker on target host...");
|
|
2778
3040
|
const timeoutSignal = AbortSignal.timeout(DOCKER_PREFLIGHT_TIMEOUT_MS);
|
|
@@ -2787,7 +3049,7 @@ async function checkDockerPreflight(opts) {
|
|
|
2787
3049
|
});
|
|
2788
3050
|
let out = "";
|
|
2789
3051
|
try {
|
|
2790
|
-
const result = await session
|
|
3052
|
+
const result = await execPrivileged(session, DOCKER_CHECK_CMD, execSignal);
|
|
2791
3053
|
out = result.stdout.trim();
|
|
2792
3054
|
} catch {
|
|
2793
3055
|
spin.warn("Docker check timed out \u2014 proceeding anyway. Bootstrap will verify.");
|
|
@@ -2819,7 +3081,7 @@ async function checkDockerPreflight(opts) {
|
|
|
2819
3081
|
var DOCKER_POLL_MS = 3e3;
|
|
2820
3082
|
var DOCKER_TIMEOUT_MS = 9e4;
|
|
2821
3083
|
async function startRemoteDocker(opts, spin) {
|
|
2822
|
-
const { acquireSession } = await import("./pool-
|
|
3084
|
+
const { acquireSession } = await import("./pool-EX4ZTA5P.js");
|
|
2823
3085
|
const { ProviderError } = await import("./errors-OK47MQFD.js");
|
|
2824
3086
|
spin.text = "Starting Docker on the remote server...";
|
|
2825
3087
|
const { session, release } = await acquireSession({
|
|
@@ -2845,7 +3107,8 @@ async function startRemoteDocker(opts, spin) {
|
|
|
2845
3107
|
while (Date.now() < deadline) {
|
|
2846
3108
|
await new Promise((r) => setTimeout(r, DOCKER_POLL_MS));
|
|
2847
3109
|
spin.text = `Waiting for Docker to start on ${opts.host}...`;
|
|
2848
|
-
const check = await
|
|
3110
|
+
const check = await execPrivileged(
|
|
3111
|
+
session,
|
|
2849
3112
|
"docker version >/dev/null 2>&1 && echo OK || echo NOT_RUNNING",
|
|
2850
3113
|
opts.signal
|
|
2851
3114
|
);
|
|
@@ -2891,7 +3154,7 @@ async function pollUntilDockerReady(label) {
|
|
|
2891
3154
|
throw new Error("Docker daemon did not become ready within timeout.");
|
|
2892
3155
|
}
|
|
2893
3156
|
async function startDockerAndWait(inquirer) {
|
|
2894
|
-
const isLinux =
|
|
3157
|
+
const isLinux = process21.platform === "linux";
|
|
2895
3158
|
const { choice } = await inquirer.prompt([{
|
|
2896
3159
|
type: "list",
|
|
2897
3160
|
name: "choice",
|
|
@@ -2958,7 +3221,7 @@ async function ensureAuthorizedKey(keyPath, host, inquirer) {
|
|
|
2958
3221
|
if (!existsSync2(pubKeyPath)) return;
|
|
2959
3222
|
const pubKey = readFileSync3(pubKeyPath, "utf-8").trim();
|
|
2960
3223
|
const isLocal = LOCALHOST_ALIASES.has(host.toLowerCase());
|
|
2961
|
-
const authorizedKeysPath =
|
|
3224
|
+
const authorizedKeysPath = path2.join(os.homedir(), ".ssh", "authorized_keys");
|
|
2962
3225
|
if (isLocal) {
|
|
2963
3226
|
const existing = existsSync2(authorizedKeysPath) ? readFileSync3(authorizedKeysPath, "utf-8") : "";
|
|
2964
3227
|
if (existing.includes(pubKey)) {
|
|
@@ -2973,7 +3236,7 @@ async function ensureAuthorizedKey(keyPath, host, inquirer) {
|
|
|
2973
3236
|
default: true
|
|
2974
3237
|
}]);
|
|
2975
3238
|
if (addKey) {
|
|
2976
|
-
|
|
3239
|
+
mkdirSync2(path2.join(os.homedir(), ".ssh"), { recursive: true });
|
|
2977
3240
|
const entry = existing.endsWith("\n") || existing === "" ? pubKey + "\n" : "\n" + pubKey + "\n";
|
|
2978
3241
|
writeFileSync3(authorizedKeysPath, existing + entry, { encoding: "utf-8", flag: "a" });
|
|
2979
3242
|
spawnSync("chmod", ["600", authorizedKeysPath], { stdio: "ignore" });
|
|
@@ -2983,15 +3246,15 @@ async function ensureAuthorizedKey(keyPath, host, inquirer) {
|
|
|
2983
3246
|
info(` ${pubKey}`);
|
|
2984
3247
|
}
|
|
2985
3248
|
} else {
|
|
2986
|
-
|
|
3249
|
+
process21.stdout.write("\n");
|
|
2987
3250
|
info("Make sure this public key is in ~/.ssh/authorized_keys on your server:");
|
|
2988
3251
|
info(` ${pubKey}`);
|
|
2989
3252
|
info("(If you just created the server, paste the line above into the server's ~/.ssh/authorized_keys file.)\n");
|
|
2990
3253
|
}
|
|
2991
3254
|
}
|
|
2992
3255
|
async function generateSshKey() {
|
|
2993
|
-
const keyPath =
|
|
2994
|
-
|
|
3256
|
+
const keyPath = path2.join(os.homedir(), ".ssh", "id_ed25519");
|
|
3257
|
+
mkdirSync2(path2.join(os.homedir(), ".ssh"), { recursive: true });
|
|
2995
3258
|
const result = spawnSync("ssh-keygen", ["-t", "ed25519", "-f", keyPath, "-N", "", "-C", "clawops"], {
|
|
2996
3259
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2997
3260
|
});
|
|
@@ -3006,27 +3269,27 @@ async function generateSshKey() {
|
|
|
3006
3269
|
function detectSshKey() {
|
|
3007
3270
|
const candidates = ["id_ed25519", "id_ecdsa", "id_rsa", "id_dsa"];
|
|
3008
3271
|
for (const name of candidates) {
|
|
3009
|
-
const p =
|
|
3272
|
+
const p = path2.join(os.homedir(), ".ssh", name);
|
|
3010
3273
|
if (existsSync2(p)) return p;
|
|
3011
3274
|
}
|
|
3012
|
-
return
|
|
3275
|
+
return path2.join(os.homedir(), ".ssh", "id_ed25519");
|
|
3013
3276
|
}
|
|
3014
3277
|
function printMcpSnippet() {
|
|
3015
|
-
|
|
3016
|
-
|
|
3278
|
+
process21.stdout.write("\n");
|
|
3279
|
+
process21.stdout.write(JSON.stringify({
|
|
3017
3280
|
mcpServers: {
|
|
3018
3281
|
clawops: { command: "clawops", args: ["mcp", "serve", "--read-only"] }
|
|
3019
3282
|
}
|
|
3020
3283
|
}, null, 2) + "\n\n");
|
|
3021
3284
|
}
|
|
3022
3285
|
async function maybeWireGatewayMcp(opts) {
|
|
3023
|
-
|
|
3286
|
+
process21.stdout.write("\n");
|
|
3024
3287
|
const { wireGateway } = await inquirerPromptWire(opts.inquirer);
|
|
3025
3288
|
if (!wireGateway) return;
|
|
3026
|
-
const { connect } = await import("./ssh-
|
|
3289
|
+
const { connect } = await import("./ssh-B3NZAU4I.js");
|
|
3027
3290
|
const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
|
|
3028
|
-
const { wireGatewayMcp } = await import("./mcp-wire-
|
|
3029
|
-
const knownHostsPath =
|
|
3291
|
+
const { wireGatewayMcp } = await import("./mcp-wire-BFEY2Y6H.js");
|
|
3292
|
+
const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
|
|
3030
3293
|
const spin = spinner("Wiring gateway MCP client...");
|
|
3031
3294
|
const session = await connect({
|
|
3032
3295
|
host: opts.host,
|
|
@@ -3040,8 +3303,7 @@ async function maybeWireGatewayMcp(opts) {
|
|
|
3040
3303
|
const result = await wireGatewayMcp(session, opts.signal ?? AbortSignal.timeout(3e4));
|
|
3041
3304
|
if (result.status === "unsupported") {
|
|
3042
3305
|
spin.warn("This gateway has no `openclaw mcp add`, so nothing was wired.");
|
|
3043
|
-
info("OpenClaw 2026.
|
|
3044
|
-
info("Upgrade to 2026.7.1-2 and run: clawops mcp wire --stack " + opts.stackName);
|
|
3306
|
+
info("Upgrade OpenClaw to 2026.7.1-2 or later and run: clawops mcp wire --stack " + opts.stackName);
|
|
3045
3307
|
} else if (result.status === "probe-failed") {
|
|
3046
3308
|
spin.warn("The gateway could not reach clawops, so nothing was wired.");
|
|
3047
3309
|
info("clawops does not run on the gateway host. Start it where the gateway can reach it,");
|
|
@@ -3062,7 +3324,7 @@ async function maybeWireGatewayMcp(opts) {
|
|
|
3062
3324
|
}
|
|
3063
3325
|
}
|
|
3064
3326
|
async function maybeWireGatewayMcpCloud(opts) {
|
|
3065
|
-
|
|
3327
|
+
process21.stdout.write("\n");
|
|
3066
3328
|
const { wireGateway } = await inquirerPromptWire(opts.inquirer);
|
|
3067
3329
|
if (!wireGateway) return;
|
|
3068
3330
|
info(`Run this after the server is ready: clawops mcp wire --stack ${opts.stackName}`);
|
|
@@ -3078,8 +3340,8 @@ async function inquirerPromptWire(inquirer) {
|
|
|
3078
3340
|
function loadCatalogs(yaml) {
|
|
3079
3341
|
const specDir = resolveSpecDir();
|
|
3080
3342
|
try {
|
|
3081
|
-
const modelsRaw = yaml.load(readFileSync3(
|
|
3082
|
-
const integrationsRaw = yaml.load(readFileSync3(
|
|
3343
|
+
const modelsRaw = yaml.load(readFileSync3(path2.join(specDir, "models.yaml"), "utf-8"));
|
|
3344
|
+
const integrationsRaw = yaml.load(readFileSync3(path2.join(specDir, "integrations.yaml"), "utf-8"));
|
|
3083
3345
|
return {
|
|
3084
3346
|
models: modelsRaw.providers,
|
|
3085
3347
|
integrations: integrationsRaw.integrations
|
|
@@ -3130,13 +3392,13 @@ function instanceChoices(provider) {
|
|
|
3130
3392
|
return table[provider] ?? [];
|
|
3131
3393
|
}
|
|
3132
3394
|
async function maybeHarden(opts) {
|
|
3133
|
-
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-
|
|
3395
|
+
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-PCDK3UQ5.js");
|
|
3134
3396
|
const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
|
|
3135
3397
|
const config = getConfig2();
|
|
3136
3398
|
if (!config) return;
|
|
3137
3399
|
const availableModules = resolveModules(MODULE_CATALOG, void 0, opts.provider);
|
|
3138
3400
|
if (availableModules.length === 0) return;
|
|
3139
|
-
|
|
3401
|
+
process21.stdout.write("\n");
|
|
3140
3402
|
const { selectedIds } = await opts.inquirer.prompt([{
|
|
3141
3403
|
type: "checkbox",
|
|
3142
3404
|
name: "selectedIds",
|
|
@@ -3152,9 +3414,9 @@ async function maybeHarden(opts) {
|
|
|
3152
3414
|
return;
|
|
3153
3415
|
}
|
|
3154
3416
|
const modules = availableModules.filter((m) => selectedIds.includes(m.id));
|
|
3155
|
-
|
|
3417
|
+
process21.stdout.write("\nApplying hardening modules...\n\n");
|
|
3156
3418
|
try {
|
|
3157
|
-
const { buildContext } = await import("./context-
|
|
3419
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
3158
3420
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
3159
3421
|
const ctx = buildContext({ stack: opts.stackName });
|
|
3160
3422
|
const stackObj = await ctx.getStack();
|
|
@@ -3173,11 +3435,11 @@ async function maybeHarden(opts) {
|
|
|
3173
3435
|
modules,
|
|
3174
3436
|
signal: opts.signal,
|
|
3175
3437
|
onProgress: (r) => {
|
|
3176
|
-
|
|
3438
|
+
process21.stdout.write(` \xB7 ${r.module.label}
|
|
3177
3439
|
`);
|
|
3178
3440
|
}
|
|
3179
3441
|
});
|
|
3180
|
-
|
|
3442
|
+
process21.stdout.write("\n" + formatHardenSummary(results));
|
|
3181
3443
|
const errors = results.filter((r) => r.error);
|
|
3182
3444
|
if (errors.length > 0) {
|
|
3183
3445
|
for (const r of errors) fail(`${r.module.label}: ${r.error}`);
|
|
@@ -3191,26 +3453,52 @@ async function maybeHarden(opts) {
|
|
|
3191
3453
|
info("Run `clawops harden --stack " + opts.stackName + "` to retry.");
|
|
3192
3454
|
}
|
|
3193
3455
|
}
|
|
3456
|
+
async function detectSshCidrDefault() {
|
|
3457
|
+
const { detectEgressIp } = await import("./firewall-T3NTPZJD.js");
|
|
3458
|
+
const result = await detectEgressIp("https://ifconfig.me");
|
|
3459
|
+
if (!result.ok) return void 0;
|
|
3460
|
+
const ip = result.ip.trim();
|
|
3461
|
+
return ip === "" ? void 0 : ip.includes("/") ? ip : `${ip}/32`;
|
|
3462
|
+
}
|
|
3463
|
+
function validateCidrAnswer(value) {
|
|
3464
|
+
const v = value.trim();
|
|
3465
|
+
if (v === "") return "Required \u2014 enter a CIDR such as 203.0.113.4/32";
|
|
3466
|
+
const looksIpv6 = v.includes(":");
|
|
3467
|
+
if (!looksIpv6 && !/^(\d{1,3}\.){3}\d{1,3}\/(3[0-2]|[12]?\d)$/.test(v)) {
|
|
3468
|
+
return `"${v}" is not a CIDR. Use an address and a prefix, e.g. 203.0.113.4/32`;
|
|
3469
|
+
}
|
|
3470
|
+
return true;
|
|
3471
|
+
}
|
|
3472
|
+
function admitsInternet(cidr) {
|
|
3473
|
+
const v = cidr.trim();
|
|
3474
|
+
return v === "0.0.0.0/0" || v === "::/0";
|
|
3475
|
+
}
|
|
3476
|
+
function wizardChannels(integrations) {
|
|
3477
|
+
return integrations.filter((i) => i.wizardSupported !== false);
|
|
3478
|
+
}
|
|
3479
|
+
function startChannelConfig(integ) {
|
|
3480
|
+
return { ...integ.defaults ?? {} };
|
|
3481
|
+
}
|
|
3194
3482
|
|
|
3195
3483
|
// src/cli/commands/secret.ts
|
|
3196
|
-
import { defineCommand as
|
|
3197
|
-
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync4, unlinkSync, mkdirSync as
|
|
3484
|
+
import { defineCommand as defineCommand20 } from "citty";
|
|
3485
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync4, unlinkSync, mkdirSync as mkdirSync3, statSync } from "fs";
|
|
3198
3486
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
3199
|
-
import
|
|
3487
|
+
import path3 from "path";
|
|
3200
3488
|
import os2 from "os";
|
|
3201
|
-
import
|
|
3202
|
-
var SECRETS_DIR =
|
|
3489
|
+
import process22 from "process";
|
|
3490
|
+
var SECRETS_DIR = path3.join(os2.homedir(), ".clawops", "secrets");
|
|
3203
3491
|
function secretsDir() {
|
|
3204
3492
|
return SECRETS_DIR;
|
|
3205
3493
|
}
|
|
3206
3494
|
function secretPath(name) {
|
|
3207
|
-
return
|
|
3495
|
+
return path3.join(secretsDir(), name);
|
|
3208
3496
|
}
|
|
3209
3497
|
function listSecretNames() {
|
|
3210
3498
|
if (!existsSync3(secretsDir())) return [];
|
|
3211
3499
|
return readdirSync(secretsDir()).filter((f) => !f.startsWith("."));
|
|
3212
3500
|
}
|
|
3213
|
-
var listCmd =
|
|
3501
|
+
var listCmd = defineCommand20({
|
|
3214
3502
|
meta: { name: "list", description: "List all stored secrets and their status" },
|
|
3215
3503
|
args: {
|
|
3216
3504
|
json: { type: "boolean", description: "Emit JSON" }
|
|
@@ -3235,7 +3523,7 @@ var listCmd = defineCommand19({
|
|
|
3235
3523
|
}
|
|
3236
3524
|
});
|
|
3237
3525
|
if (args.json) {
|
|
3238
|
-
|
|
3526
|
+
process22.stdout.write(JSON.stringify(rows, null, 2) + "\n");
|
|
3239
3527
|
return;
|
|
3240
3528
|
}
|
|
3241
3529
|
const tableRows = rows.map((r) => [
|
|
@@ -3244,10 +3532,10 @@ var listCmd = defineCommand19({
|
|
|
3244
3532
|
r.modified,
|
|
3245
3533
|
r.path
|
|
3246
3534
|
]);
|
|
3247
|
-
|
|
3535
|
+
process22.stdout.write("\n" + renderTable(["Name", "Status", "Modified", "Path"], tableRows) + "\n\n");
|
|
3248
3536
|
}
|
|
3249
3537
|
});
|
|
3250
|
-
var setCmd =
|
|
3538
|
+
var setCmd = defineCommand20({
|
|
3251
3539
|
meta: { name: "set", description: "Create or update a secret" },
|
|
3252
3540
|
args: {
|
|
3253
3541
|
value: { type: "string", description: "Secret value (skips interactive prompt)" }
|
|
@@ -3256,7 +3544,7 @@ var setCmd = defineCommand19({
|
|
|
3256
3544
|
const [name] = args._ ?? [];
|
|
3257
3545
|
if (!name) {
|
|
3258
3546
|
failure("Usage: clawops secret set <name>");
|
|
3259
|
-
|
|
3547
|
+
process22.exit(2);
|
|
3260
3548
|
}
|
|
3261
3549
|
const inquirer = (await import("inquirer")).default;
|
|
3262
3550
|
let value;
|
|
@@ -3271,14 +3559,14 @@ var setCmd = defineCommand19({
|
|
|
3271
3559
|
}]);
|
|
3272
3560
|
value = secretValue;
|
|
3273
3561
|
}
|
|
3274
|
-
|
|
3562
|
+
mkdirSync3(secretsDir(), { recursive: true });
|
|
3275
3563
|
spawnSync2("chmod", ["700", secretsDir()], { stdio: "ignore" });
|
|
3276
3564
|
writeFileSync4(secretPath(name), value.trim(), { encoding: "utf-8", mode: 384 });
|
|
3277
3565
|
success(`Secret "${name}" saved to ${secretPath(name)} (chmod 600)`);
|
|
3278
3566
|
info("Run `clawops secret rotate <name>` to propagate it to a running stack.");
|
|
3279
3567
|
}
|
|
3280
3568
|
});
|
|
3281
|
-
var deleteCmd =
|
|
3569
|
+
var deleteCmd = defineCommand20({
|
|
3282
3570
|
meta: { name: "delete", description: "Remove a stored secret" },
|
|
3283
3571
|
args: {
|
|
3284
3572
|
yes: { type: "boolean", description: "Skip confirmation prompt" }
|
|
@@ -3287,12 +3575,12 @@ var deleteCmd = defineCommand19({
|
|
|
3287
3575
|
const [name] = args._ ?? [];
|
|
3288
3576
|
if (!name) {
|
|
3289
3577
|
failure("Usage: clawops secret delete <name>");
|
|
3290
|
-
|
|
3578
|
+
process22.exit(2);
|
|
3291
3579
|
}
|
|
3292
3580
|
const p = secretPath(name);
|
|
3293
3581
|
if (!existsSync3(p)) {
|
|
3294
3582
|
failure(`Secret "${name}" not found at ${p}`);
|
|
3295
|
-
|
|
3583
|
+
process22.exit(1);
|
|
3296
3584
|
}
|
|
3297
3585
|
const { listOverlays } = await import("./overlay-store-ADZPD7EU.js");
|
|
3298
3586
|
const referencingStacks = listOverlays().filter((o) => o.secrets.some((s) => s.name === name)).map((o) => o.stackName);
|
|
@@ -3317,7 +3605,7 @@ var deleteCmd = defineCommand19({
|
|
|
3317
3605
|
success(`Secret "${name}" deleted.`);
|
|
3318
3606
|
}
|
|
3319
3607
|
});
|
|
3320
|
-
var rotateCmd =
|
|
3608
|
+
var rotateCmd = defineCommand20({
|
|
3321
3609
|
meta: { name: "rotate", description: "Update a secret and re-apply config to a running stack" },
|
|
3322
3610
|
args: {
|
|
3323
3611
|
stack: { type: "string", description: "Stack name to re-apply (defaults to config default)" },
|
|
@@ -3327,7 +3615,7 @@ var rotateCmd = defineCommand19({
|
|
|
3327
3615
|
const [name] = args._ ?? [];
|
|
3328
3616
|
if (!name) {
|
|
3329
3617
|
failure("Usage: clawops secret rotate <name> [--stack <name>]");
|
|
3330
|
-
|
|
3618
|
+
process22.exit(2);
|
|
3331
3619
|
}
|
|
3332
3620
|
const inquirer = (await import("inquirer")).default;
|
|
3333
3621
|
let value;
|
|
@@ -3342,7 +3630,7 @@ var rotateCmd = defineCommand19({
|
|
|
3342
3630
|
}]);
|
|
3343
3631
|
value = secretValue;
|
|
3344
3632
|
}
|
|
3345
|
-
|
|
3633
|
+
mkdirSync3(secretsDir(), { recursive: true });
|
|
3346
3634
|
spawnSync2("chmod", ["700", secretsDir()], { stdio: "ignore" });
|
|
3347
3635
|
writeFileSync4(secretPath(name), value.trim(), { encoding: "utf-8", mode: 384 });
|
|
3348
3636
|
success(`Secret "${name}" updated.`);
|
|
@@ -3363,10 +3651,10 @@ var rotateCmd = defineCommand19({
|
|
|
3363
3651
|
}
|
|
3364
3652
|
info(`Re-applying config overlay to stack "${targetStack}"\u2026`);
|
|
3365
3653
|
try {
|
|
3366
|
-
const { buildContext } = await import("./context-
|
|
3367
|
-
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-
|
|
3654
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
3655
|
+
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-Z6IIZR3D.js");
|
|
3368
3656
|
const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
|
|
3369
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
3657
|
+
const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
|
|
3370
3658
|
const { localStateToConnectionInfo } = await import("./state-PRBIIN7I.js");
|
|
3371
3659
|
const ctx = buildContext({ stack: targetStack });
|
|
3372
3660
|
let conn;
|
|
@@ -3401,7 +3689,7 @@ var rotateCmd = defineCommand19({
|
|
|
3401
3689
|
}
|
|
3402
3690
|
}
|
|
3403
3691
|
});
|
|
3404
|
-
var auditCmd =
|
|
3692
|
+
var auditCmd = defineCommand20({
|
|
3405
3693
|
meta: { name: "audit", description: "Report missing secrets and unresolvable $secret: refs" },
|
|
3406
3694
|
args: {
|
|
3407
3695
|
json: { type: "boolean", description: "Emit JSON" }
|
|
@@ -3430,7 +3718,7 @@ var auditCmd = defineCommand19({
|
|
|
3430
3718
|
}
|
|
3431
3719
|
} else if (s.source === "env") {
|
|
3432
3720
|
const envVar = s.ref ?? s.name;
|
|
3433
|
-
if (!
|
|
3721
|
+
if (!process22.env[envVar]) {
|
|
3434
3722
|
issues.push({ kind: "missing-env", stack: overlay.stackName, secret: s.name, detail: `Env var not set: ${envVar}` });
|
|
3435
3723
|
}
|
|
3436
3724
|
} else {
|
|
@@ -3439,7 +3727,7 @@ var auditCmd = defineCommand19({
|
|
|
3439
3727
|
}
|
|
3440
3728
|
}
|
|
3441
3729
|
if (args.json) {
|
|
3442
|
-
|
|
3730
|
+
process22.stdout.write(JSON.stringify({ issues, ok: issues.length === 0 }, null, 2) + "\n");
|
|
3443
3731
|
return;
|
|
3444
3732
|
}
|
|
3445
3733
|
if (issues.length === 0) {
|
|
@@ -3452,11 +3740,11 @@ var auditCmd = defineCommand19({
|
|
|
3452
3740
|
const prefix = issue.stack ? `[${issue.stack}] ` : "";
|
|
3453
3741
|
warn(`${prefix}${issue.secret}: ${issue.detail}`);
|
|
3454
3742
|
}
|
|
3455
|
-
|
|
3743
|
+
process22.stdout.write("\n");
|
|
3456
3744
|
info("Run `clawops secret set <name>` to update a missing or empty secret.");
|
|
3457
3745
|
}
|
|
3458
3746
|
});
|
|
3459
|
-
var secret_default =
|
|
3747
|
+
var secret_default = defineCommand20({
|
|
3460
3748
|
meta: {
|
|
3461
3749
|
name: "secret",
|
|
3462
3750
|
description: "Manage clawops secrets (list | set | delete | rotate | audit)"
|
|
@@ -3472,7 +3760,7 @@ var secret_default = defineCommand19({
|
|
|
3472
3760
|
});
|
|
3473
3761
|
|
|
3474
3762
|
// src/cli/commands/help.ts
|
|
3475
|
-
import { defineCommand as
|
|
3763
|
+
import { defineCommand as defineCommand21 } from "citty";
|
|
3476
3764
|
var COMMANDS = [
|
|
3477
3765
|
["setup", "Interactive first-run wizard \u2014 configure, deploy, and wire AI apps"],
|
|
3478
3766
|
["init", "Write config and generate SSH key pair (non-interactive)"],
|
|
@@ -3504,7 +3792,7 @@ var GLOBAL_FLAGS = [
|
|
|
3504
3792
|
["--dry-run", "Preview without applying (mutating commands)"],
|
|
3505
3793
|
["--yes", "Skip interactive confirmations (CI mode)"]
|
|
3506
3794
|
];
|
|
3507
|
-
var help_default =
|
|
3795
|
+
var help_default = defineCommand21({
|
|
3508
3796
|
meta: {
|
|
3509
3797
|
name: "help",
|
|
3510
3798
|
description: "Show available commands and global flags"
|
|
@@ -3544,16 +3832,16 @@ ${dim("Run `clawops <command> --help` for command-specific flags.")}
|
|
|
3544
3832
|
});
|
|
3545
3833
|
|
|
3546
3834
|
// src/cli/commands/bug.ts
|
|
3547
|
-
import { defineCommand as
|
|
3548
|
-
import
|
|
3549
|
-
import { accessSync
|
|
3835
|
+
import { defineCommand as defineCommand22 } from "citty";
|
|
3836
|
+
import process23 from "process";
|
|
3837
|
+
import { accessSync, constants } from "fs";
|
|
3550
3838
|
import { execSync as execSync2 } from "child_process";
|
|
3551
3839
|
var ISSUES_URL = `${REPO_URL}/issues/new`;
|
|
3552
3840
|
var BODY_CAP = 2e3;
|
|
3553
|
-
var VERSION = true ? "
|
|
3841
|
+
var VERSION = true ? "2.0.0" : "dev";
|
|
3554
3842
|
async function collectContext() {
|
|
3555
|
-
const nodeVersion =
|
|
3556
|
-
const os3 = `${
|
|
3843
|
+
const nodeVersion = process23.version;
|
|
3844
|
+
const os3 = `${process23.platform} ${process23.arch}`;
|
|
3557
3845
|
let provider = "unknown";
|
|
3558
3846
|
let stackCount = 0;
|
|
3559
3847
|
let sshKey = "unknown";
|
|
@@ -3564,9 +3852,9 @@ async function collectContext() {
|
|
|
3564
3852
|
const stacks = Object.values(config.stacks);
|
|
3565
3853
|
stackCount = stacks.length;
|
|
3566
3854
|
provider = stacks[0]?.provider ?? "none";
|
|
3567
|
-
const keyPath = config.ssh.keyPath.replace(/^~/,
|
|
3855
|
+
const keyPath = config.ssh.keyPath.replace(/^~/, process23.env["HOME"] ?? "~");
|
|
3568
3856
|
try {
|
|
3569
|
-
|
|
3857
|
+
accessSync(keyPath, constants.R_OK);
|
|
3570
3858
|
sshKey = "present";
|
|
3571
3859
|
} catch {
|
|
3572
3860
|
sshKey = "missing";
|
|
@@ -3604,14 +3892,14 @@ function buildIssueUrl(title, command, context) {
|
|
|
3604
3892
|
}
|
|
3605
3893
|
function openBrowser2(url) {
|
|
3606
3894
|
try {
|
|
3607
|
-
const cmd =
|
|
3895
|
+
const cmd = process23.platform === "darwin" ? "open" : "xdg-open";
|
|
3608
3896
|
execSync2(`${cmd} "${url}"`, { stdio: "ignore" });
|
|
3609
3897
|
return true;
|
|
3610
3898
|
} catch {
|
|
3611
3899
|
return false;
|
|
3612
3900
|
}
|
|
3613
3901
|
}
|
|
3614
|
-
var bug_default =
|
|
3902
|
+
var bug_default = defineCommand22({
|
|
3615
3903
|
meta: {
|
|
3616
3904
|
name: "bug",
|
|
3617
3905
|
description: "Open a pre-filled GitHub issue with system context from clawops doctor"
|
|
@@ -3625,7 +3913,7 @@ var bug_default = defineCommand21({
|
|
|
3625
3913
|
let command = "";
|
|
3626
3914
|
if (!args.json) {
|
|
3627
3915
|
const { createInterface: createInterface3 } = await import("readline/promises");
|
|
3628
|
-
const rl = createInterface3({ input:
|
|
3916
|
+
const rl = createInterface3({ input: process23.stdin, output: process23.stdout });
|
|
3629
3917
|
try {
|
|
3630
3918
|
title = (await rl.question("Describe the issue in one line: ")).trim();
|
|
3631
3919
|
command = (await rl.question("Which command triggered it? (optional): ")).trim();
|
|
@@ -3638,12 +3926,12 @@ var bug_default = defineCommand21({
|
|
|
3638
3926
|
printJson(jsonOk({ url }));
|
|
3639
3927
|
return;
|
|
3640
3928
|
}
|
|
3641
|
-
|
|
3929
|
+
process23.stdout.write("\n");
|
|
3642
3930
|
info(`Opening: ${url}`);
|
|
3643
|
-
|
|
3931
|
+
process23.stdout.write("\n");
|
|
3644
3932
|
const opened = openBrowser2(url);
|
|
3645
3933
|
if (!opened) {
|
|
3646
|
-
|
|
3934
|
+
process23.stdout.write(`Open this URL to file the issue:
|
|
3647
3935
|
${url}
|
|
3648
3936
|
`);
|
|
3649
3937
|
}
|
|
@@ -3651,9 +3939,9 @@ ${url}
|
|
|
3651
3939
|
});
|
|
3652
3940
|
|
|
3653
3941
|
// src/cli/commands/harden.ts
|
|
3654
|
-
import { defineCommand as
|
|
3655
|
-
import
|
|
3656
|
-
var harden_default =
|
|
3942
|
+
import { defineCommand as defineCommand23 } from "citty";
|
|
3943
|
+
import process24 from "process";
|
|
3944
|
+
var harden_default = defineCommand23({
|
|
3657
3945
|
meta: {
|
|
3658
3946
|
name: "harden",
|
|
3659
3947
|
description: "Apply security hardening modules to a deployed stack"
|
|
@@ -3667,26 +3955,26 @@ var harden_default = defineCommand22({
|
|
|
3667
3955
|
json: { type: "boolean", description: "Emit structured JSON output" }
|
|
3668
3956
|
},
|
|
3669
3957
|
async run({ args }) {
|
|
3670
|
-
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-
|
|
3671
|
-
const { buildContext } = await import("./context-
|
|
3958
|
+
const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-PCDK3UQ5.js");
|
|
3959
|
+
const { buildContext } = await import("./context-KHKWJJMC.js");
|
|
3672
3960
|
const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
|
|
3673
3961
|
if (args.list) {
|
|
3674
|
-
|
|
3962
|
+
process24.stdout.write("\nAvailable hardening modules:\n\n");
|
|
3675
3963
|
for (const mod of MODULE_CATALOG) {
|
|
3676
3964
|
const on = mod.defaultOn ? chalk.green("ON ") : chalk.dim("off");
|
|
3677
3965
|
const providers = mod.providers === "all" ? "all" : mod.providers.join(", ");
|
|
3678
|
-
|
|
3966
|
+
process24.stdout.write(
|
|
3679
3967
|
` ${on} ${mod.id.padEnd(28)} ${mod.label} [${providers}]
|
|
3680
3968
|
`
|
|
3681
3969
|
);
|
|
3682
3970
|
}
|
|
3683
|
-
|
|
3971
|
+
process24.stdout.write("\n");
|
|
3684
3972
|
return;
|
|
3685
3973
|
}
|
|
3686
3974
|
const config = getConfig2();
|
|
3687
3975
|
if (!config) {
|
|
3688
3976
|
failure("No clawops config found. Run `clawops init` first.");
|
|
3689
|
-
|
|
3977
|
+
process24.exit(1);
|
|
3690
3978
|
}
|
|
3691
3979
|
const ctx = buildContext({ stack: args.stack });
|
|
3692
3980
|
const provider = ctx.adapter.name;
|
|
@@ -3696,17 +3984,17 @@ var harden_default = defineCommand22({
|
|
|
3696
3984
|
return;
|
|
3697
3985
|
}
|
|
3698
3986
|
if (!args.json) {
|
|
3699
|
-
|
|
3987
|
+
process24.stdout.write("\nModules to run:\n");
|
|
3700
3988
|
for (const mod of modules) {
|
|
3701
3989
|
const flag = mod.defaultOn ? "" : chalk.yellow(" [opt-in]");
|
|
3702
|
-
|
|
3990
|
+
process24.stdout.write(` \xB7 ${mod.label}${flag}
|
|
3703
3991
|
`);
|
|
3704
3992
|
}
|
|
3705
|
-
|
|
3993
|
+
process24.stdout.write("\n");
|
|
3706
3994
|
}
|
|
3707
3995
|
if (!args["dry-run"] && !args.yes && !args.json) {
|
|
3708
3996
|
const { createInterface: createInterface3 } = await import("readline/promises");
|
|
3709
|
-
const rl = createInterface3({ input:
|
|
3997
|
+
const rl = createInterface3({ input: process24.stdin, output: process24.stdout });
|
|
3710
3998
|
const answer = await rl.question("Apply these hardening modules? [y/N] ");
|
|
3711
3999
|
rl.close();
|
|
3712
4000
|
if (!answer.toLowerCase().startsWith("y")) {
|
|
@@ -3715,8 +4003,8 @@ var harden_default = defineCommand22({
|
|
|
3715
4003
|
}
|
|
3716
4004
|
}
|
|
3717
4005
|
const ac = new AbortController();
|
|
3718
|
-
|
|
3719
|
-
|
|
4006
|
+
process24.on("SIGINT", () => ac.abort());
|
|
4007
|
+
process24.on("SIGTERM", () => ac.abort());
|
|
3720
4008
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
3721
4009
|
const stackObj = await ctx.getStack();
|
|
3722
4010
|
const outputMap = await stackObj.outputs();
|
|
@@ -3730,7 +4018,7 @@ var harden_default = defineCommand22({
|
|
|
3730
4018
|
knownHostsPath: config.ssh.knownHostsPath
|
|
3731
4019
|
});
|
|
3732
4020
|
if (!args.json) {
|
|
3733
|
-
|
|
4021
|
+
process24.stdout.write(args["dry-run"] ? "Checking hardening state...\n\n" : "Applying hardening...\n\n");
|
|
3734
4022
|
}
|
|
3735
4023
|
const results = await runHardening(conn, {
|
|
3736
4024
|
modules,
|
|
@@ -3738,7 +4026,7 @@ var harden_default = defineCommand22({
|
|
|
3738
4026
|
signal: ac.signal,
|
|
3739
4027
|
onProgress: args.json ? void 0 : (r) => {
|
|
3740
4028
|
const icon = r.error ? chalk.red("\u2717") : r.applyResult?.changed ? chalk.green("\u2713") : chalk.dim("\xB7");
|
|
3741
|
-
|
|
4029
|
+
process24.stdout.write(` ${icon} ${r.module.label}
|
|
3742
4030
|
`);
|
|
3743
4031
|
}
|
|
3744
4032
|
});
|
|
@@ -3756,14 +4044,14 @@ var harden_default = defineCommand22({
|
|
|
3756
4044
|
}));
|
|
3757
4045
|
return;
|
|
3758
4046
|
}
|
|
3759
|
-
|
|
4047
|
+
process24.stdout.write("\n" + formatHardenSummary(results));
|
|
3760
4048
|
const errors = results.filter((r) => r.error);
|
|
3761
4049
|
if (errors.length > 0) {
|
|
3762
|
-
|
|
4050
|
+
process24.stdout.write("\n");
|
|
3763
4051
|
for (const r of errors) {
|
|
3764
4052
|
failure(`${r.module.label}: ${r.error}`);
|
|
3765
4053
|
}
|
|
3766
|
-
|
|
4054
|
+
process24.exit(1);
|
|
3767
4055
|
}
|
|
3768
4056
|
if (!args["dry-run"]) {
|
|
3769
4057
|
success("Hardening complete.");
|
|
@@ -3772,26 +4060,26 @@ var harden_default = defineCommand22({
|
|
|
3772
4060
|
});
|
|
3773
4061
|
|
|
3774
4062
|
// src/cli/error-handler.ts
|
|
3775
|
-
import
|
|
4063
|
+
import process25 from "process";
|
|
3776
4064
|
function handleError(err) {
|
|
3777
4065
|
if (err instanceof ClawopsError) {
|
|
3778
4066
|
failure(err.message);
|
|
3779
|
-
|
|
4067
|
+
process25.exit(err.exitCode);
|
|
3780
4068
|
}
|
|
3781
4069
|
if (err instanceof Error) {
|
|
3782
4070
|
failure(`Unexpected error: ${err.message}`);
|
|
3783
|
-
if (
|
|
3784
|
-
|
|
4071
|
+
if (process25.env["DEBUG"]) {
|
|
4072
|
+
process25.stderr.write(err.stack ?? "\n");
|
|
3785
4073
|
}
|
|
3786
|
-
|
|
4074
|
+
process25.exit(1);
|
|
3787
4075
|
}
|
|
3788
4076
|
failure(`Unknown error: ${String(err)}`);
|
|
3789
|
-
|
|
4077
|
+
process25.exit(1);
|
|
3790
4078
|
}
|
|
3791
4079
|
|
|
3792
4080
|
// src/cli/index.ts
|
|
3793
|
-
var CLI_VERSION = true ? "
|
|
3794
|
-
var main =
|
|
4081
|
+
var CLI_VERSION = true ? "2.0.0" : "dev";
|
|
4082
|
+
var main = defineCommand24({
|
|
3795
4083
|
meta: {
|
|
3796
4084
|
name: "clawops",
|
|
3797
4085
|
description: "Deploy and manage self-hosted OpenClaw instances across clouds",
|
|
@@ -3819,6 +4107,7 @@ var main = defineCommand23({
|
|
|
3819
4107
|
logs: logs_default,
|
|
3820
4108
|
config: config_default,
|
|
3821
4109
|
agents: agents_default,
|
|
4110
|
+
migrate: migrate_default,
|
|
3822
4111
|
gateway: gateway_default,
|
|
3823
4112
|
backup: backup_default,
|
|
3824
4113
|
stacks: stacks_default,
|