@clawops/cli 2.1.2 → 2.2.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 +122 -2
- package/dist/agent-activity-25FAWLAI.js +16 -0
- package/dist/{apply-HJ5GAKHW.js → apply-F5SQ2ZOS.js} +12 -12
- package/dist/{automation-MKCJ46I6.js → automation-NBYQ6MFZ.js} +1 -1
- package/dist/backup-flows-NDP5JTR3.js +29 -0
- package/dist/{bootstrap-NN647RCY.js → bootstrap-32TZXVDF.js} +5 -5
- package/dist/bootstrap.sh.tmpl +4 -0
- package/dist/chunk-27335SIH.js +51 -0
- package/dist/{chunk-X56CQ6GB.js → chunk-4MHDW2NB.js} +1 -1
- package/dist/{chunk-E4NJSJ54.js → chunk-4PY6PN4G.js} +51 -7
- package/dist/{chunk-3Z5TDXCJ.js → chunk-55UMO37O.js} +1 -1
- package/dist/{chunk-IQQTB7ER.js → chunk-66R52R7K.js} +10 -14
- package/dist/chunk-755X5OGS.js +213 -0
- package/dist/{chunk-QDNFADJI.js → chunk-AVERNLUY.js} +6 -6
- package/dist/{chunk-R3DTA35B.js → chunk-AX65JMDZ.js} +20 -20
- package/dist/{chunk-T5XCMYAV.js → chunk-CQOYUWR7.js} +766 -210
- package/dist/chunk-E6BRQK67.js +289 -0
- package/dist/{chunk-64PONSA5.js → chunk-GFIR2IIJ.js} +3 -1
- package/dist/chunk-LV7UO5CG.js +82 -0
- package/dist/{chunk-GIEVAGEK.js → chunk-N5ZYUTZS.js} +16 -1
- package/dist/{chunk-GFUK6KDR.js → chunk-OUQVKIIA.js} +372 -34
- package/dist/{chunk-XFN3YDZE.js → chunk-PASAVVNS.js} +1 -1
- package/dist/chunk-PCSX6Y3U.js +297 -0
- package/dist/chunk-POYS3EVQ.js +14 -0
- package/dist/{chunk-ELU3XQEA.js → chunk-RDUKRIRC.js} +1 -1
- package/dist/chunk-SX75L2T3.js +46 -0
- package/dist/{chunk-2Z7J3ERQ.js → chunk-TYNMOYVM.js} +1 -1
- package/dist/{chunk-2I7KCMXZ.js → chunk-UAJJEDCJ.js} +2 -2
- package/dist/{chunk-NUDIHZEH.js → chunk-WPZIJONN.js} +14 -12
- package/dist/{chunk-BELTQGHT.js → chunk-YPVWF7IA.js} +4 -4
- package/dist/{chunk-3OLUJPCB.js → chunk-Z3XLWZWF.js} +1 -1
- package/dist/cli.js +383 -925
- package/dist/conn-M2V5HBPJ.js +9 -0
- package/dist/{context-7VI3R6EK.js → context-EHSLWKSH.js} +6 -6
- package/dist/{docker-3PDO4TS7.js → docker-IKM5GRMC.js} +2 -2
- package/dist/{flows-RI7FGHAM.js → flows-6B2I6TRW.js} +7 -7
- package/dist/{generate-Q2BGVMH7.js → generate-4YYNW3IU.js} +9 -8
- package/dist/{harden-3KKCR7BL.js → harden-UHAOVQMZ.js} +5 -5
- package/dist/init-HOPXDNWY.js +13 -0
- package/dist/{mcp-wire-FQOMM7MK.js → mcp-wire-BXF4DJFT.js} +26 -19
- package/dist/migrate-flow-24XA4GZL.js +25 -0
- package/dist/{package-JFVGCNKU.js → package-HYGHRX35.js} +3 -1
- package/dist/{pool-PIIKJ4L2.js → pool-NT4XBSUT.js} +2 -2
- package/dist/{privileged-JFA6LLTV.js → privileged-5Y6O4H7Z.js} +1 -1
- package/dist/{ready-KDNRBNSU.js → ready-IJEUBFVX.js} +2 -2
- package/dist/{remote-config-3HZLUWEA.js → remote-config-OZYBMBSK.js} +3 -3
- package/dist/{runtime-X3KW5TCP.js → runtime-VW53TU2W.js} +5 -1
- package/dist/server-E3YA32ND.js +53 -0
- package/dist/ssh-HOM7GF3M.js +13 -0
- package/dist/{stack-config-LUTS2XAD.js → stack-config-ICQ5YHUL.js} +2 -2
- package/dist/stack-delete-NTIGKWUF.js +13 -0
- package/dist/{state-AOAYLYY5.js → state-AKZP2S4T.js} +2 -2
- package/dist/{store-OVB3DAJR.js → store-YOLQYXMB.js} +1 -1
- package/dist/{tailscale-cutover-6YP2QKFM.js → tailscale-cutover-V42HO5UM.js} +3 -3
- package/dist/{validate-JXKN5WD6.js → validate-DZE6LFEB.js} +1 -1
- package/dist/{wait-WO72TRRQ.js → wait-DOUNNJNN.js} +2 -2
- package/package.json +3 -1
- package/spec/mcp-tools.yaml +282 -35
- package/dist/logs-5A7YYA53.js +0 -15
- package/dist/server-B7I6HBQH.js +0 -46
- package/dist/ssh-VAI5DU5M.js +0 -11
- package/dist/upgrade-ULD4KFYC.js +0 -88
- /package/dist/{chunk-NUL5F6YV.js → chunk-SOZ62ZXW.js} +0 -0
|
@@ -1,23 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
listOverlays
|
|
4
|
+
} from "./chunk-6ZFIFDBJ.js";
|
|
2
5
|
import {
|
|
3
6
|
inspectContainer
|
|
4
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-Z3XLWZWF.js";
|
|
5
8
|
import {
|
|
6
9
|
execPrivileged
|
|
7
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-WPZIJONN.js";
|
|
8
11
|
import {
|
|
9
12
|
interpretProbe,
|
|
10
13
|
probeCommand
|
|
11
14
|
} from "./chunk-LT4XMO5D.js";
|
|
12
15
|
import {
|
|
13
16
|
PUBLISH_INSPECT_CMD,
|
|
17
|
+
STATE_DIR_CONTAINER,
|
|
14
18
|
STATE_DIR_HOST_LINUX,
|
|
15
19
|
configPathForOS,
|
|
20
|
+
gatewayRunCommand,
|
|
16
21
|
publishForRestart
|
|
17
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-N5ZYUTZS.js";
|
|
18
23
|
import {
|
|
19
|
-
GATEWAY_PORT
|
|
24
|
+
GATEWAY_PORT,
|
|
25
|
+
IMAGE_INSPECT_CMD,
|
|
26
|
+
imageForRestart,
|
|
27
|
+
versionOf
|
|
20
28
|
} from "./chunk-VOEJYRIK.js";
|
|
29
|
+
import {
|
|
30
|
+
err,
|
|
31
|
+
ok
|
|
32
|
+
} from "./chunk-EI7CQQ7D.js";
|
|
21
33
|
import {
|
|
22
34
|
chalk,
|
|
23
35
|
failure
|
|
@@ -146,8 +158,8 @@ function renderSnapshot(snap, opts) {
|
|
|
146
158
|
return lines.join("\n");
|
|
147
159
|
}
|
|
148
160
|
async function probeEntries() {
|
|
149
|
-
const { buildContext } = await import("./context-
|
|
150
|
-
const { getConfig } = await import("./store-
|
|
161
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
162
|
+
const { getConfig } = await import("./store-YOLQYXMB.js");
|
|
151
163
|
const config = getConfig();
|
|
152
164
|
if (!config) return [];
|
|
153
165
|
return Promise.all(
|
|
@@ -215,7 +227,7 @@ function renderMenu(entries, selectedIdx, showAll, noColor, confirmDelete) {
|
|
|
215
227
|
return lines.join("\n");
|
|
216
228
|
}
|
|
217
229
|
async function deleteFromRegistry(name) {
|
|
218
|
-
const { requireConfig, setConfig } = await import("./store-
|
|
230
|
+
const { requireConfig, setConfig } = await import("./store-YOLQYXMB.js");
|
|
219
231
|
const config = requireConfig();
|
|
220
232
|
const newStacks = { ...config.stacks };
|
|
221
233
|
delete newStacks[name];
|
|
@@ -323,9 +335,9 @@ async function runDashboard(session, stackName, opts, ac, setKeyHandler, menuMod
|
|
|
323
335
|
try {
|
|
324
336
|
lastSnapshot = await gatherSnapshot(session, ac.signal, opts.tailLines);
|
|
325
337
|
redraw();
|
|
326
|
-
} catch (
|
|
338
|
+
} catch (err2) {
|
|
327
339
|
if (!ac.signal.aborted) {
|
|
328
|
-
process.stdout.write("\x1B[2J\x1B[H Error gathering snapshot: " + (
|
|
340
|
+
process.stdout.write("\x1B[2J\x1B[H Error gathering snapshot: " + (err2 instanceof Error ? err2.message : String(err2)) + "\n");
|
|
329
341
|
}
|
|
330
342
|
}
|
|
331
343
|
if (!ac.signal.aborted) scheduleRefresh();
|
|
@@ -373,8 +385,8 @@ var monitor_default = defineCommand({
|
|
|
373
385
|
const tailLines = Math.max(1, parseInt(String(args.tail ?? "10"), 10) || 10);
|
|
374
386
|
const isTTY = Boolean(process.stdout.isTTY);
|
|
375
387
|
const noColor = Boolean(args["no-color"]) || !isTTY;
|
|
376
|
-
const { buildContext } = await import("./context-
|
|
377
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
388
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
389
|
+
const { acquireSession, drainPool } = await import("./pool-NT4XBSUT.js");
|
|
378
390
|
const ac = new AbortController();
|
|
379
391
|
process.on("SIGINT", () => ac.abort());
|
|
380
392
|
process.on("SIGTERM", () => ac.abort());
|
|
@@ -502,13 +514,211 @@ var monitor_default = defineCommand({
|
|
|
502
514
|
}
|
|
503
515
|
});
|
|
504
516
|
|
|
517
|
+
// src/openclaw/upgrade.ts
|
|
518
|
+
function judgePreflight(report) {
|
|
519
|
+
const { targetVersion, foundVersion, status, reason } = report;
|
|
520
|
+
if (status === "indeterminate") {
|
|
521
|
+
return {
|
|
522
|
+
ok: false,
|
|
523
|
+
reason: reason ?? "the database schema could not be determined, so compatibility with the target release is unknown"
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
if (typeof foundVersion === "number" && typeof targetVersion === "number") {
|
|
527
|
+
if (foundVersion > targetVersion) {
|
|
528
|
+
return {
|
|
529
|
+
ok: false,
|
|
530
|
+
reason: `the state database is at schema ${foundVersion}, but the target release understands ${targetVersion}. This is a downgrade across a schema boundary: the older release would read a database written by a newer one. Restore a backup taken before the upgrade instead.`
|
|
531
|
+
};
|
|
532
|
+
}
|
|
533
|
+
if (foundVersion < targetVersion) {
|
|
534
|
+
return { ok: true, note: `state will migrate from schema ${foundVersion} to ${targetVersion}` };
|
|
535
|
+
}
|
|
536
|
+
return { ok: true };
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
ok: false,
|
|
540
|
+
reason: reason ?? `preflight returned no schema version (status: ${status ?? "unknown"})`
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
function parsePreflight(stdout) {
|
|
544
|
+
try {
|
|
545
|
+
const parsed = JSON.parse(stdout.trim());
|
|
546
|
+
if (parsed !== null && typeof parsed === "object") return parsed;
|
|
547
|
+
} catch {
|
|
548
|
+
}
|
|
549
|
+
return void 0;
|
|
550
|
+
}
|
|
551
|
+
function snapshotCommand(image, stateDir, repo) {
|
|
552
|
+
return `docker run --rm -v ${stateDir}:/home/node/.openclaw ${image} openclaw backup sqlite create --global --repository ${repo} --json`;
|
|
553
|
+
}
|
|
554
|
+
function preflightCommand(targetImage, stateDir, snapshotDb) {
|
|
555
|
+
return `docker run --rm -v ${stateDir}:/home/node/.openclaw ${targetImage} openclaw database preflight ${snapshotDb} --json`;
|
|
556
|
+
}
|
|
557
|
+
function snapshotPathFrom(stdout) {
|
|
558
|
+
try {
|
|
559
|
+
const parsed = JSON.parse(stdout.trim());
|
|
560
|
+
if (parsed.ok === true && typeof parsed.snapshotPath === "string") return parsed.snapshotPath;
|
|
561
|
+
} catch {
|
|
562
|
+
}
|
|
563
|
+
return void 0;
|
|
564
|
+
}
|
|
565
|
+
function repairCommand(image, stateDir) {
|
|
566
|
+
return `docker run --rm -v ${stateDir}:/home/node/.openclaw ${image} openclaw doctor --fix --non-interactive --yes`;
|
|
567
|
+
}
|
|
568
|
+
function describeOutcome(o, version) {
|
|
569
|
+
switch (o.kind) {
|
|
570
|
+
case "started":
|
|
571
|
+
return `Gateway updated to ${version}.`;
|
|
572
|
+
case "repaired":
|
|
573
|
+
return `Gateway updated to ${version} after a one-shot repair (doctor --fix).`;
|
|
574
|
+
case "rolled-back":
|
|
575
|
+
return `Rolled back to ${o.to}: the gateway did not start on ${version} \u2014 ${o.reason}. The previous image is running again. If it also fails, restore the snapshot taken before the upgrade.`;
|
|
576
|
+
case "failed":
|
|
577
|
+
return `Upgrade to ${version} failed and the rollback did not come up either \u2014 ${o.reason}. The state snapshot taken before the upgrade is at ${o.snapshotPath}; restore it with \`openclaw backup sqlite restore\` before retrying.`;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
async function resolveUpgrade(steps, ctx) {
|
|
581
|
+
const first = await steps.gate();
|
|
582
|
+
if (first.ok) return { kind: "started" };
|
|
583
|
+
await steps.repair();
|
|
584
|
+
await steps.run(ctx.version);
|
|
585
|
+
const second = await steps.gate();
|
|
586
|
+
if (second.ok) return { kind: "repaired" };
|
|
587
|
+
const reason = second.reason ?? first.reason ?? "unknown";
|
|
588
|
+
if (!ctx.previousVersion) return { kind: "failed", reason, snapshotPath: ctx.snapshotPath };
|
|
589
|
+
await steps.run(ctx.previousVersion);
|
|
590
|
+
const back = await steps.gate();
|
|
591
|
+
return back.ok ? { kind: "rolled-back", to: ctx.previousVersion, reason } : { kind: "failed", reason, snapshotPath: ctx.snapshotPath };
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// src/openclaw/gateway-ops.ts
|
|
595
|
+
function dockerRunCmd(version, publish = "loopback") {
|
|
596
|
+
return gatewayRunCommand({
|
|
597
|
+
image: `ghcr.io/openclaw/openclaw:${version}`,
|
|
598
|
+
stateDir: STATE_DIR_HOST_LINUX,
|
|
599
|
+
publish
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
var STATUS_FORMAT = '{"status":"{{.State.Status}}","started":"{{.State.StartedAt}}","image":"{{.Config.Image}}"}';
|
|
603
|
+
async function gatewayStatus(session, signal) {
|
|
604
|
+
const inspected = await inspectContainer(session, "openclaw", STATUS_FORMAT, signal);
|
|
605
|
+
if (inspected.kind === "missing") return ok({ status: "not running", started: "", image: "" });
|
|
606
|
+
if (inspected.kind === "error") {
|
|
607
|
+
return err(`Could not ask docker about the gateway: ${inspected.detail}`);
|
|
608
|
+
}
|
|
609
|
+
try {
|
|
610
|
+
return ok(JSON.parse(inspected.value));
|
|
611
|
+
} catch {
|
|
612
|
+
return ok({ status: "unknown", started: "", image: "" });
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
async function resolveUpdateVersion(requested) {
|
|
616
|
+
const yaml = await import("./js-yaml-PTEEG4FO.js");
|
|
617
|
+
const { assertSupportedVersion, loadVersionSpec } = await import("./versions-W5LRQCAJ.js");
|
|
618
|
+
if (!requested) return ok(loadVersionSpec(yaml).support.recommended);
|
|
619
|
+
const result = await assertSupportedVersion(requested, yaml);
|
|
620
|
+
return result.ok ? ok(result.value) : err(result.error.message);
|
|
621
|
+
}
|
|
622
|
+
async function currentGatewayVersion(session, signal) {
|
|
623
|
+
const cur = imageForRestart((await execPrivileged(session, IMAGE_INSPECT_CMD, signal)).stdout);
|
|
624
|
+
return cur.ok ? versionOf(cur.value) : void 0;
|
|
625
|
+
}
|
|
626
|
+
async function updateGateway(session, version, hooks = {}) {
|
|
627
|
+
const { signal } = hooks;
|
|
628
|
+
const progress = hooks.onProgress ?? (() => {
|
|
629
|
+
});
|
|
630
|
+
const sleep = hooks.sleep ?? ((ms) => new Promise((res) => setTimeout(res, ms)));
|
|
631
|
+
const targetImage = `ghcr.io/openclaw/openclaw:${version}`;
|
|
632
|
+
const pullResult = await execPrivileged(session, `docker pull ${targetImage}`, signal);
|
|
633
|
+
if (pullResult.code !== 0) {
|
|
634
|
+
return { ok: false, version, message: `Pull failed: ${pullResult.stderr}` };
|
|
635
|
+
}
|
|
636
|
+
progress("Checking state compatibility...");
|
|
637
|
+
const cur = imageForRestart((await execPrivileged(session, IMAGE_INSPECT_CMD, signal)).stdout);
|
|
638
|
+
const previousVersion = cur.ok ? versionOf(cur.value) : void 0;
|
|
639
|
+
const snapRepo = `${STATE_DIR_CONTAINER}/snapshots`;
|
|
640
|
+
const snapOut = await execPrivileged(
|
|
641
|
+
session,
|
|
642
|
+
snapshotCommand(cur.ok ? cur.value : targetImage, STATE_DIR_HOST_LINUX, snapRepo),
|
|
643
|
+
signal
|
|
644
|
+
);
|
|
645
|
+
const snapPath = snapshotPathFrom(snapOut.stdout);
|
|
646
|
+
const hostSnapPath = snapPath?.startsWith(STATE_DIR_CONTAINER) ? STATE_DIR_HOST_LINUX + snapPath.slice(STATE_DIR_CONTAINER.length) : snapPath;
|
|
647
|
+
if (!snapPath) {
|
|
648
|
+
return {
|
|
649
|
+
ok: false,
|
|
650
|
+
version,
|
|
651
|
+
message: "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, 2e3)
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
const pre = await execPrivileged(
|
|
655
|
+
session,
|
|
656
|
+
preflightCommand(targetImage, STATE_DIR_HOST_LINUX, `${snapPath}/database.sqlite`),
|
|
657
|
+
signal
|
|
658
|
+
);
|
|
659
|
+
const report = parsePreflight(pre.stdout);
|
|
660
|
+
const verdict = report ? judgePreflight(report) : { ok: false, reason: `preflight produced no readable report: ${pre.stderr.slice(0, 200)}` };
|
|
661
|
+
if (!verdict.ok) {
|
|
662
|
+
return {
|
|
663
|
+
ok: false,
|
|
664
|
+
version,
|
|
665
|
+
message: `Refusing to upgrade to ${version}: ${verdict.reason}`,
|
|
666
|
+
hint: `A snapshot of the current state was kept at ${hostSnapPath}.`
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
if (verdict.note) hooks.onNote?.(verdict.note);
|
|
670
|
+
progress(`Updating gateway to ${version}...`);
|
|
671
|
+
const pubU = await execPrivileged(session, PUBLISH_INSPECT_CMD, signal);
|
|
672
|
+
const publish = publishForRestart(pubU.stdout);
|
|
673
|
+
const runResult = await execPrivileged(session, dockerRunCmd(version, publish), signal);
|
|
674
|
+
if (runResult.code !== 0) {
|
|
675
|
+
return {
|
|
676
|
+
ok: false,
|
|
677
|
+
version,
|
|
678
|
+
message: `Start failed: ${runResult.stderr}`,
|
|
679
|
+
hint: `State snapshot from before the upgrade: ${hostSnapPath}`
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
progress("Waiting for the gateway to start...");
|
|
683
|
+
const gate = async () => {
|
|
684
|
+
let last;
|
|
685
|
+
for (let i = 0; i < 15; i++) {
|
|
686
|
+
if (signal?.aborted) return { ok: false, reason: "aborted" };
|
|
687
|
+
const r = await execPrivileged(session, probeCommand("started", GATEWAY_PORT), signal);
|
|
688
|
+
const v = interpretProbe("started", r.stdout);
|
|
689
|
+
if (v.ok) return { ok: true };
|
|
690
|
+
last = v.reason;
|
|
691
|
+
await sleep(2e3);
|
|
692
|
+
}
|
|
693
|
+
return { ok: false, reason: last ?? "no response" };
|
|
694
|
+
};
|
|
695
|
+
const outcome = await resolveUpgrade(
|
|
696
|
+
{
|
|
697
|
+
gate,
|
|
698
|
+
repair: async () => {
|
|
699
|
+
progress("Gateway did not start; attempting one-shot repair...");
|
|
700
|
+
await execPrivileged(session, repairCommand(targetImage, STATE_DIR_HOST_LINUX), signal);
|
|
701
|
+
},
|
|
702
|
+
run: async (v) => {
|
|
703
|
+
await execPrivileged(session, dockerRunCmd(v, publish), signal);
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
{ version, previousVersion, snapshotPath: hostSnapPath ?? snapPath }
|
|
707
|
+
);
|
|
708
|
+
const message = describeOutcome(outcome, version);
|
|
709
|
+
if (outcome.kind === "started" || outcome.kind === "repaired") {
|
|
710
|
+
return { ok: true, version, ...previousVersion ? { previousVersion } : {}, outcome, message };
|
|
711
|
+
}
|
|
712
|
+
return { ok: false, version, message, outcome };
|
|
713
|
+
}
|
|
714
|
+
|
|
505
715
|
// src/diagnostics/index.ts
|
|
506
716
|
import process2 from "process";
|
|
507
717
|
import { accessSync, mkdirSync, readFileSync, constants } from "fs";
|
|
508
718
|
import ssh2 from "ssh2";
|
|
509
719
|
import path from "path";
|
|
510
720
|
async function runDiagnostics(opts = {}, deps = {}) {
|
|
511
|
-
const { getConfig, getConfigDir } = await import("./store-
|
|
721
|
+
const { getConfig, getConfigDir } = await import("./store-YOLQYXMB.js");
|
|
512
722
|
const config = getConfig();
|
|
513
723
|
const sections = [];
|
|
514
724
|
sections.push({ title: "Runtime", checks: await runtimeChecks(getConfigDir()) });
|
|
@@ -528,10 +738,10 @@ async function runDiagnostics(opts = {}, deps = {}) {
|
|
|
528
738
|
let handle;
|
|
529
739
|
try {
|
|
530
740
|
handle = await open(opts.stack, opts.signal);
|
|
531
|
-
} catch (
|
|
741
|
+
} catch (err2) {
|
|
532
742
|
sections.push({
|
|
533
743
|
title: "Remote health",
|
|
534
|
-
checks: [
|
|
744
|
+
checks: [connectionCheck(messageOf(err2))]
|
|
535
745
|
});
|
|
536
746
|
}
|
|
537
747
|
if (handle) {
|
|
@@ -546,7 +756,7 @@ async function runDiagnostics(opts = {}, deps = {}) {
|
|
|
546
756
|
});
|
|
547
757
|
} finally {
|
|
548
758
|
handle.release();
|
|
549
|
-
const { drainPool } = await import("./pool-
|
|
759
|
+
const { drainPool } = await import("./pool-NT4XBSUT.js");
|
|
550
760
|
drainPool();
|
|
551
761
|
}
|
|
552
762
|
}
|
|
@@ -654,8 +864,8 @@ async function providerCredentialCheck(provider, why) {
|
|
|
654
864
|
const adapter = getProvider(provider);
|
|
655
865
|
const result = await adapter.validateConfig();
|
|
656
866
|
return result.ok ? { name: provider, status: "pass", detail: why } : { name: provider, status: "fail", detail: result.errors.join("; ") };
|
|
657
|
-
} catch (
|
|
658
|
-
return { name: provider, status: "fail", detail: messageOf(
|
|
867
|
+
} catch (err2) {
|
|
868
|
+
return { name: provider, status: "fail", detail: messageOf(err2) };
|
|
659
869
|
}
|
|
660
870
|
}
|
|
661
871
|
async function credentialChecks(config, provider) {
|
|
@@ -682,15 +892,15 @@ async function credentialChecks(config, provider) {
|
|
|
682
892
|
if (result.ok) {
|
|
683
893
|
checks.push({ name: providerName, status: "pass", detail: `stack "${stackName}"` });
|
|
684
894
|
} else {
|
|
685
|
-
for (const
|
|
686
|
-
checks.push({ name: providerName, status: "fail", detail: `stack "${stackName}" \u2014 ${
|
|
895
|
+
for (const err2 of result.errors) {
|
|
896
|
+
checks.push({ name: providerName, status: "fail", detail: `stack "${stackName}" \u2014 ${err2}` });
|
|
687
897
|
}
|
|
688
898
|
}
|
|
689
|
-
} catch (
|
|
899
|
+
} catch (err2) {
|
|
690
900
|
checks.push({
|
|
691
901
|
name: providerName,
|
|
692
902
|
status: "fail",
|
|
693
|
-
detail: `stack "${stackName}" \u2014 ${messageOf(
|
|
903
|
+
detail: `stack "${stackName}" \u2014 ${messageOf(err2)}`
|
|
694
904
|
});
|
|
695
905
|
}
|
|
696
906
|
}
|
|
@@ -725,7 +935,7 @@ async function versionChecks() {
|
|
|
725
935
|
}
|
|
726
936
|
async function remoteChecks(session, signal) {
|
|
727
937
|
const checks = [];
|
|
728
|
-
const { containerStatus } = await import("./docker-
|
|
938
|
+
const { containerStatus } = await import("./docker-IKM5GRMC.js");
|
|
729
939
|
const container = await containerStatus(session, "openclaw", signal);
|
|
730
940
|
checks.push(
|
|
731
941
|
container.error ? {
|
|
@@ -808,8 +1018,8 @@ async function deployedVersionCheck(image, deployedVersion) {
|
|
|
808
1018
|
}
|
|
809
1019
|
async function hardeningChecks(conn, stack, signal) {
|
|
810
1020
|
try {
|
|
811
|
-
const { MODULE_CATALOG, resolveModules, withRemoteExec } = await import("./harden-
|
|
812
|
-
const { buildContext } = await import("./context-
|
|
1021
|
+
const { MODULE_CATALOG, resolveModules, withRemoteExec } = await import("./harden-UHAOVQMZ.js");
|
|
1022
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
813
1023
|
const provider = buildContext({ stack }).adapter.name;
|
|
814
1024
|
const modules = resolveModules(MODULE_CATALOG, void 0, provider);
|
|
815
1025
|
const checks = [];
|
|
@@ -826,15 +1036,15 @@ async function hardeningChecks(conn, stack, signal) {
|
|
|
826
1036
|
}
|
|
827
1037
|
});
|
|
828
1038
|
return checks;
|
|
829
|
-
} catch (
|
|
830
|
-
return [{ name: "Hardening checks", status: "fail", detail: messageOf(
|
|
1039
|
+
} catch (err2) {
|
|
1040
|
+
return [{ name: "Hardening checks", status: "fail", detail: messageOf(err2) }];
|
|
831
1041
|
}
|
|
832
1042
|
}
|
|
833
1043
|
async function defaultOpenSession(stack, signal) {
|
|
834
|
-
const { getConfig } = await import("./store-
|
|
835
|
-
const { buildContext } = await import("./context-
|
|
1044
|
+
const { getConfig } = await import("./store-YOLQYXMB.js");
|
|
1045
|
+
const { buildContext } = await import("./context-EHSLWKSH.js");
|
|
836
1046
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
837
|
-
const { acquireSession } = await import("./pool-
|
|
1047
|
+
const { acquireSession } = await import("./pool-NT4XBSUT.js");
|
|
838
1048
|
const config = getConfig();
|
|
839
1049
|
if (!config) throw new Error("no config \u2014 run `clawops init`");
|
|
840
1050
|
const ctx = buildContext({ stack });
|
|
@@ -859,8 +1069,18 @@ async function defaultOpenSession(stack, signal) {
|
|
|
859
1069
|
});
|
|
860
1070
|
return { session, release, conn };
|
|
861
1071
|
}
|
|
862
|
-
function messageOf(
|
|
863
|
-
return
|
|
1072
|
+
function messageOf(err2) {
|
|
1073
|
+
return err2 instanceof Error ? err2.message : String(err2);
|
|
1074
|
+
}
|
|
1075
|
+
function connectionCheck(message) {
|
|
1076
|
+
const [summary, ...rest] = message.split("\n");
|
|
1077
|
+
const remedy = rest.join("\n ").trim();
|
|
1078
|
+
return {
|
|
1079
|
+
name: "Connection",
|
|
1080
|
+
status: "fail",
|
|
1081
|
+
detail: summary,
|
|
1082
|
+
...remedy ? { remedy } : {}
|
|
1083
|
+
};
|
|
864
1084
|
}
|
|
865
1085
|
async function accountChecks(config, stack, provider, instanceType) {
|
|
866
1086
|
if (!config && !provider) return [];
|
|
@@ -883,8 +1103,8 @@ async function accountChecks(config, stack, provider, instanceType) {
|
|
|
883
1103
|
detail: r.detail,
|
|
884
1104
|
remedy: r.mutates ? `clawops setup can fix this: ${r.mutates}` : void 0
|
|
885
1105
|
}));
|
|
886
|
-
} catch (
|
|
887
|
-
return [{ name: "Account preflight", status: "warn", detail: messageOf(
|
|
1106
|
+
} catch (err2) {
|
|
1107
|
+
return [{ name: "Account preflight", status: "warn", detail: messageOf(err2) }];
|
|
888
1108
|
}
|
|
889
1109
|
}
|
|
890
1110
|
function bucketFromStateUrl(stateUrl) {
|
|
@@ -893,9 +1113,127 @@ function bucketFromStateUrl(stateUrl) {
|
|
|
893
1113
|
return match?.[1];
|
|
894
1114
|
}
|
|
895
1115
|
|
|
1116
|
+
// src/secrets/store.ts
|
|
1117
|
+
import { existsSync, readdirSync, readFileSync as readFileSync2, statSync, unlinkSync } from "fs";
|
|
1118
|
+
import path2 from "path";
|
|
1119
|
+
import os from "os";
|
|
1120
|
+
import process3 from "process";
|
|
1121
|
+
function secretsDir() {
|
|
1122
|
+
return path2.join(os.homedir(), ".clawops", "secrets");
|
|
1123
|
+
}
|
|
1124
|
+
function resolveSecretPath(name) {
|
|
1125
|
+
const dir = secretsDir();
|
|
1126
|
+
const invalid = name.length === 0 || name === "." || name === ".." || name.includes("/") || name.includes("\\") || name.includes("\0") || path2.dirname(path2.resolve(dir, name)) !== path2.resolve(dir);
|
|
1127
|
+
if (invalid) {
|
|
1128
|
+
return {
|
|
1129
|
+
ok: false,
|
|
1130
|
+
reason: `Secret name "${name}" is not valid: a secret name is a single file name inside ${dir} (no "/" or "\\", and not "." or "..").`
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
return { ok: true, path: path2.join(dir, name) };
|
|
1134
|
+
}
|
|
1135
|
+
function listSecretNames() {
|
|
1136
|
+
if (!existsSync(secretsDir())) return [];
|
|
1137
|
+
return readdirSync(secretsDir()).filter((f) => !f.startsWith("."));
|
|
1138
|
+
}
|
|
1139
|
+
function listSecrets() {
|
|
1140
|
+
return listSecretNames().map((name) => {
|
|
1141
|
+
const p = path2.join(secretsDir(), name);
|
|
1142
|
+
try {
|
|
1143
|
+
const stat = statSync(p);
|
|
1144
|
+
const nonEmpty = readFileSync2(p, "utf-8").trim().length > 0;
|
|
1145
|
+
const modified = stat.mtime.toISOString().slice(0, 10);
|
|
1146
|
+
return { name, status: nonEmpty ? "ok" : "empty", source: "file", path: p, modified, resolvable: nonEmpty };
|
|
1147
|
+
} catch {
|
|
1148
|
+
return { name, status: "unreadable", source: "file", path: p, modified: "\u2014", resolvable: false };
|
|
1149
|
+
}
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
var NO_SECRETS_MESSAGE = "No secrets stored in ~/.clawops/secrets/";
|
|
1153
|
+
var SET_SECRET_HINT = "Run `clawops secret set <name>` to add one.";
|
|
1154
|
+
function auditSecrets() {
|
|
1155
|
+
const issues = [];
|
|
1156
|
+
for (const name of listSecretNames()) {
|
|
1157
|
+
const p = path2.join(secretsDir(), name);
|
|
1158
|
+
try {
|
|
1159
|
+
if (readFileSync2(p, "utf-8").trim().length === 0) {
|
|
1160
|
+
issues.push({ kind: "empty-secret", secret: name, detail: `File exists but is empty: ${p}` });
|
|
1161
|
+
}
|
|
1162
|
+
} catch {
|
|
1163
|
+
issues.push({ kind: "unreadable-secret", secret: name, detail: `Cannot read: ${p}` });
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
for (const overlay of listOverlays()) {
|
|
1167
|
+
for (const s of overlay.secrets) {
|
|
1168
|
+
if (s.source === "file") {
|
|
1169
|
+
const ref = s.ref ?? path2.join(secretsDir(), s.name);
|
|
1170
|
+
if (!existsSync(ref)) {
|
|
1171
|
+
issues.push({ kind: "missing-file", stack: overlay.stackName, secret: s.name, detail: `File not found: ${ref}` });
|
|
1172
|
+
}
|
|
1173
|
+
} else if (s.source === "env") {
|
|
1174
|
+
const envVar = s.ref ?? s.name;
|
|
1175
|
+
if (!process3.env[envVar]) {
|
|
1176
|
+
issues.push({ kind: "missing-env", stack: overlay.stackName, secret: s.name, detail: `Env var not set: ${envVar}` });
|
|
1177
|
+
}
|
|
1178
|
+
} else {
|
|
1179
|
+
issues.push({ kind: "cloud-sm-unresolved", stack: overlay.stackName, secret: s.name, detail: `Source "${s.source}" is not auto-resolved \u2014 set manually` });
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
return { issues, ok: issues.length === 0 };
|
|
1184
|
+
}
|
|
1185
|
+
var AUDIT_CLEAN_MESSAGE = "All secrets are resolvable. No issues found.";
|
|
1186
|
+
var AUDIT_FIX_HINT = "Run `clawops secret set <name>` to update a missing or empty secret.";
|
|
1187
|
+
function auditIssuesHeadline(count) {
|
|
1188
|
+
return `${count} issue${count === 1 ? "" : "s"} found:`;
|
|
1189
|
+
}
|
|
1190
|
+
function formatAuditIssue(issue) {
|
|
1191
|
+
const prefix = issue.stack ? `[${issue.stack}] ` : "";
|
|
1192
|
+
return `${prefix}${issue.secret}: ${issue.detail}`;
|
|
1193
|
+
}
|
|
1194
|
+
function prepareSecretDelete(name) {
|
|
1195
|
+
const resolved = resolveSecretPath(name);
|
|
1196
|
+
if (!resolved.ok) return { ok: false, kind: "invalid-name", reason: resolved.reason };
|
|
1197
|
+
const p = resolved.path;
|
|
1198
|
+
if (!existsSync(p)) {
|
|
1199
|
+
return { ok: false, kind: "not-found", reason: `Secret "${name}" not found at ${p}` };
|
|
1200
|
+
}
|
|
1201
|
+
const referencingStacks = listOverlays().filter((o) => o.secrets.some((s) => s.name === name)).map((o) => o.stackName);
|
|
1202
|
+
const warnings = referencingStacks.length > 0 ? [
|
|
1203
|
+
`Secret "${name}" is referenced by stack(s): ${referencingStacks.join(", ")}`,
|
|
1204
|
+
"Deleting it will leave those stacks with an unresolvable $secret: ref."
|
|
1205
|
+
] : [];
|
|
1206
|
+
return { ok: true, name, path: p, referencingStacks, warnings };
|
|
1207
|
+
}
|
|
1208
|
+
function deleteConfirmQuestion(name) {
|
|
1209
|
+
return `Delete secret "${name}"?`;
|
|
1210
|
+
}
|
|
1211
|
+
function deletePreparedSecret(prepared) {
|
|
1212
|
+
unlinkSync(prepared.path);
|
|
1213
|
+
return `Secret "${prepared.name}" deleted.`;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
896
1216
|
export {
|
|
1217
|
+
dockerRunCmd,
|
|
1218
|
+
gatewayStatus,
|
|
1219
|
+
resolveUpdateVersion,
|
|
1220
|
+
currentGatewayVersion,
|
|
1221
|
+
updateGateway,
|
|
897
1222
|
runDiagnostics,
|
|
898
1223
|
formatUptime,
|
|
899
1224
|
gatherSnapshot,
|
|
900
|
-
monitor_default
|
|
1225
|
+
monitor_default,
|
|
1226
|
+
secretsDir,
|
|
1227
|
+
resolveSecretPath,
|
|
1228
|
+
listSecrets,
|
|
1229
|
+
NO_SECRETS_MESSAGE,
|
|
1230
|
+
SET_SECRET_HINT,
|
|
1231
|
+
auditSecrets,
|
|
1232
|
+
AUDIT_CLEAN_MESSAGE,
|
|
1233
|
+
AUDIT_FIX_HINT,
|
|
1234
|
+
auditIssuesHeadline,
|
|
1235
|
+
formatAuditIssue,
|
|
1236
|
+
prepareSecretDelete,
|
|
1237
|
+
deleteConfirmQuestion,
|
|
1238
|
+
deletePreparedSecret
|
|
901
1239
|
};
|
|
@@ -44,7 +44,7 @@ async function validatePlanConfig(plan) {
|
|
|
44
44
|
]);
|
|
45
45
|
const { loadVersionSpec } = await import("./versions-W5LRQCAJ.js");
|
|
46
46
|
const spec = loadVersionSpec(yaml);
|
|
47
|
-
const { deepMerge } = await import("./remote-config-
|
|
47
|
+
const { deepMerge } = await import("./remote-config-OZYBMBSK.js");
|
|
48
48
|
const merged = deepMerge(
|
|
49
49
|
{ gateway: { mode: "local" } },
|
|
50
50
|
overlay
|