@clawops/cli 1.1.0 → 1.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.
Files changed (37) hide show
  1. package/README.md +3 -2
  2. package/dist/apply-KBF7OPWL.js +14 -0
  3. package/dist/{automation-JG2YBEL7.js → automation-RULUSJBW.js} +2 -2
  4. package/dist/{aws-A3323GNM.js → aws-KQBWY43W.js} +12 -10
  5. package/dist/{azure-LNWBK2ZI.js → azure-4EJFVJZL.js} +4 -3
  6. package/dist/{bootstrap-7ZS2GLFJ.js → bootstrap-UYCOEJQX.js} +48 -12
  7. package/dist/{chunk-LT24GUUO.js → chunk-3QJBNAHW.js} +1 -1
  8. package/dist/{chunk-BA6MDFZK.js → chunk-4U3LTLWZ.js} +2 -83
  9. package/dist/{chunk-PERSDQMT.js → chunk-ACYJBSLJ.js} +7 -7
  10. package/dist/chunk-BOPSG2LI.js +77 -0
  11. package/dist/{chunk-PRYLTCS4.js → chunk-BRPU7AQC.js} +1 -1
  12. package/dist/{chunk-ALSUDYA7.js → chunk-CX5SL5HP.js} +1 -1
  13. package/dist/{chunk-ZSE4QRKE.js → chunk-KGXPLI7W.js} +19 -1
  14. package/dist/chunk-LU63NZD3.js +116 -0
  15. package/dist/chunk-QJ6ERXHN.js +266 -0
  16. package/dist/{chunk-D4UAHAKI.js → chunk-QMXM2SMO.js} +10 -6
  17. package/dist/chunk-UDNZUSKA.js +97 -0
  18. package/dist/chunk-ZVOEQCNW.js +92 -0
  19. package/dist/cli.js +1256 -60
  20. package/dist/context-VUAYRAJY.js +10 -0
  21. package/dist/errors-OK47MQFD.js +21 -0
  22. package/dist/{gcp-OHWCTFLL.js → gcp-SKURG3L6.js} +4 -3
  23. package/dist/generate-WG5VONSE.js +14 -0
  24. package/dist/js-yaml-PTEEG4FO.js +2647 -0
  25. package/dist/{outputs-6DAVEEAZ.js → outputs-DJHBY7EE.js} +2 -2
  26. package/dist/{package-FA6UOG2I.js → package-3JZ767DN.js} +1 -1
  27. package/dist/{pool-FAVU7WCW.js → pool-FBFHATDG.js} +3 -2
  28. package/dist/remote-config-JQ77SRC2.js +21 -0
  29. package/dist/secrets-SVZWNAPK.js +7 -0
  30. package/dist/{server-V5ZFL76Y.js → server-QJRWK2RV.js} +64 -150
  31. package/dist/ssh-IQXNME3D.js +8 -0
  32. package/dist/{store-ARJ2EO6L.js → store-SDUR52Z5.js} +2 -2
  33. package/package.json +1 -1
  34. package/dist/apply-DPS23GLL.js +0 -12
  35. package/dist/chunk-UCIVU24B.js +0 -56
  36. package/dist/context-T52JWL3P.js +0 -10
  37. package/dist/generate-55MITRXU.js +0 -14
package/README.md CHANGED
@@ -143,7 +143,8 @@ clawops down --yes # Destroy local-provider stack
143
143
 
144
144
  | Command | Description |
145
145
  |---|---|
146
- | `init` | Interactive setup wizard — writes config, generates SSH key pair |
146
+ | `setup` | First-run wizard — guided LLM, integrations, and deploy-plan generation |
147
+ | `init` | Register a stack in `~/.clawops/config.json` without provisioning |
147
148
  | `up` | Provision or update stack (`--dry-run` for preview) |
148
149
  | `down` | Destroy local-provider stack (requires `--yes`; `--dry-run` shows current outputs) |
149
150
  | `destroy` | Destroy cloud-provider stack with confirmation prompt (`--dry-run` shows current outputs) |
