@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.
Files changed (57) hide show
  1. package/README.md +412 -70
  2. package/dist/{apply-F3KY2HTB.js → apply-AM5HERE7.js} +8 -6
  3. package/dist/{aws-L4RR2LSE.js → aws-AMON7DA7.js} +20 -9
  4. package/dist/{azure-7OXQENQL.js → azure-T4G6OBJQ.js} +19 -8
  5. package/dist/{bootstrap-HL4DNUNG.js → bootstrap-DMRPWJGN.js} +28 -19
  6. package/dist/bootstrap.sh.tmpl +51 -31
  7. package/dist/channels-KBUR42IU.js +53 -0
  8. package/dist/{chunk-DQDAWNGB.js → chunk-3WYA2QDM.js} +1 -1
  9. package/dist/chunk-6TIYVHPX.js +152 -0
  10. package/dist/{chunk-3XK66DKK.js → chunk-AHAAEXWC.js} +41 -0
  11. package/dist/{chunk-GZN7PKP4.js → chunk-AX74VXTV.js} +292 -94
  12. package/dist/{chunk-C5X375RO.js → chunk-CKSPROD5.js} +59 -15
  13. package/dist/{chunk-UKTP332Z.js → chunk-GAZPPSKU.js} +6 -7
  14. package/dist/{chunk-FNS56UQY.js → chunk-GYV4KNW7.js} +319 -252
  15. package/dist/{chunk-PVUVI35Y.js → chunk-JW5A7E3X.js} +5 -1
  16. package/dist/chunk-LT4XMO5D.js +50 -0
  17. package/dist/{chunk-P5LKWRPL.js → chunk-LU2V3LXA.js} +3 -3
  18. package/dist/chunk-N44K3LHE.js +140 -0
  19. package/dist/chunk-NUDIHZEH.js +55 -0
  20. package/dist/chunk-SBLQH2TJ.js +56 -0
  21. package/dist/chunk-UFRO4H5W.js +228 -0
  22. package/dist/{chunk-A4QR4TXW.js → chunk-WHJMKDV7.js} +24 -4
  23. package/dist/chunk-YR3O2I3S.js +101 -0
  24. package/dist/cli.js +866 -577
  25. package/dist/config-validate-K2N6XLDX.js +76 -0
  26. package/dist/{context-5LCUG2QR.js → context-KHKWJJMC.js} +1 -1
  27. package/dist/{firewall-XQGO7HWK.js → firewall-T3NTPZJD.js} +10 -0
  28. package/dist/{gcp-UG6AWPIZ.js → gcp-2JBTD7ZM.js} +19 -8
  29. package/dist/{generate-OAGYT3VH.js → generate-2XV2GSP2.js} +4 -4
  30. package/dist/{harden-URPXLSVF.js → harden-PCDK3UQ5.js} +48 -11
  31. package/dist/health-5CCOXYGG.js +11 -0
  32. package/dist/logs-P7YCKVF4.js +15 -0
  33. package/dist/{mcp-wire-7E5JQY47.js → mcp-wire-BFEY2Y6H.js} +23 -17
  34. package/dist/{package-5AR4ZPK4.js → package-3HUW57XH.js} +3 -3
  35. package/dist/plugins-DTST3OER.js +49 -0
  36. package/dist/{pool-QGFQRMGU.js → pool-EX4ZTA5P.js} +2 -2
  37. package/dist/privileged-JFA6LLTV.js +17 -0
  38. package/dist/{remote-config-QDLN4UMG.js → remote-config-Z6IIZR3D.js} +3 -3
  39. package/dist/runtime-LZ7NA53V.js +42 -0
  40. package/dist/{server-FIZCOLHL.js → server-A5AB67GQ.js} +16 -12
  41. package/dist/spec-path-KQOOIQCA.js +9 -0
  42. package/dist/{ssh-CNJNLM3E.js → ssh-B3NZAU4I.js} +1 -1
  43. package/dist/upgrade-ULD4KFYC.js +88 -0
  44. package/dist/validate-BOQRWX4F.js +14 -0
  45. package/dist/{version-guard-VUCNZXQS.js → version-guard-GPSTCPT7.js} +3 -3
  46. package/dist/{versions-2UVAT6PQ.js → versions-W5LRQCAJ.js} +2 -2
  47. package/package.json +3 -3
  48. package/spec/deploy-plan.schema.json +114 -25
  49. package/spec/integrations.yaml +135 -28
  50. package/spec/mcp-tools.yaml +68 -35
  51. package/spec/models.yaml +9 -0
  52. package/spec/openclaw-2.0.config.schema.json +1 -0
  53. package/spec/openclaw-versions.yaml +116 -27
  54. package/dist/chunk-35WVQY3X.js +0 -122
  55. package/dist/chunk-6X45DDGH.js +0 -42
  56. package/dist/chunk-SEZMVVAS.js +0 -156
  57. package/dist/validate-CFQ4FH2T.js +0 -10
