@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.
Files changed (75) hide show
  1. package/README.md +98 -12
  2. package/dist/apply-KYFPWI5U.js +27 -0
  3. package/dist/automation-RMAIBNVC.js +44 -0
  4. package/dist/bedrock-XVASJX2K.js +61 -0
  5. package/dist/{bootstrap-DMRPWJGN.js → bootstrap-HNGKY6WW.js} +11 -8
  6. package/dist/{chunk-N44K3LHE.js → chunk-2I7KCMXZ.js} +6 -4
  7. package/dist/{chunk-3WYA2QDM.js → chunk-2Z7J3ERQ.js} +1 -1
  8. package/dist/chunk-3OLUJPCB.js +36 -0
  9. package/dist/chunk-5XT5IAJK.js +69 -0
  10. package/dist/chunk-AEEMRFZV.js +24 -0
  11. package/dist/{chunk-YR3O2I3S.js → chunk-BSLIYB5Q.js} +6 -1
  12. package/dist/{chunk-AHAAEXWC.js → chunk-E4NJSJ54.js} +16 -93
  13. package/dist/chunk-FEK7VJAY.js +16 -0
  14. package/dist/{chunk-6TIYVHPX.js → chunk-GIEVAGEK.js} +7 -33
  15. package/dist/chunk-HSPHXG6W.js +67 -0
  16. package/dist/{chunk-GYV4KNW7.js → chunk-IVWP4QUJ.js} +133 -40
  17. package/dist/{chunk-AX74VXTV.js → chunk-KC3GC3PE.js} +58 -49
  18. package/dist/{chunk-UFRO4H5W.js → chunk-LBE6COC4.js} +56 -40
  19. package/dist/chunk-LEVIW2PV.js +890 -0
  20. package/dist/chunk-MC5ATCXS.js +108 -0
  21. package/dist/chunk-N4WZLWJ4.js +16 -0
  22. package/dist/{chunk-SBLQH2TJ.js → chunk-NUL5F6YV.js} +1 -1
  23. package/dist/chunk-RHOWYIWT.js +52 -0
  24. package/dist/{chunk-WHJMKDV7.js → chunk-STLRHLLV.js} +65 -12
  25. package/dist/chunk-V25QRZRB.js +45 -0
  26. package/dist/chunk-VOEJYRIK.js +33 -0
  27. package/dist/{providers-2OABPW2E.js → chunk-VUFMZEIM.js} +3 -2
  28. package/dist/chunk-W3OLNNJW.js +51 -0
  29. package/dist/cli-WLD3EBEM.js +11 -0
  30. package/dist/cli-auth-GD5SK7JU.js +13 -0
  31. package/dist/cli.js +518 -206
  32. package/dist/context-7EVC6W45.js +20 -0
  33. package/dist/docker-3PDO4TS7.js +12 -0
  34. package/dist/{firewall-T3NTPZJD.js → firewall-5VKZ4WBH.js} +22 -1
  35. package/dist/generate-I6YQ5V2T.js +29 -0
  36. package/dist/{harden-PCDK3UQ5.js → harden-72JMRT4E.js} +8 -5
  37. package/dist/index.d.ts +56 -0
  38. package/dist/known-hosts-file-I3Q3NBYQ.js +22 -0
  39. package/dist/{logs-P7YCKVF4.js → logs-5A7YYA53.js} +1 -1
  40. package/dist/{mcp-wire-BFEY2Y6H.js → mcp-wire-3SWBM7D6.js} +25 -15
  41. package/dist/models-AGTQWYDL.js +32 -0
  42. package/dist/network-args-PYJRJBYO.js +64 -0
  43. package/dist/{package-3HUW57XH.js → package-5BCMEBTE.js} +10 -3
  44. package/dist/passphrase-MJBE45N7.js +15 -0
  45. package/dist/{pool-EX4ZTA5P.js → pool-PIIKJ4L2.js} +3 -2
  46. package/dist/preflight-EF52S3WW.js +162 -0
  47. package/dist/preflight-MWWM5JT3.js +181 -0
  48. package/dist/preflight-TFD4N5ZU.js +141 -0
  49. package/dist/providers-FW6H6OPF.js +11 -0
  50. package/dist/ready-KDNRBNSU.js +95 -0
  51. package/dist/{remote-config-Z6IIZR3D.js → remote-config-3HZLUWEA.js} +3 -2
  52. package/dist/{runtime-LZ7NA53V.js → runtime-X3KW5TCP.js} +4 -2
  53. package/dist/server-GBA7BJGK.js +46 -0
  54. package/dist/ssh-VAI5DU5M.js +11 -0
  55. package/dist/ssh-key-DUO6OKOI.js +7 -0
  56. package/dist/stack-config-R4SPSUQZ.js +12 -0
  57. package/dist/{validate-BOQRWX4F.js → validate-YVI3BJ55.js} +1 -1
  58. package/dist/wait-WO72TRRQ.js +79 -0
  59. package/package.json +10 -3
  60. package/spec/mcp-tools.yaml +21 -2
  61. package/spec/models.yaml +14 -3
  62. package/spec/providers.schema.json +163 -28
  63. package/dist/apply-AM5HERE7.js +0 -19
  64. package/dist/automation-RULUSJBW.js +0 -30
  65. package/dist/aws-AMON7DA7.js +0 -265
  66. package/dist/azure-T4G6OBJQ.js +0 -273
  67. package/dist/chunk-CKSPROD5.js +0 -129
  68. package/dist/chunk-LU2V3LXA.js +0 -114
  69. package/dist/context-KHKWJJMC.js +0 -10
  70. package/dist/gcp-2JBTD7ZM.js +0 -204
  71. package/dist/generate-2XV2GSP2.js +0 -15
  72. package/dist/local-DXBEVZ5C.js +0 -47
  73. package/dist/server-A5AB67GQ.js +0 -36
  74. package/dist/ssh-B3NZAU4I.js +0 -8
  75. 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(`SSH connection failed: ${err.message}`));
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
- err,
4
- ok
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 CONFIG_MOUNT_PATH2 = `${STATE_DIR_CONTAINER}/${CONFIG_FILENAME}`;
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 ADD_HOST_FLAG2 = "--add-host=host.docker.internal:host-gateway";
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
- ADD_HOST_FLAG2,
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
- CONFIG_MOUNT_PATH2 as CONFIG_MOUNT_PATH,
114
+ CONFIG_MOUNT_PATH,
141
115
  stateDirForOS,
