@clawops/cli 1.3.0 → 1.4.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 +97 -0
- package/dist/{apply-DLNE2JYZ.js → apply-5RREMN3L.js} +3 -3
- package/dist/{aws-KQBWY43W.js → aws-FRE2JVAZ.js} +9 -4
- package/dist/{azure-4EJFVJZL.js → azure-PVC3AQVC.js} +11 -4
- package/dist/chunk-JYQZJMD3.js +764 -0
- package/dist/{chunk-JDN6PLH2.js → chunk-T5EX55GP.js} +3 -3
- package/dist/{chunk-ZDPBBYYV.js → chunk-UAA6YOOP.js} +2 -2
- package/dist/{chunk-UDNZUSKA.js → chunk-ZFNPM2WG.js} +1 -1
- package/dist/{chunk-JCUZU5BH.js → chunk-ZONXY3C6.js} +2 -2
- package/dist/cli.js +63 -50
- package/dist/{context-M2RMDHX6.js → context-PSGEX2D7.js} +1 -1
- package/dist/{gcp-SKURG3L6.js → gcp-BXDTC6EK.js} +60 -17
- package/dist/{generate-U7PJ5LRG.js → generate-Z7DBMANX.js} +2 -2
- package/dist/{package-AFUTH62F.js → package-WAJRGBBM.js} +2 -1
- package/dist/{remote-config-JQ77SRC2.js → remote-config-QF5TT7GU.js} +1 -1
- package/dist/{server-I62UES5V.js → server-IWLZJ4EU.js} +92 -19
- package/package.json +2 -1
- package/dist/chunk-LVIIYY27.js +0 -266
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
} from "./chunk-3QJBNAHW.js";
|
|
5
5
|
import {
|
|
6
6
|
generatePlan
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-UAA6YOOP.js";
|
|
8
8
|
import {
|
|
9
9
|
applyPlan
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-ZONXY3C6.js";
|
|
11
11
|
import {
|
|
12
12
|
validatePlan
|
|
13
13
|
} from "./chunk-YTH4L2GN.js";
|
|
@@ -15,22 +15,24 @@ import "./chunk-6ZFIFDBJ.js";
|
|
|
15
15
|
import "./chunk-BOPSG2LI.js";
|
|
16
16
|
import {
|
|
17
17
|
errText,
|
|
18
|
+
formatUptime,
|
|
19
|
+
gatherSnapshot,
|
|
18
20
|
handleConfigGet,
|
|
19
21
|
handleConfigSet,
|
|
20
22
|
handleConfigUnset,
|
|
21
23
|
handleConfigValidate,
|
|
22
24
|
okText,
|
|
23
25
|
resolveConn
|
|
24
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-JYQZJMD3.js";
|
|
25
27
|
import {
|
|
26
28
|
buildContext
|
|
27
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-T5EX55GP.js";
|
|
28
30
|
import {
|
|
29
31
|
acquireSession,
|
|
30
32
|
drainPool
|
|
31
33
|
} from "./chunk-ZVOEQCNW.js";
|
|
32
34
|
import "./chunk-4U3LTLWZ.js";
|
|
33
|
-
import "./chunk-
|
|
35
|
+
import "./chunk-ZFNPM2WG.js";
|
|
34
36
|
import "./chunk-A2I76FTA.js";
|
|
35
37
|
import {
|
|
36
38
|
getConfig,
|
|
@@ -140,7 +142,7 @@ var clawops_statusAnnotations = {
|
|
|
140
142
|
};
|
|
141
143
|
var clawops_logs_tailSchema = z.object({
|
|
142
144
|
stackName: z.string().optional(),
|
|
143
|
-
tailLines: z.number().int().default(100),
|
|
145
|
+
tailLines: z.number().int().optional().default(100),
|
|
144
146
|
sinceMin: z.number().int().optional()
|
|
145
147
|
});
|
|
146
148
|
var clawops_logs_tailAnnotations = {
|
|
@@ -151,6 +153,18 @@ var clawops_logs_tailAnnotations = {
|
|
|
151
153
|
openWorldHint: true,
|
|
152
154
|
toolsets: ["cli", "read"]
|
|
153
155
|
};
|
|
156
|
+
var clawops_monitorSchema = z.object({
|
|
157
|
+
stackName: z.string().optional(),
|
|
158
|
+
tailLines: z.number().int().optional().default(5)
|
|
159
|
+
});
|
|
160
|
+
var clawops_monitorAnnotations = {
|
|
161
|
+
title: "Monitor Stack Health",
|
|
162
|
+
readOnlyHint: true,
|
|
163
|
+
destructiveHint: false,
|
|
164
|
+
idempotentHint: true,
|
|
165
|
+
openWorldHint: true,
|
|
166
|
+
toolsets: ["cli", "read"]
|
|
167
|
+
};
|
|
154
168
|
var clawops_stacks_listSchema = z.object({});
|
|
155
169
|
var clawops_stacks_listAnnotations = {
|
|
156
170
|
title: "List Stacks",
|
|
@@ -187,9 +201,9 @@ var clawops_upSchema = z.object({
|
|
|
187
201
|
stackName: z.string().optional(),
|
|
188
202
|
provider: z.enum(["aws", "gcp", "azure", "local"]).optional(),
|
|
189
203
|
region: z.string().optional(),
|
|
190
|
-
instanceType: z.enum(["micro", "small", "medium", "large", "gpu"]).default("small"),
|
|
204
|
+
instanceType: z.enum(["micro", "small", "medium", "large", "gpu"]).optional().default("small"),
|
|
191
205
|
openclawVersion: z.string().optional(),
|
|
192
|
-
dryRun: z.boolean().default(false)
|
|
206
|
+
dryRun: z.boolean().optional().default(false)
|
|
193
207
|
});
|
|
194
208
|
var clawops_upAnnotations = {
|
|
195
209
|
title: "Provision and Deploy Stack",
|
|
@@ -201,7 +215,7 @@ var clawops_upAnnotations = {
|
|
|
201
215
|
};
|
|
202
216
|
var clawops_destroySchema = z.object({
|
|
203
217
|
stackName: z.string(),
|
|
204
|
-
yes: z.boolean().default(false)
|
|
218
|
+
yes: z.boolean().optional().default(false)
|
|
205
219
|
});
|
|
206
220
|
var clawops_destroyAnnotations = {
|
|
207
221
|
title: "Destroy Stack (DESTRUCTIVE)",
|
|
@@ -213,7 +227,7 @@ var clawops_destroyAnnotations = {
|
|
|
213
227
|
};
|
|
214
228
|
var clawops_applySchema = z.object({
|
|
215
229
|
planPath: z.string(),
|
|
216
|
-
yes: z.boolean().default(false)
|
|
230
|
+
yes: z.boolean().optional().default(false)
|
|
217
231
|
});
|
|
218
232
|
var clawops_applyAnnotations = {
|
|
219
233
|
title: "Apply Maker Plan",
|
|
@@ -242,7 +256,7 @@ var clawops_config_setSchema = z.object({
|
|
|
242
256
|
stackName: z.string().optional(),
|
|
243
257
|
key: z.string(),
|
|
244
258
|
value: z.string(),
|
|
245
|
-
restart: z.boolean().default(false)
|
|
259
|
+
restart: z.boolean().optional().default(false)
|
|
246
260
|
});
|
|
247
261
|
var clawops_config_setAnnotations = {
|
|
248
262
|
title: "Set OpenClaw Config Value",
|
|
@@ -255,7 +269,7 @@ var clawops_config_setAnnotations = {
|
|
|
255
269
|
var clawops_config_unsetSchema = z.object({
|
|
256
270
|
stackName: z.string().optional(),
|
|
257
271
|
key: z.string(),
|
|
258
|
-
restart: z.boolean().default(false)
|
|
272
|
+
restart: z.boolean().optional().default(false)
|
|
259
273
|
});
|
|
260
274
|
var clawops_config_unsetAnnotations = {
|
|
261
275
|
title: "Unset OpenClaw Config Key",
|
|
@@ -302,8 +316,8 @@ var clawops_gateway_restartAnnotations = {
|
|
|
302
316
|
var clawops_workflow_deploy_appSchema = z.object({
|
|
303
317
|
provider: z.enum(["aws", "gcp", "azure", "local"]),
|
|
304
318
|
region: z.string().optional(),
|
|
305
|
-
stackName: z.string().default("default"),
|
|
306
|
-
instanceType: z.enum(["micro", "small", "medium", "large", "gpu"]).default("small")
|
|
319
|
+
stackName: z.string().optional().default("default"),
|
|
320
|
+
instanceType: z.enum(["micro", "small", "medium", "large", "gpu"]).optional().default("small")
|
|
307
321
|
});
|
|
308
322
|
var clawops_workflow_deploy_appAnnotations = {
|
|
309
323
|
title: "Deploy OpenClaw (End-to-End Workflow)",
|
|
@@ -339,6 +353,7 @@ var TOOLSETS = {
|
|
|
339
353
|
cli: [
|
|
340
354
|
"clawops_status",
|
|
341
355
|
"clawops_logs_tail",
|
|
356
|
+
"clawops_monitor",
|
|
342
357
|
"clawops_config_get",
|
|
343
358
|
"clawops_agents_list",
|
|
344
359
|
"clawops_up",
|
|
@@ -359,6 +374,7 @@ var TOOLSETS = {
|
|
|
359
374
|
read: [
|
|
360
375
|
"clawops_status",
|
|
361
376
|
"clawops_logs_tail",
|
|
377
|
+
"clawops_monitor",
|
|
362
378
|
"clawops_stacks_list",
|
|
363
379
|
"clawops_config_get",
|
|
364
380
|
"clawops_agents_list",
|
|
@@ -835,6 +851,62 @@ async function handleTaskStatus(input, _server) {
|
|
|
835
851
|
return { content: [{ type: "text", text: JSON.stringify(record, null, 2) }] };
|
|
836
852
|
}
|
|
837
853
|
|
|
854
|
+
// src/mcp/tools/cli/monitor.ts
|
|
855
|
+
async function handleMonitor(input, _server) {
|
|
856
|
+
const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-FBFHATDG.js");
|
|
857
|
+
const ctx = buildContext({ stack: input.stackName });
|
|
858
|
+
const tailLines = input.tailLines ?? 5;
|
|
859
|
+
let conn;
|
|
860
|
+
if (ctx.adapter.name === "local") {
|
|
861
|
+
if (!ctx.localState) {
|
|
862
|
+
return text2(JSON.stringify({ error: "Stack is not bootstrapped. Run `clawops up` first." }));
|
|
863
|
+
}
|
|
864
|
+
const ls = ctx.localState;
|
|
865
|
+
conn = { host: ls.sshHost, port: ls.sshPort, user: ls.sshUser, privateKeyPath: ls.privateKeyPath, knownHostsPath: ls.knownHostsPath };
|
|
866
|
+
} else {
|
|
867
|
+
const stack = await ctx.getStack();
|
|
868
|
+
const outputMap = await stack.outputs();
|
|
869
|
+
const outputs = Object.fromEntries(
|
|
870
|
+
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
871
|
+
);
|
|
872
|
+
if (!outputs["publicIp"]) {
|
|
873
|
+
return text2(JSON.stringify({ error: "Stack has no outputs. Run `clawops up` first." }));
|
|
874
|
+
}
|
|
875
|
+
const base = extractBaseOutputs(outputs);
|
|
876
|
+
conn = ctx.adapter.getConnectionInfo({
|
|
877
|
+
...base,
|
|
878
|
+
privateKeyPath: ctx.config.ssh.keyPath,
|
|
879
|
+
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
const ac = new AbortController();
|
|
883
|
+
const { session, release } = await acquireSession2({ ...conn, signal: ac.signal });
|
|
884
|
+
try {
|
|
885
|
+
const snap = await gatherSnapshot(session, ac.signal, tailLines);
|
|
886
|
+
return text2(JSON.stringify({
|
|
887
|
+
gateway: snap.gateway,
|
|
888
|
+
container: {
|
|
889
|
+
status: snap.container.status,
|
|
890
|
+
image: snap.container.image,
|
|
891
|
+
restartCount: snap.container.restartCount,
|
|
892
|
+
memUsage: snap.container.memUsage,
|
|
893
|
+
cpuPct: snap.container.cpuPct,
|
|
894
|
+
uptime: formatUptime(snap.container.startedAt)
|
|
895
|
+
},
|
|
896
|
+
disk: snap.disk,
|
|
897
|
+
logLines: snap.logLines,
|
|
898
|
+
capturedAt: snap.capturedAt.toISOString()
|
|
899
|
+
}, null, 2));
|
|
900
|
+
} finally {
|
|
901
|
+
release();
|
|
902
|
+
drainPool2();
|
|
903
|
+
ac.abort();
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
function text2(t) {
|
|
907
|
+
return { content: [{ type: "text", text: t }] };
|
|
908
|
+
}
|
|
909
|
+
|
|
838
910
|
// src/mcp/tools/workflow/deploy_app.ts
|
|
839
911
|
async function handleWorkflowDeployApp(input, server) {
|
|
840
912
|
const parts = [`## Deployment Workflow \u2014 ${input.provider} / ${input.stackName}
|
|
@@ -947,6 +1019,7 @@ function makeEntry(schema, annotations, handler) {
|
|
|
947
1019
|
var TOOL_REGISTRY = {
|
|
948
1020
|
clawops_status: makeEntry(clawops_statusSchema, clawops_statusAnnotations, handleStatus),
|
|
949
1021
|
clawops_logs_tail: makeEntry(clawops_logs_tailSchema, clawops_logs_tailAnnotations, handleLogsTail),
|
|
1022
|
+
clawops_monitor: makeEntry(clawops_monitorSchema, clawops_monitorAnnotations, handleMonitor),
|
|
950
1023
|
clawops_stacks_list: makeEntry(clawops_stacks_listSchema, clawops_stacks_listAnnotations, handleStacksList),
|
|
951
1024
|
clawops_config_get: makeEntry(clawops_config_getSchema, clawops_config_getAnnotations, handleConfigGet),
|
|
952
1025
|
clawops_agents_list: makeEntry(clawops_agents_listSchema, clawops_agents_listAnnotations, handleAgentsList),
|
|
@@ -1059,19 +1132,19 @@ function registerResources(server) {
|
|
|
1059
1132
|
async (uri, { name }) => {
|
|
1060
1133
|
const stackName = Array.isArray(name) ? name[0] : name;
|
|
1061
1134
|
const filePath = path3.join(getConfigDir(), "state", `${stackName}.last-run.json`);
|
|
1062
|
-
let
|
|
1135
|
+
let text3;
|
|
1063
1136
|
try {
|
|
1064
1137
|
const raw = readFileSync2(filePath, "utf-8");
|
|
1065
1138
|
const parsed = JSON.parse(raw);
|
|
1066
|
-
|
|
1139
|
+
text3 = parsed.output ?? "(no output recorded)";
|
|
1067
1140
|
} catch {
|
|
1068
|
-
|
|
1141
|
+
text3 = `No last-run output found for stack "${stackName}".`;
|
|
1069
1142
|
}
|
|
1070
1143
|
return {
|
|
1071
1144
|
contents: [{
|
|
1072
1145
|
uri: uri.href,
|
|
1073
1146
|
mimeType: "text/plain",
|
|
1074
|
-
text:
|
|
1147
|
+
text: text3
|
|
1075
1148
|
}]
|
|
1076
1149
|
};
|
|
1077
1150
|
}
|
|
@@ -1232,7 +1305,7 @@ If none of the above resolves the issue:
|
|
|
1232
1305
|
|
|
1233
1306
|
// src/mcp/server.ts
|
|
1234
1307
|
async function serveMcp(opts) {
|
|
1235
|
-
const { version } = await import("./package-
|
|
1308
|
+
const { version } = await import("./package-WAJRGBBM.js");
|
|
1236
1309
|
const server = new McpServer({ name: "clawops", version });
|
|
1237
1310
|
registerTools(server, opts);
|
|
1238
1311
|
registerResources(server);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clawops/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Deploy and manage self-hosted OpenClaw instances across clouds",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"@pulumi/docker": "^4.0.0",
|
|
33
33
|
"@pulumi/gcp": "^7.0.0",
|
|
34
34
|
"@pulumi/pulumi": "^3.0.0",
|
|
35
|
+
"@pulumi/random": "^4.20.0",
|
|
35
36
|
"ajv": "^8.0.0",
|
|
36
37
|
"ajv-formats": "^3.0.0",
|
|
37
38
|
"chalk": "^5.0.0",
|
package/dist/chunk-LVIIYY27.js
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
buildContext
|
|
4
|
-
} from "./chunk-JDN6PLH2.js";
|
|
5
|
-
import {
|
|
6
|
-
acquireSession,
|
|
7
|
-
drainPool
|
|
8
|
-
} from "./chunk-ZVOEQCNW.js";
|
|
9
|
-
import {
|
|
10
|
-
OPENCLAW_CONFIG,
|
|
11
|
-
atomicWriteConfig,
|
|
12
|
-
restartGateway
|
|
13
|
-
} from "./chunk-UDNZUSKA.js";
|
|
14
|
-
import {
|
|
15
|
-
StateError
|
|
16
|
-
} from "./chunk-KGXPLI7W.js";
|
|
17
|
-
|
|
18
|
-
// src/mcp/tools/_conn.ts
|
|
19
|
-
async function resolveConn(ctx) {
|
|
20
|
-
if (ctx.adapter.name === "local") {
|
|
21
|
-
const state = ctx.localState;
|
|
22
|
-
if (!state) throw new StateError("Stack has no local state \u2014 run `clawops up` first.");
|
|
23
|
-
return {
|
|
24
|
-
host: state.sshHost,
|
|
25
|
-
port: state.sshPort,
|
|
26
|
-
user: state.sshUser,
|
|
27
|
-
privateKeyPath: state.privateKeyPath,
|
|
28
|
-
knownHostsPath: state.knownHostsPath
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
const { extractBaseOutputs } = await import("./outputs-DJHBY7EE.js");
|
|
32
|
-
const stack = await ctx.getStack();
|
|
33
|
-
const outputMap = await stack.outputs();
|
|
34
|
-
const outputs = Object.fromEntries(
|
|
35
|
-
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
36
|
-
);
|
|
37
|
-
if (!outputs["publicIp"]) {
|
|
38
|
-
throw new StateError("Stack has no outputs \u2014 run `clawops up` first.");
|
|
39
|
-
}
|
|
40
|
-
const base = extractBaseOutputs(outputs);
|
|
41
|
-
return ctx.adapter.getConnectionInfo({
|
|
42
|
-
...base,
|
|
43
|
-
privateKeyPath: ctx.config.ssh.keyPath,
|
|
44
|
-
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
function errText(message) {
|
|
48
|
-
return { content: [{ type: "text", text: message }], isError: true };
|
|
49
|
-
}
|
|
50
|
-
function okText(t) {
|
|
51
|
-
return { content: [{ type: "text", text: t }] };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/mcp/tools/cli/config.ts
|
|
55
|
-
var VALID_AUTH_MODES = /* @__PURE__ */ new Set(["none", "token", "password", "trusted-proxy"]);
|
|
56
|
-
async function handleConfigGet(input, _server) {
|
|
57
|
-
const ac = new AbortController();
|
|
58
|
-
const ctx = buildContext({ stack: input.stackName });
|
|
59
|
-
const conn = await resolveConn(ctx);
|
|
60
|
-
const { session, release } = await acquireSession(conn);
|
|
61
|
-
try {
|
|
62
|
-
const result = await session.exec(`cat ${OPENCLAW_CONFIG}`, ac.signal);
|
|
63
|
-
let cfg;
|
|
64
|
-
try {
|
|
65
|
-
cfg = JSON.parse(result.stdout);
|
|
66
|
-
} catch {
|
|
67
|
-
return errText(`Cannot parse ${OPENCLAW_CONFIG}: ${result.stderr || result.stdout}`);
|
|
68
|
-
}
|
|
69
|
-
const value = input.key ? getPath(cfg, input.key) : cfg;
|
|
70
|
-
return okText(JSON.stringify(value, null, 2));
|
|
71
|
-
} finally {
|
|
72
|
-
release();
|
|
73
|
-
drainPool();
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
async function handleConfigSet(input, server) {
|
|
77
|
-
const elicit = await server.server.elicitInput({
|
|
78
|
-
message: `Set ${input.key} = ${input.value} on stack "${input.stackName ?? "default"}"?`,
|
|
79
|
-
requestedSchema: {
|
|
80
|
-
type: "object",
|
|
81
|
-
properties: { confirmed: { type: "boolean", title: "Confirm config change" } },
|
|
82
|
-
required: ["confirmed"]
|
|
83
|
-
}
|
|
84
|
-
});
|
|
85
|
-
if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
|
|
86
|
-
return okText("Config change cancelled.");
|
|
87
|
-
}
|
|
88
|
-
const ac = new AbortController();
|
|
89
|
-
const ctx = buildContext({ stack: input.stackName });
|
|
90
|
-
const conn = await resolveConn(ctx);
|
|
91
|
-
const { session, release } = await acquireSession(conn);
|
|
92
|
-
try {
|
|
93
|
-
const readResult = await session.exec(`cat ${OPENCLAW_CONFIG}`, ac.signal);
|
|
94
|
-
let cfg;
|
|
95
|
-
try {
|
|
96
|
-
cfg = JSON.parse(readResult.stdout);
|
|
97
|
-
} catch {
|
|
98
|
-
return errText(`Cannot parse ${OPENCLAW_CONFIG}: ${readResult.stderr}`);
|
|
99
|
-
}
|
|
100
|
-
let parsedValue = input.value;
|
|
101
|
-
try {
|
|
102
|
-
parsedValue = JSON.parse(input.value);
|
|
103
|
-
} catch {
|
|
104
|
-
}
|
|
105
|
-
setPath(cfg, input.key, parsedValue);
|
|
106
|
-
try {
|
|
107
|
-
await atomicWriteConfig(session, cfg, ac.signal);
|
|
108
|
-
} catch (err) {
|
|
109
|
-
return errText(`Failed to write config: ${err.message}`);
|
|
110
|
-
}
|
|
111
|
-
let note = "";
|
|
112
|
-
if (input.restart) {
|
|
113
|
-
try {
|
|
114
|
-
await restartGateway(session, ac.signal);
|
|
115
|
-
note = " (gateway restarted)";
|
|
116
|
-
} catch (err) {
|
|
117
|
-
return errText(`Gateway restart failed: ${err.message}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return okText(`Config set: ${input.key}${note}`);
|
|
121
|
-
} finally {
|
|
122
|
-
release();
|
|
123
|
-
drainPool();
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
async function handleConfigUnset(input, server) {
|
|
127
|
-
const elicit = await server.server.elicitInput({
|
|
128
|
-
message: `Remove config key "${input.key}" on stack "${input.stackName ?? "default"}"?`,
|
|
129
|
-
requestedSchema: {
|
|
130
|
-
type: "object",
|
|
131
|
-
properties: { confirmed: { type: "boolean", title: "Confirm key removal" } },
|
|
132
|
-
required: ["confirmed"]
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
|
|
136
|
-
return okText("Config unset cancelled.");
|
|
137
|
-
}
|
|
138
|
-
const ac = new AbortController();
|
|
139
|
-
const ctx = buildContext({ stack: input.stackName });
|
|
140
|
-
const conn = await resolveConn(ctx);
|
|
141
|
-
const { session, release } = await acquireSession(conn);
|
|
142
|
-
try {
|
|
143
|
-
const readResult = await session.exec(`cat ${OPENCLAW_CONFIG}`, ac.signal);
|
|
144
|
-
let cfg;
|
|
145
|
-
try {
|
|
146
|
-
cfg = JSON.parse(readResult.stdout);
|
|
147
|
-
} catch {
|
|
148
|
-
return errText(`Cannot parse ${OPENCLAW_CONFIG}: ${readResult.stderr}`);
|
|
149
|
-
}
|
|
150
|
-
deletePath(cfg, input.key);
|
|
151
|
-
try {
|
|
152
|
-
await atomicWriteConfig(session, cfg, ac.signal);
|
|
153
|
-
} catch (err) {
|
|
154
|
-
return errText(`Failed to write config: ${err.message}`);
|
|
155
|
-
}
|
|
156
|
-
let note = "";
|
|
157
|
-
if (input.restart) {
|
|
158
|
-
try {
|
|
159
|
-
await restartGateway(session, ac.signal);
|
|
160
|
-
note = " (gateway restarted)";
|
|
161
|
-
} catch (err) {
|
|
162
|
-
return errText(`Gateway restart failed: ${err.message}`);
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
return okText(`Config key removed: ${input.key}${note}`);
|
|
166
|
-
} finally {
|
|
167
|
-
release();
|
|
168
|
-
drainPool();
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
async function handleConfigValidate(input, _server) {
|
|
172
|
-
const ac = new AbortController();
|
|
173
|
-
const ctx = buildContext({ stack: input.stackName });
|
|
174
|
-
const conn = await resolveConn(ctx);
|
|
175
|
-
const { session, release } = await acquireSession(conn);
|
|
176
|
-
try {
|
|
177
|
-
const result = await session.exec(`cat ${OPENCLAW_CONFIG}`, ac.signal);
|
|
178
|
-
let cfg;
|
|
179
|
-
try {
|
|
180
|
-
cfg = JSON.parse(result.stdout);
|
|
181
|
-
} catch {
|
|
182
|
-
return okText(JSON.stringify({ valid: false, issues: [`Invalid JSON: ${result.stderr || result.stdout}`] }));
|
|
183
|
-
}
|
|
184
|
-
const issues = validateOpenclawConfig(cfg);
|
|
185
|
-
return okText(JSON.stringify({ valid: issues.length === 0, issues }));
|
|
186
|
-
} finally {
|
|
187
|
-
release();
|
|
188
|
-
drainPool();
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
function getPath(obj, dotKey) {
|
|
192
|
-
return dotKey.split(".").reduce((cur, k) => {
|
|
193
|
-
if (cur !== null && typeof cur === "object") return cur[k];
|
|
194
|
-
return void 0;
|
|
195
|
-
}, obj);
|
|
196
|
-
}
|
|
197
|
-
function setPath(obj, dotKey, value) {
|
|
198
|
-
const keys = dotKey.split(".");
|
|
199
|
-
let cur = obj;
|
|
200
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
201
|
-
const k = keys[i];
|
|
202
|
-
if (typeof cur[k] !== "object" || cur[k] === null) cur[k] = {};
|
|
203
|
-
cur = cur[k];
|
|
204
|
-
}
|
|
205
|
-
cur[keys[keys.length - 1]] = value;
|
|
206
|
-
}
|
|
207
|
-
function deletePath(obj, dotKey) {
|
|
208
|
-
const keys = dotKey.split(".");
|
|
209
|
-
let cur = obj;
|
|
210
|
-
for (let i = 0; i < keys.length - 1; i++) {
|
|
211
|
-
const k = keys[i];
|
|
212
|
-
if (typeof cur[k] !== "object" || cur[k] === null) return;
|
|
213
|
-
cur = cur[k];
|
|
214
|
-
}
|
|
215
|
-
delete cur[keys[keys.length - 1]];
|
|
216
|
-
}
|
|
217
|
-
function validateOpenclawConfig(cfg) {
|
|
218
|
-
const issues = [];
|
|
219
|
-
if ("version" in cfg) {
|
|
220
|
-
issues.push(
|
|
221
|
-
"Top-level 'version' is not a valid OpenClaw config key. Use 'meta.lastTouchedVersion' (string) instead."
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
if ("channels" in cfg && Array.isArray(cfg["channels"])) {
|
|
225
|
-
issues.push(
|
|
226
|
-
`'channels' must be an object keyed by provider name (e.g. {"discord":{...}}), not an array.`
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
const meta = cfg["meta"];
|
|
230
|
-
if (meta !== void 0 && (typeof meta !== "object" || Array.isArray(meta) || meta === null)) {
|
|
231
|
-
issues.push("'meta' must be an object.");
|
|
232
|
-
} else if (meta && typeof meta === "object") {
|
|
233
|
-
const ltv = meta["lastTouchedVersion"];
|
|
234
|
-
if (ltv !== void 0 && typeof ltv !== "string") {
|
|
235
|
-
issues.push("'meta.lastTouchedVersion' must be a string.");
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
const gateway = cfg["gateway"];
|
|
239
|
-
if (gateway !== void 0 && typeof gateway === "object" && !Array.isArray(gateway) && gateway !== null) {
|
|
240
|
-
const gw = gateway;
|
|
241
|
-
if ("port" in gw && typeof gw["port"] !== "number") {
|
|
242
|
-
issues.push("'gateway.port' must be a number.");
|
|
243
|
-
}
|
|
244
|
-
const auth = gw["auth"];
|
|
245
|
-
if (auth !== void 0 && typeof auth === "object" && !Array.isArray(auth) && auth !== null) {
|
|
246
|
-
const mode = auth["mode"];
|
|
247
|
-
if (mode !== void 0 && !VALID_AUTH_MODES.has(mode)) {
|
|
248
|
-
issues.push(
|
|
249
|
-
`'gateway.auth.mode' must be one of: ${[...VALID_AUTH_MODES].join(", ")}. Got: "${mode}".`
|
|
250
|
-
);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return issues;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
export {
|
|
258
|
-
resolveConn,
|
|
259
|
-
errText,
|
|
260
|
-
okText,
|
|
261
|
-
handleConfigGet,
|
|
262
|
-
handleConfigSet,
|
|
263
|
-
handleConfigUnset,
|
|
264
|
-
handleConfigValidate,
|
|
265
|
-
validateOpenclawConfig
|
|
266
|
-
};
|