@@ -341,7 +342,7 @@ pnpm dev doctor # verify toolchain
341
342
  ```bash
342
343
  pnpm dev # run CLI from src/ via tsx
343
344
  pnpm build # tsup → dist/
344
- pnpm test # vitest (476 tests, ~2s)
345
+ pnpm test # vitest (493 tests, ~3s)
345
346
  pnpm test:changed # vitest --changed (fast edit loop)
346
347
  pnpm test:integration # Docker-based SSH integration tests
347
348
  pnpm typecheck # tsc --noEmit
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ applyPlan
4
+ } from "./chunk-LU63NZD3.js";
5
+ import "./chunk-YTH4L2GN.js";
6
+ import "./chunk-BOPSG2LI.js";
7
+ import "./chunk-ACYJBSLJ.js";
8
+ import "./chunk-BRPU7AQC.js";
9
+ import "./chunk-UDNZUSKA.js";
10
+ import "./chunk-CX5SL5HP.js";
11
+ import "./chunk-KGXPLI7W.js";
12
+ export {
13
+ applyPlan
14
+ };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getConfigDir
4
- } from "./chunk-ALSUDYA7.js";
5
- import "./chunk-ZSE4QRKE.js";
4
+ } from "./chunk-CX5SL5HP.js";
5
+ import "./chunk-KGXPLI7W.js";
6
6
 
7
7
  // src/pulumi/automation.ts
8
8
  import path from "path";
@@ -15,7 +15,7 @@ var awsProgram = async () => {
15
15
  const cfg = new pulumi.Config();
16
16
  const instanceType = cfg.get("instanceType") ?? "t3.small";
17
17
  const region = cfg.get("region") ?? "us-east-1";
18
- const openclawVersion = cfg.get("openclawVersion") ?? "stable";
18
+ const openclawVersion = cfg.get("openclawVersion") ?? "latest";
19
19
  const accessMode = cfg.get("accessMode") ?? "restricted";
20
20
  const allowedCidrs = cfg.get("allowedCidrs") ?? "";
21
21
  const sshCidrs = cfg.get("sshCidrs") ?? "";
@@ -114,7 +114,8 @@ var awsProgram = async () => {
114
114
  if (bedrockEnabled) {
115
115
  new aws.iam.RolePolicyAttachment("clawops-bedrock", {
116
116
  role: role.name,
117
- policyArn: "arn:aws:iam::aws:policy/AmazonBedrockReadOnly"
117
+ // FullAccess required for bedrock:InvokeModel — ReadOnly only covers describe/list.
118
+ policyArn: "arn:aws:iam::aws:policy/AmazonBedrockFullAccess"
118
119
  });
119
120
  }
120
121
  const instanceProfile = new aws.iam.InstanceProfile("clawops-profile", {
@@ -163,10 +164,8 @@ var awsProgram = async () => {
163
164
  };
164
165
  };
165
166
  function makeStartupScript(openclawVersion, bedrockEnabled) {
166
- const bedrockEnvFile = bedrockEnabled ? `
167
- # Write AWS_PROFILE for Bedrock
168
- echo "AWS_PROFILE=default" > /etc/openclaw.env
169
- ` : "";
167
+ const bedrockEnvSetup = bedrockEnabled ? '\n# Write AWS_PROFILE so the container can use the instance role for Bedrock\necho "AWS_PROFILE=default" > /etc/openclaw.env\n' : "";
168
+ const bedrockEnvFlag = bedrockEnabled ? " --env-file /etc/openclaw.env \\\\\n" : "";
170
169
  return `#!/bin/bash
171
170
  set -euo pipefail
172
171
 
@@ -176,8 +175,9 @@ mkdir -p /home/clawops/.ssh
176
175
  chmod 700 /home/clawops/.ssh
177
176
  chown clawops:clawops /home/clawops/.ssh
178
177
 
179
- # Install Docker if not present
178
+ # Install Docker if not present (Ubuntu/Debian only; AWS AMI is ubuntu-22.04)
180
179
  if ! command -v docker &>/dev/null; then
180
+ export DEBIAN_FRONTEND=noninteractive
181
181
  apt-get update -q
182
182
  apt-get install -y -q ca-certificates curl gnupg lsb-release
183
183
  install -m 0755 -d /etc/apt/keyrings
@@ -193,16 +193,17 @@ if ! command -v docker &>/dev/null; then
193
193
  fi
194
194
 
195
195
  usermod -aG docker clawops
196
- ${bedrockEnvFile}
196
+ ${bedrockEnvSetup}
197
197
  # Pull OpenClaw image
198
198
  OPENCLAW_VERSION="${openclawVersion}"
199
199
  docker pull ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION}
200
200
 
201
201
  # Create default openclaw.json if not present
