@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.
- package/README.md +3 -2
- package/dist/apply-KBF7OPWL.js +14 -0
- package/dist/{automation-JG2YBEL7.js → automation-RULUSJBW.js} +2 -2
- package/dist/{aws-A3323GNM.js → aws-KQBWY43W.js} +12 -10
- package/dist/{azure-LNWBK2ZI.js → azure-4EJFVJZL.js} +4 -3
- package/dist/{bootstrap-7ZS2GLFJ.js → bootstrap-UYCOEJQX.js} +48 -12
- package/dist/{chunk-LT24GUUO.js → chunk-3QJBNAHW.js} +1 -1
- package/dist/{chunk-BA6MDFZK.js → chunk-4U3LTLWZ.js} +2 -83
- package/dist/{chunk-PERSDQMT.js → chunk-ACYJBSLJ.js} +7 -7
- package/dist/chunk-BOPSG2LI.js +77 -0
- package/dist/{chunk-PRYLTCS4.js → chunk-BRPU7AQC.js} +1 -1
- package/dist/{chunk-ALSUDYA7.js → chunk-CX5SL5HP.js} +1 -1
- package/dist/{chunk-ZSE4QRKE.js → chunk-KGXPLI7W.js} +19 -1
- package/dist/chunk-LU63NZD3.js +116 -0
- package/dist/chunk-QJ6ERXHN.js +266 -0
- package/dist/{chunk-D4UAHAKI.js → chunk-QMXM2SMO.js} +10 -6
- package/dist/chunk-UDNZUSKA.js +97 -0
- package/dist/chunk-ZVOEQCNW.js +92 -0
- package/dist/cli.js +1256 -60
- package/dist/context-VUAYRAJY.js +10 -0
- package/dist/errors-OK47MQFD.js +21 -0
- package/dist/{gcp-OHWCTFLL.js → gcp-SKURG3L6.js} +4 -3
- package/dist/generate-WG5VONSE.js +14 -0
- package/dist/js-yaml-PTEEG4FO.js +2647 -0
- package/dist/{outputs-6DAVEEAZ.js → outputs-DJHBY7EE.js} +2 -2
- package/dist/{package-FA6UOG2I.js → package-3JZ767DN.js} +1 -1
- package/dist/{pool-FAVU7WCW.js → pool-FBFHATDG.js} +3 -2
- package/dist/remote-config-JQ77SRC2.js +21 -0
- package/dist/secrets-SVZWNAPK.js +7 -0
- package/dist/{server-V5ZFL76Y.js → server-QJRWK2RV.js} +64 -150
- package/dist/ssh-IQXNME3D.js +8 -0
- package/dist/{store-ARJ2EO6L.js → store-SDUR52Z5.js} +2 -2
- package/package.json +1 -1
- package/dist/apply-DPS23GLL.js +0 -12
- package/dist/chunk-UCIVU24B.js +0 -56
- package/dist/context-T52JWL3P.js +0 -10
- 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
|
-
| `
|
|
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 (
|
|
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
|
+
};
|
|
@@ -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") ?? "
|
|
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
|
-
|
|
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
|
|
167
|
-
|
|
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
|
-
${
|
|
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
|
-
{"
|
|
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") ?? "
|
|
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
|
-
{"
|
|
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-
|
|
8
|
-
import
|
|
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-
|
|
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 = `
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
NetworkError
|
|
4
|
-
} from "./chunk-
|
|
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
|
-
|
|
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-
|
|
4
|
+
} from "./chunk-BRPU7AQC.js";
|
|
5
5
|
import {
|
|
6
6
|
getConfigDir,
|
|
7
7
|
requireConfig
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-CX5SL5HP.js";
|
|
9
9
|
import {
|
|
10
10
|
UsageError
|
|
11
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
+
};
|
|
@@ -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
|
+
};
|