@clawops/cli 2.1.2 → 2.2.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 (63) hide show
  1. package/README.md +122 -2
  2. package/dist/agent-activity-25FAWLAI.js +16 -0
  3. package/dist/{apply-HJ5GAKHW.js → apply-F5SQ2ZOS.js} +12 -12
  4. package/dist/{automation-MKCJ46I6.js → automation-NBYQ6MFZ.js} +1 -1
  5. package/dist/backup-flows-NDP5JTR3.js +29 -0
  6. package/dist/{bootstrap-NN647RCY.js → bootstrap-32TZXVDF.js} +5 -5
  7. package/dist/bootstrap.sh.tmpl +4 -0
  8. package/dist/chunk-27335SIH.js +51 -0
  9. package/dist/{chunk-X56CQ6GB.js → chunk-4MHDW2NB.js} +1 -1
  10. package/dist/{chunk-E4NJSJ54.js → chunk-4PY6PN4G.js} +51 -7
  11. package/dist/{chunk-3Z5TDXCJ.js → chunk-55UMO37O.js} +1 -1
  12. package/dist/{chunk-IQQTB7ER.js → chunk-66R52R7K.js} +10 -14
  13. package/dist/chunk-755X5OGS.js +213 -0
  14. package/dist/{chunk-QDNFADJI.js → chunk-AVERNLUY.js} +6 -6
  15. package/dist/{chunk-R3DTA35B.js → chunk-AX65JMDZ.js} +20 -20
  16. package/dist/{chunk-T5XCMYAV.js → chunk-CQOYUWR7.js} +766 -210
  17. package/dist/chunk-E6BRQK67.js +289 -0
  18. package/dist/{chunk-64PONSA5.js → chunk-GFIR2IIJ.js} +3 -1
  19. package/dist/chunk-LV7UO5CG.js +82 -0
  20. package/dist/{chunk-GIEVAGEK.js → chunk-N5ZYUTZS.js} +16 -1
  21. package/dist/{chunk-GFUK6KDR.js → chunk-OUQVKIIA.js} +372 -34
  22. package/dist/{chunk-XFN3YDZE.js → chunk-PASAVVNS.js} +1 -1
  23. package/dist/chunk-PCSX6Y3U.js +297 -0
  24. package/dist/chunk-POYS3EVQ.js +14 -0
  25. package/dist/{chunk-ELU3XQEA.js → chunk-RDUKRIRC.js} +1 -1
  26. package/dist/chunk-SX75L2T3.js +46 -0
  27. package/dist/{chunk-2Z7J3ERQ.js → chunk-TYNMOYVM.js} +1 -1
  28. package/dist/{chunk-2I7KCMXZ.js → chunk-UAJJEDCJ.js} +2 -2
  29. package/dist/{chunk-NUDIHZEH.js → chunk-WPZIJONN.js} +14 -12
  30. package/dist/{chunk-BELTQGHT.js → chunk-YPVWF7IA.js} +4 -4
  31. package/dist/{chunk-3OLUJPCB.js → chunk-Z3XLWZWF.js} +1 -1
  32. package/dist/cli.js +383 -925
  33. package/dist/conn-M2V5HBPJ.js +9 -0
  34. package/dist/{context-7VI3R6EK.js → context-EHSLWKSH.js} +6 -6
  35. package/dist/{docker-3PDO4TS7.js → docker-IKM5GRMC.js} +2 -2
  36. package/dist/{flows-RI7FGHAM.js → flows-6B2I6TRW.js} +7 -7
  37. package/dist/{generate-Q2BGVMH7.js → generate-4YYNW3IU.js} +9 -8
  38. package/dist/{harden-3KKCR7BL.js → harden-UHAOVQMZ.js} +5 -5
  39. package/dist/init-HOPXDNWY.js +13 -0
  40. package/dist/{mcp-wire-FQOMM7MK.js → mcp-wire-BXF4DJFT.js} +26 -19
  41. package/dist/migrate-flow-24XA4GZL.js +25 -0
  42. package/dist/{package-JFVGCNKU.js → package-HYGHRX35.js} +3 -1
  43. package/dist/{pool-PIIKJ4L2.js → pool-NT4XBSUT.js} +2 -2
  44. package/dist/{privileged-JFA6LLTV.js → privileged-5Y6O4H7Z.js} +1 -1
  45. package/dist/{ready-KDNRBNSU.js → ready-IJEUBFVX.js} +2 -2
  46. package/dist/{remote-config-3HZLUWEA.js → remote-config-OZYBMBSK.js} +3 -3
  47. package/dist/{runtime-X3KW5TCP.js → runtime-VW53TU2W.js} +5 -1
  48. package/dist/server-E3YA32ND.js +53 -0
  49. package/dist/ssh-HOM7GF3M.js +13 -0
  50. package/dist/{stack-config-LUTS2XAD.js → stack-config-ICQ5YHUL.js} +2 -2
  51. package/dist/stack-delete-NTIGKWUF.js +13 -0
  52. package/dist/{state-AOAYLYY5.js → state-AKZP2S4T.js} +2 -2
  53. package/dist/{store-OVB3DAJR.js → store-YOLQYXMB.js} +1 -1
  54. package/dist/{tailscale-cutover-6YP2QKFM.js → tailscale-cutover-V42HO5UM.js} +3 -3
  55. package/dist/{validate-JXKN5WD6.js → validate-DZE6LFEB.js} +1 -1
  56. package/dist/{wait-WO72TRRQ.js → wait-DOUNNJNN.js} +2 -2
  57. package/package.json +3 -1
  58. package/spec/mcp-tools.yaml +282 -35
  59. package/dist/logs-5A7YYA53.js +0 -15
  60. package/dist/server-B7I6HBQH.js +0 -46
  61. package/dist/ssh-VAI5DU5M.js +0 -11
  62. package/dist/upgrade-ULD4KFYC.js +0 -88
  63. /package/dist/{chunk-NUL5F6YV.js → chunk-SOZ62ZXW.js} +0 -0
@@ -1,7 +1,39 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ migrationQuestion,
4
+ prepareMigration,
5
+ runMigration
6
+ } from "./chunk-E6BRQK67.js";
7
+ import {
8
+ ACTIVATED,
9
+ ARCHIVE_IS_A_CREDENTIAL,
10
+ PLUGINS_NOT_CARRIED,
11
+ activateStaged,
12
+ activatedLines,
13
+ activationFailureDetail,
14
+ createBackup,
15
+ defaultBackupFilename,
16
+ nothingChanged,
17
+ stageRestore
18
+ } from "./chunk-PCSX6Y3U.js";
19
+ import {
20
+ checkStackDelete,
21
+ deleteStackFromConfig,
22
+ forgetNotice
23
+ } from "./chunk-LV7UO5CG.js";
2
24
  import {
3
25
  applyPlan
4
- } from "./chunk-R3DTA35B.js";
26
+ } from "./chunk-AX65JMDZ.js";
27
+ import {
28
+ resolveConn
29
+ } from "./chunk-SX75L2T3.js";
30
+ import {
31
+ DEFAULT_AGENT_LOG_LIMIT,
32
+ readAgentActivity
33
+ } from "./chunk-27335SIH.js";
34
+ import {
35
+ generatePlan
36
+ } from "./chunk-66R52R7K.js";
5
37
  import {
6
38
  extractBaseOutputs
7
39
  } from "./chunk-3QJBNAHW.js";