202
+ # apply.ts will overwrite this with the plan's config overlay post-provisioning.
202
203
  OPENCLAW_CONFIG=/home/clawops/openclaw.json
203
204
  if [ ! -f "\${OPENCLAW_CONFIG}" ]; then
204
205
  cat > "\${OPENCLAW_CONFIG}" <<'OPENCLAWJSON'
205
- {"version":"2026.4","gateway":{"port":18789,"auth":{"mode":"token"}},"models":{},"channels":[]}
206
+ {"meta":{"lastTouchedVersion":"2026.4"},"gateway":{"port":18789,"auth":{"mode":"token"}},"models":{},"channels":{}}
206
207
  OPENCLAWJSON
207
208
  chown clawops:clawops "\${OPENCLAW_CONFIG}"
208
209
  fi
@@ -215,7 +216,8 @@ docker run -d \\
215
216
  --restart unless-stopped \\
216
217
  -p ${GATEWAY_PORT}:${GATEWAY_PORT} \\
217
218
  -v "\${OPENCLAW_CONFIG}":/app/config.json:ro \\
218
- ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION}
219
+ ${bedrockEnvFlag} ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION} \\
220
+ node openclaw.mjs gateway run --allow-unconfigured
219
221
  `;
220
222
  }
221
223
 
@@ -16,7 +16,7 @@ var azureProgram = async () => {
16
16
  const stackName = pulumi.getStack();
17
17
  const instanceType = cfg.get("instanceType") ?? "Standard_B2s";
18
18
  const location = cfg.get("region") ?? "eastus";
19
- const openclawVersion = cfg.get("openclawVersion") ?? "stable";
19
+ const openclawVersion = cfg.get("openclawVersion") ?? "latest";
20
20
  const accessMode = cfg.get("accessMode") ?? "restricted";
21
21
  const allowedCidrs = cfg.get("allowedCidrs") ?? "";
22
22
  const sshCidrs = cfg.get("sshCidrs") ?? "";
@@ -206,7 +206,7 @@ docker pull ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION}
206
206
  OPENCLAW_CONFIG=/home/clawops/openclaw.json
207
207
  if [ ! -f "\${OPENCLAW_CONFIG}" ]; then
208
208
  cat > "\${OPENCLAW_CONFIG}" <<'OPENCLAWJSON'
209
- {"version":"2026.4","gateway":{"port":18789,"auth":{"mode":"token"}},"models":{},"channels":[]}
209
+ {"meta":{"lastTouchedVersion":"2026.4"},"gateway":{"port":18789,"auth":{"mode":"token"}},"models":{},"channels":{}}
210
210
  OPENCLAWJSON
211
211
  chown clawops:clawops "\${OPENCLAW_CONFIG}"
212
212
  fi
@@ -219,7 +219,8 @@ docker run -d \\
219
219
  --restart unless-stopped \\
220
220
  -p 18789:18789 \\
221
221
  -v "\${OPENCLAW_CONFIG}":/app/config.json:ro \\
222
- ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION}
222
+ ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION} \\
223
+ node openclaw.mjs gateway run --allow-unconfigured
223
224
  `;
224
225
  }
225
226
 
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- acquireSession
4
- } from "./chunk-BA6MDFZK.js";
5
2
  import {
6
3
  writeLocalState
7
- } from "./chunk-PRYLTCS4.js";
8
- import "./chunk-ALSUDYA7.js";
4
+ } from "./chunk-BRPU7AQC.js";
5
+ import {
6
+ acquireSession
7
+ } from "./chunk-ZVOEQCNW.js";
8
+ import "./chunk-4U3LTLWZ.js";
9
+ import "./chunk-CX5SL5HP.js";
9
10
  import {
10
11
  ProviderError
11
- } from "./chunk-ZSE4QRKE.js";
12
+ } from "./chunk-KGXPLI7W.js";
12
13
 
13
14
  // src/providers/local/bootstrap.ts
14
15
  import { readFileSync } from "fs";
