@clawops/cli 1.1.0 → 1.2.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 (39) hide show
  1. package/README.md +115 -44
  2. package/dist/apply-KBF7OPWL.js +14 -0
  3. package/dist/{automation-JG2YBEL7.js → automation-RULUSJBW.js} +2 -2
  4. package/dist/{aws-A3323GNM.js → aws-KQBWY43W.js} +12 -10
  5. package/dist/{azure-LNWBK2ZI.js → azure-4EJFVJZL.js} +4 -3
  6. package/dist/{bootstrap-7ZS2GLFJ.js → bootstrap-UYCOEJQX.js} +48 -12
  7. package/dist/{chunk-LT24GUUO.js → chunk-3QJBNAHW.js} +1 -1
  8. package/dist/{chunk-BA6MDFZK.js → chunk-4U3LTLWZ.js} +2 -83
  9. package/dist/{chunk-PERSDQMT.js → chunk-ACYJBSLJ.js} +7 -7
  10. package/dist/chunk-BOPSG2LI.js +77 -0
  11. package/dist/{chunk-PRYLTCS4.js → chunk-BRPU7AQC.js} +1 -1
  12. package/dist/{chunk-ALSUDYA7.js → chunk-CX5SL5HP.js} +1 -1
  13. package/dist/{chunk-ZSE4QRKE.js → chunk-KGXPLI7W.js} +19 -1
  14. package/dist/chunk-LU63NZD3.js +116 -0
  15. package/dist/chunk-OIGTOLB3.js +99 -0
  16. package/dist/chunk-QJ6ERXHN.js +266 -0
  17. package/dist/{chunk-D4UAHAKI.js → chunk-SSB6SGPQ.js} +10 -6
  18. package/dist/chunk-UDNZUSKA.js +97 -0
  19. package/dist/chunk-ZVOEQCNW.js +92 -0
  20. package/dist/cli.js +1340 -115
  21. package/dist/context-VUAYRAJY.js +10 -0
  22. package/dist/errors-OK47MQFD.js +21 -0
  23. package/dist/{gcp-OHWCTFLL.js → gcp-SKURG3L6.js} +4 -3
  24. package/dist/generate-PZPLG3ZM.js +14 -0
  25. package/dist/js-yaml-PTEEG4FO.js +2647 -0
  26. package/dist/mcp-apps-KY44ED3Y.js +11 -0
  27. package/dist/{outputs-6DAVEEAZ.js → outputs-DJHBY7EE.js} +2 -2
  28. package/dist/{package-FA6UOG2I.js → package-SE3M4NJA.js} +1 -1
  29. package/dist/{pool-FAVU7WCW.js → pool-FBFHATDG.js} +3 -2
  30. package/dist/remote-config-JQ77SRC2.js +21 -0
  31. package/dist/secrets-SVZWNAPK.js +7 -0
  32. package/dist/{server-V5ZFL76Y.js → server-4DNHLGEG.js} +64 -150
  33. package/dist/ssh-IQXNME3D.js +8 -0
  34. package/dist/{store-ARJ2EO6L.js → store-SDUR52Z5.js} +2 -2
  35. package/package.json +1 -1
  36. package/dist/apply-DPS23GLL.js +0 -12
  37. package/dist/chunk-UCIVU24B.js +0 -56
  38. package/dist/context-T52JWL3P.js +0 -10
  39. package/dist/generate-55MITRXU.js +0 -14
package/README.md CHANGED
@@ -45,78 +45,140 @@ and Cursor drive them through typed MCP tools with explicit safety controls.
45
45
 
46
46
  ---
47
47
 
48
- ## Quick Start — local VM (fastest path)
48
+ ## Quick Start
49
49
 