@@ -9,46 +41,48 @@ import {
9
41
  atomicWriteConfig,
10
42
  readRemoteConfig,
11
43
  restartGateway
12
- } from "./chunk-2I7KCMXZ.js";
13
- import {
14
- generatePlan
15
- } from "./chunk-IQQTB7ER.js";
44
+ } from "./chunk-UAJJEDCJ.js";
16
45
  import {
17
46
  validatePlan
18
- } from "./chunk-XFN3YDZE.js";
47
+ } from "./chunk-PASAVVNS.js";
48
+ import {
49
+ buildContext
50
+ } from "./chunk-AVERNLUY.js";
19
51
  import {
20
52
  acquireSession,
21
53
  drainPool
22
- } from "./chunk-2Z7J3ERQ.js";
23
- import {
24
- buildContext
25
- } from "./chunk-QDNFADJI.js";
54
+ } from "./chunk-TYNMOYVM.js";
26
55
  import {
56
+ AUDIT_CLEAN_MESSAGE,
57
+ AUDIT_FIX_HINT,
58
+ NO_SECRETS_MESSAGE,
59
+ SET_SECRET_HINT,
60
+ auditSecrets,
61
+ currentGatewayVersion,
62
+ deleteConfirmQuestion,
63
+ deletePreparedSecret,
27
64
  formatUptime,
65
+ gatewayStatus,
28
66
  gatherSnapshot,
29
- runDiagnostics
30
- } from "./chunk-GFUK6KDR.js";
67
+ listSecrets,
68
+ prepareSecretDelete,
69
+ resolveUpdateVersion,
70
+ runDiagnostics,
71
+ updateGateway
72
+ } from "./chunk-OUQVKIIA.js";
31
73
  import {
32
74
  GATEWAY_LOGS_PROBE,
33
75
  chooseLogSource,
34
76
  containerLogsCommand,
35
77
  gatewayLogsCommand
36
- } from "./chunk-NUL5F6YV.js";
78
+ } from "./chunk-SOZ62ZXW.js";
37
79
  import {
38
80
  execPrivileged
39
- } from "./chunk-NUDIHZEH.js";
40
- import {
41
- STATE_DIR_HOST_LINUX,
42
- gatewayRunCommand
43
- } from "./chunk-GIEVAGEK.js";
44
- import {
45
- IMAGE_INSPECT_CMD,
46
- imageForRestart
47
- } from "./chunk-VOEJYRIK.js";
81
+ } from "./chunk-WPZIJONN.js";
48
82
  import {
49
83
  getConfig,
50
84
  getConfigDir
51
- } from "./chunk-64PONSA5.js";
85
+ } from "./chunk-GFIR2IIJ.js";
52
86
  import {
53
87
  StateError,
54
88
  UsageError
@@ -56,7 +90,7 @@ import {
56
90
 
57
91
  // src/mcp/server.ts
58
92
  import { createServer } from "http";
59
- import { randomUUID as randomUUID5, timingSafeEqual } from "crypto";
93
+ import { randomUUID as randomUUID8, timingSafeEqual } from "crypto";
60
94
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
61
95
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
62
96
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
@@ -141,7 +175,7 @@ function withAudit(toolName, handler) {
141
175
  // src/mcp/tools/_generated.ts
142
176
  import { z } from "zod";
143
177
  var clawops_statusSchema = z.object({
144
- stackName: z.string().optional()
178
+ stackName: z.string().optional().describe("Stack name. Defaults to active stack from config.")
145
179
  });
146
180
  var clawops_statusDescription = "Get what is deployed for a clawops-managed stack: public IP, gateway URL,\nSSH user, or that nothing is deployed yet. Reads stack outputs; does not\ncontact the host.\n\nUse when: the user asks what exists for a stack, or where to reach it.\n\nDo NOT use when: the user asks whether the gateway is actually WORKING \u2014\nthat needs the host, so use clawops_doctor. Also not for live logs (use\nclawops_logs_tail) or config values (use clawops_config_get).";
147
181
  var clawops_statusAnnotations = {
@@ -153,10 +187,10 @@ var clawops_statusAnnotations = {
153
187
  toolsets: ["cli", "read"]
154
188
  };
155
189
  var clawops_doctorSchema = z.object({
156
- stackName: z.string().optional(),
157
- failuresOnly: z.boolean().optional().default(false)
190
+ stackName: z.string().optional().describe("Stack to include remote checks for. Without it, only the local machine is\nchecked \u2014 no SSH connection is made.\n"),
191
+ failuresOnly: z.boolean().optional().default(false).describe("Return only failing and warning checks. Passing checks are counted, not listed.")
158
192
  });
159
- var clawops_doctorDescription = "Run clawops's diagnostics and return the report: Node and Pulumi runtime, config,\nSSH key and known_hosts, cloud credentials per configured provider, and the\nsupported OpenClaw range. With stackName, also contacts the host for container\nstate, the deployed OpenClaw version, a real gateway health probe, whether the\nport is published to the internet, disk usage on the state directory, log\nrotation, and hardening drift.\n\nUse when: something is not working and you do not yet know what; before any\ndeploy, upgrade or migration; or to find out which OpenClaw version a gateway is\nactually running.\n\nDo NOT use when: you already know the problem and want to fix it. This tool only\nreports \u2014 it changes nothing, and never runs `openclaw doctor --fix`. No tool\nrepairs a gateway: `clawops gateway update` is CLI-only, so tell the user to run\nit themselves, after `clawops backup create`.\n\nEvery check carries a status: fail (something is wrong that clawops can name),\nwarn (worth knowing, not broken), info (did not apply). `ok` is false only when\nsomething failed \u2014 a fresh machine with no stacks is full of warnings and healthy.";
193
+ var clawops_doctorDescription = "Run clawops's diagnostics and return the report: Node and Pulumi runtime, config,\nSSH key and known_hosts, cloud credentials per configured provider, and the\nsupported OpenClaw range. With stackName, also contacts the host for container\nstate, the deployed OpenClaw version, a real gateway health probe, whether the\nport is published to the internet, disk usage on the state directory, log\nrotation, and hardening drift.\n\nUse when: something is not working and you do not yet know what; before any\ndeploy, upgrade or migration; or to find out which OpenClaw version a gateway is\nactually running.\n\nDo NOT use when: you already know the problem and want to fix it. This tool only\nreports \u2014 it changes nothing, and never runs `openclaw doctor --fix`. To move a\ngateway to another version, use clawops_gateway_update after clawops_backup_create.\n\nEvery check carries a status: fail (something is wrong that clawops can name),\nwarn (worth knowing, not broken), info (did not apply). `ok` is false only when\nsomething failed \u2014 a fresh machine with no stacks is full of warnings and healthy.";
160
194
  var clawops_doctorAnnotations = {
161
195
  title: "Run Diagnostics",
162
196
  readOnlyHint: true,
@@ -166,9 +200,9 @@ var clawops_doctorAnnotations = {
166
200
  toolsets: ["cli", "read"]
167
201
  };
168
202
  var clawops_logs_tailSchema = z.object({
169
- stackName: z.string().optional(),
170
- tailLines: z.number().int().optional().default(100),
171
- sinceMin: z.number().int().optional()
203
+ stackName: z.string().optional().describe("Which stack's gateway to read. Omitted = the default stack in ~/.clawops/config.json"),
204
+ tailLines: z.number().int().optional().default(100).describe("How many of the most recent lines to return. Keep it small; output is trimmed to 8KB regardless"),
205
+ sinceMin: z.number().int().optional().describe("Lines since N minutes ago")
172
206
  });
173
207
  var clawops_logs_tailDescription = "Tail recent gateway logs from a clawops-managed instance.\n\nUse when: the user wants to investigate recent activity or errors, or\nasks \"what's been happening\" on the gateway.\n\nDo NOT use when: the user wants real-time streaming logs (those are not\nwell-suited to tool calls; suggest the user run `clawops logs -f` directly\nin their terminal). Do NOT use for instance-level system logs: no tool runs\narbitrary remote commands, so tell the user to run `clawops ssh --command\n'journalctl ...'` themselves.";
174
208
  var clawops_logs_tailAnnotations = {
@@ -180,8 +214,8 @@ var clawops_logs_tailAnnotations = {
180
214
  toolsets: ["cli", "read"]
181
215
  };
182
216
  var clawops_monitorSchema = z.object({
183
- stackName: z.string().optional(),
184
- tailLines: z.number().int().optional().default(5)
217
+ stackName: z.string().optional().describe("Stack name. Defaults to active stack."),
218
+ tailLines: z.number().int().optional().default(5).describe("Log lines to include in snapshot.")
185
219
  });
186
220
  var clawops_monitorDescription = "Take a live snapshot of a running clawops stack: gateway health, container\nstatus, resource usage (CPU, memory, disk), and recent log lines.\n\nUse when: the user wants to know if the gateway is running, how much\nmemory or CPU it is using, what recent log activity looks like, or\nwants a quick health overview richer than clawops_status.\n\nDo NOT use when: the user wants real-time streaming logs (use\nclawops_logs_tail or suggest `clawops logs -f`). Do NOT use for\nconfiguration queries (use clawops_config_get).";
187
221
  var clawops_monitorAnnotations = {
@@ -203,8 +237,8 @@ var clawops_stacks_listAnnotations = {
203
237
  toolsets: ["admin", "read"]
204
238
  };
205
239
  var clawops_config_getSchema = z.object({
206
- stackName: z.string().optional(),
207
- key: z.string().optional()
240
+ stackName: z.string().optional().describe("Which stack's gateway config to read. Omitted = the default stack in ~/.clawops/config.json"),
241
+ key: z.string().optional().describe("Dot-path config key, e.g., gateway.auth.mode. Omit to dump the full config.")
208
242
  });
209
243
  var clawops_config_getDescription = "Read a configuration value from the remote OpenClaw gateway.\n\nUse when: the user wants to inspect current OpenClaw config (e.g., which\nmodel provider is active, which channels are enabled).\n\nDo NOT use when: the user wants to change the config \u2014 use\nclawops_config_set instead.";
210
244
  var clawops_config_getAnnotations = {
@@ -216,9 +250,9 @@ var clawops_config_getAnnotations = {
216
250
  toolsets: ["cli", "read"]
217
251
  };
218
252
  var clawops_agents_listSchema = z.object({
219
- stackName: z.string().optional()
253
+ stackName: z.string().optional().describe("Which stack's agents to list. Omitted = the default stack in ~/.clawops/config.json")
220
254
  });
221
- var clawops_agents_listDescription = "List agents currently registered on the remote OpenClaw gateway.\n\nUse when: the user wants to see which agents are running, debug agent\nrouting, or count active workspaces.\n\nDo NOT use when: the user wants one agent's logs \u2014 no tool exposes those; tell\nthe user to run `clawops agents logs <name>`.";
255
+ var clawops_agents_listDescription = "List agents currently registered on the remote OpenClaw gateway.\n\nUse when: the user wants to see which agents are running, debug agent\nrouting, or count active workspaces.\n\nDo NOT use when: the user wants one agent's activity \u2014 that is clawops_agents_logs.";
222
256
  var clawops_agents_listAnnotations = {
223
257
  title: "List OpenClaw Agents",
224
258
  readOnlyHint: true,
@@ -227,16 +261,63 @@ var clawops_agents_listAnnotations = {
227
261
  openWorldHint: true,
228
262
  toolsets: ["cli", "read"]
229
263
  };
264
+ var clawops_agents_logsSchema = z.object({
265
+ stackName: z.string().optional().describe("Which stack the agent runs on. Omitted = the default stack in ~/.clawops/config.json"),
266
+ name: z.string().describe("The agent's name, as clawops_agents_list reports it"),
267
+ limit: z.number().int().optional().default(50).describe("Maximum activity records to return. Omitted = 50"),
268
+ cursor: z.string().optional().describe("The cursor a previous call returned, to continue from where it stopped. Omitted = the most recent records")
269
+ });
270
+ var clawops_agents_logsDescription = "One agent's recent activity, read from the gateway's audit log, newest last.\nPaginated: pass the returned cursor to continue.\n\nUse when: the user asks what a specific agent did, or why it behaved as it did.\n\nDo NOT use when: the user wants the gateway's own log stream \u2014 that is\nclawops_logs_tail. Do NOT use to find agent names \u2014 that is clawops_agents_list.";
271
+ var clawops_agents_logsAnnotations = {
272
+ title: "Read One Agent's Activity",
273
+ readOnlyHint: true,
274
+ destructiveHint: false,
275
+ idempotentHint: true,
276
+ openWorldHint: true,
277
+ toolsets: ["cli", "read"]
278
+ };
279
+ var clawops_gateway_statusSchema = z.object({
280
+ stackName: z.string().optional().describe("Which stack's gateway to inspect. Omitted = the default stack in ~/.clawops/config.json")
281
+ });
282
+ var clawops_gateway_statusDescription = "Whether the OpenClaw gateway container is running, when it started, and which image it\nruns, as docker reports it on the host.\n\nUse when: the user asks whether the gateway is up or which OpenClaw version is deployed.\n\nDo NOT use when: the user wants a full health check \u2014 clawops_doctor probes the gateway\nitself rather than the container. Do NOT use to change anything; this only reads.";
283
+ var clawops_gateway_statusAnnotations = {
284
+ title: "Gateway Container Status",
285
+ readOnlyHint: true,
286
+ destructiveHint: false,
287
+ idempotentHint: true,
288
+ openWorldHint: true,
289
+ toolsets: ["cli", "read"]
290
+ };
291
+ var clawops_secret_listSchema = z.object({});
292
+ var clawops_secret_listDescription = "List the secrets clawops stores locally, by name and status. Never returns a value.\n\nUse when: the user asks which secrets are set, or before a deploy that references one.\n\nDo NOT use when: the user wants to set or rotate a secret. No tool does that, because the\nvalue would pass through the conversation (R6): ask the user to run\n`clawops secret set <NAME>` or `clawops secret rotate <NAME>` in a terminal.";
293
+ var clawops_secret_listAnnotations = {
294
+ title: "List Stored Secrets",
295
+ readOnlyHint: true,
296
+ destructiveHint: false,
297
+ idempotentHint: true,
298
+ openWorldHint: false,
299
+ toolsets: ["cli", "read"]
300
+ };
301
+ var clawops_secret_auditSchema = z.object({});
302
+ var clawops_secret_auditDescription = "Report secrets that stacks reference but clawops does not have, and `$secret:` references\nthat cannot be resolved. Never returns a value.\n\nUse when: a deploy or config change fails on a missing secret, or before applying a plan\nwhose config references secrets.\n\nDo NOT use when: the user wants to set the missing secret \u2014 ask them to run\n`clawops secret set <NAME>` in a terminal; no tool takes a secret value (R6).";
303
+ var clawops_secret_auditAnnotations = {
304
+ title: "Audit Secret References",
305
+ readOnlyHint: true,
306
+ destructiveHint: false,
307
+ idempotentHint: true,
308
+ openWorldHint: false,
309
+ toolsets: ["cli", "read"]
310
+ };
230
311
  var clawops_upSchema = z.object({
231
- stackName: z.string().optional(),
232
- provider: z.enum(["aws", "gcp", "azure", "local"]).optional(),
233
- region: z.string().optional(),
234
- instanceType: z.string().optional().default("small"),
235
- sshCidr: z.string().optional(),
236
- gatewayCidr: z.string().optional(),
237
- publishGateway: z.enum(["loopback", "all"]).optional(),
238
- openclawVersion: z.string().optional(),
239
- dryRun: z.boolean().optional().default(false)
312
+ stackName: z.string().optional().describe("Name for the stack to provision, and the name every later command refers to it by. Omitted = the default stack in ~/.clawops/config.json"),
313
+ provider: z.enum(["aws", "gcp", "azure", "local"]).optional().describe("Defaults to provider configured for this stack"),
314
+ region: z.string().optional().describe("Cloud region to deploy into, in the provider's own spelling (us-east-1, us-central1, eastus). Omitted = the region recorded for the stack"),
315
+ instanceType: z.string().optional().default("small").describe("A clawops size (micro|small|medium|large|gpu) or a provider-native machine type.\nNot an enum: Azure offers SKU families per subscription, and an account offered\nnone of the five sizes clawops names would otherwise have no way to deploy.\n"),
316
+ sshCidr: z.string().optional().describe("CIDR(s) allowed to reach SSH, comma-separated, or 'auto' for the caller's own\naddress. Omitted means none, and nothing will be able to connect \u2014 including\nevery clawops day-two command.\n"),
317
+ gatewayCidr: z.string().optional().describe("CIDR(s) allowed to reach the gateway port. Requires publishGateway=all."),
318
+ publishGateway: z.enum(["loopback", "all"]).optional().describe("Which interface the gateway binds. 'all' serves plaintext HTTP."),
319
+ openclawVersion: z.string().optional().describe("semver or 'stable'/'dev'"),
320
+ dryRun: z.boolean().optional().default(false).describe("Show what would be created and change nothing. Use this first when the user has not yet approved a spend")
240
321
  });
241
322
  var clawops_upDescription = 'Provision and deploy a clawops stack. Idempotent \u2014 re-running with no spec\nchange produces no diff. Long-running (median 3min, p99 8min) so emits\nprogress notifications per R12.\n\nUse when: the user explicitly asks to deploy, provision, create, or "spin\nup" a stack. Always after the user has reviewed a plan\n(clawops_plan first when in doubt).\n\nDo NOT use when: the user has not yet generated a plan and is in\nexploratory/discovery mode \u2014 use clawops_plan first. Do NOT use for an\nexisting stack you only need to update; refresh first.';
242
323
  var clawops_upAnnotations = {
@@ -248,10 +329,10 @@ var clawops_upAnnotations = {
248
329
  toolsets: ["cli"]
249
330
  };
250
331
  var clawops_destroySchema = z.object({
251
- stackName: z.string(),
252
- yes: z.boolean().optional().default(false)
332
+ stackName: z.string().describe("The stack to destroy. Required, and worth repeating back to the user before calling: this deletes every resource it provisioned"),
333
+ yes: z.boolean().optional().default(false).describe("Skip elicitation; CI/scripted use only")
253
334
  });
254
- var clawops_destroyDescription = "Destroy a clawops stack. Removes ALL provisioned resources. Triggers\nelicitation confirmation showing the resource diff before execution\n(R19). Cannot be undone.\n\nUse when: the user explicitly asks to destroy, tear down, delete, or\nremove a stack. Always confirm the stack name.\n\nDo NOT use when: the user wants to stop the gateway temporarily \u2014 that is not\na destroy, and no tool stops a gateway; clawops_gateway_restart is the only\ngateway tool. Do NOT use when in doubt about which stack; list first with\nclawops_stacks_list.";
335
+ var clawops_destroyDescription = "Destroy a clawops stack. Removes ALL provisioned resources. Triggers\nelicitation confirmation showing the resource diff before execution\n(R19). Cannot be undone.\n\nUse when: the user explicitly asks to destroy, tear down, delete, or\nremove a stack. Always confirm the stack name.\n\nDo NOT use when: the user wants to stop the gateway temporarily \u2014 that is not\na destroy, and no tool stops a gateway; clawops_gateway_restart restarts it. Do NOT use when in doubt about which stack; list first with\nclawops_stacks_list.";
255
336
  var clawops_destroyAnnotations = {
256
337
  title: "Destroy Stack (DESTRUCTIVE)",
257
338
  readOnlyHint: false,
@@ -261,8 +342,8 @@ var clawops_destroyAnnotations = {
261
342
  toolsets: ["cli"]
262
343
  };
263
344
  var clawops_applySchema = z.object({
264
- planPath: z.string(),
265
- yes: z.boolean().optional().default(false)
345
+ planPath: z.string().describe("Absolute path to plan JSON (R7)"),
346
+ yes: z.boolean().optional().default(false).describe("Skip the confirmation and apply immediately. Only when the user has already approved this specific plan")
266
347
  });
267
348
  var clawops_applyDescription = "Apply a previously-generated Maker plan (deploy-plan.schema.json).\nDeterministic \u2014 the plan describes exactly what will be created.\n\nUse when: the user has a plan file path and wants to apply it. This is\nthe agent-friendly path for any deploy/destroy operation.\n\nDo NOT use when: there's no plan file \u2014 generate one first with\nclawops_plan.";
268
349
  var clawops_applyAnnotations = {
@@ -274,16 +355,16 @@ var clawops_applyAnnotations = {
274
355
  toolsets: ["cli"]
275
356
  };
276
357
  var clawops_planSchema = z.object({
277
- stackName: z.string().optional(),
278
- provider: z.enum(["aws", "gcp", "azure", "local"]).optional(),
279
- region: z.string().optional(),
280
- instanceType: z.string().optional(),
281
- openclawVersion: z.string().optional(),
282
- sshCidr: z.string().optional(),
283
- gatewayCidr: z.string().optional(),
284
- publishGateway: z.enum(["loopback", "all"]).optional(),
285
- privateOnly: z.boolean().optional(),
286
- outPath: z.string().optional()
358
+ stackName: z.string().optional().describe("Which stack the plan is for. Omitted = the default stack in ~/.clawops/config.json"),
359
+ provider: z.enum(["aws", "gcp", "azure", "local"]).optional().describe("Cloud to plan against. Omitted = the provider recorded for the stack. 'local' has no plan/apply path and is refused"),
360
+ region: z.string().optional().describe("Cloud region, in the provider's own spelling (us-east-1, us-central1, eastus). Omitted = the region recorded for the stack"),
361
+ instanceType: z.string().optional().describe("A clawops alias (micro|small|medium|large|gpu) or a machine type the cloud names itself, e.g. t3.small"),
362
+ openclawVersion: z.string().optional().describe("semver, or 'stable'/'dev'"),
363
+ sshCidr: z.string().optional().describe("CIDR(s) allowed to reach SSH, comma-separated, or 'auto' for this machine. Omitted = none, and nothing will be able to connect"),
364
+ gatewayCidr: z.string().optional().describe("CIDR(s) allowed to reach the gateway port, or 'auto'. Requires publishGateway=all"),
365
+ publishGateway: z.enum(["loopback", "all"]).optional().describe("Which interface the gateway binds. loopback (default) keeps it off the network"),
366
+ privateOnly: z.boolean().optional().describe("Close public SSH and gateway access; reach the stack over its tailnet. Requires a verified tailnet address (clawops_harden with tailscale), and refuses unless that address answers SSH now"),
367
+ outPath: z.string().optional().describe("Absolute path to write plan; if omitted, plan returned inline")
287
368
  });
288
369
  var clawops_planDescription = "Generate a Maker deploy plan (does NOT apply). Plan is JSON conforming to\ndeploy-plan.schema.json \u2014 review before applying.\n\nUse when: the user wants to see what would be created before committing,\nor you (the agent) need a reviewable artifact for the user to approve.\n\nDo NOT use when: the user has explicitly asked to deploy and you already\nhave their approval \u2014 go directly to clawops_up.";
289
370
  var clawops_planAnnotations = {
@@ -295,10 +376,10 @@ var clawops_planAnnotations = {
295
376
  toolsets: ["cli", "read"]
296
377
  };
297
378
  var clawops_config_setSchema = z.object({
298
- stackName: z.string().optional(),
299
- key: z.string(),
300
- value: z.string(),
301
- restart: z.boolean().optional().default(false)
379
+ stackName: z.string().optional().describe("Which stack's gateway config to write. Omitted = the default stack in ~/.clawops/config.json"),
380
+ key: z.string().describe("Dotted path into the gateway config, e.g. models.provider or channels.slack.enabled"),
381
+ value: z.string().describe("Stringified; JSON for complex values"),
382
+ restart: z.boolean().optional().default(false).describe("Restart the gateway so the change takes effect. Without it the value is written and the running gateway keeps the old one")
302
383
  });
303
384
  var clawops_config_setDescription = "Set a configuration value on the remote OpenClaw gateway. Optionally\nrestarts the gateway after.\n\nUse when: the user wants to change OpenClaw configuration (model\nprovider, channel auth, gateway port).\n\nDo NOT use when: the user is reading config \u2014 use clawops_config_get.\nDo NOT bulk-edit; one key at a time so changes are auditable (R21).";
304
385
  var clawops_config_setAnnotations = {
@@ -310,9 +391,9 @@ var clawops_config_setAnnotations = {
310
391
  toolsets: ["cli"]
311
392
  };
312
393
  var clawops_config_unsetSchema = z.object({
313
- stackName: z.string().optional(),
314
- key: z.string(),
315
- restart: z.boolean().optional().default(false)
394
+ stackName: z.string().optional().describe("Which stack's gateway config to write. Omitted = the default stack in ~/.clawops/config.json"),
395
+ key: z.string().describe("Dot-path config key to remove"),
396
+ restart: z.boolean().optional().default(false).describe("Restart the gateway so the removal takes effect. Without it the key is removed and the running gateway keeps the old value")
316
397
  });
317
398
  var clawops_config_unsetDescription = "Remove a configuration key from the remote OpenClaw gateway config, reverting\nit to the OpenClaw default.\n\nUse when: the user wants to delete a config key entirely (e.g., remove a\nchannel, clear an override).\n\nDo NOT use when: the user wants to set the key to a new value \u2014 use\nclawops_config_set instead.";
318
399
  var clawops_config_unsetAnnotations = {
@@ -324,7 +405,7 @@ var clawops_config_unsetAnnotations = {
324
405
  toolsets: ["cli"]
325
406
  };
326
407
  var clawops_config_validateSchema = z.object({
327
- stackName: z.string().optional()
408
+ stackName: z.string().optional().describe("Which stack's deployed config to validate. Omitted = the default stack in ~/.clawops/config.json")
328
409
  });
329
410
  var clawops_config_validateDescription = "Validate the remote OpenClaw gateway config against the known schema. Checks\nfor structural errors (wrong types, unknown top-level keys) that would cause\nOpenClaw to fail on startup.\n\nUse when: the user wants to verify config before restarting the gateway, or\nafter editing openclaw.json manually.\n\nDo NOT use when: the user wants to change config \u2014 use clawops_config_set.";
330
411
  var clawops_config_validateAnnotations = {
@@ -336,7 +417,7 @@ var clawops_config_validateAnnotations = {
336
417
  toolsets: ["cli", "read"]
337
418
  };
338
419
  var clawops_gateway_restartSchema = z.object({
339
- stackName: z.string().optional()
420
+ stackName: z.string().optional().describe("Which stack's gateway to restart. Omitted = the default stack in ~/.clawops/config.json")
340
421
  });
341
422
  var clawops_gateway_restartDescription = "Restart the OpenClaw gateway daemon on the remote instance.\n\nUse when: a gateway-wide config change requires reload, or the gateway is\nreported as unresponsive.\n\nNote: OpenClaw 2.0 removed per-agent restart; `gateway restart` is the only\nrestart it offers, and it affects every agent on the host. Brief downtime (~10s).";
342
423
  var clawops_gateway_restartAnnotations = {
@@ -347,13 +428,115 @@ var clawops_gateway_restartAnnotations = {
347
428
  openWorldHint: true,
348
429
  toolsets: ["cli"]
349
430
  };
431
+ var clawops_gateway_updateSchema = z.object({
432
+ stackName: z.string().optional().describe("Which stack's gateway to update. Omitted = the default stack in ~/.clawops/config.json"),
433
+ version: z.string().describe("A concrete OpenClaw version to move to, e.g. 2026.9.2. Moving tags are refused"),
434
+ yes: z.boolean().optional().default(false).describe("Skip the confirmation. Only when the user has already approved this specific update")
435
+ });
436
+ var clawops_gateway_updateDescription = "Move the gateway to a different OpenClaw version: pull the image and restart the gateway\non it. Every agent on the host restarts with it.\n\nUse when: the user asks to upgrade or downgrade OpenClaw on a running stack, naming a\nversion.\n\nDo NOT use when: the user has not named a concrete version \u2014 moving tags such as\n`latest` are refused, as the CLI refuses them. Do NOT use without a backup the user can\nreturn to: suggest clawops_backup_create first.";
437
+ var clawops_gateway_updateAnnotations = {
438
+ title: "Update OpenClaw Version",
439
+ readOnlyHint: false,
440
+ destructiveHint: true,
441
+ idempotentHint: true,
442
+ openWorldHint: true,
443
+ toolsets: ["cli"]
444
+ };
445
+ var clawops_backup_createSchema = z.object({
446
+ stackName: z.string().optional().describe("Which stack to back up. Omitted = the default stack in ~/.clawops/config.json"),
447
+ out: z.string().optional().describe("Absolute local path to write the archive to. Omitted = a timestamped file under ~/.clawops/backups")
448
+ });
449
+ var clawops_backup_createDescription = "Take a backup of the gateway's state (config, database, sessions, transcripts and\ninstalled plugins) and download it to a local archive. Live state is not changed.\n\nUse when: the user asks for a backup, and before anything that replaces state: a\nversion update, a migration, a restore, or a plan that replaces the instance.\n\nDo NOT use when: the user wants to back up cloud infrastructure \u2014 Pulumi state and plans\ncover that, not this.";
450
+ var clawops_backup_createAnnotations = {
451
+ title: "Back Up Gateway State",
452
+ readOnlyHint: false,
453
+ destructiveHint: false,
454
+ idempotentHint: false,
455
+ openWorldHint: true,
456
+ toolsets: ["cli"]
457
+ };
458
+ var clawops_backup_restoreSchema = z.object({
459
+ stackName: z.string().optional().describe("Which stack to restore onto. Omitted = the default stack in ~/.clawops/config.json"),
460
+ file: z.string().describe("Absolute local path to the backup archive, as clawops_backup_create wrote it"),
461
+ activate: z.boolean().optional().default(false).describe("Put the restored state into service. Omitted = expand into a staging directory and touch nothing live"),
462
+ yes: z.boolean().optional().default(false).describe("Skip the confirmation. Only when the user has already approved this specific restore")
463
+ });
464
+ var clawops_backup_restoreDescription = "Verify a backup archive and expand it on the host beside the live state, never over it.\nWith activate: true, also put it into service: stop the gateway, swap the restored state\nin (keeping the state it replaces), restart, and roll back if the gateway does not answer.\n\nUse when: the user asks to restore or recover from a backup, naming the archive.\n\nDo NOT use when: the gateway is merely unhealthy \u2014 clawops_workflow_recover diagnoses that\nwithout discarding state. Restoring is time travel: everything since the backup is lost,\nand channel credentials may need relinking.";
465
+ var clawops_backup_restoreAnnotations = {
466
+ title: "Restore Gateway State (DESTRUCTIVE with activate)",
467
+ readOnlyHint: false,
468
+ destructiveHint: true,
469
+ idempotentHint: false,
470
+ openWorldHint: true,
471
+ toolsets: ["cli"]
472
+ };
473
+ var clawops_migrateSchema = z.object({
474
+ stackName: z.string().optional().describe("Which stack to migrate. Omitted = the default stack in ~/.clawops/config.json"),
475
+ openclawVersion: z.string().optional().describe("The OpenClaw 2.x version to migrate to. Omitted = the version this clawops release defaults to"),
476
+ yes: z.boolean().optional().default(false).describe("Skip the confirmation. Only when the user has already approved this migration")
477
+ });
478
+ var clawops_migrateDescription = "Move a deployment from OpenClaw 1.x onto the 2.0 runtime contract: state ownership,\nconfig, and the gateway's run command, in the order a real migration showed works.\n\nUse when: a stack still runs OpenClaw 1.x and the user wants it on 2.x. clawops_doctor\nand clawops_gateway_status say which line a stack runs.\n\nDo NOT use when: the stack is already on 2.x \u2014 use clawops_gateway_update to change\nversions within 2.x. Take a backup first with clawops_backup_create.";
479
+ var clawops_migrateAnnotations = {
480
+ title: "Migrate 1.x to 2.0 (DESTRUCTIVE)",
481
+ readOnlyHint: false,
482
+ destructiveHint: true,
483
+ idempotentHint: false,
484
+ openWorldHint: true,
485
+ toolsets: ["cli"]
486
+ };
487
+ var clawops_stacks_deleteSchema = z.object({
488
+ name: z.string().describe("The stack to remove from config, as clawops_stacks_list reports it"),
489
+ force: z.boolean().optional().default(false).describe("Skip the safety checks: allow deleting the default stack, or one still deployed. Omitted = refuse both"),
490
+ yes: z.boolean().optional().default(false).describe("Skip the confirmation. Only when the user has already approved this deletion")
491
+ });
492
+ var clawops_stacks_deleteDescription = "Remove a stack from ~/.clawops/config.json. This forgets the stack; it does not tear\ndown its infrastructure, and it refuses a stack that is still deployed unless forced.\n\nUse when: the user wants to stop tracking a stack whose resources are already gone.\n\nDo NOT use when: the user wants the infrastructure removed \u2014 that is clawops_destroy,\nfirst. Forcing the delete of a deployed stack leaves resources running and billing that\nclawops can no longer see.";
493
+ var clawops_stacks_deleteAnnotations = {
494
+ title: "Forget a Stack (DESTRUCTIVE)",
495
+ readOnlyHint: false,
496
+ destructiveHint: true,
497
+ idempotentHint: true,
498
+ openWorldHint: false,
499
+ toolsets: ["admin"]
500
+ };
501
+ var clawops_secret_deleteSchema = z.object({
502
+ name: z.string().describe("The secret's name, as clawops_secret_list reports it"),
503
+ yes: z.boolean().optional().default(false).describe("Skip the confirmation. Only when the user has already approved this deletion")
504
+ });
505
+ var clawops_secret_deleteDescription = "Delete a locally stored secret by name. Warns when a stack still references it.\n\nUse when: the user asks to remove a secret they no longer use.\n\nDo NOT use when: the user wants to replace a value \u2014 ask them to run\n`clawops secret rotate <NAME>`; no tool takes a secret value (R6).";
506
+ var clawops_secret_deleteAnnotations = {
507
+ title: "Delete a Stored Secret (DESTRUCTIVE)",
508
+ readOnlyHint: false,
509
+ destructiveHint: true,
510
+ idempotentHint: true,
511
+ openWorldHint: false,
512
+ toolsets: ["cli"]
513
+ };
514
+ var clawops_initSchema = z.object({
515
+ provider: z.enum(["aws", "gcp", "azure", "local"]).describe("Which cloud this stack deploys to, or 'local' for a machine you already have"),
516
+ stackName: z.string().optional().describe('Name for the stack, used by every later call. Omitted = "default"'),
517
+ stateUrl: z.string().optional().describe("Where Pulumi state lives, e.g. s3://bucket/clawops, gs://bucket/clawops, azblob://container. Omitted = clawops names one from the cloud account it can see, which needs credentials"),
518
+ region: z.string().optional().describe("Cloud region in the provider's own spelling. Omitted = us-east-1 (aws), us-central1 (gcp), eastus (azure)"),
519
+ host: z.string().optional().describe("[local only] Hostname or IP of the machine to manage. Required when provider is local"),
520
+ sshUser: z.string().optional().describe("[local only] SSH login user. Omitted = root"),
521
+ sshPort: z.number().int().optional().describe("[local only] SSH port. Omitted = 22"),
522
+ force: z.boolean().optional().describe("Overwrite a stack that already exists. Refused without it, because replacing a state backend orphans the state it points at")
523
+ });
524
+ var clawops_initDescription = "Register a stack and write ~/.clawops/config.json: the provider, the state backend, the\nregion, and an SSH key pair generated if one is not already there. Nothing is provisioned\nand nothing is charged; this only creates local configuration.\n\nUse when: any other clawops tool reports that there is no config, or the user wants to add\na second stack alongside the ones they have. This is the first call on a machine that has\nnever run clawops \u2014 a fresh container, a new laptop, a sandbox.\n\nAdding a stack is additive and safe: existing stacks are kept. Overwriting one needs\nforce: true, because changing a state backend orphans the Pulumi state it points at \u2014 the\ninfrastructure stays up and clawops can no longer see or destroy it.\n\nFor aws, gcp and azure, stateUrl can be omitted and clawops names a bucket from the\naccount it can see; that needs cloud credentials in the environment, so in a sandbox pass\nstateUrl explicitly. The local provider needs host instead, and no cloud account at all.\n\nDo NOT use when: the user wants to deploy \u2014 that is clawops_up, after this. Credentials\nare never passed here: clawops reads them from the environment (R6).";
525
+ var clawops_initAnnotations = {
526
+ title: "Initialise clawops Config",
527
+ readOnlyHint: false,
528
+ destructiveHint: false,
529
+ idempotentHint: false,
530
+ openWorldHint: false,
531
+ toolsets: ["cli"]
532
+ };
350
533
  var clawops_hardenSchema = z.object({
351
- stackName: z.string().optional(),
352
- options: z.string().optional(),
353
- dryRun: z.boolean().optional().default(false),
354
- tailscale: z.boolean().optional().default(false),
355
- tailscaleRevert: z.boolean().optional().default(false),
356
- yes: z.boolean().optional().default(false)
534
+ stackName: z.string().optional().describe("Which stack to harden. Omitted = the default stack in ~/.clawops/config.json"),
535
+ options: z.string().optional().describe("Comma-separated module IDs; default is every defaultOn module for the provider"),
536
+ dryRun: z.boolean().optional().default(false).describe("Report the current state, change nothing"),
537
+ tailscale: z.boolean().optional().default(false).describe("Join the tailnet, verify this machine reaches the host there, then use that address"),
538
+ tailscaleRevert: z.boolean().optional().default(false).describe("Leave the tailnet and go back to the public address"),
539
+ yes: z.boolean().optional().default(false).describe("Skip elicitation; CI/scripted use only")
357
540
  });
358
541
  var clawops_hardenDescription = "Apply security hardening to a deployed stack: SSH, UFW, fail2ban,\nunattended-upgrades, the Docker socket, and per-cloud checks. Optionally join\nthe stack to a Tailscale network and reach it there instead of over the public\ninternet.\n\nUse when: the user asks to harden, secure, or lock down a stack; asks what the\nhardening report says (with dryRun: true, which changes nothing); or asks to put\na stack on their tailnet.\n\ntailscale: true installs Tailscale, joins the tailnet as clawops-<stack>, and\nthen moves clawops onto that address \u2014 but only after opening an SSH session to\nit, against host keys pinned over the connection already trusted. If that fails,\nnothing is recorded and the public address stays in use. The auth key comes from\n`clawops secret set TAILSCALE_AUTH_KEY` and is never passed through this tool\n(R6).\n\ntailscaleRevert: true takes the host back off the tailnet, over its public\naddress. On a private-only stack it refuses and names the plan/apply commands\nthat reopen SSH first \u2014 relay them rather than trying to work around it.\n\nTo close the public ports afterwards, plan with privateOnly: true and apply that\nplan; this tool does not change firewall rules.\n\nDo NOT use when: the user wants to know whether a stack is healthy \u2014 that is\nclawops_doctor. Do NOT pass tailscale and tailscaleRevert together.";
359
542
  var clawops_hardenAnnotations = {
@@ -365,10 +548,10 @@ var clawops_hardenAnnotations = {
365
548
  toolsets: ["cli"]
366
549
  };
367
550
  var clawops_workflow_deploy_appSchema = z.object({
368
- provider: z.enum(["aws", "gcp", "azure", "local"]),
369
- region: z.string().optional(),
370
- stackName: z.string().optional().default("default"),
371
- instanceType: z.enum(["micro", "small", "medium", "large", "gpu"]).optional().default("small")
551
+ provider: z.enum(["aws", "gcp", "azure", "local"]).describe("Cloud to deploy to. Omitted = the default provider in ~/.clawops/config.json"),
552
+ region: z.string().optional().describe("Cloud region, in the provider's own spelling (us-east-1, us-central1, eastus). Omitted = the provider's default"),
553
+ stackName: z.string().optional().default("default").describe("Name for the new stack. Omitted = the default stack name in ~/.clawops/config.json"),
554
+ instanceType: z.enum(["micro", "small", "medium", "large", "gpu"]).optional().default("small").describe("Machine size: a clawops alias (micro|small|medium|large|gpu) or a type the cloud names itself, e.g. t3.small")
372
555
  });
373
556
  var clawops_workflow_deploy_appDescription = 'Single-tool workflow that takes a user from "I want to deploy OpenClaw\nto <provider>" to a verified, healthy gateway. Internally: plan \u2192 user\nconfirms (elicitation) \u2192 up \u2192 wait for healthy \u2192 return URL.\n\nUse when: the user expresses end-to-end deployment intent ("deploy to\nAWS", "spin up an OpenClaw on GCP for me").\n\nDo NOT use when: the user is mid-deployment and only needs one step\n(e.g., they already have a plan; use clawops_apply). Do NOT use for\ndestroying or updating \u2014 separate workflows.';
374
557
  var clawops_workflow_deploy_appAnnotations = {
@@ -380,7 +563,7 @@ var clawops_workflow_deploy_appAnnotations = {
380
563
  toolsets: ["workflow"]
381
564
  };
382
565
  var clawops_workflow_recoverSchema = z.object({
383
- stackName: z.string().optional()
566
+ stackName: z.string().optional().describe("Which stack to diagnose. Omitted = the default stack in ~/.clawops/config.json")
384
567
  });
385
568
  var clawops_workflow_recoverDescription = `Diagnostic workflow for an unhealthy stack. Internally: status check \u2192
386
569
  gateway logs \u2192 agent logs \u2192 systemd service status \u2192 produces a structured
@@ -400,7 +583,7 @@ var clawops_workflow_recoverAnnotations = {
400
583
  toolsets: ["workflow", "read"]
401
584
  };
402
585
  var clawops_task_statusSchema = z.object({
403
- taskId: z.string()
586
+ taskId: z.string().describe("The taskId returned by a long-running tool such as clawops_up, clawops_apply or clawops_destroy")
404
587
  });
405
588
  var clawops_task_statusDescription = "Poll the status of a long-running clawops task (returned by clawops_up,\nclawops_destroy, clawops_apply, etc.). Per R12 streaming model.\n\nUse when: the user is waiting on a long-running deploy/destroy and wants\nprogress, OR you need to check whether a previously-started operation\nfinished.\n\nDo NOT use when: there is no active taskId \u2014 start the operation first.";
406
589
  var clawops_task_statusAnnotations = {
@@ -419,6 +602,10 @@ var TOOLSETS = {
419
602
  "clawops_monitor",
420
603
  "clawops_config_get",
421
604
  "clawops_agents_list",
605
+ "clawops_agents_logs",
606
+ "clawops_gateway_status",
607
+ "clawops_secret_list",
608
+ "clawops_secret_audit",
422
609
  "clawops_up",
423
610
  "clawops_destroy",
424
611
  "clawops_apply",
@@ -427,6 +614,12 @@ var TOOLSETS = {
427
614
  "clawops_config_unset",
428
615
  "clawops_config_validate",
429
616
  "clawops_gateway_restart",
617
+ "clawops_gateway_update",
618
+ "clawops_backup_create",
619
+ "clawops_backup_restore",
620
+ "clawops_migrate",
621
+ "clawops_secret_delete",
622
+ "clawops_init",
430
623
  "clawops_harden",
431
624
  "clawops_task_status"
432
625
  ],
@@ -442,13 +635,18 @@ var TOOLSETS = {
442
635
  "clawops_stacks_list",
443
636
  "clawops_config_get",
444
637
  "clawops_agents_list",
638
+ "clawops_agents_logs",
639
+ "clawops_gateway_status",
640
+ "clawops_secret_list",
641
+ "clawops_secret_audit",
445
642
  "clawops_plan",
446
643
  "clawops_config_validate",
447
644
  "clawops_workflow_recover",
448
645
  "clawops_task_status"
449
646
  ],
450
647
  admin: [
451
- "clawops_stacks_list"
648
+ "clawops_stacks_list",
649
+ "clawops_stacks_delete"
452
650
  ]
453
651
  };
454
652
 
@@ -485,34 +683,6 @@ function text(t) {
485
683
  }
486
684
 
487
685
  // src/mcp/tools/_conn.ts
488
- async function resolveConn(ctx) {
489
- if (ctx.adapter.name === "local") {
490
- const state = ctx.localState;
491
- if (!state) throw new StateError("Stack has no local state \u2014 run `clawops up` first.");
492
- return {
493
- host: state.sshHost,
494
- port: state.sshPort,
495
- user: state.sshUser,
496
- privateKeyPath: state.privateKeyPath,
497
- knownHostsPath: state.knownHostsPath
498
- };
499
- }
500
- const { extractBaseOutputs: extractBaseOutputs2 } = await import("./outputs-DJHBY7EE.js");
501
- const stack = await ctx.getStack();
502
- const outputMap = await stack.outputs();
503
- const outputs = Object.fromEntries(
504
- Object.entries(outputMap).map(([k, v]) => [k, v.value])
505
- );
506
- if (!outputs["publicIp"]) {
507
- throw new StateError("Stack has no outputs \u2014 run `clawops up` first.");
508
- }
509
- const base = extractBaseOutputs2(outputs);
510
- return ctx.adapter.getConnectionInfo({
511
- ...base,
512
- privateKeyPath: ctx.config.ssh.keyPath,
513
- knownHostsPath: ctx.config.ssh.knownHostsPath
514
- });
515
- }
516
686
  function errText(message) {
517
687
  return { content: [{ type: "text", text: message }], isError: true };
518
688
  }
@@ -586,6 +756,29 @@ ${result.stdout}`;
586
756
  }
587
757
  }
588
758
 
759
+ // src/mcp/tools/_confirm.ts
760
+ async function confirmDestructive(server, opts) {
761
+ const capabilities = server.server.getClientCapabilities();
762
+ if (!capabilities?.elicitation) {
763
+ return {
764
+ confirmed: false,
765
+ reason: `This client cannot show a confirmation prompt, and ${opts.what} is not something clawops will do unconfirmed. Ask the user to confirm, then call this tool again with \`yes: true\`. (The client did not declare the MCP elicitation capability.)`
766
+ };
767
+ }
768
+ const elicit = await server.server.elicitInput({
769
+ message: opts.message,
770
+ requestedSchema: {
771
+ type: "object",
772
+ properties: { confirmed: { type: "boolean", title: opts.title } },
773
+ required: ["confirmed"]
774
+ }
775
+ });
776
+ if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
777
+ return { confirmed: false, reason: `Cancelled \u2014 ${opts.what} did not run. Nothing was changed.` };
778
+ }
779
+ return { confirmed: true };
780
+ }
781
+
589
782
  // src/mcp/tools/cli/stacks.ts
590
783
  async function handleStacksList(_input, _server) {
591
784
  const config = getConfig();
@@ -601,6 +794,24 @@ async function handleStacksList(_input, _server) {
601
794
  }));
602
795
  return { content: [{ type: "text", text: JSON.stringify({ stacks }, null, 2) }] };
603
796
  }
797
+ async function handleStacksDelete(input, server) {
798
+ const force = input.force === true;
799
+ const check = await checkStackDelete(input.name, { force });
800
+ if (!check.ok) return errText(check.reason);
801
+ if (!input.yes) {
802
+ const consequence = force ? "force skips the still-deployed check: if this stack is still deployed, its resources stay running and billing, and clawops can no longer see them." : check.warnings.length > 0 ? `${check.warnings.join(" ")} If it is still deployed, its resources stay running and billing.` : "clawops sees no deployment for it, so nothing should be left running.";
803
+ const confirmation = await confirmDestructive(server, {
804
+ message: `Forget stack "${input.name}"? This removes it from ~/.clawops/config.json; it does NOT tear down its infrastructure. ${consequence}` + (check.isDefault ? " It is the default stack, so another stack becomes the default." : ""),
805
+ title: "Confirm forgetting the stack",
806
+ what: `forgetting stack "${input.name}"`
807
+ });
808
+ if (!confirmation.confirmed) return okText(confirmation.reason);
809
+ }
810
+ const deleted = deleteStackFromConfig(input.name);
811
+ const lines = [...check.warnings, forgetNotice(input.name), deleted.message];
812
+ if (deleted.newDefaultMessage) lines.push(deleted.newDefaultMessage);
813
+ return okText(lines.join("\n"));
814
+ }
604
815
 
605
816
  // src/mcp/tools/cli/config.ts
606
817
  async function handleConfigGet(input, _server) {
@@ -623,16 +834,13 @@ async function handleConfigGet(input, _server) {
623
834
  }
624
835
  }
625
836
  async function handleConfigSet(input, server) {
626
- const elicit = await server.server.elicitInput({
837
+ const confirmation = await confirmDestructive(server, {
627
838
  message: `Set ${input.key} = ${input.value} on stack "${input.stackName ?? "default"}"?`,
628
- requestedSchema: {
629
- type: "object",
630
- properties: { confirmed: { type: "boolean", title: "Confirm config change" } },
631
- required: ["confirmed"]
632
- }
839
+ title: "Confirm config change",
840
+ what: "changing gateway configuration"
633
841
  });
634
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
635
- return okText("Config change cancelled.");
842
+ if (!confirmation.confirmed) {
843
+ return okText(confirmation.reason);
636
844
  }
637
845
  const ac = new AbortController();
638
846
  const ctx = buildContext({ stack: input.stackName });
@@ -672,16 +880,13 @@ async function handleConfigSet(input, server) {
672
880
  }
673
881
  }
674
882
  async function handleConfigUnset(input, server) {
675
- const elicit = await server.server.elicitInput({
883
+ const confirmation = await confirmDestructive(server, {
676
884
  message: `Remove config key "${input.key}" on stack "${input.stackName ?? "default"}"?`,
677
- requestedSchema: {
678
- type: "object",
679
- properties: { confirmed: { type: "boolean", title: "Confirm key removal" } },
680
- required: ["confirmed"]
681
- }
885
+ title: "Confirm key removal",
886
+ what: "changing gateway configuration"
682
887
  });
683
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
684
- return okText("Config unset cancelled.");
888
+ if (!confirmation.confirmed) {
889
+ return okText(confirmation.reason);
685
890
  }
686
891
  const ac = new AbortController();
687
892
  const ctx = buildContext({ stack: input.stackName });
@@ -783,42 +988,34 @@ async function handleAgentsList(input, _server) {
783
988
  drainPool();
784
989
  }
785
990
  }
786
-
787
- // src/mcp/tools/cli/gateway.ts
788
- async function handleGatewayRestart(input, server) {
789
- const elicit = await server.server.elicitInput({
790
- message: `Restart the OpenClaw gateway on stack "${input.stackName ?? "default"}"? This will briefly interrupt connections.`,
791
- requestedSchema: {
792
- type: "object",
793
- properties: { confirmed: { type: "boolean", title: "Confirm restart" } },
794
- required: ["confirmed"]
795
- }
796
- });
797
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
798
- return okText("Gateway restart cancelled.");
991
+ async function handleAgentsLogs(input, _server) {
992
+ if (input.name.trim() === "") {
993
+ return errText("name is required: the agent name, as clawops_agents_list reports it.");
799
994
  }
800
995
  const ctx = buildContext({ stack: input.stackName });
801
996
  const conn = await resolveConn(ctx);
802
997
  const { session, release } = await acquireSession(conn);
803
998
  try {
804
- const imgResult = await execPrivileged(session, IMAGE_INSPECT_CMD);
805
- const image = imageForRestart(imgResult.stdout);
806
- if (!image.ok) return errText(image.error);
807
- const result = await execPrivileged(
808
- session,
809
- gatewayRunCommand({ image: image.value, stateDir: STATE_DIR_HOST_LINUX })
810
- );
811
- if (result.code !== 0) {
812
- return errText(`Gateway restart failed: ${result.stderr}`);
813
- }
814
- return okText("Gateway restarted successfully.");
999
+ const page = await readAgentActivity(session, {
1000
+ agentId: input.name,
1001
+ limit: input.limit ?? DEFAULT_AGENT_LOG_LIMIT,
1002
+ ...input.cursor ? { cursor: input.cursor } : {}
1003
+ });
1004
+ if (!page.ok) return errText(page.error);
1005
+ const body = {
1006
+ agent: input.name,
1007
+ ...page.value.cursor ? { cursor: page.value.cursor } : {},
1008
+ count: page.value.records.length,
1009
+ records: page.value.records
1010
+ };
1011
+ return okText(trimForMcp(JSON.stringify(body, null, 2), ctx.stackName).content);
815
1012
  } finally {
816
1013
  release();
817
1014
  drainPool();
818
1015
  }
819
1016
  }
820
1017
 
821
- // src/mcp/tools/cli/up.ts
1018
+ // src/mcp/tools/cli/gateway.ts
822
1019
  import { randomUUID as randomUUID2 } from "crypto";
823
1020
 
824
1021
  // src/mcp/progress.ts
@@ -852,19 +1049,100 @@ function makeProgressEmitter(server, progressToken) {
852
1049
  };
853
1050
  }
854
1051
 
1052
+ // src/mcp/tools/cli/gateway.ts
1053
+ async function handleGatewayRestart(input, server) {
1054
+ const confirmation = await confirmDestructive(server, {
1055
+ message: `Restart the OpenClaw gateway on stack "${input.stackName ?? "default"}"? This will briefly interrupt connections.`,
1056
+ title: "Confirm restart",
1057
+ what: "restarting the gateway"
1058
+ });
1059
+ if (!confirmation.confirmed) {
1060
+ return okText(confirmation.reason);
1061
+ }
1062
+ const ctx = buildContext({ stack: input.stackName });
1063
+ const conn = await resolveConn(ctx);
1064
+ const { session, release } = await acquireSession(conn);
1065
+ try {
1066
+ await restartGateway(session);
1067
+ return okText("Gateway restarted.");
1068
+ } catch (e) {
1069
+ return errText(e.message);
1070
+ } finally {
1071
+ release();
1072
+ drainPool();
1073
+ }
1074
+ }
1075
+ async function handleGatewayStatus(input, _server) {
1076
+ const ctx = buildContext({ stack: input.stackName });
1077
+ const conn = await resolveConn(ctx);
1078
+ const { session, release } = await acquireSession(conn);
1079
+ try {
1080
+ const status = await gatewayStatus(session);
1081
+ if (!status.ok) return errText(status.error);
1082
+ return okText(trimForMcp(JSON.stringify(status.value, null, 2), ctx.stackName).content);
1083
+ } finally {
1084
+ release();
1085
+ drainPool();
1086
+ }
1087
+ }
1088
+ async function handleGatewayUpdate(input, server) {
1089
+ if (input.version.trim() === "") {
1090
+ return errText("version is required: name the concrete OpenClaw version to move to, e.g. 2026.9.2.");
1091
+ }
1092
+ const target = await resolveUpdateVersion(input.version);
1093
+ if (!target.ok) return errText(target.error);
1094
+ const version = target.value;
1095
+ const ctx = buildContext({ stack: input.stackName });
1096
+ const conn = await resolveConn(ctx);
1097
+ const { session, release } = await acquireSession(conn);
1098
+ try {
1099
+ if (!input.yes) {
1100
+ const current = await currentGatewayVersion(session).catch(() => void 0);
1101
+ const confirmation = await confirmDestructive(server, {
1102
+ message: `Update the OpenClaw gateway on stack "${ctx.stackName}" ${current ? `from ${current} ` : ""}to ${version}? Every agent on the host restarts with it. A snapshot of the state database is taken first.`,
1103
+ title: "Confirm update",
1104
+ what: `updating the gateway to ${version}`
1105
+ });
1106
+ if (!confirmation.confirmed) return okText(confirmation.reason);
1107
+ }
1108
+ const taskId = randomUUID2();
1109
+ const emit = makeProgressEmitter(server, void 0);
1110
+ startTask(taskId, `clawops_gateway_update stack=${ctx.stackName} version=${version}`);
1111
+ const notes = [];
1112
+ try {
1113
+ emit(`Updating gateway to ${version}...`);
1114
+ const result = await updateGateway(session, version, {
1115
+ onProgress: emit,
1116
+ onNote: (text4) => {
1117
+ notes.push(text4);
1118
+ emit(text4);
1119
+ }
1120
+ });
1121
+ const text3 = [result.message, ...result.ok ? [] : result.hint ? [result.hint] : [], ...notes].join("\n");
1122
+ updateTask(taskId, result.ok ? "completed" : "failed", result.ok ? text3 : void 0, result.ok ? void 0 : text3);
1123
+ const { content } = trimForMcp(text3, ctx.stackName);
1124
+ return result.ok ? okText(content) : errText(content);
1125
+ } catch (e) {
1126
+ updateTask(taskId, "failed", void 0, e instanceof Error ? e.message : String(e));
1127
+ throw e;
1128
+ }
1129
+ } finally {
1130
+ release();
1131
+ drainPool();
1132
+ }
1133
+ }
1134
+
855
1135
  // src/mcp/tools/cli/up.ts
1136
+ import { randomUUID as randomUUID3 } from "crypto";
856
1137
  async function handleUp(input, server) {
857
1138
  if (!input.dryRun) {
858
- const elicit = await server.server.elicitInput({
1139
+ const confirmation = await confirmDestructive(server, {
859
1140
  message: `Deploy stack "${input.stackName ?? "default"}" (provider: ${input.provider ?? "from config"}, instance: ${input.instanceType})? This will provision cloud resources.`,
860
- requestedSchema: {
861
- type: "object",
862
- properties: { confirmed: { type: "boolean", title: "Confirm deployment" } },
863
- required: ["confirmed"]
864
- }
1141
+ title: "Confirm deployment",
1142
+ what: "provisioning cloud resources"
865
1143
  });
866
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
867
- return okText("Deployment cancelled.");
1144
+ if (!confirmation.confirmed) {
1145
+ return okText(confirmation.reason);
868
1146
  }
869
1147
  }
870
1148
  const ctx = buildContext({ stack: input.stackName, provider: input.provider });
@@ -877,7 +1155,7 @@ async function handleUp(input, server) {
877
1155
  );
878
1156
  }
879
1157
  const { localOpts } = stackConfig;
880
- const { localBootstrap } = await import("./bootstrap-NN647RCY.js");
1158
+ const { localBootstrap } = await import("./bootstrap-32TZXVDF.js");
881
1159
  const ac = new AbortController();
882
1160
  const state = await localBootstrap({
883
1161
  host: localOpts.host,
@@ -896,12 +1174,12 @@ async function handleUp(input, server) {
896
1174
  if (!validation.ok) {
897
1175
  return errText(`Provider config invalid: ${validation.errors.join("; ")}`);
898
1176
  }
899
- const taskId = randomUUID2();
1177
+ const taskId = randomUUID3();
900
1178
  const progressToken = void 0;
901
1179
  const emit = makeProgressEmitter(server, progressToken);
902
1180
  startTask(taskId, `clawops_up stack=${ctx.stackName}`);
903
- const { generatePlan: generatePlan2 } = await import("./generate-Q2BGVMH7.js");
904
- const { applyPlan: applyPlan2 } = await import("./apply-HJ5GAKHW.js");
1181
+ const { generatePlan: generatePlan2 } = await import("./generate-4YYNW3IU.js");
1182
+ const { applyPlan: applyPlan2 } = await import("./apply-F5SQ2ZOS.js");
905
1183
  const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
906
1184
  const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
907
1185
  const network = await resolveNetworkFlags(
@@ -950,26 +1228,23 @@ async function handleUp(input, server) {
950
1228
  }
951
1229
 
952
1230
  // src/mcp/tools/cli/destroy.ts
953
- import { randomUUID as randomUUID3 } from "crypto";
1231
+ import { randomUUID as randomUUID4 } from "crypto";
954
1232
  async function handleDestroy(input, server) {
955
1233
  if (!input.yes) {
956
- const elicit = await server.server.elicitInput({
1234
+ const confirmation = await confirmDestructive(server, {
957
1235
  message: `DESTROY stack "${input.stackName}"? This permanently deletes all provisioned resources and cannot be undone.`,
958
- requestedSchema: {
959
- type: "object",
960
- properties: { confirmed: { type: "boolean", title: "Confirm destruction (irreversible)" } },
961
- required: ["confirmed"]
962
- }
1236
+ title: "Confirm destruction (irreversible)",
1237
+ what: "destroying a stack"
963
1238
  });
964
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
965
- return okText("Destruction cancelled.");
1239
+ if (!confirmation.confirmed) {
1240
+ return okText(confirmation.reason);
966
1241
  }
967
1242
  }
968
1243
  const ctx = buildContext({ stack: input.stackName });
969
1244
  if (ctx.adapter.name === "local") {
970
1245
  return errText("Local provider stacks are destroyed with `clawops down --destroy` via SSH. Use clawops_up with the local provider instead.");
971
1246
  }
972
- const taskId = randomUUID3();
1247
+ const taskId = randomUUID4();
973
1248
  const emit = makeProgressEmitter(server, void 0);
974
1249
  startTask(taskId, `clawops_destroy stack=${input.stackName}`);
975
1250
  try {
@@ -1019,7 +1294,7 @@ async function handlePlan(input, _server) {
1019
1294
  let network = resolved;
1020
1295
  if (input.privateOnly) {
1021
1296
  const { privateOnlyNetwork, assertTailnetReachable } = await import("./private-only-DIA26I2E.js");
1022
- const { probeSsh } = await import("./tailscale-cutover-6YP2QKFM.js");
1297
+ const { probeSsh } = await import("./tailscale-cutover-V42HO5UM.js");
1023
1298
  network = privateOnlyNetwork(flags, resolved, ctx.config.stacks[stackName]?.tailscale, stackName);
1024
1299
  await assertTailnetReachable(ctx, (conn) => probeSsh(conn));
1025
1300
  }
@@ -1056,30 +1331,31 @@ async function handleHarden(input, server) {
1056
1331
  "tailscale and tailscaleRevert do the opposite of each other; pass one, not both."
1057
1332
  );
1058
1333
  }
1059
- const { getConfig: getConfig2 } = await import("./store-OVB3DAJR.js");
1334
+ const { getConfig: getConfig2 } = await import("./store-YOLQYXMB.js");
1060
1335
  const config = getConfig2();
1061
- if (!config) return errText("No clawops config found. The user needs to run `clawops init` first.");
1336
+ if (!config) {
1337
+ return errText(
1338
+ "No clawops config on this machine. Call clawops_init first to register a stack; until then every clawops tool will say this."
1339
+ );
1340
+ }
1062
1341
  const ctx = buildContext({ stack: input.stackName });
1063
1342
  if (!input.dryRun && !input.yes) {
1064
1343
  const what = input.tailscaleRevert ? `Take "${ctx.stackName}" off its tailnet and go back to its public address?` : input.tailscale ? `Harden "${ctx.stackName}" and join it to your tailnet? clawops will use the tailnet address afterwards.` : `Apply hardening modules to "${ctx.stackName}"? This changes the running host.`;
1065
- const elicit = await server.server.elicitInput({
1344
+ const confirmation = await confirmDestructive(server, {
1066
1345
  message: what,
1067
- requestedSchema: {
1068
- type: "object",
1069
- properties: { confirmed: { type: "boolean", title: "Confirm" } },
1070
- required: ["confirmed"]
1071
- }
1346
+ title: "Confirm",
1347
+ what: "changing a running host"
1072
1348
  });
1073
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
1074
- return okText("Hardening cancelled. Nothing was changed.");
1349
+ if (!confirmation.confirmed) {
1350
+ return okText(confirmation.reason);
1075
1351
  }
1076
1352
  }
1077
- const { revertTailnet, cutOverToTailnet, connectionFor } = await import("./flows-RI7FGHAM.js");
1353
+ const { revertTailnet, cutOverToTailnet, connectionFor } = await import("./flows-6B2I6TRW.js");
1078
1354
  if (input.tailscaleRevert) {
1079
1355
  const outcome = await revertTailnet(ctx, config);
1080
1356
  return outcome.ok ? okText(outcome.message) : errText(outcome.reason);
1081
1357
  }
1082
- const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary, makeTailscaleModule } = await import("./harden-3KKCR7BL.js");
1358
+ const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary, makeTailscaleModule } = await import("./harden-UHAOVQMZ.js");
1083
1359
  const forStack = makeTailscaleModule(ctx.stackName);
1084
1360
  const resolved = resolveModules(MODULE_CATALOG, input.options, ctx.adapter.name).map(
1085
1361
  (m) => m.id === "tailscale" ? forStack : m
@@ -1121,9 +1397,35 @@ async function handleHarden(input, server) {
1121
1397
  return failed.length > 0 ? errText(content) : okText(content);
1122
1398
  }
1123
1399
 
1400
+ // src/mcp/tools/cli/init.ts
1401
+ async function handleInit(input, _server) {
1402
+ const { initStack } = await import("./init-HOPXDNWY.js");
1403
+ const result = await initStack({
1404
+ provider: input.provider,
1405
+ stackName: input.stackName,
1406
+ stateUrl: input.stateUrl,
1407
+ region: input.region,
1408
+ force: input.force,
1409
+ host: input.host,
1410
+ sshUser: input.sshUser,
1411
+ sshPort: input.sshPort
1412
+ });
1413
+ if (!result.ok) return errText(result.reason);
1414
+ const lines = [
1415
+ `Stack "${result.stackName}" registered for ${result.provider}.`,
1416
+ `Config: ${result.configPath}`,
1417
+ `State backend: ${result.stateUrl}`,
1418
+ ...result.region ? [`Region: ${result.region}`] : [],
1419
+ `SSH key: ${result.keyPath}${result.keyGenerated ? " (generated now)" : " (already existed)"}`,
1420
+ "",
1421
+ "Nothing has been provisioned and nothing is being charged. Deploying from here needs cloud credentials in the environment clawops runs in; clawops_doctor reports which are missing."
1422
+ ];
1423
+ return okText(lines.join("\n"));
1424
+ }
1425
+
1124
1426
  // src/mcp/tools/cli/apply.ts
1125
1427
  import { readFileSync } from "fs";
1126
- import { randomUUID as randomUUID4 } from "crypto";
1428
+ import { randomUUID as randomUUID5 } from "crypto";
1127
1429
  async function handleApply(input, server) {
1128
1430
  let plan;
1129
1431
  try {
@@ -1142,21 +1444,18 @@ ${validation.errors.join("\n")}`);
1142
1444
  }
1143
1445
  if (!input.yes) {
1144
1446
  const diffLine = plan.diff ? `${plan.diff.create.length} to create, ${plan.diff.update.length} to update, ${plan.diff.delete.length} to delete` : "diff unavailable";
1145
- const elicit = await server.server.elicitInput({
1447
+ const confirmation = await confirmDestructive(server, {
1146
1448
  message: `Apply plan for stack "${plan.spec.stackName}" (${plan.spec.provider})?
1147
1449
  Changes: ${diffLine}.
1148
1450
  This will provision cloud resources and may incur costs.`,
1149
- requestedSchema: {
1150
- type: "object",
1151
- properties: { confirmed: { type: "boolean", title: "Confirm plan apply" } },
1152
- required: ["confirmed"]
1153
- }
1451
+ title: "Confirm plan apply",
1452
+ what: "applying a plan"
1154
1453
  });
1155
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
1156
- return okText("Apply cancelled.");
1454
+ if (!confirmation.confirmed) {
1455
+ return okText(confirmation.reason);
1157
1456
  }
1158
1457
  }
1159
- const taskId = randomUUID4();
1458
+ const taskId = randomUUID5();
1160
1459
  const emit = makeProgressEmitter(server, void 0);
1161
1460
  startTask(taskId, `clawops_apply stack=${plan.spec.stackName}`);
1162
1461
  try {
@@ -1196,7 +1495,7 @@ async function handleTaskStatus(input, _server) {
1196
1495
 
1197
1496
  // src/mcp/tools/cli/monitor.ts
1198
1497
  async function handleMonitor(input, _server) {
1199
- const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-PIIKJ4L2.js");
1498
+ const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-NT4XBSUT.js");
1200
1499
  const ctx = buildContext({ stack: input.stackName });
1201
1500
  const tailLines = input.tailLines ?? 5;
1202
1501
  let conn;
@@ -1250,6 +1549,255 @@ function text2(t) {
1250
1549
  return { content: [{ type: "text", text: t }] };
1251
1550
  }
1252
1551
 
1552
+ // src/mcp/tools/cli/backup.ts
1553
+ import { randomUUID as randomUUID6 } from "crypto";
1554
+ import { mkdirSync as mkdirSync3 } from "fs";
1555
+ import path3 from "path";
1556
+ function relativePathRefusal(param, value) {
1557
+ return `"${param}" must be an absolute path; got "${value}". The MCP server does not resolve paths against a working directory. Pass the full path, e.g. /Users/me/backups/openclaw.tar.gz.`;
1558
+ }
1559
+ function capped(text3, stackName, isError = false) {
1560
+ const { content } = trimForMcp(text3, stackName);
1561
+ return isError ? errText(content) : okText(content);
1562
+ }
1563
+ async function handleBackupCreate(input, server) {
1564
+ if (input.out !== void 0 && !path3.isAbsolute(input.out)) {
1565
+ return errText(relativePathRefusal("out", input.out));
1566
+ }
1567
+ const ctx = buildContext({ stack: input.stackName });
1568
+ let outPath = input.out;
1569
+ if (outPath === void 0) {
1570
+ const dir = path3.join(getConfigDir(), "backups");
1571
+ mkdirSync3(dir, { recursive: true, mode: 448 });
1572
+ outPath = path3.join(dir, defaultBackupFilename());
1573
+ }
1574
+ const taskId = randomUUID6();
1575
+ const emit = makeProgressEmitter(server, void 0);
1576
+ startTask(taskId, `clawops_backup_create stack=${ctx.stackName}`);
1577
+ const ac = new AbortController();
1578
+ const conn = await resolveConn(ctx);
1579
+ const { session, release } = await acquireSession({ ...conn, signal: ac.signal });
1580
+ try {
1581
+ const created = await createBackup(session, {
1582
+ outPath,
1583
+ signal: ac.signal,
1584
+ onPhase: (phase) => emit(phase === "creating" ? "Creating backup on remote host..." : `Downloading to ${outPath}...`)
1585
+ });
1586
+ if (!created.ok) {
1587
+ updateTask(taskId, "failed", void 0, created.reason);
1588
+ return capped(created.reason, ctx.stackName, true);
1589
+ }
1590
+ const summary = [`Backup saved to ${created.path}`, ...ARCHIVE_IS_A_CREDENTIAL].join("\n");
1591
+ updateTask(taskId, "completed", summary);
1592
+ return capped(summary, ctx.stackName);
1593
+ } catch (err) {
1594
+ const msg = err instanceof Error ? err.message : String(err);
1595
+ updateTask(taskId, "failed", void 0, msg);
1596
+ return errText(`Backup failed: ${msg}`);
1597
+ } finally {
1598
+ release();
1599
+ drainPool();
1600
+ }
1601
+ }
1602
+ async function handleBackupRestore(input, server) {
1603
+ if (!path3.isAbsolute(input.file)) {
1604
+ return errText(relativePathRefusal("file", input.file));
1605
+ }
1606
+ const activate = input.activate === true;
1607
+ const stackLabel = input.stackName ?? "default";
1608
+ if (!input.yes) {
1609
+ const confirmation = await confirmDestructive(server, activate ? {
1610
+ message: `ACTIVATE the backup ${input.file} on stack "${stackLabel}"? The gateway is stopped and the restored state put into service. Everything since the backup is lost from the live gateway (the state it replaces is kept alongside it), and channel credentials may need relinking. clawops puts the previous state back if the gateway does not come up.`,
1611
+ title: "Confirm restore and activation",
1612
+ what: "activating a restored backup"
1613
+ } : {
1614
+ message: `Expand the backup ${input.file} onto stack "${stackLabel}", staging only? It is verified and expanded beside the live state; the gateway and its live state are not touched.`,
1615
+ title: "Confirm staging-only restore",
1616
+ what: "restoring a backup to a staging directory"
1617
+ });
1618
+ if (!confirmation.confirmed) return okText(confirmation.reason);
1619
+ }
1620
+ const ctx = buildContext({ stack: input.stackName });
1621
+ const taskId = randomUUID6();
1622
+ const emit = makeProgressEmitter(server, void 0);
1623
+ startTask(taskId, `clawops_backup_restore stack=${ctx.stackName}${activate ? " activate" : ""}`);
1624
+ const fail = (text3) => {
1625
+ updateTask(taskId, "failed", void 0, text3);
1626
+ return capped(text3, ctx.stackName, true);
1627
+ };
1628
+ const ac = new AbortController();
1629
+ const conn = await resolveConn(ctx);
1630
+ const { session, release } = await acquireSession({ ...conn, signal: ac.signal });
1631
+ try {
1632
+ const staged = await stageRestore(session, {
1633
+ file: input.file,
1634
+ signal: ac.signal,
1635
+ onPhase: (phase) => emit(phase === "uploading" ? "Uploading archive..." : "Verifying and restoring to a staging directory...")
1636
+ });
1637
+ const raw = staged.unparsedReport !== void 0 ? [`OpenClaw reported: ${staged.unparsedReport}`] : [];
1638
+ if (!staged.ok) {
1639
+ return fail([
1640
+ staged.reason,
1641
+ ...staged.stagingOnHost ? [nothingChanged(staged.stagingOnHost)] : [],
1642
+ ...raw
1643
+ ].join("\n"));
1644
+ }
1645
+ const lines = [
1646
+ `Archive verified and expanded to ${staged.stagingOnHost} on the host.`,
1647
+ ...staged.entryCount !== void 0 ? [`${staged.entryCount} entries restored.`] : [],
1648
+ ...raw,
1649
+ // Verbatim: rolled-back approvals and channel credentials are the operator's to judge.
1650
+ ...staged.warnings.map((w) => `Warning: ${w}`)
1651
+ ];
1652
+ if (!activate) {
1653
+ lines.push(
1654
+ "",
1655
+ "Nothing has been activated. Call clawops_backup_restore again with activate: true to put",
1656
+ "this state into service. The state to adopt is",
1657
+ ` ${staged.statePath}`,
1658
+ `(the archive holds ${staged.stateDirInArchive} under payload/posix), replacing ${staged.stateDir}.`,
1659
+ ...PLUGINS_NOT_CARRIED
1660
+ );
1661
+ const text4 = lines.join("\n");
1662
+ updateTask(taskId, "completed", text4);
1663
+ return capped(text4, ctx.stackName);
1664
+ }
1665
+ const outcome = await activateStaged(session, staged, {
1666
+ signal: ac.signal,
1667
+ onPhase: (phase) => {
1668
+ if (phase === "activating") emit("Activating the restored state...");
1669
+ else if (phase === "restarting") emit("Restarting the gateway...");
1670
+ else if (phase === "waiting") emit("Waiting for the gateway to answer...");
1671
+ }
1672
+ });
1673
+ if (!outcome.ok) {
1674
+ return fail([...lines, "", outcome.reason, ...activationFailureDetail(outcome)].join("\n"));
1675
+ }
1676
+ lines.push("", ACTIVATED, ...activatedLines(outcome.preserved));
1677
+ const text3 = lines.join("\n");
1678
+ updateTask(taskId, "completed", text3);
1679
+ return capped(text3, ctx.stackName);
1680
+ } catch (err) {
1681
+ return fail(`Restore failed: ${err instanceof Error ? err.message : String(err)}`);
1682
+ } finally {
1683
+ release();
1684
+ drainPool();
1685
+ }
1686
+ }
1687
+
1688
+ // src/mcp/tools/cli/migrate.ts
1689
+ import { randomUUID as randomUUID7 } from "crypto";
1690
+ var SYNC_WINDOW = { ms: 1e4 };
1691
+ async function handleMigrate(input, server) {
1692
+ let target;
1693
+ try {
1694
+ target = await prepareMigration({ stack: input.stackName, openclawVersion: input.openclawVersion });
1695
+ } catch (err) {
1696
+ if (err instanceof UsageError) return errText(err.message);
1697
+ throw err;
1698
+ }
1699
+ if (!input.yes) {
1700
+ const confirmation = await confirmDestructive(server, {
1701
+ message: migrationQuestion(target),
1702
+ title: `Confirm migrating "${target.stackName}" to ${target.version}`,
1703
+ what: `migrating stack "${target.stackName}"`
1704
+ });
1705
+ if (!confirmation.confirmed) return okText(confirmation.reason);
1706
+ }
1707
+ const taskId = randomUUID7();
1708
+ const emit = makeProgressEmitter(server, void 0);
1709
+ startTask(taskId, `clawops_migrate stack=${target.stackName} target=${target.version}`);
1710
+ const run = runMigration(target, {
1711
+ onStep: (text3) => {
1712
+ emit(text3);
1713
+ updateTask(taskId, "running", text3);
1714
+ }
1715
+ }).then(
1716
+ (result) => {
1717
+ const text3 = render(result, target.stackName);
1718
+ updateTask(taskId, result.exitCode === 0 ? "completed" : "failed", text3, result.exitCode === 0 ? void 0 : text3);
1719
+ return { kind: "done", result, text: text3 };
1720
+ },
1721
+ (err) => {
1722
+ const msg = err instanceof Error ? err.message : String(err);
1723
+ updateTask(taskId, "failed", void 0, msg);
1724
+ return { kind: "error", err, msg };
1725
+ }
1726
+ );
1727
+ let timer;
1728
+ const timeout = new Promise((resolve) => {
1729
+ timer = setTimeout(() => resolve({ kind: "pending" }), SYNC_WINDOW.ms);
1730
+ });
1731
+ const first = await Promise.race([run, timeout]);
1732
+ clearTimeout(timer);
1733
+ if (first.kind === "pending") {
1734
+ return okText(
1735
+ JSON.stringify(
1736
+ {
1737
+ taskId,
1738
+ status: "running",
1739
+ stack: target.stackName,
1740
+ target: target.version,
1741
+ message: "The migration is still running on the host. Poll clawops_task_status with this taskId for the outcome; do not start another migration of this stack meanwhile."
1742
+ },
1743
+ null,
1744
+ 2
1745
+ )
1746
+ );
1747
+ }
1748
+ if (first.kind === "error") {
1749
+ if (first.err instanceof UsageError) return errText(first.msg);
1750
+ throw first.err;
1751
+ }
1752
+ return first.result.exitCode === 0 ? okText(first.text) : errText(first.text);
1753
+ }
1754
+ function render(result, stackName) {
1755
+ const text3 = result.lines.map((l) => l.text).join("\n");
1756
+ return trimForMcp(text3, stackName).content;
1757
+ }
1758
+
1759
+ // src/mcp/tools/cli/secret.ts
1760
+ var RESOURCE_NAME = "secrets";
1761
+ function capped2(payload) {
1762
+ const { content } = trimForMcp(JSON.stringify(payload, null, 2), RESOURCE_NAME);
1763
+ return okText(content);
1764
+ }
1765
+ async function handleSecretList(_input, _server) {
1766
+ const secrets = listSecrets();
1767
+ if (secrets.length === 0) {
1768
+ return capped2({ secrets, message: `${NO_SECRETS_MESSAGE} ${SET_SECRET_HINT}` });
1769
+ }
1770
+ return capped2({ secrets });
1771
+ }
1772
+ async function handleSecretAudit(_input, _server) {
1773
+ const report = auditSecrets();
1774
+ return capped2({
1775
+ ...report,
1776
+ message: report.ok ? AUDIT_CLEAN_MESSAGE : AUDIT_FIX_HINT
1777
+ });
1778
+ }
1779
+ async function handleSecretDelete(input, server) {
1780
+ const prepared = prepareSecretDelete(input.name);
1781
+ if (!prepared.ok) return errText(prepared.reason);
1782
+ if (!input.yes) {
1783
+ const confirmation = await confirmDestructive(server, {
1784
+ message: [deleteConfirmQuestion(prepared.name), ...prepared.warnings].join("\n"),
1785
+ title: `Delete secret "${prepared.name}"`,
1786
+ what: `deleting secret "${prepared.name}"`
1787
+ });
1788
+ if (!confirmation.confirmed) {
1789
+ return okText([confirmation.reason, ...prepared.warnings].join("\n"));
1790
+ }
1791
+ }
1792
+ const message = deletePreparedSecret(prepared);
1793
+ return capped2({
1794
+ deleted: prepared.name,
1795
+ message,
1796
+ referencingStacks: prepared.referencingStacks,
1797
+ warnings: prepared.warnings
1798
+ });
1799
+ }
1800
+
1253
1801
  // src/mcp/tools/workflow/deploy_app.ts
1254
1802
  async function handleWorkflowDeployApp(input, server) {
1255
1803
  const parts = [`## Deployment Workflow \u2014 ${input.provider} / ${input.stackName}
@@ -1282,17 +1830,14 @@ Falling back to direct deployment.`);
1282
1830
  );
1283
1831
  }
1284
1832
  const diffLine = plan.diff ? `${plan.diff.create.length} to create, ${plan.diff.update.length} to update, ${plan.diff.delete.length} to delete` : "? changes";
1285
- const elicit = await server.server.elicitInput({
1833
+ const confirmation = await confirmDestructive(server, {
1286
1834
  message: `Deploy OpenClaw on ${provider} (stack: "${input.stackName}", instance: ${input.instanceType}${input.region ? `, region: ${input.region}` : ""})?
1287
1835
  Changes: ${diffLine}. This will provision cloud resources and may incur costs.`,
1288
- requestedSchema: {
1289
- type: "object",
1290
- properties: { confirmed: { type: "boolean", title: "Confirm end-to-end deployment" } },
1291
- required: ["confirmed"]
1292
- }
1836
+ title: "Confirm end-to-end deployment",
1837
+ what: "provisioning cloud resources"
1293
1838
  });
1294
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
1295
- return okText("Deployment workflow cancelled.");
1839
+ if (!confirmation.confirmed) {
1840
+ return okText(confirmation.reason);
1296
1841
  }
1297
1842
  parts.push("### Step 2: Apply");
1298
1843
  const emit = makeProgressEmitter(server, void 0);
@@ -1371,11 +1916,22 @@ var TOOL_REGISTRY = {
1371
1916
  clawops_destroy: makeEntry(clawops_destroySchema, clawops_destroyAnnotations, handleDestroy, clawops_destroyDescription),
1372
1917
  clawops_apply: makeEntry(clawops_applySchema, clawops_applyAnnotations, handleApply, clawops_applyDescription),
1373
1918
  clawops_plan: makeEntry(clawops_planSchema, clawops_planAnnotations, handlePlan, clawops_planDescription),
1919
+ clawops_init: makeEntry(clawops_initSchema, clawops_initAnnotations, handleInit, clawops_initDescription),
1374
1920
  clawops_harden: makeEntry(clawops_hardenSchema, clawops_hardenAnnotations, handleHarden, clawops_hardenDescription),
1375
1921
  clawops_config_set: makeEntry(clawops_config_setSchema, clawops_config_setAnnotations, handleConfigSet, clawops_config_setDescription),
1376
1922
  clawops_config_unset: makeEntry(clawops_config_unsetSchema, clawops_config_unsetAnnotations, handleConfigUnset, clawops_config_unsetDescription),
1377
1923
  clawops_config_validate: makeEntry(clawops_config_validateSchema, clawops_config_validateAnnotations, handleConfigValidate, clawops_config_validateDescription),
1378
1924
  clawops_gateway_restart: makeEntry(clawops_gateway_restartSchema, clawops_gateway_restartAnnotations, handleGatewayRestart, clawops_gateway_restartDescription),
1925
+ clawops_agents_logs: makeEntry(clawops_agents_logsSchema, clawops_agents_logsAnnotations, handleAgentsLogs, clawops_agents_logsDescription),
1926
+ clawops_gateway_status: makeEntry(clawops_gateway_statusSchema, clawops_gateway_statusAnnotations, handleGatewayStatus, clawops_gateway_statusDescription),
1927
+ clawops_secret_list: makeEntry(clawops_secret_listSchema, clawops_secret_listAnnotations, handleSecretList, clawops_secret_listDescription),
1928
+ clawops_secret_audit: makeEntry(clawops_secret_auditSchema, clawops_secret_auditAnnotations, handleSecretAudit, clawops_secret_auditDescription),
1929
+ clawops_gateway_update: makeEntry(clawops_gateway_updateSchema, clawops_gateway_updateAnnotations, handleGatewayUpdate, clawops_gateway_updateDescription),
1930
+ clawops_backup_create: makeEntry(clawops_backup_createSchema, clawops_backup_createAnnotations, handleBackupCreate, clawops_backup_createDescription),
1931
+ clawops_backup_restore: makeEntry(clawops_backup_restoreSchema, clawops_backup_restoreAnnotations, handleBackupRestore, clawops_backup_restoreDescription),
1932
+ clawops_migrate: makeEntry(clawops_migrateSchema, clawops_migrateAnnotations, handleMigrate, clawops_migrateDescription),
1933
+ clawops_stacks_delete: makeEntry(clawops_stacks_deleteSchema, clawops_stacks_deleteAnnotations, handleStacksDelete, clawops_stacks_deleteDescription),
1934
+ clawops_secret_delete: makeEntry(clawops_secret_deleteSchema, clawops_secret_deleteAnnotations, handleSecretDelete, clawops_secret_deleteDescription),
1379
1935
  clawops_workflow_deploy_app: makeEntry(clawops_workflow_deploy_appSchema, clawops_workflow_deploy_appAnnotations, handleWorkflowDeployApp, clawops_workflow_deploy_appDescription),
1380
1936
  clawops_workflow_recover: makeEntry(clawops_workflow_recoverSchema, clawops_workflow_recoverAnnotations, handleWorkflowRecover, clawops_workflow_recoverDescription),
1381
1937
  clawops_task_status: makeEntry(clawops_task_statusSchema, clawops_task_statusAnnotations, handleTaskStatus, clawops_task_statusDescription)
@@ -1427,7 +1983,7 @@ function registerTools(server, opts) {
1427
1983
 
1428
1984
  // src/mcp/resources.ts
1429
1985
  import { readFileSync as readFileSync2 } from "fs";
1430
- import path3 from "path";
1986
+ import path4 from "path";
1431
1987
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
1432
1988
  function registerResources(server) {
1433
1989
  server.registerResource(
@@ -1479,7 +2035,7 @@ function registerResources(server) {
1479
2035
  { description: "Full Pulumi output from the last up/destroy run for a stack" },
1480
2036
  async (uri, { name }) => {
1481
2037
  const stackName = Array.isArray(name) ? name[0] : name;
1482
- const filePath = path3.join(getConfigDir(), "state", `${stackName}.last-run.json`);
2038
+ const filePath = path4.join(getConfigDir(), "state", `${stackName}.last-run.json`);
1483
2039
  let text3;
1484
2040
  try {
1485
2041
  const raw = readFileSync2(filePath, "utf-8");
@@ -1676,7 +2232,7 @@ function bearerFrom(header) {
1676
2232
  return match?.[1]?.trim();
1677
2233
  }
1678
2234
  async function serveMcp(opts) {
1679
- const { version } = await import("./package-JFVGCNKU.js");
2235
+ const { version } = await import("./package-HYGHRX35.js");
1680
2236
  const makeServer = () => {
1681
2237
  const server = new McpServer({ name: "clawops", version });
1682
2238
  registerTools(server, opts);
@@ -1744,7 +2300,7 @@ async function handleHttp(req, res, sessions, makeServer, token) {
1744
2300
  return;
1745
2301
  }
1746
2302
  const transport = new StreamableHTTPServerTransport({
1747
- sessionIdGenerator: () => randomUUID5(),
2303
+ sessionIdGenerator: () => randomUUID8(),
1748
2304
  onsessioninitialized: (id) => {
1749
2305
  sessions.set(id, transport);
1750
2306
  }