@@ -27,7 +28,8 @@ function renderScript(openclawVersion) {
27
28
  async function localBootstrap(opts) {
28
29
  const script = renderScript(opts.openclawVersion);
29
30
  const b64 = Buffer.from(script, "utf-8").toString("base64");
30
- const command = `echo '${b64}' | base64 -d | sudo bash`;
31
+ const command = opts.sudoPassword ? `printf '%s
32
+ ' ${shellQuote(opts.sudoPassword)} | sudo -S -p '' bash -c "echo '${b64}' | base64 -d | bash"` : `echo '${b64}' | base64 -d | sudo bash`;
31
33
  const { session, release } = await acquireSession({
32
34
  host: opts.host,
33
35
  port: opts.port,
@@ -37,12 +39,16 @@ async function localBootstrap(opts) {
37
39
  signal: opts.signal
38
40
  });
39
41
  try {
40
- const result = await session.exec(command, opts.signal);
41
- if (result.code !== 0) {
42
- throw new ProviderError(
43
- `Bootstrap script failed (exit ${result.code}):
42
+ if (opts.onOutput) {
43
+ await execStreaming(session, command, opts.onOutput, opts.signal);
44
+ } else {
45
+ const result = await session.exec(command, opts.signal);
46
+ if (result.code !== 0) {
47
+ throw new ProviderError(
48
+ `Bootstrap script failed (exit ${result.code}):
44
49
  ${result.stderr || result.stdout}`
45
- );
50
+ );
51
+ }
46
52
  }
47
53
  } finally {
48
54
  release();
@@ -86,6 +92,36 @@ async function waitForGateway(host, port, signal) {
86
92
  function sleep(ms) {
87
93
  return new Promise((resolve) => setTimeout(resolve, ms));
88
94
  }
95
+ function shellQuote(s) {
96
+ return `'${s.replace(/'/g, "'\\''")}'`;
97
+ }
98
+ async function execStreaming(session, command, onOutput, signal) {
99
+ const ch = await session.stream(command, signal);
100
+ let leftover = "";
101
+ let stderr = "";
102
+ ch.on("data", (chunk) => {
103
+ const text = leftover + chunk.toString("utf-8");
104
+ const lines = text.split("\n");
105
+ leftover = lines.pop() ?? "";
106
+ for (const line of lines) {
107
+ if (line.trim()) onOutput(line);
108
+ }
109
+ });
110
+ ch.stderr.on("data", (chunk) => {
111
+ stderr += chunk.toString("utf-8");
112
+ });
113
+ const exitCode = await new Promise((resolve, reject) => {
114
+ ch.on("close", (code) => {
115
+ if (leftover.trim()) onOutput(leftover);
116
+ resolve(code ?? 0);
117
+ });
118
+ ch.on("error", reject);
119
+ });
120
+ if (exitCode !== 0) {
121
+ throw new ProviderError(`Bootstrap script failed (exit ${exitCode}):
122
+ ${stderr}`);
123
+ }
124
+ }
89
125
  export {
90
126
  localBootstrap
91
127
  };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  StateError
4
- } from "./chunk-ZSE4QRKE.js";
4
+ } from "./chunk-KGXPLI7W.js";
5
5
 
6
6
  // src/pulumi/outputs.ts
7
7
  function extractBaseOutputs(raw) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  NetworkError
4
- } from "./chunk-ZSE4QRKE.js";
4
+ } from "./chunk-KGXPLI7W.js";
5
5
 
6
6
  // src/transport/ssh.ts
7
7
  import { readFileSync, appendFileSync, mkdirSync } from "fs";
@@ -172,87 +172,6 @@ function verifyHostKey(host, port, keyHash, knownHostsPath) {
172
172
  return true;
173
173
  }
174
174
 