50
- The local provider needs only a Linux host reachable over SSH — no cloud account required.
50
+ ```bash
51
+ npm install -g @clawops/cli
52
+ clawops setup
53
+ ```
54
+
55
+ `clawops setup` is an interactive wizard that gets OpenClaw running in about 2 minutes. It
56
+ handles everything in one flow — no config files to write by hand, no commands to memorize.
57
+
58
+ ### What the wizard does
59
+
60
+ **Step 1 — Choose a deployment target**
61
+
62
+ Pick an existing server you can SSH into (Linux or macOS), or a new cloud VM on AWS, GCP, or
63
+ Azure. Cloud deployments walk you through authenticating with the provider CLI if you aren't
64
+ already signed in.
65
+
66
+ **Step 2 — Pick an LLM provider**
67
+
68
+ Choose from Anthropic, OpenAI, Amazon Bedrock, Ollama, or others. The wizard prompts for your
69
+ API key and saves it locally (in `~/.clawops/secrets/`, chmod 600) — it is never sent anywhere
70
+ except to OpenClaw on the target host when the config is applied.
71
+
72
+ **Step 3 — Add chat integrations (optional)**
73
+
74
+ Select any combination of Discord, Telegram, Slack, WhatsApp, or Teams. The wizard collects each
75
+ integration's bot token the same way as the API key — paste it in, reference an env var, or point
76
+ to a file.
77
+
78
+ **Step 4 — Wire your AI editor**
79
+
80
+ Select which AI apps should have access to clawops — Claude Desktop, Claude Code, Cursor,
81
+ Windsurf, VS Code, and Zed are all supported. The wizard writes an MCP server entry into each
82
+ app's config file using the absolute binary path so the app can launch it independently.
51
83
 
52
- **Prerequisites:** Node.js 22, an SSH key, a Linux host (Ubuntu/Debian/RHEL) you can reach.
84
+ **Step 5 Deploy**
85
+
86
+ The wizard bootstraps OpenClaw on the target host over SSH (installs Docker, pulls the image,
87
+ starts the container), applies your LLM and integration config, generates a gateway auth token,
88
+ and prints a direct dashboard URL:
89
+
90
+ ```
91
+ ✔ All done! OpenClaw is running.
92
+ ℹ Open dashboard: http://192.168.1.50:18789?token=<your-token>
93
+ ℹ Token saved to ~/.clawops/secrets/GATEWAY_TOKEN_my-stack
94
+ ```
95
+
96
+ **Prerequisites:** Node.js ≥ 22, an SSH key, and either an SSH-reachable Linux/macOS host or a
97
+ cloud account with CLI credentials configured (`aws configure`, `gcloud auth login`, or `az login`).
98
+
99
+ For a full narrated walkthrough with example output, see [`docs/demo-script.md`](docs/demo-script.md).
100
+
101
+ ---
102
+
103
+ ### Manual setup — existing server
104
+
105
+ If you prefer step-by-step control, or are adding clawops to an already-running deployment:
53
106
 
54
107
  ```bash
55
108
  npm install -g @clawops/cli
56
109
 
57
- # Check your environment
58
- clawops doctor
110
+ clawops doctor # verify environment
59
111
 
60
- # Configure clawops for a local host
61
112
  clawops init --provider local --host 192.168.1.50 --user ubuntu --key-path ~/.ssh/id_ed25519
62
-
63
- # Bootstrap OpenClaw on the host (installs Docker + OpenClaw over SSH)
64
- clawops up
65
-
66
- # Verify it's running
113
+ clawops up # installs Docker + OpenClaw over SSH
67
114
  clawops status
68
-
69
- # Start the MCP server for Claude Code
70
- clawops mcp serve
71
115
  ```
72
116
 
73
- See [`docs/examples/local-vm.md`](docs/examples/local-vm.md) for the full walkthrough including
74
- SSH prerequisites, firewall setup, and troubleshooting.
117
+ See [`docs/examples/local-vm.md`](docs/examples/local-vm.md) for SSH prerequisites, firewall
118
+ setup, and troubleshooting.
75
119
 
76
- ## Quick Start — cloud (AWS)
120
+ ### Manual setup — cloud (AWS)
77
121
 
