@clawops/cli 2.0.0 → 2.0.2

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 +99 -12
  2. package/dist/apply-WOUTUUQH.js +27 -0
  3. package/dist/automation-UKRM3BD2.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-GYV4KNW7.js → chunk-5TXCUOZV.js} +133 -40
  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-LEVIW2PV.js +890 -0
  17. package/dist/chunk-MC5ATCXS.js +108 -0
  18. package/dist/chunk-N4WZLWJ4.js +16 -0
  19. package/dist/{chunk-SBLQH2TJ.js → chunk-NUL5F6YV.js} +1 -1
  20. package/dist/chunk-PSOSBJVO.js +51 -0
  21. package/dist/{chunk-AX74VXTV.js → chunk-RATEIAFU.js} +58 -49
  22. package/dist/chunk-RHOWYIWT.js +52 -0
  23. package/dist/{chunk-WHJMKDV7.js → chunk-SDZKCICH.js} +65 -12
  24. package/dist/chunk-V25QRZRB.js +45 -0
  25. package/dist/chunk-VOEJYRIK.js +33 -0
  26. package/dist/{providers-2OABPW2E.js → chunk-VUFMZEIM.js} +3 -2
  27. package/dist/chunk-WTZ2CT35.js +69 -0
  28. package/dist/{chunk-UFRO4H5W.js → chunk-ZMMRIWFS.js} +56 -40
  29. package/dist/cli-NH7VUNOU.js +11 -0
  30. package/dist/cli-auth-GD5SK7JU.js +13 -0
  31. package/dist/cli.js +518 -206
  32. package/dist/context-M4JBBIAV.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-J6MIJGII.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-NTGL5ITF.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-3A6BOALU.js} +11 -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-RWM2JI3D.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 +11 -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,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/index.js";
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
+ };
@@ -1,42 +1,50 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  applyPlan
4
- } from "./chunk-UFRO4H5W.js";
4
+ } from "./chunk-ZMMRIWFS.js";
5
+ import {
6
+ extractBaseOutputs
7
+ } from "./chunk-3QJBNAHW.js";
5
8
  import {
6
9
  atomicWriteConfig,
7
10
  readRemoteConfig,
8
11
  restartGateway
9
- } from "./chunk-N44K3LHE.js";
10
- import {
11
- extractBaseOutputs
12
- } from "./chunk-3QJBNAHW.js";
12
+ } from "./chunk-2I7KCMXZ.js";
13
13
  import {
14
14
  generatePlan
15
- } from "./chunk-WHJMKDV7.js";
15
+ } from "./chunk-SDZKCICH.js";
16
16
  import {
17
17
  validatePlan
18
- } from "./chunk-YR3O2I3S.js";
18
+ } from "./chunk-BSLIYB5Q.js";
19
19
  import {
20
20
  acquireSession,
21
21
  drainPool
22
- } from "./chunk-3WYA2QDM.js";
22
+ } from "./chunk-2Z7J3ERQ.js";
23
23
  import {
24
24
  buildContext
25
- } from "./chunk-LU2V3LXA.js";
25
+ } from "./chunk-WTZ2CT35.js";
26
26
  import {
27
27
  formatUptime,
28
28
  gatherSnapshot,
29
29
  runDiagnostics
30
- } from "./chunk-GYV4KNW7.js";
30
+ } from "./chunk-5TXCUOZV.js";
31
31
  import {
32
32
  GATEWAY_LOGS_PROBE,
33
33
  chooseLogSource,
34
34
  containerLogsCommand,
35
35
  gatewayLogsCommand
36
- } from "./chunk-SBLQH2TJ.js";
36
+ } from "./chunk-NUL5F6YV.js";
37
37
  import {
38
38
  execPrivileged
39
39
  } from "./chunk-NUDIHZEH.js";
