@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,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,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
+ };
@@ -8,9 +8,10 @@ import {
8
8
  deepMerge,
9
9
  readRemoteConfig,
10
10
  restartGateway
11
- } from "./chunk-N44K3LHE.js";
11
+ } from "./chunk-2I7KCMXZ.js";
12
12
  import "./chunk-NUDIHZEH.js";
13
- import "./chunk-6TIYVHPX.js";
13
+ import "./chunk-GIEVAGEK.js";
14
+ import "./chunk-VOEJYRIK.js";
14
15
  import "./chunk-EI7CQQ7D.js";
15
16
  export {
16
17
  OPENCLAW_CONFIG,
@@ -5,7 +5,6 @@ import {
5
5
  CONFIG_MOUNT_PATH,
6
6
  CONTAINER_UID,
7
7
  ENV_FILE_PATH,
8
- GATEWAY_PORT,
9
8
  PUBLISH_INSPECT_CMD,
10
9
  SECURITY_FLAGS,
11
10
  STATE_DIR_CONTAINER,
@@ -18,7 +17,10 @@ import {
18
17
  publishForRestart,
19
18
  publishedGatewayPort,
20
19
  stateDirForOS
21
- } from "./chunk-6TIYVHPX.js";
20
+ } from "./chunk-GIEVAGEK.js";
21
+ import {
22
+ GATEWAY_PORT
23
+ } from "./chunk-VOEJYRIK.js";
22
24
  import "./chunk-EI7CQQ7D.js";
23
25
  export {
24
26
  ADD_HOST_FLAG,
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ MCP_HTTP_PORT,
4
+ authRequirement,
5
+ bearerFrom,
6
+ isLoopbackBind,
7
+ serveMcp
8
+ } from "./chunk-RATEIAFU.js";
9
+ import "./chunk-ZMMRIWFS.js";
10
+ import "./chunk-BOPSG2LI.js";
11
+ import "./chunk-6ZFIFDBJ.js";
12
+ import "./chunk-3QJBNAHW.js";
13
+ import "./chunk-2I7KCMXZ.js";
14
+ import "./chunk-HSPHXG6W.js";
15
+ import "./chunk-AEEMRFZV.js";
16
+ import "./chunk-SDZKCICH.js";
17
+ import "./chunk-BSLIYB5Q.js";
18
+ import "./chunk-2Z7J3ERQ.js";
19
+ import "./chunk-E4NJSJ54.js";
20
+ import "./chunk-MC5ATCXS.js";
21
+ import "./chunk-WTZ2CT35.js";
22
+ import "./chunk-5TXCUOZV.js";
23
+ import "./chunk-NUL5F6YV.js";
24
+ import "./chunk-3OLUJPCB.js";
25
+ import "./chunk-NUDIHZEH.js";
26
+ import "./chunk-JW5A7E3X.js";
27
+ import "./chunk-LT4XMO5D.js";
28
+ import "./chunk-LEVIW2PV.js";
29
+ import "./chunk-GIEVAGEK.js";
30
+ import "./chunk-VOEJYRIK.js";
31
+ import "./chunk-EI7CQQ7D.js";
32
+ import "./chunk-FEK7VJAY.js";
33
+ import "./chunk-RHOWYIWT.js";
34
+ import "./chunk-Q7NQY5HV.js";
35
+ import "./chunk-N4WZLWJ4.js";
36
+ import "./chunk-A2I76FTA.js";
37
+ import "./chunk-CX5SL5HP.js";
38
+ import "./chunk-KGXPLI7W.js";
39
+ import "./chunk-VUFMZEIM.js";
40
+ export {
41
+ MCP_HTTP_PORT,
42
+ authRequirement,
43
+ bearerFrom,
44
+ isLoopbackBind,
45
+ serveMcp
46
+ };
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ connect,
4
+ describeConnectError
5
+ } from "./chunk-E4NJSJ54.js";
6
+ import "./chunk-MC5ATCXS.js";
7
+ import "./chunk-KGXPLI7W.js";
8
+ export {
9
+ connect,
10
+ describeConnectError
11
+ };
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ resolvePublicKey
4
+ } from "./chunk-AEEMRFZV.js";
5
+ export {
6
+ resolvePublicKey
7
+ };
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ writeStackConfig
4
+ } from "./chunk-HSPHXG6W.js";
5
+ import "./chunk-AEEMRFZV.js";
6
+ import "./chunk-VOEJYRIK.js";
7
+ import "./chunk-EI7CQQ7D.js";
8
+ import "./chunk-CX5SL5HP.js";
9
+ import "./chunk-KGXPLI7W.js";
10
+ export {
11
+ writeStackConfig
12
+ };
@@ -4,7 +4,7 @@ import {
4
4
  validatePlan,
5
5
  validatePlanConfig,
6
6
  validatePlanNetwork
7
- } from "./chunk-YR3O2I3S.js";
7
+ } from "./chunk-BSLIYB5Q.js";
8
8
  import "./chunk-JW5A7E3X.js";
