@clawops/cli 1.7.9 → 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 (98) hide show
  1. package/README.md +504 -76
  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-HL4DNUNG.js → bootstrap-HNGKY6WW.js} +31 -19
  6. package/dist/bootstrap.sh.tmpl +51 -31
  7. package/dist/channels-KBUR42IU.js +53 -0
  8. package/dist/chunk-2I7KCMXZ.js +142 -0
  9. package/dist/{chunk-DQDAWNGB.js → chunk-2Z7J3ERQ.js} +1 -1
  10. package/dist/chunk-3OLUJPCB.js +36 -0
  11. package/dist/chunk-5XT5IAJK.js +69 -0
  12. package/dist/chunk-AEEMRFZV.js +24 -0
  13. package/dist/chunk-BSLIYB5Q.js +106 -0
  14. package/dist/{chunk-3XK66DKK.js → chunk-E4NJSJ54.js} +57 -93
  15. package/dist/chunk-FEK7VJAY.js +16 -0
  16. package/dist/{chunk-UKTP332Z.js → chunk-GAZPPSKU.js} +6 -7
  17. package/dist/chunk-GIEVAGEK.js +126 -0
  18. package/dist/chunk-HSPHXG6W.js +67 -0
  19. package/dist/chunk-IVWP4QUJ.js +901 -0
  20. package/dist/{chunk-PVUVI35Y.js → chunk-JW5A7E3X.js} +5 -1
  21. package/dist/{chunk-GZN7PKP4.js → chunk-KC3GC3PE.js} +328 -121
  22. package/dist/chunk-LBE6COC4.js +244 -0
  23. package/dist/chunk-LEVIW2PV.js +890 -0
  24. package/dist/chunk-LT4XMO5D.js +50 -0
  25. package/dist/chunk-MC5ATCXS.js +108 -0
  26. package/dist/chunk-N4WZLWJ4.js +16 -0
  27. package/dist/chunk-NUDIHZEH.js +55 -0
  28. package/dist/chunk-NUL5F6YV.js +56 -0
  29. package/dist/chunk-RHOWYIWT.js +52 -0
  30. package/dist/chunk-STLRHLLV.js +193 -0
  31. package/dist/chunk-V25QRZRB.js +45 -0
  32. package/dist/chunk-VOEJYRIK.js +33 -0
  33. package/dist/{providers-2OABPW2E.js → chunk-VUFMZEIM.js} +3 -2
  34. package/dist/chunk-W3OLNNJW.js +51 -0
  35. package/dist/cli-WLD3EBEM.js +11 -0
  36. package/dist/cli-auth-GD5SK7JU.js +13 -0
  37. package/dist/cli.js +1296 -695
  38. package/dist/config-validate-K2N6XLDX.js +76 -0
  39. package/dist/context-7EVC6W45.js +20 -0
  40. package/dist/docker-3PDO4TS7.js +12 -0
  41. package/dist/{firewall-XQGO7HWK.js → firewall-5VKZ4WBH.js} +32 -1
  42. package/dist/generate-I6YQ5V2T.js +29 -0
  43. package/dist/{harden-URPXLSVF.js → harden-72JMRT4E.js} +51 -11
  44. package/dist/health-5CCOXYGG.js +11 -0
  45. package/dist/index.d.ts +56 -0
  46. package/dist/known-hosts-file-I3Q3NBYQ.js +22 -0
  47. package/dist/logs-5A7YYA53.js +15 -0
  48. package/dist/{mcp-wire-7E5JQY47.js → mcp-wire-3SWBM7D6.js} +32 -16
  49. package/dist/models-AGTQWYDL.js +32 -0
  50. package/dist/network-args-PYJRJBYO.js +64 -0
  51. package/dist/{package-5AR4ZPK4.js → package-5BCMEBTE.js} +11 -4
  52. package/dist/passphrase-MJBE45N7.js +15 -0
  53. package/dist/plugins-DTST3OER.js +49 -0
  54. package/dist/{pool-QGFQRMGU.js → pool-PIIKJ4L2.js} +3 -2
  55. package/dist/preflight-EF52S3WW.js +162 -0
  56. package/dist/preflight-MWWM5JT3.js +181 -0
  57. package/dist/preflight-TFD4N5ZU.js +141 -0
  58. package/dist/privileged-JFA6LLTV.js +17 -0
  59. package/dist/providers-FW6H6OPF.js +11 -0
  60. package/dist/ready-KDNRBNSU.js +95 -0
  61. package/dist/{remote-config-QDLN4UMG.js → remote-config-3HZLUWEA.js} +4 -3
  62. package/dist/runtime-X3KW5TCP.js +44 -0
  63. package/dist/server-GBA7BJGK.js +46 -0
  64. package/dist/spec-path-KQOOIQCA.js +9 -0
  65. package/dist/ssh-VAI5DU5M.js +11 -0
  66. package/dist/ssh-key-DUO6OKOI.js +7 -0
  67. package/dist/stack-config-R4SPSUQZ.js +12 -0
  68. package/dist/upgrade-ULD4KFYC.js +88 -0
  69. package/dist/validate-YVI3BJ55.js +14 -0
  70. package/dist/{version-guard-VUCNZXQS.js → version-guard-XVYW5WFJ.js} +2 -2
  71. package/dist/{versions-2UVAT6PQ.js → versions-W5LRQCAJ.js} +2 -2
  72. package/dist/wait-WO72TRRQ.js +79 -0
  73. package/package.json +11 -4
  74. package/spec/deploy-plan.schema.json +114 -25
  75. package/spec/integrations.yaml +135 -28
  76. package/spec/mcp-tools.yaml +89 -37
  77. package/spec/models.yaml +23 -3
  78. package/spec/openclaw-2.0.config.schema.json +1 -0
  79. package/spec/openclaw-versions.yaml +116 -27
  80. package/spec/providers.schema.json +163 -28
  81. package/dist/apply-F3KY2HTB.js +0 -17
  82. package/dist/automation-RULUSJBW.js +0 -30
  83. package/dist/aws-L4RR2LSE.js +0 -254
  84. package/dist/azure-7OXQENQL.js +0 -262
  85. package/dist/chunk-35WVQY3X.js +0 -122
  86. package/dist/chunk-6X45DDGH.js +0 -42
  87. package/dist/chunk-A4QR4TXW.js +0 -120
  88. package/dist/chunk-C5X375RO.js +0 -85
  89. package/dist/chunk-FNS56UQY.js +0 -741
  90. package/dist/chunk-P5LKWRPL.js +0 -114
  91. package/dist/chunk-SEZMVVAS.js +0 -156
  92. package/dist/context-5LCUG2QR.js +0 -10
  93. package/dist/gcp-UG6AWPIZ.js +0 -193
  94. package/dist/generate-OAGYT3VH.js +0 -15
  95. package/dist/local-DXBEVZ5C.js +0 -47
  96. package/dist/server-FIZCOLHL.js +0 -32
  97. package/dist/ssh-CNJNLM3E.js +0 -8
  98. package/dist/validate-CFQ4FH2T.js +0 -10
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ PASSPHRASE_FILE,
4
+ ensurePassphrase,
5
+ passphraseInEnvironment,
6
+ passphrasePath,
7
+ passphraseStatus
8
+ } from "./chunk-V25QRZRB.js";
9
+ export {
10
+ PASSPHRASE_FILE,
11
+ ensurePassphrase,
12
+ passphraseInEnvironment,
13
+ passphrasePath,
14
+ passphraseStatus
15
+ };
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/openclaw/plugins.ts
4
+ function providerIdOf(p) {
5
+ return p.configPath?.split(".").pop() ?? p.id;
6
+ }
7
+ function requiredPlugins(cfg, catalog) {
8
+ if (cfg === null || typeof cfg !== "object") return [];
9
+ const models = cfg["models"];
10
+ if (models === null || typeof models !== "object") return [];
11
+ const providers = models["providers"];
12
+ if (providers === null || typeof providers !== "object") return [];
13
+ const configured = new Set(Object.keys(providers));
14
+ const out = [];
15
+ for (const entry of catalog.providers) {
16
+ if (!entry.plugin) continue;
17
+ const id = providerIdOf(entry);
18
+ if (configured.has(id)) {
19
+ out.push({ providerId: id, package: entry.plugin.package, version: entry.plugin.version });
20
+ }
21
+ }
22
+ return out;
23
+ }
24
+ function installCommand(plugin, image, stateDir) {
25
+ return `docker run --rm -v ${stateDir}:/home/node/.openclaw ${image} openclaw plugins install 'clawhub:${plugin.package}@${plugin.version}' --accept-capabilities`;
26
+ }
27
+ function missingProviders(cfg, pluginsListJson) {
28
+ let loaded = /* @__PURE__ */ new Set();
29
+ try {
30
+ const parsed = JSON.parse(pluginsListJson);
31
+ const list = Array.isArray(parsed) ? parsed : parsed.plugins ?? [];
32
+ loaded = new Set(
33
+ list.filter((p) => p.enabled !== false && p.status !== "error").flatMap((p) => p.providerIds ?? [])
34
+ );
35
+ } catch {
36
+ return [];
37
+ }
38
+ if (cfg === null || typeof cfg !== "object") return [];
39
+ const models = cfg["models"];
40
+ if (models === null || typeof models !== "object") return [];
41
+ const providers = models["providers"];
42
+ if (providers === null || typeof providers !== "object") return [];
43
+ return Object.keys(providers).filter((id) => !loaded.has(id));
44
+ }
45
+ export {
46
+ installCommand,
47
+ missingProviders,
48
+ requiredPlugins
49
+ };
@@ -2,8 +2,9 @@
2
2
  import {
3
3
  acquireSession,
4
4
  drainPool
5
- } from "./chunk-DQDAWNGB.js";
6
- import "./chunk-3XK66DKK.js";
5
+ } from "./chunk-2Z7J3ERQ.js";
6
+ import "./chunk-E4NJSJ54.js";
7
+ import "./chunk-MC5ATCXS.js";
7
8
  import "./chunk-KGXPLI7W.js";