78
122
  ```bash
79
123
  npm install -g @clawops/cli
80
- # or: npx @clawops/cli
81
124
 
82
125
  # Requires AWS credentials in your environment (AWS_PROFILE or ~/.aws/credentials)
83
126
  clawops init --provider aws
84
127
 
85
- # Edit ~/.clawops/config.json — set stateUrl to your S3 bucket:
86
- # "stateUrl": "s3://my-clawops-state"
128
+ # Edit ~/.clawops/config.json — set stateUrl to your S3 bucket
87
129
 
88
- # Generate a deploy plan (runs pulumi preview internally)
89
130
  clawops plan --provider aws --stack default --out /tmp/plan.json
90
-
91
- # Review the plan, then apply
92
131
  clawops apply /tmp/plan.json
93
132
  ```
94
133
 
95
134
  ---
96
135
 
97
- ## Connect Claude Code
136
+ ## Connect an AI editor
137
+
138
+ The `setup` wizard handles this automatically (Step 4). To wire or re-wire editors at any time:
139
+
140
+ ```bash
141
+ clawops mcp install
142
+ ```
143
+
144
+ This opens the same interactive checkbox used in the wizard — select Claude Desktop, Claude Code,
145
+ Cursor, Windsurf, VS Code, or Zed and clawops writes the MCP entry into each app's config using
146
+ the correct absolute binary path.
98
147
 
99
- Add to your Claude Code MCP config (`~/.claude.json` or project `.mcp.json`):
148
+ To add the entry manually instead, paste this into your editor's MCP config:
100
149
 
101
150
  ```json
102
151
  {
103
152
  "mcpServers": {
104
153
  "clawops": {
105
- "command": "clawops",
154
+ "command": "/path/to/clawops",
106
155
  "args": ["mcp", "serve", "--read-only"]
107
156
  }
108
157
  }
109
158
  }
110
159
  ```
111
160
 
161
+ Replace `/path/to/clawops` with the output of `which clawops`. Config file locations:
162
+
163
+ | App | Path |
164
+ |---|---|
165
+ | Claude Desktop (macOS) | `~/Library/Application Support/Claude/claude_desktop_config.json` |
166
+ | Claude Desktop (Linux) | `~/.config/Claude/claude_desktop_config.json` |
167
+ | Claude Code | `~/.claude.json` |
168
+ | Cursor | `~/.cursor/mcp.json` |
169
+ | Windsurf | `~/.codeium/windsurf/mcp_config.json` |
170
+ | VS Code (macOS) | `~/Library/Application Support/Code/User/mcp.json` |
171
+ | VS Code (Linux) | `~/.config/Code/User/mcp.json` |
172
+ | Zed | `~/.config/zed/settings.json` (key: `context_servers`) |
173
+
112
174
  **Start with `--read-only`** — it enables status, logs, config reads, and diagnostics while
113
- blocking any operation that mutates infrastructure. Remove `--read-only` only once you understand
114
- which tools are destructive and have reviewed [`docs/security/mcp-safety.md`](docs/security/mcp-safety.md).
175
+ blocking mutations. Remove it only after reviewing
176
+ [`docs/security/mcp-safety.md`](docs/security/mcp-safety.md).
115
177
 
116
178
  Destructive tools (`clawops_destroy`, `clawops_up`, `clawops_config_set`, etc.) require explicit
117
- confirmation from the agent before executing — they will never run silently.
179
+ confirmation before executing — they will never run silently.
118
180
 
119
- For Cursor, VS Code, or HTTP mode setup see [`docs/mcp/`](docs/mcp/).
181
+ For HTTP mode setup see [`docs/mcp/`](docs/mcp/).
120
182
 
121
183
  ---
122
184
 
@@ -143,7 +205,8 @@ clawops down --yes # Destroy local-provider stack
143
205
 
144
206
  | Command | Description |
145
207
  |---|---|
146
- | `init` | Interactive setup wizard — writes config, generates SSH key pair |
208
+ | `setup` | First-run wizard — guided LLM, integrations, and deploy-plan generation |
209
+ | `init` | Register a stack in `~/.clawops/config.json` without provisioning |
147
210
  | `up` | Provision or update stack (`--dry-run` for preview) |