40
+ import {
41
+ STATE_DIR_HOST_LINUX,
42
+ gatewayRunCommand
43
+ } from "./chunk-GIEVAGEK.js";
44
+ import {
45
+ IMAGE_INSPECT_CMD,
46
+ imageForRestart
47
+ } from "./chunk-VOEJYRIK.js";
40
48
  import {
41
49
  getConfig,
42
50
  getConfigDir
@@ -45,12 +53,6 @@ import {
45
53
  StateError,
46
54
  UsageError
47
55
  } from "./chunk-KGXPLI7W.js";
48
- import {
49
- IMAGE_INSPECT_CMD,
50
- STATE_DIR_HOST_LINUX,
51
- gatewayRunCommand,
52
- imageForRestart
53
- } from "./chunk-6TIYVHPX.js";
54
56
 
55
57
  // src/mcp/server.ts
56
58
  import { createServer } from "http";
@@ -222,7 +224,10 @@ var clawops_upSchema = z.object({
222
224
  stackName: z.string().optional(),
223
225
  provider: z.enum(["aws", "gcp", "azure", "local"]).optional(),
224
226
  region: z.string().optional(),
225
- instanceType: z.enum(["micro", "small", "medium", "large", "gpu"]).optional().default("small"),
227
+ instanceType: z.string().optional().default("small"),
228
+ sshCidr: z.string().optional(),
229
+ gatewayCidr: z.string().optional(),
230
+ publishGateway: z.enum(["loopback", "all"]).optional(),
226
231
  openclawVersion: z.string().optional(),
227
232
  dryRun: z.boolean().optional().default(false)
228
233
  });
@@ -799,7 +804,6 @@ function makeProgressEmitter(server, progressToken) {
799
804
  }
800
805
 
801
806
  // src/mcp/tools/cli/up.ts
802
- var VALID_INSTANCE_TYPES = ["micro", "small", "medium", "large", "gpu"];
803
807
  async function handleUp(input, server) {
804
808
  if (!input.dryRun) {
805
809
  const elicit = await server.server.elicitInput({
@@ -824,7 +828,7 @@ async function handleUp(input, server) {
824
828
  );
825
829
  }
826
830
  const { localOpts } = stackConfig;
827
- const { localBootstrap } = await import("./bootstrap-DMRPWJGN.js");
831
+ const { localBootstrap } = await import("./bootstrap-HNGKY6WW.js");
828
832
  const ac = new AbortController();
829
833
  const state = await localBootstrap({
830
834
  host: localOpts.host,
@@ -839,10 +843,6 @@ async function handleUp(input, server) {
839
843
  });
840
844
  return okText(JSON.stringify({ stack: ctx.stackName, ...state }, null, 2));
841
845
  }
842
- const instanceAlias = input.instanceType ?? "small";
843
- if (!VALID_INSTANCE_TYPES.includes(instanceAlias)) {
844
- throw new UsageError(`Invalid instanceType: ${instanceAlias}`);
845
- }
846
846
  const validation = await ctx.adapter.validateConfig();
847
847
  if (!validation.ok) {
848
848
  return errText(`Provider config invalid: ${validation.errors.join("; ")}`);
@@ -851,38 +851,47 @@ async function handleUp(input, server) {
851
851
  const progressToken = void 0;
852
852
  const emit = makeProgressEmitter(server, progressToken);
853
853
  startTask(taskId, `clawops_up stack=${ctx.stackName}`);
854
- const stack = await ctx.getStack();
855
- const region = input.region ?? ctx.adapter.defaultRegion();
856
- const instanceType = ctx.adapter.normalizeInstanceType(instanceAlias);
857
- await stack.setConfig("region", { value: region });
858
- await stack.setConfig("instanceType", { value: instanceType });
859
- await stack.setConfig("openclawVersion", { value: openclawVersion });
860
- if (input.dryRun) {
861
- const lines = [];
862
- await stack.preview({ onOutput: (o) => {
863
- emit(o.trim());
864
- lines.push(o);
865
- } });
866
- const output = lines.join("");
867
- updateTask(taskId, "completed", output);
868
- const { content } = trimForMcp(output, ctx.stackName);
869
- return okText(content);
870
- }
854
+ const { generatePlan: generatePlan2 } = await import("./generate-J6MIJGII.js");
855
+ const { applyPlan: applyPlan2 } = await import("./apply-WOUTUUQH.js");
856
+ const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
857
+ const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
858
+ const network = await resolveNetworkFlags(
859
+ {
860
+ ...input.sshCidr ? { sshCidr: input.sshCidr } : {},
861
+ ...input.gatewayCidr ? { gatewayCidr: input.gatewayCidr } : {},
862
+ ...input.publishGateway ? { publishGateway: input.publishGateway } : {}
863
+ },
864
+ { detectEgressIp: () => detectEgressIp("https://ifconfig.me/ip") }
865
+ );
871
866
  try {
867
+ const plan = await generatePlan2({
868
+ stackName: ctx.stackName,
869
+ provider: ctx.adapter.name,
870
+ ...input.region ? { region: input.region } : {},
871
+ ...input.instanceType ? { instanceType: input.instanceType } : {},
872
+ openclawVersion,
873
+ network
874
+ });
875
+ if (input.dryRun) {
876
+ const summary2 = JSON.stringify(plan.diff ?? { totalChanges: 0 }, null, 2);
877
+ updateTask(taskId, "completed", summary2);
878
+ const { content } = trimForMcp(summary2, ctx.stackName);
879
+ return okText(content);
880
+ }
872
881
  const lines = [];
873
- const result = await stack.up({
882
+ const result = await applyPlan2(plan, {
874
883
  onOutput: (o) => {
875
884
  emit(o.trim());
876
885
  lines.push(o);
877
- }
886
+ },
887
+ onProgress: (o) => emit(o)
878
888
  });
879
889
  const summary = `Stack "${ctx.stackName}" deployed.
880
- ` + (result.outputs["publicIp"] ? `Public IP: ${result.outputs["publicIp"].value}
881
- ` : "") + (result.outputs["gatewayUrl"] ? `Gateway URL: ${result.outputs["gatewayUrl"].value}
890
+ ` + (result.outputs["publicIp"] ? `Public IP: ${String(result.outputs["publicIp"])}
891
+ ` : "") + (result.outputs["gatewayUrl"] ? `Gateway URL: ${String(result.outputs["gatewayUrl"])}
882
892
  ` : "");
883
893
  updateTask(taskId, "completed", summary);
884
- const fullOutput = lines.join("");
885
- trimForMcp(fullOutput, ctx.stackName);
894
+ trimForMcp(lines.join(""), ctx.stackName);
886
895
  return okText(summary);
887
896
  } catch (err) {
888
897
  const msg = err instanceof Error ? err.message : String(err);
@@ -1047,7 +1056,7 @@ async function handleTaskStatus(input, _server) {
1047
1056
 
1048
1057
  // src/mcp/tools/cli/monitor.ts
1049
1058
  async function handleMonitor(input, _server) {
1050
- const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-EX4ZTA5P.js");
1059
+ const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-PIIKJ4L2.js");
1051
1060
  const ctx = buildContext({ stack: input.stackName });
1052
1061
  const tailLines = input.tailLines ?? 5;
1053
1062
  let conn;
@@ -1523,7 +1532,7 @@ function bearerFrom(header) {
1523
1532
  return match?.[1]?.trim();
1524
1533
  }
1525
1534
  async function serveMcp(opts) {
1526
- const { version } = await import("./package-3HUW57XH.js");
1535
+ const { version } = await import("./package-3A6BOALU.js");
1527
1536
  const makeServer = () => {
1528
1537
  const server = new McpServer({ name: "clawops", version });
1529
1538
  registerTools(server, opts);
@@ -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-WTZ2CT35.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-3A6BOALU.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
+ };