@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
package/dist/cli.js CHANGED
@@ -1,23 +1,20 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- MCP_APPS,
4
- buildMcpEntry,
5
- writeAppConfigs
6
- } from "./chunk-OIGTOLB3.js";
7
2
  import {
8
3
  monitor_default,
9
4
  runDiagnostics
10
- } from "./chunk-GYV4KNW7.js";
5
+ } from "./chunk-IVWP4QUJ.js";
11
6
  import {
12
7
  GATEWAY_LOGS_PROBE,
13
8
  chooseLogSource,
14
9
  containerLogsCommand,
15
10
  gatewayLogsCommand
16
- } from "./chunk-SBLQH2TJ.js";
11
+ } from "./chunk-NUL5F6YV.js";
17
12
  import {
18
- interpretProbe,
19
- probeCommand
20
- } from "./chunk-LT4XMO5D.js";
13
+ MCP_APPS,
14
+ buildMcpEntry,
15
+ writeAppConfigs
16
+ } from "./chunk-OIGTOLB3.js";
17
+ import "./chunk-3OLUJPCB.js";
21
18
  import {
22
19
  execPrivileged,
23
20
  execPrivilegedWithInput,
@@ -26,6 +23,29 @@ import {
26
23
  import {
27
24
  resolveSpecDir
28
25
  } from "./chunk-JW5A7E3X.js";
26
+ import {
27
+ interpretProbe,
28
+ probeCommand
29
+ } from "./chunk-LT4XMO5D.js";
30
+ import "./chunk-LEVIW2PV.js";
31
+ import {
32
+ CONFIG_FILENAME,
33
+ CONTAINER_UID,
34
+ PUBLISH_INSPECT_CMD,
35
+ STATE_DIR_HOST_LINUX,
36
+ configPathForOS,
37
+ gatewayRunCommand,
38
+ publishForRestart
39
+ } from "./chunk-GIEVAGEK.js";
40
+ import {
41
+ GATEWAY_PORT,
42
+ IMAGE_INSPECT_CMD,
43
+ imageForRestart,
44
+ versionOf
45
+ } from "./chunk-VOEJYRIK.js";
46
+ import "./chunk-EI7CQQ7D.js";
47
+ import "./chunk-FEK7VJAY.js";
48
+ import "./chunk-RHOWYIWT.js";
29
49
  import {
30
50
  REPO_URL,
31
51
  chalk,
@@ -36,6 +56,7 @@ import {
36
56
  success,
37
57
  warn
38
58
  } from "./chunk-Q7NQY5HV.js";
59
+ import "./chunk-N4WZLWJ4.js";
39
60
  import {
40
61
  getConfig,
41
62
  getConfigDir,
@@ -46,30 +67,95 @@ import {
46
67
  ClawopsError,
47
68
  UsageError
48
69
  } from "./chunk-KGXPLI7W.js";
49
- import {
50
- CONFIG_FILENAME,
51
- CONTAINER_UID,
52
- GATEWAY_PORT,
53
- IMAGE_INSPECT_CMD,
54
- PUBLISH_INSPECT_CMD,
55
- STATE_DIR_HOST_LINUX,
56
- configPathForOS,
57
- gatewayRunCommand,
58
- imageForRestart,
59
- publishForRestart,
60
- versionOf
61
- } from "./chunk-6TIYVHPX.js";
62
- import "./chunk-EI7CQQ7D.js";
70
+ import "./chunk-VUFMZEIM.js";
63
71
 
64
72
  // src/cli/index.ts
65
73
  import { defineCommand as defineCommand24, runMain } from "citty";
66
74
 
67
75
  // src/cli/commands/init.ts
68
76
  import { defineCommand } from "citty";
69
- import { generateKeyPairSync } from "crypto";
77
+ import { spawnSync } from "child_process";
70
78
  import { writeFileSync, mkdirSync, existsSync } from "fs";
71
79
  import path from "path";
72
80
  import process2 from "process";
81
+
82
+ // src/providers/state-bucket.ts
83
+ function deriveStateBucket(provider, scope) {
84
+ if (provider === "azure") {
85
+ return { ok: true, name: "clawops-state" };
86
+ }
87
+ if (!scope.account) {
88
+ return {
89
+ ok: false,
90
+ needs: provider === "aws" ? "an AWS account id \u2014 set AWS_PROFILE, or run `aws sso login --profile <name>`" : "a GCP project \u2014 run `gcloud config set project <id>`, or set GOOGLE_CLOUD_PROJECT"
91
+ };
92
+ }
93
+ if (provider === "gcp") return { ok: true, name: `clawops-state-${scope.account}` };
94
+ if (!scope.region) return { ok: false, needs: "a region" };
95
+ return { ok: true, name: `clawops-state-${scope.account}-${scope.region}` };
96
+ }
97
+ function stateUrlFor(provider, bucket) {
98
+ const verdict = validateStateBucket(provider, bucket);
99
+ if (verdict !== true) throw new Error(`Not a usable ${provider} state backend name: ${verdict}`);
100
+ const scheme = provider === "aws" ? "s3://" : provider === "gcp" ? "gs://" : "azblob://";
101
+ return provider === "azure" ? `${scheme}${bucket}` : `${scheme}${bucket}/clawops`;
102
+ }
103
+ var IPV4 = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
104
+ function validateStateBucket(provider, name) {
105
+ const n = name.trim();
106
+ if (n === "") return "Required";
107
+ if (n !== name) return "No leading or trailing spaces";
108
+ if (n.length < 3) return `Too short \u2014 ${labelFor(provider)} names are at least 3 characters`;
109
+ const dotted = provider === "gcp" && n.includes(".");
110
+ const max = dotted ? 222 : 63;
111
+ if (n.length > max) return `Too long \u2014 ${labelFor(provider)} names are at most ${max} characters`;
112
+ if (dotted && n.split(".").some((part) => part.length > 63)) {
113
+ return "Each dot-separated part of a Cloud Storage name is at most 63 characters";
114
+ }
115
+ if (n !== n.toLowerCase()) return "Lowercase only";
116
+ if (provider === "azure") {
117
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(n)) {
118
+ return "Letters, digits and single hyphens only, starting and ending with a letter or digit";
119
+ }
120
+ return true;
121
+ }
122
+ if (!/^[a-z0-9][a-z0-9._-]*[a-z0-9]$/.test(n)) {
123
+ return "Letters, digits, dots, hyphens and underscores only, starting and ending with a letter or digit";
124
+ }
125
+ if (n.includes("..")) return "No consecutive dots";
126
+ if (IPV4.test(n)) return "Cannot be formatted as an IP address";
127
+ if (provider === "aws") {
128
+ if (n.startsWith("xn--")) return 'Cannot start with "xn--"';
129
+ if (n.startsWith("sthree-")) return 'Cannot start with "sthree-"';
130
+ if (n.endsWith("-s3alias")) return 'Cannot end with "-s3alias"';
131
+ if (n.endsWith("--ol-s3")) return 'Cannot end with "--ol-s3"';
132
+ if (n.includes("_")) return "Underscores are not allowed in S3 bucket names";
133
+ return true;
134
+ }
135
+ if (n.startsWith("goog")) return 'Cannot start with "goog"';
136
+ if (n.includes("google")) return 'Cannot contain "google"';
137
+ return true;
138
+ }
139
+ function labelFor(provider) {
140
+ if (provider === "aws") return "S3 bucket";
141
+ if (provider === "gcp") return "Cloud Storage bucket";
142
+ return "blob container";
143
+ }
144
+ async function resolveScopeAccount(provider, signal) {
145
+ if (provider === "azure") return void 0;
146
+ try {
147
+ if (provider === "aws") {
148
+ const { callerAccount } = await import("./preflight-TFD4N5ZU.js");
149
+ return await callerAccount(signal);
150
+ }
151
+ const { resolveProjectId } = await import("./preflight-EF52S3WW.js");
152
+ return resolveProjectId();
153
+ } catch {
154
+ return void 0;
155
+ }
156
+ }
157
+
158
+ // src/cli/commands/init.ts
73
159
  var SUPPORTED_PROVIDERS = ["gcp", "aws", "azure", "local"];
74
160
  var PROVIDER_DEFAULTS = {
75
161
  gcp: { region: "us-central1", credEnv: "GOOGLE_APPLICATION_CREDENTIALS", stateScheme: "gs://" },
@@ -111,9 +197,9 @@ var init_default = defineCommand({
111
197
  );
112
198
  }
113
199
  const existing = getConfig();
114
- if (existing && !forceOverwrite && !nonInteractive) {
200
+ if (existing?.stacks[stackName] && !forceOverwrite) {
115
201
  failure(
116
- `Config already exists at ${path.join(getConfigDir(), "config.json")}. Use --force to overwrite.`
202
+ `Stack "${stackName}" already exists in ${path.join(getConfigDir(), "config.json")} (${existing.stacks[stackName].stateUrl}). Use --force to overwrite its settings, or pass a different --stack name to add another one.`
117
203
  );
118
204
  process2.exit(1);
119
205
  }
@@ -126,12 +212,21 @@ var init_default = defineCommand({
126
212
  throw new UsageError(`SSH key not found at ${keyPath}`);
127
213
  }
128
214
  info("Generating SSH key pair...");
129
- const { privateKey } = generateKeyPairSync("ed25519", {
130
- privateKeyEncoding: { type: "pkcs8", format: "pem" },
131
- publicKeyEncoding: { type: "spki", format: "pem" }
132
- });
133
- writeFileSync(keyPath, privateKey, { mode: 384 });
134
- success(`SSH private key written to ${keyPath}`);
215
+ const gen = spawnSync(
216
+ "ssh-keygen",
217
+ ["-t", "ed25519", "-f", keyPath, "-N", "", "-C", "clawops", "-q"],
218
+ { stdio: ["ignore", "pipe", "pipe"] }
219
+ );
220
+ if (gen.error || gen.status !== 0) {
221
+ const reason = gen.error?.message ?? gen.stderr?.toString().trim() ?? `exit ${gen.status}`;
222
+ throw new UsageError(
223
+ `Could not generate an SSH key: ${reason}
224
+ Generate one yourself and point clawops at it:
225
+ ssh-keygen -t ed25519 -f ${keyPath} -N '' -C clawops
226
+ clawops init --key-path ${keyPath}`
227
+ );
228
+ }
229
+ success(`SSH key pair written to ${keyPath} (and ${keyPath}.pub)`);
135
230
  } else {
136
231
  info(`Using existing SSH key at ${keyPath}`);
137
232
  }
@@ -146,43 +241,41 @@ var init_default = defineCommand({
146
241
  }
147
242
  const sshUser = typeof args["ssh-user"] === "string" ? args["ssh-user"] : "root";
148
243
  const sshPort = typeof args["ssh-port"] === "string" ? parseInt(args["ssh-port"], 10) : 22;
149
- config = {
150
- version: 1,
151
- defaults: { stack: stackName, provider },
152
- stacks: {
153
- [stackName]: {
154
- provider,
155
- stateUrl: "file://~/.clawops/state",
156
- credentialsRef: { source: "file", envVars: [] },
157
- localOpts: { host, sshUser, sshPort, sshKeyPath: keyPath }
158
- }
159
- },
160
- ssh: { keyPath, knownHostsPath }
161
- };
244
+ config = merge(existing, stackName, provider, keyPath, knownHostsPath, {
245
+ provider,
246
+ stateUrl: "file://~/.clawops/state",
247
+ credentialsRef: { source: "file", envVars: [] },
248
+ localOpts: { host, sshUser, sshPort, sshKeyPath: keyPath }
249
+ });
162
250
  } else {
163
251
  const defaults = PROVIDER_DEFAULTS[provider];
164
252
  const region = typeof args.region === "string" ? args.region : defaults.region;
165
- const stateUrl = typeof args.state === "string" ? args.state : `${defaults.stateScheme}CHANGEME/clawops`;
166
- config = {
167
- version: 1,
168
- defaults: { stack: stackName, provider },
169
- stacks: {
170
- [stackName]: {
171
- provider,
172
- stateUrl,
173
- region,
174
- credentialsRef: { source: "env", envVars: [defaults.credEnv] }
175
- }
176
- },
177
- ssh: { keyPath, knownHostsPath }
178
- };
179
- if (stateUrl.includes("CHANGEME")) {
180
- process2.stdout.write("\n");
181
- info(
182
- `Update stateUrl in the config to a real state backend before running \`clawops up\`.
183
- Example: clawops init --provider ${provider} --state ${defaults.stateScheme}your-bucket/clawops`
184
- );
253
+ let stateUrl;
254
+ if (typeof args.state === "string") {
255
+ stateUrl = args.state;
256
+ } else {
257
+ const account = await resolveScopeAccount(provider);
258
+ const derived = deriveStateBucket(provider, { account, region });
259
+ if (!derived.ok) {
260
+ throw new UsageError(
261
+ `Could not name a state backend for ${provider}: clawops needs ${derived.needs}.
262
+ Authenticate and run this again, or name the backend yourself:
263
+ clawops init --provider ${provider} --state ${defaults.stateScheme}your-bucket/clawops`
264
+ );
265
+ }
266
+ stateUrl = stateUrlFor(provider, derived.name);
185
267
  }
268
+ config = merge(existing, stackName, provider, keyPath, knownHostsPath, {
269
+ provider,
270
+ stateUrl,
271
+ region,
272
+ credentialsRef: { source: "env", envVars: [defaults.credEnv] }
273
+ });
274
+ process2.stdout.write("\n");
275
+ info(
276
+ `State backend: ${stateUrl}
277
+ clawops doctor --provider ${provider} checks it exists, and clawops setup offers to create it.`
278
+ );
186
279
  process2.stdout.write("\n");
187
280
  success(`Provider: ${provider} Region: ${region} Stack: ${stackName}`);
188
281
  setConfig(config);
@@ -195,6 +288,15 @@ var init_default = defineCommand({
195
288
  success(`Provider: ${provider} Stack: ${stackName}`);
196
289
  }
197
290
  });
291
+ function merge(existing, stackName, provider, keyPath, knownHostsPath, stack) {
292
+ return {
293
+ ...existing ?? {},
294
+ version: 1,
295
+ defaults: { stack: stackName, provider },
296
+ stacks: { ...existing?.stacks ?? {}, [stackName]: stack },
297
+ ssh: { keyPath, knownHostsPath }
298
+ };
299
+ }
198
300
 
199
301
  // src/cli/commands/up.ts
200
302
  import { defineCommand as defineCommand2 } from "citty";
@@ -217,7 +319,6 @@ function renderTable(headers, rows) {
217
319
  }
218
320
 
219
321
  // src/cli/commands/up.ts
220
- var VALID_INSTANCE_TYPES = ["micro", "small", "medium", "large", "gpu"];
221
322
  var up_default = defineCommand2({
222
323
  meta: {
223
324
  name: "up",
@@ -226,17 +327,20 @@ var up_default = defineCommand2({
226
327
  args: {
227
328
  provider: { type: "string", description: "Cloud provider (gcp|aws|azure|local)" },
228
329
  region: { type: "string", description: "Cloud region" },
229
- "instance-type": { type: "string", description: "Instance size alias (micro|small|medium|large|gpu)" },
330
+ "instance-type": { type: "string", description: "Instance size: a clawops alias (micro|small|medium|large|gpu) or a type your cloud names itself, e.g. t3.small" },
230
331
  "dry-run": { type: "boolean", description: "Preview without applying" },
231
332
  "no-wait": { type: "boolean", description: "Return immediately without waiting for healthy state" },
232
333
  "openclaw-version": { type: "string", description: "OpenClaw release (e.g. 2026.7.1-2). Moving tags are resolved and range-checked" },
233
334
  stack: { type: "string", description: "Target stack name" },
234
335
  config: { type: "string", description: "Path to openclaw config overlay JSON (local provider only)" },
235
- "gateway-port": { type: "string", description: `Host port to publish the gateway on (default ${GATEWAY_PORT}, local provider only \u2014 cloud stacks set spec.network.gatewayPort in the plan)` }
336
+ "gateway-port": { type: "string", description: `Host port to publish the gateway on (default ${GATEWAY_PORT})` },
337
+ "ssh-cidr": { type: "string", description: "CIDR(s) allowed to reach SSH, comma-separated, or 'auto' for this machine's IP. Omitted = none, and nothing will be able to connect" },
338
+ "gateway-cidr": { type: "string", description: "CIDR(s) allowed to reach the gateway port, or 'auto'. Requires --publish-gateway all" },
339
+ "publish-gateway": { type: "string", description: 'loopback (default) or all. "all" serves plaintext HTTP \u2014 put TLS in front of it' }
236
340
  },
237
341
  async run({ args }) {
238
- const { buildContext } = await import("./context-KHKWJJMC.js");
239
- const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-GPSTCPT7.js");
342
+ const { buildContext } = await import("./context-7EVC6W45.js");
343
+ const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
240
344
  const ctx = buildContext(args);
241
345
  const requestedVersion = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : await defaultOpenclawVersion();
242
346
  const openclawVersion = await guardOpenclawVersion(requestedVersion);
@@ -248,11 +352,11 @@ var up_default = defineCommand2({
248
352
  );
249
353
  }
250
354
  const { localOpts } = stackConfig;
251
- const { localBootstrap } = await import("./bootstrap-DMRPWJGN.js");
355
+ const { localBootstrap } = await import("./bootstrap-HNGKY6WW.js");
252
356
  const gatewayPort = parseGatewayPort(args["gateway-port"]);
253
- const abortController = new AbortController();
254
- process3.on("SIGINT", () => abortController.abort());
255
- process3.on("SIGTERM", () => abortController.abort());
357
+ const abortController2 = new AbortController();
358
+ process3.on("SIGINT", () => abortController2.abort());
359
+ process3.on("SIGTERM", () => abortController2.abort());
256
360
  const spin2 = spinner(`Bootstrapping local host "${localOpts.host}"...`);
257
361
  try {
258
362
  const state = await localBootstrap({
@@ -265,7 +369,7 @@ var up_default = defineCommand2({
265
369
  stackName: ctx.stackName,
266
370
  gatewayPort,
267
371
  noWait: Boolean(args["no-wait"]),
268
- signal: abortController.signal
372
+ signal: abortController2.signal
269
373
  });
270
374
  spin2.succeed(`Host "${localOpts.host}" bootstrapped`);
271
375
  info(`Gateway URL: ${state.gatewayUrl}`);
@@ -278,7 +382,7 @@ var up_default = defineCommand2({
278
382
  user: localOpts.sshUser,
279
383
  privateKeyPath: localOpts.sshKeyPath,
280
384
  knownHostsPath: ctx.config.ssh.knownHostsPath,
281
- signal: abortController.signal
385
+ signal: abortController2.signal
282
386
  });
283
387
  info("Config overlay applied and gateway restarted.");
284
388
  }
@@ -288,64 +392,90 @@ var up_default = defineCommand2({
288
392
  }
289
393
  return;
290
394
  }
291
- const instanceAlias = typeof args["instance-type"] === "string" ? args["instance-type"] : "small";
292
- if (!VALID_INSTANCE_TYPES.includes(instanceAlias)) {
293
- throw new UsageError(
294
- `Invalid --instance-type: ${instanceAlias}. Valid values: ${VALID_INSTANCE_TYPES.join(", ")}`
295
- );
296
- }
395
+ const { generatePlan } = await import("./generate-I6YQ5V2T.js");
396
+ const { applyPlan } = await import("./apply-KYFPWI5U.js");
397
+ const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
398
+ const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
297
399
  const isDryRun = Boolean(args["dry-run"]);
298
400
  const validation = await ctx.adapter.validateConfig();
299
401
  if (!validation.ok) {
300
402
  for (const e of validation.errors) failure(e);
301
403
  process3.exit(3);
302
404
  }
303
- const stack = await ctx.getStack();
304
- const region = typeof args.region === "string" ? args.region : ctx.adapter.defaultRegion();
305
- const instanceType = ctx.adapter.normalizeInstanceType(
306
- instanceAlias
307
- );
308
- await stack.setConfig("region", { value: region });
309
- await stack.setConfig("instanceType", { value: instanceType });
310
- await stack.setConfig("openclawVersion", { value: openclawVersion });
311
- if (isDryRun) {
312
- info("Previewing changes (--dry-run)...");
313
- const preview = await stack.preview({ onOutput: (out) => process3.stdout.write(out) });
314
- process3.stdout.write("\n");
315
- if (preview.changeSummary) {
316
- const rows = Object.entries(preview.changeSummary).filter(([, count]) => count > 0).map(([op, count]) => [op, String(count)]);
317
- if (rows.length > 0) {
318
- process3.stdout.write(renderTable(["Operation", "Count"], rows) + "\n");
319
- }
320
- }
321
- success("Preview complete (no resources changed)");
322
- return;
405
+ if (typeof args.config === "string") {
406
+ info("--config applies to local stacks only; put the overlay in the plan for a cloud stack.");
323
407
  }
408
+ const abortController = new AbortController();
409
+ process3.on("SIGINT", () => abortController.abort());
410
+ process3.on("SIGTERM", () => abortController.abort());
411
+ const network = await resolveNetworkFlags(
412
+ {
413
+ sshCidr: strArg(args["ssh-cidr"]),
414
+ gatewayCidr: strArg(args["gateway-cidr"]),
415
+ publishGateway: strArg(args["publish-gateway"])
416
+ },
417
+ { detectEgressIp: () => detectEgressIp("https://ifconfig.me/ip") }
418
+ );
419
+ const cloudGatewayPort = strArg(args["gateway-port"]);
420
+ if (cloudGatewayPort) network.gatewayPort = parseGatewayPort(cloudGatewayPort);
324
421
  const spin = spinner(`Deploying stack "${ctx.stackName}"...`);
325
422
  try {
326
- const result = await stack.up({
327
- onOutput: (out) => {
328
- spin.text = out.trim() || spin.text;
423
+ const plan = await generatePlan(
424
+ {
425
+ stackName: ctx.stackName,
426
+ provider: ctx.adapter.name,
427
+ ...strArg(args.region) ? { region: strArg(args.region) } : {},
428
+ ...strArg(args["instance-type"]) ? { instanceType: strArg(args["instance-type"]) } : {},
429
+ openclawVersion,
430
+ network
431
+ },
432
+ { signal: abortController.signal }
433
+ );
434
+ if (isDryRun) {
435
+ spin.stop();
436
+ info("Previewing changes (--dry-run)...");
437
+ const diff = plan.diff;
438
+ if (diff && diff.totalChanges > 0) {
439
+ const rows = [
440
+ ["create", String(diff.create.length)],
441
+ ["update", String(diff.update.length)],
442
+ ["delete", String(diff.delete.length)]
443
+ ].filter(([, count]) => count !== "0");
444
+ process3.stdout.write(renderTable(["Operation", "Count"], rows) + "\n");
329
445
  }
446
+ success("Preview complete (no resources changed)");
447
+ return;
448
+ }
449
+ const result = await applyPlan(plan, {
450
+ onOutput: (line2) => {
451
+ spin.text = line2.trim() || spin.text;
452
+ },
453
+ onProgress: (line2) => {
454
+ const text = line2.trim();
455
+ if (!text) return;
456
+ spin.text = text;
457
+ if (!process3.stderr.isTTY) process3.stderr.write(`${text}
458
+ `);
459
+ },
460
+ signal: abortController.signal,
461
+ skipReadiness: Boolean(args["no-wait"])
330
462
  });
331
463
  spin.succeed(`Stack "${ctx.stackName}" deployed`);
332
- const outputs = result.outputs;
333
- if (outputs["publicIp"]) {
334
- info(`Public IP: ${outputs["publicIp"].value}`);
335
- }
336
- if (outputs["gatewayUrl"]) {
337
- info(`Gateway URL: ${outputs["gatewayUrl"].value}`);
338
- }
464
+ if (result.outputs["publicIp"]) info(`Public IP: ${String(result.outputs["publicIp"])}`);
465
+ if (result.outputs["gatewayUrl"]) info(`Gateway URL: ${String(result.outputs["gatewayUrl"])}`);
339
466
  } catch (err) {
340
467
  spin.fail("Deployment failed");
341
468
  throw err;
342
469
  }
343
470
  }
344
471
  });
472
+ function strArg(value) {
473
+ return typeof value === "string" ? value : void 0;
474
+ }
345
475
  async function applyLocalConfigOverlay(opts) {
346
- const { connect } = await import("./ssh-B3NZAU4I.js");
476
+ const { connect } = await import("./ssh-VAI5DU5M.js");
347
477
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
348
- const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-Z6IIZR3D.js");
478
+ const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-3HZLUWEA.js");
349
479
  let overlay;
350
480
  try {
351
481
  overlay = JSON.parse(readFileSync(opts.configPath, "utf-8"));
@@ -393,7 +523,7 @@ var down_default = defineCommand3({
393
523
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
394
524
  },
395
525
  async run({ args }) {
396
- const { buildContext } = await import("./context-KHKWJJMC.js");
526
+ const { buildContext } = await import("./context-7EVC6W45.js");
397
527
  const ctx = buildContext(args);
398
528
  if (args["dry-run"]) {
399
529
  info(`Dry run \u2014 would destroy stack "${ctx.stackName}"`);
@@ -457,7 +587,7 @@ var status_default = defineCommand4({
457
587
  json: { type: "boolean", description: "Emit JSON" }
458
588
  },
459
589
  async run({ args }) {
460
- const { buildContext } = await import("./context-KHKWJJMC.js");
590
+ const { buildContext } = await import("./context-7EVC6W45.js");
461
591
  const ctx = buildContext(args);
462
592
  if (ctx.adapter.name === "local") {
463
593
  const state = ctx.localState;
@@ -524,13 +654,18 @@ var plan_default = defineCommand5({
524
654
  provider: { type: "string", description: "Cloud provider (aws|gcp|azure)" },
525
655
  stack: { type: "string", description: "Target stack name" },
526
656
  region: { type: "string", description: "Cloud region" },
527
- "instance-type": { type: "string", description: "Instance size alias (micro|small|medium|large|gpu)" },
657
+ "instance-type": { type: "string", description: "Instance size: a clawops alias (micro|small|medium|large|gpu) or a type your cloud names itself, e.g. t3.small" },
528
658
  "openclaw-version": { type: "string", description: "semver or 'stable'/'dev'" },
659
+ "ssh-cidr": { type: "string", description: "CIDR(s) allowed to reach SSH, comma-separated, or 'auto' for this machine's IP. Omitted = none, and nothing will be able to connect" },
660
+ "gateway-cidr": { type: "string", description: "CIDR(s) allowed to reach the gateway port, comma-separated, or 'auto'. Requires --publish-gateway all" },
661
+ "publish-gateway": { type: "string", description: 'loopback (default) or all. "all" serves plaintext HTTP \u2014 put TLS in front of it' },
529
662
  out: { type: "string", description: "Write plan JSON to this absolute path (default: stdout)" }
530
663
  },
531
664
  async run({ args }) {
532
- const { buildContext } = await import("./context-KHKWJJMC.js");
533
- const { generatePlan } = await import("./generate-2XV2GSP2.js");
665
+ const { buildContext } = await import("./context-7EVC6W45.js");
666
+ const { generatePlan } = await import("./generate-I6YQ5V2T.js");
667
+ const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
668
+ const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
534
669
  const ctx = buildContext(args);
535
670
  if (ctx.adapter.name === "local") {
536
671
  throw new UsageError(
@@ -542,6 +677,14 @@ var plan_default = defineCommand5({
542
677
  if (outPath && !isAbsolute(outPath)) {
543
678
  throw new UsageError("--out path must be absolute (R7). Use an absolute path like /tmp/plan.json.");
544
679
  }
680
+ const network = await resolveNetworkFlags(
681
+ {
682
+ sshCidr: strArg2(args["ssh-cidr"]),
683
+ gatewayCidr: strArg2(args["gateway-cidr"]),
684
+ publishGateway: strArg2(args["publish-gateway"])
685
+ },
686
+ { detectEgressIp: () => detectEgressIp("https://ifconfig.me/ip") }
687
+ );
545
688
  const abortController = new AbortController();
546
689
  process7.on("SIGINT", () => abortController.abort());
547
690
  process7.on("SIGTERM", () => abortController.abort());
@@ -554,7 +697,8 @@ var plan_default = defineCommand5({
554
697
  provider,
555
698
  region: typeof args.region === "string" ? args.region : void 0,
556
699
  instanceType: typeof args["instance-type"] === "string" ? args["instance-type"] : void 0,
557
- openclawVersion: typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : void 0
700
+ openclawVersion: typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : void 0,
701
+ network
558
702
  },
559
703
  { signal: abortController.signal }
560
704
  );
@@ -604,6 +748,9 @@ Changes: ${create.length} to create, ${update.length} to update, ${del.length} t
604
748
  }
605
749
  }
606
750
  });
751
+ function strArg2(value) {
752
+ return typeof value === "string" ? value : void 0;
753
+ }
607
754
 
608
755
  // src/cli/commands/apply.ts
609
756
  import { defineCommand as defineCommand6 } from "citty";
@@ -621,8 +768,8 @@ var apply_default = defineCommand6({
621
768
  "dry-run": { type: "boolean", description: "Validate plan and show diff without applying" }
622
769
  },
623
770
  async run({ args }) {
624
- const { validatePlan } = await import("./validate-BOQRWX4F.js");
625
- const { applyPlan } = await import("./apply-AM5HERE7.js");
771
+ const { validatePlan } = await import("./validate-YVI3BJ55.js");
772
+ const { applyPlan } = await import("./apply-KYFPWI5U.js");
626
773
  const planPath = args._?.[0];
627
774
  if (!planPath) {
628
775
  throw new UsageError("Usage: clawops apply <plan.json>");
@@ -691,6 +838,16 @@ ${validation.errors.join("\n")}`);
691
838
  onOutput: (line2) => {
692
839
  spin.text = line2.trim() || spin.text;
693
840
  },
841
+ // A spinner renders nothing without a TTY, so a scripted or CI deploy sat silent
842
+ // through the minutes an image pull takes. These lines are rare — one per half
843
+ // minute at most — so they are cheap to print outright where nothing else will.
844
+ onProgress: (line2) => {
845
+ const text = line2.trim();
846
+ if (!text) return;
847
+ spin.text = text;
848
+ if (!process8.stderr.isTTY) process8.stderr.write(`${text}
849
+ `);
850
+ },
694
851
  signal: abortController.signal,
695
852
  confirmDrift: args.yes ? void 0 : async () => {
696
853
  spin.stop();
@@ -738,7 +895,7 @@ var destroy_default = defineCommand7({
738
895
  "dry-run": { type: "boolean", description: "Show what would be destroyed without destroying" }
739
896
  },
740
897
  async run({ args }) {
741
- const { buildContext } = await import("./context-KHKWJJMC.js");
898
+ const { buildContext } = await import("./context-7EVC6W45.js");
742
899
  const ctx = buildContext(args);
743
900
  if (ctx.adapter.name === "local") {
744
901
  throw new UsageError(
@@ -776,6 +933,7 @@ Pass --yes to proceed with destruction.
776
933
  const abortController = new AbortController();
777
934
  process9.on("SIGINT", () => abortController.abort());
778
935
  process9.on("SIGTERM", () => abortController.abort());
936
+ const doomedHost = await hostOf(stack, ctx);
779
937
  const spin = spinner(`Destroying stack "${ctx.stackName}"\u2026`);
780
938
  try {
781
939
  await stack.destroy({
@@ -789,8 +947,35 @@ Pass --yes to proceed with destruction.
789
947
  spin.fail("Destroy failed");
790
948
  throw err;
791
949
  }
950
+ if (doomedHost) {
951
+ const { forgetHost } = await import("./known-hosts-file-I3Q3NBYQ.js");
952
+ const removed = forgetHost(
953
+ expandHome(ctx.config.ssh.knownHostsPath),
954
+ doomedHost.host,
955
+ doomedHost.port
956
+ );
957
+ if (removed) info(`Forgot the host key for ${doomedHost.host} \u2014 its instance is gone.`);
958
+ }
792
959
  }
793
960
  });
961
+ async function hostOf(stack, ctx) {
962
+ try {
963
+ const outputMap = await stack.outputs();
964
+ const raw = Object.fromEntries(Object.entries(outputMap).map(([k, v]) => [k, v.value]));
965
+ const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
966
+ const conn = ctx.adapter.getConnectionInfo({
967
+ ...extractBaseOutputs(raw),
968
+ privateKeyPath: ctx.config.ssh.keyPath,
969
+ knownHostsPath: ctx.config.ssh.knownHostsPath
970
+ });
971
+ return conn.host ? { host: conn.host, port: conn.port } : void 0;
972
+ } catch {
973
+ return void 0;
974
+ }
975
+ }
976
+ function expandHome(p) {
977
+ return p.replace(/^~/, process9.env["HOME"] ?? "~");
978
+ }
794
979
 
795
980
  // src/cli/commands/ssh.ts
796
981
  import { defineCommand as defineCommand8 } from "citty";
@@ -805,8 +990,8 @@ var ssh_default = defineCommand8({
805
990
  command: { type: "string", description: "Remote command to run (instead of interactive shell)" }
806
991
  },
807
992
  async run({ args }) {
808
- const { buildContext } = await import("./context-KHKWJJMC.js");
809
- const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
993
+ const { buildContext } = await import("./context-7EVC6W45.js");
994
+ const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
810
995
  const ctx = buildContext(args);
811
996
  let conn;
812
997
  if (ctx.adapter.name === "local") {
@@ -892,9 +1077,9 @@ var tunnel_default = defineCommand9({
892
1077
  "no-open": { type: "boolean", description: "Do not open browser after tunnel is ready" }
893
1078
  },
894
1079
  async run({ args }) {
895
- const { buildContext } = await import("./context-KHKWJJMC.js");
1080
+ const { buildContext } = await import("./context-7EVC6W45.js");
896
1081
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
897
- const { acquireSession } = await import("./pool-EX4ZTA5P.js");
1082
+ const { acquireSession } = await import("./pool-PIIKJ4L2.js");
898
1083
  const ctx = buildContext(args);
899
1084
  const stack = await ctx.getStack();
900
1085
  const outputMap = await stack.outputs();
@@ -961,9 +1146,9 @@ var logs_default = defineCommand10({
961
1146
  json: { type: "boolean", description: "Emit the gateway's structured JSON log lines" }
962
1147
  },
963
1148
  async run({ args }) {
964
- const { buildContext } = await import("./context-KHKWJJMC.js");
1149
+ const { buildContext } = await import("./context-7EVC6W45.js");
965
1150
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
966
- const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
1151
+ const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
967
1152
  const ctx = buildContext(args);
968
1153
  const stack = await ctx.getStack();
969
1154
  const outputMap = await stack.outputs();
@@ -997,8 +1182,8 @@ var logs_default = defineCommand10({
997
1182
  });
998
1183
  spin.stop();
999
1184
  try {
1000
- const probe = since ? { stdout: "" } : await execPrivileged(session, GATEWAY_LOGS_PROBE, abortController.signal);
1001
- const choice = chooseLogSource({ since, gatewayReachable: probe.stdout.trim() === "ok" });
1185
+ const probe = since ? { code: 1, stderr: "" } : await execPrivileged(session, GATEWAY_LOGS_PROBE, abortController.signal);
1186
+ const choice = chooseLogSource({ since, gatewayReachable: probe.code === 0 });
1002
1187
  const opts = { tail: tailLines, follow, since, json: Boolean(args.json) };
1003
1188
  const command = choice.source === "gateway" ? gatewayLogsCommand(opts) : containerLogsCommand(opts);
1004
1189
  info(`Logs: ${choice.source} \u2014 ${choice.reason}`);
@@ -1067,9 +1252,9 @@ var config_default = defineCommand11({
1067
1252
  "dry-run": { type: "boolean", description: "Show what would change without writing" }
1068
1253
  },
1069
1254
  async run({ args }) {
1070
- const { buildContext } = await import("./context-KHKWJJMC.js");
1255
+ const { buildContext } = await import("./context-7EVC6W45.js");
1071
1256
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1072
- const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
1257
+ const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1073
1258
  const [action, key, value] = args._ ?? [];
1074
1259
  if (!action || !["get", "set", "unset", "validate"].includes(action)) {
1075
1260
  failure("Usage: clawops config <get [key] | set key value | unset key | validate>");
@@ -1157,7 +1342,7 @@ var config_default = defineCommand11({
1157
1342
  process13.stdout.write(JSON.stringify(cfg, null, 2) + "\n");
1158
1343
  return;
1159
1344
  }
1160
- const { atomicWriteConfig } = await import("./remote-config-Z6IIZR3D.js");
1345
+ const { atomicWriteConfig } = await import("./remote-config-3HZLUWEA.js");
1161
1346
  try {
1162
1347
  await atomicWriteConfig(session, cfg, abortController.signal);
1163
1348
  } catch (err) {
@@ -1202,9 +1387,9 @@ var agents_default = defineCommand12({
1202
1387
  cursor: { type: "string", description: "Continue from a previous `logs` result cursor" }
1203
1388
  },
1204
1389
  async run({ args }) {
1205
- const { buildContext } = await import("./context-KHKWJJMC.js");
1390
+ const { buildContext } = await import("./context-7EVC6W45.js");
1206
1391
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1207
- const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
1392
+ const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1208
1393
  const [action, name] = args._ ?? [];
1209
1394
  if (action === "restart") {
1210
1395
  failure(
@@ -1277,7 +1462,7 @@ var agents_default = defineCommand12({
1277
1462
  );
1278
1463
  }
1279
1464
  } else {
1280
- const { agentAuditCommand } = await import("./logs-P7YCKVF4.js");
1465
+ const { agentAuditCommand } = await import("./logs-5A7YYA53.js");
1281
1466
  const limit = typeof args.limit === "string" ? parseInt(args.limit, 10) : 50;
1282
1467
  const result = await execPrivileged(
1283
1468
  session,
@@ -1434,10 +1619,10 @@ var migrate_default = defineCommand13({
1434
1619
  yes: { type: "boolean", description: "Skip the confirmation prompt" }
1435
1620
  },
1436
1621
  async run({ args }) {
1437
- const { buildContext } = await import("./context-KHKWJJMC.js");
1622
+ const { buildContext } = await import("./context-7EVC6W45.js");
1438
1623
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1439
- const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
1440
- const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-GPSTCPT7.js");
1624
+ const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1625
+ const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
1441
1626
  const requested = typeof args["openclaw-version"] === "string" ? args["openclaw-version"] : void 0;
1442
1627
  const target = requested ? await guardOpenclawVersion(requested) : await defaultOpenclawVersion();
1443
1628
  const targetImage = `ghcr.io/openclaw/openclaw:${target}`;
@@ -1573,9 +1758,9 @@ var gateway_default = defineCommand14({
1573
1758
  json: { type: "boolean", description: "Emit JSON (for status)" }
1574
1759
  },
1575
1760
  async run({ args }) {
1576
- const { buildContext } = await import("./context-KHKWJJMC.js");
1761
+ const { buildContext } = await import("./context-7EVC6W45.js");
1577
1762
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
1578
- const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
1763
+ const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1579
1764
  const [action, versionArg] = args._ ?? [];
1580
1765
  if (!action || !["status", "restart", "update"].includes(action)) {
1581
1766
  failure("Usage: clawops gateway <status | restart | update [version]>");
@@ -1606,12 +1791,24 @@ var gateway_default = defineCommand14({
1606
1791
  });
1607
1792
  try {
1608
1793
  if (action === "status") {
1609
- const statusCmd = `docker inspect openclaw --format '{"status":"{{.State.Status}}","started":"{{.State.StartedAt}}","image":"{{.Config.Image}}"}' 2>/dev/null || echo '{"status":"not running","started":"","image":""}'`;
1610
- const result = await session.exec(statusCmd, abortController.signal);
1794
+ const { inspectContainer } = await import("./docker-3PDO4TS7.js");
1795
+ const inspected = await inspectContainer(
1796
+ session,
1797
+ "openclaw",
1798
+ '{"status":"{{.State.Status}}","started":"{{.State.StartedAt}}","image":"{{.Config.Image}}"}',
1799
+ abortController.signal
1800
+ );
1611
1801
  let status = { status: "unknown", started: "", image: "" };
1612
- try {
1613
- status = JSON.parse(result.stdout.trim());
1614
- } catch {
1802
+ if (inspected.kind === "ok") {
1803
+ try {
1804
+ status = JSON.parse(inspected.value);
1805
+ } catch {
1806
+ }
1807
+ } else if (inspected.kind === "missing") {
1808
+ status = { status: "not running", started: "", image: "" };
1809
+ } else {
1810
+ failure(`Could not ask docker about the gateway: ${inspected.detail}`);
1811
+ process16.exit(1);
1615
1812
  }
1616
1813
  if (args.json) {
1617
1814
  printJson(jsonOk(status));
@@ -1650,7 +1847,7 @@ var gateway_default = defineCommand14({
1650
1847
  }
1651
1848
  success(`Gateway restarted (${version}).`);
1652
1849
  } else {
1653
- const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-GPSTCPT7.js");
1850
+ const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
1654
1851
  const requested = versionArg ?? args.channel;
1655
1852
  const version = requested ? await guardOpenclawVersion(requested) : await defaultOpenclawVersion();
1656
1853
  const spin = spinner(`Updating gateway to ${version}...`);
@@ -1793,8 +1990,8 @@ var backup_default = defineCommand15({
1793
1990
  yes: { type: "boolean", description: "[restore] Skip confirmation prompt" }
1794
1991
  },
1795
1992
  async run({ args }) {
1796
- const { buildContext } = await import("./context-KHKWJJMC.js");
1797
- const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
1993
+ const { buildContext } = await import("./context-7EVC6W45.js");
1994
+ const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
1798
1995
  const action = args.action;
1799
1996
  if (action !== "create" && action !== "restore") {
1800
1997
  throw new UsageError(`Unknown action: ${action}. Use "create" or "restore"`);
@@ -2004,7 +2201,7 @@ var stacks_default = defineCommand16({
2004
2201
  let isDeployed = false;
2005
2202
  if (!args.force) {
2006
2203
  try {
2007
- const { buildContext } = await import("./context-KHKWJJMC.js");
2204
+ const { buildContext } = await import("./context-7EVC6W45.js");
2008
2205
  const ctx = buildContext({ stack: name });
2009
2206
  if (ctx.adapter.name === "local") {
2010
2207
  isDeployed = !!ctx.localState;
@@ -2072,6 +2269,8 @@ var doctor_default = defineCommand17({
2072
2269
  },
2073
2270
  args: {
2074
2271
  stack: { type: "string", description: "Stack name to include remote health checks" },
2272
+ provider: { type: "string", description: "Check this provider's credentials and account setup, with or without a stack" },
2273
+ "instance-type": { type: "string", description: "Ask account checks about this machine size rather than the provider default" },
2075
2274
  json: { type: "boolean", description: "Emit the report as JSON" }
2076
2275
  },
2077
2276
  async run({ args }) {
@@ -2081,7 +2280,12 @@ var doctor_default = defineCommand17({
2081
2280
  process19.on("SIGTERM", abort);
2082
2281
  let report;
2083
2282
  try {
2084
- report = await runDiagnostics({ stack: args.stack, signal: ac.signal });
2283
+ report = await runDiagnostics({
2284
+ stack: args.stack,
2285
+ provider: typeof args.provider === "string" ? args.provider : void 0,
2286
+ instanceType: typeof args["instance-type"] === "string" ? args["instance-type"] : void 0,
2287
+ signal: ac.signal
2288
+ });
2085
2289
  } finally {
2086
2290
  process19.off("SIGINT", abort);
2087
2291
  process19.off("SIGTERM", abort);
@@ -2114,7 +2318,7 @@ ${section.title}
2114
2318
  process19.stdout.write("\n");
2115
2319
  }
2116
2320
  function line(check) {
2117
- const label = check.detail ? `${check.name.padEnd(NAME_COLUMN)}${check.detail}` : check.name;
2321
+ const label = check.detail ? `${check.name.padEnd(NAME_COLUMN)}${check.name.length >= NAME_COLUMN ? " " : ""}${check.detail}` : check.name;
2118
2322
  const emit = { pass: success, fail: failure, warn, info }[check.status];
2119
2323
  emit(label);
2120
2324
  if (check.remedy) info(` ${check.remedy}`);
@@ -2138,7 +2342,7 @@ var serveCmd = defineCommand18({
2138
2342
  inspector: { type: "boolean", description: "Enable MCP inspector" }
2139
2343
  },
2140
2344
  async run({ args }) {
2141
- const { serveMcp } = await import("./server-A5AB67GQ.js");
2345
+ const { serveMcp } = await import("./server-GBA7BJGK.js");
2142
2346
  await serveMcp({
2143
2347
  port: args.http ? Number(args.http) : void 0,
2144
2348
  bind: args.bind,
@@ -2216,11 +2420,11 @@ var wireCmd = defineCommand18({
2216
2420
  rewire: { type: "boolean", description: "Replace an existing clawops entry" }
2217
2421
  },
2218
2422
  async run({ args }) {
2219
- const { buildContext } = await import("./context-KHKWJJMC.js");
2220
- const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
2423
+ const { buildContext } = await import("./context-7EVC6W45.js");
2424
+ const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
2221
2425
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
2222
- const { wireGatewayMcp } = await import("./mcp-wire-BFEY2Y6H.js");
2223
- const { MCP_HTTP_PORT } = await import("./server-A5AB67GQ.js");
2426
+ const { wireGatewayMcp } = await import("./mcp-wire-3SWBM7D6.js");
2427
+ const { MCP_HTTP_PORT } = await import("./server-GBA7BJGK.js");
2224
2428
  const ac = new AbortController();
2225
2429
  process20.on("SIGINT", () => {
2226
2430
  ac.abort();
@@ -2321,7 +2525,7 @@ import process21 from "process";
2321
2525
  import os from "os";
2322
2526
  import { randomBytes } from "crypto";
2323
2527
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
2324
- import { execSync, spawnSync, spawn as spawn2 } from "child_process";
2528
+ import { execSync, spawnSync as spawnSync2, spawn as spawn2 } from "child_process";
2325
2529
  import path2 from "path";
2326
2530
  var setup_default = defineCommand19({
2327
2531
  meta: {
@@ -2433,6 +2637,14 @@ var setup_default = defineCommand19({
2433
2637
  }
2434
2638
  process21.stdout.write("\n");
2435
2639
  const sshCidrDefault = provider === "local" ? void 0 : await detectSshCidrDefault();
2640
+ const scopeAccount = provider === "local" ? void 0 : await resolveScopeAccount(provider);
2641
+ if (provider !== "local" && provider !== "azure" && !scopeAccount) {
2642
+ const needed = deriveStateBucket(provider, {});
2643
+ if (!needed.ok) {
2644
+ warn(`clawops cannot suggest a name for the state backend: it needs ${needed.needs}.`);
2645
+ info("Type the name of a bucket you already have, or quit, authenticate, and start again.\n");
2646
+ }
2647
+ }
2436
2648
  const stackAnswers = await inquirer.prompt([
2437
2649
  {
2438
2650
  type: "input",
@@ -2454,12 +2666,7 @@ var setup_default = defineCommand19({
2454
2666
  message: "Server size: (bigger = faster, but costs more per month)",
2455
2667
  choices: instanceChoices(provider)
2456
2668
  },
2457
- {
2458
- type: "input",
2459
- name: "stateBucket",
2460
- message: `${stateLabel(provider)} bucket name: (a storage bucket that tracks what's deployed \u2014 create one first if you haven't)`,
2461
- validate: (v) => v.trim() !== "" || "Required \u2014 create a bucket in your cloud console first"
2462
- },
2669
+ stateBucketQuestion(provider, scopeAccount),
2463
2670
  {
2464
2671
  type: "input",
2465
2672
  name: "sshKeyPath",
@@ -2485,6 +2692,18 @@ var setup_default = defineCommand19({
2485
2692
  default: "latest"
2486
2693
  }
2487
2694
  ]);
2695
+ if (provider !== "local") {
2696
+ await runAccountPreflight({
2697
+ provider,
2698
+ region: typeof stackAnswers.region === "string" ? stackAnswers.region : void 0,
2699
+ bucket: stackAnswers.stateBucket,
2700
+ // The size was chosen three questions ago. Azure offers SKU families per subscription,
2701
+ // so asking about the provider default here would clear a size the operator is not
2702
+ // deploying and say nothing about the one they are.
2703
+ instanceType: typeof stackAnswers.instanceSize === "string" ? stackAnswers.instanceSize : void 0,
2704
+ inquirer
2705
+ });
2706
+ }
2488
2707
  if (provider !== "local" && admitsInternet(stackAnswers.sshCidr)) {
2489
2708
  warn(
2490
2709
  "SSH will be reachable from anywhere on the internet. `clawops harden` reports this as a finding; narrowing it to your own IP is the usual fix."
@@ -2502,6 +2721,8 @@ var setup_default = defineCommand19({
2502
2721
  const modelProvider = catalogs.models.find((p) => p.id === modelProviderId);
2503
2722
  const modelConfig = {};
2504
2723
  const secrets = [];
2724
+ let resolvedModelId;
2725
+ let bedrockRegion;
2505
2726
  const { selectedModelId } = await inquirer.prompt([{
2506
2727
  type: "list",
2507
2728
  name: "selectedModelId",
@@ -2521,7 +2742,22 @@ var setup_default = defineCommand19({
2521
2742
  } else if (modelProvider.credentialSource === "aws-profile") {
2522
2743
  process21.stdout.write("\n");
2523
2744
  info(modelProvider.iamNote ?? "Bedrock uses your AWS server's IAM role \u2014 no API key needed.");
2524
- info("Make sure your EC2 instance has the AmazonBedrockFullAccess IAM policy attached.\n");
2745
+ bedrockRegion = typeof stackAnswers.region === "string" && stackAnswers.region ? stackAnswers.region : defaultRegion("aws");
2746
+ const { fetchInferenceProfiles, resolveInferenceProfile } = await import("./bedrock-XVASJX2K.js");
2747
+ const listed = await fetchInferenceProfiles(bedrockRegion);
2748
+ if (!listed.ok) {
2749
+ warn(listed.error);
2750
+ info("Continuing with the foundation-model id; Bedrock will refuse it until this is fixed.");
2751
+ } else {
2752
+ const wanted = selectedModel.modelId ?? selectedModel.id;
2753
+ const resolution = resolveInferenceProfile(wanted, listed.profiles, bedrockRegion);
2754
+ if (!resolution.ok) {
2755
+ const { UsageError: UsageError2 } = await import("./errors-OK47MQFD.js");
2756
+ throw new UsageError2(resolution.error);
2757
+ }
2758
+ resolvedModelId = resolution.profileId;
2759
+ info(`Using inference profile ${resolution.profileId} (${resolution.why}).`);
2760
+ }
2525
2761
  } else if (modelProvider.id === "ollama") {
2526
2762
  const { baseUrl } = await inquirer.prompt([{
2527
2763
  type: "input",
@@ -2535,11 +2771,15 @@ var setup_default = defineCommand19({
2535
2771
  }
2536
2772
  modelConfig["baseUrl"] = rewritten;
2537
2773
  }
2538
- const builtModelConfig = {
2539
- provider: modelProviderId,
2540
- ...selectedModel.modelId ? { modelId: selectedModel.modelId } : { model: selectedModel.id },
2541
- ...modelConfig
2542
- };
2774
+ const { buildModelsBlock } = await import("./models-AGTQWYDL.js");
2775
+ const builtModelConfig = buildModelsBlock({
2776
+ provider: modelProvider,
2777
+ model: { id: selectedModel.id, modelId: selectedModel.modelId, displayName: selectedModel.displayName },
2778
+ resolvedModelId,
2779
+ apiKeyRef: typeof modelConfig["apiKey"] === "string" ? modelConfig["apiKey"] : void 0,
2780
+ baseUrl: typeof modelConfig["baseUrl"] === "string" ? modelConfig["baseUrl"] : void 0,
2781
+ region: modelProvider.credentialSource === "aws-profile" ? bedrockRegion : void 0
2782
+ });
2543
2783
  process21.stdout.write("\n");
2544
2784
  info("Use \u2191\u2193 to move, Space to select/deselect, Enter to confirm.");
2545
2785
  const { selectedIntegrationIds } = await inquirer.prompt([{
@@ -2603,7 +2843,7 @@ var setup_default = defineCommand19({
2603
2843
  const gatewayToken = randomBytes(24).toString("hex");
2604
2844
  const secretsDir2 = path2.join(os.homedir(), ".clawops", "secrets");
2605
2845
  mkdirSync2(secretsDir2, { recursive: true });
2606
- spawnSync("chmod", ["700", secretsDir2], { stdio: "ignore" });
2846
+ spawnSync2("chmod", ["700", secretsDir2], { stdio: "ignore" });
2607
2847
  const tokenPath = path2.join(secretsDir2, `GATEWAY_TOKEN_${stackAnswers.stackName}`);
2608
2848
  writeFileSync3(tokenPath, gatewayToken, { encoding: "utf-8", mode: 384 });
2609
2849
  const openclawConfigOverlay = {
@@ -2784,7 +3024,7 @@ var setup_default = defineCommand19({
2784
3024
  default: false
2785
3025
  }]);
2786
3026
  if (applyNow) {
2787
- const { applyPlan } = await import("./apply-AM5HERE7.js");
3027
+ const { applyPlan } = await import("./apply-KYFPWI5U.js");
2788
3028
  const plan = JSON.parse(readFileSync3(outputPath, "utf-8"));
2789
3029
  try {
2790
3030
  await applyPlan(plan, {
@@ -2813,9 +3053,9 @@ To deploy later: clawops apply ${outputPath}`);
2813
3053
  });
2814
3054
  async function runLocalDeploy(opts) {
2815
3055
  const { setConfig: setConfig2, getConfig: getConfig2, getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
2816
- const { localBootstrap } = await import("./bootstrap-DMRPWJGN.js");
2817
- const { connect } = await import("./ssh-B3NZAU4I.js");
2818
- const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-Z6IIZR3D.js");
3056
+ const { localBootstrap } = await import("./bootstrap-HNGKY6WW.js");
3057
+ const { connect } = await import("./ssh-VAI5DU5M.js");
3058
+ const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-3HZLUWEA.js");
2819
3059
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
2820
3060
  const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
2821
3061
  const existing = getConfig2();
@@ -2896,7 +3136,7 @@ async function runLocalDeploy(opts) {
2896
3136
  spin2.fail("Configuration failed");
2897
3137
  throw err;
2898
3138
  }
2899
- const { drainPool } = await import("./pool-EX4ZTA5P.js");
3139
+ const { drainPool } = await import("./pool-PIIKJ4L2.js");
2900
3140
  drainPool();
2901
3141
  const tokenPath = path2.join(os.homedir(), ".clawops", "secrets", `GATEWAY_TOKEN_${opts.stackName}`);
2902
3142
  const dashboardUrl = `${state.gatewayUrl}?token=${opts.gatewayToken}`;
@@ -2956,7 +3196,7 @@ ${cfg.loginHint}`);
2956
3196
  }]);
2957
3197
  if (runNow) {
2958
3198
  process21.stdout.write("\n");
2959
- const result = spawnSync(cfg.loginCmd[0], cfg.loginCmd.slice(1), { stdio: "inherit" });
3199
+ const result = spawnSync2(cfg.loginCmd[0], cfg.loginCmd.slice(1), { stdio: "inherit" });
2960
3200
  if (result.error) {
2961
3201
  failure(`Could not launch ${cliName} \u2014 is it installed? (${result.error.message})`);
2962
3202
  info(`Install guide: ${cliInstallUrl(provider)}`);
@@ -3001,7 +3241,7 @@ async function promptSecret(name, label, envDefault, inquirer) {
3001
3241
  validate: (v) => v.trim() !== "" || "Value cannot be empty"
3002
3242
  }]);
3003
3243
  mkdirSync2(secretsDir2, { recursive: true });
3004
- spawnSync("chmod", ["700", secretsDir2], { stdio: "ignore" });
3244
+ spawnSync2("chmod", ["700", secretsDir2], { stdio: "ignore" });
3005
3245
  const secretPath2 = path2.join(secretsDir2, name);
3006
3246
  writeFileSync3(secretPath2, secretValue.trim(), { encoding: "utf-8", mode: 384 });
3007
3247
  success(`Secret saved to ${secretPath2} (chmod 600)`);
@@ -3034,7 +3274,7 @@ var DOCKER_CHECK_CMD = [
3034
3274
  "echo NOT_RUNNING"
3035
3275
  ].join("; ");
3036
3276
  async function checkDockerPreflight(opts) {
3037
- const { acquireSession } = await import("./pool-EX4ZTA5P.js");
3277
+ const { acquireSession } = await import("./pool-PIIKJ4L2.js");
3038
3278
  const { ProviderError } = await import("./errors-OK47MQFD.js");
3039
3279
  const spin = spinner("Checking Docker on target host...");
3040
3280
  const timeoutSignal = AbortSignal.timeout(DOCKER_PREFLIGHT_TIMEOUT_MS);
@@ -3081,7 +3321,7 @@ async function checkDockerPreflight(opts) {
3081
3321
  var DOCKER_POLL_MS = 3e3;
3082
3322
  var DOCKER_TIMEOUT_MS = 9e4;
3083
3323
  async function startRemoteDocker(opts, spin) {
3084
- const { acquireSession } = await import("./pool-EX4ZTA5P.js");
3324
+ const { acquireSession } = await import("./pool-PIIKJ4L2.js");
3085
3325
  const { ProviderError } = await import("./errors-OK47MQFD.js");
3086
3326
  spin.text = "Starting Docker on the remote server...";
3087
3327
  const { session, release } = await acquireSession({
@@ -3178,7 +3418,7 @@ async function startDockerAndWait(inquirer) {
3178
3418
  await pollUntilDockerReady("Waiting for Docker daemon...");
3179
3419
  } else if (choice === "systemctl") {
3180
3420
  const spin = spinner("Running: sudo systemctl start docker...");
3181
- const res = spawnSync("sudo", ["systemctl", "start", "docker"], { stdio: "pipe" });
3421
+ const res = spawnSync2("sudo", ["systemctl", "start", "docker"], { stdio: "pipe" });
3182
3422
  if (res.status !== 0) {
3183
3423
  spin.fail("systemctl start docker failed.");
3184
3424
  throw new Error("systemctl start docker failed");
@@ -3186,18 +3426,18 @@ async function startDockerAndWait(inquirer) {
3186
3426
  spin.succeed("Docker service started.");
3187
3427
  await pollUntilDockerReady("Waiting for Docker daemon to be reachable...");
3188
3428
  } else if (choice === "desktop") {
3189
- const backendRunning = spawnSync("pgrep", ["-f", "com.docker.backend"], { stdio: "ignore" }).status === 0;
3429
+ const backendRunning = spawnSync2("pgrep", ["-f", "com.docker.backend"], { stdio: "ignore" }).status === 0;
3190
3430
  if (backendRunning) {
3191
3431
  const killSpin = spinner("Docker Desktop is stuck \u2014 force-restarting...");
3192
3432
  for (const pat of ["com.docker.backend", "com.docker.virtualization", "com.docker.build", "Docker Desktop"]) {
3193
- spawnSync("pkill", ["-9", "-f", pat], { stdio: "ignore" });
3433
+ spawnSync2("pkill", ["-9", "-f", pat], { stdio: "ignore" });
3194
3434
  }
3195
3435
  await new Promise((r) => setTimeout(r, 2e3));
3196
3436
  killSpin.succeed("Old Docker processes cleared.");
3197
3437
  }
3198
3438
  const openSpin = spinner("Launching Docker Desktop...");
3199
- let opened = spawnSync("open", ["-a", "Docker"], { stdio: "ignore" });
3200
- if (opened.status !== 0) opened = spawnSync("open", ["/Applications/Docker.app"], { stdio: "ignore" });
3439
+ let opened = spawnSync2("open", ["-a", "Docker"], { stdio: "ignore" });
3440
+ if (opened.status !== 0) opened = spawnSync2("open", ["/Applications/Docker.app"], { stdio: "ignore" });
3201
3441
  if (opened.status !== 0) {
3202
3442
  openSpin.fail("Could not open Docker Desktop \u2014 is it installed in /Applications?");
3203
3443
  throw new Error("Failed to open Docker Desktop");
@@ -3206,7 +3446,7 @@ async function startDockerAndWait(inquirer) {
3206
3446
  await pollUntilDockerReady("Waiting for Docker engine to start...");
3207
3447
  } else if (choice === "colima") {
3208
3448
  const spin = spinner("Running: colima start...");
3209
- const res = spawnSync("colima", ["start"], { stdio: "pipe" });
3449
+ const res = spawnSync2("colima", ["start"], { stdio: "pipe" });
3210
3450
  if (res.status !== 0) {
3211
3451
  spin.fail("colima start failed \u2014 is Colima installed? (brew install colima docker)");
3212
3452
  throw new Error("colima start failed");
@@ -3239,7 +3479,7 @@ async function ensureAuthorizedKey(keyPath, host, inquirer) {
3239
3479
  mkdirSync2(path2.join(os.homedir(), ".ssh"), { recursive: true });
3240
3480
  const entry = existing.endsWith("\n") || existing === "" ? pubKey + "\n" : "\n" + pubKey + "\n";
3241
3481
  writeFileSync3(authorizedKeysPath, existing + entry, { encoding: "utf-8", flag: "a" });
3242
- spawnSync("chmod", ["600", authorizedKeysPath], { stdio: "ignore" });
3482
+ spawnSync2("chmod", ["600", authorizedKeysPath], { stdio: "ignore" });
3243
3483
  success("Public key added to ~/.ssh/authorized_keys.");
3244
3484
  } else {
3245
3485
  info("SSH will likely fail. Add this line to ~/.ssh/authorized_keys manually:");
@@ -3255,7 +3495,7 @@ async function ensureAuthorizedKey(keyPath, host, inquirer) {
3255
3495
  async function generateSshKey() {
3256
3496
  const keyPath = path2.join(os.homedir(), ".ssh", "id_ed25519");
3257
3497
  mkdirSync2(path2.join(os.homedir(), ".ssh"), { recursive: true });
3258
- const result = spawnSync("ssh-keygen", ["-t", "ed25519", "-f", keyPath, "-N", "", "-C", "clawops"], {
3498
+ const result = spawnSync2("ssh-keygen", ["-t", "ed25519", "-f", keyPath, "-N", "", "-C", "clawops"], {
3259
3499
  stdio: ["ignore", "pipe", "pipe"]
3260
3500
  });
3261
3501
  if (result.status !== 0) {
@@ -3286,9 +3526,9 @@ async function maybeWireGatewayMcp(opts) {
3286
3526
  process21.stdout.write("\n");
3287
3527
  const { wireGateway } = await inquirerPromptWire(opts.inquirer);
3288
3528
  if (!wireGateway) return;
3289
- const { connect } = await import("./ssh-B3NZAU4I.js");
3529
+ const { connect } = await import("./ssh-VAI5DU5M.js");
3290
3530
  const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
3291
- const { wireGatewayMcp } = await import("./mcp-wire-BFEY2Y6H.js");
3531
+ const { wireGatewayMcp } = await import("./mcp-wire-3SWBM7D6.js");
3292
3532
  const knownHostsPath = path2.join(getConfigDir2(), "known_hosts");
3293
3533
  const spin = spinner("Wiring gateway MCP client...");
3294
3534
  const session = await connect({
@@ -3362,6 +3602,21 @@ function defaultRegion(provider) {
3362
3602
  if (provider === "azure") return "eastus";
3363
3603
  return "";
3364
3604
  }
3605
+ function stateBucketQuestion(provider, account) {
3606
+ return {
3607
+ type: "input",
3608
+ name: "stateBucket",
3609
+ message: `${stateLabel(provider)} name: (where clawops records what it deployed \u2014 press Enter to accept)`,
3610
+ default: (answers) => {
3611
+ const derived = deriveStateBucket(provider, {
3612
+ account,
3613
+ region: answers.region ?? defaultRegion(provider)
3614
+ });
3615
+ return derived.ok ? derived.name : void 0;
3616
+ },
3617
+ validate: (v) => validateStateBucket(provider, v)
3618
+ };
3619
+ }
3365
3620
  function stateLabel(provider) {
3366
3621
  if (provider === "aws") return "S3";
3367
3622
  if (provider === "gcp") return "GCS";
@@ -3392,7 +3647,7 @@ function instanceChoices(provider) {
3392
3647
  return table[provider] ?? [];
3393
3648
  }
3394
3649
  async function maybeHarden(opts) {
3395
- const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-PCDK3UQ5.js");
3650
+ const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-72JMRT4E.js");
3396
3651
  const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
3397
3652
  const config = getConfig2();
3398
3653
  if (!config) return;
@@ -3416,7 +3671,7 @@ async function maybeHarden(opts) {
3416
3671
  const modules = availableModules.filter((m) => selectedIds.includes(m.id));
3417
3672
  process21.stdout.write("\nApplying hardening modules...\n\n");
3418
3673
  try {
3419
- const { buildContext } = await import("./context-KHKWJJMC.js");
3674
+ const { buildContext } = await import("./context-7EVC6W45.js");
3420
3675
  const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
3421
3676
  const ctx = buildContext({ stack: opts.stackName });
3422
3677
  const stackObj = await ctx.getStack();
@@ -3454,8 +3709,8 @@ async function maybeHarden(opts) {
3454
3709
  }
3455
3710
  }
3456
3711
  async function detectSshCidrDefault() {
3457
- const { detectEgressIp } = await import("./firewall-T3NTPZJD.js");
3458
- const result = await detectEgressIp("https://ifconfig.me");
3712
+ const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
3713
+ const result = await detectEgressIp("https://ifconfig.me/ip");
3459
3714
  if (!result.ok) return void 0;
3460
3715
  const ip = result.ip.trim();
3461
3716
  return ip === "" ? void 0 : ip.includes("/") ? ip : `${ip}/32`;
@@ -3479,11 +3734,68 @@ function wizardChannels(integrations) {
3479
3734
  function startChannelConfig(integ) {
3480
3735
  return { ...integ.defaults ?? {} };
3481
3736
  }
3737
+ async function runAccountPreflight(opts) {
3738
+ const { getProvider } = await import("./providers-FW6H6OPF.js");
3739
+ let checks;
3740
+ try {
3741
+ const adapter = getProvider(opts.provider);
3742
+ if (!adapter.preflight) return;
3743
+ checks = await adapter.preflight({
3744
+ region: opts.region,
3745
+ bucket: opts.bucket,
3746
+ instanceType: opts.instanceType
3747
+ });
3748
+ } catch (err) {
3749
+ warn(`Could not check the ${opts.provider} account: ${err instanceof Error ? err.message : String(err)}`);
3750
+ return;
3751
+ }
3752
+ const unanswered = checks.filter((c) => c.unknown);
3753
+ const failed = checks.filter((c) => !c.ok && !c.unknown);
3754
+ if (unanswered.length > 0) {
3755
+ process21.stdout.write("\n");
3756
+ for (const check of unanswered) {
3757
+ warn(`Could not check: ${check.label}`);
3758
+ if (check.detail) info(` ${check.detail}`);
3759
+ }
3760
+ }
3761
+ if (failed.length === 0) {
3762
+ success(
3763
+ unanswered.length > 0 ? `${opts.provider} account is ready, as far as clawops could tell.` : `${opts.provider} account is ready.`
3764
+ );
3765
+ process21.stdout.write("\n");
3766
+ return;
3767
+ }
3768
+ process21.stdout.write("\n");
3769
+ for (const check of failed) {
3770
+ warn(check.label);
3771
+ if (check.detail) info(` ${check.detail}`);
3772
+ if (!check.fix) {
3773
+ continue;
3774
+ }
3775
+ const { apply } = await opts.inquirer.prompt([{
3776
+ type: "confirm",
3777
+ name: "apply",
3778
+ message: `Fix this now? ${check.mutates}`,
3779
+ default: true
3780
+ }]);
3781
+ if (!apply) {
3782
+ info(" Left as is. The deploy will fail until this is resolved.");
3783
+ continue;
3784
+ }
3785
+ try {
3786
+ await check.fix();
3787
+ success(` Done \u2014 ${check.mutates}`);
3788
+ } catch (err) {
3789
+ failure(` Could not fix it: ${err instanceof Error ? err.message : String(err)}`);
3790
+ }
3791
+ }
3792
+ process21.stdout.write("\n");
3793
+ }
3482
3794
 
3483
3795
  // src/cli/commands/secret.ts
3484
3796
  import { defineCommand as defineCommand20 } from "citty";
3485
3797
  import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync4, unlinkSync, mkdirSync as mkdirSync3, statSync } from "fs";
3486
- import { spawnSync as spawnSync2 } from "child_process";
3798
+ import { spawnSync as spawnSync3 } from "child_process";
3487
3799
  import path3 from "path";
3488
3800
  import os2 from "os";
3489
3801
  import process22 from "process";
@@ -3560,7 +3872,7 @@ var setCmd = defineCommand20({
3560
3872
  value = secretValue;
3561
3873
  }
3562
3874
  mkdirSync3(secretsDir(), { recursive: true });
3563
- spawnSync2("chmod", ["700", secretsDir()], { stdio: "ignore" });
3875
+ spawnSync3("chmod", ["700", secretsDir()], { stdio: "ignore" });
3564
3876
  writeFileSync4(secretPath(name), value.trim(), { encoding: "utf-8", mode: 384 });
3565
3877
  success(`Secret "${name}" saved to ${secretPath(name)} (chmod 600)`);
3566
3878
  info("Run `clawops secret rotate <name>` to propagate it to a running stack.");
@@ -3631,7 +3943,7 @@ var rotateCmd = defineCommand20({
3631
3943
  value = secretValue;
3632
3944
  }
3633
3945
  mkdirSync3(secretsDir(), { recursive: true });
3634
- spawnSync2("chmod", ["700", secretsDir()], { stdio: "ignore" });
3946
+ spawnSync3("chmod", ["700", secretsDir()], { stdio: "ignore" });
3635
3947
  writeFileSync4(secretPath(name), value.trim(), { encoding: "utf-8", mode: 384 });
3636
3948
  success(`Secret "${name}" updated.`);
3637
3949
  const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
@@ -3651,10 +3963,10 @@ var rotateCmd = defineCommand20({
3651
3963
  }
3652
3964
  info(`Re-applying config overlay to stack "${targetStack}"\u2026`);
3653
3965
  try {
3654
- const { buildContext } = await import("./context-KHKWJJMC.js");
3655
- const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-Z6IIZR3D.js");
3966
+ const { buildContext } = await import("./context-7EVC6W45.js");
3967
+ const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-3HZLUWEA.js");
3656
3968
  const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
3657
- const { acquireSession, drainPool } = await import("./pool-EX4ZTA5P.js");
3969
+ const { acquireSession, drainPool } = await import("./pool-PIIKJ4L2.js");
3658
3970
  const { localStateToConnectionInfo } = await import("./state-PRBIIN7I.js");
3659
3971
  const ctx = buildContext({ stack: targetStack });
3660
3972
  let conn;
@@ -3838,7 +4150,7 @@ import { accessSync, constants } from "fs";
3838
4150
  import { execSync as execSync2 } from "child_process";
3839
4151
  var ISSUES_URL = `${REPO_URL}/issues/new`;
3840
4152
  var BODY_CAP = 2e3;
3841
- var VERSION = true ? "2.0.0" : "dev";
4153
+ var VERSION = true ? "2.0.1" : "dev";
3842
4154
  async function collectContext() {
3843
4155
  const nodeVersion = process23.version;
3844
4156
  const os3 = `${process23.platform} ${process23.arch}`;
@@ -3955,8 +4267,8 @@ var harden_default = defineCommand23({
3955
4267
  json: { type: "boolean", description: "Emit structured JSON output" }
3956
4268
  },
3957
4269
  async run({ args }) {
3958
- const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-PCDK3UQ5.js");
3959
- const { buildContext } = await import("./context-KHKWJJMC.js");
4270
+ const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary } = await import("./harden-72JMRT4E.js");
4271
+ const { buildContext } = await import("./context-7EVC6W45.js");
3960
4272
  const { getConfig: getConfig2 } = await import("./store-SDUR52Z5.js");
3961
4273
  if (args.list) {
3962
4274
  process24.stdout.write("\nAvailable hardening modules:\n\n");
@@ -4078,7 +4390,7 @@ function handleError(err) {
4078
4390
  }
4079
4391
 
4080
4392
  // src/cli/index.ts
4081
- var CLI_VERSION = true ? "2.0.0" : "dev";
4393
+ var CLI_VERSION = true ? "2.0.1" : "dev";
4082
4394
  var main = defineCommand24({
4083
4395
  meta: {
4084
4396
  name: "clawops",