148
211
  | `down` | Destroy local-provider stack (requires `--yes`; `--dry-run` shows current outputs) |
149
212
  | `destroy` | Destroy cloud-provider stack with confirmation prompt (`--dry-run` shows current outputs) |
@@ -159,7 +222,9 @@ clawops down --yes # Destroy local-provider stack
159
222
  | `backup` | Create or restore an OpenClaw state backup |
160
223
  | `stacks` | List named stacks and their state |
161
224
  | `doctor` | Check Node version, config, SSH key, provider credentials, and Pulumi home |
162
- | `mcp` | Start the embedded MCP server (`mcp serve`) |
225
+ | `mcp serve` | Start the embedded MCP server (stdio or HTTP) |
226
+ | `mcp install` | Interactively wire clawops into AI editors |
227
+ | `help` | List all commands and global flags |
163
228
 
164
229
  Full flag reference: `clawops <command> --help`
165
230
 
@@ -197,23 +262,29 @@ See [`docs/plan-apply.md`](docs/plan-apply.md) for full semantics, drift guidanc
197
262
  clawops ships an embedded [MCP](https://modelcontextprotocol.io/) server. Claude Code, Cursor, and
198
263
  any MCP-compatible agent can drive deployments without leaving the chat interface.
199
264
 
200
- ### Stdio mode (Claude Code / VS Code)
265
+ ### Wire your editor
201
266
 
202
- ```json
203
- {
204
- "mcpServers": {
205
- "clawops": {
206
- "command": "clawops",
207
- "args": ["mcp", "serve", "--read-only"]
208
- }
209
- }
210
- }
267
+ ```bash
268
+ clawops mcp install # interactive checkbox — writes config for selected apps
269
+ ```
270
+
271
+ The wizard resolves the absolute binary path automatically so app launchers can find `clawops`
272
+ without inheriting your shell's `PATH`. See [Connect an AI editor](#connect-an-ai-editor) above
273
+ for manual config paths.
274
+
275
+ ### Stdio mode (Claude Code / Cursor / VS Code)
276
+
277
+ Start the server manually or confirm your config is correct:
278
+
279
+ ```bash
280
+ clawops mcp serve --read-only # safe for first evaluation
281
+ clawops mcp serve # full mode — enables provisioning, config write, ssh exec
211
282
  ```
212
283
 
213
284
  ### HTTP mode (remote / multi-client)
214
285
 
215
286
  ```bash
216
- clawops mcp serve --http --port 3333 --bind 127.0.0.1
287
+ clawops mcp serve --http 3333 --bind 127.0.0.1
217
288
  # MCP HTTP server listening on 127.0.0.1:3333
218
289
  ```
219
290
 
@@ -341,7 +412,7 @@ pnpm dev doctor # verify toolchain
341
412
  ```bash
342
413
  pnpm dev # run CLI from src/ via tsx
343
414
  pnpm build # tsup → dist/
344
- pnpm test # vitest (476 tests, ~2s)
415
+ pnpm test # vitest (493 tests, ~3s)
345
416
  pnpm test:changed # vitest --changed (fast edit loop)
346
417
  pnpm test:integration # Docker-based SSH integration tests
347
418
  pnpm typecheck # tsc --noEmit
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ applyPlan
4
+ } from "./chunk-LU63NZD3.js";
5
+ import "./chunk-YTH4L2GN.js";
6
+ import "./chunk-BOPSG2LI.js";
7
+ import "./chunk-ACYJBSLJ.js";
8
+ import "./chunk-BRPU7AQC.js";
9
+ import "./chunk-UDNZUSKA.js";
10
+ import "./chunk-CX5SL5HP.js";
11
+ import "./chunk-KGXPLI7W.js";
12
+ export {
13
+ applyPlan
14
+ };
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  getConfigDir
4
- } from "./chunk-ALSUDYA7.js";
5
- import "./chunk-ZSE4QRKE.js";
4
+ } from "./chunk-CX5SL5HP.js";
5
+ import "./chunk-KGXPLI7W.js";
6
6
 