9
9
  export {
10
10
  assertValidPlan,
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ connect
4
+ } from "./chunk-E4NJSJ54.js";
5
+ import "./chunk-MC5ATCXS.js";
6
+ import {
7
+ NetworkError
8
+ } from "./chunk-KGXPLI7W.js";
9
+
10
+ // src/transport/wait.ts
11
+ var TRANSIENT = [
12
+ "ECONNREFUSED",
13
+ "ETIMEDOUT",
14
+ "EHOSTUNREACH",
15
+ "ENETUNREACH",
16
+ "ECONNRESET",
17
+ "EPIPE",
18
+ "ENOTFOUND",
19
+ "Timed out while waiting for handshake",
20
+ "Connection closed by server",
21
+ "Connection lost before handshake",
22
+ "All configured authentication methods failed"
23
+ ];
24
+ function isTransient(message) {
25
+ return TRANSIENT.some((t) => message.includes(t));
26
+ }
27
+ async function waitForSsh(conn, opts = {}) {
28
+ const timeoutMs = opts.timeoutMs ?? 3e5;
29
+ const intervalMs = opts.intervalMs ?? 5e3;
30
+ const connect2 = opts.connect ?? connect;
31
+ const sleep = opts.sleep ?? defaultSleep;
32
+ const deadline = Date.now() + timeoutMs;
33
+ let attempts = 0;
34
+ let lastError = "no attempt completed";
35
+ for (; ; ) {
36
+ if (opts.signal?.aborted) throw new NetworkError("Waiting for SSH was aborted");
37
+ attempts++;
38
+ try {
39
+ const session = await connect2({ ...conn, signal: opts.signal });
40
+ if (attempts > 1) opts.onProgress?.(`SSH is up after ${attempts} attempts.`);
41
+ return session;
42
+ } catch (err) {
43
+ lastError = err instanceof Error ? err.message : String(err);
44
+ if (!isTransient(lastError)) {
45
+ throw new NetworkError(
46
+ `SSH to ${conn.host}:${conn.port} failed for a reason waiting will not fix: ${lastError}`
47
+ );
48
+ }
49
+ if (attempts === 1) {
50
+ opts.onProgress?.(
51
+ `Waiting for ${conn.host}:${conn.port} to accept SSH \u2014 a new instance takes a minute.`
52
+ );
53
+ }
54
+ }
55
+ if (Date.now() + intervalMs >= deadline) {
56
+ throw new NetworkError(
57
+ `${conn.host}:${conn.port} did not accept SSH within ${Math.round(timeoutMs / 1e3)}s (${attempts} attempts, last error: ${lastError}). The instance may still be booting, or the firewall may not admit this machine \u2014 check \`clawops doctor --stack <name>\`.`
58
+ );
59
+ }
60
+ await sleep(intervalMs, opts.signal);
61
+ }
62
+ }
63
+ function defaultSleep(ms, signal) {
64
+ return new Promise((resolve, reject) => {
65
+ const timer = setTimeout(() => {
66
+ signal?.removeEventListener("abort", onAbort);
67
+ resolve();
68
+ }, ms);
69
+ const onAbort = () => {
70
+ clearTimeout(timer);
71
+ reject(new NetworkError("Waiting for SSH was aborted"));
72
+ };
73
+ signal?.addEventListener("abort", onAbort, { once: true });
74
+ });
75
+ }
76
+ export {
77
+ isTransient,
78
+ waitForSsh
79
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawops/cli",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Deploy and manage self-hosted OpenClaw instances across clouds",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,18 +33,24 @@
33
33
  "test:pulumi": "vitest run tests/providers/aws/program.test.ts tests/providers/azure/program.test.ts",
34
34
  "test:changed": "vitest --changed",
35
35
  "test:integration": "vitest run --config vitest.integration.config.ts",
36
+ "test:cloud": "bash scripts/e2e/cloud.sh",
36
37
  "typecheck": "NODE_OPTIONS=--max-old-space-size=4096 tsc --noEmit",
37
38
  "lint": "eslint --max-warnings=0 src tests scripts",
38
39
  "gen:schemas": "tsx scripts/gen-schemas.ts",
39
40
  "changeset": "changeset",
40
41
  "release": "pnpm build && changeset publish",
41
- "test:mutation": "node scripts/dev/mutation-check.mjs"
42
+ "test:mutation": "node scripts/dev/mutation-check.mjs",
43
+ "graph": "node scripts/dev/graph.mjs",
44
+ "verify:pack": "node scripts/dev/verify-pack.mjs"
42
45
  },
43
46
  "dependencies": {
47
+ "@aws-sdk/client-bedrock": "^3.1063.0",
44
48
  "@aws-sdk/client-cloudwatch-logs": "^3.1063.0",
45
49
  "@aws-sdk/client-ec2": "^3.1063.0",
46
50
  "@aws-sdk/client-guardduty": "^3.1063.0",
47
51
  "@aws-sdk/client-iam": "^3.1063.0",
52
+ "@aws-sdk/client-s3": "3.1132.0",
53
+ "@aws-sdk/client-sts": "3.1132.0",
48
54
  "@modelcontextprotocol/sdk": "^1.0.0",
49
55
  "@pulumi/aws": "^6.0.0",
50
56
  "@pulumi/azure-native": "^2.0.0",
@@ -58,6 +64,7 @@
58
64
  "chalk": "^5.0.0",
59
65
  "citty": "^0.1.0",
60
66
  "conf": "^12.0.0",
67
+ "google-auth-library": "^11.0.2",
61
68
  "inquirer": "^9.0.0",
62
69
  "ora": "^8.0.0",
63
70
  "ssh2": "^1.0.0",
@@ -82,7 +89,8 @@
82
89
  },
83
90
  "pnpm": {
84
91
  "overrides": {
85
- "@pulumi/pulumi": "^3.0.0"
92
+ "@pulumi/pulumi": "^3.0.0",
93
+ "@smithy/types": "4.14.3"
86
94
  },
87
95
  "onlyBuiltDependencies": [
88
96
  "@pulumi/command",