@clawops/cli 1.2.0 → 1.3.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,6 +1,6 @@
1
1
  # clawops
2
2
 
3
- Deploy OpenClaw once. Operate it from your terminal or Claude Code.
3
+ MCP-native infrastructure ops for OpenClaw with read-only mode, destructive-action confirmation, and audit logs built in.
4
4
 
5
5
  **clawops** is a CLI and [MCP](https://modelcontextprotocol.io/) server for deploying and operating
6
6
  self-hosted [OpenClaw](https://github.com/openclaw/openclaw) instances. Provision on AWS, GCP,
@@ -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
 
@@ -160,7 +222,9 @@ clawops down --yes # Destroy local-provider stack
160
222
  | `backup` | Create or restore an OpenClaw state backup |
161
223
  | `stacks` | List named stacks and their state |
162
224
  | `doctor` | Check Node version, config, SSH key, provider credentials, and Pulumi home |
163
- | `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 |
164
228
 
165
229
  Full flag reference: `clawops <command> --help`
166
230
 
@@ -198,23 +262,29 @@ See [`docs/plan-apply.md`](docs/plan-apply.md) for full semantics, drift guidanc
198
262
  clawops ships an embedded [MCP](https://modelcontextprotocol.io/) server. Claude Code, Cursor, and
199
263
  any MCP-compatible agent can drive deployments without leaving the chat interface.
200
264
 
201
- ### Stdio mode (Claude Code / VS Code)
265
+ ### Wire your editor
202
266
 
203
- ```json
204
- {
205
- "mcpServers": {
206
- "clawops": {
207
- "command": "clawops",
208
- "args": ["mcp", "serve", "--read-only"]
209
- }
210
- }
211
- }
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
212
282
  ```
213
283
 
214
284
  ### HTTP mode (remote / multi-client)
215
285
 
216
286
  ```bash
217
- clawops mcp serve --http --port 3333 --bind 127.0.0.1
287
+ clawops mcp serve --http 3333 --bind 127.0.0.1
218
288
  # MCP HTTP server listening on 127.0.0.1:3333
219
289
  ```
220
290
 
@@ -1,12 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  applyPlan
4
- } from "./chunk-LU63NZD3.js";
4
+ } from "./chunk-JCUZU5BH.js";
5
5
  import "./chunk-YTH4L2GN.js";
6
+ import "./chunk-6ZFIFDBJ.js";
6
7
  import "./chunk-BOPSG2LI.js";
7
- import "./chunk-ACYJBSLJ.js";
8
- import "./chunk-BRPU7AQC.js";
8
+ import "./chunk-JDN6PLH2.js";
9
9
  import "./chunk-UDNZUSKA.js";
10
+ import "./chunk-A2I76FTA.js";
10
11
  import "./chunk-CX5SL5HP.js";
11
12
  import "./chunk-KGXPLI7W.js";
12
13
  export {
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- writeLocalState
4
- } from "./chunk-BRPU7AQC.js";
5
2
  import {
6
3
  acquireSession
7
4
  } from "./chunk-ZVOEQCNW.js";
8
5
  import "./chunk-4U3LTLWZ.js";
6
+ import {
7
+ writeLocalState
8
+ } from "./chunk-A2I76FTA.js";
9
9
  import "./chunk-CX5SL5HP.js";
10
10
  import {
11
11
  ProviderError
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/plan/overlay-store.ts
4
+ import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from "fs";
5
+ import path from "path";
6
+ import os from "os";
7
+ function overlayPath(stackName) {
8
+ return path.join(os.homedir(), ".clawops", "overlays", `${stackName}.json`);
9
+ }
10
+ function saveOverlay(stackName, overlay, secrets) {
11
+ const dir = path.join(os.homedir(), ".clawops", "overlays");
12
+ mkdirSync(dir, { recursive: true });
13
+ const data = { stackName, savedAt: (/* @__PURE__ */ new Date()).toISOString(), overlay, secrets };
14
+ writeFileSync(overlayPath(stackName), JSON.stringify(data, null, 2) + "\n", "utf-8");
15
+ }
16
+ function loadOverlay(stackName) {
17
+ const p = overlayPath(stackName);
18
+ if (!existsSync(p)) return null;
19
+ try {
20
+ return JSON.parse(readFileSync(p, "utf-8"));
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+ function listOverlays() {
26
+ const dir = path.join(os.homedir(), ".clawops", "overlays");
27
+ if (!existsSync(dir)) return [];
28
+ return readdirSync(dir).filter((f) => f.endsWith(".json")).flatMap((f) => {
29
+ try {
30
+ return [JSON.parse(readFileSync(path.join(dir, f), "utf-8"))];
31
+ } catch {
32
+ return [];
33
+ }
34
+ });
35
+ }
36
+
37
+ export {
38
+ saveOverlay,
39
+ loadOverlay,
40
+ listOverlays
41
+ };
@@ -30,8 +30,20 @@ function writeLocalState(stackName, state) {
30
30
  writeFileSync(tmp, JSON.stringify(state, null, 2) + "\n", "utf-8");
31
31
  renameSync(tmp, dest);
32
32
  }
33
+ function localStateToConnectionInfo(state) {
34
+ return {
35
+ host: state.sshHost,
36
+ port: state.sshPort,
37
+ user: state.sshUser,
38
+ privateKeyPath: state.privateKeyPath,
39
+ knownHostsPath: state.knownHostsPath
40
+ };
41
+ }
33
42
 
34
43
  export {
44
+ stateDir,
45
+ statePath,
35
46
  readLocalState,
36
- writeLocalState
47
+ writeLocalState,
48
+ localStateToConnectionInfo
37
49
  };
@@ -2,12 +2,15 @@
2
2
  import {
3
3
  validatePlan
4
4
  } from "./chunk-YTH4L2GN.js";
5
+ import {
6
+ saveOverlay
7
+ } from "./chunk-6ZFIFDBJ.js";
5
8
  import {
6
9
  resolveSecrets
7
10
  } from "./chunk-BOPSG2LI.js";
8
11
  import {
9
12
  buildContext
10
- } from "./chunk-ACYJBSLJ.js";
13
+ } from "./chunk-JDN6PLH2.js";
11
14
  import {
12
15
  atomicWriteConfig,
13
16
  deepMerge,
@@ -106,6 +109,7 @@ async function applyConfigOverlay(plan, outputs, ctx, signal) {
106
109
  });
107
110
  await atomicWriteConfig(session, merged, signal);
108
111
  await restartGateway(session, signal);
112
+ saveOverlay(plan.spec.stackName, configOverlay, plan.spec.secrets ?? []);
109
113
  } finally {
110
114
  session.close();
111
115
  }
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  readLocalState
4
- } from "./chunk-BRPU7AQC.js";
4
+ } from "./chunk-A2I76FTA.js";
5
5
  import {
6
6
  getConfigDir,
7
7
  requireConfig
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildContext
4
- } from "./chunk-ACYJBSLJ.js";
4
+ } from "./chunk-JDN6PLH2.js";
5
5
  import {
6
6
  acquireSession,
7
7
  drainPool
@@ -0,0 +1,99 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/mcp-apps.ts
4
+ import { execSync } from "child_process";
5
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
6
+ import path from "path";
7
+ import os from "os";
8
+ var MCP_APPS = [
9
+ {
10
+ id: "claude-desktop",
11
+ name: "Claude Desktop",
12
+ configPath: () => process.platform === "darwin" ? path.join(os.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json") : path.join(os.homedir(), ".config", "Claude", "claude_desktop_config.json"),
13
+ isInstalled: () => existsSync(
14
+ process.platform === "darwin" ? path.join(os.homedir(), "Library", "Application Support", "Claude") : path.join(os.homedir(), ".config", "Claude")
15
+ )
16
+ },
17
+ {
18
+ id: "claude-code",
19
+ name: "Claude Code",
20
+ configPath: () => path.join(os.homedir(), ".claude.json"),
21
+ isInstalled: () => {
22
+ if (existsSync(path.join(os.homedir(), ".claude.json"))) return true;
23
+ try {
24
+ execSync("claude --version", { stdio: "ignore" });
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+ },
31
+ {
32
+ id: "cursor",
33
+ name: "Cursor",
34
+ configPath: () => path.join(os.homedir(), ".cursor", "mcp.json"),
35
+ isInstalled: () => existsSync(path.join(os.homedir(), ".cursor"))
36
+ },
37
+ {
38
+ id: "windsurf",
39
+ name: "Windsurf",
40
+ configPath: () => path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json"),
41
+ isInstalled: () => existsSync(path.join(os.homedir(), ".codeium", "windsurf"))
42
+ },
43
+ {
44
+ id: "vscode",
45
+ name: "VS Code",
46
+ configPath: () => process.platform === "linux" ? path.join(os.homedir(), ".config", "Code", "User", "mcp.json") : path.join(os.homedir(), "Library", "Application Support", "Code", "User", "mcp.json"),
47
+ isInstalled: () => existsSync(
48
+ process.platform === "linux" ? path.join(os.homedir(), ".config", "Code") : path.join(os.homedir(), "Library", "Application Support", "Code")
49
+ )
50
+ },
51
+ {
52
+ id: "zed",
53
+ name: "Zed",
54
+ configPath: () => path.join(os.homedir(), ".config", "zed", "settings.json"),
55
+ isInstalled: () => existsSync(path.join(os.homedir(), ".config", "zed")),
56
+ configKey: "context_servers",
57
+ entryExtra: { type: "stdio" }
58
+ }
59
+ ];
60
+ function buildMcpEntry() {
61
+ try {
62
+ const bin = execSync("which clawops", {
63
+ encoding: "utf-8",
64
+ stdio: ["ignore", "pipe", "ignore"]
65
+ }).trim();
66
+ if (bin) return { command: bin, args: ["mcp", "serve", "--read-only"], resolved: true };
67
+ } catch {
68
+ }
69
+ return { command: "clawops", args: ["mcp", "serve", "--read-only"], resolved: false };
70
+ }
71
+ function writeAppConfigs(apps, entry) {
72
+ return apps.map((app) => {
73
+ const cfgPath = app.configPath();
74
+ try {
75
+ let config = {};
76
+ if (existsSync(cfgPath)) {
77
+ try {
78
+ config = JSON.parse(readFileSync(cfgPath, "utf-8"));
79
+ } catch {
80
+ }
81
+ }
82
+ mkdirSync(path.dirname(cfgPath), { recursive: true });
83
+ const key = app.configKey ?? "mcpServers";
84
+ const servers = config[key] ?? {};
85
+ servers["clawops"] = { ...entry, ...app.entryExtra ?? {} };
86
+ config[key] = servers;
87
+ writeFileSync(cfgPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
88
+ return { app, configPath: cfgPath, ok: true };
89
+ } catch (err) {
90
+ return { app, configPath: cfgPath, ok: false, error: err.message };
91
+ }
92
+ });
93
+ }
94
+
95
+ export {
96
+ MCP_APPS,
97
+ buildMcpEntry,
98
+ writeAppConfigs
99
+ };
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-YTH4L2GN.js";
5
5
  import {
6
6
  buildContext
7
- } from "./chunk-ACYJBSLJ.js";
7
+ } from "./chunk-JDN6PLH2.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-3JZ767DN.js");
48
+ const { version } = await import("./package-AFUTH62F.js");
49
49
  const config = getConfig();
50
50
  const instanceType = intent.instanceType ?? "small";
51
51
  const openclawVersion = intent.openclawVersion ?? "latest";