7
7
  // src/pulumi/automation.ts
8
8
  import path from "path";
@@ -15,7 +15,7 @@ var awsProgram = async () => {
15
15
  const cfg = new pulumi.Config();
16
16
  const instanceType = cfg.get("instanceType") ?? "t3.small";
17
17
  const region = cfg.get("region") ?? "us-east-1";
18
- const openclawVersion = cfg.get("openclawVersion") ?? "stable";
18
+ const openclawVersion = cfg.get("openclawVersion") ?? "latest";
19
19
  const accessMode = cfg.get("accessMode") ?? "restricted";
20
20
  const allowedCidrs = cfg.get("allowedCidrs") ?? "";
21
21
  const sshCidrs = cfg.get("sshCidrs") ?? "";
@@ -114,7 +114,8 @@ var awsProgram = async () => {
114
114
  if (bedrockEnabled) {
115
115
  new aws.iam.RolePolicyAttachment("clawops-bedrock", {
116
116
  role: role.name,
117
- policyArn: "arn:aws:iam::aws:policy/AmazonBedrockReadOnly"
117
+ // FullAccess required for bedrock:InvokeModel — ReadOnly only covers describe/list.
118
+ policyArn: "arn:aws:iam::aws:policy/AmazonBedrockFullAccess"
118
119
  });
119
120
  }
120
121
  const instanceProfile = new aws.iam.InstanceProfile("clawops-profile", {
@@ -163,10 +164,8 @@ var awsProgram = async () => {
163
164
  };
164
165
  };
165
166
  function makeStartupScript(openclawVersion, bedrockEnabled) {
166
- const bedrockEnvFile = bedrockEnabled ? `
167
- # Write AWS_PROFILE for Bedrock
168
- echo "AWS_PROFILE=default" > /etc/openclaw.env
169
- ` : "";
167
+ const bedrockEnvSetup = bedrockEnabled ? '\n# Write AWS_PROFILE so the container can use the instance role for Bedrock\necho "AWS_PROFILE=default" > /etc/openclaw.env\n' : "";
168
+ const bedrockEnvFlag = bedrockEnabled ? " --env-file /etc/openclaw.env \\\\\n" : "";
170
169
  return `#!/bin/bash
171
170
  set -euo pipefail
172
171
 
@@ -176,8 +175,9 @@ mkdir -p /home/clawops/.ssh
176
175
  chmod 700 /home/clawops/.ssh
177
176
  chown clawops:clawops /home/clawops/.ssh
178
177
 
179
- # Install Docker if not present
178
+ # Install Docker if not present (Ubuntu/Debian only; AWS AMI is ubuntu-22.04)
180
179
  if ! command -v docker &>/dev/null; then
180
+ export DEBIAN_FRONTEND=noninteractive
181
181
  apt-get update -q
182
182
  apt-get install -y -q ca-certificates curl gnupg lsb-release
183
183
  install -m 0755 -d /etc/apt/keyrings
@@ -193,16 +193,17 @@ if ! command -v docker &>/dev/null; then
193
193
  fi
194
194
 
195
195
  usermod -aG docker clawops
196
- ${bedrockEnvFile}
196
+ ${bedrockEnvSetup}
197
197
  # Pull OpenClaw image
198
198
  OPENCLAW_VERSION="${openclawVersion}"
199
199
  docker pull ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION}
200
200
 
201
201
  # Create default openclaw.json if not present
202
+ # apply.ts will overwrite this with the plan's config overlay post-provisioning.
202
203
  OPENCLAW_CONFIG=/home/clawops/openclaw.json
203
204
  if [ ! -f "\${OPENCLAW_CONFIG}" ]; then
204
205
  cat > "\${OPENCLAW_CONFIG}" <<'OPENCLAWJSON'
205
- {"version":"2026.4","gateway":{"port":18789,"auth":{"mode":"token"}},"models":{},"channels":[]}
206
+ {"meta":{"lastTouchedVersion":"2026.4"},"gateway":{"port":18789,"auth":{"mode":"token"}},"models":{},"channels":{}}
206
207
  OPENCLAWJSON
207
208
  chown clawops:clawops "\${OPENCLAW_CONFIG}"
208
209
  fi
@@ -215,7 +216,8 @@ docker run -d \\
215
216
  --restart unless-stopped \\
216
217
  -p ${GATEWAY_PORT}:${GATEWAY_PORT} \\
217
218
  -v "\${OPENCLAW_CONFIG}":/app/config.json:ro \\
218
- ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION}
219
+ ${bedrockEnvFlag} ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION} \\
220
+ node openclaw.mjs gateway run --allow-unconfigured
219
221
  `;
220
222
  }
221
223
 
@@ -16,7 +16,7 @@ var azureProgram = async () => {
16
16
  const stackName = pulumi.getStack();
17
17
  const instanceType = cfg.get("instanceType") ?? "Standard_B2s";
18
18
  const location = cfg.get("region") ?? "eastus";
19
- const openclawVersion = cfg.get("openclawVersion") ?? "stable";
19
+ const openclawVersion = cfg.get("openclawVersion") ?? "latest";
20
20
  const accessMode = cfg.get("accessMode") ?? "restricted";
21
21
  const allowedCidrs = cfg.get("allowedCidrs") ?? "";
22
22
  const sshCidrs = cfg.get("sshCidrs") ?? "";
@@ -206,7 +206,7 @@ docker pull ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION}
206
206
  OPENCLAW_CONFIG=/home/clawops/openclaw.json
207
207
  if [ ! -f "\${OPENCLAW_CONFIG}" ]; then
208
208
  cat > "\${OPENCLAW_CONFIG}" <<'OPENCLAWJSON'
209
- {"version":"2026.4","gateway":{"port":18789,"auth":{"mode":"token"}},"models":{},"channels":[]}
209
+ {"meta":{"lastTouchedVersion":"2026.4"},"gateway":{"port":18789,"auth":{"mode":"token"}},"models":{},"channels":{}}
210
210
  OPENCLAWJSON
211
211
  chown clawops:clawops "\${OPENCLAW_CONFIG}"
212
212
  fi
@@ -219,7 +219,8 @@ docker run -d \\
219
219
  --restart unless-stopped \\
220
220
  -p 18789:18789 \\
221
221
  -v "\${OPENCLAW_CONFIG}":/app/config.json:ro \\
222
- ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION}
222
+ ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION} \\
223
+ node openclaw.mjs gateway run --allow-unconfigured
223
224
  `;
224
225
  }
225
226
 
@@ -1,14 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- acquireSession
4
- } from "./chunk-BA6MDFZK.js";
5
2
  import {
6
3
  writeLocalState
7
- } from "./chunk-PRYLTCS4.js";
8
- import "./chunk-ALSUDYA7.js";
4
+ } from "./chunk-BRPU7AQC.js";
5
+ import {
6
+ acquireSession
7
+ } from "./chunk-ZVOEQCNW.js";
8
+ import "./chunk-4U3LTLWZ.js";
9
+ import "./chunk-CX5SL5HP.js";
9
10
  import {
10
11
  ProviderError
11
- } from "./chunk-ZSE4QRKE.js";
12
+ } from "./chunk-KGXPLI7W.js";
12
13
 
13
14
  // src/providers/local/bootstrap.ts
14
15
  import { readFileSync } from "fs";
@@ -27,7 +28,8 @@ function renderScript(openclawVersion) {
27
28
  async function localBootstrap(opts) {
28
29
  const script = renderScript(opts.openclawVersion);
29
30
  const b64 = Buffer.from(script, "utf-8").toString("base64");
30
- const command = `echo '${b64}' | base64 -d | sudo bash`;
31
+ const command = opts.sudoPassword ? `printf '%s
32
+ ' ${shellQuote(opts.sudoPassword)} | sudo -S -p '' bash -c "echo '${b64}' | base64 -d | bash"` : `echo '${b64}' | base64 -d | sudo bash`;
31
33
  const { session, release } = await acquireSession({
32
34
  host: opts.host,
33
35
  port: opts.port,
@@ -37,12 +39,16 @@ async function localBootstrap(opts) {
37
39
  signal: opts.signal
38
40
  });
39
41
  try {
40
- const result = await session.exec(command, opts.signal);
41
- if (result.code !== 0) {
42
- throw new ProviderError(
43
- `Bootstrap script failed (exit ${result.code}):
42
+ if (opts.onOutput) {
43
+ await execStreaming(session, command, opts.onOutput, opts.signal);
44
+ } else {
45
+ const result = await session.exec(command, opts.signal);
46
+ if (result.code !== 0) {
47
+ throw new ProviderError(
48
+ `Bootstrap script failed (exit ${result.code}):
44
49
  ${result.stderr || result.stdout}`
45
- );
50
+ );
51
+ }
46
52
  }
47
53
  } finally {
48
54
  release();
@@ -86,6 +92,36 @@ async function waitForGateway(host, port, signal) {
86
92
  function sleep(ms) {
87
93
  return new Promise((resolve) => setTimeout(resolve, ms));
88
94
  }
95
+ function shellQuote(s) {
96
+ return `'${s.replace(/'/g, "'\\''")}'`;
97
+ }
98
+ async function execStreaming(session, command, onOutput, signal) {
99
+ const ch = await session.stream(command, signal);
100
+ let leftover = "";
101
+ let stderr = "";
102
+ ch.on("data", (chunk) => {
103
+ const text = leftover + chunk.toString("utf-8");
104
+ const lines = text.split("\n");
105
+ leftover = lines.pop() ?? "";
106
+ for (const line of lines) {
107
+ if (line.trim()) onOutput(line);
108
+ }
109
+ });
110
+ ch.stderr.on("data", (chunk) => {
111
+ stderr += chunk.toString("utf-8");
112
+ });
113
+ const exitCode = await new Promise((resolve, reject) => {
114
+ ch.on("close", (code) => {
115
+ if (leftover.trim()) onOutput(leftover);
116
+ resolve(code ?? 0);
117
+ });
118
+ ch.on("error", reject);
119
+ });
120
+ if (exitCode !== 0) {
121
+ throw new ProviderError(`Bootstrap script failed (exit ${exitCode}):
122
+ ${stderr}`);
123
+ }
124
+ }
89
125
  export {
90
126
  localBootstrap
91
127
  };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  StateError
4
- } from "./chunk-ZSE4QRKE.js";
4
+ } from "./chunk-KGXPLI7W.js";
5
5
 
