@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.
Files changed (44) hide show
  1. package/README.md +39 -0
  2. package/dist/apply-5RREMN3L.js +0 -0
  3. package/dist/automation-RULUSJBW.js +0 -0
  4. package/dist/aws-FRE2JVAZ.js +0 -0
  5. package/dist/azure-PVC3AQVC.js +0 -0
  6. package/dist/bootstrap-G4UZ2FKH.js +0 -0
  7. package/dist/chunk-3QJBNAHW.js +0 -0
  8. package/dist/chunk-4U3LTLWZ.js +0 -0
  9. package/dist/chunk-6ZFIFDBJ.js +0 -0
  10. package/dist/chunk-A2I76FTA.js +0 -0
  11. package/dist/chunk-BOPSG2LI.js +0 -0
  12. package/dist/chunk-CX5SL5HP.js +0 -0
  13. package/dist/{chunk-UAA6YOOP.js → chunk-IVX62LFO.js} +1 -1
  14. package/dist/chunk-KGXPLI7W.js +0 -0
  15. package/dist/chunk-OIGTOLB3.js +0 -0
  16. package/dist/{chunk-JYQZJMD3.js → chunk-R5S6IXBE.js} +35 -28
  17. package/dist/chunk-T5EX55GP.js +0 -0
  18. package/dist/chunk-YTH4L2GN.js +0 -0
  19. package/dist/chunk-ZFNPM2WG.js +0 -0
  20. package/dist/chunk-ZONXY3C6.js +0 -0
  21. package/dist/chunk-ZVOEQCNW.js +0 -0
  22. package/dist/cli.js +147 -6
  23. package/dist/context-PSGEX2D7.js +0 -0
  24. package/dist/errors-OK47MQFD.js +0 -0
  25. package/dist/firewall-YYDOWDDP.js +0 -0
  26. package/dist/gcp-BXDTC6EK.js +0 -0
  27. package/dist/{generate-Z7DBMANX.js → generate-2QGKYR42.js} +1 -1
  28. package/dist/js-yaml-PTEEG4FO.js +0 -0
  29. package/dist/local-DXBEVZ5C.js +0 -0
  30. package/dist/mcp-apps-KY44ED3Y.js +0 -0
  31. package/dist/mcp-wire-ZWIYMLEW.js +45 -0
  32. package/dist/outputs-DJHBY7EE.js +0 -0
  33. package/dist/overlay-store-ADZPD7EU.js +0 -0
  34. package/dist/{package-WAJRGBBM.js → package-QLDA65A3.js} +1 -1
  35. package/dist/pool-FBFHATDG.js +0 -0
  36. package/dist/providers-2OABPW2E.js +0 -0
  37. package/dist/remote-config-QF5TT7GU.js +0 -0
  38. package/dist/secrets-SVZWNAPK.js +0 -0
  39. package/dist/{server-IWLZJ4EU.js → server-LOL2NUJ2.js} +3 -3
  40. package/dist/ssh-IQXNME3D.js +0 -0
  41. package/dist/state-PRBIIN7I.js +0 -0
  42. package/dist/store-SDUR52Z5.js +0 -0
  43. package/dist/validate-T5M5EHSJ.js +0 -0
  44. package/package.json +26 -14
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # clawops
2
2
 
3
+ [![npm version](https://img.shields.io/npm/v/@clawops/cli)](https://www.npmjs.com/package/@clawops/cli)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@clawops/cli)](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:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
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-WAJRGBBM.js");
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";
File without changes
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 process from "process";
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
- process.stdout.write("\x1B[2J\x1B[H\n Checking stacks...\n");
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
- process.stdout.write("\x1B[2J\x1B[H" + renderMenu(entries, selectedIdx, showAll, noColor, confirmDelete));
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
- process.stdout.write("\x1B[2J\x1B[H" + out + "\n");
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
- process.stdout.write("\x1B[2J\x1B[H Error gathering snapshot: " + (err instanceof Error ? err.message : String(err)) + "\n");
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(process.stdout.isTTY);
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
- process.on("SIGINT", () => ac.abort());
382
- process.on("SIGTERM", () => ac.abort());
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
- process.exit(4);
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
- process.exit(4);
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
- process.stdout.write(
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
- process.stdin.setRawMode(true);
426
- process.stdin.resume();
427
- process.stdin.setEncoding("utf8");
428
- process.stdout.write("\x1B[?25l");
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
- process.stdin.on("data", (key) => keyHandler2(key));
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
- process.stdin.setRawMode(false);
438
- process.stdin.pause();
439
- process.stdout.write("\x1B[?25h\n");
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
- process.exit(2);
453
+ process2.exit(2);
448
454
  }
449
- process.stdin.setRawMode(true);
450
- process.stdin.resume();
451
- process.stdin.setEncoding("utf8");
452
- process.stdout.write("\x1B[?25l");
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
- process.stdin.on("data", (key) => keyHandler(key));
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
- process.stdin.setRawMode(false);
501
- process.stdin.pause();
502
- process.stdout.write("\x1B[?25h\n");
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,
File without changes
File without changes
File without changes
File without changes
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-JYQZJMD3.js";
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-Z7DBMANX.js");
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-IWLZJ4EU.js");
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
 
File without changes
File without changes
File without changes
File without changes
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  generatePlan,
4
4
  planId
5
- } from "./chunk-UAA6YOOP.js";
5
+ } from "./chunk-IVX62LFO.js";
6
6
  import "./chunk-YTH4L2GN.js";
7
7
  import "./chunk-T5EX55GP.js";
8
8
  import "./chunk-A2I76FTA.js";
File without changes
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
+ };
File without changes
File without changes
@@ -2,7 +2,7 @@
2
2
 
3
3
  // package.json
4
4
  var name = "@clawops/cli";
5
- var version = "1.4.0";
5
+ var version = "1.5.0";
6
6
  var description = "Deploy and manage self-hosted OpenClaw instances across clouds";
7
7
  var type = "module";
8
8
  var bin = {
File without changes
File without changes
File without changes
File without changes
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-3QJBNAHW.js";
5
5
  import {
6
6
  generatePlan
7
- } from "./chunk-UAA6YOOP.js";
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-JYQZJMD3.js";
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-WAJRGBBM.js");
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);
File without changes
File without changes
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawops/cli",
3
- "version": "1.4.0",
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
- "scripts": {
64
- "dev": "tsx src/cli/index.ts",
65
- "build": "tsup",
66
- "test": "vitest run",
67
- "test:pulumi": "vitest run tests/providers/aws/program.test.ts tests/providers/azure/program.test.ts",
68
- "test:changed": "vitest --changed",
69
- "test:integration": "vitest run --config vitest.integration.config.ts",
70
- "typecheck": "NODE_OPTIONS=--max-old-space-size=4096 tsc --noEmit",
71
- "lint": "eslint --max-warnings=0 src tests scripts",
72
- "gen:schemas": "tsx scripts/gen-schemas.ts",
73
- "changeset": "changeset",
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
+ }