142
116
  configPathForOS,
143
117
  ENV_FILE_PATH,
144
118
  SECURITY_FLAGS,
145
119
  SUPERVISOR_ENV,
146
- ADD_HOST_FLAG2 as ADD_HOST_FLAG,
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
- interpretProbe,
4
- probeCommand
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
- chalk,
11
- failure
12
- } from "./chunk-Q7NQY5HV.js";
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-6TIYVHPX.js";
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
- execPrivileged(
44
+ inspectContainer(
40
45
  session,
41
- `${DOCKER} inspect openclaw --format '{{.State.Status}}|{{.Config.Image}}|{{.State.StartedAt}}|{{.RestartCount}}' 2>/dev/null || echo 'not found|||0'`,
42
- signal
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-KHKWJJMC.js");
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-KHKWJJMC.js");
364
- const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
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-EX4ZTA5P.js");
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
- checks.push({ name: "SSH key", status: "pass", detail: keyPath });
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 credentialChecks(config) {
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-2OABPW2E.js");
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 containerResult = await execPrivileged(
668
- session,
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
- containerStatus === "running" ? { name: "Container", status: "pass", detail: "running" } : { name: "Container", status: "fail", detail: containerStatus || "unknown" }
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-PCDK3UQ5.js");
749
- const { buildContext } = await import("./context-KHKWJJMC.js");
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-KHKWJJMC.js");
835
+ const { buildContext } = await import("./context-7EVC6W45.js");
773
836
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
774
- const { acquireSession } = await import("./pool-EX4ZTA5P.js");
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,