6
6
  // src/pulumi/outputs.ts
7
7
  function extractBaseOutputs(raw) {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  NetworkError
4
- } from "./chunk-ZSE4QRKE.js";
4
+ } from "./chunk-KGXPLI7W.js";
5
5
 
6
6
  // src/transport/ssh.ts
7
7
  import { readFileSync, appendFileSync, mkdirSync } from "fs";
@@ -172,87 +172,6 @@ function verifyHostKey(host, port, keyHash, knownHostsPath) {
172
172
  return true;
173
173
  }
174
174
 
175
- // src/transport/pool.ts
176
- var IDLE_TTL_MS = 5 * 60 * 1e3;
177
- var MAX_PER_HOST = 4;
178
- var CLEANUP_INTERVAL_MS = 3e4;
179
- var pool = /* @__PURE__ */ new Map();
180
- var cleanupTimer = null;
181
- function poolKey(opts) {
182
- return `${opts.user}@${opts.host}:${opts.port}`;
183
- }
184
- function startCleanupTimer() {
185
- if (cleanupTimer) return;
186
- cleanupTimer = setInterval(() => {
187
- const now = Date.now();
188
- for (const [key, entries] of pool) {
189
- const active = entries.filter((e) => {
190
- if (!e.inUse && now - e.lastUsed > IDLE_TTL_MS) {
191
- e.session.close();
192
- return false;
193
- }
194
- return true;
195
- });
196
- if (active.length === 0) {
197
- pool.delete(key);
198
- } else {
199
- pool.set(key, active);
200
- }
201
- }
202
- if (pool.size === 0 && cleanupTimer) {
203
- clearInterval(cleanupTimer);
204
- cleanupTimer = null;
205
- }
206
- }, CLEANUP_INTERVAL_MS);
207
- cleanupTimer.unref();
208
- }
209
- async function acquireSession(opts) {
210
- const key = poolKey(opts);
211
- const entries = pool.get(key) ?? [];
212
- const idle = entries.find((e) => !e.inUse);
213
- if (idle) {
214
- idle.inUse = true;
215
- idle.lastUsed = Date.now();
216
- return {
217
- session: idle.session,
218
- release() {
219
- idle.inUse = false;
220
- idle.lastUsed = Date.now();
221
- }
222
- };
223
- }
224
- if (entries.length >= MAX_PER_HOST) {
225
- throw new NetworkError(
226
- `SSH connection pool exhausted for ${key} (max ${MAX_PER_HOST} concurrent connections).`
227
- );
228
- }
229
- const session = await connect(opts);
230
- const entry = { session, hostKey: key, lastUsed: Date.now(), inUse: true };
231
- entries.push(entry);
232
- pool.set(key, entries);
233
- startCleanupTimer();
234
- return {
235
- session,
236
- release() {
237
- entry.inUse = false;
238
- entry.lastUsed = Date.now();
239
- }
240
- };
241
- }
242
- function drainPool() {
243
- for (const entries of pool.values()) {
244
- for (const e of entries) {
245
- e.session.close();
246
- }
247
- }
248
- pool.clear();
249
- if (cleanupTimer) {
250
- clearInterval(cleanupTimer);
251
- cleanupTimer = null;
252
- }
253
- }
254
-
255
175
  export {
256
- acquireSession,
257
- drainPool
176
+ connect
258
177
  };
