@clawops/cli 1.7.8 → 2.0.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 +412 -70
- package/dist/{apply-F3KY2HTB.js → apply-AM5HERE7.js} +8 -6
- package/dist/{aws-L4RR2LSE.js → aws-AMON7DA7.js} +20 -9
- package/dist/{azure-7OXQENQL.js → azure-T4G6OBJQ.js} +19 -8
- package/dist/{bootstrap-HL4DNUNG.js → bootstrap-DMRPWJGN.js} +28 -19
- package/dist/bootstrap.sh.tmpl +51 -31
- package/dist/channels-KBUR42IU.js +53 -0
- package/dist/{chunk-DQDAWNGB.js → chunk-3WYA2QDM.js} +1 -1
- package/dist/chunk-6TIYVHPX.js +152 -0
- package/dist/{chunk-3XK66DKK.js → chunk-AHAAEXWC.js} +41 -0
- package/dist/{chunk-QQNKBTYZ.js → chunk-AX74VXTV.js} +292 -94
- package/dist/{chunk-C5X375RO.js → chunk-CKSPROD5.js} +59 -15
- package/dist/{chunk-UKTP332Z.js → chunk-GAZPPSKU.js} +6 -7
- package/dist/{chunk-FNS56UQY.js → chunk-GYV4KNW7.js} +319 -252
- package/dist/{chunk-PVUVI35Y.js → chunk-JW5A7E3X.js} +5 -1
- package/dist/chunk-LT4XMO5D.js +50 -0
- package/dist/{chunk-P5LKWRPL.js → chunk-LU2V3LXA.js} +3 -3
- package/dist/chunk-N44K3LHE.js +140 -0
- package/dist/chunk-NUDIHZEH.js +55 -0
- package/dist/chunk-SBLQH2TJ.js +56 -0
- package/dist/chunk-UFRO4H5W.js +228 -0
- package/dist/{chunk-RAAONHW3.js → chunk-WHJMKDV7.js} +24 -4
- package/dist/chunk-YR3O2I3S.js +101 -0
- package/dist/cli.js +866 -577
- package/dist/config-validate-K2N6XLDX.js +76 -0
- package/dist/{context-5LCUG2QR.js → context-KHKWJJMC.js} +1 -1
- package/dist/{firewall-XQGO7HWK.js → firewall-T3NTPZJD.js} +10 -0
- package/dist/{gcp-UG6AWPIZ.js → gcp-2JBTD7ZM.js} +19 -8
- package/dist/{generate-ELD7QF5Z.js → generate-2XV2GSP2.js} +4 -4
- package/dist/{harden-URPXLSVF.js → harden-PCDK3UQ5.js} +48 -11
- package/dist/health-5CCOXYGG.js +11 -0
- package/dist/logs-P7YCKVF4.js +15 -0
- package/dist/{mcp-wire-ZMIVFAUC.js → mcp-wire-BFEY2Y6H.js} +23 -17
- package/dist/{package-QYZFN3FP.js → package-3HUW57XH.js} +3 -3
- package/dist/plugins-DTST3OER.js +49 -0
- package/dist/{pool-QGFQRMGU.js → pool-EX4ZTA5P.js} +2 -2
- package/dist/privileged-JFA6LLTV.js +17 -0
- package/dist/{remote-config-QDLN4UMG.js → remote-config-Z6IIZR3D.js} +3 -3
- package/dist/runtime-LZ7NA53V.js +42 -0
- package/dist/{server-FWTOSSZP.js → server-A5AB67GQ.js} +16 -12
- package/dist/spec-path-KQOOIQCA.js +9 -0
- package/dist/{ssh-CNJNLM3E.js → ssh-B3NZAU4I.js} +1 -1
- package/dist/upgrade-ULD4KFYC.js +88 -0
- package/dist/validate-BOQRWX4F.js +14 -0
- package/dist/{version-guard-VUCNZXQS.js → version-guard-GPSTCPT7.js} +3 -3
- package/dist/{versions-2UVAT6PQ.js → versions-W5LRQCAJ.js} +2 -2
- package/package.json +3 -3
- package/spec/deploy-plan.schema.json +114 -25
- package/spec/integrations.yaml +135 -28
- package/spec/mcp-tools.yaml +68 -35
- package/spec/models.yaml +9 -0
- package/spec/openclaw-2.0.config.schema.json +1 -0
- package/spec/openclaw-versions.yaml +116 -27
- package/dist/chunk-35WVQY3X.js +0 -122
- package/dist/chunk-6X45DDGH.js +0 -42
- package/dist/chunk-SEZMVVAS.js +0 -156
- package/dist/validate-CFQ4FH2T.js +0 -10
|
@@ -1,40 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
applyPlan
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-UFRO4H5W.js";
|
|
5
|
+
import {
|
|
6
|
+
atomicWriteConfig,
|
|
7
|
+
readRemoteConfig,
|
|
8
|
+
restartGateway
|
|
9
|
+
} from "./chunk-N44K3LHE.js";
|
|
5
10
|
import {
|
|
6
11
|
extractBaseOutputs
|
|
7
12
|
} from "./chunk-3QJBNAHW.js";
|
|
8
13
|
import {
|
|
9
14
|
generatePlan
|
|
10
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-WHJMKDV7.js";
|
|
11
16
|
import {
|
|
12
17
|
validatePlan
|
|
13
|
-
} from "./chunk-
|
|
14
|
-
import {
|
|
15
|
-
errText,
|
|
16
|
-
formatUptime,
|
|
17
|
-
gatherSnapshot,
|
|
18
|
-
handleConfigGet,
|
|
19
|
-
handleConfigSet,
|
|
20
|
-
handleConfigUnset,
|
|
21
|
-
handleConfigValidate,
|
|
22
|
-
okText,
|
|
23
|
-
resolveConn
|
|
24
|
-
} from "./chunk-FNS56UQY.js";
|
|
18
|
+
} from "./chunk-YR3O2I3S.js";
|
|
25
19
|
import {
|
|
26
20
|
acquireSession,
|
|
27
21
|
drainPool
|
|
28
|
-
} from "./chunk-
|
|
29
|
-
import {
|
|
30
|
-
IMAGE_INSPECT_CMD,
|
|
31
|
-
OPENCLAW_CONFIG,
|
|
32
|
-
gatewayRunCommand,
|
|
33
|
-
imageForRestart
|
|
34
|
-
} from "./chunk-SEZMVVAS.js";
|
|
22
|
+
} from "./chunk-3WYA2QDM.js";
|
|
35
23
|
import {
|
|
36
24
|
buildContext
|
|
37
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-LU2V3LXA.js";
|
|
26
|
+
import {
|
|
27
|
+
formatUptime,
|
|
28
|
+
gatherSnapshot,
|
|
29
|
+
runDiagnostics
|
|
30
|
+
} from "./chunk-GYV4KNW7.js";
|
|
31
|
+
import {
|
|
32
|
+
GATEWAY_LOGS_PROBE,
|
|
33
|
+
chooseLogSource,
|
|
34
|
+
containerLogsCommand,
|
|
35
|
+
gatewayLogsCommand
|
|
36
|
+
} from "./chunk-SBLQH2TJ.js";
|
|
37
|
+
import {
|
|
38
|
+
execPrivileged
|
|
39
|
+
} from "./chunk-NUDIHZEH.js";
|
|
38
40
|
import {
|
|
39
41
|
getConfig,
|
|
40
42
|
getConfigDir
|
|
@@ -43,6 +45,12 @@ import {
|
|
|
43
45
|
StateError,
|
|
44
46
|
UsageError
|
|
45
47
|
} from "./chunk-KGXPLI7W.js";
|
|
48
|
+
import {
|
|
49
|
+
IMAGE_INSPECT_CMD,
|
|
50
|
+
STATE_DIR_HOST_LINUX,
|
|
51
|
+
gatewayRunCommand,
|
|
52
|
+
imageForRestart
|
|
53
|
+
} from "./chunk-6TIYVHPX.js";
|
|
46
54
|
|
|
47
55
|
// src/mcp/server.ts
|
|
48
56
|
import { createServer } from "http";
|
|
@@ -141,6 +149,18 @@ var clawops_statusAnnotations = {
|
|
|
141
149
|
openWorldHint: true,
|
|
142
150
|
toolsets: ["cli", "read"]
|
|
143
151
|
};
|
|
152
|
+
var clawops_doctorSchema = z.object({
|
|
153
|
+
stackName: z.string().optional(),
|
|
154
|
+
failuresOnly: z.boolean().optional().default(false)
|
|
155
|
+
});
|
|
156
|
+
var clawops_doctorAnnotations = {
|
|
157
|
+
title: "Run Diagnostics",
|
|
158
|
+
readOnlyHint: true,
|
|
159
|
+
destructiveHint: false,
|
|
160
|
+
idempotentHint: true,
|
|
161
|
+
openWorldHint: true,
|
|
162
|
+
toolsets: ["cli", "read"]
|
|
163
|
+
};
|
|
144
164
|
var clawops_logs_tailSchema = z.object({
|
|
145
165
|
stackName: z.string().optional(),
|
|
146
166
|
tailLines: z.number().int().optional().default(100),
|
|
@@ -291,18 +311,6 @@ var clawops_config_validateAnnotations = {
|
|
|
291
311
|
openWorldHint: true,
|
|
292
312
|
toolsets: ["cli", "read"]
|
|
293
313
|
};
|
|
294
|
-
var clawops_agents_restartSchema = z.object({
|
|
295
|
-
stackName: z.string().optional(),
|
|
296
|
-
agentId: z.string()
|
|
297
|
-
});
|
|
298
|
-
var clawops_agents_restartAnnotations = {
|
|
299
|
-
title: "Restart OpenClaw Agent",
|
|
300
|
-
readOnlyHint: false,
|
|
301
|
-
destructiveHint: true,
|
|
302
|
-
idempotentHint: true,
|
|
303
|
-
openWorldHint: true,
|
|
304
|
-
toolsets: ["cli"]
|
|
305
|
-
};
|
|
306
314
|
var clawops_gateway_restartSchema = z.object({
|
|
307
315
|
stackName: z.string().optional()
|
|
308
316
|
});
|
|
@@ -353,6 +361,7 @@ var clawops_task_statusAnnotations = {
|
|
|
353
361
|
var TOOLSETS = {
|
|
354
362
|
cli: [
|
|
355
363
|
"clawops_status",
|
|
364
|
+
"clawops_doctor",
|
|
356
365
|
"clawops_logs_tail",
|
|
357
366
|
"clawops_monitor",
|
|
358
367
|
"clawops_config_get",
|
|
@@ -364,7 +373,6 @@ var TOOLSETS = {
|
|
|
364
373
|
"clawops_config_set",
|
|
365
374
|
"clawops_config_unset",
|
|
366
375
|
"clawops_config_validate",
|
|
367
|
-
"clawops_agents_restart",
|
|
368
376
|
"clawops_gateway_restart",
|
|
369
377
|
"clawops_task_status"
|
|
370
378
|
],
|
|
@@ -374,6 +382,7 @@ var TOOLSETS = {
|
|
|
374
382
|
],
|
|
375
383
|
read: [
|
|
376
384
|
"clawops_status",
|
|
385
|
+
"clawops_doctor",
|
|
377
386
|
"clawops_logs_tail",
|
|
378
387
|
"clawops_monitor",
|
|
379
388
|
"clawops_stacks_list",
|
|
@@ -421,32 +430,102 @@ function text(t) {
|
|
|
421
430
|
return { content: [{ type: "text", text: t }] };
|
|
422
431
|
}
|
|
423
432
|
|
|
424
|
-
// src/mcp/tools/
|
|
433
|
+
// src/mcp/tools/_conn.ts
|
|
434
|
+
async function resolveConn(ctx) {
|
|
435
|
+
if (ctx.adapter.name === "local") {
|
|
436
|
+
const state = ctx.localState;
|
|
437
|
+
if (!state) throw new StateError("Stack has no local state \u2014 run `clawops up` first.");
|
|
438
|
+
return {
|
|
439
|
+
host: state.sshHost,
|
|
440
|
+
port: state.sshPort,
|
|
441
|
+
user: state.sshUser,
|
|
442
|
+
privateKeyPath: state.privateKeyPath,
|
|
443
|
+
knownHostsPath: state.knownHostsPath
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const { extractBaseOutputs: extractBaseOutputs2 } = await import("./outputs-DJHBY7EE.js");
|
|
447
|
+
const stack = await ctx.getStack();
|
|
448
|
+
const outputMap = await stack.outputs();
|
|
449
|
+
const outputs = Object.fromEntries(
|
|
450
|
+
Object.entries(outputMap).map(([k, v]) => [k, v.value])
|
|
451
|
+
);
|
|
452
|
+
if (!outputs["publicIp"]) {
|
|
453
|
+
throw new StateError("Stack has no outputs \u2014 run `clawops up` first.");
|
|
454
|
+
}
|
|
455
|
+
const base = extractBaseOutputs2(outputs);
|
|
456
|
+
return ctx.adapter.getConnectionInfo({
|
|
457
|
+
...base,
|
|
458
|
+
privateKeyPath: ctx.config.ssh.keyPath,
|
|
459
|
+
knownHostsPath: ctx.config.ssh.knownHostsPath
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
function errText(message) {
|
|
463
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
464
|
+
}
|
|
465
|
+
function okText(t) {
|
|
466
|
+
return { content: [{ type: "text", text: t }] };
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/mcp/tools/_trim.ts
|
|
470
|
+
import { writeFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
471
|
+
import path2 from "path";
|
|
425
472
|
var MAX_BYTES = 8 * 1024;
|
|
473
|
+
function trimForMcp(output, stackName) {
|
|
474
|
+
try {
|
|
475
|
+
const dir = path2.join(getConfigDir(), "state");
|
|
476
|
+
mkdirSync2(dir, { recursive: true });
|
|
477
|
+
writeFileSync(path2.join(dir, `${stackName}.last-run.json`), JSON.stringify({ output }), "utf-8");
|
|
478
|
+
} catch {
|
|
479
|
+
}
|
|
480
|
+
if (Buffer.byteLength(output) <= MAX_BYTES) {
|
|
481
|
+
return { content: output, truncated: false };
|
|
482
|
+
}
|
|
483
|
+
const trimmed = output.slice(0, MAX_BYTES);
|
|
484
|
+
return {
|
|
485
|
+
content: trimmed + `
|
|
486
|
+
|
|
487
|
+
[Output truncated at 8KB. Full output available at clawops://stacks/${stackName}/last-run]`,
|
|
488
|
+
truncated: true
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/mcp/tools/cli/doctor.ts
|
|
493
|
+
async function handleDoctor(input, _server) {
|
|
494
|
+
const ac = new AbortController();
|
|
495
|
+
const report = await runDiagnostics({ stack: input.stackName, signal: ac.signal });
|
|
496
|
+
const payload = input.failuresOnly ? failuresOnly(report) : report;
|
|
497
|
+
const { content } = trimForMcp(JSON.stringify(payload, null, 2), input.stackName ?? "local");
|
|
498
|
+
return okText(content);
|
|
499
|
+
}
|
|
500
|
+
function failuresOnly(report) {
|
|
501
|
+
const sections = report.sections.map((s) => ({ ...s, checks: s.checks.filter((c) => c.status === "fail" || c.status === "warn") })).filter((s) => s.checks.length > 0);
|
|
502
|
+
return { sections, ok: report.ok, counts: report.counts };
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// src/mcp/tools/cli/logs.ts
|
|
506
|
+
var MAX_BYTES2 = 8 * 1024;
|
|
426
507
|
async function handleLogsTail(input, _server) {
|
|
427
508
|
const ctx = buildContext({ stack: input.stackName });
|
|
428
509
|
const conn = await resolveConn(ctx);
|
|
429
510
|
const { session, release } = await acquireSession(conn);
|
|
430
511
|
try {
|
|
431
512
|
const tailLines = input.tailLines ?? 100;
|
|
432
|
-
const
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
"|| docker logs openclaw",
|
|
439
|
-
`-n ${tailLines}`
|
|
440
|
-
].filter(Boolean).join(" ");
|
|
441
|
-
const result = await session.exec(command);
|
|
513
|
+
const since = input.sinceMin ? `${input.sinceMin}m` : void 0;
|
|
514
|
+
const probe = since ? { stdout: "" } : await execPrivileged(session, GATEWAY_LOGS_PROBE);
|
|
515
|
+
const choice = chooseLogSource({ since, gatewayReachable: probe.stdout.trim() === "ok" });
|
|
516
|
+
const opts = { tail: tailLines, follow: false, since, json: choice.source === "gateway" };
|
|
517
|
+
const command = choice.source === "gateway" ? gatewayLogsCommand(opts) : containerLogsCommand(opts);
|
|
518
|
+
const result = await execPrivileged(session, command);
|
|
442
519
|
if (result.code !== 0 && !result.stdout) {
|
|
443
520
|
return errText(`Failed to fetch logs: ${result.stderr}`);
|
|
444
521
|
}
|
|
445
|
-
let output =
|
|
446
|
-
|
|
447
|
-
|
|
522
|
+
let output = `[source: ${choice.source} \u2014 ${choice.reason}]
|
|
523
|
+
${result.stdout}`;
|
|
524
|
+
if (Buffer.byteLength(output) > MAX_BYTES2) {
|
|
525
|
+
output = output.slice(0, MAX_BYTES2) + "\n\n[output truncated at 8KB]";
|
|
448
526
|
}
|
|
449
|
-
return okText(output
|
|
527
|
+
return okText(result.stdout ? output : `[source: ${choice.source}]
|
|
528
|
+
(no log output)`);
|
|
450
529
|
} finally {
|
|
451
530
|
release();
|
|
452
531
|
drainPool();
|
|
@@ -469,42 +548,182 @@ async function handleStacksList(_input, _server) {
|
|
|
469
548
|
return { content: [{ type: "text", text: JSON.stringify({ stacks }, null, 2) }] };
|
|
470
549
|
}
|
|
471
550
|
|
|
472
|
-
// src/mcp/tools/cli/
|
|
473
|
-
async function
|
|
551
|
+
// src/mcp/tools/cli/config.ts
|
|
552
|
+
async function handleConfigGet(input, _server) {
|
|
553
|
+
const ac = new AbortController();
|
|
474
554
|
const ctx = buildContext({ stack: input.stackName });
|
|
475
555
|
const conn = await resolveConn(ctx);
|
|
476
556
|
const { session, release } = await acquireSession(conn);
|
|
477
557
|
try {
|
|
478
|
-
|
|
479
|
-
|
|
558
|
+
let cfg;
|
|
559
|
+
try {
|
|
560
|
+
cfg = await readRemoteConfig(session, ac.signal);
|
|
561
|
+
} catch (err) {
|
|
562
|
+
return errText(err.message);
|
|
563
|
+
}
|
|
564
|
+
const value = input.key ? getPath(cfg, input.key) : cfg;
|
|
565
|
+
return okText(JSON.stringify(value, null, 2));
|
|
480
566
|
} finally {
|
|
481
567
|
release();
|
|
482
568
|
drainPool();
|
|
483
569
|
}
|
|
484
570
|
}
|
|
485
|
-
async function
|
|
571
|
+
async function handleConfigSet(input, server) {
|
|
486
572
|
const elicit = await server.server.elicitInput({
|
|
487
|
-
message: `
|
|
573
|
+
message: `Set ${input.key} = ${input.value} on stack "${input.stackName ?? "default"}"?`,
|
|
488
574
|
requestedSchema: {
|
|
489
575
|
type: "object",
|
|
490
|
-
properties: { confirmed: { type: "boolean", title: "Confirm
|
|
576
|
+
properties: { confirmed: { type: "boolean", title: "Confirm config change" } },
|
|
491
577
|
required: ["confirmed"]
|
|
492
578
|
}
|
|
493
579
|
});
|
|
494
580
|
if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
|
|
495
|
-
return okText("
|
|
581
|
+
return okText("Config change cancelled.");
|
|
496
582
|
}
|
|
583
|
+
const ac = new AbortController();
|
|
497
584
|
const ctx = buildContext({ stack: input.stackName });
|
|
498
585
|
const conn = await resolveConn(ctx);
|
|
499
586
|
const { session, release } = await acquireSession(conn);
|
|
500
587
|
try {
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
588
|
+
let cfg;
|
|
589
|
+
try {
|
|
590
|
+
cfg = await readRemoteConfig(session, ac.signal);
|
|
591
|
+
} catch (err) {
|
|
592
|
+
return errText(err.message);
|
|
593
|
+
}
|
|
594
|
+
let parsedValue = input.value;
|
|
595
|
+
try {
|
|
596
|
+
parsedValue = JSON.parse(input.value);
|
|
597
|
+
} catch {
|
|
598
|
+
}
|
|
599
|
+
setPath(cfg, input.key, parsedValue);
|
|
600
|
+
try {
|
|
601
|
+
await atomicWriteConfig(session, cfg, ac.signal);
|
|
602
|
+
} catch (err) {
|
|
603
|
+
return errText(`Failed to write config: ${err.message}`);
|
|
604
|
+
}
|
|
605
|
+
let note = "";
|
|
606
|
+
if (input.restart) {
|
|
607
|
+
try {
|
|
608
|
+
await restartGateway(session, ac.signal);
|
|
609
|
+
note = " (gateway restarted)";
|
|
610
|
+
} catch (err) {
|
|
611
|
+
return errText(`Gateway restart failed: ${err.message}`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return okText(`Config set: ${input.key}${note}`);
|
|
615
|
+
} finally {
|
|
616
|
+
release();
|
|
617
|
+
drainPool();
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
async function handleConfigUnset(input, server) {
|
|
621
|
+
const elicit = await server.server.elicitInput({
|
|
622
|
+
message: `Remove config key "${input.key}" on stack "${input.stackName ?? "default"}"?`,
|
|
623
|
+
requestedSchema: {
|
|
624
|
+
type: "object",
|
|
625
|
+
properties: { confirmed: { type: "boolean", title: "Confirm key removal" } },
|
|
626
|
+
required: ["confirmed"]
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
if (elicit.action !== "accept" || !elicit.content?.["confirmed"]) {
|
|
630
|
+
return okText("Config unset cancelled.");
|
|
631
|
+
}
|
|
632
|
+
const ac = new AbortController();
|
|
633
|
+
const ctx = buildContext({ stack: input.stackName });
|
|
634
|
+
const conn = await resolveConn(ctx);
|
|
635
|
+
const { session, release } = await acquireSession(conn);
|
|
636
|
+
try {
|
|
637
|
+
let cfg;
|
|
638
|
+
try {
|
|
639
|
+
cfg = await readRemoteConfig(session, ac.signal);
|
|
640
|
+
} catch (err) {
|
|
641
|
+
return errText(err.message);
|
|
642
|
+
}
|
|
643
|
+
deletePath(cfg, input.key);
|
|
644
|
+
try {
|
|
645
|
+
await atomicWriteConfig(session, cfg, ac.signal);
|
|
646
|
+
} catch (err) {
|
|
647
|
+
return errText(`Failed to write config: ${err.message}`);
|
|
648
|
+
}
|
|
649
|
+
let note = "";
|
|
650
|
+
if (input.restart) {
|
|
651
|
+
try {
|
|
652
|
+
await restartGateway(session, ac.signal);
|
|
653
|
+
note = " (gateway restarted)";
|
|
654
|
+
} catch (err) {
|
|
655
|
+
return errText(`Gateway restart failed: ${err.message}`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return okText(`Config key removed: ${input.key}${note}`);
|
|
659
|
+
} finally {
|
|
660
|
+
release();
|
|
661
|
+
drainPool();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
async function handleConfigValidate(input, _server) {
|
|
665
|
+
const ac = new AbortController();
|
|
666
|
+
const ctx = buildContext({ stack: input.stackName });
|
|
667
|
+
const conn = await resolveConn(ctx);
|
|
668
|
+
const { session, release } = await acquireSession(conn);
|
|
669
|
+
try {
|
|
670
|
+
let cfg;
|
|
671
|
+
try {
|
|
672
|
+
cfg = await readRemoteConfig(session, ac.signal);
|
|
673
|
+
} catch (err) {
|
|
674
|
+
return okText(JSON.stringify({ valid: false, issues: [err.message] }));
|
|
675
|
+
}
|
|
676
|
+
const { validateConfig } = await import("./config-validate-K2N6XLDX.js");
|
|
677
|
+
const yaml = await import("./js-yaml-PTEEG4FO.js");
|
|
678
|
+
const { loadVersionSpec } = await import("./versions-W5LRQCAJ.js");
|
|
679
|
+
const spec = loadVersionSpec(yaml);
|
|
680
|
+
const { errors, warnings } = await validateConfig(cfg, {
|
|
681
|
+
schemaCapturedFrom: spec.runtime?.configSchemaCapturedFrom
|
|
682
|
+
});
|
|
683
|
+
return okText(JSON.stringify({ valid: errors.length === 0, issues: errors, warnings }));
|
|
684
|
+
} finally {
|
|
685
|
+
release();
|
|
686
|
+
drainPool();
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function getPath(obj, dotKey) {
|
|
690
|
+
return dotKey.split(".").reduce((cur, k) => {
|
|
691
|
+
if (cur !== null && typeof cur === "object") return cur[k];
|
|
692
|
+
return void 0;
|
|
693
|
+
}, obj);
|
|
694
|
+
}
|
|
695
|
+
function setPath(obj, dotKey, value) {
|
|
696
|
+
const keys = dotKey.split(".");
|
|
697
|
+
let cur = obj;
|
|
698
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
699
|
+
const k = keys[i];
|
|
700
|
+
if (typeof cur[k] !== "object" || cur[k] === null) cur[k] = {};
|
|
701
|
+
cur = cur[k];
|
|
702
|
+
}
|
|
703
|
+
cur[keys[keys.length - 1]] = value;
|
|
704
|
+
}
|
|
705
|
+
function deletePath(obj, dotKey) {
|
|
706
|
+
const keys = dotKey.split(".");
|
|
707
|
+
let cur = obj;
|
|
708
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
709
|
+
const k = keys[i];
|
|
710
|
+
if (typeof cur[k] !== "object" || cur[k] === null) return;
|
|
711
|
+
cur = cur[k];
|
|
712
|
+
}
|
|
713
|
+
delete cur[keys[keys.length - 1]];
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// src/mcp/tools/cli/agents.ts
|
|
717
|
+
async function handleAgentsList(input, _server) {
|
|
718
|
+
const ctx = buildContext({ stack: input.stackName });
|
|
719
|
+
const conn = await resolveConn(ctx);
|
|
720
|
+
const { session, release } = await acquireSession(conn);
|
|
721
|
+
try {
|
|
722
|
+
const result = await execPrivileged(session, "docker exec openclaw openclaw agents list --json");
|
|
504
723
|
if (result.code !== 0) {
|
|
505
|
-
return errText(`
|
|
724
|
+
return errText(`Cannot list agents: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.code}`}`);
|
|
506
725
|
}
|
|
507
|
-
return okText(
|
|
726
|
+
return okText(result.stdout.trim() || "[]");
|
|
508
727
|
} finally {
|
|
509
728
|
release();
|
|
510
729
|
drainPool();
|
|
@@ -528,11 +747,12 @@ async function handleGatewayRestart(input, server) {
|
|
|
528
747
|
const conn = await resolveConn(ctx);
|
|
529
748
|
const { session, release } = await acquireSession(conn);
|
|
530
749
|
try {
|
|
531
|
-
const imgResult = await session
|
|
750
|
+
const imgResult = await execPrivileged(session, IMAGE_INSPECT_CMD);
|
|
532
751
|
const image = imageForRestart(imgResult.stdout);
|
|
533
752
|
if (!image.ok) return errText(image.error);
|
|
534
|
-
const result = await
|
|
535
|
-
|
|
753
|
+
const result = await execPrivileged(
|
|
754
|
+
session,
|
|
755
|
+
gatewayRunCommand({ image: image.value, stateDir: STATE_DIR_HOST_LINUX })
|
|
536
756
|
);
|
|
537
757
|
if (result.code !== 0) {
|
|
538
758
|
return errText(`Gateway restart failed: ${result.stderr}`);
|
|
@@ -578,29 +798,6 @@ function makeProgressEmitter(server, progressToken) {
|
|
|
578
798
|
};
|
|
579
799
|
}
|
|
580
800
|
|
|
581
|
-
// src/mcp/tools/_trim.ts
|
|
582
|
-
import { writeFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
583
|
-
import path2 from "path";
|
|
584
|
-
var MAX_BYTES2 = 8 * 1024;
|
|
585
|
-
function trimForMcp(output, stackName) {
|
|
586
|
-
try {
|
|
587
|
-
const dir = path2.join(getConfigDir(), "state");
|
|
588
|
-
mkdirSync2(dir, { recursive: true });
|
|
589
|
-
writeFileSync(path2.join(dir, `${stackName}.last-run.json`), JSON.stringify({ output }), "utf-8");
|
|
590
|
-
} catch {
|
|
591
|
-
}
|
|
592
|
-
if (Buffer.byteLength(output) <= MAX_BYTES2) {
|
|
593
|
-
return { content: output, truncated: false };
|
|
594
|
-
}
|
|
595
|
-
const trimmed = output.slice(0, MAX_BYTES2);
|
|
596
|
-
return {
|
|
597
|
-
content: trimmed + `
|
|
598
|
-
|
|
599
|
-
[Output truncated at 8KB. Full output available at clawops://stacks/${stackName}/last-run]`,
|
|
600
|
-
truncated: true
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
|
|
604
801
|
// src/mcp/tools/cli/up.ts
|
|
605
802
|
var VALID_INSTANCE_TYPES = ["micro", "small", "medium", "large", "gpu"];
|
|
606
803
|
async function handleUp(input, server) {
|
|
@@ -627,7 +824,7 @@ async function handleUp(input, server) {
|
|
|
627
824
|
);
|
|
628
825
|
}
|
|
629
826
|
const { localOpts } = stackConfig;
|
|
630
|
-
const { localBootstrap } = await import("./bootstrap-
|
|
827
|
+
const { localBootstrap } = await import("./bootstrap-DMRPWJGN.js");
|
|
631
828
|
const ac = new AbortController();
|
|
632
829
|
const state = await localBootstrap({
|
|
633
830
|
host: localOpts.host,
|
|
@@ -850,7 +1047,7 @@ async function handleTaskStatus(input, _server) {
|
|
|
850
1047
|
|
|
851
1048
|
// src/mcp/tools/cli/monitor.ts
|
|
852
1049
|
async function handleMonitor(input, _server) {
|
|
853
|
-
const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-
|
|
1050
|
+
const { acquireSession: acquireSession2, drainPool: drainPool2 } = await import("./pool-EX4ZTA5P.js");
|
|
854
1051
|
const ctx = buildContext({ stack: input.stackName });
|
|
855
1052
|
const tailLines = input.tailLines ?? 5;
|
|
856
1053
|
let conn;
|
|
@@ -1015,6 +1212,7 @@ function makeEntry(schema, annotations, handler) {
|
|
|
1015
1212
|
}
|
|
1016
1213
|
var TOOL_REGISTRY = {
|
|
1017
1214
|
clawops_status: makeEntry(clawops_statusSchema, clawops_statusAnnotations, handleStatus),
|
|
1215
|
+
clawops_doctor: makeEntry(clawops_doctorSchema, clawops_doctorAnnotations, handleDoctor),
|
|
1018
1216
|
clawops_logs_tail: makeEntry(clawops_logs_tailSchema, clawops_logs_tailAnnotations, handleLogsTail),
|
|
1019
1217
|
clawops_monitor: makeEntry(clawops_monitorSchema, clawops_monitorAnnotations, handleMonitor),
|
|
1020
1218
|
clawops_stacks_list: makeEntry(clawops_stacks_listSchema, clawops_stacks_listAnnotations, handleStacksList),
|
|
@@ -1027,12 +1225,12 @@ var TOOL_REGISTRY = {
|
|
|
1027
1225
|
clawops_config_set: makeEntry(clawops_config_setSchema, clawops_config_setAnnotations, handleConfigSet),
|
|
1028
1226
|
clawops_config_unset: makeEntry(clawops_config_unsetSchema, clawops_config_unsetAnnotations, handleConfigUnset),
|
|
1029
1227
|
clawops_config_validate: makeEntry(clawops_config_validateSchema, clawops_config_validateAnnotations, handleConfigValidate),
|
|
1030
|
-
clawops_agents_restart: makeEntry(clawops_agents_restartSchema, clawops_agents_restartAnnotations, handleAgentsRestart),
|
|
1031
1228
|
clawops_gateway_restart: makeEntry(clawops_gateway_restartSchema, clawops_gateway_restartAnnotations, handleGatewayRestart),
|
|
1032
1229
|
clawops_workflow_deploy_app: makeEntry(clawops_workflow_deploy_appSchema, clawops_workflow_deploy_appAnnotations, handleWorkflowDeployApp),
|
|
1033
1230
|
clawops_workflow_recover: makeEntry(clawops_workflow_recoverSchema, clawops_workflow_recoverAnnotations, handleWorkflowRecover),
|
|
1034
1231
|
clawops_task_status: makeEntry(clawops_task_statusSchema, clawops_task_statusAnnotations, handleTaskStatus)
|
|
1035
1232
|
};
|
|
1233
|
+
var TOOL_NAMES = Object.keys(TOOL_REGISTRY);
|
|
1036
1234
|
function resolveEnabledTools(opts) {
|
|
1037
1235
|
let names;
|
|
1038
1236
|
if (opts.readOnly) {
|
|
@@ -1258,7 +1456,7 @@ clawops_gateway_restart({ stackName: "${s}" })
|
|
|
1258
1456
|
### Agents not running
|
|
1259
1457
|
\`\`\`
|
|
1260
1458
|
clawops_agents_list({ stackName: "${s}" })
|
|
1261
|
-
|
|
1459
|
+
clawops_gateway_restart({ stackName: "${s}" })
|
|
1262
1460
|
\`\`\`
|
|
1263
1461
|
|
|
1264
1462
|
### Bootstrap failed or incomplete
|
|
@@ -1325,7 +1523,7 @@ function bearerFrom(header) {
|
|
|
1325
1523
|
return match?.[1]?.trim();
|
|
1326
1524
|
}
|
|
1327
1525
|
async function serveMcp(opts) {
|
|
1328
|
-
const { version } = await import("./package-
|
|
1526
|
+
const { version } = await import("./package-3HUW57XH.js");
|
|
1329
1527
|
const makeServer = () => {
|
|
1330
1528
|
const server = new McpServer({ name: "clawops", version });
|
|
1331
1529
|
registerTools(server, opts);
|
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CONFIG_FILENAME,
|
|
4
|
+
CONTAINER_UID,
|
|
5
|
+
GATEWAY_PORT,
|
|
6
|
+
STATE_DIR_HOST_LINUX,
|
|
7
|
+
gatewayRunCommand
|
|
8
|
+
} from "./chunk-6TIYVHPX.js";
|
|
2
9
|
|
|
3
10
|
// src/providers/startup.ts
|
|
4
11
|
function makeStartupScript(opts) {
|
|
5
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
openclawVersion,
|
|
14
|
+
os,
|
|
15
|
+
bedrockEnabled = false,
|
|
16
|
+
publishGateway = "loopback",
|
|
17
|
+
gatewayPort = GATEWAY_PORT
|
|
18
|
+
} = opts;
|
|
6
19
|
const dockerDistro = os === "ubuntu" ? "ubuntu" : "debian";
|
|
7
20
|
const bedrockEnvBlock = bedrockEnabled ? makeBedrockEnvBlock() : "";
|
|
8
21
|
return `#!/bin/bash
|
|
@@ -53,27 +66,58 @@ if [ ! -s "\${OPENCLAW_ENV_FILE}" ]; then
|
|
|
53
66
|
chown clawops:clawops "\${OPENCLAW_ENV_FILE}"
|
|
54
67
|
fi
|
|
55
68
|
|
|
69
|
+
# \u2500\u2500 State directory \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
70
|
+
# One directory holds config, the SQLite state and any plugins installed below.
|
|
71
|
+
# Owned NUMERICALLY by the uid the container runs as: useradd clawops gets 1001 on
|
|
72
|
+
# Ubuntu 24.04 (the ubuntu user already holds 1000), and a 1001-owned state dir makes the
|
|
73
|
+
# gateway exit 1 with EACCES on its own SQLite WAL. There is no degraded mode.
|
|
74
|
+
OPENCLAW_STATE_DIR=${STATE_DIR_HOST_LINUX}
|
|
75
|
+
OPENCLAW_CONFIG="\${OPENCLAW_STATE_DIR}/${CONFIG_FILENAME}"
|
|
76
|
+
mkdir -p "\${OPENCLAW_STATE_DIR}"
|
|
77
|
+
|
|
78
|
+
# Migrate a pre-2.0 deployment: the config used to be a bare file in the service user's
|
|
79
|
+
# home. Without this an in-place upgrade comes up with no configuration at all. Guarded on
|
|
80
|
+
# the target being absent, so re-running provisioning never clobbers a later edit.
|
|
81
|
+
OPENCLAW_LEGACY_CONFIG=/home/clawops/openclaw.json
|
|
82
|
+
if [ -f "\${OPENCLAW_LEGACY_CONFIG}" ] && [ ! -f "\${OPENCLAW_CONFIG}" ]; then
|
|
83
|
+
cp -p "\${OPENCLAW_LEGACY_CONFIG}" "\${OPENCLAW_CONFIG}"
|
|
84
|
+
mv "\${OPENCLAW_LEGACY_CONFIG}" "\${OPENCLAW_LEGACY_CONFIG}.migrated"
|
|
85
|
+
echo "clawops: migrated config from \${OPENCLAW_LEGACY_CONFIG}"
|
|
86
|
+
OPENCLAW_MIGRATED=1
|
|
87
|
+
fi
|
|
88
|
+
|
|
56
89
|
# \u2500\u2500 Default config (apply.ts will overwrite with plan overlay) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
57
|
-
OPENCLAW_CONFIG=/home/clawops/openclaw.json
|
|
58
90
|
if [ ! -f "\${OPENCLAW_CONFIG}" ]; then
|
|
59
91
|
cat > "\${OPENCLAW_CONFIG}" <<'OPENCLAWJSON'
|
|
60
|
-
{"meta":{"lastTouchedVersion":"2026.
|
|
92
|
+
{"meta":{"lastTouchedVersion":"2026.9"},"gateway":{"mode":"local","port":${gatewayPort},"auth":{"mode":"token"}},"models":{},"channels":{}}
|
|
61
93
|
OPENCLAWJSON
|
|
62
|
-
|
|
94
|
+
fi
|
|
95
|
+
|
|
96
|
+
# Numeric, and last, so it covers the config and anything migrated above.
|
|
97
|
+
chown -R ${CONTAINER_UID}:${CONTAINER_UID} "\${OPENCLAW_STATE_DIR}"
|
|
98
|
+
|
|
99
|
+
# A 1.x config has no gateway.mode, and clawops no longer passes --allow-unconfigured \u2014
|
|
100
|
+
# so a migrated deployment would exit 78 with "existing config is missing gateway.mode".
|
|
101
|
+
# Normalised with OpenClaw's own tooling rather than by editing JSON in shell: it is
|
|
102
|
+
# schema-aware, writes atomically, and applies OpenClaw's internal config migrations at the
|
|
103
|
+
# same time. Runs after the chown so the container can write.
|
|
104
|
+
if [ "\${OPENCLAW_MIGRATED:-0}" = "1" ]; then
|
|
105
|
+
docker run --rm -v "\${OPENCLAW_STATE_DIR}":/home/node/.openclaw \\
|
|
106
|
+
ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION} \\
|
|
107
|
+
openclaw config set gateway.mode local || \\
|
|
108
|
+
echo "clawops: WARNING could not set gateway.mode; gateway may refuse to start" >&2
|
|
63
109
|
fi
|
|
64
110
|
|
|
65
111
|
# \u2500\u2500 Start OpenClaw container \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
${bedrockEnvBlock} ghcr.io/openclaw/openclaw:\${OPENCLAW_VERSION} \\
|
|
76
|
-
node openclaw.mjs gateway run --allow-unconfigured --port 18789
|
|
112
|
+
# Built by src/openclaw/runtime.ts, so this cannot drift from the restart paths.
|
|
113
|
+
${gatewayRunCommand({
|
|
114
|
+
image: "ghcr.io/openclaw/openclaw:${OPENCLAW_VERSION}",
|
|
115
|
+
stateDir: '"${OPENCLAW_STATE_DIR}"',
|
|
116
|
+
envFilePath: '"${OPENCLAW_ENV_FILE}"',
|
|
117
|
+
extraArgs: bedrockEnvBlock.trim(),
|
|
118
|
+
publish: publishGateway,
|
|
119
|
+
port: gatewayPort
|
|
120
|
+
})}
|
|
77
121
|
`;
|
|
78
122
|
}
|
|
79
123
|
function makeBedrockEnvBlock() {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
resolveSpecDir
|
|
4
|
+
} from "./chunk-JW5A7E3X.js";
|
|
2
5
|
import {
|
|
3
6
|
err,
|
|
4
7
|
ok
|
|
5
8
|
} from "./chunk-EI7CQQ7D.js";
|
|
6
|
-
import {
|
|
7
|
-
resolveSpecDir
|
|
8
|
-
} from "./chunk-PVUVI35Y.js";
|
|
9
9
|
|
|
10
10
|
// src/openclaw/versions.ts
|
|
11
11
|
import { readFileSync } from "fs";
|
|
@@ -28,7 +28,8 @@ function loadVersionSpec(yaml) {
|
|
|
28
28
|
min: raw.support.min,
|
|
29
29
|
max: raw.support.max ?? "",
|
|
30
30
|
recommended: raw.support.recommended ?? raw.support.min
|
|
31
|
-
}
|
|
31
|
+
},
|
|
32
|
+
...raw.runtime ? { runtime: raw.runtime } : {}
|
|
32
33
|
};
|
|
33
34
|
return _spec;
|
|
34
35
|
} catch (e) {
|
|
@@ -77,9 +78,7 @@ function checkVersion(version, support) {
|
|
|
77
78
|
reason: "too-new",
|
|
78
79
|
supportedRange: describeRange(support),
|
|
79
80
|
message: `OpenClaw ${version} is newer than this clawops release supports (${describeRange(support)}).
|
|
80
|
-
|
|
81
|
-
Use clawops 2.x for OpenClaw 2026.9.1 and later: npm install -g @clawops/cli@latest
|
|
82
|
-
To stay on this line, pin an OpenClaw version at or below ${support.max}.`
|
|
81
|
+
Pin an OpenClaw version at or below ${support.max}, or upgrade clawops.`
|
|
83
82
|
});
|
|
84
83
|
}
|
|
85
84
|
return ok(version);
|