@clawops/cli 1.2.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.
- package/README.md +112 -42
- package/dist/chunk-OIGTOLB3.js +99 -0
- package/dist/{chunk-QMXM2SMO.js → chunk-SSB6SGPQ.js} +1 -1
- package/dist/cli.js +235 -206
- package/dist/{generate-WG5VONSE.js → generate-PZPLG3ZM.js} +1 -1
- package/dist/mcp-apps-KY44ED3Y.js +11 -0
- package/dist/{package-3JZ767DN.js → package-SE3M4NJA.js} +1 -1
- package/dist/{server-QJRWK2RV.js → server-4DNHLGEG.js} +2 -2
- package/package.json +1 -1
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
|
|
48
|
+
## Quick Start
|
|
49
49
|
|
|
50
|
-
|
|
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
|
-
**
|
|
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
|
-
#
|
|
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
|
|
74
|
-
|
|
117
|
+
See [`docs/examples/local-vm.md`](docs/examples/local-vm.md) for SSH prerequisites, firewall
|
|
118
|
+
setup, and troubleshooting.
|
|
75
119
|
|
|
76
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
114
|
-
|
|
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
|
|
179
|
+
confirmation before executing — they will never run silently.
|
|
118
180
|
|
|
119
|
-
For
|
|
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 (
|
|
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
|
-
###
|
|
265
|
+
### Wire your editor
|
|
202
266
|
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
|
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
|
|
|
@@ -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
|
+
};
|
|
@@ -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-
|
|
48
|
+
const { version } = await import("./package-SE3M4NJA.js");
|
|
49
49
|
const config = getConfig();
|
|
50
50
|
const instanceType = intent.instanceType ?? "small";
|
|
51
51
|
const openclawVersion = intent.openclawVersion ?? "latest";
|
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
validateOpenclawConfig
|
|
4
4
|
} from "./chunk-QJ6ERXHN.js";
|
|
5
|
+
import {
|
|
6
|
+
MCP_APPS,
|
|
7
|
+
buildMcpEntry,
|
|
8
|
+
writeAppConfigs
|
|
9
|
+
} from "./chunk-OIGTOLB3.js";
|
|
5
10
|
import "./chunk-ACYJBSLJ.js";
|
|
6
11
|
import "./chunk-BRPU7AQC.js";
|
|
7
12
|
import "./chunk-ZVOEQCNW.js";
|
|
@@ -19,7 +24,7 @@ import {
|
|
|
19
24
|
} from "./chunk-KGXPLI7W.js";
|
|
20
25
|
|
|
21
26
|
// src/cli/index.ts
|
|
22
|
-
import { defineCommand as
|
|
27
|
+
import { defineCommand as defineCommand20, runMain } from "citty";
|
|
23
28
|
|
|
24
29
|
// src/cli/commands/init.ts
|
|
25
30
|
import { defineCommand } from "citty";
|
|
@@ -495,7 +500,7 @@ var plan_default = defineCommand5({
|
|
|
495
500
|
},
|
|
496
501
|
async run({ args }) {
|
|
497
502
|
const { buildContext } = await import("./context-VUAYRAJY.js");
|
|
498
|
-
const { generatePlan } = await import("./generate-
|
|
503
|
+
const { generatePlan } = await import("./generate-PZPLG3ZM.js");
|
|
499
504
|
const ctx = buildContext(args);
|
|
500
505
|
if (ctx.adapter.name === "local") {
|
|
501
506
|
throw new UsageError(
|
|
@@ -1782,13 +1787,11 @@ var doctor_default = defineCommand16({
|
|
|
1782
1787
|
|
|
1783
1788
|
// src/cli/commands/mcp.ts
|
|
1784
1789
|
import { defineCommand as defineCommand17 } from "citty";
|
|
1785
|
-
import
|
|
1786
|
-
|
|
1787
|
-
import os from "os";
|
|
1788
|
-
var mcp_default = defineCommand17({
|
|
1790
|
+
import process19 from "process";
|
|
1791
|
+
var serveCmd = defineCommand17({
|
|
1789
1792
|
meta: {
|
|
1790
|
-
name: "
|
|
1791
|
-
description: "
|
|
1793
|
+
name: "serve",
|
|
1794
|
+
description: "Start the clawops MCP server"
|
|
1792
1795
|
},
|
|
1793
1796
|
args: {
|
|
1794
1797
|
http: { type: "string", description: "HTTP port for standalone mode" },
|
|
@@ -1796,25 +1799,10 @@ var mcp_default = defineCommand17({
|
|
|
1796
1799
|
"read-only": { type: "boolean", description: "Only register read toolset" },
|
|
1797
1800
|
"no-destructive": { type: "boolean", description: "Filter out destructive tools" },
|
|
1798
1801
|
toolsets: { type: "string", description: "Comma-separated toolsets to enable" },
|
|
1799
|
-
inspector: { type: "boolean", description: "Enable MCP inspector" }
|
|
1800
|
-
claude: { type: "boolean", description: "Install for Claude Desktop" },
|
|
1801
|
-
cursor: { type: "boolean", description: "Install for Cursor" },
|
|
1802
|
-
vscode: { type: "boolean", description: "Install for VS Code" },
|
|
1803
|
-
windsurf: { type: "boolean", description: "Install for Windsurf" },
|
|
1804
|
-
zed: { type: "boolean", description: "Install for Zed" }
|
|
1802
|
+
inspector: { type: "boolean", description: "Enable MCP inspector" }
|
|
1805
1803
|
},
|
|
1806
1804
|
async run({ args }) {
|
|
1807
|
-
const
|
|
1808
|
-
const requestedClients = installFlags.filter((f) => Boolean(args[f]));
|
|
1809
|
-
if (requestedClients.length > 0) {
|
|
1810
|
-
for (const client of requestedClients) {
|
|
1811
|
-
installMcp(client);
|
|
1812
|
-
process.stderr.write(`Installed MCP config for: ${client}
|
|
1813
|
-
`);
|
|
1814
|
-
}
|
|
1815
|
-
return;
|
|
1816
|
-
}
|
|
1817
|
-
const { serveMcp } = await import("./server-QJRWK2RV.js");
|
|
1805
|
+
const { serveMcp } = await import("./server-4DNHLGEG.js");
|
|
1818
1806
|
await serveMcp({
|
|
1819
1807
|
port: args.http ? Number(args.http) : void 0,
|
|
1820
1808
|
bind: args.bind,
|
|
@@ -1825,60 +1813,86 @@ var mcp_default = defineCommand17({
|
|
|
1825
1813
|
});
|
|
1826
1814
|
}
|
|
1827
1815
|
});
|
|
1828
|
-
var
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1816
|
+
var installCmd = defineCommand17({
|
|
1817
|
+
meta: {
|
|
1818
|
+
name: "install",
|
|
1819
|
+
description: "Interactively wire clawops into AI editors (Claude Desktop, Claude Code, Cursor, \u2026)"
|
|
1820
|
+
},
|
|
1821
|
+
args: {},
|
|
1822
|
+
async run() {
|
|
1823
|
+
const inquirer = (await import("inquirer")).default;
|
|
1824
|
+
const { MCP_APPS: MCP_APPS2, buildMcpEntry: buildMcpEntry2, writeAppConfigs: writeAppConfigs2 } = await import("./mcp-apps-KY44ED3Y.js");
|
|
1825
|
+
const mcpEntry = buildMcpEntry2();
|
|
1826
|
+
if (!mcpEntry.resolved) {
|
|
1827
|
+
info("clawops is not installed globally \u2014 AI apps may not be able to start the MCP server.");
|
|
1828
|
+
info("Install it first: npm install -g @clawops/cli");
|
|
1829
|
+
info('The config will still be written now using "clawops" as the command name.\n');
|
|
1830
|
+
}
|
|
1831
|
+
info("Use \u2191\u2193 to move, Space to select/deselect, Enter to confirm.");
|
|
1832
|
+
const choices = MCP_APPS2.map((app) => ({
|
|
1833
|
+
name: app.isInstalled() ? app.name : `${app.name} (not detected \u2014 config will be written anyway)`,
|
|
1834
|
+
value: app.id,
|
|
1835
|
+
checked: app.isInstalled()
|
|
1836
|
+
}));
|
|
1837
|
+
const { selectedIds } = await inquirer.prompt([{
|
|
1838
|
+
type: "checkbox",
|
|
1839
|
+
name: "selectedIds",
|
|
1840
|
+
message: "Which AI editors should have access to clawops?",
|
|
1841
|
+
choices,
|
|
1842
|
+
pageSize: MCP_APPS2.length + 1
|
|
1843
|
+
}]);
|
|
1844
|
+
const selected = MCP_APPS2.filter((app) => selectedIds.includes(app.id));
|
|
1845
|
+
if (selected.length === 0) {
|
|
1846
|
+
info("No apps selected. Add this to an app's MCP config manually:");
|
|
1847
|
+
process19.stdout.write(JSON.stringify({
|
|
1848
|
+
mcpServers: { clawops: { command: mcpEntry.command, args: mcpEntry.args } }
|
|
1849
|
+
}, null, 2) + "\n\n");
|
|
1850
|
+
info("Config file locations:");
|
|
1851
|
+
for (const app of MCP_APPS2) {
|
|
1852
|
+
info(` ${app.name.padEnd(18)} ${app.configPath()}`);
|
|
1853
|
+
}
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
const results = writeAppConfigs2(selected, { command: mcpEntry.command, args: mcpEntry.args });
|
|
1857
|
+
for (const r of results) {
|
|
1858
|
+
if (r.ok) {
|
|
1859
|
+
success(`${r.app.name} configured (${r.configPath})`);
|
|
1860
|
+
} else {
|
|
1861
|
+
failure(`Could not configure ${r.app.name}: ${r.error ?? "unknown error"}`);
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
const needsRestart = selected.filter((app) => app.id !== "claude-code");
|
|
1865
|
+
if (needsRestart.length > 0) {
|
|
1866
|
+
info(`Restart ${needsRestart.map((a) => a.name).join(", ")} to load the clawops tool.`);
|
|
1860
1867
|
}
|
|
1861
1868
|
}
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1869
|
+
});
|
|
1870
|
+
var mcp_default = defineCommand17({
|
|
1871
|
+
meta: {
|
|
1872
|
+
name: "mcp",
|
|
1873
|
+
description: "MCP server operations"
|
|
1874
|
+
},
|
|
1875
|
+
args: {},
|
|
1876
|
+
subCommands: {
|
|
1877
|
+
serve: serveCmd,
|
|
1878
|
+
install: installCmd
|
|
1879
|
+
},
|
|
1880
|
+
run() {
|
|
1881
|
+
process19.stdout.write("Usage: clawops mcp <serve | install>\n\n");
|
|
1882
|
+
process19.stdout.write(" serve Start the MCP server (stdio or HTTP)\n");
|
|
1883
|
+
process19.stdout.write(" install Wire clawops into AI editors interactively\n\n");
|
|
1884
|
+
process19.stdout.write("Run `clawops mcp <command> --help` for flags.\n");
|
|
1870
1885
|
}
|
|
1871
|
-
|
|
1872
|
-
}
|
|
1886
|
+
});
|
|
1873
1887
|
|
|
1874
1888
|
// src/cli/commands/setup.ts
|
|
1875
1889
|
import { defineCommand as defineCommand18 } from "citty";
|
|
1876
|
-
import
|
|
1877
|
-
import
|
|
1890
|
+
import process20 from "process";
|
|
1891
|
+
import os from "os";
|
|
1878
1892
|
import { randomBytes } from "crypto";
|
|
1879
|
-
import { readFileSync as
|
|
1893
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
1880
1894
|
import { execSync, spawnSync, spawn as spawn2 } from "child_process";
|
|
1881
|
-
import
|
|
1895
|
+
import path3 from "path";
|
|
1882
1896
|
import { fileURLToPath } from "url";
|
|
1883
1897
|
var setup_default = defineCommand18({
|
|
1884
1898
|
meta: {
|
|
@@ -1895,16 +1909,16 @@ var setup_default = defineCommand18({
|
|
|
1895
1909
|
const catalogs = loadCatalogs(yaml);
|
|
1896
1910
|
const dryRun = Boolean(args["dry-run"]);
|
|
1897
1911
|
const ac = new AbortController();
|
|
1898
|
-
|
|
1912
|
+
process20.on("SIGINT", () => {
|
|
1899
1913
|
ac.abort();
|
|
1900
|
-
|
|
1914
|
+
process20.exit(130);
|
|
1901
1915
|
});
|
|
1902
|
-
|
|
1916
|
+
process20.on("SIGTERM", () => {
|
|
1903
1917
|
ac.abort();
|
|
1904
|
-
|
|
1918
|
+
process20.exit(143);
|
|
1905
1919
|
});
|
|
1906
|
-
|
|
1907
|
-
|
|
1920
|
+
process20.stdout.write("\nWelcome to clawops setup. This wizard will help you deploy OpenClaw\n");
|
|
1921
|
+
process20.stdout.write("and connect it to your AI tools. It takes about 2 minutes.\n\n");
|
|
1908
1922
|
const { deploymentType } = await inquirer.prompt([{
|
|
1909
1923
|
type: "list",
|
|
1910
1924
|
name: "deploymentType",
|
|
@@ -1916,7 +1930,7 @@ var setup_default = defineCommand18({
|
|
|
1916
1930
|
}]);
|
|
1917
1931
|
let provider;
|
|
1918
1932
|
let localHost = "";
|
|
1919
|
-
let localUser =
|
|
1933
|
+
let localUser = os.userInfo().username;
|
|
1920
1934
|
let localKeyPath = detectSshKey();
|
|
1921
1935
|
let localPort = 22;
|
|
1922
1936
|
let localSudoPassword = "";
|
|
@@ -1932,12 +1946,12 @@ var setup_default = defineCommand18({
|
|
|
1932
1946
|
]
|
|
1933
1947
|
}]);
|
|
1934
1948
|
provider = answer.provider;
|
|
1935
|
-
|
|
1949
|
+
process20.stdout.write("\n");
|
|
1936
1950
|
await ensureCloudAuth(provider, inquirer);
|
|
1937
1951
|
} else {
|
|
1938
1952
|
provider = "local";
|
|
1939
1953
|
info("We'll connect to your server over SSH to install and configure OpenClaw.\n");
|
|
1940
|
-
if (!
|
|
1954
|
+
if (!existsSync2(localKeyPath)) {
|
|
1941
1955
|
info(`No SSH key found at ${localKeyPath}.`);
|
|
1942
1956
|
const { genKey } = await inquirer.prompt([{
|
|
1943
1957
|
type: "confirm",
|
|
@@ -1973,7 +1987,7 @@ var setup_default = defineCommand18({
|
|
|
1973
1987
|
name: "keyPath",
|
|
1974
1988
|
message: `SSH private key file: (the key file on your computer used to log in \u2014 e.g. ~/.ssh/id_ed25519)`,
|
|
1975
1989
|
default: localKeyPath,
|
|
1976
|
-
validate: (v) =>
|
|
1990
|
+
validate: (v) => existsSync2(v.trim()) || `File not found: ${v.trim()}`
|
|
1977
1991
|
}
|
|
1978
1992
|
]);
|
|
1979
1993
|
localHost = localAnswers.host;
|
|
@@ -1988,7 +2002,7 @@ var setup_default = defineCommand18({
|
|
|
1988
2002
|
}]);
|
|
1989
2003
|
localSudoPassword = sudoPass;
|
|
1990
2004
|
}
|
|
1991
|
-
|
|
2005
|
+
process20.stdout.write("\n");
|
|
1992
2006
|
const stackAnswers = await inquirer.prompt([
|
|
1993
2007
|
{
|
|
1994
2008
|
type: "input",
|
|
@@ -2036,7 +2050,7 @@ var setup_default = defineCommand18({
|
|
|
2036
2050
|
default: "latest"
|
|
2037
2051
|
}
|
|
2038
2052
|
]);
|
|
2039
|
-
|
|
2053
|
+
process20.stdout.write("\n");
|
|
2040
2054
|
info("Choose the AI model that your OpenClaw agent will use.");
|
|
2041
2055
|
info("You'll need an API key from your chosen provider (except Bedrock and Ollama).\n");
|
|
2042
2056
|
const { modelProviderId } = await inquirer.prompt([{
|
|
@@ -2065,7 +2079,7 @@ var setup_default = defineCommand18({
|
|
|
2065
2079
|
secrets.push(entry);
|
|
2066
2080
|
modelConfig["apiKey"] = `$secret:${secretName}`;
|
|
2067
2081
|
} else if (modelProvider.credentialSource === "aws-profile") {
|
|
2068
|
-
|
|
2082
|
+
process20.stdout.write("\n");
|
|
2069
2083
|
info(modelProvider.iamNote ?? "Bedrock uses your AWS server's IAM role \u2014 no API key needed.");
|
|
2070
2084
|
info("Make sure your EC2 instance has the AmazonBedrockFullAccess IAM policy attached.\n");
|
|
2071
2085
|
} else if (modelProvider.id === "ollama") {
|
|
@@ -2082,7 +2096,7 @@ var setup_default = defineCommand18({
|
|
|
2082
2096
|
...selectedModel.modelId ? { modelId: selectedModel.modelId } : { model: selectedModel.id },
|
|
2083
2097
|
...modelConfig
|
|
2084
2098
|
};
|
|
2085
|
-
|
|
2099
|
+
process20.stdout.write("\n");
|
|
2086
2100
|
info("Use \u2191\u2193 to move, Space to select/deselect, Enter to confirm.");
|
|
2087
2101
|
const { selectedIntegrationIds } = await inquirer.prompt([{
|
|
2088
2102
|
type: "checkbox",
|
|
@@ -2098,7 +2112,7 @@ var setup_default = defineCommand18({
|
|
|
2098
2112
|
const channelsConfig = {};
|
|
2099
2113
|
const infraRequired = [];
|
|
2100
2114
|
for (const integ of catalogs.integrations.filter((i) => selectedIntegrationIds.includes(i.id))) {
|
|
2101
|
-
|
|
2115
|
+
process20.stdout.write("\n");
|
|
2102
2116
|
info(`Setting up ${integ.displayName}:`);
|
|
2103
2117
|
const channelConfig = {};
|
|
2104
2118
|
for (const field of integ.fields) {
|
|
@@ -2134,11 +2148,11 @@ var setup_default = defineCommand18({
|
|
|
2134
2148
|
}
|
|
2135
2149
|
}
|
|
2136
2150
|
const gatewayToken = randomBytes(24).toString("hex");
|
|
2137
|
-
const secretsDir =
|
|
2138
|
-
|
|
2151
|
+
const secretsDir = path3.join(os.homedir(), ".clawops", "secrets");
|
|
2152
|
+
mkdirSync3(secretsDir, { recursive: true });
|
|
2139
2153
|
spawnSync("chmod", ["700", secretsDir], { stdio: "ignore" });
|
|
2140
|
-
const tokenPath =
|
|
2141
|
-
|
|
2154
|
+
const tokenPath = path3.join(secretsDir, `GATEWAY_TOKEN_${stackAnswers.stackName}`);
|
|
2155
|
+
writeFileSync3(tokenPath, gatewayToken, { encoding: "utf-8", mode: 384 });
|
|
2142
2156
|
const openclawConfigOverlay = {
|
|
2143
2157
|
models: builtModelConfig,
|
|
2144
2158
|
gateway: { auth: { mode: "token", token: gatewayToken } }
|
|
@@ -2148,17 +2162,17 @@ var setup_default = defineCommand18({
|
|
|
2148
2162
|
}
|
|
2149
2163
|
let outputPath;
|
|
2150
2164
|
if (provider === "local") {
|
|
2151
|
-
outputPath =
|
|
2152
|
-
|
|
2153
|
-
|
|
2165
|
+
outputPath = path3.join(outDir, `openclaw-${stackAnswers.stackName}.json`);
|
|
2166
|
+
writeFileSync3(outputPath, JSON.stringify(openclawConfigOverlay, null, 2), "utf-8");
|
|
2167
|
+
process20.stdout.write("\n");
|
|
2154
2168
|
success(`Config file saved to ${outputPath}`);
|
|
2155
2169
|
} else {
|
|
2156
2170
|
let sshPublicKey = "";
|
|
2157
2171
|
try {
|
|
2158
|
-
sshPublicKey =
|
|
2172
|
+
sshPublicKey = readFileSync3(stackAnswers.sshKeyPath ?? "", "utf-8").trim();
|
|
2159
2173
|
} catch {
|
|
2160
2174
|
failure(`Cannot read SSH public key at ${stackAnswers.sshKeyPath ?? ""}`);
|
|
2161
|
-
|
|
2175
|
+
process20.exit(1);
|
|
2162
2176
|
}
|
|
2163
2177
|
const plan = {
|
|
2164
2178
|
apiVersion: "clawops.dev/v1",
|
|
@@ -2187,12 +2201,12 @@ var setup_default = defineCommand18({
|
|
|
2187
2201
|
...stackAnswers.stateBucket ? { stateBucket: stackAnswers.stateBucket } : {}
|
|
2188
2202
|
}
|
|
2189
2203
|
};
|
|
2190
|
-
outputPath =
|
|
2191
|
-
|
|
2192
|
-
|
|
2204
|
+
outputPath = path3.join(outDir, `clawops-${stackAnswers.stackName}-plan.json`);
|
|
2205
|
+
writeFileSync3(outputPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
2206
|
+
process20.stdout.write("\n");
|
|
2193
2207
|
success(`Deployment plan saved to ${outputPath}`);
|
|
2194
2208
|
}
|
|
2195
|
-
|
|
2209
|
+
process20.stdout.write("\n");
|
|
2196
2210
|
const mcpEntry = buildMcpEntry();
|
|
2197
2211
|
if (!mcpEntry.resolved) {
|
|
2198
2212
|
info("Note: clawops is not installed globally \u2014 AI apps will not be able to start");
|
|
@@ -2215,21 +2229,12 @@ var setup_default = defineCommand18({
|
|
|
2215
2229
|
}]);
|
|
2216
2230
|
const selectedApps = MCP_APPS.filter((app) => selectedAppIds.includes(app.id));
|
|
2217
2231
|
if (selectedApps.length > 0) {
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
}
|
|
2225
|
-
const mcpServers = existing["mcpServers"] ?? {};
|
|
2226
|
-
mcpServers["clawops"] = { command: mcpEntry.command, args: mcpEntry.args };
|
|
2227
|
-
existing["mcpServers"] = mcpServers;
|
|
2228
|
-
mkdirSync4(path4.dirname(cfgPath), { recursive: true });
|
|
2229
|
-
writeFileSync4(cfgPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
2230
|
-
success(`${app.name} configured (${cfgPath})`);
|
|
2231
|
-
} catch (err) {
|
|
2232
|
-
failure(`Could not configure ${app.name}: ${err.message}`);
|
|
2232
|
+
const results = writeAppConfigs(selectedApps, { command: mcpEntry.command, args: mcpEntry.args });
|
|
2233
|
+
for (const r of results) {
|
|
2234
|
+
if (r.ok) {
|
|
2235
|
+
success(`${r.app.name} configured (${r.configPath})`);
|
|
2236
|
+
} else {
|
|
2237
|
+
failure(`Could not configure ${r.app.name}: ${r.error ?? "unknown error"}`);
|
|
2233
2238
|
info("Add this to its MCP config manually:");
|
|
2234
2239
|
printMcpSnippet();
|
|
2235
2240
|
}
|
|
@@ -2247,7 +2252,7 @@ var setup_default = defineCommand18({
|
|
|
2247
2252
|
}
|
|
2248
2253
|
}
|
|
2249
2254
|
if (infraRequired.length > 0) {
|
|
2250
|
-
|
|
2255
|
+
process20.stdout.write("\n");
|
|
2251
2256
|
info("\u2500\u2500 Action required: webhook registration \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2252
2257
|
info("These integrations need a webhook URL set up in their developer portal:");
|
|
2253
2258
|
for (const integ of infraRequired) {
|
|
@@ -2258,7 +2263,7 @@ var setup_default = defineCommand18({
|
|
|
2258
2263
|
}
|
|
2259
2264
|
}
|
|
2260
2265
|
if (modelProvider.id === "ollama" && selectedModel.pullSuffix !== void 0) {
|
|
2261
|
-
|
|
2266
|
+
process20.stdout.write("\n");
|
|
2262
2267
|
info("\u2500\u2500 Action required: download the Ollama model \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
2263
2268
|
info("After deployment, run this command on the server (or locally if Ollama is local):");
|
|
2264
2269
|
info(` ollama pull ${selectedModel.id}${selectedModel.pullSuffix}`);
|
|
@@ -2288,8 +2293,8 @@ var setup_default = defineCommand18({
|
|
|
2288
2293
|
});
|
|
2289
2294
|
} else {
|
|
2290
2295
|
const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
|
|
2291
|
-
const knownHostsPath =
|
|
2292
|
-
|
|
2296
|
+
const knownHostsPath = path3.join(getConfigDir2(), "known_hosts");
|
|
2297
|
+
process20.stdout.write("\n");
|
|
2293
2298
|
info("To deploy later, run these two commands:");
|
|
2294
2299
|
info(` clawops init --provider local --host ${localHost} --port ${localPort} --user ${localUser} --key ${localKeyPath} --stack ${stackAnswers.stackName}`);
|
|
2295
2300
|
info(` clawops up --stack ${stackAnswers.stackName} --config ${outputPath}`);
|
|
@@ -2305,17 +2310,17 @@ var setup_default = defineCommand18({
|
|
|
2305
2310
|
}]);
|
|
2306
2311
|
if (applyNow) {
|
|
2307
2312
|
const { applyPlan } = await import("./apply-KBF7OPWL.js");
|
|
2308
|
-
const plan = JSON.parse(
|
|
2313
|
+
const plan = JSON.parse(readFileSync3(outputPath, "utf-8"));
|
|
2309
2314
|
try {
|
|
2310
2315
|
await applyPlan(plan, {
|
|
2311
|
-
onOutput: (line) =>
|
|
2316
|
+
onOutput: (line) => process20.stdout.write(line),
|
|
2312
2317
|
signal: ac.signal
|
|
2313
2318
|
});
|
|
2314
2319
|
success("Server provisioned and OpenClaw installed.");
|
|
2315
2320
|
info(`Run: clawops doctor --stack ${stackAnswers.stackName} to verify everything is healthy`);
|
|
2316
2321
|
} catch (err) {
|
|
2317
2322
|
failure(err instanceof Error ? err.message : String(err));
|
|
2318
|
-
|
|
2323
|
+
process20.exit(1);
|
|
2319
2324
|
}
|
|
2320
2325
|
} else {
|
|
2321
2326
|
info(`
|
|
@@ -2330,7 +2335,7 @@ async function runLocalDeploy(opts) {
|
|
|
2330
2335
|
const { connect } = await import("./ssh-IQXNME3D.js");
|
|
2331
2336
|
const { readRemoteConfig, atomicWriteConfig, restartGateway, deepMerge } = await import("./remote-config-JQ77SRC2.js");
|
|
2332
2337
|
const { resolveSecrets } = await import("./secrets-SVZWNAPK.js");
|
|
2333
|
-
const knownHostsPath =
|
|
2338
|
+
const knownHostsPath = path3.join(getConfigDir2(), "known_hosts");
|
|
2334
2339
|
const existing = getConfig2();
|
|
2335
2340
|
const stackEntry = {
|
|
2336
2341
|
provider: "local",
|
|
@@ -2409,9 +2414,9 @@ async function runLocalDeploy(opts) {
|
|
|
2409
2414
|
}
|
|
2410
2415
|
const { drainPool } = await import("./pool-FBFHATDG.js");
|
|
2411
2416
|
drainPool();
|
|
2412
|
-
const tokenPath =
|
|
2417
|
+
const tokenPath = path3.join(os.homedir(), ".clawops", "secrets", `GATEWAY_TOKEN_${opts.stackName}`);
|
|
2413
2418
|
const dashboardUrl = `${state.gatewayUrl}?token=${opts.gatewayToken}`;
|
|
2414
|
-
|
|
2419
|
+
process20.stdout.write("\n");
|
|
2415
2420
|
success("All done! OpenClaw is running.");
|
|
2416
2421
|
info(`Open dashboard: ${dashboardUrl}`);
|
|
2417
2422
|
info(` (or enter the token manually in the "Gateway Token" field: ${opts.gatewayToken})`);
|
|
@@ -2465,7 +2470,7 @@ ${cfg.loginHint}`);
|
|
|
2465
2470
|
default: true
|
|
2466
2471
|
}]);
|
|
2467
2472
|
if (runNow) {
|
|
2468
|
-
|
|
2473
|
+
process20.stdout.write("\n");
|
|
2469
2474
|
const result = spawnSync(cfg.loginCmd[0], cfg.loginCmd.slice(1), { stdio: "inherit" });
|
|
2470
2475
|
if (result.error) {
|
|
2471
2476
|
failure(`Could not launch ${cliName} \u2014 is it installed? (${result.error.message})`);
|
|
@@ -2475,10 +2480,10 @@ ${cfg.loginHint}`);
|
|
|
2475
2480
|
}
|
|
2476
2481
|
try {
|
|
2477
2482
|
const identity = cfg.check();
|
|
2478
|
-
|
|
2483
|
+
process20.stdout.write("\n");
|
|
2479
2484
|
success(`Authenticated: ${cfg.identity(identity)}`);
|
|
2480
2485
|
} catch {
|
|
2481
|
-
|
|
2486
|
+
process20.stdout.write("\n");
|
|
2482
2487
|
failure(`Still not authenticated \u2014 the sign-in may have been cancelled or failed.`);
|
|
2483
2488
|
info("Re-run `clawops setup` after signing in, or continue and authenticate before running `clawops up`.\n");
|
|
2484
2489
|
}
|
|
@@ -2492,7 +2497,7 @@ function cliInstallUrl(provider) {
|
|
|
2492
2497
|
return "https://learn.microsoft.com/en-us/cli/azure/install-azure-cli";
|
|
2493
2498
|
}
|
|
2494
2499
|
async function promptSecret(name, label, envDefault, inquirer) {
|
|
2495
|
-
const secretsDir =
|
|
2500
|
+
const secretsDir = path3.join(os.homedir(), ".clawops", "secrets");
|
|
2496
2501
|
const { secretSource } = await inquirer.prompt([{
|
|
2497
2502
|
type: "list",
|
|
2498
2503
|
name: "secretSource",
|
|
@@ -2510,10 +2515,10 @@ async function promptSecret(name, label, envDefault, inquirer) {
|
|
|
2510
2515
|
message: `Paste your ${label}: (input is hidden)`,
|
|
2511
2516
|
validate: (v) => v.trim() !== "" || "Value cannot be empty"
|
|
2512
2517
|
}]);
|
|
2513
|
-
|
|
2518
|
+
mkdirSync3(secretsDir, { recursive: true });
|
|
2514
2519
|
spawnSync("chmod", ["700", secretsDir], { stdio: "ignore" });
|
|
2515
|
-
const secretPath =
|
|
2516
|
-
|
|
2520
|
+
const secretPath = path3.join(secretsDir, name);
|
|
2521
|
+
writeFileSync3(secretPath, secretValue.trim(), { encoding: "utf-8", mode: 384 });
|
|
2517
2522
|
success(`Secret saved to ${secretPath} (chmod 600)`);
|
|
2518
2523
|
return { name, source: "file", ref: secretPath };
|
|
2519
2524
|
}
|
|
@@ -2530,7 +2535,7 @@ async function promptSecret(name, label, envDefault, inquirer) {
|
|
|
2530
2535
|
type: "input",
|
|
2531
2536
|
name: "filePath",
|
|
2532
2537
|
message: `File path: (full path to the file containing your ${label})`,
|
|
2533
|
-
validate: (v) =>
|
|
2538
|
+
validate: (v) => existsSync2(v.trim()) || `File not found: ${v.trim()}`
|
|
2534
2539
|
}]);
|
|
2535
2540
|
return { name, source: "file", ref: filePath };
|
|
2536
2541
|
}
|
|
@@ -2663,7 +2668,7 @@ async function pollUntilDockerReady(label) {
|
|
|
2663
2668
|
throw new Error("Docker daemon did not become ready within timeout.");
|
|
2664
2669
|
}
|
|
2665
2670
|
async function startDockerAndWait(inquirer) {
|
|
2666
|
-
const isLinux =
|
|
2671
|
+
const isLinux = process20.platform === "linux";
|
|
2667
2672
|
const { choice } = await inquirer.prompt([{
|
|
2668
2673
|
type: "list",
|
|
2669
2674
|
name: "choice",
|
|
@@ -2727,12 +2732,12 @@ async function startDockerAndWait(inquirer) {
|
|
|
2727
2732
|
var LOCALHOST_ALIASES = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "::1"]);
|
|
2728
2733
|
async function ensureAuthorizedKey(keyPath, host, inquirer) {
|
|
2729
2734
|
const pubKeyPath = `${keyPath}.pub`;
|
|
2730
|
-
if (!
|
|
2731
|
-
const pubKey =
|
|
2735
|
+
if (!existsSync2(pubKeyPath)) return;
|
|
2736
|
+
const pubKey = readFileSync3(pubKeyPath, "utf-8").trim();
|
|
2732
2737
|
const isLocal = LOCALHOST_ALIASES.has(host.toLowerCase());
|
|
2733
|
-
const authorizedKeysPath =
|
|
2738
|
+
const authorizedKeysPath = path3.join(os.homedir(), ".ssh", "authorized_keys");
|
|
2734
2739
|
if (isLocal) {
|
|
2735
|
-
const existing =
|
|
2740
|
+
const existing = existsSync2(authorizedKeysPath) ? readFileSync3(authorizedKeysPath, "utf-8") : "";
|
|
2736
2741
|
if (existing.includes(pubKey)) {
|
|
2737
2742
|
success("SSH public key is already in authorized_keys.");
|
|
2738
2743
|
return;
|
|
@@ -2745,9 +2750,9 @@ async function ensureAuthorizedKey(keyPath, host, inquirer) {
|
|
|
2745
2750
|
default: true
|
|
2746
2751
|
}]);
|
|
2747
2752
|
if (addKey) {
|
|
2748
|
-
|
|
2753
|
+
mkdirSync3(path3.join(os.homedir(), ".ssh"), { recursive: true });
|
|
2749
2754
|
const entry = existing.endsWith("\n") || existing === "" ? pubKey + "\n" : "\n" + pubKey + "\n";
|
|
2750
|
-
|
|
2755
|
+
writeFileSync3(authorizedKeysPath, existing + entry, { encoding: "utf-8", flag: "a" });
|
|
2751
2756
|
spawnSync("chmod", ["600", authorizedKeysPath], { stdio: "ignore" });
|
|
2752
2757
|
success("Public key added to ~/.ssh/authorized_keys.");
|
|
2753
2758
|
} else {
|
|
@@ -2755,15 +2760,15 @@ async function ensureAuthorizedKey(keyPath, host, inquirer) {
|
|
|
2755
2760
|
info(` ${pubKey}`);
|
|
2756
2761
|
}
|
|
2757
2762
|
} else {
|
|
2758
|
-
|
|
2763
|
+
process20.stdout.write("\n");
|
|
2759
2764
|
info("Make sure this public key is in ~/.ssh/authorized_keys on your server:");
|
|
2760
2765
|
info(` ${pubKey}`);
|
|
2761
2766
|
info("(If you just created the server, paste the line above into the server's ~/.ssh/authorized_keys file.)\n");
|
|
2762
2767
|
}
|
|
2763
2768
|
}
|
|
2764
2769
|
async function generateSshKey() {
|
|
2765
|
-
const keyPath =
|
|
2766
|
-
|
|
2770
|
+
const keyPath = path3.join(os.homedir(), ".ssh", "id_ed25519");
|
|
2771
|
+
mkdirSync3(path3.join(os.homedir(), ".ssh"), { recursive: true });
|
|
2767
2772
|
const result = spawnSync("ssh-keygen", ["-t", "ed25519", "-f", keyPath, "-N", "", "-C", "clawops"], {
|
|
2768
2773
|
stdio: ["ignore", "pipe", "pipe"]
|
|
2769
2774
|
});
|
|
@@ -2772,77 +2777,30 @@ async function generateSshKey() {
|
|
|
2772
2777
|
}
|
|
2773
2778
|
success(`SSH key pair generated: ${keyPath}`);
|
|
2774
2779
|
info(`Public key (add this to your server's ~/.ssh/authorized_keys if needed):
|
|
2775
|
-
${
|
|
2780
|
+
${readFileSync3(`${keyPath}.pub`, "utf-8").trim()}`);
|
|
2776
2781
|
return keyPath;
|
|
2777
2782
|
}
|
|
2778
2783
|
function detectSshKey() {
|
|
2779
2784
|
const candidates = ["id_ed25519", "id_ecdsa", "id_rsa", "id_dsa"];
|
|
2780
2785
|
for (const name of candidates) {
|
|
2781
|
-
const p =
|
|
2782
|
-
if (
|
|
2783
|
-
}
|
|
2784
|
-
return path4.join(os2.homedir(), ".ssh", "id_ed25519");
|
|
2785
|
-
}
|
|
2786
|
-
function buildMcpEntry() {
|
|
2787
|
-
try {
|
|
2788
|
-
const bin = execSync("which clawops", {
|
|
2789
|
-
encoding: "utf-8",
|
|
2790
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
2791
|
-
}).trim();
|
|
2792
|
-
if (bin) return { command: bin, args: ["mcp", "serve", "--read-only"], resolved: true };
|
|
2793
|
-
} catch {
|
|
2786
|
+
const p = path3.join(os.homedir(), ".ssh", name);
|
|
2787
|
+
if (existsSync2(p)) return p;
|
|
2794
2788
|
}
|
|
2795
|
-
return
|
|
2789
|
+
return path3.join(os.homedir(), ".ssh", "id_ed25519");
|
|
2796
2790
|
}
|
|
2797
|
-
var MCP_APPS = [
|
|
2798
|
-
{
|
|
2799
|
-
id: "claude-desktop",
|
|
2800
|
-
name: "Claude Desktop",
|
|
2801
|
-
configPath: () => process19.platform === "darwin" ? path4.join(os2.homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json") : path4.join(os2.homedir(), ".config", "Claude", "claude_desktop_config.json"),
|
|
2802
|
-
isInstalled: () => existsSync3(
|
|
2803
|
-
process19.platform === "darwin" ? path4.join(os2.homedir(), "Library", "Application Support", "Claude") : path4.join(os2.homedir(), ".config", "Claude")
|
|
2804
|
-
)
|
|
2805
|
-
},
|
|
2806
|
-
{
|
|
2807
|
-
id: "claude-code",
|
|
2808
|
-
name: "Claude Code",
|
|
2809
|
-
configPath: () => path4.join(os2.homedir(), ".claude.json"),
|
|
2810
|
-
isInstalled: () => {
|
|
2811
|
-
if (existsSync3(path4.join(os2.homedir(), ".claude.json"))) return true;
|
|
2812
|
-
try {
|
|
2813
|
-
execSync("claude --version", { stdio: "ignore" });
|
|
2814
|
-
return true;
|
|
2815
|
-
} catch {
|
|
2816
|
-
return false;
|
|
2817
|
-
}
|
|
2818
|
-
}
|
|
2819
|
-
},
|
|
2820
|
-
{
|
|
2821
|
-
id: "cursor",
|
|
2822
|
-
name: "Cursor",
|
|
2823
|
-
configPath: () => path4.join(os2.homedir(), ".cursor", "mcp.json"),
|
|
2824
|
-
isInstalled: () => existsSync3(path4.join(os2.homedir(), ".cursor"))
|
|
2825
|
-
},
|
|
2826
|
-
{
|
|
2827
|
-
id: "windsurf",
|
|
2828
|
-
name: "Windsurf",
|
|
2829
|
-
configPath: () => path4.join(os2.homedir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
2830
|
-
isInstalled: () => existsSync3(path4.join(os2.homedir(), ".codeium", "windsurf"))
|
|
2831
|
-
}
|
|
2832
|
-
];
|
|
2833
2791
|
function printMcpSnippet() {
|
|
2834
|
-
|
|
2835
|
-
|
|
2792
|
+
process20.stdout.write("\n");
|
|
2793
|
+
process20.stdout.write(JSON.stringify({
|
|
2836
2794
|
mcpServers: {
|
|
2837
2795
|
clawops: { command: "clawops", args: ["mcp", "serve", "--read-only"] }
|
|
2838
2796
|
}
|
|
2839
2797
|
}, null, 2) + "\n\n");
|
|
2840
2798
|
}
|
|
2841
2799
|
function loadCatalogs(yaml) {
|
|
2842
|
-
const specDir =
|
|
2800
|
+
const specDir = path3.join(path3.dirname(fileURLToPath(import.meta.url)), "../../../spec");
|
|
2843
2801
|
try {
|
|
2844
|
-
const modelsRaw = yaml.load(
|
|
2845
|
-
const integrationsRaw = yaml.load(
|
|
2802
|
+
const modelsRaw = yaml.load(readFileSync3(path3.join(specDir, "models.yaml"), "utf-8"));
|
|
2803
|
+
const integrationsRaw = yaml.load(readFileSync3(path3.join(specDir, "integrations.yaml"), "utf-8"));
|
|
2846
2804
|
return {
|
|
2847
2805
|
models: modelsRaw.providers,
|
|
2848
2806
|
integrations: integrationsRaw.integrations
|
|
@@ -2887,26 +2845,96 @@ function instanceChoices(provider) {
|
|
|
2887
2845
|
return table[provider] ?? [];
|
|
2888
2846
|
}
|
|
2889
2847
|
|
|
2848
|
+
// src/cli/commands/help.ts
|
|
2849
|
+
import { defineCommand as defineCommand19 } from "citty";
|
|
2850
|
+
var COMMANDS = [
|
|
2851
|
+
["setup", "Interactive first-run wizard \u2014 configure, deploy, and wire AI apps"],
|
|
2852
|
+
["init", "Write config and generate SSH key pair (non-interactive)"],
|
|
2853
|
+
["up", "Provision or update a stack"],
|
|
2854
|
+
["down", "Destroy a local-provider stack (requires --yes)"],
|
|
2855
|
+
["destroy", "Destroy a cloud-provider stack with confirmation prompt (requires --yes)"],
|
|
2856
|
+
["status", "Show stack outputs: IP, gateway URL, region, provisioned time"],
|
|
2857
|
+
["plan", "Generate a deploy-plan JSON artifact (dry-run safe)"],
|
|
2858
|
+
["apply", "Apply a previously reviewed plan file"],
|
|
2859
|
+
["ssh", "Interactive SSH session or run a remote command (--command)"],
|
|
2860
|
+
["logs", "Stream OpenClaw logs (-f, --tail N, --since 5m)"],
|
|
2861
|
+
["tunnel", "Port-forward gateway UI to localhost over SSH"],
|
|
2862
|
+
["config", "Get / set / validate remote OpenClaw config values"],
|
|
2863
|
+
["agents", "List or restart OpenClaw agents"],
|
|
2864
|
+
["gateway", "Restart the OpenClaw gateway service"],
|
|
2865
|
+
["backup", "Create or restore an OpenClaw state backup"],
|
|
2866
|
+
["stacks", "List named stacks and their state"],
|
|
2867
|
+
["doctor", "Check Node version, config, SSH key, provider credentials, and Pulumi home"],
|
|
2868
|
+
["mcp", "MCP server operations (mcp serve | mcp install)"],
|
|
2869
|
+
["help", "Show this help message"]
|
|
2870
|
+
];
|
|
2871
|
+
var GLOBAL_FLAGS = [
|
|
2872
|
+
["--stack <name>", "Target named stack (overrides config default)"],
|
|
2873
|
+
["--provider <name>", "Override provider (aws | gcp | azure | local)"],
|
|
2874
|
+
["--json", "Emit JSON to stdout"],
|
|
2875
|
+
["--quiet", "Suppress non-error output"],
|
|
2876
|
+
["--dry-run", "Preview without applying (mutating commands)"],
|
|
2877
|
+
["--yes", "Skip interactive confirmations (CI mode)"]
|
|
2878
|
+
];
|
|
2879
|
+
var help_default = defineCommand19({
|
|
2880
|
+
meta: {
|
|
2881
|
+
name: "help",
|
|
2882
|
+
description: "Show available commands and global flags"
|
|
2883
|
+
},
|
|
2884
|
+
args: {},
|
|
2885
|
+
run() {
|
|
2886
|
+
const bold = chalk.bold;
|
|
2887
|
+
const dim = chalk.dim;
|
|
2888
|
+
const cyan = chalk.cyan;
|
|
2889
|
+
const cmdWidth = Math.max(...COMMANDS.map(([c]) => c.length));
|
|
2890
|
+
const flagWidth = Math.max(...GLOBAL_FLAGS.map(([f]) => f.length));
|
|
2891
|
+
process.stdout.write(`
|
|
2892
|
+
${bold("clawops")} \u2014 Deploy and manage self-hosted OpenClaw instances
|
|
2893
|
+
|
|
2894
|
+
`);
|
|
2895
|
+
process.stdout.write(`${bold("Usage:")} clawops <command> [flags]
|
|
2896
|
+
|
|
2897
|
+
`);
|
|
2898
|
+
process.stdout.write(`${bold("Commands:")}
|
|
2899
|
+
`);
|
|
2900
|
+
for (const [cmd, desc] of COMMANDS) {
|
|
2901
|
+
process.stdout.write(` ${cyan(cmd.padEnd(cmdWidth))} ${desc}
|
|
2902
|
+
`);
|
|
2903
|
+
}
|
|
2904
|
+
process.stdout.write(`
|
|
2905
|
+
${bold("Global flags:")}
|
|
2906
|
+
`);
|
|
2907
|
+
for (const [flag, desc] of GLOBAL_FLAGS) {
|
|
2908
|
+
process.stdout.write(` ${flag.padEnd(flagWidth)} ${dim(desc)}
|
|
2909
|
+
`);
|
|
2910
|
+
}
|
|
2911
|
+
process.stdout.write(`
|
|
2912
|
+
${dim("Run `clawops <command> --help` for command-specific flags.")}
|
|
2913
|
+
|
|
2914
|
+
`);
|
|
2915
|
+
}
|
|
2916
|
+
});
|
|
2917
|
+
|
|
2890
2918
|
// src/cli/error-handler.ts
|
|
2891
|
-
import
|
|
2919
|
+
import process21 from "process";
|
|
2892
2920
|
function handleError(err) {
|
|
2893
2921
|
if (err instanceof ClawopsError) {
|
|
2894
2922
|
failure(err.message);
|
|
2895
|
-
|
|
2923
|
+
process21.exit(err.exitCode);
|
|
2896
2924
|
}
|
|
2897
2925
|
if (err instanceof Error) {
|
|
2898
2926
|
failure(`Unexpected error: ${err.message}`);
|
|
2899
|
-
if (
|
|
2900
|
-
|
|
2927
|
+
if (process21.env["DEBUG"]) {
|
|
2928
|
+
process21.stderr.write(err.stack ?? "\n");
|
|
2901
2929
|
}
|
|
2902
|
-
|
|
2930
|
+
process21.exit(1);
|
|
2903
2931
|
}
|
|
2904
2932
|
failure(`Unknown error: ${String(err)}`);
|
|
2905
|
-
|
|
2933
|
+
process21.exit(1);
|
|
2906
2934
|
}
|
|
2907
2935
|
|
|
2908
2936
|
// src/cli/index.ts
|
|
2909
|
-
var main =
|
|
2937
|
+
var main = defineCommand20({
|
|
2910
2938
|
meta: {
|
|
2911
2939
|
name: "clawops",
|
|
2912
2940
|
description: "Deploy and manage self-hosted OpenClaw instances across clouds",
|
|
@@ -2939,7 +2967,8 @@ var main = defineCommand19({
|
|
|
2939
2967
|
stacks: stacks_default,
|
|
2940
2968
|
doctor: doctor_default,
|
|
2941
2969
|
mcp: mcp_default,
|
|
2942
|
-
setup: setup_default
|
|
2970
|
+
setup: setup_default,
|
|
2971
|
+
help: help_default
|
|
2943
2972
|
}
|
|
2944
2973
|
});
|
|
2945
2974
|
try {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
generatePlan
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-SSB6SGPQ.js";
|
|
5
5
|
import {
|
|
6
6
|
applyPlan
|
|
7
7
|
} from "./chunk-LU63NZD3.js";
|
|
@@ -1231,7 +1231,7 @@ If none of the above resolves the issue:
|
|
|
1231
1231
|
|
|
1232
1232
|
// src/mcp/server.ts
|
|
1233
1233
|
async function serveMcp(opts) {
|
|
1234
|
-
const { version } = await import("./package-
|
|
1234
|
+
const { version } = await import("./package-SE3M4NJA.js");
|
|
1235
1235
|
const server = new McpServer({ name: "clawops", version });
|
|
1236
1236
|
registerTools(server, opts);
|
|
1237
1237
|
registerResources(server);
|