@clawops/cli 2.1.2 → 2.1.3

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 (30) hide show
  1. package/README.md +2 -0
  2. package/dist/{apply-HJ5GAKHW.js → apply-JARPVA5Y.js} +8 -8
  3. package/dist/{automation-MKCJ46I6.js → automation-NBYQ6MFZ.js} +1 -1
  4. package/dist/{bootstrap-NN647RCY.js → bootstrap-3PBVY7MA.js} +2 -2
  5. package/dist/{chunk-X56CQ6GB.js → chunk-4MHDW2NB.js} +1 -1
  6. package/dist/{chunk-3Z5TDXCJ.js → chunk-55UMO37O.js} +1 -1
  7. package/dist/chunk-755X5OGS.js +213 -0
  8. package/dist/{chunk-R3DTA35B.js → chunk-7T4CIT7C.js} +6 -6
  9. package/dist/{chunk-QDNFADJI.js → chunk-AVERNLUY.js} +6 -6
  10. package/dist/{chunk-GFUK6KDR.js → chunk-CPCO6UJK.js} +9 -9
  11. package/dist/{chunk-64PONSA5.js → chunk-GFIR2IIJ.js} +3 -1
  12. package/dist/{chunk-IQQTB7ER.js → chunk-PTCVFLZ3.js} +4 -4
  13. package/dist/{chunk-ELU3XQEA.js → chunk-RDUKRIRC.js} +1 -1
  14. package/dist/{chunk-T5XCMYAV.js → chunk-TYHTISI5.js} +187 -137
  15. package/dist/cli.js +127 -274
  16. package/dist/{context-7VI3R6EK.js → context-WOKSKAIA.js} +5 -5
  17. package/dist/{flows-RI7FGHAM.js → flows-HGJIA6EL.js} +7 -7
  18. package/dist/{generate-Q2BGVMH7.js → generate-NE54BATS.js} +6 -6
  19. package/dist/{harden-3KKCR7BL.js → harden-RUOUA5BV.js} +2 -2
  20. package/dist/init-HOPXDNWY.js +13 -0
  21. package/dist/{mcp-wire-FQOMM7MK.js → mcp-wire-FIO5DFBY.js} +11 -11
  22. package/dist/{package-JFVGCNKU.js → package-MF5LPITL.js} +1 -1
  23. package/dist/{server-B7I6HBQH.js → server-OXWHBTXK.js} +11 -11
  24. package/dist/{stack-config-LUTS2XAD.js → stack-config-ICQ5YHUL.js} +2 -2
  25. package/dist/{state-AOAYLYY5.js → state-AKZP2S4T.js} +2 -2
  26. package/dist/{store-OVB3DAJR.js → store-YOLQYXMB.js} +1 -1
  27. package/dist/{tailscale-cutover-6YP2QKFM.js → tailscale-cutover-EKAQSDXP.js} +2 -2
  28. package/package.json +1 -1
  29. package/spec/mcp-tools.yaml +69 -28
  30. package/dist/{chunk-BELTQGHT.js → chunk-CHNSL3TC.js} +3 -3
@@ -1,7 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  applyPlan
4
- } from "./chunk-R3DTA35B.js";
4
+ } from "./chunk-7T4CIT7C.js";
5
+ import {
6
+ generatePlan
7
+ } from "./chunk-PTCVFLZ3.js";
5
8
  import {
6
9
  extractBaseOutputs
7
10
  } from "./chunk-3QJBNAHW.js";
@@ -10,24 +13,21 @@ import {
10
13
  readRemoteConfig,
11
14
  restartGateway
12
15
  } from "./chunk-2I7KCMXZ.js";
13
- import {
14
- generatePlan
15
- } from "./chunk-IQQTB7ER.js";
16
16
  import {
17
17
  validatePlan
18
18
  } from "./chunk-XFN3YDZE.js";