@@ -23,7 +23,11 @@ function resolveSpecDir(startUrl = import.meta.url) {
23
23
  `Cannot locate the spec/ directory (searched upward from ${dirname(fileURLToPath(startUrl))}). If this is an installed package, spec/ may be missing from the published files.`
24
24
  );
25
25
  }
26
+ function _resetSpecDirCache() {
27
+ _cached = void 0;
28
+ }
26
29
 
27
30
  export {
28
- resolveSpecDir
31
+ resolveSpecDir,
32
+ _resetSpecDirCache
29
33
  };
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/openclaw/health.ts
4
+ var HEALTH_PATHS = {
5
+ /** Process is up and serving. `{"ok":true,"status":"live"}` */
6
+ live: "/health",
7
+ /** Startup completed — the gate for "is this deploy finished". `{"ok":true,"status":"started"}` */
8
+ started: "/startupz",
9
+ /** Accepting work. `{"ready":true}` */
10
+ ready: "/readyz"
11
+ };
12
+ function probeCommand(kind, port, pathPrefix = "") {
13
+ return `${pathPrefix}curl -fsS -m 5 http://127.0.0.1:${port}${HEALTH_PATHS[kind]} 2>/dev/null || true`;
14
+ }
15
+ function interpretProbe(kind, body) {
16
+ const text = body.trim();
17
+ if (text === "") return { ok: false, reason: "no response from the gateway" };
18
+ if (text.startsWith("<")) {
19
+ return {
20
+ ok: false,
21
+ reason: `${HEALTH_PATHS[kind]} returned HTML, not JSON \u2014 the gateway serves its Control UI on a catch-all route, so an unknown path answers 200 with the SPA. The endpoint is missing or renamed on this OpenClaw version.`
22
+ };
23
+ }
24
+ let parsed;
25
+ try {
26
+ parsed = JSON.parse(text);
27
+ } catch {
28
+ return { ok: false, reason: `${HEALTH_PATHS[kind]} returned unparseable JSON: ${text.slice(0, 80)}` };
29
+ }
30
+ if (parsed === null || typeof parsed !== "object") {
31
+ return { ok: false, reason: `${HEALTH_PATHS[kind]} returned ${text.slice(0, 40)}` };
32
+ }
33
+ const obj = parsed;
34
+ if (kind === "ready") {
35
+ return obj["ready"] === true ? { ok: true } : { ok: false, reason: `gateway is not ready: ${text.slice(0, 80)}` };
36
+ }
37
+ if (obj["ok"] !== true) {
38
+ return { ok: false, reason: `gateway reported not ok: ${text.slice(0, 80)}` };
39
+ }
40
+ if (kind === "started" && obj["status"] !== "started") {
41
+ return { ok: false, reason: `gateway is still starting (status: ${String(obj["status"])})` };
42
+ }
43
+ return { ok: true };
44
+ }
45
+
46
+ export {
47
+ HEALTH_PATHS,
48
+ probeCommand,
49
+ interpretProbe
50
+ };
@@ -56,17 +56,17 @@ function makeProviderProxy(name) {
56
56
  if (resolved) return resolved;
57
57
  switch (name) {
58
58
  case "gcp": {
59
- const mod = await import("./gcp-UG6AWPIZ.js");
59
+ const mod = await import("./gcp-2JBTD7ZM.js");
60
60
  resolved = mod.default;
61
61
  return resolved;
62
62
  }
63
63
  case "aws": {
64
- const mod = await import("./aws-L4RR2LSE.js");
64
+ const mod = await import("./aws-AMON7DA7.js");
65
65
  resolved = mod.default;
66
66
  return resolved;
67
67
  }
68
68
  case "azure": {
69
- const mod = await import("./azure-7OXQENQL.js");
69
+ const mod = await import("./azure-T4G6OBJQ.js");
70
70
  resolved = mod.default;
71
71
  return resolved;
72
72
  }
@@ -0,0 +1,140 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ execPrivileged
4
+ } from "./chunk-NUDIHZEH.js";
5
+ import {
6
+ CONTAINER_UID,
7
+ GATEWAY_PORT,
8
+ IMAGE_INSPECT_CMD,
9
+ PUBLISH_INSPECT_CMD,
10
+ configPathForOS,
11
+ gatewayRunCommand,
12
+ imageForRestart,
13
+ publishForRestart,
14
+ stateDirForOS
15
+ } from "./chunk-6TIYVHPX.js";
16
+
17
+ // src/plan/remote-config.ts
18
+ var OPENCLAW_CONFIG_LINUX = configPathForOS("Linux");
19
+ var OPENCLAW_CONFIG_MACOS = configPathForOS("Darwin");
20
+ var OPENCLAW_CONFIG = OPENCLAW_CONFIG_LINUX;
21
+ var OPENCLAW_TMP = "/tmp/clawops-config.json.tmp";
22
+ async function detectOS(session, signal) {
23
+ const result = await session.exec("uname -s", signal);
24
+ return result.stdout.trim() === "Darwin" ? "Darwin" : "Linux";
25
+ }
26
+ async function readRemoteConfig(session, signal) {
27
+ const os = await detectOS(session, signal);
28
+ const configPath = configPathForOS(os);
29
+ const result = os === "Darwin" ? await session.exec(`cat ${configPath}`, signal) : await execPrivileged(session, `cat ${configPath}`, signal);
30
+ if (result.code !== 0) {
31
+ throw new Error(`Cannot read ${configPath}: ${result.stderr}`);
32
+ }
33
+ try {
34
+ return JSON.parse(result.stdout);
35
+ } catch {
36
+ throw new Error(`Cannot parse ${configPath}: invalid JSON`);
37
+ }
38
+ }
39
+ async function atomicWriteConfig(session, cfg, signal, opts = {}) {
40
+ const os = await detectOS(session, signal);
41
+ const configPath = configPathForOS(os);
42
+ const json = JSON.stringify(cfg, null, 2);
43
+ if (!opts.skipValidation) {
44
+ const [{ validateConfig }, yaml] = await Promise.all([
45
+ import("./config-validate-K2N6XLDX.js"),
46
+ import("./js-yaml-PTEEG4FO.js")
47
+ ]);
48
+ const { loadVersionSpec } = await import("./versions-W5LRQCAJ.js");
49
+ const spec = loadVersionSpec(yaml);
50
+ const { errors, warnings } = await validateConfig(cfg, {
51
+ openclawVersion: opts.openclawVersion,
52
+ schemaCapturedFrom: spec.runtime?.configSchemaCapturedFrom
53
+ });
54
+ for (const w of warnings) process.stderr.write(`warning: ${w}
55
+ `);
56
+ if (errors.length > 0) {
57
+ const rejected = `${configPath}.rejected.${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
58
+ const b64r = Buffer.from(json, "utf-8").toString("base64");
59
+ await execPrivileged(session, `echo '${b64r}' | base64 -d > ${rejected}`, signal);
60
+ throw new Error(
61
+ `Refusing to write an invalid OpenClaw config:
62
+ ` + errors.map((e) => ` - ${e}`).join("\n") + `
63
+ The rejected config was kept at ${rejected}. The deployment's current config is unchanged.`
64
+ );
65
+ }
66
+ }
67
+ const b64 = Buffer.from(json, "utf-8").toString("base64");
68
+ const chown = os === "Linux" ? ` && chown ${CONTAINER_UID}:${CONTAINER_UID} ${configPath}` : "";
69
+ const cmd = `echo '${b64}' | base64 -d > ${OPENCLAW_TMP} && mv ${OPENCLAW_TMP} ${configPath}` + chown;
70
+ const result = os === "Darwin" ? await session.exec(cmd, signal) : await execPrivileged(session, cmd, signal);
71
+ if (result.code !== 0) {
72
+ throw new Error(`Failed to write config: ${result.stderr}`);
73
+ }
74
+ }
75
+ async function restartGateway(session, signal) {
76
+ const os = await detectOS(session, signal);
77
+ const pathPrefix = os === "Darwin" ? 'export PATH="/usr/local/bin:/opt/homebrew/bin:/Applications/Docker.app/Contents/Resources/bin:$PATH" && ' : "";
78
+ const imgCmd = `${pathPrefix}${IMAGE_INSPECT_CMD}`;
79
+ const imgResult = os === "Darwin" ? await session.exec(imgCmd, signal) : await execPrivileged(session, imgCmd, signal);
80
+ const resolved = imageForRestart(imgResult.stdout);
81
+ if (!resolved.ok) throw new Error(resolved.error);
82
+ const image = resolved.value;
83
+ const pubCmd = `${pathPrefix}${PUBLISH_INSPECT_CMD}`;
84
+ const pubResult = os === "Darwin" ? await session.exec(pubCmd, signal) : await execPrivileged(session, pubCmd, signal);
85
+ const restartCmd = gatewayRunCommand({
86
+ image,
87
+ stateDir: stateDirForOS(os),
88
+ pathPrefix,
89
+ publish: publishForRestart(pubResult.stdout)
90
+ });
91
+ const result = os === "Darwin" ? await session.exec(restartCmd, signal) : await execPrivileged(session, restartCmd, signal);
92
+ if (result.code !== 0) {
93
+ throw new Error(`Gateway restart failed: ${result.stderr}`);
94
+ }
95
+ const healthy = await waitForGateway(session, pathPrefix, signal);
96
+ if (!healthy.ok) {
97
+ throw new Error(
98
+ `Gateway restarted but did not finish starting on port ${GATEWAY_PORT}` + (healthy.reason ? ` \u2014 ${healthy.reason}` : "") + `. The previous container has already been replaced; inspect it with \`docker logs openclaw\`. If the newly-applied config is at fault, revert it and restart \u2014 before v1.7.2 this config was never applied, so a value that has sat unused may now be taking effect.`
99
+ );
100
+ }
101
+ }
102
+ async function waitForGateway(session, pathPrefix, signal, attempts = 15) {
103
+ const { probeCommand, interpretProbe } = await import("./health-5CCOXYGG.js");
104
+ const probe = probeCommand("started", GATEWAY_PORT, pathPrefix);
105
+ let last = "no response from the gateway";
106
+ for (let i = 0; i < attempts; i++) {
107
+ if (signal?.aborted) return { ok: false, reason: "aborted" };
108
+ const r = await session.exec(probe, signal);
109
+ const verdict = interpretProbe("started", r.stdout);
110
+ if (verdict.ok) return { ok: true };
111
+ last = verdict.reason ?? last;
112
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
113
+ }
114
+ return { ok: false, reason: last };
115
+ }
116
+ function deepMerge(base, overlay) {
117
+ const result = { ...base };
118
+ for (const [key, val] of Object.entries(overlay)) {
119
+ if (val !== null && typeof val === "object" && !Array.isArray(val) && typeof result[key] === "object" && result[key] !== null && !Array.isArray(result[key])) {
120
+ result[key] = deepMerge(
121
+ result[key],
122
+ val
123
+ );
124
+ } else {
125
+ result[key] = val;
126
+ }
127
+ }
128
+ return result;
129
+ }
130
+
131
+ export {
132
+ OPENCLAW_CONFIG_LINUX,
133
+ OPENCLAW_CONFIG_MACOS,
134
+ OPENCLAW_CONFIG,
135
+ OPENCLAW_TMP,
136
+ readRemoteConfig,
137
+ atomicWriteConfig,
138
+ restartGateway,
139
+ deepMerge
140
+ };
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/transport/privileged.ts
4
+ function shellQuote(s) {
5
+ return `'${s.replace(/'/g, `'\\''`)}'`;
6
+ }
7
+ function sudoWrap(cmd) {
8
+ return `sudo -n bash -c ${shellQuote(cmd)}`;
9
+ }
10
+ function looksLikePermissionDenied(result) {
11
+ if (result.code === 0) return false;
12
+ const text = `${result.stderr} ${result.stdout}`.toLowerCase();
13
+ return text.includes("permission denied") || text.includes("docker.sock") || text.includes("operation not permitted");
14
+ }
15
+ var sudoNeeded = /* @__PURE__ */ new WeakMap();
16
+ function resetPrivilegeCache(session) {
17
+ sudoNeeded.delete(session);
18
+ }
19
+ async function execPrivileged(session, cmd, signal) {
20
+ const key = session;
21
+ if (sudoNeeded.get(key) === true) return session.exec(sudoWrap(cmd), signal);
22
+ const direct = await session.exec(cmd, signal);
23
+ if (!looksLikePermissionDenied(direct)) {
24
+ if (direct.code === 0) sudoNeeded.set(key, false);
25
+ return direct;
26
+ }
27
+ const escalated = await session.exec(sudoWrap(cmd), signal);
28
+ if (escalated.code === 0) sudoNeeded.set(key, true);
29
+ return escalated;
30
+ }
31
+ async function streamPrivileged(session, cmd, signal) {
32
+ const key = session;
33
+ if (sudoNeeded.get(key) === void 0) {
34
+ const probe = await session.exec('docker version --format "{{.Server.Version}}"', signal);
35
+ sudoNeeded.set(key, looksLikePermissionDenied(probe));
36
+ }
37
+ return session.stream(sudoNeeded.get(key) ? sudoWrap(cmd) : cmd, signal);
38
+ }
39
+ async function execPrivilegedWithInput(session, cmd, input, signal) {
40
+ const key = session;
41
+ if (sudoNeeded.get(key) === void 0) {
42
+ const probe = await session.exec('docker version --format "{{.Server.Version}}"', signal);
43
+ sudoNeeded.set(key, looksLikePermissionDenied(probe));
44
+ }
45
+ return session.execWithInput(sudoNeeded.get(key) ? sudoWrap(cmd) : cmd, input, signal);
46
+ }
47
+
48
+ export {
49
+ shellQuote,
50
+ sudoWrap,
51
+ resetPrivilegeCache,
52
+ execPrivileged,
53
+ streamPrivileged,
54
+ execPrivilegedWithInput
55
+ };
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/openclaw/logs.ts
4
+ var GATEWAY_LOGS_PROBE = "docker exec openclaw openclaw logs --limit 1 >/dev/null 2>&1 && echo ok || echo no";
5
+ function gatewayLogsCommand(opts) {
6
+ return [
7
+ "docker exec openclaw openclaw logs",
8
+ `--limit ${opts.tail}`,
9
+ opts.follow ? "--follow" : "",
10
+ opts.json ? "--json" : ""
11
+ ].filter(Boolean).join(" ");
12
+ }
13
+ function containerLogsCommand(opts) {
14
+ return [
15
+ "docker logs openclaw",
16
+ `-n ${opts.tail}`,
17
+ opts.follow ? "-f" : "",
18
+ opts.since ? `--since ${shellQuote(opts.since)}` : ""
19
+ ].filter(Boolean).join(" ");
20
+ }
21
+ function chooseLogSource(opts) {
22
+ if (opts.since) {
23
+ return {
24
+ source: "container",
25
+ reason: "--since is a container-log filter; the gateway log command has no equivalent"
26
+ };
27
+ }
28
+ if (!opts.gatewayReachable) {
29
+ return {
30
+ source: "container",
31
+ reason: "the gateway is not answering, so its own logs cannot be read \u2014 showing container output"
32
+ };
33
+ }
34
+ return { source: "gateway", reason: "read from the gateway over RPC" };
35
+ }
36
+ function agentAuditCommand(opts) {
37
+ return [
38
+ "docker exec openclaw openclaw audit",
39
+ `--agent ${shellQuote(opts.agentId)}`,
40
+ "--kind agent_run",
41
+ "--json",
42
+ `--limit ${opts.limit}`,
43
+ opts.cursor ? `--cursor ${shellQuote(opts.cursor)}` : ""
44
+ ].filter(Boolean).join(" ");
45
+ }
46
+ function shellQuote(value) {
47
+ return `'${value.replace(/'/g, `'\\''`)}'`;
48
+ }
49
+
50
+ export {
51
+ GATEWAY_LOGS_PROBE,
52
+ gatewayLogsCommand,
53
+ containerLogsCommand,
54
+ chooseLogSource,
55
+ agentAuditCommand
56
+ };
@@ -0,0 +1,228 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ atomicWriteConfig,
4
+ deepMerge,
5
+ readRemoteConfig,
6
+ restartGateway
7
+ } from "./chunk-N44K3LHE.js";
8
+ import {
9
+ validatePlan
10
+ } from "./chunk-YR3O2I3S.js";
11
+ import {
12
+ saveOverlay
13
+ } from "./chunk-6ZFIFDBJ.js";
14
+ import {
15
+ resolveSecrets
16
+ } from "./chunk-BOPSG2LI.js";
17
+ import {
18
+ buildContext
19
+ } from "./chunk-LU2V3LXA.js";
20
+ import {
21
+ UsageError
22
+ } from "./chunk-KGXPLI7W.js";
23
+ import {
24
+ GATEWAY_PORT
25
+ } from "./chunk-6TIYVHPX.js";
26
+
27
+ // src/plan/apply.ts
28
+ async function applyPlan(plan, opts) {
29
+ const validation = validatePlan(plan);
30
+ if (!validation.ok) {
31
+ throw new UsageError(
32
+ `Invalid deploy plan:
33
+ ${validation.errors.join("\n")}`
34
+ );
35
+ }
36
+ if (plan.spec.provider === "local") {
37
+ throw new UsageError(
38
+ "plan/apply is not supported for the local provider. Use `clawops up` directly."
39
+ );
40
+ }
41
+ const { guardOpenclawVersion } = await import("./version-guard-GPSTCPT7.js");
42
+ await guardOpenclawVersion(plan.spec.openclaw.version);
43
+ const ctx = buildContext({
44
+ stack: plan.spec.stackName,
45
+ provider: plan.spec.provider
46
+ });
47
+ const stack = await ctx.getStack();
48
+ await stack.setConfig("instanceType", { value: plan.spec.instanceType });
49
+ if (plan.spec.region) {
50
+ await stack.setConfig("region", { value: plan.spec.region });
51
+ }
52
+ await stack.setConfig("openclawVersion", { value: plan.spec.openclaw.version });
53
+ await stack.setConfig("publishGateway", {
54
+ value: plan.spec.network?.publishGateway ?? "loopback"
55
+ });
56
+ await stack.setConfig("gatewayPort", {
57
+ value: String(plan.spec.network?.gatewayPort ?? GATEWAY_PORT)
58
+ });
59
+ const modelProvider = plan.spec.openclaw.config?.["models"]?.["provider"];
60
+ if (modelProvider === "bedrock") {
61
+ await stack.setConfig("bedrockEnabled", { value: "true" });
62
+ }
63
+ if (plan.metadata.stackVersion !== void 0) {
64
+ const currentInfo = await stack.info();
65
+ if (currentInfo !== void 0 && currentInfo.version !== plan.metadata.stackVersion) {
66
+ process.stderr.write(
67
+ `
68
+ Warning: stack "${plan.spec.stackName}" has changed since this plan was generated (plan version: ${plan.metadata.stackVersion}, current: ${currentInfo.version}).
69
+ The diff you reviewed may no longer reflect what will be applied.
70
+
71
+ `
72
+ );
73
+ if (opts?.confirmDrift) {
74
+ await opts.confirmDrift();
75
+ }
76
+ }
77
+ }
78
+ const start = Date.now();
79
+ const result = await stack.up({ onOutput: opts?.onOutput, signal: opts?.signal });
80
+ const outputs = Object.fromEntries(
81
+ Object.entries(result.outputs).map(([k, v]) => [k, v.value])
82
+ );
83
+ const changeSummary = {};
84
+ if (result.summary.resourceChanges) {
85
+ for (const [op, count] of Object.entries(result.summary.resourceChanges)) {
86
+ changeSummary[op] = count;
87
+ }
88
+ }
89
+ const hasOverlay = plan.spec.openclaw.config !== void 0 || plan.spec.openclaw.channels !== void 0;
90
+ if (hasOverlay) {
91
+ await applyConfigOverlay(plan, outputs, ctx, opts?.signal);
92
+ }
93
+ return {
94
+ outputs,
95
+ changeSummary,
96
+ durationMs: Date.now() - start
97
+ };
98
+ }
99
+ async function applyConfigOverlay(plan, outputs, ctx, signal) {
100
+ const { connect } = await import("./ssh-B3NZAU4I.js");
101
+ const connInfo = ctx.adapter.getConnectionInfo(outputs);
102
+ const session = await connect({
103
+ host: connInfo.host,
104
+ port: connInfo.port,
105
+ user: connInfo.user,
106
+ privateKeyPath: connInfo.privateKeyPath,
107
+ knownHostsPath: connInfo.knownHostsPath,
108
+ signal
109
+ });
110
+ try {
111
+ const remote = await readRemoteConfig(session, signal);
112
+ const configOverlay = plan.spec.openclaw.config ?? {};
113
+ const resolvedOverlay = resolveSecrets(
114
+ configOverlay,
115
+ plan.spec.secrets ?? []
116
+ );
117
+ const merged = deepMerge(remote, {
118
+ ...resolvedOverlay,
119
+ ...plan.spec.openclaw.channels !== void 0 ? { channels: plan.spec.openclaw.channels } : {}
120
+ });
121
+ await atomicWriteConfig(session, merged, signal, {
122
+ openclawVersion: plan.spec.openclaw.version
123
+ });
124
+ await installProviderPlugins(session, merged, plan.spec.openclaw.version, signal);
125
+ await installChannelPlugins(session, merged, plan.spec.openclaw.version, signal);
126
+ await restartGateway(session, signal);
127
+ await verifyProviders(session, merged, signal);
128
+ await verifyChannels(session, merged, signal);
129
+ saveOverlay(plan.spec.stackName, configOverlay, plan.spec.secrets ?? []);
130
+ } finally {
131
+ session.close();
132
+ }
133
+ }
134
+ async function installProviderPlugins(session, cfg, openclawVersion, signal) {
135
+ const [{ requiredPlugins, installCommand }, { STATE_DIR_HOST_LINUX }, yaml, { readFileSync }, { join }, { resolveSpecDir }] = await Promise.all([
136
+ import("./plugins-DTST3OER.js"),
137
+ import("./runtime-LZ7NA53V.js"),
138
+ import("./js-yaml-PTEEG4FO.js"),
139
+ import("fs"),
140
+ import("path"),
141
+ import("./spec-path-KQOOIQCA.js")
142
+ ]);
143
+ const catalog = yaml.load(
144
+ readFileSync(join(resolveSpecDir(), "models.yaml"), "utf-8")
145
+ );
146
+ const needed = requiredPlugins(cfg, catalog);
147
+ if (needed.length === 0) return;
148
+ const { execPrivileged } = await import("./privileged-JFA6LLTV.js");
149
+ const image = `ghcr.io/openclaw/openclaw:${openclawVersion}`;
150
+ for (const plugin of needed) {
151
+ const result = await execPrivileged(
152
+ session,
153
+ installCommand(plugin, image, STATE_DIR_HOST_LINUX),
154
+ signal
155
+ );
156
+ if (result.code !== 0) {
157
+ process.stderr.write(
158
+ `[clawops] warning: could not install ${plugin.package}@${plugin.version} for provider "${plugin.providerId}": ${result.stderr || result.stdout}
159
+ `
160
+ );
161
+ }
162
+ }
163
+ }
164
+ async function verifyProviders(session, cfg, signal) {
165
+ const { missingProviders } = await import("./plugins-DTST3OER.js");
166
+ const { execPrivileged } = await import("./privileged-JFA6LLTV.js");
167
+ const result = await execPrivileged(
168
+ session,
169
+ "docker exec openclaw openclaw plugins list --json",
170
+ signal
171
+ );
172
+ if (result.code !== 0) return;
173
+ const missing = missingProviders(cfg, result.stdout);
174
+ if (missing.length > 0) {
175
+ process.stderr.write(
176
+ `[clawops] warning: the gateway is running, but these configured model providers did not load: ${missing.join(", ")}.
177
+ [clawops] The deployment will look healthy and fail on first use. Check egress to ClawHub, then re-run apply.
178
+ `
179
+ );
180
+ }
181
+ }
182
+ async function loadChannelCatalog() {
183
+ const [yaml, { readFileSync }, { join }, { resolveSpecDir }] = await Promise.all([
184
+ import("./js-yaml-PTEEG4FO.js"),
185
+ import("fs"),
186
+ import("path"),
187
+ import("./spec-path-KQOOIQCA.js")
188
+ ]);
189
+ return yaml.load(readFileSync(join(resolveSpecDir(), "integrations.yaml"), "utf-8"));
190
+ }
191
+ async function installChannelPlugins(session, cfg, openclawVersion, signal) {
192
+ const [{ requiredChannelPlugins, channelInstallCommand }, { STATE_DIR_HOST_LINUX }] = await Promise.all([import("./channels-KBUR42IU.js"), import("./runtime-LZ7NA53V.js")]);
193
+ const needed = requiredChannelPlugins(cfg, await loadChannelCatalog());
194
+ if (needed.length === 0) return;
195
+ const { execPrivileged } = await import("./privileged-JFA6LLTV.js");
196
+ const image = `ghcr.io/openclaw/openclaw:${openclawVersion}`;
197
+ for (const plugin of needed) {
198
+ const result = await execPrivileged(
199
+ session,
200
+ channelInstallCommand(plugin, image, STATE_DIR_HOST_LINUX),
201
+ signal
202
+ );
203
+ if (result.code !== 0) {
204
+ process.stderr.write(
205
+ `[clawops] warning: could not install ${plugin.package}@${plugin.version} for channel "${plugin.channelKey}": ${result.stderr || result.stdout}
206
+ `
207
+ );
208
+ }
209
+ }
210
+ }
211
+ async function verifyChannels(session, cfg, signal) {
212
+ const { missingChannels, CHANNELS_LIST_CMD } = await import("./channels-KBUR42IU.js");
213
+ const { execPrivileged } = await import("./privileged-JFA6LLTV.js");
214
+ const result = await execPrivileged(session, CHANNELS_LIST_CMD, signal);
215
+ if (result.code !== 0) return;
216
+ const missing = missingChannels(cfg, result.stdout, await loadChannelCatalog());
217
+ if (missing.length > 0) {
218
+ process.stderr.write(
219
+ `[clawops] warning: the gateway is running, but these configured channels are not installed: ${missing.join(", ")}.
220
+ [clawops] They will never connect. Check egress to registry.npmjs.org, then re-run apply.
221
+ `
222
+ );
223
+ }
224
+ }
225
+
226
+ export {
227
+ applyPlan
228
+ };
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  validatePlan
4
- } from "./chunk-6X45DDGH.js";
4
+ } from "./chunk-YR3O2I3S.js";
5
5
  import {
6
6
  buildContext
7
- } from "./chunk-P5LKWRPL.js";
7
+ } from "./chunk-LU2V3LXA.js";
8
8
  import {
9
9
  getConfig
10
10
  } from "./chunk-CX5SL5HP.js";
