@clawops/cli 2.0.0 → 2.0.1
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 +98 -12
- package/dist/apply-KYFPWI5U.js +27 -0
- package/dist/automation-RMAIBNVC.js +44 -0
- package/dist/bedrock-XVASJX2K.js +61 -0
- package/dist/{bootstrap-DMRPWJGN.js → bootstrap-HNGKY6WW.js} +11 -8
- package/dist/{chunk-N44K3LHE.js → chunk-2I7KCMXZ.js} +6 -4
- package/dist/{chunk-3WYA2QDM.js → chunk-2Z7J3ERQ.js} +1 -1
- package/dist/chunk-3OLUJPCB.js +36 -0
- package/dist/chunk-5XT5IAJK.js +69 -0
- package/dist/chunk-AEEMRFZV.js +24 -0
- package/dist/{chunk-YR3O2I3S.js → chunk-BSLIYB5Q.js} +6 -1
- package/dist/{chunk-AHAAEXWC.js → chunk-E4NJSJ54.js} +16 -93
- package/dist/chunk-FEK7VJAY.js +16 -0
- package/dist/{chunk-6TIYVHPX.js → chunk-GIEVAGEK.js} +7 -33
- package/dist/chunk-HSPHXG6W.js +67 -0
- package/dist/{chunk-GYV4KNW7.js → chunk-IVWP4QUJ.js} +133 -40
- package/dist/{chunk-AX74VXTV.js → chunk-KC3GC3PE.js} +58 -49
- package/dist/{chunk-UFRO4H5W.js → chunk-LBE6COC4.js} +56 -40
- package/dist/chunk-LEVIW2PV.js +890 -0
- package/dist/chunk-MC5ATCXS.js +108 -0
- package/dist/chunk-N4WZLWJ4.js +16 -0
- package/dist/{chunk-SBLQH2TJ.js → chunk-NUL5F6YV.js} +1 -1
- package/dist/chunk-RHOWYIWT.js +52 -0
- package/dist/{chunk-WHJMKDV7.js → chunk-STLRHLLV.js} +65 -12
- package/dist/chunk-V25QRZRB.js +45 -0
- package/dist/chunk-VOEJYRIK.js +33 -0
- package/dist/{providers-2OABPW2E.js → chunk-VUFMZEIM.js} +3 -2
- package/dist/chunk-W3OLNNJW.js +51 -0
- package/dist/cli-WLD3EBEM.js +11 -0
- package/dist/cli-auth-GD5SK7JU.js +13 -0
- package/dist/cli.js +518 -206
- package/dist/context-7EVC6W45.js +20 -0
- package/dist/docker-3PDO4TS7.js +12 -0
- package/dist/{firewall-T3NTPZJD.js → firewall-5VKZ4WBH.js} +22 -1
- package/dist/generate-I6YQ5V2T.js +29 -0
- package/dist/{harden-PCDK3UQ5.js → harden-72JMRT4E.js} +8 -5
- package/dist/index.d.ts +56 -0
- package/dist/known-hosts-file-I3Q3NBYQ.js +22 -0
- package/dist/{logs-P7YCKVF4.js → logs-5A7YYA53.js} +1 -1
- package/dist/{mcp-wire-BFEY2Y6H.js → mcp-wire-3SWBM7D6.js} +25 -15
- package/dist/models-AGTQWYDL.js +32 -0
- package/dist/network-args-PYJRJBYO.js +64 -0
- package/dist/{package-3HUW57XH.js → package-5BCMEBTE.js} +10 -3
- package/dist/passphrase-MJBE45N7.js +15 -0
- package/dist/{pool-EX4ZTA5P.js → pool-PIIKJ4L2.js} +3 -2
- package/dist/preflight-EF52S3WW.js +162 -0
- package/dist/preflight-MWWM5JT3.js +181 -0
- package/dist/preflight-TFD4N5ZU.js +141 -0
- package/dist/providers-FW6H6OPF.js +11 -0
- package/dist/ready-KDNRBNSU.js +95 -0
- package/dist/{remote-config-Z6IIZR3D.js → remote-config-3HZLUWEA.js} +3 -2
- package/dist/{runtime-LZ7NA53V.js → runtime-X3KW5TCP.js} +4 -2
- package/dist/server-GBA7BJGK.js +46 -0
- package/dist/ssh-VAI5DU5M.js +11 -0
- package/dist/ssh-key-DUO6OKOI.js +7 -0
- package/dist/stack-config-R4SPSUQZ.js +12 -0
- package/dist/{validate-BOQRWX4F.js → validate-YVI3BJ55.js} +1 -1
- package/dist/wait-WO72TRRQ.js +79 -0
- package/package.json +10 -3
- package/spec/mcp-tools.yaml +21 -2
- package/spec/models.yaml +14 -3
- package/spec/providers.schema.json +163 -28
- package/dist/apply-AM5HERE7.js +0 -19
- package/dist/automation-RULUSJBW.js +0 -30
- package/dist/aws-AMON7DA7.js +0 -265
- package/dist/azure-T4G6OBJQ.js +0 -273
- package/dist/chunk-CKSPROD5.js +0 -129
- package/dist/chunk-LU2V3LXA.js +0 -114
- package/dist/context-KHKWJJMC.js +0 -10
- package/dist/gcp-2JBTD7ZM.js +0 -204
- package/dist/generate-2XV2GSP2.js +0 -15
- package/dist/local-DXBEVZ5C.js +0 -47
- package/dist/server-A5AB67GQ.js +0 -36
- package/dist/ssh-B3NZAU4I.js +0 -8
- package/dist/{version-guard-GPSTCPT7.js → version-guard-XVYW5WFJ.js} +1 -1
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
formatKnownHostsLine,
|
|
4
|
+
keyTypeFromBlob,
|
|
5
|
+
verifyAgainstKnownHosts
|
|
6
|
+
} from "./chunk-MC5ATCXS.js";
|
|
2
7
|
import {
|
|
3
8
|
NetworkError
|
|
4
9
|
} from "./chunk-KGXPLI7W.js";
|
|
@@ -8,98 +13,6 @@ import { readFileSync, appendFileSync, mkdirSync } from "fs";
|
|
|
8
13
|
import { createServer } from "net";
|
|
9
14
|
import path from "path";
|
|
10
15
|
import { Client } from "ssh2";
|
|
11
|
-
|
|
12
|
-
// src/transport/known-hosts.ts
|
|
13
|
-
import { createHmac, timingSafeEqual } from "crypto";
|
|
14
|
-
function hostEntryFor(host, port) {
|
|
15
|
-
return port === 22 ? host : `[${host}]:${port}`;
|
|
16
|
-
}
|
|
17
|
-
function parseKnownHosts(content) {
|
|
18
|
-
const entries = [];
|
|
19
|
-
for (const raw of content.split("\n")) {
|
|
20
|
-
const line = raw.trim();
|
|
21
|
-
if (!line || line.startsWith("#")) continue;
|
|
22
|
-
let parts = line.split(/\s+/);
|
|
23
|
-
let marker;
|
|
24
|
-
if (parts[0]?.startsWith("@")) {
|
|
25
|
-
marker = parts[0];
|
|
26
|
-
parts = parts.slice(1);
|
|
27
|
-
}
|
|
28
|
-
const hostField = parts[0];
|
|
29
|
-
if (!hostField) continue;
|
|
30
|
-
const hosts = hostField.split(",");
|
|
31
|
-
if (parts.length >= 3) {
|
|
32
|
-
entries.push({ hosts, keyType: parts[1], base64Key: parts[2], ...marker ? { marker } : {} });
|
|
33
|
-
} else if (parts.length === 2 && /^[0-9a-f]+$/i.test(parts[1] ?? "")) {
|
|
34
|
-
entries.push({ hosts, hexKey: parts[1], ...marker ? { marker } : {} });
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return entries;
|
|
38
|
-
}
|
|
39
|
-
function hashedHostMatches(token, hostEntry) {
|
|
40
|
-
const parts = token.split("|");
|
|
41
|
-
if (parts.length !== 4 || parts[1] !== "1") return false;
|
|
42
|
-
const salt = parts[2];
|
|
43
|
-
const expected = parts[3];
|
|
44
|
-
if (!salt || !expected) return false;
|
|
45
|
-
try {
|
|
46
|
-
const actual = createHmac("sha1", Buffer.from(salt, "base64")).update(hostEntry).digest("base64");
|
|
47
|
-
const a = Buffer.from(actual);
|
|
48
|
-
const b = Buffer.from(expected);
|
|
49
|
-
return a.length === b.length && timingSafeEqual(a, b);
|
|
50
|
-
} catch {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
function patternToRegExp(pattern) {
|
|
55
|
-
const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
|
|
56
|
-
return new RegExp(`^${escaped}$`, "i");
|
|
57
|
-
}
|
|
58
|
-
var isPattern = (host) => host.includes("*") || host.includes("?");
|
|
59
|
-
function entryMatchesHost(entry, hostEntry) {
|
|
60
|
-
let matched = false;
|
|
61
|
-
for (const host of entry.hosts) {
|
|
62
|
-
if (host.startsWith("|1|")) {
|
|
63
|
-
if (hashedHostMatches(host, hostEntry)) matched = true;
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
const negated = host.startsWith("!");
|
|
67
|
-
const pattern = negated ? host.slice(1) : host;
|
|
68
|
-
const hit = isPattern(pattern) ? patternToRegExp(pattern).test(hostEntry) : pattern === hostEntry;
|
|
69
|
-
if (hit) {
|
|
70
|
-
if (negated) return false;
|
|
71
|
-
matched = true;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return matched;
|
|
75
|
-
}
|
|
76
|
-
function verifyAgainstKnownHosts(content, host, port, key) {
|
|
77
|
-
const hostEntry = hostEntryFor(host, port);
|
|
78
|
-
const base64Key = key.toString("base64");
|
|
79
|
-
const hexKey = key.toString("hex");
|
|
80
|
-
let sawHost = false;
|
|
81
|
-
for (const entry of parseKnownHosts(content)) {
|
|
82
|
-
if (!entryMatchesHost(entry, hostEntry)) continue;
|
|
83
|
-
if (entry.marker === "@cert-authority") continue;
|
|
84
|
-
sawHost = true;
|
|
85
|
-
const matches = entry.base64Key ? entry.base64Key === base64Key : entry.hexKey?.toLowerCase() === hexKey.toLowerCase();
|
|
86
|
-
if (matches) return entry.marker === "@revoked" ? "mismatch" : "match";
|
|
87
|
-
}
|
|
88
|
-
return sawHost ? "mismatch" : "unknown";
|
|
89
|
-
}
|
|
90
|
-
function formatKnownHostsLine(host, port, keyType, key) {
|
|
91
|
-
return `${hostEntryFor(host, port)} ${keyType} ${key.toString("base64")}
|
|
92
|
-
`;
|
|
93
|
-
}
|
|
94
|
-
function keyTypeFromBlob(key) {
|
|
95
|
-
if (key.length < 4) return void 0;
|
|
96
|
-
const len = key.readUInt32BE(0);
|
|
97
|
-
if (len === 0 || len > 64 || key.length < 4 + len) return void 0;
|
|
98
|
-
const type = key.subarray(4, 4 + len).toString("utf-8");
|
|
99
|
-
return /^[\x20-\x7e]+$/.test(type) ? type : void 0;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// src/transport/ssh.ts
|
|
103
16
|
var Ssh2Session = class {
|
|
104
17
|
constructor(client) {
|
|
105
18
|
this.client = client;
|
|
@@ -240,6 +153,15 @@ var Ssh2Session = class {
|
|
|
240
153
|
this.client.end();
|
|
241
154
|
}
|
|
242
155
|
};
|
|
156
|
+
function describeConnectError(message, opts) {
|
|
157
|
+
if (!/host.*(denied|verification)/i.test(message)) {
|
|
158
|
+
return `SSH connection failed: ${message}`;
|
|
159
|
+
}
|
|
160
|
+
const entry = opts.port === 22 ? opts.host : `[${opts.host}]:${opts.port}`;
|
|
161
|
+
return `the host key for ${opts.host}:${opts.port} does not match the one recorded in ${opts.knownHostsPath}. If your cloud reassigned this address \u2014 the usual cause \u2014 drop the stale entry and retry:
|
|
162
|
+
ssh-keygen -R ${entry} -f ${opts.knownHostsPath}
|
|
163
|
+
If you did not expect this address to change hands, do not connect.`;
|
|
164
|
+
}
|
|
243
165
|
async function connect(opts) {
|
|
244
166
|
let privateKey;
|
|
245
167
|
try {
|
|
@@ -266,7 +188,7 @@ async function connect(opts) {
|
|
|
266
188
|
});
|
|
267
189
|
client.on("error", (err) => {
|
|
268
190
|
opts.signal?.removeEventListener("abort", onAbort);
|
|
269
|
-
reject(new NetworkError(
|
|
191
|
+
reject(new NetworkError(describeConnectError(err.message, opts)));
|
|
270
192
|
});
|
|
271
193
|
const config = {
|
|
272
194
|
host: opts.host,
|
|
@@ -299,5 +221,6 @@ function verifyHostKey(host, port, keyHash, knownHostsPath) {
|
|
|
299
221
|
}
|
|
300
222
|
|
|
301
223
|
export {
|
|
224
|
+
describeConnectError,
|
|
302
225
|
connect
|
|
303
226
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/providers/azure/sizes.ts
|
|
4
|
+
var INSTANCE_TYPE_MAP = {
|
|
5
|
+
micro: "Standard_B1s",
|
|
6
|
+
small: "Standard_B2s",
|
|
7
|
+
medium: "Standard_B4ms",
|
|
8
|
+
large: "Standard_B8ms",
|
|
9
|
+
gpu: "Standard_NC6s_v3"
|
|
10
|
+
};
|
|
11
|
+
var DEFAULT_ALIAS = "small";
|
|
12
|
+
|
|
13
|
+
export {
|
|
14
|
+
INSTANCE_TYPE_MAP,
|
|
15
|
+
DEFAULT_ALIAS
|
|
16
|
+
};
|
|
@@ -1,29 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./chunk-EI7CQQ7D.js";
|
|
6
|
-
|
|
7
|
-
// src/openclaw/run-flags.ts
|
|
8
|
-
var CONFIG_MOUNT_PATH = "/app/config.json";
|
|
9
|
-
var GATEWAY_PORT = 18789;
|
|
10
|
-
var CONFIG_PATH_ENV = `-e OPENCLAW_CONFIG_PATH=${CONFIG_MOUNT_PATH}`;
|
|
11
|
-
var ADD_HOST_FLAG = "--add-host=host.docker.internal:host-gateway";
|
|
12
|
-
var PORT_PIN = `--port ${GATEWAY_PORT}`;
|
|
13
|
-
var COMMON_RUN_FLAGS = `${CONFIG_PATH_ENV} ${ADD_HOST_FLAG}`;
|
|
14
|
-
var IMAGE_INSPECT_CMD = `docker inspect openclaw --format '{{.Config.Image}}'`;
|
|
15
|
-
function imageForRestart(inspectStdout) {
|
|
16
|
-
const image = inspectStdout.trim();
|
|
17
|
-
if (!image || image.includes("Error") || !image.includes(":")) {
|
|
18
|
-
return err(
|
|
19
|
-
"No running OpenClaw container found, so there is no image version to reuse. Deploy one with `clawops up --openclaw-version <version>`. clawops does not fall back to `latest` or `stable` here: both now point at OpenClaw 2.0, which this release line does not support."
|
|
20
|
-
);
|
|
21
|
-
}
|
|
22
|
-
return ok(image);
|
|
23
|
-
}
|
|
24
|
-
function versionOf(image) {
|
|
25
|
-
return image.split(":")[1] ?? "unknown";
|
|
26
|
-
}
|
|
3
|
+
GATEWAY_PORT
|
|
4
|
+
} from "./chunk-VOEJYRIK.js";
|
|
27
5
|
|
|
28
6
|
// src/openclaw/runtime.ts
|
|
29
7
|
var STATE_DIR_CONTAINER = "/home/node/.openclaw";
|
|
@@ -31,7 +9,7 @@ var STATE_DIR_HOST_LINUX = "/var/lib/clawops/openclaw";
|
|
|
31
9
|
var STATE_DIR_HOST_MACOS = "${HOME}/.clawops/openclaw";
|
|
32
10
|
var CONFIG_FILENAME = "openclaw.json";
|
|
33
11
|
var CONTAINER_UID = 1e3;
|
|
34
|
-
var
|
|
12
|
+
var CONFIG_MOUNT_PATH = `${STATE_DIR_CONTAINER}/${CONFIG_FILENAME}`;
|
|
35
13
|
function stateDirForOS(os) {
|
|
36
14
|
return os === "Darwin" ? STATE_DIR_HOST_MACOS : STATE_DIR_HOST_LINUX;
|
|
37
15
|
}
|
|
@@ -46,7 +24,7 @@ var SECURITY_FLAGS = [
|
|
|
46
24
|
"--pids-limit 512"
|
|
47
25
|
].join(" ");
|
|
48
26
|
var SUPERVISOR_ENV = "-e OPENCLAW_SUPERVISOR_MODE=external";
|
|
49
|
-
var
|
|
27
|
+
var ADD_HOST_FLAG = "--add-host=host.docker.internal:host-gateway";
|
|
50
28
|
function publishFlag(port, scope) {
|
|
51
29
|
return scope === "loopback" ? `-p 127.0.0.1:${port}:${port}` : `-p ${port}:${port}`;
|
|
52
30
|
}
|
|
@@ -73,7 +51,7 @@ function gatewayRunArgs(spec) {
|
|
|
73
51
|
capacity,
|
|
74
52
|
// No OPENCLAW_CONFIG_PATH: STATE_DIR_CONTAINER is OpenClaw's own default, so mounting
|
|
75
53
|
// it there *is* the configuration. One fewer thing to keep in sync.
|
|
76
|
-
|
|
54
|
+
ADD_HOST_FLAG,
|
|
77
55
|
SUPERVISOR_ENV,
|
|
78
56
|
envFileArg,
|
|
79
57
|
// The directory, writable. Never the file, and never :ro — see GatewayRunSpec.stateDir.
|
|
@@ -128,22 +106,18 @@ function publishForRestart(inspectStdout) {
|
|
|
128
106
|
}
|
|
129
107
|
|
|
130
108
|
export {
|
|
131
|
-
GATEWAY_PORT,
|
|
132
|
-
IMAGE_INSPECT_CMD,
|
|
133
|
-
imageForRestart,
|
|
134
|
-
versionOf,
|
|
135
109
|
STATE_DIR_CONTAINER,
|
|
136
110
|
STATE_DIR_HOST_LINUX,
|
|
137
111
|
STATE_DIR_HOST_MACOS,
|
|
138
112
|
CONFIG_FILENAME,
|
|
139
113
|
CONTAINER_UID,
|
|
140
|
-
|
|
114
|
+
CONFIG_MOUNT_PATH,
|
|
141
115
|
stateDirForOS,
|
|
142
116
|
configPathForOS,
|
|
143
117
|
ENV_FILE_PATH,
|
|
144
118
|
SECURITY_FLAGS,
|
|
145
119
|
SUPERVISOR_ENV,
|
|
146
|
-
|
|
120
|
+
ADD_HOST_FLAG,
|
|
147
121
|
gatewayRunArgs,
|
|
148
122
|
gatewayRunCommand,
|
|
149
123
|
PUBLISH_INSPECT_CMD,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
resolvePublicKey
|
|
4
|
+
} from "./chunk-AEEMRFZV.js";
|
|
5
|
+
import {
|
|
6
|
+
GATEWAY_PORT
|
|
7
|
+
} from "./chunk-VOEJYRIK.js";
|
|
8
|
+
import {
|
|
9
|
+
getConfig
|
|
10
|
+
} from "./chunk-CX5SL5HP.js";
|
|
11
|
+
import {
|
|
12
|
+
UsageError
|
|
13
|
+
} from "./chunk-KGXPLI7W.js";
|
|
14
|
+
|
|
15
|
+
// src/plan/stack-config.ts
|
|
16
|
+
import process from "process";
|
|
17
|
+
async function writeStackConfig(stack, plan) {
|
|
18
|
+
const sshPublicKey = plan.spec.ssh?.publicKey ?? fallbackPublicKey();
|
|
19
|
+
if (!sshPublicKey) {
|
|
20
|
+
throw new UsageError(
|
|
21
|
+
"This plan carries no SSH public key, and none could be resolved from `ssh.keyPath` in ~/.clawops/config.json. The instance would have no way to admit anyone. Run `clawops doctor` to see why the key is unusable, then regenerate the plan."
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
await stack.setConfig("sshPublicKey", { value: sshPublicKey });
|
|
25
|
+
await stack.setConfig("instanceType", { value: plan.spec.instanceType });
|
|
26
|
+
if (plan.spec.region) {
|
|
27
|
+
await stack.setConfig("region", { value: plan.spec.region });
|
|
28
|
+
}
|
|
29
|
+
await stack.setConfig("openclawVersion", { value: plan.spec.openclaw.version });
|
|
30
|
+
await stack.setConfig("publishGateway", {
|
|
31
|
+
value: plan.spec.network?.publishGateway ?? "loopback"
|
|
32
|
+
});
|
|
33
|
+
await stack.setConfig("gatewayPort", {
|
|
34
|
+
value: String(plan.spec.network?.gatewayPort ?? GATEWAY_PORT)
|
|
35
|
+
});
|
|
36
|
+
if (plan.spec.provider === "gcp") {
|
|
37
|
+
const { resolveProjectId } = await import("./preflight-EF52S3WW.js");
|
|
38
|
+
const project = resolveProjectId();
|
|
39
|
+
if (project) await stack.setConfig("gcp:project", { value: project });
|
|
40
|
+
}
|
|
41
|
+
if (plan.spec.provider === "azure") {
|
|
42
|
+
const { resolveSubscriptionId } = await import("./cli-auth-GD5SK7JU.js");
|
|
43
|
+
const subscription = resolveSubscriptionId();
|
|
44
|
+
if (subscription) await stack.setConfig("azure-native:subscriptionId", { value: subscription });
|
|
45
|
+
}
|
|
46
|
+
await stack.setConfig("accessMode", { value: "restricted" });
|
|
47
|
+
await stack.setConfig("sshCidrs", {
|
|
48
|
+
value: (plan.spec.network?.allowedSshCidrs ?? []).join(",")
|
|
49
|
+
});
|
|
50
|
+
await stack.setConfig("gatewayCidrs", {
|
|
51
|
+
value: (plan.spec.network?.allowedGatewayCidrs ?? []).join(",")
|
|
52
|
+
});
|
|
53
|
+
const modelProvider = plan.spec.openclaw.config?.["models"]?.["provider"];
|
|
54
|
+
if (modelProvider === "bedrock") {
|
|
55
|
+
await stack.setConfig("bedrockEnabled", { value: "true" });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function fallbackPublicKey() {
|
|
59
|
+
const config = getConfig();
|
|
60
|
+
if (!config) return void 0;
|
|
61
|
+
const keyPath = config.ssh.keyPath.replace(/^~/, process.env["HOME"] ?? "~");
|
|
62
|
+
return resolvePublicKey(keyPath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export {
|
|
66
|
+
writeStackConfig
|
|
67
|
+
};
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "./chunk-LT4XMO5D.js";
|
|
3
|
+
inspectContainer
|
|
4
|
+
} from "./chunk-3OLUJPCB.js";
|
|
6
5
|
import {
|
|
7
6
|
execPrivileged
|
|
8
7
|
} from "./chunk-NUDIHZEH.js";
|
|
9
8
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from "./chunk-
|
|
9
|
+
interpretProbe,
|
|
10
|
+
probeCommand
|
|
11
|
+
} from "./chunk-LT4XMO5D.js";
|
|
13
12
|
import {
|
|
14
|
-
GATEWAY_PORT,
|
|
15
13
|
PUBLISH_INSPECT_CMD,
|
|
16
14
|
STATE_DIR_HOST_LINUX,
|
|
17
15
|
configPathForOS,
|
|
18
16
|
publishForRestart
|
|
19
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-GIEVAGEK.js";
|
|
18
|
+
import {
|
|
19
|
+
GATEWAY_PORT
|
|
20
|
+
} from "./chunk-VOEJYRIK.js";
|
|
21
|
+
import {
|
|
22
|
+
chalk,
|
|
23
|
+
failure
|
|
24
|
+
} from "./chunk-Q7NQY5HV.js";
|
|
20
25
|
|
|
21
26
|
// src/cli/commands/monitor.ts
|
|
22
27
|
import { defineCommand } from "citty";
|
|
@@ -36,11 +41,19 @@ function formatUptime(startedAt) {
|
|
|
36
41
|
async function gatherSnapshot(session, signal, tailLines = 10) {
|
|
37
42
|
const DOCKER = "PATH=/usr/local/bin:/opt/homebrew/bin:$PATH docker";
|
|
38
43
|
const [inspectRaw, statsRaw, healthRaw, configRaw, diskRaw, logsRaw] = await Promise.all([
|
|
39
|
-
|
|
44
|
+
inspectContainer(
|
|
40
45
|
session,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
"openclaw",
|
|
47
|
+
"{{.State.Status}}|{{.Config.Image}}|{{.State.StartedAt}}|{{.RestartCount}}",
|
|
48
|
+
signal,
|
|
49
|
+
DOCKER
|
|
50
|
+
).then((r) => ({
|
|
51
|
+
// The dashboard shows a word per field; a refusal says so rather than borrowing
|
|
52
|
+
// "not found", which would claim the container is gone.
|
|
53
|
+
stdout: r.kind === "ok" ? r.value : r.kind === "missing" ? "not found|||0" : "unreachable|||0",
|
|
54
|
+
stderr: r.kind === "error" ? r.detail : "",
|
|
55
|
+
code: r.kind === "ok" ? 0 : 1
|
|
56
|
+
})),
|
|
44
57
|
execPrivileged(
|
|
45
58
|
session,
|
|
46
59
|
`${DOCKER} stats openclaw --no-stream --format '{{.MemUsage}}|{{.CPUPerc}}' 2>/dev/null || echo '\u2014|\u2014'`,
|
|
@@ -133,7 +146,7 @@ function renderSnapshot(snap, opts) {
|
|
|
133
146
|
return lines.join("\n");
|
|
134
147
|
}
|
|
135
148
|
async function probeEntries() {
|
|
136
|
-
const { buildContext } = await import("./context-
|
|
149
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
137
150
|
const { getConfig } = await import("./store-SDUR52Z5.js");
|
|
138
151
|
const config = getConfig();
|
|
139
152
|
if (!config) return [];
|
|
@@ -360,8 +373,8 @@ var monitor_default = defineCommand({
|
|
|
360
373
|
const tailLines = Math.max(1, parseInt(String(args.tail ?? "10"), 10) || 10);
|
|
361
374
|
const isTTY = Boolean(process.stdout.isTTY);
|
|
362
375
|
const noColor = Boolean(args["no-color"]) || !isTTY;
|
|
363
|
-
const { buildContext } = await import("./context-
|
|
364
|
-
const { acquireSession, drainPool } = await import("./pool-
|
|
376
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
377
|
+
const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
365
378
|
const ac = new AbortController();
|
|
366
379
|
process.on("SIGINT", () => ac.abort());
|
|
367
380
|
process.on("SIGTERM", () => ac.abort());
|
|
@@ -491,13 +504,14 @@ var monitor_default = defineCommand({
|
|
|
491
504
|
|
|
492
505
|
// src/diagnostics/index.ts
|
|
493
506
|
import process2 from "process";
|
|
494
|
-
import { accessSync, mkdirSync, constants } from "fs";
|
|
507
|
+
import { accessSync, mkdirSync, readFileSync, constants } from "fs";
|
|
508
|
+
import ssh2 from "ssh2";
|
|
495
509
|
import path from "path";
|
|
496
510
|
async function runDiagnostics(opts = {}, deps = {}) {
|
|
497
511
|
const { getConfig, getConfigDir } = await import("./store-SDUR52Z5.js");
|
|
498
512
|
const config = getConfig();
|
|
499
513
|
const sections = [];
|
|
500
|
-
sections.push({ title: "Runtime", checks: runtimeChecks(getConfigDir()) });
|
|
514
|
+
sections.push({ title: "Runtime", checks: await runtimeChecks(getConfigDir()) });
|
|
501
515
|
sections.push({
|
|
502
516
|
title: "Config",
|
|
503
517
|
checks: [
|
|
@@ -505,8 +519,10 @@ async function runDiagnostics(opts = {}, deps = {}) {
|
|
|
505
519
|
]
|
|
506
520
|
});
|
|
507
521
|
sections.push({ title: "SSH", checks: sshChecks(config) });
|
|
508
|
-
sections.push({ title: "Credentials", checks: await credentialChecks(config) });
|
|
522
|
+
sections.push({ title: "Credentials", checks: await credentialChecks(config, opts.provider) });
|
|
509
523
|
sections.push({ title: "OpenClaw", checks: await versionChecks() });
|
|
524
|
+
const account = await accountChecks(config, opts.stack, opts.provider, opts.instanceType);
|
|
525
|
+
if (account.length > 0) sections.push({ title: "Cloud account", checks: account });
|
|
510
526
|
if (opts.stack) {
|
|
511
527
|
const open = deps.openSession ?? defaultOpenSession;
|
|
512
528
|
let handle;
|
|
@@ -530,7 +546,7 @@ async function runDiagnostics(opts = {}, deps = {}) {
|
|
|
530
546
|
});
|
|
531
547
|
} finally {
|
|
532
548
|
handle.release();
|
|
533
|
-
const { drainPool } = await import("./pool-
|
|
549
|
+
const { drainPool } = await import("./pool-PIIKJ4L2.js");
|
|
534
550
|
drainPool();
|
|
535
551
|
}
|
|
536
552
|
}
|
|
@@ -542,7 +558,7 @@ function summarise(sections) {
|
|
|
542
558
|
for (const s of sections) for (const c of s.checks) counts[c.status]++;
|
|
543
559
|
return { sections, ok: counts.fail === 0, counts };
|
|
544
560
|
}
|
|
545
|
-
function runtimeChecks(configDir) {
|
|
561
|
+
async function runtimeChecks(configDir) {
|
|
546
562
|
const checks = [];
|
|
547
563
|
const nodeMajor = parseInt(process2.version.slice(1).split(".")[0] ?? "0", 10);
|
|
548
564
|
checks.push(
|
|
@@ -560,6 +576,35 @@ function runtimeChecks(configDir) {
|
|
|
560
576
|
} catch {
|
|
561
577
|
checks.push({ name: "Pulumi home", status: "fail", detail: `${pulumiHome} (not writable)` });
|
|
562
578
|
}
|
|
579
|
+
const { pulumiCliStatus } = await import("./cli-WLD3EBEM.js");
|
|
580
|
+
const cli = await pulumiCliStatus(configDir);
|
|
581
|
+
checks.push(
|
|
582
|
+
cli.kind === "managed" ? { name: "Pulumi CLI", status: "pass", detail: `${cli.version} (${cli.root})` } : cli.kind === "path" ? { name: "Pulumi CLI", status: "pass", detail: `${cli.version} (on PATH)` } : {
|
|
583
|
+
name: "Pulumi CLI",
|
|
584
|
+
status: "warn",
|
|
585
|
+
detail: "not installed",
|
|
586
|
+
remedy: `clawops installs it into ${path.join(configDir, ".pulumi-cli")} on first apply`
|
|
587
|
+
}
|
|
588
|
+
);
|
|
589
|
+
const { passphraseStatus, passphrasePath } = await import("./passphrase-MJBE45N7.js");
|
|
590
|
+
const passphrase = passphraseStatus(configDir);
|
|
591
|
+
checks.push(
|
|
592
|
+
passphrase === "environment" ? {
|
|
593
|
+
name: "State passphrase",
|
|
594
|
+
status: "pass",
|
|
595
|
+
detail: "PULUMI_CONFIG_PASSPHRASE set in the environment"
|
|
596
|
+
} : passphrase === "stored" ? {
|
|
597
|
+
name: "State passphrase",
|
|
598
|
+
status: "pass",
|
|
599
|
+
detail: passphrasePath(configDir),
|
|
600
|
+
remedy: "back this up \u2014 without it, an existing stack's secrets cannot be read"
|
|
601
|
+
} : {
|
|
602
|
+
name: "State passphrase",
|
|
603
|
+
status: "warn",
|
|
604
|
+
detail: "not created yet",
|
|
605
|
+
remedy: `clawops generates one at ${passphrasePath(configDir)} on first apply`
|
|
606
|
+
}
|
|
607
|
+
);
|
|
563
608
|
return checks;
|
|
564
609
|
}
|
|
565
610
|
function expandHome(p) {
|
|
@@ -571,7 +616,15 @@ function sshChecks(config) {
|
|
|
571
616
|
const keyPath = expandHome(config.ssh.keyPath);
|
|
572
617
|
try {
|
|
573
618
|
accessSync(keyPath, constants.R_OK);
|
|
574
|
-
|
|
619
|
+
const parsed = ssh2.utils.parseKey(readFileSync(keyPath));
|
|
620
|
+
checks.push(
|
|
621
|
+
parsed instanceof Error ? {
|
|
622
|
+
name: "SSH key",
|
|
623
|
+
status: "fail",
|
|
624
|
+
detail: `${keyPath} (${parsed.message.toLowerCase()})`,
|
|
625
|
+
remedy: `clawops connects with ssh2. Regenerate in OpenSSH format: ssh-keygen -t ed25519 -f ${keyPath} -N '' -C clawops`
|
|
626
|
+
} : { name: "SSH key", status: "pass", detail: `${keyPath} (${parsed.type})` }
|
|
627
|
+
);
|
|
575
628
|
} catch {
|
|
576
629
|
checks.push({
|
|
577
630
|
name: "SSH key",
|
|
@@ -592,15 +645,23 @@ function sshChecks(config) {
|
|
|
592
645
|
}
|
|
593
646
|
return checks;
|
|
594
647
|
}
|
|
595
|
-
async function
|
|
648
|
+
async function providerCredentialCheck(provider, why) {
|
|
649
|
+
if (provider === "local") {
|
|
650
|
+
return { name: "local", status: "pass", detail: "SSH-only, no cloud credentials required" };
|
|
651
|
+
}
|
|
652
|
+
const { getProvider } = await import("./providers-FW6H6OPF.js");
|
|
653
|
+
try {
|
|
654
|
+
const adapter = getProvider(provider);
|
|
655
|
+
const result = await adapter.validateConfig();
|
|
656
|
+
return result.ok ? { name: provider, status: "pass", detail: why } : { name: provider, status: "fail", detail: result.errors.join("; ") };
|
|
657
|
+
} catch (err) {
|
|
658
|
+
return { name: provider, status: "fail", detail: messageOf(err) };
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
async function credentialChecks(config, provider) {
|
|
662
|
+
if (provider) return [await providerCredentialCheck(provider, "requested with --provider")];
|
|
596
663
|
if (!config) return [{ name: "Credentials", status: "info", detail: "skipped \u2014 no config" }];
|
|
597
|
-
const { getProvider } = await import("./providers-
|
|
598
|
-
await Promise.all([
|
|
599
|
-
import("./aws-AMON7DA7.js"),
|
|
600
|
-
import("./gcp-2JBTD7ZM.js"),
|
|
601
|
-
import("./azure-T4G6OBJQ.js"),
|
|
602
|
-
import("./local-DXBEVZ5C.js")
|
|
603
|
-
]);
|
|
664
|
+
const { getProvider } = await import("./providers-FW6H6OPF.js");
|
|
604
665
|
const checks = [];
|
|
605
666
|
const seen = /* @__PURE__ */ new Set();
|
|
606
667
|
for (const [stackName, stackCfg] of Object.entries(config.stacks)) {
|
|
@@ -664,14 +725,16 @@ async function versionChecks() {
|
|
|
664
725
|
}
|
|
665
726
|
async function remoteChecks(session, signal) {
|
|
666
727
|
const checks = [];
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
`docker inspect openclaw --format '{{.State.Status}}' 2>/dev/null || echo 'not found'`,
|
|
670
|
-
signal
|
|
671
|
-
);
|
|
672
|
-
const containerStatus = containerResult.stdout.trim();
|
|
728
|
+
const { containerStatus } = await import("./docker-3PDO4TS7.js");
|
|
729
|
+
const container = await containerStatus(session, "openclaw", signal);
|
|
673
730
|
checks.push(
|
|
674
|
-
|
|
731
|
+
container.error ? {
|
|
732
|
+
// "not found" would be a claim about the deployment. This is a claim about clawops.
|
|
733
|
+
name: "Container",
|
|
734
|
+
status: "fail",
|
|
735
|
+
detail: `could not ask docker \u2014 ${container.error}`,
|
|
736
|
+
remedy: "the SSH user may have been added to the docker group after this session opened"
|
|
737
|
+
} : container.status === "running" ? { name: "Container", status: "pass", detail: "running" } : { name: "Container", status: "fail", detail: container.status }
|
|
675
738
|
);
|
|
676
739
|
const imageResult = await execPrivileged(
|
|
677
740
|
session,
|
|
@@ -745,8 +808,8 @@ async function deployedVersionCheck(image, deployedVersion) {
|
|
|
745
808
|
}
|
|
746
809
|
async function hardeningChecks(conn, stack, signal) {
|
|
747
810
|
try {
|
|
748
|
-
const { MODULE_CATALOG, resolveModules, withRemoteExec } = await import("./harden-
|
|
749
|
-
const { buildContext } = await import("./context-
|
|
811
|
+
const { MODULE_CATALOG, resolveModules, withRemoteExec } = await import("./harden-72JMRT4E.js");
|
|
812
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
750
813
|
const provider = buildContext({ stack }).adapter.name;
|
|
751
814
|
const modules = resolveModules(MODULE_CATALOG, void 0, provider);
|
|
752
815
|
const checks = [];
|
|
@@ -769,9 +832,9 @@ async function hardeningChecks(conn, stack, signal) {
|
|
|
769
832
|
}
|
|
770
833
|
async function defaultOpenSession(stack, signal) {
|
|
771
834
|
const { getConfig } = await import("./store-SDUR52Z5.js");
|
|
772
|
-
const { buildContext } = await import("./context-
|
|
835
|
+
const { buildContext } = await import("./context-7EVC6W45.js");
|
|
773
836
|
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
774
|
-
const { acquireSession } = await import("./pool-
|
|
837
|
+
const { acquireSession } = await import("./pool-PIIKJ4L2.js");
|
|
775
838
|
const config = getConfig();
|
|
776
839
|
if (!config) throw new Error("no config \u2014 run `clawops init`");
|
|
777
840
|
const ctx = buildContext({ stack });
|
|
@@ -799,6 +862,36 @@ async function defaultOpenSession(stack, signal) {
|
|
|
799
862
|
function messageOf(err) {
|
|
800
863
|
return err instanceof Error ? err.message : String(err);
|
|
801
864
|
}
|
|
865
|
+
async function accountChecks(config, stack, provider, instanceType) {
|
|
866
|
+
if (!config && !provider) return [];
|
|
867
|
+
const stackName = stack ?? config?.defaults?.stack;
|
|
868
|
+
const fromStack = stackName ? config?.stacks[stackName] : void 0;
|
|
869
|
+
const stackCfg = provider && fromStack?.provider !== provider ? Object.values(config?.stacks ?? {}).find((s) => s.provider === provider) : fromStack;
|
|
870
|
+
const providerName = provider ?? stackCfg?.provider;
|
|
871
|
+
if (!providerName || providerName === "local") return [];
|
|
872
|
+
try {
|
|
873
|
+
const { getProvider } = await import("./providers-FW6H6OPF.js");
|
|
874
|
+
const adapter = getProvider(providerName);
|
|
875
|
+
if (!adapter.preflight) return [];
|
|
876
|
+
const bucket = stackCfg ? bucketFromStateUrl(stackCfg.stateUrl) : void 0;
|
|
877
|
+
const results = await adapter.preflight({ region: stackCfg?.region, bucket, instanceType });
|
|
878
|
+
return results.map((r) => ({
|
|
879
|
+
name: r.label,
|
|
880
|
+
// A question clawops could not ask is not an answer about the account. Failing the
|
|
881
|
+
// report over a denied read permission would be as wrong as passing it.
|
|
882
|
+
status: r.unknown ? "warn" : r.ok ? "pass" : "fail",
|
|
883
|
+
detail: r.detail,
|
|
884
|
+
remedy: r.mutates ? `clawops setup can fix this: ${r.mutates}` : void 0
|
|
885
|
+
}));
|
|
886
|
+
} catch (err) {
|
|
887
|
+
return [{ name: "Account preflight", status: "warn", detail: messageOf(err) }];
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
function bucketFromStateUrl(stateUrl) {
|
|
891
|
+
if (!stateUrl) return void 0;
|
|
892
|
+
const match = /^[a-z0-9]+:\/\/([^/]+)/.exec(stateUrl);
|
|
893
|
+
return match?.[1];
|
|
894
|
+
}
|
|
802
895
|
|
|
803
896
|
export {
|
|
804
897
|
runDiagnostics,
|