19
+ import {
20
+ buildContext
21
+ } from "./chunk-AVERNLUY.js";
19
22
  import {
20
23
  acquireSession,
21
24
  drainPool
22
25
  } from "./chunk-2Z7J3ERQ.js";
23
- import {
24
- buildContext
25
- } from "./chunk-QDNFADJI.js";
26
26
  import {
27
27
  formatUptime,
28
28
  gatherSnapshot,
29
29
  runDiagnostics
30
- } from "./chunk-GFUK6KDR.js";
30
+ } from "./chunk-CPCO6UJK.js";
31
31
  import {
32
32
  GATEWAY_LOGS_PROBE,
33
33
  chooseLogSource,
@@ -48,7 +48,7 @@ import {
48
48
  import {
49
49
  getConfig,
50
50
  getConfigDir
51
- } from "./chunk-64PONSA5.js";
51
+ } from "./chunk-GFIR2IIJ.js";
52
52
  import {
53
53
  StateError,
54
54
  UsageError
@@ -141,7 +141,7 @@ function withAudit(toolName, handler) {
141
141
  // src/mcp/tools/_generated.ts
142
142
  import { z } from "zod";
143
143
  var clawops_statusSchema = z.object({
144
- stackName: z.string().optional()
144
+ stackName: z.string().optional().describe("Stack name. Defaults to active stack from config.")
145
145
  });
146
146
  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
147
  var clawops_statusAnnotations = {
@@ -153,8 +153,8 @@ var clawops_statusAnnotations = {
153
153
  toolsets: ["cli", "read"]
154
154
  };
155
155
  var clawops_doctorSchema = z.object({
156
- stackName: z.string().optional(),
157
- failuresOnly: z.boolean().optional().default(false)
156
+ 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"),
157
+ failuresOnly: z.boolean().optional().default(false).describe("Return only failing and warning checks. Passing checks are counted, not listed.")
158
158
  });
159
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.";
160
160
  var clawops_doctorAnnotations = {
@@ -166,9 +166,9 @@ var clawops_doctorAnnotations = {
166
166
  toolsets: ["cli", "read"]
167
167
  };
168
168
  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()
169
+ stackName: z.string().optional().describe("Which stack's gateway to read. Omitted = the default stack in ~/.clawops/config.json"),
170
+ 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"),
171
+ sinceMin: z.number().int().optional().describe("Lines since N minutes ago")
172
172
  });
173
173
  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
174
  var clawops_logs_tailAnnotations = {
@@ -180,8 +180,8 @@ var clawops_logs_tailAnnotations = {
180
180
  toolsets: ["cli", "read"]
181
181
  };
182
182
  var clawops_monitorSchema = z.object({
183
- stackName: z.string().optional(),
184
- tailLines: z.number().int().optional().default(5)
183
+ stackName: z.string().optional().describe("Stack name. Defaults to active stack."),
184
+ tailLines: z.number().int().optional().default(5).describe("Log lines to include in snapshot.")
185
185
  });
186
186
  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
187
  var clawops_monitorAnnotations = {
@@ -203,8 +203,8 @@ var clawops_stacks_listAnnotations = {
203
203
  toolsets: ["admin", "read"]
204
204
  };
205
205
  var clawops_config_getSchema = z.object({
206
- stackName: z.string().optional(),
207
- key: z.string().optional()
206
+ stackName: z.string().optional().describe("Which stack's gateway config to read. Omitted = the default stack in ~/.clawops/config.json"),
207
+ key: z.string().optional().describe("Dot-path config key, e.g., gateway.auth.mode. Omit to dump the full config.")
208
208
  });
209
209
  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
210
  var clawops_config_getAnnotations = {
@@ -216,7 +216,7 @@ var clawops_config_getAnnotations = {
216
216
  toolsets: ["cli", "read"]
217
217
  };
218
218
  var clawops_agents_listSchema = z.object({
219
- stackName: z.string().optional()
219
+ stackName: z.string().optional().describe("Which stack's agents to list. Omitted = the default stack in ~/.clawops/config.json")
220
220
  });
221
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>`.";
222
222
  var clawops_agents_listAnnotations = {
@@ -228,15 +228,15 @@ var clawops_agents_listAnnotations = {
228
228
  toolsets: ["cli", "read"]
229
229
  };
230
230
  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)
231
+ 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"),
232
+ provider: z.enum(["aws", "gcp", "azure", "local"]).optional().describe("Defaults to provider configured for this stack"),
233
+ 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"),
234
+ 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"),
235
+ 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"),
236
+ gatewayCidr: z.string().optional().describe("CIDR(s) allowed to reach the gateway port. Requires publishGateway=all."),
237
+ publishGateway: z.enum(["loopback", "all"]).optional().describe("Which interface the gateway binds. 'all' serves plaintext HTTP."),
238
+ openclawVersion: z.string().optional().describe("semver or 'stable'/'dev'"),
239
+ 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
240
  });
241
241
  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
242
  var clawops_upAnnotations = {
@@ -248,8 +248,8 @@ var clawops_upAnnotations = {
248
248
  toolsets: ["cli"]
249
249
  };
250
250
  var clawops_destroySchema = z.object({
251
- stackName: z.string(),
252
- yes: z.boolean().optional().default(false)
251
+ stackName: z.string().describe("The stack to destroy. Required, and worth repeating back to the user before calling: this deletes every resource it provisioned"),
252
+ yes: z.boolean().optional().default(false).describe("Skip elicitation; CI/scripted use only")
253
253
  });
254
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.";
255
255
  var clawops_destroyAnnotations = {
@@ -261,8 +261,8 @@ var clawops_destroyAnnotations = {
261
261
  toolsets: ["cli"]
262
262
  };
263
263
  var clawops_applySchema = z.object({
264
- planPath: z.string(),
265
- yes: z.boolean().optional().default(false)
264
+ planPath: z.string().describe("Absolute path to plan JSON (R7)"),
265
+ yes: z.boolean().optional().default(false).describe("Skip the confirmation and apply immediately. Only when the user has already approved this specific plan")
266
266
  });
267
267
  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
268
  var clawops_applyAnnotations = {
@@ -274,16 +274,16 @@ var clawops_applyAnnotations = {
274
274
  toolsets: ["cli"]
275
275
  };
276
276
  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()
277
+ stackName: z.string().optional().describe("Which stack the plan is for. Omitted = the default stack in ~/.clawops/config.json"),
278
+ 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"),
279
+ 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"),
280
+ 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"),
281
+ openclawVersion: z.string().optional().describe("semver, or 'stable'/'dev'"),
282
+ 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"),
283
+ gatewayCidr: z.string().optional().describe("CIDR(s) allowed to reach the gateway port, or 'auto'. Requires publishGateway=all"),
284
+ publishGateway: z.enum(["loopback", "all"]).optional().describe("Which interface the gateway binds. loopback (default) keeps it off the network"),
285
+ 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"),
286
+ outPath: z.string().optional().describe("Absolute path to write plan; if omitted, plan returned inline")
287
287
  });
288
288
  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
289
  var clawops_planAnnotations = {
@@ -295,10 +295,10 @@ var clawops_planAnnotations = {
295
295
  toolsets: ["cli", "read"]
296
296
  };
297
297
  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)
298
+ stackName: z.string().optional().describe("Which stack's gateway config to write. Omitted = the default stack in ~/.clawops/config.json"),
299
+ key: z.string().describe("Dotted path into the gateway config, e.g. models.provider or channels.slack.enabled"),
300
+ value: z.string().describe("Stringified; JSON for complex values"),
301
+ 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
302
  });
303
303
  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
304
  var clawops_config_setAnnotations = {
@@ -310,9 +310,9 @@ var clawops_config_setAnnotations = {
310
310
  toolsets: ["cli"]
311
311
  };
312
312
  var clawops_config_unsetSchema = z.object({
313
- stackName: z.string().optional(),
314
- key: z.string(),
315
- restart: z.boolean().optional().default(false)
313
+ stackName: z.string().optional().describe("Which stack's gateway config to write. Omitted = the default stack in ~/.clawops/config.json"),
314
+ key: z.string().describe("Dot-path config key to remove"),
315
+ 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
316
  });
317
317
  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
318
  var clawops_config_unsetAnnotations = {
@@ -324,7 +324,7 @@ var clawops_config_unsetAnnotations = {
324
324
  toolsets: ["cli"]
325
325
  };
326
326
  var clawops_config_validateSchema = z.object({
327
- stackName: z.string().optional()
327
+ stackName: z.string().optional().describe("Which stack's deployed config to validate. Omitted = the default stack in ~/.clawops/config.json")
328
328
  });
329
329
  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
330
  var clawops_config_validateAnnotations = {
@@ -336,7 +336,7 @@ var clawops_config_validateAnnotations = {
336
336
  toolsets: ["cli", "read"]
337
337
  };
338
338
  var clawops_gateway_restartSchema = z.object({
339
- stackName: z.string().optional()
339
+ stackName: z.string().optional().describe("Which stack's gateway to restart. Omitted = the default stack in ~/.clawops/config.json")
340
340
  });
341
341
  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
342
  var clawops_gateway_restartAnnotations = {
@@ -347,13 +347,32 @@ var clawops_gateway_restartAnnotations = {
347
347
  openWorldHint: true,
348
348
  toolsets: ["cli"]
349
349
  };
350
+ var clawops_initSchema = z.object({
351
+ provider: z.enum(["aws", "gcp", "azure", "local"]).describe("Which cloud this stack deploys to, or 'local' for a machine you already have"),
352
+ stackName: z.string().optional().describe('Name for the stack, used by every later call. Omitted = "default"'),
353
+ 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"),
354
+ region: z.string().optional().describe("Cloud region in the provider's own spelling. Omitted = us-east-1 (aws), us-central1 (gcp), eastus (azure)"),
355
+ host: z.string().optional().describe("[local only] Hostname or IP of the machine to manage. Required when provider is local"),
356
+ sshUser: z.string().optional().describe("[local only] SSH login user. Omitted = root"),
357
+ sshPort: z.number().int().optional().describe("[local only] SSH port. Omitted = 22"),
358
+ 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")
359
+ });
360
+ 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).";
361
+ var clawops_initAnnotations = {
362
+ title: "Initialise clawops Config",
363
+ readOnlyHint: false,
364
+ destructiveHint: false,
365
+ idempotentHint: false,
366
+ openWorldHint: false,
367
+ toolsets: ["cli"]
368
+ };
350
369
  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)
370
+ stackName: z.string().optional().describe("Which stack to harden. Omitted = the default stack in ~/.clawops/config.json"),
371
+ options: z.string().optional().describe("Comma-separated module IDs; default is every defaultOn module for the provider"),
372
+ dryRun: z.boolean().optional().default(false).describe("Report the current state, change nothing"),
373
+ tailscale: z.boolean().optional().default(false).describe("Join the tailnet, verify this machine reaches the host there, then use that address"),
374
+ tailscaleRevert: z.boolean().optional().default(false).describe("Leave the tailnet and go back to the public address"),
375
+ yes: z.boolean().optional().default(false).describe("Skip elicitation; CI/scripted use only")
357
376
  });
358
377
  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
378
  var clawops_hardenAnnotations = {
@@ -365,10 +384,10 @@ var clawops_hardenAnnotations = {
365
384
  toolsets: ["cli"]
366
385
  };
367
386
  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")
387
+ provider: z.enum(["aws", "gcp", "azure", "local"]).describe("Cloud to deploy to. Omitted = the default provider in ~/.clawops/config.json"),
388
+ region: z.string().optional().describe("Cloud region, in the provider's own spelling (us-east-1, us-central1, eastus). Omitted = the provider's default"),
389
+ stackName: z.string().optional().default("default").describe("Name for the new stack. Omitted = the default stack name in ~/.clawops/config.json"),
390
+ 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
391
  });
373
392
  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
393
  var clawops_workflow_deploy_appAnnotations = {
@@ -380,7 +399,7 @@ var clawops_workflow_deploy_appAnnotations = {
380
399
  toolsets: ["workflow"]
381
400
  };
382
401
  var clawops_workflow_recoverSchema = z.object({
383
- stackName: z.string().optional()
402
+ stackName: z.string().optional().describe("Which stack to diagnose. Omitted = the default stack in ~/.clawops/config.json")
384
403
  });
385
404
  var clawops_workflow_recoverDescription = `Diagnostic workflow for an unhealthy stack. Internally: status check \u2192
386
405
  gateway logs \u2192 agent logs \u2192 systemd service status \u2192 produces a structured
@@ -400,7 +419,7 @@ var clawops_workflow_recoverAnnotations = {
400
419
  toolsets: ["workflow", "read"]
401
420
  };
402
421
  var clawops_task_statusSchema = z.object({
403
- taskId: z.string()
422
+ taskId: z.string().describe("The taskId returned by a long-running tool such as clawops_up, clawops_apply or clawops_destroy")
404
423
  });
405
424
  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
425
  var clawops_task_statusAnnotations = {
@@ -427,6 +446,7 @@ var TOOLSETS = {
427
446
  "clawops_config_unset",
428
447
  "clawops_config_validate",
429
448
  "clawops_gateway_restart",
449
+ "clawops_init",
430
450
  "clawops_harden",
431
451
  "clawops_task_status"
432
452
  ],
@@ -602,6 +622,29 @@ async function handleStacksList(_input, _server) {
602
622
  return { content: [{ type: "text", text: JSON.stringify({ stacks }, null, 2) }] };
603
623
  }
604
624
 
625
+ // src/mcp/tools/_confirm.ts
626
+ async function confirmDestructive(server, opts) {
627
+ const capabilities = server.server.getClientCapabilities();
628
+ if (!capabilities?.elicitation) {
629
+ return {
630
+ confirmed: false,
631
+ 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.)`
632
+ };
633
+ }
634
+ const elicit = await server.server.elicitInput({
635
+ message: opts.message,
636
+ requestedSchema: {
637
+ type: "object",
638
+ properties: { confirmed: { type: "boolean", title: opts.title } },
639
+ required: ["confirmed"]
640
+ }
641
+ });
642
+ if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
643
+ return { confirmed: false, reason: `Cancelled \u2014 ${opts.what} did not run. Nothing was changed.` };
644
+ }
645
+ return { confirmed: true };
646
+ }
647
+
605
648
  // src/mcp/tools/cli/config.ts
606
649
  async function handleConfigGet(input, _server) {
607
650
  const ac = new AbortController();
@@ -623,16 +666,13 @@ async function handleConfigGet(input, _server) {
623
666
  }
624
667
  }
625
668
  async function handleConfigSet(input, server) {
626
- const elicit = await server.server.elicitInput({
669
+ const confirmation = await confirmDestructive(server, {
627
670
  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
- }
671
+ title: "Confirm config change",
672
+ what: "changing gateway configuration"
633
673
  });
634
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
635
- return okText("Config change cancelled.");
674
+ if (!confirmation.confirmed) {
675
+ return okText(confirmation.reason);
636
676
  }
637
677
  const ac = new AbortController();
638
678
  const ctx = buildContext({ stack: input.stackName });
@@ -672,16 +712,13 @@ async function handleConfigSet(input, server) {
672
712
  }
673
713
  }
674
714
  async function handleConfigUnset(input, server) {
675
- const elicit = await server.server.elicitInput({
715
+ const confirmation = await confirmDestructive(server, {
676
716
  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
- }
717
+ title: "Confirm key removal",
718
+ what: "changing gateway configuration"
682
719
  });
683
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
684
- return okText("Config unset cancelled.");
720
+ if (!confirmation.confirmed) {
721
+ return okText(confirmation.reason);
685
722
  }
686
723
  const ac = new AbortController();
687
724
  const ctx = buildContext({ stack: input.stackName });
@@ -786,16 +823,13 @@ async function handleAgentsList(input, _server) {
786
823
 
787
824
  // src/mcp/tools/cli/gateway.ts
788
825
  async function handleGatewayRestart(input, server) {
789
- const elicit = await server.server.elicitInput({
826
+ const confirmation = await confirmDestructive(server, {
790
827
  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
- }
828
+ title: "Confirm restart",
829
+ what: "restarting the gateway"
796
830
  });
797
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
798
- return okText("Gateway restart cancelled.");
831
+ if (!confirmation.confirmed) {
832
+ return okText(confirmation.reason);
799
833
  }
800
834
  const ctx = buildContext({ stack: input.stackName });
801
835
  const conn = await resolveConn(ctx);
@@ -855,16 +889,13 @@ function makeProgressEmitter(server, progressToken) {
855
889
  // src/mcp/tools/cli/up.ts
856
890
  async function handleUp(input, server) {
857
891
  if (!input.dryRun) {
858
- const elicit = await server.server.elicitInput({
892
+ const confirmation = await confirmDestructive(server, {
859
893
  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
- }
894
+ title: "Confirm deployment",
895
+ what: "provisioning cloud resources"
865
896
  });
866
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
867
- return okText("Deployment cancelled.");
897
+ if (!confirmation.confirmed) {
898
+ return okText(confirmation.reason);
868
899
  }
869
900
  }
870
901
  const ctx = buildContext({ stack: input.stackName, provider: input.provider });
@@ -877,7 +908,7 @@ async function handleUp(input, server) {
877
908
  );
878
909
  }
879
910
  const { localOpts } = stackConfig;
880
- const { localBootstrap } = await import("./bootstrap-NN647RCY.js");
911
+ const { localBootstrap } = await import("./bootstrap-3PBVY7MA.js");
881
912
  const ac = new AbortController();
882
913
  const state = await localBootstrap({
883
914
  host: localOpts.host,
@@ -900,8 +931,8 @@ async function handleUp(input, server) {
900
931
  const progressToken = void 0;
901
932
  const emit = makeProgressEmitter(server, progressToken);
902
933
  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");
934
+ const { generatePlan: generatePlan2 } = await import("./generate-NE54BATS.js");
935
+ const { applyPlan: applyPlan2 } = await import("./apply-JARPVA5Y.js");
905
936
  const { resolveNetworkFlags } = await import("./network-args-PYJRJBYO.js");
906
937
  const { detectEgressIp } = await import("./firewall-5VKZ4WBH.js");
907
938
  const network = await resolveNetworkFlags(
@@ -953,16 +984,13 @@ async function handleUp(input, server) {
953
984
  import { randomUUID as randomUUID3 } from "crypto";
954
985
  async function handleDestroy(input, server) {
955
986
  if (!input.yes) {
956
- const elicit = await server.server.elicitInput({
987
+ const confirmation = await confirmDestructive(server, {
957
988
  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
- }
989
+ title: "Confirm destruction (irreversible)",
990
+ what: "destroying a stack"
963
991
  });
964
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
965
- return okText("Destruction cancelled.");
992
+ if (!confirmation.confirmed) {
993
+ return okText(confirmation.reason);
966
994
  }
967
995
  }
968
996
  const ctx = buildContext({ stack: input.stackName });
@@ -1019,7 +1047,7 @@ async function handlePlan(input, _server) {
1019
1047
  let network = resolved;
1020
1048
  if (input.privateOnly) {
1021
1049
  const { privateOnlyNetwork, assertTailnetReachable } = await import("./private-only-DIA26I2E.js");
1022
- const { probeSsh } = await import("./tailscale-cutover-6YP2QKFM.js");
1050
+ const { probeSsh } = await import("./tailscale-cutover-EKAQSDXP.js");
1023
1051
  network = privateOnlyNetwork(flags, resolved, ctx.config.stacks[stackName]?.tailscale, stackName);
1024
1052
  await assertTailnetReachable(ctx, (conn) => probeSsh(conn));
1025
1053
  }
@@ -1056,30 +1084,31 @@ async function handleHarden(input, server) {
1056
1084
  "tailscale and tailscaleRevert do the opposite of each other; pass one, not both."
1057
1085
  );
1058
1086
  }
1059
- const { getConfig: getConfig2 } = await import("./store-OVB3DAJR.js");
1087
+ const { getConfig: getConfig2 } = await import("./store-YOLQYXMB.js");
1060
1088
  const config = getConfig2();
1061
- if (!config) return errText("No clawops config found. The user needs to run `clawops init` first.");
1089
+ if (!config) {
1090
+ return errText(
1091
+ "No clawops config on this machine. Call clawops_init first to register a stack; until then every clawops tool will say this."
1092
+ );
1093
+ }
1062
1094
  const ctx = buildContext({ stack: input.stackName });
1063
1095
  if (!input.dryRun && !input.yes) {
1064
1096
  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({
1097
+ const confirmation = await confirmDestructive(server, {
1066
1098
  message: what,
1067
- requestedSchema: {
1068
- type: "object",
1069
- properties: { confirmed: { type: "boolean", title: "Confirm" } },
1070
- required: ["confirmed"]
1071
- }
1099
+ title: "Confirm",
1100
+ what: "changing a running host"
1072
1101
  });
1073
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
1074
- return okText("Hardening cancelled. Nothing was changed.");
1102
+ if (!confirmation.confirmed) {
1103
+ return okText(confirmation.reason);
1075
1104
  }
1076
1105
  }
1077
- const { revertTailnet, cutOverToTailnet, connectionFor } = await import("./flows-RI7FGHAM.js");
1106
+ const { revertTailnet, cutOverToTailnet, connectionFor } = await import("./flows-HGJIA6EL.js");
1078
1107
  if (input.tailscaleRevert) {
1079
1108
  const outcome = await revertTailnet(ctx, config);
1080
1109
  return outcome.ok ? okText(outcome.message) : errText(outcome.reason);
1081
1110
  }
1082
- const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary, makeTailscaleModule } = await import("./harden-3KKCR7BL.js");
1111
+ const { MODULE_CATALOG, resolveModules, runHardening, formatHardenSummary, makeTailscaleModule } = await import("./harden-RUOUA5BV.js");
1083
1112
  const forStack = makeTailscaleModule(ctx.stackName);
1084
1113
  const resolved = resolveModules(MODULE_CATALOG, input.options, ctx.adapter.name).map(
1085
1114
  (m) => m.id === "tailscale" ? forStack : m
@@ -1121,6 +1150,32 @@ async function handleHarden(input, server) {
1121
1150
  return failed.length > 0 ? errText(content) : okText(content);
1122
1151
  }
1123
1152
 
1153
+ // src/mcp/tools/cli/init.ts
1154
+ async function handleInit(input, _server) {
1155
+ const { initStack } = await import("./init-HOPXDNWY.js");
1156
+ const result = await initStack({
1157
+ provider: input.provider,
1158
+ stackName: input.stackName,
1159
+ stateUrl: input.stateUrl,
1160
+ region: input.region,
1161
+ force: input.force,
1162
+ host: input.host,
1163
+ sshUser: input.sshUser,
1164
+ sshPort: input.sshPort
1165
+ });
1166
+ if (!result.ok) return errText(result.reason);
1167
+ const lines = [
1168
+ `Stack "${result.stackName}" registered for ${result.provider}.`,
1169
+ `Config: ${result.configPath}`,
1170
+ `State backend: ${result.stateUrl}`,
1171
+ ...result.region ? [`Region: ${result.region}`] : [],
1172
+ `SSH key: ${result.keyPath}${result.keyGenerated ? " (generated now)" : " (already existed)"}`,
1173
+ "",
1174
+ "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."
1175
+ ];
1176
+ return okText(lines.join("\n"));
1177
+ }
1178
+
1124
1179
  // src/mcp/tools/cli/apply.ts
1125
1180
  import { readFileSync } from "fs";
1126
1181
  import { randomUUID as randomUUID4 } from "crypto";
@@ -1142,18 +1197,15 @@ ${validation.errors.join("\n")}`);
1142
1197
  }
1143
1198
  if (!input.yes) {
1144
1199
  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({
1200
+ const confirmation = await confirmDestructive(server, {
1146
1201
  message: `Apply plan for stack "${plan.spec.stackName}" (${plan.spec.provider})?
1147
1202
  Changes: ${diffLine}.
1148
1203
  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
- }
1204
+ title: "Confirm plan apply",
1205
+ what: "applying a plan"
1154
1206
  });
1155
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
1156
- return okText("Apply cancelled.");
1207
+ if (!confirmation.confirmed) {
1208
+ return okText(confirmation.reason);
1157
1209
  }
1158
1210
  }
1159
1211
  const taskId = randomUUID4();
@@ -1282,17 +1334,14 @@ Falling back to direct deployment.`);
1282
1334
  );
1283
1335
  }
1284
1336
  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({
1337
+ const confirmation = await confirmDestructive(server, {
1286
1338
  message: `Deploy OpenClaw on ${provider} (stack: "${input.stackName}", instance: ${input.instanceType}${input.region ? `, region: ${input.region}` : ""})?
1287
1339
  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
- }
1340
+ title: "Confirm end-to-end deployment",
1341
+ what: "provisioning cloud resources"
1293
1342
  });
1294
- if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
1295
- return okText("Deployment workflow cancelled.");
1343
+ if (!confirmation.confirmed) {
1344
+ return okText(confirmation.reason);
1296
1345
  }
1297
1346
  parts.push("### Step 2: Apply");
1298
1347
  const emit = makeProgressEmitter(server, void 0);
@@ -1371,6 +1420,7 @@ var TOOL_REGISTRY = {
1371
1420
  clawops_destroy: makeEntry(clawops_destroySchema, clawops_destroyAnnotations, handleDestroy, clawops_destroyDescription),
1372
1421
  clawops_apply: makeEntry(clawops_applySchema, clawops_applyAnnotations, handleApply, clawops_applyDescription),
1373
1422
  clawops_plan: makeEntry(clawops_planSchema, clawops_planAnnotations, handlePlan, clawops_planDescription),
1423
+ clawops_init: makeEntry(clawops_initSchema, clawops_initAnnotations, handleInit, clawops_initDescription),
1374
1424
  clawops_harden: makeEntry(clawops_hardenSchema, clawops_hardenAnnotations, handleHarden, clawops_hardenDescription),
1375
1425
  clawops_config_set: makeEntry(clawops_config_setSchema, clawops_config_setAnnotations, handleConfigSet, clawops_config_setDescription),
1376
1426
  clawops_config_unset: makeEntry(clawops_config_unsetSchema, clawops_config_unsetAnnotations, handleConfigUnset, clawops_config_unsetDescription),
@@ -1676,7 +1726,7 @@ function bearerFrom(header) {
1676
1726
  return match?.[1]?.trim();
1677
1727
  }
1678
1728
  async function serveMcp(opts) {
1679
- const { version } = await import("./package-JFVGCNKU.js");
1729
+ const { version } = await import("./package-MF5LPITL.js");
1680
1730
  const makeServer = () => {
1681
1731
  const server = new McpServer({ name: "clawops", version });
1682
1732
  registerTools(server, opts);