@atbash/cli 0.6.0-dev.1 → 0.6.1-dev.2
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 +57 -8
- package/dist/bin/atbash.js +6 -3
- package/dist/bin/atbash.js.map +1 -1
- package/dist/commands/config-cmd.js +15 -15
- package/dist/commands/config-cmd.js.map +1 -1
- package/dist/commands/connect.d.ts +84 -24
- package/dist/commands/connect.js +289 -73
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/held.js +2 -2
- package/dist/commands/held.js.map +1 -1
- package/dist/commands/history.js +1 -1
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/judge.js +2 -2
- package/dist/commands/judge.js.map +1 -1
- package/dist/commands/mcp-cmd.d.ts +13 -0
- package/dist/commands/mcp-cmd.js +216 -0
- package/dist/commands/mcp-cmd.js.map +1 -0
- package/dist/commands/policy.js +1 -1
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/setup.d.ts +390 -8
- package/dist/commands/setup.js +1873 -164
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/stats.js +1 -1
- package/dist/commands/stats.js.map +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/tier.js +1 -1
- package/dist/commands/tier.js.map +1 -1
- package/dist/commands/tools.js +3 -3
- package/dist/commands/tools.js.map +1 -1
- package/dist/commands/whoami.js +1 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/shared/atbash-targets.d.ts +49 -0
- package/dist/shared/atbash-targets.js +63 -0
- package/dist/shared/atbash-targets.js.map +1 -0
- package/dist/shared/openclaw-runtime.d.ts +221 -0
- package/dist/shared/openclaw-runtime.js +476 -0
- package/dist/shared/openclaw-runtime.js.map +1 -0
- package/dist/shared/win-exec.d.ts +63 -0
- package/dist/shared/win-exec.js +147 -0
- package/dist/shared/win-exec.js.map +1 -0
- package/package.json +3 -2
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.detectChannel = detectChannel;
|
|
7
|
+
exports.updateCommand = updateCommand;
|
|
8
|
+
exports.detectOpenclaw = detectOpenclaw;
|
|
9
|
+
exports.writeStrategy = writeStrategy;
|
|
10
|
+
exports.supportStatus = supportStatus;
|
|
11
|
+
exports.verifyOpenclawPlugin = verifyOpenclawPlugin;
|
|
12
|
+
exports.pluginEntryAcceptsHooks = pluginEntryAcceptsHooks;
|
|
13
|
+
exports.validateConfig = validateConfig;
|
|
14
|
+
/**
|
|
15
|
+
* What the OpenClaw on THIS machine actually is, and what it can actually do.
|
|
16
|
+
*
|
|
17
|
+
* WHY THIS MODULE EXISTS — it is the fix for a whole class of bug, not one bug.
|
|
18
|
+
*
|
|
19
|
+
* `atbash setup` used to assert OpenClaw's shape from a single machine we happened
|
|
20
|
+
* to have installed. It hardcoded `~/.openclaw/extensions/<id>` as the plugin
|
|
21
|
+
* store and wrote that path into `plugins.load.paths`. On the box it was written
|
|
22
|
+
* against — 2026.2.1, brew formula — that was correct. On a fresh 2026.6.6 it is
|
|
23
|
+
* a path OpenClaw never creates, and a `load.paths` entry naming a missing
|
|
24
|
+
* directory fails the ENTIRE config, which then takes down the
|
|
25
|
+
* `openclaw plugins install` in the same run. The command broke its own install
|
|
26
|
+
* step and left behind a dangling entry that `openclaw doctor --fix` offers to
|
|
27
|
+
* delete — silently un-governing the agent.
|
|
28
|
+
*
|
|
29
|
+
* The lesson is not "update the hardcoded path". It is that we cannot know what
|
|
30
|
+
* OpenClaw a user has:
|
|
31
|
+
*
|
|
32
|
+
* - It ships on at least three channels, each on its own version number. When
|
|
33
|
+
* this was written: brew formula `openclaw-cli` 2026.7.1, brew cask
|
|
34
|
+
* `openclaw` 2026.8.1, npm-global 2026.8.2, plus an npm `extended-stable`
|
|
35
|
+
* tag on a fourth number. "The latest OpenClaw" does not identify a build.
|
|
36
|
+
* - It moved its plugin store twice in six months, and the migration is
|
|
37
|
+
* destructive (`plugins/installs.json` → `installs.json.migrated`).
|
|
38
|
+
* - Keys change meaning: `plugins.allow` was repurposed to gate bundled
|
|
39
|
+
* provider discovery; `plugins.entries.<id>.hooks` went from a FATAL unknown
|
|
40
|
+
* key on 2026.2.1 to a valid one on 2026.6.6.
|
|
41
|
+
*
|
|
42
|
+
* So: ASK, never assume. Everything here is probed from the binary on the box,
|
|
43
|
+
* and version numbers are used for REPORTING, not for deciding. Capability
|
|
44
|
+
* detection is what makes a range of versions supportable instead of one —
|
|
45
|
+
* comparing version strings is how you end up shipping a table that goes stale
|
|
46
|
+
* the next time OpenClaw releases.
|
|
47
|
+
*/
|
|
48
|
+
const child_process_1 = require("child_process");
|
|
49
|
+
const fs_1 = __importDefault(require("fs"));
|
|
50
|
+
const path_1 = __importDefault(require("path"));
|
|
51
|
+
const win_exec_1 = require("./win-exec");
|
|
52
|
+
const NO_CAPS = { patch: false, validate: false, schema: false, configFile: false };
|
|
53
|
+
/** Bounded probe of a command. Never throws; a missing binary is a normal answer. */
|
|
54
|
+
function probe(command, args) {
|
|
55
|
+
const r = (0, win_exec_1.spawnPortable)(command, args, { encoding: "utf8", timeout: 20000 });
|
|
56
|
+
const out = `${r.stdout ?? ""}${r.stderr ?? ""}`;
|
|
57
|
+
return { ok: !r.error && r.status === 0, out };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Where the `openclaw` on PATH came from.
|
|
61
|
+
*
|
|
62
|
+
* Path-based rather than asked of a package manager, because shelling out to
|
|
63
|
+
* brew and npm to answer a question we can read off a path is slow and can hang
|
|
64
|
+
* on a bad network — and this runs before we have written anything, where a
|
|
65
|
+
* hang looks like the command being broken.
|
|
66
|
+
*
|
|
67
|
+
* ⚠️ The npm case is not hypothetical or unusual: the machine that reported the
|
|
68
|
+
* original failure ran `/Users/m4/.nvm/versions/node/v24.13.0/bin/openclaw`,
|
|
69
|
+
* so `brew info openclaw-cli` there said "Not installed" while OpenClaw was
|
|
70
|
+
* plainly running. Telling that user `brew upgrade openclaw-cli` would have been
|
|
71
|
+
* useless, which is the whole reason this returns a channel at all.
|
|
72
|
+
*/
|
|
73
|
+
function detectChannel(binPath) {
|
|
74
|
+
if (!binPath)
|
|
75
|
+
return "unknown";
|
|
76
|
+
const p = binPath.replace(/\\/g, "/");
|
|
77
|
+
// Follows the formula's Cellar layout, and the /opt/homebrew symlink to it.
|
|
78
|
+
if (/\/Cellar\/openclaw-cli\//.test(p) || /\/(opt\/homebrew|usr\/local)\/(bin|Cellar)\//.test(p))
|
|
79
|
+
return "brew-formula";
|
|
80
|
+
if (/\/Applications\/OpenClaw\.app\//.test(p))
|
|
81
|
+
return "brew-cask";
|
|
82
|
+
// nvm, fnm, volta, asdf and a plain global prefix all land under a node dir or
|
|
83
|
+
// an npm/lib/node_modules path.
|
|
84
|
+
if (/\/(\.nvm|\.fnm|\.volta|\.asdf)\//.test(p) || /\/lib\/node_modules\//.test(p) || /\/node\/v?\d/.test(p))
|
|
85
|
+
return "npm";
|
|
86
|
+
return "unknown";
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The command that updates THIS install, or undefined when we cannot tell.
|
|
90
|
+
*
|
|
91
|
+
* Returning undefined is deliberate and better than a plausible guess: a user
|
|
92
|
+
* told to run `brew upgrade openclaw-cli` on an npm install gets "No available
|
|
93
|
+
* formula", concludes our instructions are wrong, and is no closer to an
|
|
94
|
+
* upgrade. `openclaw update` is the honest fallback where the channel is
|
|
95
|
+
* unknown — it is OpenClaw's own self-updater, and 2026.6.6's `doctor` already
|
|
96
|
+
* recommends exactly that ("Run `openclaw update` to update via your package
|
|
97
|
+
* manager (npm/pnpm)").
|
|
98
|
+
*/
|
|
99
|
+
function updateCommand(channel) {
|
|
100
|
+
switch (channel) {
|
|
101
|
+
case "brew-formula":
|
|
102
|
+
return "brew upgrade openclaw-cli";
|
|
103
|
+
case "brew-cask":
|
|
104
|
+
return "brew upgrade --cask openclaw";
|
|
105
|
+
case "npm":
|
|
106
|
+
return "openclaw update";
|
|
107
|
+
default:
|
|
108
|
+
return "openclaw update";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Parse the subcommand list out of `openclaw config --help`.
|
|
113
|
+
*
|
|
114
|
+
* Reads the two-space-indented ` <name> <description>` rows commander prints
|
|
115
|
+
* under `Commands:`. A capability we cannot see is reported as absent, which is
|
|
116
|
+
* the safe direction: we fall back to the path that works everywhere rather than
|
|
117
|
+
* invoking a subcommand that exits "unknown command" mid-run.
|
|
118
|
+
*/
|
|
119
|
+
function parseConfigCaps(help) {
|
|
120
|
+
const lines = help.split(/\r?\n/);
|
|
121
|
+
// Only the `Commands:` section. `Options:` lists flags, and the trailing
|
|
122
|
+
// `Docs:` line is prose — neither describes a subcommand.
|
|
123
|
+
const start = lines.findIndex((l) => /^\s*Commands:\s*$/.test(l));
|
|
124
|
+
const section = start === -1 ? lines : lines.slice(start + 1);
|
|
125
|
+
const names = new Set(section
|
|
126
|
+
// EXACTLY two spaces of indent. Commander puts subcommand names at 2 and
|
|
127
|
+
// wraps long descriptions much deeper, and 2026.7.1's `config --help` is
|
|
128
|
+
// full of those continuations — `patch` and `set` each carry several lines
|
|
129
|
+
// of examples like "openclaw config patch --stdin". A looser `^ {2,}`
|
|
130
|
+
// could read a wrapped line as a subcommand and invent a capability that
|
|
131
|
+
// does not exist, which then gets invoked mid-run and exits "unknown
|
|
132
|
+
// command" after earlier steps have already written.
|
|
133
|
+
.map((line) => /^ {2}(?! )([a-z][a-z-]*)\s{2,}\S/.exec(line)?.[1])
|
|
134
|
+
.filter((n) => Boolean(n)));
|
|
135
|
+
return {
|
|
136
|
+
patch: names.has("patch"),
|
|
137
|
+
validate: names.has("validate"),
|
|
138
|
+
schema: names.has("schema"),
|
|
139
|
+
configFile: names.has("file"),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The version out of `openclaw --version`, in either format OpenClaw has used.
|
|
144
|
+
*
|
|
145
|
+
* ⚠️ THE OUTPUT FORMAT CHANGED TOO, and this function's first version only
|
|
146
|
+
* handled the newer one — the exact mistake this module exists to prevent,
|
|
147
|
+
* committed inside the module itself. Caught only by running a real old binary:
|
|
148
|
+
*
|
|
149
|
+
* 2026.2.1 → "2026.2.1\n" (bare, verified with od -c)
|
|
150
|
+
* 2026.7.1 → "OpenClaw 2026.7.1 (2d2ddc4)"
|
|
151
|
+
*
|
|
152
|
+
* A regex anchored on the word "OpenClaw" returned undefined for every 2026.2.x
|
|
153
|
+
* machine. Behavior did not break — capabilities drive every decision here, which
|
|
154
|
+
* is the point of the design — but the operator lost the version from the message
|
|
155
|
+
* that tells them what was detected, and any future version-dependent reporting
|
|
156
|
+
* would have silently had nothing to work with.
|
|
157
|
+
*
|
|
158
|
+
* Neither format can be assumed, so try the labelled form first and fall back to
|
|
159
|
+
* a line that is nothing but a version. Both are matched by PATTERN rather than
|
|
160
|
+
* by line position: a loaded Atbash plugin prints `[atbash] plugin loaded` and
|
|
161
|
+
* `guard manager ready …` ahead of anything else on stdout.
|
|
162
|
+
*/
|
|
163
|
+
function parseVersion(out) {
|
|
164
|
+
const labelled = /OpenClaw\s+v?(\d[\w.-]*)(?:\s+\(([0-9a-f]{4,})\))?/i.exec(out);
|
|
165
|
+
if (labelled)
|
|
166
|
+
return { version: labelled[1], ...(labelled[2] ? { build: labelled[2] } : {}) };
|
|
167
|
+
// A line holding only a version, e.g. 2026.2.1 — with an optional build hash,
|
|
168
|
+
// since a future build could print that form too.
|
|
169
|
+
const bare = /^[ \t]*v?(\d{4}\.\d+[\w.-]*)(?:[ \t]+\(([0-9a-f]{4,})\))?[ \t]*$/m.exec(out);
|
|
170
|
+
if (bare)
|
|
171
|
+
return { version: bare[1], ...(bare[2] ? { build: bare[2] } : {}) };
|
|
172
|
+
return {};
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* The openclaw to inspect: an explicit path as given, a bare name off PATH.
|
|
176
|
+
*
|
|
177
|
+
* ⚠️ An explicit path must NOT go through `where`/`which`. `--runtime` and the
|
|
178
|
+
* tests both pass a full path, and `where C:\dir\openclaw.cmd` does not answer
|
|
179
|
+
* "yes, that file" — `where` takes a NAME to search for, so a path came back as
|
|
180
|
+
* no match and a caller who had named the binary outright was told none existed.
|
|
181
|
+
*/
|
|
182
|
+
function resolveBinary(binary) {
|
|
183
|
+
if (path_1.default.isAbsolute(binary) || binary.includes("/") || binary.includes("\\")) {
|
|
184
|
+
return fs_1.default.existsSync(binary) ? binary : undefined;
|
|
185
|
+
}
|
|
186
|
+
const which = (0, child_process_1.spawnSync)(process.platform === "win32" ? "where" : "which", [binary], { encoding: "utf8" });
|
|
187
|
+
if (which.status !== 0)
|
|
188
|
+
return undefined;
|
|
189
|
+
// `where` reports EVERY match, and on Windows npm writes two: the extensionless
|
|
190
|
+
// Git Bash script first, then the `.cmd` Windows can actually run. Taking line
|
|
191
|
+
// one picked the unrunnable one — see pickExecutable.
|
|
192
|
+
return (0, win_exec_1.pickExecutable)((which.stdout ?? "").split(/\r?\n/));
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Inspect the OpenClaw on this machine. Read-only: probes `--version` and
|
|
196
|
+
* `config --help`, writes nothing, and never throws.
|
|
197
|
+
*/
|
|
198
|
+
function detectOpenclaw(binary = "openclaw") {
|
|
199
|
+
const binPath = resolveBinary(binary);
|
|
200
|
+
if (!binPath)
|
|
201
|
+
return { present: false, channel: "unknown", caps: { ...NO_CAPS } };
|
|
202
|
+
const channel = detectChannel(binPath);
|
|
203
|
+
// Probe the RESOLVED path, not the name we were handed: on Windows those are
|
|
204
|
+
// different files, and only one of them runs.
|
|
205
|
+
const ver = probe(binPath, ["--version"]);
|
|
206
|
+
const parsedVersion = parseVersion(ver.out);
|
|
207
|
+
const help = probe(binPath, ["config", "--help"]);
|
|
208
|
+
return {
|
|
209
|
+
present: true,
|
|
210
|
+
binPath,
|
|
211
|
+
channel,
|
|
212
|
+
...parsedVersion,
|
|
213
|
+
caps: help.out ? parseConfigCaps(help.out) : { ...NO_CAPS },
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
function writeStrategy(rt) {
|
|
217
|
+
return rt.caps.patch ? "openclaw-patch" : "hand-merge";
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Whether this OpenClaw is one we can wire at all, and what to say if not.
|
|
221
|
+
*
|
|
222
|
+
* DELIBERATELY PERMISSIVE. The floor is "there is an OpenClaw here", not "it has
|
|
223
|
+
* the newest config commands", because 2026.2.x installs are real — the box every
|
|
224
|
+
* CLI attestation was verified on is one — and the minimal entry-only config we
|
|
225
|
+
* now write was confirmed to load there with the plugin reporting
|
|
226
|
+
* `[atbash] plugin loaded` and `guard manager ready`. Narrowing support to the
|
|
227
|
+
* versions we happen to run is the mistake that produced this whole module.
|
|
228
|
+
*
|
|
229
|
+
* So an older build is a DEGRADED path with an offer, never a refusal: it gets
|
|
230
|
+
* the hand-merge, and the reason to upgrade is stated once, with the command for
|
|
231
|
+
* its own channel. Refusal is reserved for "no OpenClaw at all", where there is
|
|
232
|
+
* genuinely nothing to configure.
|
|
233
|
+
*/
|
|
234
|
+
function supportStatus(rt) {
|
|
235
|
+
if (!rt.present) {
|
|
236
|
+
return {
|
|
237
|
+
supported: false,
|
|
238
|
+
degraded: false,
|
|
239
|
+
message: "No OpenClaw found on this machine. Nothing was changed. If this agent runs on a different computer, run this command there instead — the plugin has to be installed where the agent actually runs.",
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
const where = rt.version ? `OpenClaw ${rt.version}` : "This OpenClaw";
|
|
243
|
+
if (writeStrategy(rt) === "openclaw-patch") {
|
|
244
|
+
return { supported: true, degraded: false, message: `${where} — supported, and OpenClaw will validate the config change itself.` };
|
|
245
|
+
}
|
|
246
|
+
return {
|
|
247
|
+
supported: true,
|
|
248
|
+
degraded: true,
|
|
249
|
+
message: `${where} is supported, but it predates OpenClaw's validated config commands (\`config patch\` / \`config validate\`), so Atbash has to edit the config file directly and cannot ask OpenClaw to check it first. Upgrading is optional and makes this step safer.`,
|
|
250
|
+
upgrade: updateCommand(rt.channel),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Parse JSON out of a stream that also carries log lines.
|
|
255
|
+
*
|
|
256
|
+
* `openclaw plugins list --json` is clean on stdout, but a loaded plugin prints
|
|
257
|
+
* its own startup lines — this project's plugin logs `[atbash] plugin loaded` and
|
|
258
|
+
* `guard manager ready` — and those can interleave. So find the JSON rather than
|
|
259
|
+
* assuming the output begins with it.
|
|
260
|
+
*/
|
|
261
|
+
function parseLooseJson(text) {
|
|
262
|
+
for (let start = text.indexOf("{"); start !== -1; start = text.indexOf("{", start + 1)) {
|
|
263
|
+
// Walk to the matching brace instead of parsing "the rest of the output".
|
|
264
|
+
//
|
|
265
|
+
// ⚠️ Slicing to the end and parsing that is what the first version did, and
|
|
266
|
+
// it failed on real 2026.2.1: its plugin log lines land on the SAME stream
|
|
267
|
+
// AFTER the JSON object, so `JSON.parse` saw `{…}` followed by
|
|
268
|
+
// `[plugins] [atbash] plugin loaded` and threw on the trailing text. The
|
|
269
|
+
// whole verification then reported "unknown" for a plugin that was
|
|
270
|
+
// demonstrably loaded — a false negative on the one check that says whether
|
|
271
|
+
// an agent is governed.
|
|
272
|
+
//
|
|
273
|
+
// String-aware, because a description field legitimately contains braces:
|
|
274
|
+
// this project's own plugin describes itself as mapping OpenClaw's hook, and
|
|
275
|
+
// other plugins embed JSON snippets in their descriptions.
|
|
276
|
+
let depth = 0;
|
|
277
|
+
let inString = false;
|
|
278
|
+
let escaped = false;
|
|
279
|
+
for (let i = start; i < text.length; i++) {
|
|
280
|
+
const ch = text[i];
|
|
281
|
+
if (inString) {
|
|
282
|
+
if (escaped)
|
|
283
|
+
escaped = false;
|
|
284
|
+
else if (ch === "\\")
|
|
285
|
+
escaped = true;
|
|
286
|
+
else if (ch === '"')
|
|
287
|
+
inString = false;
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (ch === '"')
|
|
291
|
+
inString = true;
|
|
292
|
+
else if (ch === "{")
|
|
293
|
+
depth++;
|
|
294
|
+
else if (ch === "}") {
|
|
295
|
+
depth--;
|
|
296
|
+
if (depth === 0) {
|
|
297
|
+
try {
|
|
298
|
+
return JSON.parse(text.slice(start, i + 1));
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
break; // this `{` began something that is not JSON; try the next
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return undefined;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Ask OpenClaw whether the plugin actually loaded — the check setup never had.
|
|
311
|
+
*
|
|
312
|
+
* ⚠️ WHY THIS IS NOT OPTIONAL. Setup used to declare success on the strength of
|
|
313
|
+
* having written what it meant to write. That is not the same claim: on the
|
|
314
|
+
* machine that reported the original failure, the config entry existed and the
|
|
315
|
+
* plugin did not, and OpenClaw said so plainly —
|
|
316
|
+
*
|
|
317
|
+
* plugins.entries.atbash-openclaw: plugin not found: atbash-openclaw
|
|
318
|
+
* (stale config entry ignored; remove it from plugins config)
|
|
319
|
+
*
|
|
320
|
+
* — while setup reported the run as done. An owner reading that summary believes
|
|
321
|
+
* their agent is governed. Nothing is more expensive than a false green on a
|
|
322
|
+
* control boundary, so the last word belongs to OpenClaw, not to us.
|
|
323
|
+
*
|
|
324
|
+
* ⚠️ DO NOT check `hookCount` or `hookNames` here. They are 0 and empty for a
|
|
325
|
+
* perfectly healthy plugin: hooks register when the GATEWAY starts, not when
|
|
326
|
+
* `plugins list` runs. Verified against a plugin that was demonstrably loaded and
|
|
327
|
+
* enforcing — it reports `hookCount: 0`. Treating that as a fault would fail
|
|
328
|
+
* every correct install.
|
|
329
|
+
*/
|
|
330
|
+
function verifyOpenclawPlugin(rt, opts) {
|
|
331
|
+
const binary = opts.binary ?? "openclaw";
|
|
332
|
+
if (!rt.present)
|
|
333
|
+
return { state: "unknown", detail: "OpenClaw is not on this machine." };
|
|
334
|
+
const r = (0, win_exec_1.spawnPortable)(binary, ["plugins", "list", "--json"], {
|
|
335
|
+
encoding: "utf8",
|
|
336
|
+
timeout: 120000,
|
|
337
|
+
env: {
|
|
338
|
+
...process.env,
|
|
339
|
+
...(opts.configPath ? { OPENCLAW_CONFIG_PATH: path_1.default.resolve(opts.configPath) } : {}),
|
|
340
|
+
...(opts.home ? { HOME: opts.home } : {}),
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
if (r.error)
|
|
344
|
+
return { state: "unknown", detail: `\`${binary} plugins list\` could not run: ${r.error.message}` };
|
|
345
|
+
const parsed = parseLooseJson(`${r.stdout ?? ""}${r.stderr ?? ""}`);
|
|
346
|
+
const plugins = parsed?.plugins;
|
|
347
|
+
// No parseable answer is UNKNOWN, never "missing" — reporting a governed agent
|
|
348
|
+
// as ungoverned on a parse failure is its own kind of wrong.
|
|
349
|
+
if (!Array.isArray(plugins)) {
|
|
350
|
+
return { state: "unknown", detail: `Could not read \`${binary} plugins list --json\` output.` };
|
|
351
|
+
}
|
|
352
|
+
const rows = plugins;
|
|
353
|
+
// Preference order, so a stood-down legacy entry cannot shadow the live one —
|
|
354
|
+
// the same rule the dashboard scan needs for the same reason.
|
|
355
|
+
const found = opts.entryIds
|
|
356
|
+
.map((id) => rows.find((p) => typeof p.id === "string" && p.id === id))
|
|
357
|
+
.filter((p) => Boolean(p));
|
|
358
|
+
const row = found.find((p) => p.enabled !== false) ?? found[0];
|
|
359
|
+
if (!row) {
|
|
360
|
+
return {
|
|
361
|
+
state: "missing",
|
|
362
|
+
detail: `OpenClaw does not list the Atbash plugin (looked for: ${opts.entryIds.join(", ")}). The config may name a plugin that is not installed — OpenClaw calls that a stale entry and \`openclaw doctor --fix\` will delete it.`,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const entry = typeof row.id === "string" ? row.id : undefined;
|
|
366
|
+
const version = typeof row.version === "string" ? row.version : undefined;
|
|
367
|
+
const missing = Array.isArray(row.dependencyStatus?.missing)
|
|
368
|
+
? row.dependencyStatus.missing.filter((m) => typeof m === "string")
|
|
369
|
+
: [];
|
|
370
|
+
const base = { entry, ...(version ? { version } : {}), ...(missing.length ? { missingDeps: missing } : {}) };
|
|
371
|
+
if (row.enabled === false) {
|
|
372
|
+
return { ...base, state: "disabled", detail: "The plugin is installed but switched off in the config, so it governs nothing." };
|
|
373
|
+
}
|
|
374
|
+
if (typeof row.status === "string" && row.status !== "loaded") {
|
|
375
|
+
return { ...base, state: "load-error", detail: `OpenClaw reports its status as "${row.status}" rather than "loaded".` };
|
|
376
|
+
}
|
|
377
|
+
// A plugin whose required dependencies are absent loads and then fails at the
|
|
378
|
+
// moment it matters — the SDK it signs with is one of them.
|
|
379
|
+
if (row.dependencyStatus?.requiredInstalled === false || missing.length) {
|
|
380
|
+
return {
|
|
381
|
+
...base,
|
|
382
|
+
state: "load-error",
|
|
383
|
+
detail: `OpenClaw reports required dependencies missing${missing.length ? `: ${missing.join(", ")}` : ""}. The plugin cannot reach the judge without them.`,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
// Right plugin, wrong build. Loads cleanly, hooks fire, and every judge call
|
|
387
|
+
// fails because the agent does not exist on the chain that build targets.
|
|
388
|
+
if (opts.expectedSpec && version) {
|
|
389
|
+
const wantsDev = /@dev$/.test(opts.expectedSpec);
|
|
390
|
+
const isDev = /-dev\./.test(version);
|
|
391
|
+
if (wantsDev !== isDev) {
|
|
392
|
+
return {
|
|
393
|
+
...base,
|
|
394
|
+
state: "load-error",
|
|
395
|
+
detail: `The installed build (${version}) does not match the deployment this agent was onboarded on (${opts.expectedSpec}). It will load and fire its hook, then fail every judge call because the agent does not exist on the chain that build targets.`,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return { ...base, state: "loaded" };
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Ask OpenClaw whether a config is valid, when it can answer.
|
|
403
|
+
*
|
|
404
|
+
* This is the PREFLIGHT the command never had. Setup merged into
|
|
405
|
+
* `~/.openclaw/openclaw.json` without ever checking whether that file currently
|
|
406
|
+
* loads — so on a machine whose config was already broken it added its own entry
|
|
407
|
+
* to an unloadable file, and then its `openclaw plugins install` step died on the
|
|
408
|
+
* pre-existing breakage. Reported as an Atbash failure; it was not one.
|
|
409
|
+
*
|
|
410
|
+
* `undefined` means "cannot tell" (no `validate` on this build) and must be
|
|
411
|
+
* treated as such — not as a pass and not as a failure.
|
|
412
|
+
*/
|
|
413
|
+
/**
|
|
414
|
+
* Does THIS build accept `hooks` on a plugin entry?
|
|
415
|
+
*
|
|
416
|
+
* Asked of `openclaw config schema`, never inferred from a version number or
|
|
417
|
+
* correlated with other capabilities — the whole point of this module.
|
|
418
|
+
*
|
|
419
|
+
* ⚠️ WHY THIS QUESTION EXISTS. `hooks` was ADDED, not removed: it is unknown on
|
|
420
|
+
* 2026.2.1 and valid, optional and undeprecated on 2026.6.6 and 2026.7.1, where
|
|
421
|
+
* an entry accepts `enabled | hooks | subagent | llm | config`. Because the entry
|
|
422
|
+
* schema is `additionalProperties: false`, the same key is FATAL on the old build
|
|
423
|
+
* and legitimate on the new one.
|
|
424
|
+
*
|
|
425
|
+
* Setup never writes `hooks` — nothing under it is required, and it gates hook
|
|
426
|
+
* families this plugin does not use (it registers only `before_tool_call`). But
|
|
427
|
+
* it also used to DELETE an operator's `hooks` block unconditionally, and on a
|
|
428
|
+
* build that accepts the key that is destroying their configuration:
|
|
429
|
+
* `hooks.timeoutMs` bounds this plugin's hook latency "without changing plugin
|
|
430
|
+
* code", which is a deliberate operator choice, not damage to repair.
|
|
431
|
+
*
|
|
432
|
+
* `undefined` means "cannot tell" (no `config schema` on this build). The caller
|
|
433
|
+
* must treat that as NOT supported and strip: keeping an unrecognized key makes
|
|
434
|
+
* OpenClaw refuse the entire config, and losing a timeout knob is a far smaller
|
|
435
|
+
* harm than a machine whose config will not load at all.
|
|
436
|
+
*/
|
|
437
|
+
function pluginEntryAcceptsHooks(rt, binary = "openclaw") {
|
|
438
|
+
if (!rt.present || !rt.caps.schema)
|
|
439
|
+
return undefined;
|
|
440
|
+
const r = (0, win_exec_1.spawnPortable)(binary, ["config", "schema"], {
|
|
441
|
+
encoding: "utf8",
|
|
442
|
+
timeout: 120000,
|
|
443
|
+
maxBuffer: 64 * 1024 * 1024, // the schema is a few MB
|
|
444
|
+
});
|
|
445
|
+
if (r.error || r.status !== 0)
|
|
446
|
+
return undefined;
|
|
447
|
+
const parsed = parseLooseJson(`${r.stdout ?? ""}`);
|
|
448
|
+
const entry = parsed?.properties?.plugins?.properties?.entries?.additionalProperties?.properties;
|
|
449
|
+
if (!entry || typeof entry !== "object")
|
|
450
|
+
return undefined;
|
|
451
|
+
return Object.prototype.hasOwnProperty.call(entry, "hooks");
|
|
452
|
+
}
|
|
453
|
+
function validateConfig(rt, configPath, binary = "openclaw",
|
|
454
|
+
/** The home being configured, when not the process's own — see verifyOpenclawPlugin. */
|
|
455
|
+
home) {
|
|
456
|
+
if (!rt.caps.validate)
|
|
457
|
+
return undefined;
|
|
458
|
+
const r = (0, win_exec_1.spawnPortable)(binary, ["config", "validate"], {
|
|
459
|
+
encoding: "utf8",
|
|
460
|
+
timeout: 60000,
|
|
461
|
+
env: { ...process.env, OPENCLAW_CONFIG_PATH: path_1.default.resolve(configPath), ...(home ? { HOME: home } : {}) },
|
|
462
|
+
});
|
|
463
|
+
if (r.error)
|
|
464
|
+
return undefined;
|
|
465
|
+
const out = `${r.stdout ?? ""}${r.stderr ?? ""}`;
|
|
466
|
+
// 2026.6.6/2026.7.1 print "Config valid: <path>" on success, and
|
|
467
|
+
// "OpenClaw config is invalid: <path>" followed by `× <key>: <reason>` lines.
|
|
468
|
+
const valid = r.status === 0 && /config valid/i.test(out);
|
|
469
|
+
const problems = out
|
|
470
|
+
.split(/\r?\n/)
|
|
471
|
+
.filter((l) => /^\s*[×x✘]\s/.test(l))
|
|
472
|
+
.map((l) => l.replace(/^\s*[×x✘]\s*/, "").trim())
|
|
473
|
+
.join("\n");
|
|
474
|
+
return { valid, problems: problems || (valid ? "" : out.trim()) };
|
|
475
|
+
}
|
|
476
|
+
//# sourceMappingURL=openclaw-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openclaw-runtime.js","sourceRoot":"","sources":["../../src/shared/openclaw-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Launching an npm-installed CLI on Windows, without opening a shell seam.
|
|
3
|
+
*
|
|
4
|
+
* `openclaw`, `hermes`, `npm` and every other Node CLI arrive on Windows as a
|
|
5
|
+
* `.cmd` shim, and a `.cmd` is not an executable — CreateProcess cannot run one.
|
|
6
|
+
* Node used to paper over that; since 18.20.2 / 20.12.2 (the CVE-2024-27980 fix)
|
|
7
|
+
* it refuses outright, and `spawnSync("openclaw.cmd", …)` fails with EINVAL
|
|
8
|
+
* unless the call goes through a shell. That is why every `openclaw` probe
|
|
9
|
+
* answered "could not run" on Windows while working everywhere else: detection
|
|
10
|
+
* did not misread the machine, it never reached the binary at all.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ THE OBVIOUS REPAIR, `shell: true`, IS THE WRONG ONE HERE. It concatenates
|
|
13
|
+
* the command and its arguments into ONE string for `cmd.exe` to re-parse, so a
|
|
14
|
+
* `&`, `|` or `%VAR%` anywhere in an argument — or in the install path, which is
|
|
15
|
+
* read off `where` and was never written by us — stops being data and becomes
|
|
16
|
+
* another command. This tool governs whether an agent's actions are allowed to
|
|
17
|
+
* happen; it does not get to introduce shell injection into its own probe path.
|
|
18
|
+
*
|
|
19
|
+
* So a shim is routed through `cmd.exe /d /s /c` explicitly, with the command
|
|
20
|
+
* line built here: each token is quoted for the child's own CRT parser and then
|
|
21
|
+
* caret-escaped for the `cmd.exe` pass that happens first. `shell: true` skips
|
|
22
|
+
* that second step, which is exactly the part that matters.
|
|
23
|
+
*
|
|
24
|
+
* Everything off Windows, and every real `.exe`, is spawned directly as before.
|
|
25
|
+
*/
|
|
26
|
+
import { type SpawnSyncOptions, type SpawnSyncReturns } from "child_process";
|
|
27
|
+
/** Does this command need `cmd.exe` to run at all? */
|
|
28
|
+
export declare function isWindowsShim(command: string): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Quote one token the way the child's C runtime will parse it back.
|
|
31
|
+
*
|
|
32
|
+
* The backslash rule is the unobvious half: a backslash is literal EXCEPT in the
|
|
33
|
+
* run immediately before a quote, where each one must be doubled. `C:\dir\` as
|
|
34
|
+
* the last token would otherwise end `"C:\dir\"` — an escaped quote, which
|
|
35
|
+
* swallows the rest of the line.
|
|
36
|
+
*/
|
|
37
|
+
export declare function quoteArg(arg: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Caret-escape a token for the `cmd.exe` parse.
|
|
40
|
+
*
|
|
41
|
+
* The quotes added by {@link quoteArg} are escaped too, deliberately. An escaped
|
|
42
|
+
* quote is literal text to `cmd.exe`, so it does not open a quoted section
|
|
43
|
+
* there, and arrives at the child as the plain `"` its CRT is expecting.
|
|
44
|
+
*/
|
|
45
|
+
export declare function escapeForCmd(token: string): string;
|
|
46
|
+
/** Build the full `cmd.exe` command line for a shim and its arguments. */
|
|
47
|
+
export declare function cmdLineFor(command: string, args: readonly string[]): string;
|
|
48
|
+
/**
|
|
49
|
+
* `spawnSync`, but a Windows `.cmd`/`.bat` shim actually runs.
|
|
50
|
+
*
|
|
51
|
+
* Identical to `spawnSync` in every other case — same arguments, same return —
|
|
52
|
+
* so it is a drop-in at the call sites that probe an external CLI.
|
|
53
|
+
*/
|
|
54
|
+
export declare function spawnPortable(command: string, args: readonly string[], options?: SpawnSyncOptions): SpawnSyncReturns<string>;
|
|
55
|
+
/**
|
|
56
|
+
* Pick the runnable match out of `where` output.
|
|
57
|
+
*
|
|
58
|
+
* `where openclaw` lists BOTH the extensionless POSIX-style script npm writes for
|
|
59
|
+
* Git Bash and the `openclaw.cmd` Windows actually uses, in that order. Taking
|
|
60
|
+
* the first line — which is what this did — hands back the one file on the list
|
|
61
|
+
* that Windows cannot execute.
|
|
62
|
+
*/
|
|
63
|
+
export declare function pickExecutable(lines: string[]): string | undefined;
|