175
- // src/transport/pool.ts
176
- var IDLE_TTL_MS = 5 * 60 * 1e3;
177
- var MAX_PER_HOST = 4;
178
- var CLEANUP_INTERVAL_MS = 3e4;
179
- var pool = /* @__PURE__ */ new Map();
180
- var cleanupTimer = null;
181
- function poolKey(opts) {
182
- return `${opts.user}@${opts.host}:${opts.port}`;
183
- }
184
- function startCleanupTimer() {
185
- if (cleanupTimer) return;
186
- cleanupTimer = setInterval(() => {
187
- const now = Date.now();
188
- for (const [key, entries] of pool) {
189
- const active = entries.filter((e) => {
190
- if (!e.inUse && now - e.lastUsed > IDLE_TTL_MS) {
191
- e.session.close();
192
- return false;
193
- }
194
- return true;
195
- });
196
- if (active.length === 0) {
197
- pool.delete(key);
198
- } else {
199
- pool.set(key, active);
200
- }
201
- }
202
- if (pool.size === 0 && cleanupTimer) {
203
- clearInterval(cleanupTimer);
204
- cleanupTimer = null;
205
- }
206
- }, CLEANUP_INTERVAL_MS);
207
- cleanupTimer.unref();
208
- }
209
- async function acquireSession(opts) {
210
- const key = poolKey(opts);
211
- const entries = pool.get(key) ?? [];
212
- const idle = entries.find((e) => !e.inUse);
213
- if (idle) {
214
- idle.inUse = true;
215
- idle.lastUsed = Date.now();
216
- return {
217
- session: idle.session,
218
- release() {
219
- idle.inUse = false;
220
- idle.lastUsed = Date.now();
221
- }
222
- };
223
- }
224
- if (entries.length >= MAX_PER_HOST) {
225
- throw new NetworkError(
226
- `SSH connection pool exhausted for ${key} (max ${MAX_PER_HOST} concurrent connections).`
227
- );
228
- }
229
- const session = await connect(opts);
230
- const entry = { session, hostKey: key, lastUsed: Date.now(), inUse: true };
231
- entries.push(entry);
232
- pool.set(key, entries);
233
- startCleanupTimer();
234
- return {
235
- session,
236
- release() {
237
- entry.inUse = false;
238
- entry.lastUsed = Date.now();
239
- }
240
- };
241
- }
242
- function drainPool() {
243
- for (const entries of pool.values()) {
244
- for (const e of entries) {
245
- e.session.close();
246
- }
247
- }
248
- pool.clear();
249
- if (cleanupTimer) {
250
- clearInterval(cleanupTimer);
251
- cleanupTimer = null;
252
- }
253
- }
254
-
255
175
  export {
256
- acquireSession,
257
- drainPool
176
+ connect
258
177
  };
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readLocalState
4
- } from "./chunk-PRYLTCS4.js";
4
+ } from "./chunk-BRPU7AQC.js";
5
5
  import {
6
6
  getConfigDir,
7
7
  requireConfig
8
- } from "./chunk-ALSUDYA7.js";
8
+ } from "./chunk-CX5SL5HP.js";
9
9
  import {
10
10
  UsageError
11
- } from "./chunk-ZSE4QRKE.js";
11
+ } from "./chunk-KGXPLI7W.js";
12
12
 
13
13
  // src/cli/context.ts
14
14
  function buildContext(args) {
@@ -36,7 +36,7 @@ function buildContext(args) {
36
36
  `Stack "${stackName}" not found in config. Run \`clawops init\` or use \`--stack\` to specify a different stack.`
37
37
  );
38
38
  }