@@ -1,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readLocalState
4
- } from "./chunk-PRYLTCS4.js";
4
+ } from "./chunk-BRPU7AQC.js";
5
5
  import {
6
6
  getConfigDir,
7
7
  requireConfig
8
- } from "./chunk-ALSUDYA7.js";
8
+ } from "./chunk-CX5SL5HP.js";
9
9
  import {
10
10
  UsageError
11
- } from "./chunk-ZSE4QRKE.js";
11
+ } from "./chunk-KGXPLI7W.js";
12
12
 
13
13
  // src/cli/context.ts
14
14
  function buildContext(args) {
@@ -36,7 +36,7 @@ function buildContext(args) {
36
36
  `Stack "${stackName}" not found in config. Run \`clawops init\` or use \`--stack\` to specify a different stack.`
37
37
  );
38
38
  }
39
- const { getOrCreateStack } = await import("./automation-JG2YBEL7.js");
39
+ const { getOrCreateStack } = await import("./automation-RULUSJBW.js");
40
40
  stackCache = await getOrCreateStack({
41
41
  stack: stackName,
42
42
  stateUrl: stackConfig.stateUrl,
@@ -56,17 +56,17 @@ function makeProviderProxy(name) {
56
56
  if (resolved) return resolved;
57
57
  switch (name) {
58
58
  case "gcp": {
59
- const mod = await import("./gcp-OHWCTFLL.js");
59
+ const mod = await import("./gcp-SKURG3L6.js");
60
60
  resolved = mod.default;
61
61
  return resolved;
62
62
  }
63
63
  case "aws": {
64
- const mod = await import("./aws-A3323GNM.js");
64
+ const mod = await import("./aws-KQBWY43W.js");
65
65
  resolved = mod.default;
66
66
  return resolved;
67
67
  }
68
68
  case "azure": {
69
- const mod = await import("./azure-LNWBK2ZI.js");
69
+ const mod = await import("./azure-4EJFVJZL.js");
70
70
  resolved = mod.default;
71
71
  return resolved;
72
72
  }