8
9
  export {
9
10
  acquireSession,
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/providers/gcp/preflight.ts
4
+ import process from "process";
5
+ import path from "path";
6
+ import { readFileSync } from "fs";
7
+ var REQUIRED_APIS = [
8
+ { service: "compute.googleapis.com", why: "Instance, Network, Subnetwork, Firewall, Address" },
9
+ { service: "storage.googleapis.com", why: "the GCS state backend" }
10
+ ];
11
+ async function accessToken() {
12
+ try {
13
+ const { GoogleAuth } = await import("google-auth-library");
14
+ const auth = new GoogleAuth({ scopes: ["https://www.googleapis.com/auth/cloud-platform"] });
15
+ const client = await auth.getClient();
16
+ const token = await client.getAccessToken();
17
+ return typeof token === "string" ? token : token.token ?? void 0;
18
+ } catch {
19
+ return void 0;
20
+ }
21
+ }
22
+ function resolveProjectId() {
23
+ return process.env["GOOGLE_PROJECT"] ?? process.env["GOOGLE_CLOUD_PROJECT"] ?? process.env["GCLOUD_PROJECT"] ?? process.env["CLOUDSDK_CORE_PROJECT"] ?? gcloudConfiguredProject() ?? void 0;
24
+ }
25
+ function gcloudConfiguredProject() {
26
+ try {
27
+ const home = process.env["HOME"] ?? process.env["USERPROFILE"];
28
+ const dir = process.env["CLOUDSDK_CONFIG"] ?? (home ? path.join(home, ".config", "gcloud") : void 0);
29
+ if (!dir) return void 0;
30
+ const active = process.env["CLOUDSDK_ACTIVE_CONFIG_NAME"] ?? readFileSync(path.join(dir, "active_config"), "utf-8").trim() ?? "default";
31
+ if (!active) return void 0;
32
+ const ini = readFileSync(path.join(dir, "configurations", `config_${active}`), "utf-8");
33
+ return projectFromIni(ini);
34
+ } catch {
35
+ return void 0;
36
+ }
37
+ }
38
+ function projectFromIni(ini) {
39
+ let section = "";
40
+ for (const line of ini.split(/\r?\n/)) {
41
+ const trimmed = line.trim();
42
+ if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith(";")) continue;
43
+ const header = /^\[(.+)\]$/.exec(trimmed);
44
+ if (header) {
45
+ section = header[1]?.trim() ?? "";
46
+ continue;
47
+ }
48
+ if (section !== "core") continue;
49
+ const eq = trimmed.indexOf("=");
50
+ if (eq === -1) continue;
51
+ if (trimmed.slice(0, eq).trim() !== "project") continue;
52
+ const value = trimmed.slice(eq + 1).trim();
53
+ return value === "" ? void 0 : value;
54
+ }
55
+ return void 0;
56
+ }
57
+ async function gcpPreflight(opts = {}) {
58
+ const checks = [];
59
+ const project = resolveProjectId();
60
+ checks.push({
61
+ id: "project-resolved",
62
+ label: "GCP project is set",
63
+ ok: Boolean(project),
64
+ detail: project ? `Deploying into ${project}` : "No project resolved. Run `gcloud config set project <id>`, or set GOOGLE_PROJECT."
65
+ });
66
+ if (!project) return checks;
67
+ const token = await accessToken();
68
+ if (!token) {
69
+ checks.push({
70
+ id: "adc-usable",
71
+ label: "Application Default Credentials resolve",
72
+ ok: false,
73
+ detail: "Could not obtain a token from ADC. Run `gcloud auth application-default login` \u2014 `gcloud auth login` alone authenticates the CLI and leaves ADC unset."
74
+ });
75
+ return checks;
76
+ }
77
+ const enabled = await enabledServices(project, token, opts.signal);
78
+ for (const api of REQUIRED_APIS) {
79
+ const on = enabled?.has(api.service) ?? false;
80
+ checks.push({
81
+ id: `api-${api.service.split(".")[0]}`,
82
+ label: `${api.service} enabled`,
83
+ ok: on,
84
+ detail: on ? void 0 : `Needed for ${api.why}. Nothing can be provisioned without it.`,
85
+ mutates: on ? void 0 : `Enables ${api.service} on project ${project}`,
86
+ fix: on ? void 0 : async () => {
87
+ await enableService(project, api.service, token);
88
+ }
89
+ });
90
+ }
91
+ if (opts.bucket) {
92
+ const exists = await bucketExists(opts.bucket, token, opts.signal);
93
+ checks.push({
94
+ id: "state-bucket",
95
+ label: `State bucket gs://${opts.bucket} exists`,
96
+ ok: exists,
97
+ detail: exists ? void 0 : "Pulumi needs its state backend before it can run, so clawops cannot create this as part of a deploy.",
98
+ mutates: exists ? void 0 : `Creates bucket gs://${opts.bucket} in project ${project}${opts.region ? ` (${opts.region})` : ""}, with versioning on`,
99
+ fix: exists ? void 0 : async () => {
100
+ await createBucket(opts.bucket, project, token, opts.region);
101
+ }
102
+ });
103
+ }
104
+ return checks;
105
+ }
106
+ async function enabledServices(project, token, signal) {
107
+ try {
108
+ const out = /* @__PURE__ */ new Set();
109
+ let pageToken;
110
+ do {
111
+ const url = `https://serviceusage.googleapis.com/v1/projects/${project}/services?filter=state:ENABLED&pageSize=200${pageToken ? `&pageToken=${pageToken}` : ""}`;
112
+ const res = await fetch(url, { headers: { authorization: `Bearer ${token}` }, signal });
113
+ if (!res.ok) return void 0;
114
+ const body = await res.json();
115
+ for (const s of body.services ?? []) if (s.config?.name) out.add(s.config.name);
116
+ pageToken = body.nextPageToken;
117
+ } while (pageToken);
118
+ return out;
119
+ } catch {
120
+ return void 0;
121
+ }
122
+ }
123
+ async function enableService(project, service, token) {
124
+ const res = await fetch(
125
+ `https://serviceusage.googleapis.com/v1/projects/${project}/services/${service}:enable`,
126
+ { method: "POST", headers: { authorization: `Bearer ${token}` } }
127
+ );
128
+ if (!res.ok) {
129
+ throw new Error(`Could not enable ${service}: ${res.status} ${await res.text()}`);
130
+ }
131
+ }
132
+ async function bucketExists(bucket, token, signal) {
133
+ const res = await fetch(`https://storage.googleapis.com/storage/v1/b/${bucket}`, {
134
+ headers: { authorization: `Bearer ${token}` },
135
+ signal
136
+ });
137
+ return res.ok;
138
+ }
139
+ async function createBucket(bucket, project, token, region) {
140
+ const res = await fetch(
141
+ `https://storage.googleapis.com/storage/v1/b?project=${project}`,
142
+ {
143
+ method: "POST",
144
+ headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
145
+ body: JSON.stringify({
146
+ name: bucket,
147
+ location: (region ?? "US").toUpperCase(),
148
+ // Versioning on, because this holds Pulumi state: a corrupted or truncated write
149
+ // with no history is a stack that can no longer be updated or destroyed.
150
+ versioning: { enabled: true },
151
+ iamConfiguration: { uniformBucketLevelAccess: { enabled: true } }
152
+ })
153
+ }
154
+ );
155
+ if (!res.ok) throw new Error(`Could not create gs://${bucket}: ${res.status} ${await res.text()}`);
156
+ }
157
+ export {
158
+ gcloudConfiguredProject,
159
+ gcpPreflight,
160
+ projectFromIni,
161
+ resolveProjectId
162
+ };
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_ALIAS,
4
+ INSTANCE_TYPE_MAP
5
+ } from "./chunk-FEK7VJAY.js";
6
+ import {
7
+ resolveSubscriptionId
8
+ } from "./chunk-RHOWYIWT.js";
9
+
10
+ // src/providers/azure/preflight.ts
11
+ import process from "process";
12
+ import { spawnSync } from "child_process";
13
+ var ARM = "https://management.azure.com";
14
+ var API_VERSION = "2021-04-01";
15
+ var REQUIRED_PROVIDERS = [
16
+ { namespace: "Microsoft.Compute", why: "the virtual machine and its disk" },
17
+ { namespace: "Microsoft.Network", why: "the virtual network, NSG, public IP and NIC" },
18
+ { namespace: "Microsoft.Storage", why: "the azblob state backend" }
19
+ ];
20
+ async function managementToken(signal) {
21
+ const tenantId = process.env["AZURE_TENANT_ID"];
22
+ const clientId = process.env["AZURE_CLIENT_ID"];
23
+ const clientSecret = process.env["AZURE_CLIENT_SECRET"];
24
+ if (tenantId && clientId && clientSecret) {
25
+ try {
26
+ const res = await fetch(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`, {
27
+ method: "POST",
28
+ headers: { "content-type": "application/x-www-form-urlencoded" },
29
+ body: new URLSearchParams({
30
+ grant_type: "client_credentials",
31
+ client_id: clientId,
32
+ client_secret: clientSecret,
33
+ scope: `${ARM}/.default`
34
+ }),
35
+ signal: signal ?? AbortSignal.timeout(1e4)
36
+ });
37
+ if (!res.ok) return void 0;
38
+ const body = await res.json();
39
+ return typeof body.access_token === "string" ? body.access_token : void 0;
40
+ } catch {
41
+ return void 0;
42
+ }
43
+ }
44
+ const result = spawnSync(
45
+ "az",
46
+ ["account", "get-access-token", "--resource", ARM, "--query", "accessToken", "-o", "tsv"],
47
+ { encoding: "utf-8", timeout: 2e4 }
48
+ );
49
+ if (result.status !== 0) return void 0;
50
+ const token = result.stdout.trim();
51
+ return token === "" ? void 0 : token;
52
+ }
53
+ async function registrationState(subscriptionId, namespace, token, signal) {
54
+ try {
55
+ const res = await fetch(
56
+ `${ARM}/subscriptions/${subscriptionId}/providers/${namespace}?api-version=${API_VERSION}`,
57
+ { headers: { authorization: `Bearer ${token}` }, signal }
58
+ );
59
+ if (!res.ok) return void 0;
60
+ const body = await res.json();
61
+ return typeof body.registrationState === "string" ? body.registrationState : void 0;
62
+ } catch {
63
+ return void 0;
64
+ }
65
+ }
66
+ async function register(subscriptionId, namespace, token, signal) {
67
+ const res = await fetch(
68
+ `${ARM}/subscriptions/${subscriptionId}/providers/${namespace}/register?api-version=${API_VERSION}`,
69
+ { method: "POST", headers: { authorization: `Bearer ${token}` }, signal }
70
+ );
71
+ if (!res.ok) {
72
+ throw new Error(`Could not register ${namespace}: HTTP ${res.status} ${await res.text()}`);
73
+ }
74
+ }
75
+ async function availableSizes(subscriptionId, location, token, signal) {
76
+ try {
77
+ const url = `${ARM}/subscriptions/${subscriptionId}/providers/Microsoft.Compute/skus?api-version=2021-07-01&$filter=${encodeURIComponent(`location eq '${location}'`)}`;
78
+ const res = await fetch(url, { headers: { authorization: `Bearer ${token}` }, signal });
79
+ if (!res.ok) return void 0;
80
+ const body = await res.json();
81
+ if (!Array.isArray(body.value)) return void 0;
82
+ return new Set(
83
+ body.value.filter((s) => s.resourceType === "virtualMachines").filter((s) => !Array.isArray(s.restrictions) || s.restrictions.length === 0).map((s) => s.name)
84
+ );
85
+ } catch {
86
+ return void 0;
87
+ }
88
+ }
89
+ function sizeCheck(location, available, requested = INSTANCE_TYPE_MAP[DEFAULT_ALIAS]) {
90
+ if (!available) {
91
+ return {
92
+ id: "vm-size-available",
93
+ label: `${requested} is available in ${location}`,
94
+ ok: false,
95
+ unknown: true,
96
+ detail: `clawops could not list the VM sizes this subscription is offered in ${location}. This says nothing about ${requested} \u2014 the usual cause is credentials without permission to read Microsoft.Compute/skus. A deploy can still succeed.`
97
+ };
98
+ }
99
+ if (available.has(requested)) {
100
+ return {
101
+ id: "vm-size-available",
102
+ label: `${requested} is available in ${location}`,
103
+ ok: true
104
+ };
105
+ }
106
+ const alternatives = [...available].filter((n) => /^Standard_[A-Z]*[0-9]+[a-z]*(_v\d+)?$/.test(n)).filter((n) => /[^0-9]2[a-z]*(_v\d+)?$/.test(n)).sort().slice(0, 4);
107
+ return {
108
+ id: "vm-size-available",
109
+ label: `${requested} is available in ${location}`,
110
+ ok: false,
111
+ detail: `${requested} is not offered to this subscription in ${location}, so a deploy fails after the network, NSG, public IP and NIC have been created. ` + (alternatives.length > 0 ? `Available instead: ${alternatives.join(", ")} \u2014 pass one with \`clawops plan --instance-type <size>\`.` : "Try another region.")
112
+ };
113
+ }
114
+ async function azurePreflight(opts = {}) {
115
+ const checks = [];
116
+ const subscriptionId = resolveSubscriptionId();
117
+ checks.push({
118
+ id: "subscription-resolved",
119
+ label: "Azure subscription is set",
120
+ ok: Boolean(subscriptionId),
121
+ detail: subscriptionId ? `Deploying into ${subscriptionId}` : "No subscription resolved. Run `az login`, or set ARM_SUBSCRIPTION_ID."
122
+ });
123
+ if (!subscriptionId) return checks;
124
+ checks.push(stateBackendCheck(opts.bucket));
125
+ const token = await managementToken(opts.signal);
126
+ if (!token) {
127
+ checks.push({
128
+ id: "management-token",
129
+ label: "Azure management API reachable",
130
+ ok: false,
131
+ detail: "Could not get a management token, so the resource providers and VM sizes below were not checked. Run `az login`, or set AZURE_TENANT_ID + AZURE_CLIENT_ID + AZURE_CLIENT_SECRET for a service principal."
132
+ });
133
+ return checks;
134
+ }
135
+ if (opts.region) {
136
+ checks.push(
137
+ sizeCheck(
138
+ opts.region,
139
+ await availableSizes(subscriptionId, opts.region, token, opts.signal),
140
+ opts.instanceType
141
+ )
142
+ );
143
+ }
144
+ for (const provider of REQUIRED_PROVIDERS) {
145
+ const state = await registrationState(subscriptionId, provider.namespace, token, opts.signal);
146
+ const registered = state === "Registered";
147
+ checks.push({
148
+ id: `rp-${provider.namespace.split(".")[1]?.toLowerCase()}`,
149
+ label: `${provider.namespace} registered`,
150
+ ok: registered,
151
+ detail: registered ? void 0 : `${state ?? "unknown"} \u2014 needed for ${provider.why}. Nothing can be provisioned without it, and registration takes a couple of minutes.`,
152
+ ...registered ? {} : {
153
+ mutates: `Registers the ${provider.namespace} resource provider on subscription ${subscriptionId}`,
154
+ fix: () => register(subscriptionId, provider.namespace, token, opts.signal)
155
+ }
156
+ });
157
+ }
158
+ return checks;
159
+ }
160
+ function stateBackendCheck(container) {
161
+ const account = process.env["AZURE_STORAGE_ACCOUNT"];
162
+ const hasSecret = Boolean(
163
+ process.env["AZURE_STORAGE_KEY"] ?? process.env["AZURE_STORAGE_SAS_TOKEN"]
164
+ );
165
+ const ok = Boolean(account) && hasSecret;
166
+ const where = container ? `azblob://${container}` : "the azblob state backend";
167
+ return {
168
+ id: "state-backend-credentials",
169
+ label: "azblob state backend is configured",
170
+ ok,
171
+ detail: ok ? `${where} on storage account ${account}` : `${where} needs AZURE_STORAGE_ACCOUNT and one of AZURE_STORAGE_KEY or AZURE_STORAGE_SAS_TOKEN. Pulumi authenticates to blob storage with these and not with your \`az login\`, so every other check here can pass and the deploy still fail to open its own state.`
172
+ };
173
+ }
174
+ export {
175
+ REQUIRED_PROVIDERS,
176
+ availableSizes,
177
+ azurePreflight,
178
+ managementToken,
179
+ sizeCheck,
180
+ stateBackendCheck
181
+ };
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_ALIAS,
4
+ INSTANCE_TYPE_MAP
5
+ } from "./chunk-N4WZLWJ4.js";
6
+
7
+ // src/providers/aws/preflight.ts
8
+ import process from "process";
9
+ async function callerAccount(signal) {
10
+ try {
11
+ const { STSClient, GetCallerIdentityCommand } = await import("@aws-sdk/client-sts");
12
+ const client = new STSClient({});
13
+ const result = await client.send(new GetCallerIdentityCommand({}), { abortSignal: signal });
14
+ return result.Account ?? void 0;
15
+ } catch {
16
+ return void 0;
17
+ }
18
+ }
19
+ async function bucketExists(bucket, signal) {
20
+ try {
21
+ const { S3Client, HeadBucketCommand } = await import("@aws-sdk/client-s3");
22
+ const client = new S3Client({});
23
+ await client.send(new HeadBucketCommand({ Bucket: bucket }), { abortSignal: signal });
24
+ return { ok: true };
25
+ } catch (err) {
26
+ const status = err.$metadata?.httpStatusCode;
27
+ const detail = err instanceof Error ? err.message : String(err);
28
+ if (status === 404) return { ok: false, reason: "missing", detail };
29
+ if (status === 403) return { ok: false, reason: "denied", detail };
30
+ return { ok: false, reason: "unknown", detail };
31
+ }
32
+ }
33
+ async function createBucket(bucket, region, signal) {
34
+ const {
35
+ S3Client,
36
+ CreateBucketCommand,
37
+ PutBucketVersioningCommand,
38
+ PutPublicAccessBlockCommand
39
+ } = await import("@aws-sdk/client-s3");
40
+ const client = new S3Client({ region });
41
+ await client.send(
42
+ new CreateBucketCommand({
43
+ Bucket: bucket,
44
+ // us-east-1 is the one region that rejects an explicit LocationConstraint.
45
+ ...region === "us-east-1" ? {} : { CreateBucketConfiguration: { LocationConstraint: region } }
46
+ }),
47
+ { abortSignal: signal }
48
+ );
49
+ await client.send(
50
+ new PutBucketVersioningCommand({
51
+ Bucket: bucket,
52
+ VersioningConfiguration: { Status: "Enabled" }
53
+ }),
54
+ { abortSignal: signal }
55
+ );
56
+ await client.send(
57
+ new PutPublicAccessBlockCommand({
58
+ Bucket: bucket,
59
+ PublicAccessBlockConfiguration: {
60
+ BlockPublicAcls: true,
61
+ IgnorePublicAcls: true,
62
+ BlockPublicPolicy: true,
63
+ RestrictPublicBuckets: true
64
+ }
65
+ }),
66
+ { abortSignal: signal }
67
+ );
68
+ }
69
+ async function instanceTypeOffered(instanceType, region, signal) {
70
+ try {
71
+ const { EC2Client, DescribeInstanceTypeOfferingsCommand } = await import("@aws-sdk/client-ec2");
72
+ const client = new EC2Client({ region });
73
+ const result = await client.send(
74
+ new DescribeInstanceTypeOfferingsCommand({
75
+ LocationType: "region",
76
+ Filters: [
77
+ { Name: "instance-type", Values: [instanceType] },
78
+ { Name: "location", Values: [region] }
79
+ ]
80
+ }),
81
+ { abortSignal: signal }
82
+ );
83
+ return { offered: (result.InstanceTypeOfferings?.length ?? 0) > 0 };
84
+ } catch (err) {
85
+ return { error: err instanceof Error ? err.message : String(err) };
86
+ }
87
+ }
88
+ async function awsPreflight(opts = {}) {
89
+ const checks = [];
90
+ const region = opts.region ?? process.env["AWS_REGION"] ?? "us-east-1";
91
+ const account = await callerAccount(opts.signal);
92
+ checks.push({
93
+ id: "account-resolved",
94
+ label: "AWS credentials resolve to an account",
95
+ ok: Boolean(account),
96
+ detail: account ? `Deploying into ${account} (${region})` : "No account resolved. Set AWS_PROFILE, or run `aws sso login --profile <name>`."
97
+ });
98
+ if (!account) return checks;
99
+ if (opts.bucket) {
100
+ const bucket = opts.bucket;
101
+ const result = await bucketExists(bucket, opts.signal);
102
+ checks.push(
103
+ result.ok ? { id: "state-bucket", label: `State bucket s3://${bucket} exists`, ok: true } : {
104
+ id: "state-bucket",
105
+ label: `State bucket s3://${bucket} exists`,
106
+ ok: false,
107
+ detail: result.reason === "denied" ? `s3://${bucket} exists but these credentials cannot read it, or the name belongs to another account. Bucket names are global.` : "Pulumi needs its state backend before it can run, so clawops cannot create this as part of a deploy.",
108
+ // Only offer to create what is genuinely absent: a 403 means the name is taken or
109
+ // the permission is missing, and creating it would fail either way.
110
+ ...result.reason === "missing" ? {
111
+ mutates: `Creates bucket s3://${bucket} in ${region}, with versioning on and public access blocked`,
112
+ fix: () => createBucket(bucket, region, opts.signal)
113
+ } : {}
114
+ }
115
+ );
116
+ }
117
+ const instanceType = opts.instanceType ?? INSTANCE_TYPE_MAP[DEFAULT_ALIAS];
118
+ const offering = await instanceTypeOffered(instanceType, region, opts.signal);
119
+ checks.push(
120
+ "error" in offering ? {
121
+ id: "instance-type-offered",
122
+ label: `${instanceType} is offered in ${region}`,
123
+ ok: false,
124
+ unknown: true,
125
+ detail: `clawops could not ask EC2 whether ${instanceType} is offered in ${region}: ${offering.error}. This says nothing about the instance type \u2014 the usual cause is credentials without ec2:DescribeInstanceTypeOfferings, which nothing else needs. A deploy can still succeed.`
126
+ } : {
127
+ id: "instance-type-offered",
128
+ label: `${instanceType} is offered in ${region}`,
129
+ ok: offering.offered,
130
+ detail: offering.offered ? void 0 : `${instanceType} is not offered in ${region}, so a deploy fails after the VPC, subnet, security group and address have been created. Pick another region, or pass --instance-type with a type this region has.`
131
+ }
132
+ );
133
+ return checks;
134
+ }
135
+ export {
136
+ awsPreflight,
137
+ bucketExists,
138
+ callerAccount,
139
+ createBucket,
140
+ instanceTypeOffered
141
+ };
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ execPrivileged,
4
+ execPrivilegedWithInput,
5
+ resetPrivilegeCache,
6
+ shellQuote,
7
+ streamPrivileged,
8
+ sudoWrap
9
+ } from "./chunk-NUDIHZEH.js";
10
+ export {
11
+ execPrivileged,
12
+ execPrivilegedWithInput,
13
+ resetPrivilegeCache,
14
+ shellQuote,
15
+ streamPrivileged,
16
+ sudoWrap
17
+ };
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getProvider,
4
+ listProviders,
5
+ registerProvider
6
+ } from "./chunk-VUFMZEIM.js";
7
+ export {
8
+ getProvider,
9
+ listProviders,
10
+ registerProvider
11
+ };
@@ -0,0 +1,95 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ containerStatus,
4
+ looksLikeStillBooting
5
+ } from "./chunk-3OLUJPCB.js";
6
+ import {
7
+ execPrivileged
8
+ } from "./chunk-NUDIHZEH.js";
9
+ import {
10
+ interpretProbe,
11
+ probeCommand
12
+ } from "./chunk-LT4XMO5D.js";
13
+ import {
14
+ GATEWAY_PORT
15
+ } from "./chunk-VOEJYRIK.js";
16
+ import "./chunk-EI7CQQ7D.js";
17
+
18
+ // src/openclaw/ready.ts
19
+ async function bootstrapTail(session, lines = 12, signal) {
20
+ const command = `(tail -n ${lines} /var/log/cloud-init-output.log 2>/dev/null || journalctl -u google-startup-scripts --no-pager 2>/dev/null | tail -n ${lines}) | tail -n ${lines}`;
21
+ try {
22
+ const result = await execPrivileged(session, command, signal);
23
+ const text = result.stdout.trim();
24
+ return text === "" ? void 0 : text;
25
+ } catch {
26
+ return void 0;
27
+ }
28
+ }
29
+ async function waitForGateway(session, opts = {}) {
30
+ const timeoutMs = opts.timeoutMs ?? 6e5;
31
+ const intervalMs = opts.intervalMs ?? 1e4;
32
+ const port = opts.port ?? GATEWAY_PORT;
33
+ const sleep = opts.sleep ?? defaultSleep;
34
+ const now = opts.now ?? Date.now;
35
+ const started = now();
36
+ const deadline = started + timeoutMs;
37
+ let lastContainerStatus = "unknown";
38
+ let lastReason = "not probed yet";
39
+ let announcedAt = 0;
40
+ for (; ; ) {
41
+ if (opts.signal?.aborted) throw new Error("Waiting for the gateway was aborted");
42
+ const container = await containerStatus(session, "openclaw", opts.signal);
43
+ lastContainerStatus = container.status;
44
+ if (container.error) {
45
+ if (!looksLikeStillBooting(container.error)) {
46
+ throw new Error(
47
+ `Could not ask the host about the openclaw container: ${container.error}. This is not the container being absent \u2014 clawops was unable to look.`
48
+ );
49
+ }
50
+ lastContainerStatus = "docker not installed yet";
51
+ }
52
+ if (lastContainerStatus === "running") {
53
+ const probe = await session.exec(probeCommand("started", port), opts.signal);
54
+ const verdict = interpretProbe("started", probe.stdout);
55
+ if (verdict.ok) return { waitedMs: now() - started, lastContainerStatus };
56
+ lastReason = verdict.reason ?? "the gateway did not report itself started";
57
+ } else {
58
+ lastReason = container.error ? `docker could not be asked yet: ${container.error}` : `the container is ${lastContainerStatus}`;
59
+ }
60
+ const elapsed = now() - started;
61
+ if (elapsed - announcedAt >= 3e4 || announcedAt === 0) {
62
+ announcedAt = elapsed;
63
+ opts.onProgress?.(
64
+ `Waiting for OpenClaw to start \u2014 ${lastReason}. A first deploy pulls a ~3GB image (${Math.round(elapsed / 1e3)}s so far).`
65
+ );
66
+ }
67
+ if (now() + intervalMs >= deadline) {
68
+ const tail = await bootstrapTail(session, 12, opts.signal);
69
+ throw new Error(
70
+ `The OpenClaw gateway did not answer within ${Math.round(timeoutMs / 1e3)}s. Container: ${lastContainerStatus}. Last check: ${lastReason}.` + (tail ? `
71
+
72
+ The last of the host's bootstrap log:
73
+ ${tail}` : " The instance is up \u2014 `clawops logs --stack <name>` shows what it is doing.")
74
+ );
75
+ }
76
+ await sleep(intervalMs, opts.signal);
77
+ }
78
+ }
79
+ function defaultSleep(ms, signal) {
80
+ return new Promise((resolve, reject) => {
81
+ const timer = setTimeout(() => {
82
+ signal?.removeEventListener("abort", onAbort);
83
+ resolve();
84
+ }, ms);
85
+ const onAbort = () => {
86
+ clearTimeout(timer);
87
+ reject(new Error("Waiting for the gateway was aborted"));
88
+ };
89
+ signal?.addEventListener("abort", onAbort, { once: true });
90
+ });
91
+ }
92
+ export {
93
+ bootstrapTail,
94
+ waitForGateway
95
+ };