39
- const { getOrCreateStack } = await import("./automation-JG2YBEL7.js");
39
+ const { getOrCreateStack } = await import("./automation-RULUSJBW.js");
40
40
  stackCache = await getOrCreateStack({
41
41
  stack: stackName,
42
42
  stateUrl: stackConfig.stateUrl,
@@ -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-OHWCTFLL.js");
59
+ const mod = await import("./gcp-SKURG3L6.js");
60
60
  resolved = mod.default;
61
61
  return resolved;
62
62
  }
63
63
  case "aws": {
64
- const mod = await import("./aws-A3323GNM.js");
64
+ const mod = await import("./aws-KQBWY43W.js");
65
65
  resolved = mod.default;
66
66
  return resolved;
67
67
  }
68
68
  case "azure": {
69
- const mod = await import("./azure-LNWBK2ZI.js");
69
+ const mod = await import("./azure-4EJFVJZL.js");
70
70
  resolved = mod.default;
71
71
  return resolved;
72
72
  }
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/plan/secrets.ts
4
+ import { readFileSync } from "fs";
5
+ var UNRESOLVED_SOURCES = /* @__PURE__ */ new Set(["aws-sm", "aws-ssm", "gcp-sm", "azure-kv"]);
6
+ function resolveSecrets(config, secrets) {
7
+ const resolved = buildSecretMap(secrets);
8
+ return walkResolve(config, resolved);
9
+ }
10
+ function buildSecretMap(secrets) {
11
+ const map = /* @__PURE__ */ new Map();
12
+ for (const secret of secrets) {
13
+ if (UNRESOLVED_SOURCES.has(secret.source)) {
14
+ process.stderr.write(
15
+ `[clawops] Warning: secret "${secret.name}" uses source "${secret.source}" which is not yet resolved automatically. Set the value manually after deployment with: clawops config set
16
+ `
17
+ );
18
+ continue;
19
+ }
20
+ if (secret.source === "env") {
21
+ const ref = secret.ref ?? secret.name;
22
+ const val = process.env[ref];
23
+ if (val === void 0) {
24
+ process.stderr.write(
25
+ `[clawops] Warning: secret "${secret.name}" references env var "${ref}" which is not set. The $secret: ref will remain unresolved in the config.
26
+ `
27
+ );
28
+ continue;
29
+ }
30
+ map.set(secret.name, val);
31
+ } else if (secret.source === "file") {
32
+ if (!secret.ref) {
33
+ process.stderr.write(
34
+ `[clawops] Warning: secret "${secret.name}" has source "file" but no ref path.
35
+ `
36
+ );
37
+ continue;
38
+ }
39
+ try {
40
+ map.set(secret.name, readFileSync(secret.ref, "utf-8").trim());
41
+ } catch (err) {
42
+ process.stderr.write(
43
+ `[clawops] Warning: cannot read secret "${secret.name}" from file "${secret.ref}": ${err.message}
44
+ `
45
+ );
46
+ }
47
+ }
48
+ }
49
+ return map;
50
+ }
51
+ function walkResolve(value, secrets) {
52
+ if (typeof value === "string") {
53
+ const match = /^\$secret:(.+)$/.exec(value);
54
+ if (match) {
55
+ const name = match[1];
56
+ const resolved = secrets.get(name);
57
+ if (resolved !== void 0) return resolved;
58
+ return value;
59
+ }
60
+ return value;
61
+ }
62
+ if (Array.isArray(value)) {
63
+ return value.map((item) => walkResolve(item, secrets));
64
+ }
65
+ if (value !== null && typeof value === "object") {
66
+ const result = {};
67
+ for (const [k, v] of Object.entries(value)) {
68
+ result[k] = walkResolve(v, secrets);
69
+ }
70
+ return result;
71
+ }
72
+ return value;
73
+ }
74
+
75
+ export {
76
+ resolveSecrets
77
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getConfigDir
4
- } from "./chunk-ALSUDYA7.js";
4
+ } from "./chunk-CX5SL5HP.js";
5
5
 
6
6
  // src/providers/local/state.ts
7
7
  import { readFileSync, writeFileSync, mkdirSync, renameSync } from "fs";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  StateError,
4
4
  UsageError
5
- } from "./chunk-ZSE4QRKE.js";
5
+ } from "./chunk-KGXPLI7W.js";
6
6
 
7
7
  // src/config/store.ts
8
8
  import { readFileSync, writeFileSync, mkdirSync, renameSync } from "fs";
@@ -19,6 +19,11 @@ var UsageError = class extends ClawopsError {
19
19
  super(message, "UsageError", 2, false);
20
20
  }
21
21
  };