@@ -45,10 +45,10 @@ async function generatePlan(intent, _opts) {
45
45
  "plan/apply is not supported for the local provider. Use `clawops up` directly."
46
46
  );
47
47
  }
48
- const { version } = await import("./package-5AR4ZPK4.js");
48
+ const { version } = await import("./package-3HUW57XH.js");
49
49
  const config = getConfig();
50
50
  const instanceType = intent.instanceType ?? "small";
51
- const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-VUCNZXQS.js");
51
+ const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-GPSTCPT7.js");
52
52
  const openclawVersion = await guardOpenclawVersion(
53
53
  intent.openclawVersion ?? await defaultOpenclawVersion()
54
54
  );
@@ -108,6 +108,26 @@ async function generatePlan(intent, _opts) {
108
108
  throw new Error(`Generated plan failed schema validation:
109
109
  ${validation.errors.join("\n")}`);
110
110
  }
111
+ const { validatePlanConfig, validatePlanNetwork } = await import("./validate-BOQRWX4F.js");
112
+ const cfgCheck = await validatePlanConfig(plan);
113
+ for (const w of cfgCheck.warnings) process.stderr.write(`[clawops] warning: ${w}
114
+ `);
115
+ if (!cfgCheck.ok) {
116
+ throw new Error(
117
+ `The OpenClaw config in this plan would be rejected:
118
+ ` + cfgCheck.errors.map((e) => ` - ${e}`).join("\n") + `
119
+ Fix spec.openclaw.config before applying \u2014 the gateway reads it verbatim.`
120
+ );
121
+ }
122
+ const netCheck = validatePlanNetwork(plan);
123
+ for (const w of netCheck.warnings) process.stderr.write(`[clawops] warning: ${w}
124
+ `);
125
+ if (!netCheck.ok) {
126
+ throw new Error(
127
+ `This plan's network settings contradict each other:
128
+ ` + netCheck.errors.map((e) => ` - ${e}`).join("\n")
129
+ );
130
+ }
111
131
  return plan;
112
132
  }
113
133
  function planId() {