@clawops/cli 1.4.0 → 1.6.0

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 (46) hide show
  1. package/README.md +40 -0
  2. package/dist/{apply-5RREMN3L.js → apply-SI3JKS4Z.js} +2 -2
  3. package/dist/automation-RULUSJBW.js +0 -0
  4. package/dist/{aws-FRE2JVAZ.js → aws-D7Y6LCGK.js} +52 -90
  5. package/dist/{azure-PVC3AQVC.js → azure-JQAMNVHN.js} +15 -56
  6. package/dist/{bootstrap-G4UZ2FKH.js → bootstrap-RY4VOSDJ.js} +2 -2
  7. package/dist/chunk-3QJBNAHW.js +0 -0
  8. package/dist/chunk-6ZFIFDBJ.js +0 -0
  9. package/dist/{chunk-UAA6YOOP.js → chunk-72FF7IK2.js} +2 -2
  10. package/dist/chunk-A2I76FTA.js +0 -0
  11. package/dist/chunk-BOPSG2LI.js +0 -0
  12. package/dist/{chunk-JYQZJMD3.js → chunk-C5NDLOK2.js} +41 -33
  13. package/dist/chunk-CX5SL5HP.js +0 -0
  14. package/dist/{chunk-4U3LTLWZ.js → chunk-GJEF6UQA.js} +1 -0
  15. package/dist/chunk-KGXPLI7W.js +0 -0
  16. package/dist/{chunk-ZVOEQCNW.js → chunk-LCKD7L7X.js} +1 -1
  17. package/dist/chunk-OIGTOLB3.js +0 -0
  18. package/dist/{chunk-ZONXY3C6.js → chunk-QHGFENYR.js} +2 -2
  19. package/dist/{chunk-T5EX55GP.js → chunk-QI75OVJK.js} +3 -3
  20. package/dist/chunk-XKT42M34.js +72 -0
  21. package/dist/chunk-YTH4L2GN.js +0 -0
  22. package/dist/chunk-ZFNPM2WG.js +0 -0
  23. package/dist/cli.js +311 -54
  24. package/dist/{context-PSGEX2D7.js → context-KPKBOWOP.js} +1 -1
  25. package/dist/errors-OK47MQFD.js +0 -0
  26. package/dist/firewall-XQGO7HWK.js +51 -0
  27. package/dist/{gcp-BXDTC6EK.js → gcp-6FT2A45S.js} +14 -63
  28. package/dist/{generate-Z7DBMANX.js → generate-YV7226Q7.js} +2 -2
  29. package/dist/js-yaml-PTEEG4FO.js +0 -0
  30. package/dist/local-DXBEVZ5C.js +0 -0
  31. package/dist/mcp-apps-KY44ED3Y.js +0 -0
  32. package/dist/mcp-wire-ZWIYMLEW.js +45 -0
  33. package/dist/outputs-DJHBY7EE.js +0 -0
  34. package/dist/overlay-store-ADZPD7EU.js +0 -0
  35. package/dist/{package-WAJRGBBM.js → package-EW3FS257.js} +1 -1
  36. package/dist/{pool-FBFHATDG.js → pool-JZGKPP6K.js} +2 -2
  37. package/dist/providers-2OABPW2E.js +0 -0
  38. package/dist/remote-config-QF5TT7GU.js +0 -0
  39. package/dist/secrets-SVZWNAPK.js +0 -0
  40. package/dist/{server-IWLZJ4EU.js → server-3DEZHUWB.js} +9 -9
  41. package/dist/{ssh-IQXNME3D.js → ssh-E2AMBPZY.js} +1 -1
  42. package/dist/state-PRBIIN7I.js +0 -0
  43. package/dist/store-SDUR52Z5.js +0 -0
  44. package/dist/validate-T5M5EHSJ.js +0 -0
  45. package/package.json +26 -14
  46. package/dist/firewall-YYDOWDDP.js +0 -36
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # clawops
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@clawops/cli)](https://www.npmjs.com/package/@clawops/cli)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@clawops/cli)](https://www.npmjs.com/package/@clawops/cli)
5
+
3
6
  MCP-native infrastructure ops for OpenClaw — with read-only mode, destructive-action confirmation, and audit logs built in.
4
7
 
5
8
  **clawops** is a CLI and [MCP](https://modelcontextprotocol.io/) server for deploying and operating
@@ -9,6 +12,27 @@ and Cursor drive them through typed MCP tools with explicit safety controls.
9
12
 
10
13
  ---
11
14
 
15
+ ## What's new in v1.5.0
16
+
17
+ **`clawops mcp wire`** — wire the OpenClaw gateway's own AI as an MCP client of clawops.
18
+ Once wired, in-conversation commands like "check if my stack is healthy" or "show me the last 20
19
+ log lines" invoke the real `clawops` CLI rather than having the gateway AI guess.
20
+
21
+ ```bash
22
+ clawops mcp wire # wire the default stack
23
+ clawops mcp wire --stack prod # target a named stack
24
+ clawops mcp wire --force # bypass gateway version check
25
+ ```
26
+
27
+ Version-gated: requires OpenClaw ≥ 2026.4 on the gateway side. If the version is older, the
28
+ command surfaces a clear upgrade prompt and exits cleanly.
29
+
30
+ The `clawops setup` wizard now also asks at the end of a successful deploy:
31
+ *"Should the OpenClaw gateway's AI also be able to manage this stack?"* — accepting wires the
32
+ client automatically over the same SSH session.
33
+
34
+ ---
35
+
12
36
  ## What's new in v1.4.0
13
37
 
14
38
  **`clawops monitor`** — live dashboard for any deployed stack. Shows gateway health, container
@@ -247,9 +271,13 @@ clawops down --yes # Destroy local-provider stack
247
271
  | `backup` | Create or restore an OpenClaw state backup |
248
272
  | `stacks` | List named stacks and their state |
249
273
  | `doctor` | Check Node version, config, SSH key, provider credentials, and Pulumi home |
274
+ | `secret` | Manage secrets: `list`, `set`, `delete`, `rotate`, `audit` |
275
+ | `monitor` | Live dashboard: gateway health, container stats, log tail, stack picker |
250
276
  | `mcp serve` | Start the embedded MCP server (stdio or HTTP) |
251
277
  | `mcp install` | Interactively wire clawops into AI editors |
278
+ | `mcp wire` | Wire the gateway's AI as an MCP client of clawops |
252
279
  | `help` | List all commands and global flags |
280
+ | `bug` | Open a pre-filled GitHub issue with system context from `doctor` |
253
281
 
254
282
  Full flag reference: `clawops <command> --help`
255
283
 
@@ -297,6 +325,18 @@ The wizard resolves the absolute binary path automatically so app launchers can
297
325
  without inheriting your shell's `PATH`. See [Connect an AI editor](#connect-an-ai-editor) above
298
326
  for manual config paths.
299
327
 
328
+ ### Wire the gateway AI
329
+
330
+ The OpenClaw gateway runs its own AI agent. Once wired, that agent can call clawops directly
331
+ instead of guessing at infrastructure state:
332
+
333
+ ```bash
334
+ clawops mcp wire --stack prod # write MCP client entry into gateway config + restart
335
+ ```
336
+
337
+ Requires OpenClaw ≥ 2026.4 on the gateway. The `clawops setup` wizard offers this step
338
+ automatically after a successful deploy.
339
+
300
340
  ### Stdio mode (Claude Code / Cursor / VS Code)
301
341
 
302
342
  Start the server manually or confirm your config is correct:
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  applyPlan
4
- } from "./chunk-ZONXY3C6.js";
4
+ } from "./chunk-QHGFENYR.js";
5
5
  import "./chunk-YTH4L2GN.js";