22
+ var AuthError = class extends ClawopsError {
23
+ constructor(message) {
24
+ super(message, "AuthError", 3, false);
25
+ }
26
+ };
22
27
  var StateError = class extends ClawopsError {
23
28
  constructor(message) {
24
29
  super(message, "StateError", 4, false);
@@ -34,11 +39,24 @@ var NetworkError = class extends ClawopsError {
34
39
  super(message, "NetworkError", 6, true);
35
40
  }
36
41
  };
42
+ var OperationalError = class extends ClawopsError {
43
+ constructor(message, retryable = false) {
44
+ super(message, "OperationalError", 1, retryable);
45
+ }
46
+ };
47
+ var CancelledError = class extends ClawopsError {
48
+ constructor(message = "Operation cancelled") {
49
+ super(message, "CancelledError", 130, true);
50
+ }
51
+ };
37
52
 
38
53
  export {
39
54
  ClawopsError,
40
55
  UsageError,
56
+ AuthError,
41
57
  StateError,
42
58
  ProviderError,
43
- NetworkError
59
+ NetworkError,
60
+ OperationalError,
61
+ CancelledError
44
62
  };
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ validatePlan
4
+ } from "./chunk-YTH4L2GN.js";
5
+ import {
6
+ resolveSecrets
7
+ } from "./chunk-BOPSG2LI.js";
8
+ import {
9
+ buildContext
10
+ } from "./chunk-ACYJBSLJ.js";
11
+ import {
12
+ atomicWriteConfig,
13
+ deepMerge,
14
+ readRemoteConfig,
15
+ restartGateway
16
+ } from "./chunk-UDNZUSKA.js";
17
+ import {
18
+ UsageError
19
+ } from "./chunk-KGXPLI7W.js";
20
+
21
+ // src/plan/apply.ts
22
+ async function applyPlan(plan, opts) {
23
+ const validation = validatePlan(plan);
24
+ if (!validation.ok) {
25
+ throw new UsageError(
26
+ `Invalid deploy plan:
27
+ ${validation.errors.join("\n")}`
28
+ );
29
+ }
30
+ if (plan.spec.provider === "local") {
31
+ throw new UsageError(
32
+ "plan/apply is not supported for the local provider. Use `clawops up` directly."
33
+ );
34
+ }
35
+ const ctx = buildContext({
36
+ stack: plan.spec.stackName,
37
+ provider: plan.spec.provider
38
+ });
39
+ const stack = await ctx.getStack();
40
+ await stack.setConfig("instanceType", { value: plan.spec.instanceType });
41
+ if (plan.spec.region) {
42
+ await stack.setConfig("region", { value: plan.spec.region });
43
+ }
44
+ await stack.setConfig("openclawVersion", { value: plan.spec.openclaw.version });
45
+ const modelProvider = plan.spec.openclaw.config?.["models"]?.["provider"];
46
+ if (modelProvider === "bedrock") {
47
+ await stack.setConfig("bedrockEnabled", { value: "true" });
48
+ }
49
+ if (plan.metadata.stackVersion !== void 0) {
50
+ const currentInfo = await stack.info();
51
+ if (currentInfo !== void 0 && currentInfo.version !== plan.metadata.stackVersion) {
52
+ process.stderr.write(
53
+ `
54
+ Warning: stack "${plan.spec.stackName}" has changed since this plan was generated (plan version: ${plan.metadata.stackVersion}, current: ${currentInfo.version}).
55
+ The diff you reviewed may no longer reflect what will be applied.
56
+
57
+ `
58
+ );
59
+ if (opts?.confirmDrift) {
60
+ await opts.confirmDrift();
61
+ }
62
+ }
63
+ }
64
+ const start = Date.now();
65
+ const result = await stack.up({ onOutput: opts?.onOutput, signal: opts?.signal });
66
+ const outputs = Object.fromEntries(
67
+ Object.entries(result.outputs).map(([k, v]) => [k, v.value])
68
+ );
69
+ const changeSummary = {};
70
+ if (result.summary.resourceChanges) {
71
+ for (const [op, count] of Object.entries(result.summary.resourceChanges)) {
72
+ changeSummary[op] = count;
73
+ }
74
+ }
75
+ const hasOverlay = plan.spec.openclaw.config !== void 0 || plan.spec.openclaw.channels !== void 0;
76
+ if (hasOverlay) {
77
+ await applyConfigOverlay(plan, outputs, ctx, opts?.signal);
78
+ }
79
+ return {
80
+ outputs,
81
+ changeSummary,
82
+ durationMs: Date.now() - start
83
+ };
84
+ }
85
+ async function applyConfigOverlay(plan, outputs, ctx, signal) {
86
+ const { connect } = await import("./ssh-IQXNME3D.js");
87
+ const connInfo = ctx.adapter.getConnectionInfo(outputs);
88
+ const session = await connect({
89
+ host: connInfo.host,
90
+ port: connInfo.port,
91
+ user: connInfo.user,
92
+ privateKeyPath: connInfo.privateKeyPath,
93
+ knownHostsPath: connInfo.knownHostsPath,
94
+ signal
95
+ });
96
+ try {
97
+ const remote = await readRemoteConfig(session, signal);
98
+ const configOverlay = plan.spec.openclaw.config ?? {};
99
+ const resolvedOverlay = resolveSecrets(
100
+ configOverlay,
101
+ plan.spec.secrets ?? []
102
+ );
103
+ const merged = deepMerge(remote, {
104
+ ...resolvedOverlay,
105
+ ...plan.spec.openclaw.channels !== void 0 ? { channels: plan.spec.openclaw.channels } : {}
106
+ });
107
+ await atomicWriteConfig(session, merged, signal);
108
+ await restartGateway(session, signal);
109
+ } finally {
110
+ session.close();
111
+ }
112
+ }
113
+
114
+ export {
115
+ applyPlan
116
+ };