@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.
- package/README.md +122 -2
- package/dist/agent-activity-25FAWLAI.js +16 -0
- package/dist/{apply-HJ5GAKHW.js → apply-F5SQ2ZOS.js} +12 -12
- package/dist/{automation-MKCJ46I6.js → automation-NBYQ6MFZ.js} +1 -1
- package/dist/backup-flows-NDP5JTR3.js +29 -0
- package/dist/{bootstrap-NN647RCY.js → bootstrap-32TZXVDF.js} +5 -5
- package/dist/bootstrap.sh.tmpl +4 -0
- package/dist/chunk-27335SIH.js +51 -0
- package/dist/{chunk-X56CQ6GB.js → chunk-4MHDW2NB.js} +1 -1
- package/dist/{chunk-E4NJSJ54.js → chunk-4PY6PN4G.js} +51 -7
- package/dist/{chunk-3Z5TDXCJ.js → chunk-55UMO37O.js} +1 -1
- package/dist/{chunk-IQQTB7ER.js → chunk-66R52R7K.js} +10 -14
- package/dist/chunk-755X5OGS.js +213 -0
- package/dist/{chunk-QDNFADJI.js → chunk-AVERNLUY.js} +6 -6
- package/dist/{chunk-R3DTA35B.js → chunk-AX65JMDZ.js} +20 -20
- package/dist/{chunk-T5XCMYAV.js → chunk-CQOYUWR7.js} +766 -210
- package/dist/chunk-E6BRQK67.js +289 -0
- package/dist/{chunk-64PONSA5.js → chunk-GFIR2IIJ.js} +3 -1
- package/dist/chunk-LV7UO5CG.js +82 -0
- package/dist/{chunk-GIEVAGEK.js → chunk-N5ZYUTZS.js} +16 -1
- package/dist/{chunk-GFUK6KDR.js → chunk-OUQVKIIA.js} +372 -34
- package/dist/{chunk-XFN3YDZE.js → chunk-PASAVVNS.js} +1 -1
- package/dist/chunk-PCSX6Y3U.js +297 -0
- package/dist/chunk-POYS3EVQ.js +14 -0
- package/dist/{chunk-ELU3XQEA.js → chunk-RDUKRIRC.js} +1 -1
- package/dist/chunk-SX75L2T3.js +46 -0
- package/dist/{chunk-2Z7J3ERQ.js → chunk-TYNMOYVM.js} +1 -1
- package/dist/{chunk-2I7KCMXZ.js → chunk-UAJJEDCJ.js} +2 -2
- package/dist/{chunk-NUDIHZEH.js → chunk-WPZIJONN.js} +14 -12
- package/dist/{chunk-BELTQGHT.js → chunk-YPVWF7IA.js} +4 -4
- package/dist/{chunk-3OLUJPCB.js → chunk-Z3XLWZWF.js} +1 -1
- package/dist/cli.js +383 -925
- package/dist/conn-M2V5HBPJ.js +9 -0
- package/dist/{context-7VI3R6EK.js → context-EHSLWKSH.js} +6 -6
- package/dist/{docker-3PDO4TS7.js → docker-IKM5GRMC.js} +2 -2
- package/dist/{flows-RI7FGHAM.js → flows-6B2I6TRW.js} +7 -7
- package/dist/{generate-Q2BGVMH7.js → generate-4YYNW3IU.js} +9 -8
- package/dist/{harden-3KKCR7BL.js → harden-UHAOVQMZ.js} +5 -5
- package/dist/init-HOPXDNWY.js +13 -0
- package/dist/{mcp-wire-FQOMM7MK.js → mcp-wire-BXF4DJFT.js} +26 -19
- package/dist/migrate-flow-24XA4GZL.js +25 -0
- package/dist/{package-JFVGCNKU.js → package-HYGHRX35.js} +3 -1
- package/dist/{pool-PIIKJ4L2.js → pool-NT4XBSUT.js} +2 -2
- package/dist/{privileged-JFA6LLTV.js → privileged-5Y6O4H7Z.js} +1 -1
- package/dist/{ready-KDNRBNSU.js → ready-IJEUBFVX.js} +2 -2
- package/dist/{remote-config-3HZLUWEA.js → remote-config-OZYBMBSK.js} +3 -3
- package/dist/{runtime-X3KW5TCP.js → runtime-VW53TU2W.js} +5 -1
- package/dist/server-E3YA32ND.js +53 -0
- package/dist/ssh-HOM7GF3M.js +13 -0
- package/dist/{stack-config-LUTS2XAD.js → stack-config-ICQ5YHUL.js} +2 -2
- package/dist/stack-delete-NTIGKWUF.js +13 -0
- package/dist/{state-AOAYLYY5.js → state-AKZP2S4T.js} +2 -2
- package/dist/{store-OVB3DAJR.js → store-YOLQYXMB.js} +1 -1
- package/dist/{tailscale-cutover-6YP2QKFM.js → tailscale-cutover-V42HO5UM.js} +3 -3
- package/dist/{validate-JXKN5WD6.js → validate-DZE6LFEB.js} +1 -1
- package/dist/{wait-WO72TRRQ.js → wait-DOUNNJNN.js} +2 -2
- package/package.json +3 -1
- package/spec/mcp-tools.yaml +282 -35
- package/dist/logs-5A7YYA53.js +0 -15
- package/dist/server-B7I6HBQH.js +0 -46
- package/dist/ssh-VAI5DU5M.js +0 -11
- package/dist/upgrade-ULD4KFYC.js +0 -88
- /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-
|
|
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-
|
|
13
|
-
import {
|
|
14
|
-
generatePlan
|
|
15
|
-
} from "./chunk-IQQTB7ER.js";
|
|
44
|
+
} from "./chunk-UAJJEDCJ.js";
|
|
16
45
|
import {
|
|
17
46
|
validatePlan
|
|
18
|
-
} from "./chunk-
|
|
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-
|
|
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
|
-
|
|
30
|
-
|
|
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-
|
|
78
|
+
} from "./chunk-SOZ62ZXW.js";
|
|
37
79
|
import {
|
|
38
80
|
execPrivileged
|
|
39
|
-
} from "./chunk-
|
|
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-
|
|
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
|
|
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`.
|
|
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
|
|
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
|
|
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
|
|
837
|
+
const confirmation = await confirmDestructive(server, {
|
|
627
838
|
message: `Set ${input.key} = ${input.value} on stack "${input.stackName ?? "default"}"?`,
|
|
628
|
-
|
|
629
|
-
|
|
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 (
|
|
635
|
-
return okText(
|
|
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
|
|
883
|
+
const confirmation = await confirmDestructive(server, {
|
|
676
884
|
message: `Remove config key "${input.key}" on stack "${input.stackName ?? "default"}"?`,
|
|
677
|
-
|
|
678
|
-
|
|
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 (
|
|
684
|
-
return okText(
|
|
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
|
-
|
|
788
|
-
|
|
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
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
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/
|
|
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
|
|
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
|
-
|
|
861
|
-
|
|
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 (
|
|
867
|
-
return okText(
|
|
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-
|
|
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 =
|
|
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-
|
|
904
|
-
const { applyPlan: applyPlan2 } = await import("./apply-
|
|
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
|
|
1231
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
954
1232
|
async function handleDestroy(input, server) {
|
|
955
1233
|
if (!input.yes) {
|
|
956
|
-
const
|
|
1234
|
+
const confirmation = await confirmDestructive(server, {
|
|
957
1235
|
message: `DESTROY stack "${input.stackName}"? This permanently deletes all provisioned resources and cannot be undone.`,
|
|
958
|
-
|
|
959
|
-
|
|
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 (
|
|
965
|
-
return okText(
|
|
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 =
|
|
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-
|
|
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-
|
|
1334
|
+
const { getConfig: getConfig2 } = await import("./store-YOLQYXMB.js");
|
|
1060
1335
|
const config = getConfig2();
|
|
1061
|
-
if (!config)
|
|
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
|
|
1344
|
+
const confirmation = await confirmDestructive(server, {
|
|
1066
1345
|
message: what,
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
properties: { confirmed: { type: "boolean", title: "Confirm" } },
|
|
1070
|
-
required: ["confirmed"]
|
|
1071
|
-
}
|
|
1346
|
+
title: "Confirm",
|
|
1347
|
+
what: "changing a running host"
|
|
1072
1348
|
});
|
|
1073
|
-
if (
|
|
1074
|
-
return okText(
|
|
1349
|
+
if (!confirmation.confirmed) {
|
|
1350
|
+
return okText(confirmation.reason);
|
|
1075
1351
|
}
|
|
1076
1352
|
}
|
|
1077
|
-
const { revertTailnet, cutOverToTailnet, connectionFor } = await import("./flows-
|
|
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-
|
|
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
|
|
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
|
|
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
|
-
|
|
1150
|
-
|
|
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 (
|
|
1156
|
-
return okText(
|
|
1454
|
+
if (!confirmation.confirmed) {
|
|
1455
|
+
return okText(confirmation.reason);
|
|
1157
1456
|
}
|
|
1158
1457
|
}
|
|
1159
|
-
const taskId =
|
|
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-
|
|
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
|
|
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
|
-
|
|
1289
|
-
|
|
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 (
|
|
1295
|
-
return okText(
|
|
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
|
|
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 =
|
|
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-
|
|
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: () =>
|
|
2303
|
+
sessionIdGenerator: () => randomUUID8(),
|
|
1748
2304
|
onsessioninitialized: (id) => {
|
|
1749
2305
|
sessions.set(id, transport);
|
|
1750
2306
|
}
|