6
6
  import "./chunk-6ZFIFDBJ.js";
7
7
  import "./chunk-BOPSG2LI.js";
8
- import "./chunk-T5EX55GP.js";
8
+ import "./chunk-QI75OVJK.js";
9
9
  import "./chunk-ZFNPM2WG.js";
10
10
  import "./chunk-A2I76FTA.js";
11
11
  import "./chunk-CX5SL5HP.js";
File without changes
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ makeStartupScript
4
+ } from "./chunk-XKT42M34.js";
2
5
 
3
6
  // src/providers/aws/index.ts
4
7
  import process2 from "process";
@@ -10,7 +13,7 @@ var awsProgram = async () => {
10
13
  const [pulumi, aws, { resolveIngressCidrs, detectEgressIp }] = await Promise.all([
11
14
  import("@pulumi/pulumi"),
12
15
  import("@pulumi/aws"),
13
- import("./firewall-YYDOWDDP.js")
16
+ import("./firewall-XQGO7HWK.js")
14
17
  ]);
15
18
  const cfg = new pulumi.Config();
16
19
  const instanceType = cfg.get("instanceType") ?? "t3.small";
@@ -21,20 +24,21 @@ var awsProgram = async () => {
21
24
  const sshCidrs = cfg.get("sshCidrs") ?? "";
22
25
  const gatewayCidrs = cfg.get("gatewayCidrs") ?? "";
23
26
  const bedrockEnabled = cfg.get("bedrockEnabled") === "true";
27
+ const pinnedAmiId = cfg.get("amiId");
24
28
  const sshPublicKey = cfg.get("sshPublicKey");
25
29
  if (!sshPublicKey) {
26
30
  throw new Error(
27
31
  'Stack config "sshPublicKey" is required for the AWS adapter. Set it with: pulumi config set --stack <name> sshPublicKey "ssh-ed25519 ..."'
28
32
  );
29
33
  }
30
- const detectedIp = accessMode === "auto" ? await detectEgressIp("https://checkip.amazonaws.com") : "";
34
+ const egressResult = accessMode === "auto" ? await detectEgressIp("https://ifconfig.me") : { ok: true, ip: "" };
31
35
  if (accessMode === "open") {
32
36
  process.stderr.write(
33
37
  "[clawops] WARNING: accessMode=open allows 0.0.0.0/0 on SSH and gateway ports. Only use this for development/sandbox stacks.\n"
34
38
  );
35
39
  }
36
- const sshIngressCidrs = resolveIngressCidrs(accessMode, allowedCidrs, sshCidrs, detectedIp);
37
- const gatewayIngressCidrs = resolveIngressCidrs(accessMode, allowedCidrs, gatewayCidrs, detectedIp);
40
+ const sshIngressCidrs = resolveIngressCidrs(accessMode, allowedCidrs, sshCidrs, egressResult);
41
+ const gatewayIngressCidrs = resolveIngressCidrs(accessMode, allowedCidrs, gatewayCidrs, egressResult);
38
42
  const vpc = new aws.ec2.Vpc("clawops-vpc", {
39
43
  cidrBlock: "10.0.0.0/16",
40
44
  enableDnsHostnames: true,
@@ -68,33 +72,36 @@ var awsProgram = async () => {
68
72
  subnetId: subnet.id,
69
73
  routeTableId: routeTable.id
70
74
  });
71
- const ingressRules = [
72
- ...sshIngressCidrs.map((cidr) => ({
73
- protocol: "tcp",
75
+ const sg = new aws.ec2.SecurityGroup("clawops-sg", {
76
+ vpcId: vpc.id,
77
+ description: "clawops managed security group",
78
+ tags: { Name: "clawops" }
79
+ });
80
+ sshIngressCidrs.forEach((cidr, i) => {
81
+ new aws.vpc.SecurityGroupIngressRule(`clawops-sg-ssh-${i}`, {
82
+ securityGroupId: sg.id,
83
+ ipProtocol: "tcp",
74
84
  fromPort: SSH_PORT,
75
85
  toPort: SSH_PORT,
76
- cidrBlocks: [cidr],
77
- description: "SSH"
78
- })),
79
- ...gatewayIngressCidrs.map((cidr) => ({
80
- protocol: "tcp",
86
+ cidrIpv4: cidr,
87
+ tags: { Name: `clawops-ssh-${i}` }
88
+ });
89
+ });
90
+ gatewayIngressCidrs.forEach((cidr, i) => {
91
+ new aws.vpc.SecurityGroupIngressRule(`clawops-sg-gw-${i}`, {
92
+ securityGroupId: sg.id,
93
+ ipProtocol: "tcp",
81
94
  fromPort: GATEWAY_PORT,
82
95
  toPort: GATEWAY_PORT,
83
- cidrBlocks: [cidr],
84
- description: "OpenClaw gateway"
85
- }))
86
- ];
87
- const sg = new aws.ec2.SecurityGroup("clawops-sg", {
88
- vpcId: vpc.id,
89
- ingress: ingressRules,
90
- egress: [{
91
- protocol: "-1",
92
- fromPort: 0,
93
- toPort: 0,
94
- cidrBlocks: ["0.0.0.0/0"],
95
- description: "Allow all egress"
96
- }],
97
- tags: { Name: "clawops" }
96
+ cidrIpv4: cidr,
97
+ tags: { Name: `clawops-gateway-${i}` }
98
+ });
99
+ });
100
+ new aws.vpc.SecurityGroupEgressRule("clawops-sg-egress", {
101
+ securityGroupId: sg.id,
102
+ ipProtocol: "-1",
103
+ cidrIpv4: "0.0.0.0/0",
104
+ tags: { Name: "clawops-egress" }
98
105
  });
99
106
  const role = new aws.iam.Role("clawops-role", {
100
107
  assumeRolePolicy: JSON.stringify({
@@ -112,10 +119,19 @@ var awsProgram = async () => {
112
119
  policyArn: "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
113
120
  });
114
121
  if (bedrockEnabled) {
115
- new aws.iam.RolePolicyAttachment("clawops-bedrock", {
122
+ new aws.iam.RolePolicy("clawops-bedrock-invoke", {
116
123
  role: role.name,
117
- // FullAccess required for bedrock:InvokeModel — ReadOnly only covers describe/list.
118
- policyArn: "arn:aws:iam::aws:policy/AmazonBedrockFullAccess"
124
+ policy: JSON.stringify({
125
+ Version: "2012-10-17",
126
+ Statement: [{
127
+ Effect: "Allow",
128
+ Action: [
129
+ "bedrock:InvokeModel",
130
+ "bedrock:InvokeModelWithResponseStream"
131
+ ],
132
+ Resource: "*"
133
+ }]
134
+ })
119
135
  });
120
136
  }
121
137
  const instanceProfile = new aws.iam.InstanceProfile("clawops-profile", {
@@ -125,7 +141,7 @@ var awsProgram = async () => {
125
141
  publicKey: sshPublicKey,
126
142
  tags: { Name: "clawops" }
127
143
  });
128
- const ami = await aws.ec2.getAmi({
144
+ const amiId = pinnedAmiId ?? (await aws.ec2.getAmi({
129
145
  mostRecent: true,
130
146
  owners: ["099720109477"],
131
147
  // Canonical
@@ -133,15 +149,17 @@ var awsProgram = async () => {
133
149
  { name: "name", values: ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"] },
134
150
  { name: "virtualization-type", values: ["hvm"] }
135
151
  ]
136
- });
152
+ })).id;
153
+ process.stderr.write(`[clawops] Using AMI: ${amiId}
154
+ `);
137
155
  const instance = new aws.ec2.Instance("clawops-instance", {
138
- ami: ami.id,
156
+ ami: amiId,
139
157
  instanceType,
140
158
  subnetId: subnet.id,
141
159
  vpcSecurityGroupIds: [sg.id],
142
160
  iamInstanceProfile: instanceProfile.name,
143
161
  keyName: keyPair.keyName,
144
- userData: makeStartupScript(openclawVersion, bedrockEnabled),
162
+ userData: makeStartupScript({ openclawVersion, os: "ubuntu", bedrockEnabled }),
145
163
  // IMDSv2 with hopLimit=2 so Docker containers on this host can reach IMDS
146
164
  // and obtain the instance role credentials (required for Bedrock access).
147
165
  metadataOptions: {
@@ -169,62 +187,6 @@ var awsProgram = async () => {
169
187
  provisionedAt: (/* @__PURE__ */ new Date()).toISOString()
170
188
  };
171
189
  };
172
- function makeStartupScript(openclawVersion, bedrockEnabled) {
173
- const bedrockEnvFlag = bedrockEnabled ? " -e AWS_DEFAULT_REGION=$(curl -sf http://169.254.169.254/latest/meta-data/placement/region || echo us-east-1) \\\n" : "";
174
- return `#!/bin/bash
175
- set -euo pipefail
176
-
177
- # Create clawops user with SSH access
178
- id -u clawops &>/dev/null || useradd -m -s /bin/bash clawops
179
- mkdir -p /home/clawops/.ssh
180
- chmod 700 /home/clawops/.ssh
181
- chown clawops:clawops /home/clawops/.ssh
182
-
183
- # Install Docker if not present (AWS AMI is ubuntu-22.04)
184
- if ! command -v docker &>/dev/null; then
185
- export DEBIAN_FRONTEND=noninteractive
186
- apt-get update -q
187
- apt-get install -y -q ca-certificates curl gnupg lsb-release
188
- install -m 0755 -d /etc/apt/keyrings
189
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg \\
190
- | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
191
- chmod a+r /etc/apt/keyrings/docker.gpg
192
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \\
193
- https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \\
194
- > /etc/apt/sources.list.d/docker.list
195
- apt-get update -q
196
- apt-get install -y -q docker-ce docker-ce-cli containerd.io
197
- systemctl enable --now docker
198
- fi
199
-
200
- usermod -aG docker clawops
201
-
202
- # Pull OpenClaw image
203
- OPENCLAW_VERSION="${openclawVersion}"
204
- docker pull ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION}
205
-
206
- # Create default openclaw.json if not present
207
- # apply.ts will overwrite this with the plan's config overlay post-provisioning.
208
- OPENCLAW_CONFIG=/home/clawops/openclaw.json
209
- if [ ! -f "\${OPENCLAW_CONFIG}" ]; then
210
- cat > "\${OPENCLAW_CONFIG}" <<'OPENCLAWJSON'
211
- {"meta":{"lastTouchedVersion":"2026.4"},"gateway":{"port":18789,"auth":{"mode":"token"}},"models":{},"channels":{}}
212
- OPENCLAWJSON
213
- chown clawops:clawops "\${OPENCLAW_CONFIG}"
214
- fi
215
-
216
- # Start OpenClaw container
217
- docker stop openclaw 2>/dev/null || true
218
- docker rm openclaw 2>/dev/null || true
219
- docker run -d \\
220
- --name openclaw \\
221
- --restart unless-stopped \\
222
- -p ${GATEWAY_PORT}:${GATEWAY_PORT} \\
223
- -v "\${OPENCLAW_CONFIG}":/app/config.json:ro \\
224
- ${bedrockEnvFlag} ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION} \\
225
- node openclaw.mjs gateway run --allow-unconfigured
226
- `;
227
- }
228
190
 
229
191
  // src/providers/aws/index.ts
230
192
  var INSTANCE_TYPE_MAP = {
@@ -1,4 +1,7 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ makeStartupScript
4
+ } from "./chunk-XKT42M34.js";
2
5
 
3
6
  // src/providers/azure/index.ts
4
7
  import process2 from "process";
@@ -12,7 +15,7 @@ var azureProgram = async () => {
12
15
  import("@pulumi/pulumi"),
13
16
  import("@pulumi/azure-native"),
14
17
  import("@pulumi/random"),
15
- import("./firewall-YYDOWDDP.js")
18
+ import("./firewall-XQGO7HWK.js")
16
19
  ]);
17
20
  const cfg = new pulumi.Config();
18
21
  const stackName = pulumi.getStack();
@@ -31,14 +34,15 @@ var azureProgram = async () => {
31
34
  'Stack config "sshPublicKey" is required for the Azure adapter. Set it with: pulumi config set --stack <name> sshPublicKey "ssh-ed25519 ..."'
32
35
  );
33
36
  }
34
- const detectedIp = accessMode === "auto" ? await detectEgressIp("https://ifconfig.me") : "";
37
+ const clientConfig = await azure.authorization.getClientConfig({});
38
+ const egressResult = accessMode === "auto" ? await detectEgressIp("https://ifconfig.me") : { ok: true, ip: "" };
35
39
  if (accessMode === "open") {
36
40
  process.stderr.write(
37
41
  "[clawops] WARNING: accessMode=open allows 0.0.0.0/0 on SSH and gateway ports. Only use this for development/sandbox stacks.\n"
38
42
  );
39
43
  }
40
- const sshIngressCidrs = resolveIngressCidrs(accessMode, allowedCidrs, sshCidrs, detectedIp);
41
- const gatewayIngressCidrs = resolveIngressCidrs(accessMode, allowedCidrs, gatewayCidrs, detectedIp);
44
+ const sshIngressCidrs = resolveIngressCidrs(accessMode, allowedCidrs, sshCidrs, egressResult);
45
+ const gatewayIngressCidrs = resolveIngressCidrs(accessMode, allowedCidrs, gatewayCidrs, egressResult);
42
46
  const rg = new azure.resources.ResourceGroup("clawops-rg", {
43
47
  resourceGroupName,
44
48
  location
@@ -111,7 +115,7 @@ var azureProgram = async () => {
111
115
  osProfile: {
112
116
  adminUsername: "clawops",
113
117
  computerName: "clawops",
114
- customData: Buffer.from(makeStartupScript(openclawVersion)).toString("base64"),
118
+ customData: Buffer.from(makeStartupScript({ openclawVersion, os: "ubuntu" })).toString("base64"),
115
119
  linuxConfiguration: {
116
120
  disablePasswordAuthentication: true,
117
121
  ssh: {
@@ -125,8 +129,10 @@ var azureProgram = async () => {
125
129
  storageProfile: {
126
130
  imageReference: {
127
131
  publisher: "Canonical",
128
- offer: "UbuntuServer",
129
- sku: "22.04-LTS",
132
+ // Ubuntu 22.04 LTS — Canonical migrated from the legacy 'UbuntuServer'
133
+ // offer to this naming scheme. 22_04-lts-gen2 is available in all regions.
134
+ offer: "0001-com-ubuntu-server-jammy",
135
+ sku: "22_04-lts-gen2",
130
136
  version: "latest"
131
137
  },
132
138
  osDisk: {
@@ -153,10 +159,10 @@ var azureProgram = async () => {
153
159
  enableRbacAuthorization: true
154
160
  }
155
161
  });
162
+ const kvSecretsUserRoleId = `/subscriptions/${clientConfig.subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/4633458b-17de-408a-b874-0445c86b69e6`;
156
163
  new azure.authorization.RoleAssignment("clawops-kv-role", {
157
164
  scope: kv.id,
158
- roleDefinitionId: "/providers/Microsoft.Authorization/roleDefinitions/4633458b-17de-408a-b874-0445c86b69e6",
159
- // Key Vault Secrets User
165
+ roleDefinitionId: kvSecretsUserRoleId,
160
166
  principalId: pulumi.output(vm.identity).apply((i) => i?.principalId ?? ""),
161
167
  principalType: "ServicePrincipal"
162
168
  });
@@ -183,53 +189,6 @@ var azureProgram = async () => {
183
189
  provisionedAt: (/* @__PURE__ */ new Date()).toISOString()
184
190
  };
185
191
  };
186
- function makeStartupScript(openclawVersion) {
187
- return `#!/bin/bash
188
- set -euo pipefail
189
-
190
- # Install Docker if not present
191
- if ! command -v docker &>/dev/null; then
192
- apt-get update -q
193
- apt-get install -y -q ca-certificates curl gnupg lsb-release
194
- install -m 0755 -d /etc/apt/keyrings
195
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg \\
196
- | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
197
- chmod a+r /etc/apt/keyrings/docker.gpg
198
- echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \\
199
- https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" \\
200
- > /etc/apt/sources.list.d/docker.list
201
- apt-get update -q
202
- apt-get install -y -q docker-ce docker-ce-cli containerd.io
203
- systemctl enable --now docker
204
- fi
205
-
206
- usermod -aG docker clawops
207
-
208
- # Pull OpenClaw image
209
- OPENCLAW_VERSION="${openclawVersion}"
210
- docker pull ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION}
211
-
212
- # Create default openclaw.json if not present
213
- OPENCLAW_CONFIG=/home/clawops/openclaw.json
214
- if [ ! -f "\${OPENCLAW_CONFIG}" ]; then
215
- cat > "\${OPENCLAW_CONFIG}" <<'OPENCLAWJSON'
216
- {"meta":{"lastTouchedVersion":"2026.4"},"gateway":{"port":18789,"auth":{"mode":"token"}},"models":{},"channels":{}}
217
- OPENCLAWJSON
218
- chown clawops:clawops "\${OPENCLAW_CONFIG}"
219
- fi
220
-
221
- # Start OpenClaw container
222
- docker stop openclaw 2>/dev/null || true
223
- docker rm openclaw 2>/dev/null || true
224
- docker run -d \\
225
- --name openclaw \\
226
- --restart unless-stopped \\
227
- -p 18789:18789 \\
228
- -v "\${OPENCLAW_CONFIG}":/app/config.json:ro \\
229
- ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION} \\
230
- node openclaw.mjs gateway run --allow-unconfigured
231
- `;
232
- }
233
192
 
234
193
  // src/providers/azure/index.ts
235
194
  var INSTANCE_TYPE_MAP = {
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  acquireSession
4
- } from "./chunk-ZVOEQCNW.js";
5
- import "./chunk-4U3LTLWZ.js";
4
+ } from "./chunk-LCKD7L7X.js";
5
+ import "./chunk-GJEF6UQA.js";
6
6
  import {
7
7
  writeLocalState
8
8
  } from "./chunk-A2I76FTA.js";
File without changes
File without changes
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-YTH4L2GN.js";
5
5
  import {
6
6
  buildContext
7
- } from "./chunk-T5EX55GP.js";
7
+ } from "./chunk-QI75OVJK.js";
8
8
  import {
9
9
  getConfig
10
10
  } from "./chunk-CX5SL5HP.js";
@@ -45,7 +45,7 @@ async function generatePlan(intent, _opts) {
45
45
  "plan/apply is not supported for the local provider. Use `clawops up` directly."
46
46
  );
47
47
  }
48
- const { version } = await import("./package-WAJRGBBM.js");
48
+ const { version } = await import("./package-EW3FS257.js");
49
49
  const config = getConfig();
50
50
  const instanceType = intent.instanceType ?? "small";
51
51
  const openclawVersion = intent.openclawVersion ?? "latest";
File without changes
File without changes
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildContext
4
- } from "./chunk-T5EX55GP.js";
4
+ } from "./chunk-QI75OVJK.js";
5
5
  import {
6
6
  acquireSession,
7
7
  drainPool
8
- } from "./chunk-ZVOEQCNW.js";
8
+ } from "./chunk-LCKD7L7X.js";
9
9
  import {
10
10
  OPENCLAW_CONFIG,
11
11
  atomicWriteConfig,
@@ -17,7 +17,7 @@ import {
17
17
 
18
18
  // src/cli/commands/monitor.ts
19
19
  import { defineCommand } from "citty";
20
- import process from "process";
20
+ import process2 from "process";
21
21
 
22
22
  // src/output/human.ts
23
23
  import chalk from "chalk";
@@ -37,6 +37,12 @@ function info(msg) {
37
37
  function spinner(text) {
38
38
  return ora(text).start();
39
39
  }
40
+ var REPO_URL = "https://github.com/dfridkin/clawops";
41
+ function printCta() {
42
+ process.stdout.write(
43
+ "\n" + chalk.dim(" Thank you for using clawops! If it has been useful, star the project:") + "\n" + chalk.dim(" " + REPO_URL) + "\n\n" + chalk.dim(" Found a bug? Open an issue:") + "\n" + chalk.dim(" " + REPO_URL + "/issues") + "\n\n"
44
+ );
45
+ }
40
46
 
41
47
  // src/cli/commands/monitor.ts
42
48
  var GATEWAY_PORT = 18789;
@@ -148,7 +154,7 @@ function renderSnapshot(snap, opts) {
148
154
  return lines.join("\n");
149
155
  }
150
156
  async function probeEntries() {
151
- const { buildContext: buildContext2 } = await import("./context-PSGEX2D7.js");
157
+ const { buildContext: buildContext2 } = await import("./context-KPKBOWOP.js");
152
158
  const { getConfig } = await import("./store-SDUR52Z5.js");
153
159
  const config = getConfig();
154
160
  if (!config) return [];
@@ -228,7 +234,7 @@ async function deleteFromRegistry(name) {
228
234
  setConfig(updated);
229
235
  }
230
236
  async function runStackMenu(ac, setKeyHandler, noColor) {
231
- process.stdout.write("\x1B[2J\x1B[H\n Checking stacks...\n");
237
+ process2.stdout.write("\x1B[2J\x1B[H\n Checking stacks...\n");
232
238
  const entries = await probeEntries();
233
239
  if (ac.signal.aborted) return null;
234
240
  let selectedIdx = 0;
@@ -238,7 +244,7 @@ async function runStackMenu(ac, setKeyHandler, noColor) {
238
244
  return showAll ? entries : entries.filter((e) => e.deployed);
239
245
  }
240
246
  function redraw() {
241
- process.stdout.write("\x1B[2J\x1B[H" + renderMenu(entries, selectedIdx, showAll, noColor, confirmDelete));
247
+ process2.stdout.write("\x1B[2J\x1B[H" + renderMenu(entries, selectedIdx, showAll, noColor, confirmDelete));
242
248
  }
243
249
  redraw();
244
250
  return new Promise((resolve) => {
@@ -317,7 +323,7 @@ async function runDashboard(session, stackName, opts, ac, setKeyHandler, menuMod
317
323
  noColor: opts.noColor,
318
324
  menuMode
319
325
  });
320
- process.stdout.write("\x1B[2J\x1B[H" + out + "\n");
326
+ process2.stdout.write("\x1B[2J\x1B[H" + out + "\n");
321
327
  }
322
328
  async function doRefresh() {
323
329
  cancelRefresh();
@@ -327,7 +333,7 @@ async function runDashboard(session, stackName, opts, ac, setKeyHandler, menuMod
327
333
  redraw();
328
334
  } catch (err) {
329
335
  if (!ac.signal.aborted) {
330
- process.stdout.write("\x1B[2J\x1B[H Error gathering snapshot: " + (err instanceof Error ? err.message : String(err)) + "\n");
336
+ process2.stdout.write("\x1B[2J\x1B[H Error gathering snapshot: " + (err instanceof Error ? err.message : String(err)) + "\n");
331
337
  }
332
338
  }
333
339
  if (!ac.signal.aborted) scheduleRefresh();
@@ -373,20 +379,20 @@ var monitor_default = defineCommand({
373
379
  async run({ args }) {
374
380
  const intervalSec = Math.max(2, parseInt(String(args.interval ?? "10"), 10) || 10);
375
381
  const tailLines = Math.max(1, parseInt(String(args.tail ?? "10"), 10) || 10);
376
- const isTTY = Boolean(process.stdout.isTTY);
382
+ const isTTY = Boolean(process2.stdout.isTTY);
377
383
  const noColor = Boolean(args["no-color"]) || !isTTY;
378
- const { buildContext: buildContext2 } = await import("./context-PSGEX2D7.js");
379
- const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-FBFHATDG.js");
384
+ const { buildContext: buildContext2 } = await import("./context-KPKBOWOP.js");
385
+ const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-JZGKPP6K.js");
380
386
  const ac = new AbortController();
381
- process.on("SIGINT", () => ac.abort());
382
- process.on("SIGTERM", () => ac.abort());
387
+ process2.on("SIGINT", () => ac.abort());
388
+ process2.on("SIGTERM", () => ac.abort());
383
389
  if (args.stack) {
384
390
  const ctx = buildContext2(args);
385
391
  let conn;
386
392
  if (ctx.adapter.name === "local") {
387
393
  if (!ctx.localState) {
388
394
  failure("Stack is not bootstrapped. Run `clawops up` first.");
389
- process.exit(4);
395
+ process2.exit(4);
390
396
  }
391
397
  const ls = ctx.localState;
392
398
  conn = { host: ls.sshHost, port: ls.sshPort, user: ls.sshUser, privateKeyPath: ls.privateKeyPath, knownHostsPath: ls.knownHostsPath };
@@ -399,7 +405,7 @@ var monitor_default = defineCommand({
399
405
  );
400
406
  if (!outputs["publicIp"]) {
401
407
  failure("Stack has no outputs. Run `clawops up` first.");
402
- process.exit(4);
408
+ process2.exit(4);
403
409
  }
404
410
  const base = extractBaseOutputs(outputs);
405
411
  conn = ctx.adapter.getConnectionInfo({
@@ -412,7 +418,7 @@ var monitor_default = defineCommand({
412
418
  const { session: session2, release: release2 } = await acquireSession2({ ...conn, signal: ac.signal });
413
419
  try {
414
420
  const snap = await gatherSnapshot(session2, ac.signal, tailLines);
415
- process.stdout.write(
421
+ process2.stdout.write(
416
422
  renderSnapshot(snap, { stackName: ctx.stackName, intervalSec, showLogs: true, noColor }) + "\n"
417
423
  );
418
424
  } finally {
@@ -422,21 +428,21 @@ var monitor_default = defineCommand({
422
428
  return;
423
429
  }
424
430
  const { session, release } = await acquireSession2({ ...conn, signal: ac.signal });
425
- process.stdin.setRawMode(true);
426
- process.stdin.resume();
427
- process.stdin.setEncoding("utf8");
428
- process.stdout.write("\x1B[?25l");
431
+ process2.stdin.setRawMode(true);
432
+ process2.stdin.resume();
433
+ process2.stdin.setEncoding("utf8");
434
+ process2.stdout.write("\x1B[?25l");
429
435
  let keyHandler2 = () => {
430
436
  };
431
- process.stdin.on("data", (key) => keyHandler2(key));
437
+ process2.stdin.on("data", (key) => keyHandler2(key));
432
438
  try {
433
439
  await runDashboard(session, ctx.stackName, { intervalSec, tailLines, noColor }, ac, (fn) => {
434
440
  keyHandler2 = fn;
435
441
  }, false);
436
442
  } finally {
437
- process.stdin.setRawMode(false);
438
- process.stdin.pause();
439
- process.stdout.write("\x1B[?25h\n");
443
+ process2.stdin.setRawMode(false);
444
+ process2.stdin.pause();
445
+ process2.stdout.write("\x1B[?25h\n");
440
446
  release();
441
447
  drainPool2();
442
448
  }
@@ -444,15 +450,15 @@ var monitor_default = defineCommand({
444
450
  }
445
451
  if (!isTTY) {
446
452
  failure("Pass --stack <name> or run in a TTY for interactive stack selection.");
447
- process.exit(2);
453
+ process2.exit(2);
448
454
  }
449
- process.stdin.setRawMode(true);
450
- process.stdin.resume();
451
- process.stdin.setEncoding("utf8");
452
- process.stdout.write("\x1B[?25l");
455
+ process2.stdin.setRawMode(true);
456
+ process2.stdin.resume();
457
+ process2.stdin.setEncoding("utf8");
458
+ process2.stdout.write("\x1B[?25l");
453
459
  let keyHandler = () => {
454
460
  };
455
- process.stdin.on("data", (key) => keyHandler(key));
461
+ process2.stdin.on("data", (key) => keyHandler(key));
456
462
  try {
457
463
  while (!ac.signal.aborted) {
458
464
  const stackName = await runStackMenu(ac, (fn) => {
@@ -497,9 +503,9 @@ var monitor_default = defineCommand({
497
503
  }
498
504
  }
499
505
  } finally {
500
- process.stdin.setRawMode(false);
501
- process.stdin.pause();
502
- process.stdout.write("\x1B[?25h\n");
506
+ process2.stdin.setRawMode(false);
507
+ process2.stdin.pause();
508
+ process2.stdout.write("\x1B[?25h\n");
503
509
  }
504
510
  }
505
511
  });
@@ -750,6 +756,8 @@ export {
750
756
  warn,
751
757
  info,
752
758
  spinner,
759
+ REPO_URL,
760
+ printCta,
753
761
  resolveConn,
754
762
  errText,
755
763
  okText,
File without changes