@clawops/cli 1.4.0 → 1.5.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 +39 -0
- package/dist/apply-5RREMN3L.js +0 -0
- package/dist/automation-RULUSJBW.js +0 -0
- package/dist/aws-FRE2JVAZ.js +0 -0
- package/dist/azure-PVC3AQVC.js +0 -0
- package/dist/bootstrap-G4UZ2FKH.js +0 -0
- package/dist/chunk-3QJBNAHW.js +0 -0
- package/dist/chunk-4U3LTLWZ.js +0 -0
- package/dist/chunk-6ZFIFDBJ.js +0 -0
- package/dist/chunk-A2I76FTA.js +0 -0
- package/dist/chunk-BOPSG2LI.js +0 -0
- package/dist/chunk-CX5SL5HP.js +0 -0
- package/dist/{chunk-UAA6YOOP.js → chunk-IVX62LFO.js} +1 -1
- package/dist/chunk-KGXPLI7W.js +0 -0
- package/dist/chunk-OIGTOLB3.js +0 -0
- package/dist/{chunk-JYQZJMD3.js → chunk-R5S6IXBE.js} +35 -28
- package/dist/chunk-T5EX55GP.js +0 -0
- package/dist/chunk-YTH4L2GN.js +0 -0
- package/dist/chunk-ZFNPM2WG.js +0 -0
- package/dist/chunk-ZONXY3C6.js +0 -0
- package/dist/chunk-ZVOEQCNW.js +0 -0
- package/dist/cli.js +147 -6
- package/dist/context-PSGEX2D7.js +0 -0
- package/dist/errors-OK47MQFD.js +0 -0
- package/dist/firewall-YYDOWDDP.js +0 -0
- package/dist/gcp-BXDTC6EK.js +0 -0
- package/dist/{generate-Z7DBMANX.js → generate-2QGKYR42.js} +1 -1
- package/dist/js-yaml-PTEEG4FO.js +0 -0
- package/dist/local-DXBEVZ5C.js +0 -0
- package/dist/mcp-apps-KY44ED3Y.js +0 -0
- package/dist/mcp-wire-ZWIYMLEW.js +45 -0
- package/dist/outputs-DJHBY7EE.js +0 -0
- package/dist/overlay-store-ADZPD7EU.js +0 -0
- package/dist/{package-WAJRGBBM.js → package-QLDA65A3.js} +1 -1
- package/dist/pool-FBFHATDG.js +0 -0
- package/dist/providers-2OABPW2E.js +0 -0
- package/dist/remote-config-QF5TT7GU.js +0 -0
- package/dist/secrets-SVZWNAPK.js +0 -0
- package/dist/{server-IWLZJ4EU.js → server-LOL2NUJ2.js} +3 -3
- package/dist/ssh-IQXNME3D.js +0 -0
- package/dist/state-PRBIIN7I.js +0 -0
- package/dist/store-SDUR52Z5.js +0 -0
- package/dist/validate-T5M5EHSJ.js +0 -0
- package/package.json +26 -14
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# clawops
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/@clawops/cli)
|
|
4
|
+
[](https://www.npmjs.com/package/@clawops/cli)
|
|
5
|
+
|
|
3
6
|
MCP-native infrastructure ops for OpenClaw — with read-only mode, destructive-action confirmation, and audit logs built in.
|
|
4
7
|
|
|
5
8
|
**clawops** is a CLI and [MCP](https://modelcontextprotocol.io/) server for deploying and operating
|
|
@@ -9,6 +12,27 @@ and Cursor drive them through typed MCP tools with explicit safety controls.
|
|
|
9
12
|
|
|
10
13
|
---
|
|
11
14
|
|
|
15
|
+
## What's new in v1.5.0
|
|
16
|
+
|
|
17
|
+
**`clawops mcp wire`** — wire the OpenClaw gateway's own AI as an MCP client of clawops.
|
|
18
|
+
Once wired, in-conversation commands like "check if my stack is healthy" or "show me the last 20
|
|
19
|
+
log lines" invoke the real `clawops` CLI rather than having the gateway AI guess.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
clawops mcp wire # wire the default stack
|
|
23
|
+
clawops mcp wire --stack prod # target a named stack
|
|
24
|
+
clawops mcp wire --force # bypass gateway version check
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Version-gated: requires OpenClaw ≥ 2026.4 on the gateway side. If the version is older, the
|
|
28
|
+
command surfaces a clear upgrade prompt and exits cleanly.
|
|
29
|
+
|
|
30
|
+
The `clawops setup` wizard now also asks at the end of a successful deploy:
|
|
31
|
+
*"Should the OpenClaw gateway's AI also be able to manage this stack?"* — accepting wires the
|
|
32
|
+
client automatically over the same SSH session.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
12
36
|
## What's new in v1.4.0
|
|
13
37
|
|
|
14
38
|
**`clawops monitor`** — live dashboard for any deployed stack. Shows gateway health, container
|
|
@@ -247,8 +271,11 @@ clawops down --yes # Destroy local-provider stack
|
|
|
247
271
|
| `backup` | Create or restore an OpenClaw state backup |
|
|
248
272
|
| `stacks` | List named stacks and their state |
|
|
249
273
|
| `doctor` | Check Node version, config, SSH key, provider credentials, and Pulumi home |
|
|
274
|
+
| `secret` | Manage secrets: `list`, `set`, `delete`, `rotate`, `audit` |
|
|
275
|
+
| `monitor` | Live dashboard: gateway health, container stats, log tail, stack picker |
|
|
250
276
|
| `mcp serve` | Start the embedded MCP server (stdio or HTTP) |
|
|
251
277
|
| `mcp install` | Interactively wire clawops into AI editors |
|
|
278
|
+
| `mcp wire` | Wire the gateway's AI as an MCP client of clawops |
|
|
252
279
|
| `help` | List all commands and global flags |
|
|
253
280
|
|
|
254
281
|
Full flag reference: `clawops <command> --help`
|
|
@@ -297,6 +324,18 @@ The wizard resolves the absolute binary path automatically so app launchers can
|
|
|
297
324
|
without inheriting your shell's `PATH`. See [Connect an AI editor](#connect-an-ai-editor) above
|
|
298
325
|
for manual config paths.
|
|
299
326
|
|
|
327
|
+
### Wire the gateway AI
|
|
328
|
+
|
|
329
|
+
The OpenClaw gateway runs its own AI agent. Once wired, that agent can call clawops directly
|
|
330
|
+
instead of guessing at infrastructure state:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
clawops mcp wire --stack prod # write MCP client entry into gateway config + restart
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Requires OpenClaw ≥ 2026.4 on the gateway. The `clawops setup` wizard offers this step
|
|
337
|
+
automatically after a successful deploy.
|
|
338
|
+
|
|
300
339
|
### Stdio mode (Claude Code / Cursor / VS Code)
|
|
301
340
|
|
|
302
341
|
Start the server manually or confirm your config is correct:
|
package/dist/apply-5RREMN3L.js
CHANGED
|
File without changes
|
|
File without changes
|
package/dist/aws-FRE2JVAZ.js
CHANGED
|
File without changes
|
package/dist/azure-PVC3AQVC.js
CHANGED
|
File without changes
|
|
File without changes
|
package/dist/chunk-3QJBNAHW.js
CHANGED
|
File without changes
|
package/dist/chunk-4U3LTLWZ.js
CHANGED
|
File without changes
|
package/dist/chunk-6ZFIFDBJ.js
CHANGED
|
File without changes
|
package/dist/chunk-A2I76FTA.js
CHANGED
|
File without changes
|
package/dist/chunk-BOPSG2LI.js
CHANGED
|
File without changes
|
package/dist/chunk-CX5SL5HP.js
CHANGED
|
File without changes
|
|
@@ -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-QLDA65A3.js");
|
|
49
49
|
const config = getConfig();
|
|
50
50
|
const instanceType = intent.instanceType ?? "small";
|
|
51
51
|
const openclawVersion = intent.openclawVersion ?? "latest";
|
package/dist/chunk-KGXPLI7W.js
CHANGED
|
File without changes
|
package/dist/chunk-OIGTOLB3.js
CHANGED
|
File without changes
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
|
|
18
18
|
// src/cli/commands/monitor.ts
|
|
19
19
|
import { defineCommand } from "citty";
|
|
20
|
-
import
|
|
20
|
+
import process2 from "process";
|
|
21
21
|
|
|
22
22
|
// src/output/human.ts
|
|
23
23
|
import chalk from "chalk";
|
|
@@ -37,6 +37,12 @@ function info(msg) {
|
|
|
37
37
|
function spinner(text) {
|
|
38
38
|
return ora(text).start();
|
|
39
39
|
}
|
|
40
|
+
var REPO_URL = "https://github.com/dfridkin/clawops";
|
|
41
|
+
function printCta() {
|
|
42
|
+
process.stdout.write(
|
|
43
|
+
"\n" + chalk.dim(" Thank you for using clawops! If it has been useful, star the project:") + "\n" + chalk.dim(" " + REPO_URL) + "\n\n" + chalk.dim(" Found a bug? Open an issue:") + "\n" + chalk.dim(" " + REPO_URL + "/issues") + "\n\n"
|
|
44
|
+
);
|
|
45
|
+
}
|
|
40
46
|
|
|
41
47
|
// src/cli/commands/monitor.ts
|
|
42
48
|
var GATEWAY_PORT = 18789;
|
|
@@ -228,7 +234,7 @@ async function deleteFromRegistry(name) {
|
|
|
228
234
|
setConfig(updated);
|
|
229
235
|
}
|
|
230
236
|
async function runStackMenu(ac, setKeyHandler, noColor) {
|
|
231
|
-
|
|
237
|
+
process2.stdout.write("\x1B[2J\x1B[H\n Checking stacks...\n");
|
|
232
238
|
const entries = await probeEntries();
|
|
233
239
|
if (ac.signal.aborted) return null;
|
|
234
240
|
let selectedIdx = 0;
|
|
@@ -238,7 +244,7 @@ async function runStackMenu(ac, setKeyHandler, noColor) {
|
|
|
238
244
|
return showAll ? entries : entries.filter((e) => e.deployed);
|
|
239
245
|
}
|
|
240
246
|
function redraw() {
|
|
241
|
-
|
|
247
|
+
process2.stdout.write("\x1B[2J\x1B[H" + renderMenu(entries, selectedIdx, showAll, noColor, confirmDelete));
|
|
242
248
|
}
|
|
243
249
|
redraw();
|
|
244
250
|
return new Promise((resolve) => {
|
|
@@ -317,7 +323,7 @@ async function runDashboard(session, stackName, opts, ac, setKeyHandler, menuMod
|
|
|
317
323
|
noColor: opts.noColor,
|
|
318
324
|
menuMode
|
|
319
325
|
});
|
|
320
|
-
|
|
326
|
+
process2.stdout.write("\x1B[2J\x1B[H" + out + "\n");
|
|
321
327
|
}
|
|
322
328
|
async function doRefresh() {
|
|
323
329
|
cancelRefresh();
|
|
@@ -327,7 +333,7 @@ async function runDashboard(session, stackName, opts, ac, setKeyHandler, menuMod
|
|
|
327
333
|
redraw();
|
|
328
334
|
} catch (err) {
|
|
329
335
|
if (!ac.signal.aborted) {
|
|
330
|
-
|
|
336
|
+
process2.stdout.write("\x1B[2J\x1B[H Error gathering snapshot: " + (err instanceof Error ? err.message : String(err)) + "\n");
|
|
331
337
|
}
|
|
332
338
|
}
|
|
333
339
|
if (!ac.signal.aborted) scheduleRefresh();
|
|
@@ -373,20 +379,20 @@ var monitor_default = defineCommand({
|
|
|
373
379
|
async run({ args }) {
|
|
374
380
|
const intervalSec = Math.max(2, parseInt(String(args.interval ?? "10"), 10) || 10);
|
|
375
381
|
const tailLines = Math.max(1, parseInt(String(args.tail ?? "10"), 10) || 10);
|
|
376
|
-
const isTTY = Boolean(
|
|
382
|
+
const isTTY = Boolean(process2.stdout.isTTY);
|
|
377
383
|
const noColor = Boolean(args["no-color"]) || !isTTY;
|
|
378
384
|
const { buildContext: buildContext2 } = await import("./context-PSGEX2D7.js");
|
|
379
385
|
const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-FBFHATDG.js");
|
|
380
386
|
const ac = new AbortController();
|
|
381
|
-
|
|
382
|
-
|
|
387
|
+
process2.on("SIGINT", () => ac.abort());
|
|
388
|
+
process2.on("SIGTERM", () => ac.abort());
|
|
383
389
|
if (args.stack) {
|
|
384
390
|
const ctx = buildContext2(args);
|
|
385
391
|
let conn;
|
|
386
392
|
if (ctx.adapter.name === "local") {
|
|
387
393
|
if (!ctx.localState) {
|
|
388
394
|
failure("Stack is not bootstrapped. Run `clawops up` first.");
|
|
389
|
-
|
|
395
|
+
process2.exit(4);
|
|
390
396
|
}
|
|
391
397
|
const ls = ctx.localState;
|
|
392
398
|
conn = { host: ls.sshHost, port: ls.sshPort, user: ls.sshUser, privateKeyPath: ls.privateKeyPath, knownHostsPath: ls.knownHostsPath };
|
|
@@ -399,7 +405,7 @@ var monitor_default = defineCommand({
|
|
|
399
405
|
);
|
|
400
406
|
if (!outputs["publicIp"]) {
|
|
401
407
|
failure("Stack has no outputs. Run `clawops up` first.");
|
|
402
|
-
|
|
408
|
+
process2.exit(4);
|
|
403
409
|
}
|
|
404
410
|
const base = extractBaseOutputs(outputs);
|
|
405
411
|
conn = ctx.adapter.getConnectionInfo({
|
|
@@ -412,7 +418,7 @@ var monitor_default = defineCommand({
|
|
|
412
418
|
const { session: session2, release: release2 } = await acquireSession2({ ...conn, signal: ac.signal });
|
|
413
419
|
try {
|
|
414
420
|
const snap = await gatherSnapshot(session2, ac.signal, tailLines);
|
|
415
|
-
|
|
421
|
+
process2.stdout.write(
|
|
416
422
|
renderSnapshot(snap, { stackName: ctx.stackName, intervalSec, showLogs: true, noColor }) + "\n"
|
|
417
423
|
);
|
|
418
424
|
} finally {
|
|
@@ -422,21 +428,21 @@ var monitor_default = defineCommand({
|
|
|
422
428
|
return;
|
|
423
429
|
}
|
|
424
430
|
const { session, release } = await acquireSession2({ ...conn, signal: ac.signal });
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
431
|
+
process2.stdin.setRawMode(true);
|
|
432
|
+
process2.stdin.resume();
|
|
433
|
+
process2.stdin.setEncoding("utf8");
|
|
434
|
+
process2.stdout.write("\x1B[?25l");
|
|
429
435
|
let keyHandler2 = () => {
|
|
430
436
|
};
|
|
431
|
-
|
|
437
|
+
process2.stdin.on("data", (key) => keyHandler2(key));
|
|
432
438
|
try {
|
|
433
439
|
await runDashboard(session, ctx.stackName, { intervalSec, tailLines, noColor }, ac, (fn) => {
|
|
434
440
|
keyHandler2 = fn;
|
|
435
441
|
}, false);
|
|
436
442
|
} finally {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
443
|
+
process2.stdin.setRawMode(false);
|
|
444
|
+
process2.stdin.pause();
|
|
445
|
+
process2.stdout.write("\x1B[?25h\n");
|
|
440
446
|
release();
|
|
441
447
|
drainPool2();
|
|
442
448
|
}
|
|
@@ -444,15 +450,15 @@ var monitor_default = defineCommand({
|
|
|
444
450
|
}
|
|
445
451
|
if (!isTTY) {
|
|
446
452
|
failure("Pass --stack <name> or run in a TTY for interactive stack selection.");
|
|
447
|
-
|
|
453
|
+
process2.exit(2);
|
|
448
454
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
455
|
+
process2.stdin.setRawMode(true);
|
|
456
|
+
process2.stdin.resume();
|
|
457
|
+
process2.stdin.setEncoding("utf8");
|
|
458
|
+
process2.stdout.write("\x1B[?25l");
|
|
453
459
|
let keyHandler = () => {
|
|
454
460
|
};
|
|
455
|
-
|
|
461
|
+
process2.stdin.on("data", (key) => keyHandler(key));
|
|
456
462
|
try {
|
|
457
463
|
while (!ac.signal.aborted) {
|
|
458
464
|
const stackName = await runStackMenu(ac, (fn) => {
|
|
@@ -497,9 +503,9 @@ var monitor_default = defineCommand({
|
|
|
497
503
|
}
|
|
498
504
|
}
|
|
499
505
|
} finally {
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
506
|
+
process2.stdin.setRawMode(false);
|
|
507
|
+
process2.stdin.pause();
|
|
508
|
+
process2.stdout.write("\x1B[?25h\n");
|
|
503
509
|
}
|
|
504
510
|
}
|
|
505
511
|
});
|
|
@@ -750,6 +756,7 @@ export {
|
|
|
750
756
|
warn,
|
|
751
757
|
info,
|
|
752
758
|
spinner,
|
|
759
|
+
printCta,
|
|
753
760
|
resolveConn,
|
|
754
761
|
errText,
|
|
755
762
|
okText,
|
package/dist/chunk-T5EX55GP.js
CHANGED
|
File without changes
|
package/dist/chunk-YTH4L2GN.js
CHANGED
|
File without changes
|
package/dist/chunk-ZFNPM2WG.js
CHANGED
|
File without changes
|
package/dist/chunk-ZONXY3C6.js
CHANGED
|
File without changes
|
package/dist/chunk-ZVOEQCNW.js
CHANGED
|
File without changes
|
package/dist/cli.js
CHANGED
|
@@ -9,11 +9,12 @@ import {
|
|
|
9
9
|
failure,
|
|
10
10
|
info,
|
|
11
11
|
monitor_default,
|
|
12
|
+
printCta,
|
|
12
13
|
spinner,
|
|
13
14
|
success,
|
|
14
15
|
validateOpenclawConfig,
|
|
15
16
|
warn
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-R5S6IXBE.js";
|
|
17
18
|
import "./chunk-T5EX55GP.js";
|
|
18
19
|
import "./chunk-ZVOEQCNW.js";
|
|
19
20
|
import "./chunk-4U3LTLWZ.js";
|
|
@@ -486,7 +487,7 @@ var plan_default = defineCommand5({
|
|
|
486
487
|
},
|
|
487
488
|
async run({ args }) {
|
|
488
489
|
const { buildContext } = await import("./context-PSGEX2D7.js");
|
|
489
|
-
const { generatePlan } = await import("./generate-
|
|
490
|
+
const { generatePlan } = await import("./generate-2QGKYR42.js");
|
|
490
491
|
const ctx = buildContext(args);
|
|
491
492
|
if (ctx.adapter.name === "local") {
|
|
492
493
|
throw new UsageError(
|
|
@@ -1813,7 +1814,7 @@ var serveCmd = defineCommand17({
|
|
|
1813
1814
|
inspector: { type: "boolean", description: "Enable MCP inspector" }
|
|
1814
1815
|
},
|
|
1815
1816
|
async run({ args }) {
|
|
1816
|
-
const { serveMcp } = await import("./server-
|
|
1817
|
+
const { serveMcp } = await import("./server-LOL2NUJ2.js");
|
|
1817
1818
|
await serveMcp({
|
|
1818
1819
|
port: args.http ? Number(args.http) : void 0,
|
|
1819
1820
|
bind: args.bind,
|
|
@@ -1878,6 +1879,85 @@ var installCmd = defineCommand17({
|
|
|
1878
1879
|
}
|
|
1879
1880
|
}
|
|
1880
1881
|
});
|
|
1882
|
+
var wireCmd = defineCommand17({
|
|
1883
|
+
meta: {
|
|
1884
|
+
name: "wire",
|
|
1885
|
+
description: "Wire the gateway AI as an MCP client of clawops (WO-28)"
|
|
1886
|
+
},
|
|
1887
|
+
args: {
|
|
1888
|
+
stack: { type: "string", description: "Target stack name" },
|
|
1889
|
+
force: { type: "boolean", description: "Apply even if gateway version is below minimum" }
|
|
1890
|
+
},
|
|
1891
|
+
async run({ args }) {
|
|
1892
|
+
const { buildContext } = await import("./context-PSGEX2D7.js");
|
|
1893
|
+
const { acquireSession, drainPool } = await import("./pool-FBFHATDG.js");
|
|
1894
|
+
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
1895
|
+
const { wireGatewayMcp } = await import("./mcp-wire-ZWIYMLEW.js");
|
|
1896
|
+
const ac = new AbortController();
|
|
1897
|
+
process19.on("SIGINT", () => {
|
|
1898
|
+
ac.abort();
|
|
1899
|
+
process19.exit(130);
|
|
1900
|
+
});
|
|
1901
|
+
process19.on("SIGTERM", () => {
|
|
1902
|
+
ac.abort();
|
|
1903
|
+
process19.exit(143);
|
|
1904
|
+
});
|
|
1905
|
+
const ctx = buildContext({ stack: args.stack });
|
|
1906
|
+
let conn;
|
|
1907
|
+
if (ctx.adapter.name === "local") {
|
|
1908
|
+
if (!ctx.localState) {
|
|
1909
|
+
failure("Stack is not deployed. Run `clawops up` first.");
|
|
1910
|
+
process19.exit(1);
|
|
1911
|
+
}
|
|
1912
|
+
const ls = ctx.localState;
|
|
1913
|
+
conn = { host: ls.sshHost, port: ls.sshPort, user: ls.sshUser, privateKeyPath: ls.privateKeyPath, knownHostsPath: ls.knownHostsPath };
|
|
1914
|
+
} else {
|
|
1915
|
+
const stack = await ctx.getStack();
|
|
1916
|
+
const outputMap = await stack.outputs();
|
|
1917
|
+
const outputs = Object.fromEntries(
|
|
1918
|
+
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
1919
|
+
);
|
|
1920
|
+
if (!outputs["publicIp"]) {
|
|
1921
|
+
failure("Stack has no outputs. Run `clawops up` first.");
|
|
1922
|
+
process19.exit(1);
|
|
1923
|
+
}
|
|
1924
|
+
const base = extractBaseOutputs(outputs);
|
|
1925
|
+
conn = {
|
|
1926
|
+
host: base.publicIp,
|
|
1927
|
+
port: base.sshPort ?? 22,
|
|
1928
|
+
user: base.sshUser ?? "ubuntu",
|
|
1929
|
+
privateKeyPath: ctx.config.ssh.keyPath,
|
|
1930
|
+
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
const spin = spinner(`Connecting to ${conn.host}...`);
|
|
1934
|
+
const { session, release } = await acquireSession({ ...conn, signal: ac.signal });
|
|
1935
|
+
try {
|
|
1936
|
+
spin.text = "Reading gateway config...";
|
|
1937
|
+
const result = await wireGatewayMcp(session, ac.signal, { force: Boolean(args.force) });
|
|
1938
|
+
if (result.status === "version-blocked") {
|
|
1939
|
+
spin.fail("Gateway version is too old for MCP client support.");
|
|
1940
|
+
warn(`Gateway version ${result.version} requires at least 2026.4 for MCP client support.`);
|
|
1941
|
+
info("Upgrade OpenClaw and re-run, or bypass with: clawops mcp wire --force");
|
|
1942
|
+
process19.exit(1);
|
|
1943
|
+
}
|
|
1944
|
+
if (result.rewired) {
|
|
1945
|
+
spin.succeed("Re-wiring clawops MCP client \u2014 previous entry replaced.");
|
|
1946
|
+
} else {
|
|
1947
|
+
spin.succeed("Gateway MCP client wired.");
|
|
1948
|
+
}
|
|
1949
|
+
success("The gateway's AI can now run clawops commands.");
|
|
1950
|
+
info('Try asking it: "check if my stack is healthy"');
|
|
1951
|
+
} catch (err) {
|
|
1952
|
+
spin.fail("Failed to wire gateway MCP client.");
|
|
1953
|
+
failure(err instanceof Error ? err.message : String(err));
|
|
1954
|
+
process19.exit(1);
|
|
1955
|
+
} finally {
|
|
1956
|
+
release();
|
|
1957
|
+
drainPool();
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
});
|
|
1881
1961
|
var mcp_default = defineCommand17({
|
|
1882
1962
|
meta: {
|
|
1883
1963
|
name: "mcp",
|
|
@@ -1886,7 +1966,8 @@ var mcp_default = defineCommand17({
|
|
|
1886
1966
|
args: {},
|
|
1887
1967
|
subCommands: {
|
|
1888
1968
|
serve: serveCmd,
|
|
1889
|
-
install: installCmd
|
|
1969
|
+
install: installCmd,
|
|
1970
|
+
wire: wireCmd
|
|
1890
1971
|
}
|
|
1891
1972
|
});
|
|
1892
1973
|
|
|
@@ -2296,6 +2377,15 @@ var setup_default = defineCommand18({
|
|
|
2296
2377
|
signal: ac.signal,
|
|
2297
2378
|
inquirer
|
|
2298
2379
|
});
|
|
2380
|
+
await maybeWireGatewayMcp({
|
|
2381
|
+
stackName: stackAnswers.stackName,
|
|
2382
|
+
host: localHost,
|
|
2383
|
+
port: localPort,
|
|
2384
|
+
user: localUser,
|
|
2385
|
+
keyPath: localKeyPath,
|
|
2386
|
+
signal: ac.signal,
|
|
2387
|
+
inquirer
|
|
2388
|
+
});
|
|
2299
2389
|
} else {
|
|
2300
2390
|
const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
|
|
2301
2391
|
const knownHostsPath = path3.join(getConfigDir2(), "known_hosts");
|
|
@@ -2327,6 +2417,7 @@ var setup_default = defineCommand18({
|
|
|
2327
2417
|
failure(err instanceof Error ? err.message : String(err));
|
|
2328
2418
|
process20.exit(1);
|
|
2329
2419
|
}
|
|
2420
|
+
await maybeWireGatewayMcpCloud({ stackName: stackAnswers.stackName, signal: ac.signal, inquirer });
|
|
2330
2421
|
} else {
|
|
2331
2422
|
info(`
|
|
2332
2423
|
To deploy later: clawops apply ${outputPath}`);
|
|
@@ -2431,6 +2522,7 @@ async function runLocalDeploy(opts) {
|
|
|
2431
2522
|
info(`Token saved to ${tokenPath}`);
|
|
2432
2523
|
info(`
|
|
2433
2524
|
Run clawops doctor --stack ${opts.stackName} to check everything is healthy`);
|
|
2525
|
+
printCta();
|
|
2434
2526
|
}
|
|
2435
2527
|
var CLOUD_AUTH = {
|
|
2436
2528
|
aws: {
|
|
@@ -2803,6 +2895,55 @@ function printMcpSnippet() {
|
|
|
2803
2895
|
}
|
|
2804
2896
|
}, null, 2) + "\n\n");
|
|
2805
2897
|
}
|
|
2898
|
+
async function maybeWireGatewayMcp(opts) {
|
|
2899
|
+
process20.stdout.write("\n");
|
|
2900
|
+
const { wireGateway } = await inquirerPromptWire(opts.inquirer);
|
|
2901
|
+
if (!wireGateway) return;
|
|
2902
|
+
const { connect } = await import("./ssh-IQXNME3D.js");
|
|
2903
|
+
const { getConfigDir: getConfigDir2 } = await import("./store-SDUR52Z5.js");
|
|
2904
|
+
const { wireGatewayMcp } = await import("./mcp-wire-ZWIYMLEW.js");
|
|
2905
|
+
const knownHostsPath = path3.join(getConfigDir2(), "known_hosts");
|
|
2906
|
+
const spin = spinner("Wiring gateway MCP client...");
|
|
2907
|
+
const session = await connect({
|
|
2908
|
+
host: opts.host,
|
|
2909
|
+
port: opts.port,
|
|
2910
|
+
user: opts.user,
|
|
2911
|
+
privateKeyPath: opts.keyPath,
|
|
2912
|
+
knownHostsPath,
|
|
2913
|
+
signal: opts.signal
|
|
2914
|
+
});
|
|
2915
|
+
try {
|
|
2916
|
+
const result = await wireGatewayMcp(session, opts.signal ?? AbortSignal.timeout(3e4));
|
|
2917
|
+
if (result.status === "version-blocked") {
|
|
2918
|
+
spin.warn(`Gateway version ${result.version} is too old for MCP client support (requires \u2265 2026.4).`);
|
|
2919
|
+
info("Upgrade OpenClaw and run: clawops mcp wire --stack " + opts.stackName);
|
|
2920
|
+
} else {
|
|
2921
|
+
spin.succeed(result.rewired ? "Gateway MCP client re-wired." : "Gateway MCP client wired.");
|
|
2922
|
+
success("The gateway's AI can now run clawops commands.");
|
|
2923
|
+
info('Try asking it: "check if my stack is healthy"');
|
|
2924
|
+
}
|
|
2925
|
+
} catch (err) {
|
|
2926
|
+
spin.fail("Failed to wire gateway MCP client.");
|
|
2927
|
+
failure(err instanceof Error ? err.message : String(err));
|
|
2928
|
+
info("You can retry later: clawops mcp wire --stack " + opts.stackName);
|
|
2929
|
+
} finally {
|
|
2930
|
+
session.close();
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
async function maybeWireGatewayMcpCloud(opts) {
|
|
2934
|
+
process20.stdout.write("\n");
|
|
2935
|
+
const { wireGateway } = await inquirerPromptWire(opts.inquirer);
|
|
2936
|
+
if (!wireGateway) return;
|
|
2937
|
+
info(`Run this after the server is ready: clawops mcp wire --stack ${opts.stackName}`);
|
|
2938
|
+
}
|
|
2939
|
+
async function inquirerPromptWire(inquirer) {
|
|
2940
|
+
return inquirer.prompt([{
|
|
2941
|
+
type: "confirm",
|
|
2942
|
+
name: "wireGateway",
|
|
2943
|
+
message: "Should the OpenClaw gateway's AI also be able to manage this stack? (adds clawops MCP client to gateway)",
|
|
2944
|
+
default: false
|
|
2945
|
+
}]);
|
|
2946
|
+
}
|
|
2806
2947
|
function loadCatalogs(yaml) {
|
|
2807
2948
|
const specDir = path3.join(path3.dirname(fileURLToPath(import.meta.url)), "../../../spec");
|
|
2808
2949
|
try {
|
|
@@ -3151,7 +3292,7 @@ var COMMANDS = [
|
|
|
3151
3292
|
["backup", "Create or restore an OpenClaw state backup"],
|
|
3152
3293
|
["stacks", "List named stacks and their state"],
|
|
3153
3294
|
["doctor", "Check Node version, config, SSH key, provider credentials, and Pulumi home"],
|
|
3154
|
-
["mcp", "MCP server operations (mcp serve | mcp install)"],
|
|
3295
|
+
["mcp", "MCP server operations (mcp serve | mcp install | mcp wire)"],
|
|
3155
3296
|
["secret", "Manage secrets (secret list | set | delete | rotate | audit)"],
|
|
3156
3297
|
["monitor", "Live dashboard: gateway health, container status, resource usage, log tail"],
|
|
3157
3298
|
["help", "Show this help message"]
|
|
@@ -3198,8 +3339,8 @@ ${bold("Global flags:")}
|
|
|
3198
3339
|
}
|
|
3199
3340
|
process.stdout.write(`
|
|
3200
3341
|
${dim("Run `clawops <command> --help` for command-specific flags.")}
|
|
3201
|
-
|
|
3202
3342
|
`);
|
|
3343
|
+
printCta();
|
|
3203
3344
|
}
|
|
3204
3345
|
});
|
|
3205
3346
|
|
package/dist/context-PSGEX2D7.js
CHANGED
|
File without changes
|
package/dist/errors-OK47MQFD.js
CHANGED
|
File without changes
|
|
File without changes
|
package/dist/gcp-BXDTC6EK.js
CHANGED
|
File without changes
|
package/dist/js-yaml-PTEEG4FO.js
CHANGED
|
File without changes
|
package/dist/local-DXBEVZ5C.js
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
atomicWriteConfig,
|
|
4
|
+
deepMerge,
|
|
5
|
+
readRemoteConfig,
|
|
6
|
+
restartGateway
|
|
7
|
+
} from "./chunk-ZFNPM2WG.js";
|
|
8
|
+
|
|
9
|
+
// src/cli/mcp-wire.ts
|
|
10
|
+
var GATEWAY_MCP_MIN_VERSION = "2026.4";
|
|
11
|
+
var GATEWAY_MCP_ENTRY = {
|
|
12
|
+
command: "clawops",
|
|
13
|
+
args: ["mcp", "serve"],
|
|
14
|
+
transport: "stdio"
|
|
15
|
+
};
|
|
16
|
+
function versionAtLeast(version, min) {
|
|
17
|
+
const [vy = 0, vm = 0] = version.split(".").map(Number);
|
|
18
|
+
const [my = 0, mm = 0] = min.split(".").map(Number);
|
|
19
|
+
if (vy !== my) return vy > my;
|
|
20
|
+
return vm >= mm;
|
|
21
|
+
}
|
|
22
|
+
async function wireGatewayMcp(session, signal, opts = {}) {
|
|
23
|
+
const cfg = await readRemoteConfig(session, signal);
|
|
24
|
+
const version = cfg["meta"]?.["lastTouchedVersion"];
|
|
25
|
+
if (version && !versionAtLeast(version, GATEWAY_MCP_MIN_VERSION) && !opts.force) {
|
|
26
|
+
return { status: "version-blocked", version };
|
|
27
|
+
}
|
|
28
|
+
const mcpClients = cfg["gateway"]?.["mcpClients"];
|
|
29
|
+
const rewired = !!mcpClients?.["clawops"];
|
|
30
|
+
const updated = deepMerge(cfg, {
|
|
31
|
+
gateway: {
|
|
32
|
+
mcpClients: {
|
|
33
|
+
clawops: GATEWAY_MCP_ENTRY
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
await atomicWriteConfig(session, updated, signal);
|
|
38
|
+
await restartGateway(session, signal);
|
|
39
|
+
return { status: "wired", rewired };
|
|
40
|
+
}
|
|
41
|
+
export {
|
|
42
|
+
GATEWAY_MCP_ENTRY,
|
|
43
|
+
GATEWAY_MCP_MIN_VERSION,
|
|
44
|
+
wireGatewayMcp
|
|
45
|
+
};
|
package/dist/outputs-DJHBY7EE.js
CHANGED
|
File without changes
|
|
File without changes
|
package/dist/pool-FBFHATDG.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dist/secrets-SVZWNAPK.js
CHANGED
|
File without changes
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-3QJBNAHW.js";
|
|
5
5
|
import {
|
|
6
6
|
generatePlan
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IVX62LFO.js";
|
|
8
8
|
import {
|
|
9
9
|
applyPlan
|
|
10
10
|
} from "./chunk-ZONXY3C6.js";
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
handleConfigValidate,
|
|
24
24
|
okText,
|
|
25
25
|
resolveConn
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-R5S6IXBE.js";
|
|
27
27
|
import {
|
|
28
28
|
buildContext
|
|
29
29
|
} from "./chunk-T5EX55GP.js";
|
|
@@ -1305,7 +1305,7 @@ If none of the above resolves the issue:
|
|
|
1305
1305
|
|
|
1306
1306
|
// src/mcp/server.ts
|
|
1307
1307
|
async function serveMcp(opts) {
|
|
1308
|
-
const { version } = await import("./package-
|
|
1308
|
+
const { version } = await import("./package-QLDA65A3.js");
|
|
1309
1309
|
const server = new McpServer({ name: "clawops", version });
|
|
1310
1310
|
registerTools(server, opts);
|
|
1311
1311
|
registerResources(server);
|
package/dist/ssh-IQXNME3D.js
CHANGED
|
File without changes
|
package/dist/state-PRBIIN7I.js
CHANGED
|
File without changes
|
package/dist/store-SDUR52Z5.js
CHANGED
|
File without changes
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawops/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Deploy and manage self-hosted OpenClaw instances across clouds",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,6 +24,19 @@
|
|
|
24
24
|
"engines": {
|
|
25
25
|
"node": ">=22"
|
|
26
26
|
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"dev": "tsx src/cli/index.ts",
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"test": "vitest run",
|
|
31
|
+
"test:pulumi": "vitest run tests/providers/aws/program.test.ts tests/providers/azure/program.test.ts",
|
|
32
|
+
"test:changed": "vitest --changed",
|
|
33
|
+
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
34
|
+
"typecheck": "NODE_OPTIONS=--max-old-space-size=4096 tsc --noEmit",
|
|
35
|
+
"lint": "eslint --max-warnings=0 src tests scripts",
|
|
36
|
+
"gen:schemas": "tsx scripts/gen-schemas.ts",
|
|
37
|
+
"changeset": "changeset",
|
|
38
|
+
"release": "pnpm build && changeset publish"
|
|
39
|
+
},
|
|
27
40
|
"dependencies": {
|
|
28
41
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
29
42
|
"@pulumi/aws": "^6.0.0",
|
|
@@ -60,17 +73,16 @@
|
|
|
60
73
|
"typescript-eslint": "^8.0.0",
|
|
61
74
|
"vitest": "^2.0.0"
|
|
62
75
|
},
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
"release": "pnpm build && changeset publish"
|
|
76
|
+
"pnpm": {
|
|
77
|
+
"overrides": {
|
|
78
|
+
"@pulumi/pulumi": "^3.0.0"
|
|
79
|
+
},
|
|
80
|
+
"onlyBuiltDependencies": [
|
|
81
|
+
"@pulumi/command",
|
|
82
|
+
"cpu-features",
|
|
83
|
+
"esbuild",
|
|
84
|
+
"protobufjs",
|
|
85
|
+
"ssh2"
|
|
86
|
+
]
|
|
75
87
|
}
|
|
76
|
-
}
|
|
88
|
+
}
|