@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
@@ -0,0 +1,108 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/transport/known-hosts.ts
4
+ import { createHmac, timingSafeEqual } from "crypto";
5
+ function hostEntryFor(host, port) {
6
+ return port === 22 ? host : `[${host}]:${port}`;
7
+ }
8
+ function parseKnownHosts(content) {
9
+ const entries = [];
10
+ for (const raw of content.split("\n")) {
11
+ const line = raw.trim();
12
+ if (!line || line.startsWith("#")) continue;
13
+ let parts = line.split(/\s+/);
14
+ let marker;
15
+ if (parts[0]?.startsWith("@")) {
16
+ marker = parts[0];
17
+ parts = parts.slice(1);
18
+ }
19
+ const hostField = parts[0];
20
+ if (!hostField) continue;
21
+ const hosts = hostField.split(",");
22
+ if (parts.length >= 3) {
23
+ entries.push({ hosts, keyType: parts[1], base64Key: parts[2], ...marker ? { marker } : {} });
24
+ } else if (parts.length === 2 && /^[0-9a-f]+$/i.test(parts[1] ?? "")) {
25
+ entries.push({ hosts, hexKey: parts[1], ...marker ? { marker } : {} });
26
+ }
27
+ }
28
+ return entries;
29
+ }
30
+ function hashedHostMatches(token, hostEntry) {
31
+ const parts = token.split("|");
32
+ if (parts.length !== 4 || parts[1] !== "1") return false;
33
+ const salt = parts[2];
34
+ const expected = parts[3];
35
+ if (!salt || !expected) return false;
36
+ try {
37
+ const actual = createHmac("sha1", Buffer.from(salt, "base64")).update(hostEntry).digest("base64");
38
+ const a = Buffer.from(actual);
39
+ const b = Buffer.from(expected);
40
+ return a.length === b.length && timingSafeEqual(a, b);
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+ function patternToRegExp(pattern) {
46
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*").replace(/\?/g, ".");
47
+ return new RegExp(`^${escaped}$`, "i");
48
+ }
49
+ var isPattern = (host) => host.includes("*") || host.includes("?");
50
+ function entryMatchesHost(entry, hostEntry) {
51
+ let matched = false;
52
+ for (const host of entry.hosts) {
53
+ if (host.startsWith("|1|")) {
54
+ if (hashedHostMatches(host, hostEntry)) matched = true;
55
+ continue;
56
+ }
57
+ const negated = host.startsWith("!");
58
+ const pattern = negated ? host.slice(1) : host;
59
+ const hit = isPattern(pattern) ? patternToRegExp(pattern).test(hostEntry) : pattern === hostEntry;
60
+ if (hit) {
61
+ if (negated) return false;
62
+ matched = true;
63
+ }
64
+ }
65
+ return matched;
66
+ }
67
+ function verifyAgainstKnownHosts(content, host, port, key) {
68
+ const hostEntry = hostEntryFor(host, port);
69
+ const base64Key = key.toString("base64");
70
+ const hexKey = key.toString("hex");
71
+ let sawHost = false;
72
+ for (const entry of parseKnownHosts(content)) {
73
+ if (!entryMatchesHost(entry, hostEntry)) continue;
74
+ if (entry.marker === "@cert-authority") continue;
75
+ sawHost = true;
76
+ const matches = entry.base64Key ? entry.base64Key === base64Key : entry.hexKey?.toLowerCase() === hexKey.toLowerCase();
77
+ if (matches) return entry.marker === "@revoked" ? "mismatch" : "match";
78
+ }
79
+ return sawHost ? "mismatch" : "unknown";
80
+ }
81
+ function withoutHost(content, host, port) {
82
+ const hostEntry = hostEntryFor(host, port);
83
+ const kept = [];
84
+ for (const raw of content.split("\n")) {
85
+ const [entry] = parseKnownHosts(raw);
86
+ if (entry && entryMatchesHost(entry, hostEntry)) continue;
87
+ kept.push(raw);
88
+ }
89
+ return kept.join("\n");
90
+ }
91
+ function formatKnownHostsLine(host, port, keyType, key) {
92
+ return `${hostEntryFor(host, port)} ${keyType} ${key.toString("base64")}
93
+ `;
94
+ }
95
+ function keyTypeFromBlob(key) {
96
+ if (key.length < 4) return void 0;
97
+ const len = key.readUInt32BE(0);
98
+ if (len === 0 || len > 64 || key.length < 4 + len) return void 0;
99
+ const type = key.subarray(4, 4 + len).toString("utf-8");
100
+ return /^[\x20-\x7e]+$/.test(type) ? type : void 0;
101
+ }
102
+
103
+ export {
104
+ verifyAgainstKnownHosts,
105
+ withoutHost,
106
+ formatKnownHostsLine,
107
+ keyTypeFromBlob
108
+ };
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/providers/aws/sizes.ts
4
+ var INSTANCE_TYPE_MAP = {
5
+ micro: "t3.micro",
6
+ small: "t3.small",
7
+ medium: "t3.medium",
8
+ large: "t3.large",
9
+ gpu: "g4dn.xlarge"
10
+ };
11
+ var DEFAULT_ALIAS = "small";
12
+
13
+ export {
14
+ INSTANCE_TYPE_MAP,
15
+ DEFAULT_ALIAS
16
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/openclaw/logs.ts
4
- var GATEWAY_LOGS_PROBE = "docker exec openclaw openclaw logs --limit 1 >/dev/null 2>&1 && echo ok || echo no";
4
+ var GATEWAY_LOGS_PROBE = "docker exec openclaw openclaw logs --limit 1 >/dev/null";
5
5
  function gatewayLogsCommand(opts) {
6
6
  return [
7
7
  "docker exec openclaw openclaw logs",
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/providers/azure/cli-auth.ts
4
+ import path from "path";
5
+ import process from "process";
6
+ import { readFileSync } from "fs";
7
+ function azureConfigDir() {
8
+ const explicit = process.env["AZURE_CONFIG_DIR"];
9
+ if (explicit) return explicit;
10
+ const home = process.env["HOME"] ?? process.env["USERPROFILE"];
11
+ return home ? path.join(home, ".azure") : void 0;
12
+ }
13
+ function accountFromProfile(body) {
14
+ let parsed;
15
+ try {
16
+ parsed = JSON.parse(body.replace(/^\uFEFF/, ""));
17
+ } catch {
18
+ return void 0;
19
+ }
20
+ const subscriptions = parsed?.subscriptions;
21
+ if (!Array.isArray(subscriptions)) return void 0;
22
+ const entries = subscriptions;
23
+ const chosen = entries.find((s) => s["isDefault"] === true) ?? entries[0];
24
+ if (!chosen) return void 0;
25
+ const id = typeof chosen["id"] === "string" ? chosen["id"] : void 0;
26
+ if (!id) return void 0;
27
+ const user = chosen["user"];
28
+ return {
29
+ subscriptionId: id,
30
+ name: typeof chosen["name"] === "string" ? chosen["name"] : id,
31
+ ...typeof user?.name === "string" ? { user: user.name } : {}
32
+ };
33
+ }
34
+ function azureCliAccount() {
35
+ const dir = azureConfigDir();
36
+ if (!dir) return void 0;
37
+ try {
38
+ return accountFromProfile(readFileSync(path.join(dir, "azureProfile.json"), "utf-8"));
39
+ } catch {
40
+ return void 0;
41
+ }
42
+ }
43
+ function resolveSubscriptionId() {
44
+ return process.env["ARM_SUBSCRIPTION_ID"] ?? process.env["AZURE_SUBSCRIPTION_ID"] ?? azureCliAccount()?.subscriptionId;
45
+ }
46
+
47
+ export {
48
+ azureConfigDir,
49
+ accountFromProfile,
50
+ azureCliAccount,
51
+ resolveSubscriptionId
52
+ };
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  validatePlan
4
- } from "./chunk-YR3O2I3S.js";
4
+ } from "./chunk-BSLIYB5Q.js";
5
5
  import {
6
- buildContext
7
- } from "./chunk-LU2V3LXA.js";
6
+ buildContext,
7
+ loadAdapterModule
8
+ } from "./chunk-5XT5IAJK.js";
8
9
  import {
9
10
  getConfig
10
11
  } from "./chunk-CX5SL5HP.js";
11
12
  import {
13
+ StateError,
12
14
  UsageError
13
15
  } from "./chunk-KGXPLI7W.js";
14
16
 
@@ -19,12 +21,16 @@ function parseDiff(lines) {
19
21
  const create = [];
20
22
  const update = [];
21
23
  const del = [];
24
+ const seen = /* @__PURE__ */ new Set();
22
25
  for (const line of lines) {
23
26
  const m = PREVIEW_LINE_RE.exec(line.trimStart());
24
27
  if (!m) continue;
25
28
  const [, op, resourceType, name] = m;
29
+ const urn = `urn:pulumi:::clawops::${resourceType}::${name ?? ""}`;
30
+ if (seen.has(`${op}${urn}`)) continue;
31
+ seen.add(`${op}${urn}`);
26
32
  const ref = {
27
- urn: `urn:pulumi:::clawops::${resourceType}::${name ?? ""}`,
33
+ urn,
28
34
  type: resourceType,
29
35
  ...name ? { name } : {}
30
36
  };
@@ -39,16 +45,33 @@ function parseDiff(lines) {
39
45
  totalChanges: create.length + update.length + del.length
40
46
  };
41
47
  }
48
+ var INSTANCE_ALIASES = ["micro", "small", "medium", "large", "gpu"];
49
+ function isInstanceAlias(value) {
50
+ return INSTANCE_ALIASES.includes(value);
51
+ }
52
+ async function resolveInstanceType(intent) {
53
+ const requested = intent.instanceType ?? "small";
54
+ if (!isInstanceAlias(requested)) {
55
+ process.stderr.write(
56
+ `[clawops] note: "${requested}" is not a clawops size (${INSTANCE_ALIASES.join(", ")}), so it is passed to ${intent.provider} as written.
57
+ `
58
+ );
59
+ return requested;
60
+ }
61
+ const adapter = await loadAdapterModule(intent.provider);
62
+ return adapter.normalizeInstanceType(requested);
63
+ }
64
+ var parseDiffForTest = parseDiff;
42
65
  async function generatePlan(intent, _opts) {
43
66
  if (intent.provider === "local") {
44
67
  throw new UsageError(
45
68
  "plan/apply is not supported for the local provider. Use `clawops up` directly."
46
69
  );
47
70
  }
48
- const { version } = await import("./package-3HUW57XH.js");
71
+ const { version } = await import("./package-5BCMEBTE.js");
49
72
  const config = getConfig();
50
- const instanceType = intent.instanceType ?? "small";
51
- const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-GPSTCPT7.js");
73
+ const instanceType = await resolveInstanceType(intent);
74
+ const { guardOpenclawVersion, defaultOpenclawVersion } = await import("./version-guard-XVYW5WFJ.js");
52
75
  const openclawVersion = await guardOpenclawVersion(
53
76
  intent.openclawVersion ?? await defaultOpenclawVersion()
54
77
  );
@@ -56,6 +79,13 @@ async function generatePlan(intent, _opts) {
56
79
  allowedSshCidrs: [],
57
80
  allowedGatewayCidrs: []
58
81
  };
82
+ const { resolvePublicKey } = await import("./ssh-key-DUO6OKOI.js");
83
+ const publicKey = config ? resolvePublicKey(expandHome(config.ssh.keyPath)) : void 0;
84
+ if (!publicKey) {
85
+ process.stderr.write(
86
+ "[clawops] warning: no SSH public key could be resolved" + (config ? ` from ${config.ssh.keyPath}` : " \u2014 no config file") + ". `clawops apply` will refuse this plan: the instance would have no way to admit anyone. Run `clawops doctor` to see why the key is unusable.\n"
87
+ );
88
+ }
59
89
  const stackName = intent.stackName;
60
90
  const region = intent.region ?? config?.stacks[stackName]?.region;
61
91
  const plan = {
@@ -74,15 +104,24 @@ async function generatePlan(intent, _opts) {
74
104
  instanceType,
75
105
  openclaw: { version: openclawVersion },
76
106
  network,
107
+ ...publicKey ? { ssh: { publicKey } } : {},
77
108
  ...intent.tags ? { tags: intent.tags } : {}
78
109
  }
79
110
  };
111
+ let stack;
80
112
  try {
81
113
  const ctx = buildContext({ stack: stackName, provider: intent.provider });
82
- const stack = await ctx.getStack();
83
- await stack.setConfig("instanceType", { value: instanceType });
84
- if (region) await stack.setConfig("region", { value: region });
85
- await stack.setConfig("openclawVersion", { value: openclawVersion });
114
+ stack = await ctx.getStack();
115
+ } catch (err) {
116
+ if (err instanceof UsageError) throw err;
117
+ throw new StateError(
118
+ `Cannot open the state backend for stack "${stackName}": ${messageOf(err)}
119
+ A plan is only as good as the state it was computed against, so this is not a plan clawops will write. Check the stateUrl in ~/.clawops/config.json, and that the bucket or container exists and your credentials can read it.`
120
+ );
121
+ }
122
+ try {
123
+ const { writeStackConfig } = await import("./stack-config-R4SPSUQZ.js");
124
+ await writeStackConfig(stack, plan);
86
125
  const outputLines = [];
87
126
  const preview = await stack.preview({
88
127
  onOutput: (line) => outputLines.push(line)
@@ -98,6 +137,7 @@ async function generatePlan(intent, _opts) {
98
137
  plan.metadata.stackVersion = info.version;
99
138
  }
100
139
  } catch (err) {
140
+ if (err instanceof UsageError) throw err;
101
141
  process.stderr.write(
102
142
  `[clawops] Warning: preview failed, diff section omitted: ${err instanceof Error ? err.message : String(err)}
103
143
  `
@@ -108,7 +148,7 @@ async function generatePlan(intent, _opts) {
108
148
  throw new Error(`Generated plan failed schema validation:
109
149
  ${validation.errors.join("\n")}`);
110
150
  }
111
- const { validatePlanConfig, validatePlanNetwork } = await import("./validate-BOQRWX4F.js");
151
+ const { validatePlanConfig, validatePlanNetwork } = await import("./validate-YVI3BJ55.js");
112
152
  const cfgCheck = await validatePlanConfig(plan);
113
153
  for (const w of cfgCheck.warnings) process.stderr.write(`[clawops] warning: ${w}
114
154
  `);
@@ -133,8 +173,21 @@ Fix spec.openclaw.config before applying \u2014 the gateway reads it verbatim.`
133
173
  function planId() {
134
174
  return randomUUID().slice(0, 8);
135
175
  }
176
+ function expandHome(p) {
177
+ return p.replace(/^~/, process.env["HOME"] ?? "~");
178
+ }
179
+ function messageOf(err) {
180
+ const raw = err instanceof Error ? err.message : String(err);
181
+ const lines = raw.split("\n").map((l) => l.trim()).filter((l) => l !== "");
182
+ const explained = lines.find((l) => /(^|\s)error:/i.test(l));
183
+ if (explained) return explained.replace(/^.*?error:\s*/i, "");
184
+ return lines[0] ?? raw;
185
+ }
136
186
 
137
187
  export {
188
+ INSTANCE_ALIASES,
189
+ isInstanceAlias,
190
+ parseDiffForTest,
138
191
  generatePlan,
139
192
  planId
140
193
  };
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/pulumi/passphrase.ts
4
+ import path from "path";
5
+ import process from "process";
6
+ import { randomBytes } from "crypto";
7
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from "fs";
8
+ var PASSPHRASE_FILE = "pulumi-passphrase";
9
+ function passphrasePath(configDir) {
10
+ return path.join(configDir, "secrets", PASSPHRASE_FILE);
11
+ }
12
+ function passphraseInEnvironment() {
13
+ return Boolean(
14
+ process.env["PULUMI_CONFIG_PASSPHRASE"] ?? process.env["PULUMI_CONFIG_PASSPHRASE_FILE"]
15
+ );
16
+ }
17
+ function passphraseStatus(configDir) {
18
+ if (passphraseInEnvironment()) return "environment";
19
+ return existsSync(passphrasePath(configDir)) ? "stored" : "absent";
20
+ }
21
+ function ensurePassphrase(configDir) {
22
+ if (passphraseInEnvironment()) return void 0;
23
+ const file = passphrasePath(configDir);
24
+ if (existsSync(file)) {
25
+ const stored = readFileSync(file, "utf-8").trim();
26
+ if (stored !== "") return stored;
27
+ }
28
+ const dir = path.dirname(file);
29
+ mkdirSync(dir, { recursive: true });
30
+ try {
31
+ chmodSync(dir, 448);
32
+ } catch {
33
+ }
34
+ const generated = randomBytes(32).toString("base64url");
35
+ writeFileSync(file, generated + "\n", { encoding: "utf-8", mode: 384 });
36
+ return generated;
37
+ }
38
+
39
+ export {
40
+ PASSPHRASE_FILE,
41
+ passphrasePath,
42
+ passphraseInEnvironment,
43
+ passphraseStatus,
44
+ ensurePassphrase
45
+ };
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
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
+ }
27
+
28
+ export {
29
+ GATEWAY_PORT,
30
+ IMAGE_INSPECT_CMD,
31
+ imageForRestart,
32
+ versionOf
33
+ };
@@ -17,8 +17,9 @@ function getProvider(name) {
17
17
  function listProviders() {
18
18
  return Array.from(registry.keys());
19
19
  }
20
+
20
21
  export {
22
+ registerProvider,
21
23
  getProvider,
22
- listProviders,
23
- registerProvider
24
+ listProviders
24
25
  };
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/pulumi/cli.ts
4
+ import path from "path";
5
+ import process from "process";
6
+ import { PulumiCommand } from "@pulumi/pulumi/automation";
7
+ function pulumiCliRoot(configDir) {
8
+ return path.join(configDir, ".pulumi-cli");
9
+ }
10
+ function versionOf(cmd) {
11
+ return cmd.version ? `v${cmd.version.toString()}` : "unknown version";
12
+ }
13
+ async function pulumiCliStatus(configDir) {
14
+ const root = pulumiCliRoot(configDir);
15
+ try {
16
+ return { kind: "managed", version: versionOf(await PulumiCommand.get({ root })), root };
17
+ } catch {
18
+ }
19
+ try {
20
+ return { kind: "path", version: versionOf(await PulumiCommand.get()) };
21
+ } catch {
22
+ return { kind: "missing" };
23
+ }
24
+ }
25
+ async function ensurePulumiCli(opts) {
26
+ const root = pulumiCliRoot(opts.configDir);
27
+ const status = await pulumiCliStatus(opts.configDir);
28
+ if (status.kind === "managed") return await PulumiCommand.get({ root });
29
+ if (status.kind === "path") return await PulumiCommand.get();
30
+ const announce = opts.onInstall ?? defaultAnnounce;
31
+ announce({ root });
32
+ try {
33
+ return await PulumiCommand.install({ root });
34
+ } catch (err) {
35
+ const reason = err instanceof Error ? err.message : String(err);
36
+ throw new Error(
37
+ `could not install the Pulumi CLI into ${root}: ${reason}
38
+ Install it yourself and clawops will use it: https://www.pulumi.com/docs/install/`
39
+ );
40
+ }
41
+ }
42
+ function defaultAnnounce({ root }) {
43
+ process.stderr.write(`Pulumi CLI not found \u2014 installing it into ${root} (one time).
44
+ `);
45
+ }
46
+
47
+ export {
48
+ pulumiCliRoot,
49
+ pulumiCliStatus,
50
+ ensurePulumiCli
51
+ };
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ ensurePulumiCli,
4
+ pulumiCliRoot,
5
+ pulumiCliStatus
6
+ } from "./chunk-W3OLNNJW.js";
7
+ export {
8
+ ensurePulumiCli,
9
+ pulumiCliRoot,
10
+ pulumiCliStatus
11
+ };
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ accountFromProfile,
4
+ azureCliAccount,
5
+ azureConfigDir,
6
+ resolveSubscriptionId
7
+ } from "./chunk-RHOWYIWT.js";
8
+ export {
9
+ accountFromProfile,
10
+ azureCliAccount,
11
+ azureConfigDir,
12
+ resolveSubscriptionId
13
+ };