@clawops/cli 1.0.0 → 1.1.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.
package/README.md CHANGED
@@ -1,52 +1,85 @@
1
1
  # clawops
2
2
 
3
- **clawops** is a provider-agnostic CLI for deploying and operating self-hosted [OpenClaw](https://github.com/openclaw/openclaw) instances across AWS, GCP, Azure, and local VMs. It uses the [Pulumi Automation API](https://www.pulumi.com/docs/using-pulumi/automation-api/) (embedded — no `pulumi` binary required) for idempotent infrastructure management and exposes every operation as both a CLI command and an [MCP](https://modelcontextprotocol.io/) tool, so Claude Code, Cursor, and other AI agents can drive deployments deterministically.
3
+ Deploy OpenClaw once. Operate it from your terminal or Claude Code.
4
4
 
5
- ```bash
6
- npm install -g @clawops/cli
7
- clawops init --provider aws
8
- clawops plan --out /tmp/my-plan.json # generate + review
9
- clawops apply /tmp/my-plan.json # apply after review
10
- clawops logs -f
11
- ```
5
+ **clawops** is a CLI and [MCP](https://modelcontextprotocol.io/) server for deploying and operating
6
+ self-hosted [OpenClaw](https://github.com/openclaw/openclaw) instances. Provision on AWS, GCP,
7
+ Azure, or any Linux VM — then manage day-to-day operations from the terminal, or let Claude Code
8
+ and Cursor drive them through typed MCP tools with explicit safety controls.
12
9
 
13
10
  ---
14
11
 
15
- ## Why clawops?
12
+ ## Who this is for
16
13
 
17
- Every existing OpenClaw deployment path is cloud-specific, Kubernetes-bound, or fully managed SaaS. No open-source tool unifies provisioning + lifecycle management + remote agent interaction across providers under a single CLI with first-class AI agent integration.
18
-
19
- | Capability | clawops |
20
- |---|---|
21
- | AWS, GCP, Azure, local VM | ✓ |
22
- | Idempotent infra via Pulumi (embedded) | |
23
- | SSH transport (pure Node no system `ssh`) | |
24
- | MCP server (stdio + HTTP) | ✓ |
25
- | Plan → review → apply discipline | ✓ |
26
- | JSON output everywhere (`--json`) | ✓ |
27
- | No credentials in config | ✓ |
14
+ - **OpenClaw users** who want the simplest path to self-hosting across cloud or local VMs, with
15
+ reliable deploy, status checks, logs, backups, and upgrades in a single CLI.
16
+ - **Claude Code / Cursor / MCP users** looking for a real-world reference implementation of safe
17
+ infrastructure operations through MCP — typed tool schemas, read-only mode, destructive-action
18
+ confirmation, and audit logs.
19
+ - **Self-hosted AI and local-first developers** who want to run their own AI assistant without
20
+ committing to Kubernetes, a managed SaaS platform, or a single cloud provider.
28
21
 
29
22
  ---
30
23
 
31
- ## Quick Start
24
+ ## What clawops does
25
+
26
+ - Provisions and tears down OpenClaw infrastructure on **AWS, GCP, Azure, and local VMs** using
27
+ the Pulumi Automation API (embedded — no `pulumi` binary required).
28
+ - Manages day-to-day operations: status, logs, SSH, tunnels, config, agents, gateway, backups.
29
+ - Exposes every operation as a **typed MCP tool** so AI agents can drive ops safely.
30
+ - Enforces a **plan → review → apply** discipline for cloud deployments.
31
+ - Emits **JSON output everywhere** (`--json`) for scripting and automation.
32
+ - Never stores cloud credentials — reads them from your environment's existing CLI profiles.
33
+
34
+ ## What clawops does not do
32
35
 
33
- ### Prerequisites
36
+ - **No high availability or clustering.** Optimized for single-node deployments.
37
+ - **No Kubernetes.** It deploys to VMs, not container orchestration platforms.
38
+ - **No OpenClaw skill/agent authoring.** clawops manages infrastructure; what runs on it is up to
39
+ you and OpenClaw.
40
+ - **No TLS or domain automation** (yet). Bring your own reverse proxy or see
41
+ [`docs/limitations.md`](docs/limitations.md) for the manual path.
42
+ - **No credential storage.** Cloud credentials must be configured in your environment before using
43
+ clawops. They are never written to `~/.clawops/config.json`.
44
+ - **No native Windows.** WSL2 is fully supported; see [`docs/support-matrix.md`](docs/support-matrix.md).
34
45
 
35
- - **Node.js ≥ 22** (LTS)
36
- - Cloud credentials available in the environment (see [Configuration](#configuration))
46
+ ---
47
+
48
+ ## Quick Start — local VM (fastest path)
37
49
 
38
- ### Install
50
+ The local provider needs only a Linux host reachable over SSH — no cloud account required.
51
+
52
+ **Prerequisites:** Node.js ≥ 22, an SSH key, a Linux host (Ubuntu/Debian/RHEL) you can reach.
39
53
 
40
54
  ```bash
41
55
  npm install -g @clawops/cli
42
- # or without a global install:
43
- npx @clawops/cli
56
+
57
+ # Check your environment
58
+ clawops doctor
59
+
60
+ # Configure clawops for a local host
61
+ 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
67
+ clawops status
68
+
69
+ # Start the MCP server for Claude Code
70
+ clawops mcp serve
44
71
  ```
45
72
 
46
- ### Provision on AWS
73
+ See [`docs/examples/local-vm.md`](docs/examples/local-vm.md) for the full walkthrough including
74
+ SSH prerequisites, firewall setup, and troubleshooting.
75
+
76
+ ## Quick Start — cloud (AWS)
47
77
 
48
78
  ```bash
49
- # Write ~/.clawops/config.json and generate an SSH key pair
79
+ npm install -g @clawops/cli
80
+ # or: npx @clawops/cli
81
+
82
+ # Requires AWS credentials in your environment (AWS_PROFILE or ~/.aws/credentials)
50
83
  clawops init --provider aws
51
84
 
52
85
  # Edit ~/.clawops/config.json — set stateUrl to your S3 bucket:
@@ -55,19 +88,44 @@ clawops init --provider aws
55
88
  # Generate a deploy plan (runs pulumi preview internally)
56
89
  clawops plan --provider aws --stack default --out /tmp/plan.json
57
90
 
58
- # Review the plan JSON, then apply
91
+ # Review the plan, then apply
59
92
  clawops apply /tmp/plan.json
93
+ ```
60
94
 
61
- # Or preview + apply in one step (no plan file needed)
62
- clawops up
95
+ ---
96
+
97
+ ## Connect Claude Code
98
+
99
+ Add to your Claude Code MCP config (`~/.claude.json` or project `.mcp.json`):
100
+
101
+ ```json
102
+ {
103
+ "mcpServers": {
104
+ "clawops": {
105
+ "command": "clawops",
106
+ "args": ["mcp", "serve", "--read-only"]
107
+ }
108
+ }
109
+ }
63
110
  ```
64
111
 
65
- ### Day-to-day operations
112
+ **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).
115
+
116
+ Destructive tools (`clawops_destroy`, `clawops_up`, `clawops_config_set`, etc.) require explicit
117
+ confirmation from the agent before executing — they will never run silently.
118
+
119
+ For Cursor, VS Code, or HTTP mode setup see [`docs/mcp/`](docs/mcp/).
120
+
121
+ ---
122
+
123
+ ## Day-to-day operations
66
124
 
67
125
  ```bash
68
- clawops status # Show stack outputs: IP, gateway URL, SSH info
126
+ clawops status # Stack outputs: IP, gateway URL, SSH info
69
127
  clawops logs -f # Tail OpenClaw logs over SSH
70
- clawops ssh # Open an interactive SSH session
128
+ clawops ssh # Interactive SSH session
71
129
  clawops ssh --command "docker ps"
72
130
 
73
131
  clawops config get maxAgents
@@ -75,7 +133,7 @@ clawops config set maxAgents 8
75
133
 
76
134
  clawops tunnel # Port-forward gateway UI to localhost
77
135
 
78
- clawops destroy --yes # Destroy cloud-provider stack (AWS/GCP/Azure)
136
+ clawops destroy --yes # Destroy cloud-provider stack
79
137
  clawops down --yes # Destroy local-provider stack
80
138
  ```
81
139
 
@@ -90,7 +148,7 @@ clawops down --yes # Destroy local-provider stack
90
148
  | `down` | Destroy local-provider stack (requires `--yes`; `--dry-run` shows current outputs) |
91
149
  | `destroy` | Destroy cloud-provider stack with confirmation prompt (`--dry-run` shows current outputs) |
92
150
  | `status` | Show stack outputs: IP, gateway URL, region, provisioned time |
93
- | `plan` | Generate a Maker deploy-plan JSON artifact (dry-run safe) |
151
+ | `plan` | Generate a deploy-plan JSON artifact (dry-run safe) |
94
152
  | `apply` | Apply a previously reviewed plan file (`--dry-run` validates and shows diff without applying) |
95
153
  | `ssh` | Interactive SSH session or run a remote command |
96
154
  | `logs` | Stream OpenClaw logs (`-f`, `--tail N`, `--since 5m`) |
@@ -115,7 +173,7 @@ For non-local providers, clawops enforces a review-before-apply discipline:
115
173
  # 1. Generate a plan — runs `pulumi preview` internally, produces JSON
116
174
  clawops plan --provider aws --region us-east-1 --out /tmp/plan.json
117
175
 
118
- # 2. Review plan.json — it shows exactly which resources will change
176
+ # 2. Review plan.json — the `diff` field shows projected changes at plan-generation time
119
177
  cat /tmp/plan.json | jq .diff
120
178
 
121
179
  # 3. Apply — reads and validates the plan file, then runs `pulumi up`
@@ -125,24 +183,28 @@ clawops apply /tmp/plan.json
125
183
  clawops apply /tmp/plan.json --yes # skip prompt in automation
126
184
  ```
127
185
 
128
- The plan JSON conforms to `spec/deploy-plan.schema.json` (AJV-validated). Plans are portable — generated on one machine, applied on another.
186
+ The plan JSON conforms to `spec/deploy-plan.schema.json` (AJV-validated) and captures reviewed
187
+ intent: provider, region, instance type, CIDR ranges, and OpenClaw version. `apply` re-runs
188
+ `pulumi up` using those parameters against the current live state — it does not replay a locked
189
+ execution artifact. Review and apply in the same session to minimize drift risk.
190
+
191
+ See [`docs/plan-apply.md`](docs/plan-apply.md) for full semantics, drift guidance, and the safe CI pattern.
129
192
 
130
193
  ---
131
194
 
132
195
  ## MCP server
133
196
 
134
- clawops ships an embedded [MCP](https://modelcontextprotocol.io/) server. Claude Code, Cursor, and any MCP-compatible agent can drive deployments without leaving the chat interface.
197
+ clawops ships an embedded [MCP](https://modelcontextprotocol.io/) server. Claude Code, Cursor, and
198
+ any MCP-compatible agent can drive deployments without leaving the chat interface.
135
199
 
136
200
  ### Stdio mode (Claude Code / VS Code)
137
201
 
138
- Add to your Claude Code MCP config (`~/.claude.json` or project `.mcp.json`):
139
-
140
202
  ```json
141
203
  {
142
204
  "mcpServers": {
143
205
  "clawops": {
144
206
  "command": "clawops",
145
- "args": ["mcp", "serve"]
207
+ "args": ["mcp", "serve", "--read-only"]
146
208
  }
147
209
  }
148
210
  }
@@ -155,27 +217,31 @@ clawops mcp serve --http --port 3333 --bind 127.0.0.1
155
217
  # MCP HTTP server listening on 127.0.0.1:3333
156
218
  ```
157
219
 
158
- Point your MCP client at `http://127.0.0.1:3333`.
220
+ Do not bind to a non-loopback address without additional authentication controls in front of it.
159
221
 
160
222
  ### Available tools
161
223
 
162
224
  | Tool | Toolset | Description |
163
225
  |---|---|---|
164
- | `clawops_up` | cli | Provision or update a stack |
165
226
  | `clawops_status` | read | Show stack outputs |
166
227
  | `clawops_logs_tail` | read | Tail OpenClaw logs |
167
- | `clawops_ssh_exec` | cli | Run a command over SSH |
168
- | `clawops_plan` | cli | Generate a deploy plan |
169
- | `clawops_apply` | cli | Apply a plan file |
170
- | `clawops_destroy` | cli | Destroy a stack (elicits confirmation) |
171
228
  | `clawops_config_get` | read | Read a remote config value |
172
- | `clawops_config_set` | cli | Write a remote config value |
173
229
  | `clawops_agents_list` | read | List running agents |
174
- | `clawops_stacks_list` | admin | List all stacks and their state |
175
230
  | `clawops_task_status` | read | Poll a long-running task |
231
+ | `clawops_stacks_list` | admin | List all stacks and their state |
232
+ | `clawops_up` | cli | Provision or update a stack |
233
+ | `clawops_plan` | cli | Generate a deploy plan |
234
+ | `clawops_apply` | cli | Apply a plan file |
235
+ | `clawops_ssh_exec` | cli | Run a command over SSH |
236
+ | `clawops_config_set` | cli | Write a remote config value |
237
+ | `clawops_destroy` | cli | Destroy a stack (elicits confirmation) |
176
238
  | `clawops_workflow_deploy_app` | workflow | End-to-end deploy: plan → confirm → apply → status |
177
239
 
178
- Destructive tools require explicit confirmation (R19 elicitation) unless `yes: true` is passed.
240
+ `read` toolset tools are available in `--read-only` mode. All other toolsets require full mode.
241
+ Destructive tools require explicit confirmation (elicitation) unless `yes: true` is passed.
242
+
243
+ See [`docs/security/tool-risk-matrix.md`](docs/security/tool-risk-matrix.md) for the full risk
244
+ classification of every tool.
179
245
 
180
246
  ---
181
247
 
@@ -215,6 +281,17 @@ Config lives at `~/.clawops/config.json` (override with `$CLAWOPS_HOME`).
215
281
 
216
282
  ---
217
283
 
284
+ ## Known limitations
285
+
286
+ See [`docs/limitations.md`](docs/limitations.md) for the full list. Key points:
287
+
288
+ - **Single-node deployments only** — not a high-availability or clustering platform.
289
+ - **`clawops apply` is not an immutable plan execution** — see [`docs/plan-apply.md`](docs/plan-apply.md).
290
+ - **No TLS/domain automation** in the current release.
291
+ - **MCP tools execute privileged operations** — use `--read-only` for first evaluation.
292
+
293
+ ---
294
+
218
295
  ## Architecture
219
296
 
220
297
  ```
@@ -241,7 +318,7 @@ Key design decisions:
241
318
  - **State in cloud blob storage:** GCS (`gs://`), S3 (`s3://`), Azure Blob — no local state files, no `pulumi.yaml`
242
319
  - **SSH via `ssh2`:** never shells out to `/usr/bin/ssh`; TOFU host verification against `~/.clawops/known_hosts`; connection pool with 5-min idle TTL
243
320
  - **Plan → apply discipline:** every non-local deployment goes through `generatePlan()` → review → `applyPlan()`; destructive changes always require human review of the plan JSON
244
- - **MCP-first:** every CLI operation has a typed MCP tool; schemas generated from `spec/mcp-tools.yaml`; all destructive tools use R19 elicitation
321
+ - **MCP-first:** every CLI operation has a typed MCP tool; schemas generated from `spec/mcp-tools.yaml`; all destructive tools use elicitation
245
322
 
246
323
  See [`docs/architecture.md`](docs/architecture.md) for a full narrative, and [`docs/decisions/`](docs/decisions/) for ADRs.
247
324
 
@@ -264,8 +341,9 @@ pnpm dev doctor # verify toolchain
264
341
  ```bash
265
342
  pnpm dev # run CLI from src/ via tsx
266
343
  pnpm build # tsup → dist/
267
- pnpm test # vitest (356 tests, ~2s)
344
+ pnpm test # vitest (476 tests, ~2s)
268
345
  pnpm test:changed # vitest --changed (fast edit loop)
346
+ pnpm test:integration # Docker-based SSH integration tests
269
347
  pnpm typecheck # tsc --noEmit
270
348
  pnpm lint # eslint src/ tests/ scripts/ (--max-warnings=0)
271
349
  pnpm gen:schemas # regenerate src/providers/types.ts + src/mcp/tools/_generated.ts
@@ -281,7 +359,10 @@ pnpm changeset # record a release note before merging
281
359
  | `SPEC.md` | Full technical specification (milestones, rules, schemas) |
282
360
  | `DESIGN_RULES.md` | 25 normative rules (R1–R25) referenced throughout the codebase |
283
361
  | `docs/architecture.md` | Narrative system overview |
362
+ | `docs/plan-apply.md` | Plan/apply semantics, drift guidance, CI pattern |
284
363
  | `docs/ci.md` | CI integration guide: OIDC, env vars, plan → apply in CI |
364
+ | `docs/security/` | MCP safety model, tool risk matrix, redaction, audit logs |
365
+ | `docs/providers/matrix.md` | Per-provider capability matrix |
285
366
  | `docs/decisions/` | Architecture Decision Records |
286
367
  | `.claude/skills/` | Invokable procedures: `/add-provider`, `/release`, `/tdd`, `/mcp-tool` |
287
368
  | `.claude/rules/` | Path-scoped lint rules loaded by Claude Code |
@@ -326,7 +407,9 @@ Use `pnpm changeset` to record a release note before merging a `feat` or `fix`.
326
407
  | M4 — Local VM | ✅ | Local adapter (SSH bootstrap, no Pulumi); `doctor` |
327
408
  | M5 — MCP Layer | ✅ | `mcp serve` (stdio), all CLI ops as MCP tools, progress tracking |
328
409
  | M6 — Plan/Apply | ✅ | `plan` + `apply`; deploy-plan schema; MCP HTTP transport; `workflow_deploy_app` |
329
- | M7 — v1.0 Polish | ✅ | Full `doctor` surface; `destroy` command; `--dry-run` on `up`/`down`/`destroy`/`apply`/`config`; `release.yml`; CI integration guide |
410
+ | M7 — v1.0 Polish | ✅ | Full `doctor` surface; `destroy` command; `--dry-run` across commands; CI guide |
411
+
412
+ See [`docs/roadmap.md`](docs/roadmap.md) for the public roadmap and upcoming work.
330
413
 
331
414
  ---
332
415
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  applyPlan
4
- } from "./chunk-RZE35FQF.js";
4
+ } from "./chunk-UCIVU24B.js";
5
5
  import "./chunk-PERSDQMT.js";
6
6
  import "./chunk-PRYLTCS4.js";
7
7
  import "./chunk-YTH4L2GN.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  acquireSession
4
- } from "./chunk-5XEZAU7V.js";
4
+ } from "./chunk-BA6MDFZK.js";
5
5
  import {
6
6
  writeLocalState
7
7
  } from "./chunk-PRYLTCS4.js";
@@ -116,6 +116,10 @@ async function connect(opts) {
116
116
  );
117
117
  }
118
118
  return new Promise((resolve, reject) => {
119
+ if (opts.signal?.aborted) {
120
+ reject(new NetworkError("Connection aborted"));
121
+ return;
122
+ }
119
123
  const client = new Client();
120
124
  const onAbort = () => {
121
125
  client.destroy();
@@ -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-RCGVJCON.js");
48
+ const { version } = await import("./package-FA6UOG2I.js");
49
49
  const config = getConfig();
50
50
  const instanceType = intent.instanceType ?? "small";
51
51
  const openclawVersion = intent.openclawVersion ?? "latest";
@@ -34,7 +34,7 @@ ${validation.errors.join("\n")}`
34
34
  }
35
35
  await stack.setConfig("openclawVersion", { value: plan.spec.openclaw.version });
36
36
  const start = Date.now();
37
- const result = await stack.up({ onOutput: opts?.onOutput });
37
+ const result = await stack.up({ onOutput: opts?.onOutput, signal: opts?.signal });
38
38
  const outputs = Object.fromEntries(
39
39
  Object.entries(result.outputs).map(([k, v]) => [k, v.value])
40
40
  );
package/dist/cli.js CHANGED
@@ -213,7 +213,7 @@ var up_default = defineCommand2({
213
213
  );
214
214
  }
215
215
  const { localOpts } = stackConfig;
216
- const { localBootstrap } = await import("./bootstrap-NRIDVS5F.js");
216
+ const { localBootstrap } = await import("./bootstrap-7ZS2GLFJ.js");
217
217
  const abortController = new AbortController();
218
218
  process3.on("SIGINT", () => abortController.abort());
219
219
  process3.on("SIGTERM", () => abortController.abort());
@@ -445,7 +445,7 @@ var plan_default = defineCommand5({
445
445
  },
446
446
  async run({ args }) {
447
447
  const { buildContext } = await import("./context-T52JWL3P.js");
448
- const { generatePlan } = await import("./generate-H3D7VQUP.js");
448
+ const { generatePlan } = await import("./generate-55MITRXU.js");
449
449
  const ctx = buildContext(args);
450
450
  if (ctx.adapter.name === "local") {
451
451
  throw new UsageError(
@@ -523,7 +523,7 @@ var apply_default = defineCommand6({
523
523
  },
524
524
  async run({ args }) {
525
525
  const { validatePlan } = await import("./validate-T5M5EHSJ.js");
526
- const { applyPlan } = await import("./apply-GTVALBAY.js");
526
+ const { applyPlan } = await import("./apply-DPS23GLL.js");
527
527
  const planPath = args._?.[0];
528
528
  if (!planPath) {
529
529
  throw new UsageError("Usage: clawops apply <plan.json>");
@@ -696,7 +696,7 @@ var ssh_default = defineCommand8({
696
696
  },
697
697
  async run({ args }) {
698
698
  const { buildContext } = await import("./context-T52JWL3P.js");
699
- const { acquireSession, drainPool } = await import("./pool-D4JCUA2V.js");
699
+ const { acquireSession, drainPool } = await import("./pool-FAVU7WCW.js");
700
700
  const ctx = buildContext(args);
701
701
  let conn;
702
702
  if (ctx.adapter.name === "local") {
@@ -784,7 +784,7 @@ var tunnel_default = defineCommand9({
784
784
  async run({ args }) {
785
785
  const { buildContext } = await import("./context-T52JWL3P.js");
786
786
  const { extractBaseOutputs } = await import("./outputs-6DAVEEAZ.js");
787
- const { acquireSession } = await import("./pool-D4JCUA2V.js");
787
+ const { acquireSession } = await import("./pool-FAVU7WCW.js");
788
788
  const ctx = buildContext(args);
789
789
  const stack = await ctx.getStack();
790
790
  const outputMap = await stack.outputs();
@@ -852,7 +852,7 @@ var logs_default = defineCommand10({
852
852
  async run({ args }) {
853
853
  const { buildContext } = await import("./context-T52JWL3P.js");
854
854
  const { extractBaseOutputs } = await import("./outputs-6DAVEEAZ.js");
855
- const { acquireSession, drainPool } = await import("./pool-D4JCUA2V.js");
855
+ const { acquireSession, drainPool } = await import("./pool-FAVU7WCW.js");
856
856
  const ctx = buildContext(args);
857
857
  const stack = await ctx.getStack();
858
858
  const outputMap = await stack.outputs();
@@ -969,7 +969,7 @@ var config_default = defineCommand11({
969
969
  async run({ args }) {
970
970
  const { buildContext } = await import("./context-T52JWL3P.js");
971
971
  const { extractBaseOutputs } = await import("./outputs-6DAVEEAZ.js");
972
- const { acquireSession, drainPool } = await import("./pool-D4JCUA2V.js");
972
+ const { acquireSession, drainPool } = await import("./pool-FAVU7WCW.js");
973
973
  const [action, key, value] = args._ ?? [];
974
974
  if (!action || !["get", "set", "unset"].includes(action)) {
975
975
  failure("Usage: clawops config <get [key] | set key value | unset key>");
@@ -1084,7 +1084,7 @@ var agents_default = defineCommand12({
1084
1084
  async run({ args }) {
1085
1085
  const { buildContext } = await import("./context-T52JWL3P.js");
1086
1086
  const { extractBaseOutputs } = await import("./outputs-6DAVEEAZ.js");
1087
- const { acquireSession, drainPool } = await import("./pool-D4JCUA2V.js");
1087
+ const { acquireSession, drainPool } = await import("./pool-FAVU7WCW.js");
1088
1088
  const [action, name] = args._ ?? [];
1089
1089
  if (!action || !["list", "restart", "logs"].includes(action)) {
1090
1090
  failure("Usage: clawops agents <list | restart [name] | logs <name>>");
@@ -1188,7 +1188,7 @@ var gateway_default = defineCommand13({
1188
1188
  async run({ args }) {
1189
1189
  const { buildContext } = await import("./context-T52JWL3P.js");
1190
1190
  const { extractBaseOutputs } = await import("./outputs-6DAVEEAZ.js");
1191
- const { acquireSession, drainPool } = await import("./pool-D4JCUA2V.js");
1191
+ const { acquireSession, drainPool } = await import("./pool-FAVU7WCW.js");
1192
1192
  const [action, versionArg] = args._ ?? [];
1193
1193
  if (!action || !["status", "restart", "update"].includes(action)) {
1194
1194
  failure("Usage: clawops gateway <status | restart | update [version]>");
@@ -1300,7 +1300,7 @@ var backup_default = defineCommand14({
1300
1300
  },
1301
1301
  async run({ args }) {
1302
1302
  const { buildContext } = await import("./context-T52JWL3P.js");
1303
- const { acquireSession, drainPool } = await import("./pool-D4JCUA2V.js");
1303
+ const { acquireSession, drainPool } = await import("./pool-FAVU7WCW.js");
1304
1304
  const action = args.action;
1305
1305
  if (action !== "create" && action !== "restore") {
1306
1306
  throw new UsageError(`Unknown action: ${action}. Use "create" or "restore"`);
@@ -1635,7 +1635,7 @@ var mcp_default = defineCommand17({
1635
1635
  }
1636
1636
  return;
1637
1637
  }
1638
- const { serveMcp } = await import("./server-YO4JFQND.js");
1638
+ const { serveMcp } = await import("./server-V5ZFL76Y.js");
1639
1639
  await serveMcp({
1640
1640
  port: args.http ? Number(args.http) : void 0,
1641
1641
  bind: args.bind,
@@ -1653,13 +1653,14 @@ var CLAWOPS_ENTRY = {
1653
1653
  };
1654
1654
  function getConfigPath(client) {
1655
1655
  const home = os.homedir();
1656
+ const isLinux = process.platform === "linux";
1656
1657
  switch (client) {
1657
1658
  case "claude":
1658
- return path3.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
1659
+ return isLinux ? path3.join(home, ".config", "Claude", "claude_desktop_config.json") : path3.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
1659
1660
  case "cursor":
1660
1661
  return path3.join(home, ".cursor", "mcp.json");
1661
1662
  case "vscode":
1662
- return path3.join(home, "Library", "Application Support", "Code", "User", "mcp.json");
1663
+ return isLinux ? path3.join(home, ".config", "Code", "User", "mcp.json") : path3.join(home, "Library", "Application Support", "Code", "User", "mcp.json");
1663
1664
  case "windsurf":
1664
1665
  return path3.join(home, ".codeium", "windsurf", "mcp_config.json");
1665
1666
  case "zed":
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  generatePlan,
4
4
  planId
5
- } from "./chunk-2MJFYG7U.js";
5
+ } from "./chunk-D4UAHAKI.js";
6
6
  import "./chunk-PERSDQMT.js";
7
7
  import "./chunk-PRYLTCS4.js";
8
8
  import "./chunk-YTH4L2GN.js";
@@ -2,7 +2,7 @@
2
2
 
3
3
  // package.json
4
4
  var name = "@clawops/cli";
5
- var version = "1.0.0";
5
+ var version = "1.1.0";
6
6
  var description = "Deploy and manage self-hosted OpenClaw instances across clouds";
7
7
  var type = "module";
8
8
  var bin = {
@@ -18,6 +18,7 @@ var repository = {
18
18
  type: "git",
19
19
  url: "git+https://github.com/dfridkin/clawops.git"
20
20
  };
21
+ var mcpName = "io.github.dfridkin/clawops";
21
22
  var publishConfig = {
22
23
  access: "public",
23
24
  provenance: true
@@ -32,7 +33,8 @@ var scripts = {
32
33
  test: "vitest run",
33
34
  "test:pulumi": "vitest run tests/providers/aws/program.test.ts tests/providers/azure/program.test.ts",
34
35
  "test:changed": "vitest --changed",
35
- typecheck: "tsc --noEmit",
36
+ "test:integration": "vitest run --config vitest.integration.config.ts",
37
+ typecheck: "NODE_OPTIONS=--max-old-space-size=4096 tsc --noEmit",
36
38
  lint: "eslint --max-warnings=0 src tests scripts",
37
39
  "gen:schemas": "tsx scripts/gen-schemas.ts",
38
40
  changeset: "changeset",
@@ -57,15 +59,16 @@ var dependencies = {
57
59
  zod: "^3.0.0"
58
60
  };
59
61
  var devDependencies = {
60
- "aws-sdk-client-mock": "^4.0.0",
61
62
  "@changesets/cli": "^2.0.0",
62
63
  "@types/js-yaml": "^4.0.0",
63
64
  "@types/node": "^22.0.0",
64
65
  "@types/ssh2": "^1.0.0",
65
66
  "@vitest/coverage-v8": "^2.0.0",
67
+ "aws-sdk-client-mock": "^4.0.0",
66
68
  eslint: "^9.0.0",
67
69
  "js-yaml": "^4.0.0",
68
70
  nock: "^14.0.0",
71
+ testcontainers: "^11.14.0",
69
72
  tsup: "^8.0.0",
70
73
  tsx: "^4.0.0",
71
74
  typescript: "^5.0.0",
@@ -93,6 +96,7 @@ var package_default = {
93
96
  exports,
94
97
  files,
95
98
  repository,
99
+ mcpName,
96
100
  publishConfig,
97
101
  engines,
98
102
  packageManager,
@@ -110,6 +114,7 @@ export {
110
114
  engines,
111
115
  exports,
112
116
  files,
117
+ mcpName,
113
118
  name,
114
119
  packageManager,
115
120
  pnpm,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  acquireSession,
4
4
  drainPool
5
- } from "./chunk-5XEZAU7V.js";
5
+ } from "./chunk-BA6MDFZK.js";
6
6
  import "./chunk-ZSE4QRKE.js";
7
7
  export {
8
8
  acquireSession,
@@ -2,16 +2,16 @@
2
2
  import {
3
3
  acquireSession,
4
4
  drainPool
5
- } from "./chunk-5XEZAU7V.js";
5
+ } from "./chunk-BA6MDFZK.js";
6
6
  import {
7
7
  extractBaseOutputs
8
8
  } from "./chunk-LT24GUUO.js";
9
9
  import {
10
10
  generatePlan
11
- } from "./chunk-2MJFYG7U.js";
11
+ } from "./chunk-D4UAHAKI.js";
12
12
  import {
13
13
  applyPlan
14
- } from "./chunk-RZE35FQF.js";
14
+ } from "./chunk-UCIVU24B.js";
15
15
  import {
16
16
  buildContext
17
17
  } from "./chunk-PERSDQMT.js";
@@ -701,7 +701,7 @@ async function handleUp(input, server) {
701
701
  );
702
702
  }
703
703
  const { localOpts } = stackConfig;
704
- const { localBootstrap } = await import("./bootstrap-NRIDVS5F.js");
704
+ const { localBootstrap } = await import("./bootstrap-7ZS2GLFJ.js");
705
705
  const ac = new AbortController();
706
706
  const state = await localBootstrap({
707
707
  host: localOpts.host,
@@ -1317,7 +1317,7 @@ If none of the above resolves the issue:
1317
1317
 
1318
1318
  // src/mcp/server.ts
1319
1319
  async function serveMcp(opts) {
1320
- const { version } = await import("./package-RCGVJCON.js");
1320
+ const { version } = await import("./package-FA6UOG2I.js");
1321
1321
  const server = new McpServer({ name: "clawops", version });
1322
1322
  registerTools(server, opts);
1323
1323
  registerResources(server);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawops/cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Deploy and manage self-hosted OpenClaw instances across clouds",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,6 +16,7 @@
16
16
  "type": "git",
17
17
  "url": "git+https://github.com/dfridkin/clawops.git"
18
18
  },
19
+ "mcpName": "io.github.dfridkin/clawops",
19
20
  "publishConfig": {
20
21
  "access": "public",
21
22
  "provenance": true
@@ -42,15 +43,16 @@
42
43
  "zod": "^3.0.0"
43
44
  },
44
45
  "devDependencies": {
45
- "aws-sdk-client-mock": "^4.0.0",
46
46
  "@changesets/cli": "^2.0.0",
47
47
  "@types/js-yaml": "^4.0.0",
48
48
  "@types/node": "^22.0.0",
49
49
  "@types/ssh2": "^1.0.0",
50
50
  "@vitest/coverage-v8": "^2.0.0",
51
+ "aws-sdk-client-mock": "^4.0.0",
51
52
  "eslint": "^9.0.0",
52
53
  "js-yaml": "^4.0.0",
53
54
  "nock": "^14.0.0",
55
+ "testcontainers": "^11.14.0",
54
56
  "tsup": "^8.0.0",
55
57
  "tsx": "^4.0.0",
56
58
  "typescript": "^5.0.0",
@@ -63,7 +65,8 @@
63
65
  "test": "vitest run",
64
66
  "test:pulumi": "vitest run tests/providers/aws/program.test.ts tests/providers/azure/program.test.ts",
65
67
  "test:changed": "vitest --changed",
66
- "typecheck": "tsc --noEmit",
68
+ "test:integration": "vitest run --config vitest.integration.config.ts",
69
+ "typecheck": "NODE_OPTIONS=--max-old-space-size=4096 tsc --noEmit",
67
70
  "lint": "eslint --max-warnings=0 src tests scripts",
68
71
  "gen:schemas": "tsx scripts/gen-schemas.ts",
69
72
  "changeset": "changeset",