@openclaw/plugin-inspector 0.3.10 → 0.3.12
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/CHANGELOG.md +21 -0
- package/README.md +6 -2
- package/package.json +8 -2
- package/src/advanced.js +1 -0
- package/src/api.js +4 -0
- package/src/batch.js +297 -0
- package/src/capture-cli.js +12 -1
- package/src/capture-config.js +49 -1
- package/src/cli.js +103 -6
- package/src/compatibility-report.js +4 -4
- package/src/contract-coverage.js +6 -1
- package/src/contract-probes.js +5 -0
- package/src/execution-results.js +10 -0
- package/src/fixture-summary.js +101 -4
- package/src/index.js +15 -0
- package/src/inspector.js +1 -0
- package/src/issues.js +17 -2
- package/src/report.js +26 -9
- package/src/runtime-reconciliation.js +4 -1
- package/src/sdk-mock.js +153 -1
- package/src/synthetic-entrypoint.js +47 -0
- package/src/synthetic-probes-cli.js +2 -35
- package/src/synthetic-probes.js +59 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.3.12 - 2026-06-09
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Hide maintainer-facing `inspector-gap` findings from author-facing `check`, `ci`, and `batch` output by default, with `--include-inspector-gaps` for internal coverage reports.
|
|
10
|
+
|
|
11
|
+
## 0.3.11 - 2026-05-26
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Classify the latest generated kitchen-sink registrars for meeting notes, node CLI features, hosted media, model catalogs, embedding providers, and session actions.
|
|
16
|
+
- Stop classifying package source entrypoints as missing when the published package provides built runtime entrypoints, and collapse SDK alias findings into a single compat-gap row.
|
|
17
|
+
- Treat compat-gap issues as reconciled contract coverage for their own compatibility record.
|
|
18
|
+
- Count passed synthetic hook probes as runtime coverage so conversation-access and `before_tool_call` inspector gaps close when probe artifacts prove them.
|
|
19
|
+
- Keep mock-SDK synthetic probes in-process so retained hook and registration handlers remain callable, and harden dynamic root SDK mock exports.
|
|
20
|
+
- Synthesize nested manifest config samples for optional object settings with required inner shape.
|
|
21
|
+
- Populate message and agent lifecycle synthetic hook payloads with the fields telemetry plugins commonly read.
|
|
22
|
+
- Resolve plugin manifests from parent directories when runtime capture starts from built `dist` entrypoints.
|
|
23
|
+
- Capture CLIs no longer treat `--output`, `--plugin-root`, or `--sdk` flag values as the positional entrypoint. Thanks @KrasimirKralev.
|
|
24
|
+
- Mock-SDK TypeScript capture now falls back to Node's strip-only parser on Node 26.
|
|
25
|
+
|
|
5
26
|
## 0.3.10 - 2026-05-03
|
|
6
27
|
|
|
7
28
|
### Fixed
|
package/README.md
CHANGED
|
@@ -170,6 +170,7 @@ Copy-ready examples live in:
|
|
|
170
170
|
| `plugin-inspector config` | Print resolved plugin-root config as text or JSON. |
|
|
171
171
|
| `plugin-inspector init` | Write starter config, scripts, and optional GitHub Actions workflow. |
|
|
172
172
|
| `plugin-inspector report` | Run a fixture-suite config with many plugins. |
|
|
173
|
+
| `plugin-inspector batch` | Discover plugin roots under a folder and write one aggregate impact report. |
|
|
173
174
|
| `plugin-inspector capture` | Runtime-capture one entrypoint directly. |
|
|
174
175
|
|
|
175
176
|
Common options:
|
|
@@ -186,6 +187,7 @@ Common options:
|
|
|
186
187
|
| `--mock-sdk` / `--sdk mock` | Use generated SDK and external-package mocks for runtime capture. |
|
|
187
188
|
| `--real-sdk` / `--sdk real` | Use installed real SDK dependencies instead of mocks. |
|
|
188
189
|
| `--allow-execute` | Permit commands that import plugin code. |
|
|
190
|
+
| `--include-inspector-gaps` | Include maintainer-facing scanner coverage gaps in `check`, `ci`, and `batch` output. |
|
|
189
191
|
| `--json` | Print machine-readable JSON to stdout. |
|
|
190
192
|
| `--sarif [path]` | Write SARIF from `check` or `inspect`; `ci` enables this by default. |
|
|
191
193
|
| `--junit [path]` | Write JUnit XML from `check` or `inspect`; `ci` enables this by default. |
|
|
@@ -305,8 +307,9 @@ Important report sections:
|
|
|
305
307
|
| `logs` | Informational inventory and coverage rows. |
|
|
306
308
|
| `decisions` | Maintainer-facing follow-up or compatibility-policy decisions. |
|
|
307
309
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
+
Author-facing `check`, `ci`, and `batch` output hides inspector gaps by default
|
|
311
|
+
because they are scanner coverage backlog, not plugin-author fixes. Pass
|
|
312
|
+
`--include-inspector-gaps` for Crabpot-style maintainer coverage reports.
|
|
310
313
|
|
|
311
314
|
## CI Policy And Shared Reporting Primitives
|
|
312
315
|
|
|
@@ -404,6 +407,7 @@ Stable grouped facades:
|
|
|
404
407
|
| `reports` | Render/write reports and classify issue findings. |
|
|
405
408
|
| `contracts` | Build, render, validate, and write contract captures and coverage. |
|
|
406
409
|
| `ci` | Build summaries, policy reports, execution results, SARIF, and JUnit outputs. |
|
|
410
|
+
| `batch` | Discover plugin roots and aggregate compatibility findings across a corpus. |
|
|
407
411
|
| `runtime` | Build runtime profiles, profile diffs, ref diffs, and import-loop profiles. |
|
|
408
412
|
| `synthetic` | Build and run synthetic probe plans. |
|
|
409
413
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openclaw/plugin-inspector",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.12",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Offline compatibility inspector for OpenClaw plugins.",
|
|
6
6
|
"type": "module",
|
|
@@ -56,7 +56,13 @@
|
|
|
56
56
|
"release:readiness": "npm run release:local && npm run release:crabpot",
|
|
57
57
|
"release:local": "npm run check",
|
|
58
58
|
"release:notes": "node scripts/release-notes.mjs --unreleased",
|
|
59
|
-
"test": "node --test test/*.test.js"
|
|
59
|
+
"test": "node --test test/*.test.js",
|
|
60
|
+
"check:changed": "npm run check",
|
|
61
|
+
"test:changed": "npm test",
|
|
62
|
+
"crabbox:hydrate": "crabbox actions hydrate",
|
|
63
|
+
"crabbox:run": "crabbox run",
|
|
64
|
+
"crabbox:stop": "crabbox stop",
|
|
65
|
+
"crabbox:warmup": "crabbox warmup"
|
|
60
66
|
},
|
|
61
67
|
"keywords": [
|
|
62
68
|
"openclaw",
|
package/src/advanced.js
CHANGED
|
@@ -194,6 +194,7 @@ export {
|
|
|
194
194
|
writeRuntimeCaptureReport,
|
|
195
195
|
} from "./runtime-capture-report.js";
|
|
196
196
|
export { createMockSdkPackage } from "./sdk-mock.js";
|
|
197
|
+
export { runEntrypointSyntheticProbes } from "./synthetic-entrypoint.js";
|
|
197
198
|
export {
|
|
198
199
|
buildSyntheticProbePlan,
|
|
199
200
|
defaultSyntheticHookContexts,
|
package/src/api.js
CHANGED
|
@@ -44,6 +44,7 @@ export async function inspectPluginRoot(options = {}) {
|
|
|
44
44
|
const config = await loadPluginConfig(options);
|
|
45
45
|
return inspectCompatibilityFixtureSet(config, {
|
|
46
46
|
generatedAt: options.generatedAt,
|
|
47
|
+
includeInspectorGaps: options.includeInspectorGaps,
|
|
47
48
|
openclawPath: options.openclawPath,
|
|
48
49
|
executionResults: options.executionResults,
|
|
49
50
|
targetOpenClaw: options.targetOpenClaw,
|
|
@@ -59,6 +60,7 @@ export async function inspectCompatibilityFixtureSetConfig(options = {}) {
|
|
|
59
60
|
const config = await loadFixtureSetConfig(options);
|
|
60
61
|
return inspectCompatibilityFixtureSet(config, {
|
|
61
62
|
generatedAt: options.generatedAt,
|
|
63
|
+
includeInspectorGaps: options.includeInspectorGaps,
|
|
62
64
|
openclawPath: options.openclawPath,
|
|
63
65
|
executionResults: options.executionResults,
|
|
64
66
|
targetOpenClaw: options.targetOpenClaw,
|
|
@@ -113,6 +115,7 @@ export async function buildFixtureSetColdImportReadiness(options = {}) {
|
|
|
113
115
|
options.report ??
|
|
114
116
|
(await inspectCompatibilityFixtureSet(config, {
|
|
115
117
|
generatedAt: options.generatedAt,
|
|
118
|
+
includeInspectorGaps: options.includeInspectorGaps,
|
|
116
119
|
openclawPath: options.openclawPath,
|
|
117
120
|
executionResults: options.executionResults,
|
|
118
121
|
targetOpenClaw: options.targetOpenClaw,
|
|
@@ -145,6 +148,7 @@ export async function buildFixtureSetWorkspacePlan(options = {}) {
|
|
|
145
148
|
options.report ??
|
|
146
149
|
(await inspectCompatibilityFixtureSet(config, {
|
|
147
150
|
generatedAt: options.generatedAt,
|
|
151
|
+
includeInspectorGaps: options.includeInspectorGaps,
|
|
148
152
|
openclawPath: options.openclawPath,
|
|
149
153
|
executionResults: options.executionResults,
|
|
150
154
|
targetOpenClaw: options.targetOpenClaw,
|
package/src/batch.js
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { mkdtemp, readFile, readdir, rm } from "node:fs/promises";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { renderMarkdownTable, writeJsonMarkdownArtifacts } from "./artifacts.js";
|
|
6
|
+
import { runPluginCheck } from "./api.js";
|
|
7
|
+
|
|
8
|
+
const ignoredDirs = new Set([
|
|
9
|
+
".git",
|
|
10
|
+
".hg",
|
|
11
|
+
".svn",
|
|
12
|
+
"node_modules",
|
|
13
|
+
"reports",
|
|
14
|
+
"dist",
|
|
15
|
+
"build",
|
|
16
|
+
".plugin-inspector",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
export async function runBatchAnalysis(options = {}) {
|
|
20
|
+
const rootDir = path.resolve(options.rootDir ?? options.inputDir ?? process.cwd());
|
|
21
|
+
const outDir = options.outDir ?? "reports";
|
|
22
|
+
const outRoot = path.resolve(rootDir, outDir);
|
|
23
|
+
const concurrency = Math.max(1, Math.min(Math.round(options.concurrency ?? 4), 32));
|
|
24
|
+
const keepPluginReports = options.keepPluginReports === true;
|
|
25
|
+
const pluginRoots = await discoverPluginRoots(rootDir);
|
|
26
|
+
const tempRoot = keepPluginReports ? null : await mkdtemp(path.join(os.tmpdir(), "plugin-inspector-batch-"));
|
|
27
|
+
const entries = [];
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
await runWithConcurrency(pluginRoots, concurrency, async (pluginRoot) => {
|
|
31
|
+
const reportsRoot = keepPluginReports
|
|
32
|
+
? path.join(outRoot, "plugins", slugForPath(path.relative(rootDir, pluginRoot)))
|
|
33
|
+
: path.join(tempRoot, slugForPath(path.relative(rootDir, pluginRoot)));
|
|
34
|
+
entries.push(
|
|
35
|
+
await inspectBatchPlugin(pluginRoot, {
|
|
36
|
+
...options,
|
|
37
|
+
rootDir,
|
|
38
|
+
outDir: reportsRoot,
|
|
39
|
+
openclawPath: options.openclawPath,
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
} finally {
|
|
44
|
+
if (tempRoot) await rm(tempRoot, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
entries.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
|
|
48
|
+
const report = buildBatchReport({
|
|
49
|
+
rootDir,
|
|
50
|
+
entries,
|
|
51
|
+
generatedAt: options.generatedAt ?? new Date().toISOString(),
|
|
52
|
+
});
|
|
53
|
+
const paths = await writeBatchReport(report, { outDir: outRoot, check: options.checkArtifacts });
|
|
54
|
+
return { report, paths };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function discoverPluginRoots(rootDir) {
|
|
58
|
+
const roots = [];
|
|
59
|
+
await walk(path.resolve(rootDir));
|
|
60
|
+
roots.sort();
|
|
61
|
+
return roots;
|
|
62
|
+
|
|
63
|
+
async function walk(dir) {
|
|
64
|
+
if (await isPluginRoot(dir)) {
|
|
65
|
+
roots.push(dir);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
if (!entry.isDirectory() || ignoredDirs.has(entry.name)) continue;
|
|
71
|
+
await walk(path.join(dir, entry.name));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function writeBatchReport(report, options = {}) {
|
|
77
|
+
return writeJsonMarkdownArtifacts({
|
|
78
|
+
jsonPath: path.join(options.outDir ?? "reports", "plugin-inspector-batch-report.json"),
|
|
79
|
+
markdownPath: path.join(options.outDir ?? "reports", "plugin-inspector-batch-report.md"),
|
|
80
|
+
json: report,
|
|
81
|
+
markdown: renderBatchMarkdown(report),
|
|
82
|
+
check: options.check,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function buildBatchReport({ rootDir, entries, generatedAt }) {
|
|
87
|
+
const findingFrequency = findingFrequencyRows(entries);
|
|
88
|
+
const summary = {
|
|
89
|
+
pluginCount: entries.length,
|
|
90
|
+
passed: entries.filter((entry) => entry.status === "pass").length,
|
|
91
|
+
failed: entries.filter((entry) => entry.status !== "pass").length,
|
|
92
|
+
pluginsWithErrors: entries.filter((entry) => entry.errorCount > 0).length,
|
|
93
|
+
pluginsWithWarnings: entries.filter((entry) => entry.warningCount > 0).length,
|
|
94
|
+
errorCount: entries.reduce((sum, entry) => sum + entry.errorCount, 0),
|
|
95
|
+
warningCount: entries.reduce((sum, entry) => sum + entry.warningCount, 0),
|
|
96
|
+
findingCodeCount: findingFrequency.length,
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
generatedAt,
|
|
100
|
+
rootDir,
|
|
101
|
+
summary,
|
|
102
|
+
findingFrequency,
|
|
103
|
+
plugins: entries,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function inspectBatchPlugin(pluginRoot, options) {
|
|
108
|
+
try {
|
|
109
|
+
const { report } = await runPluginCheck({
|
|
110
|
+
allowExecution: options.allowExecution,
|
|
111
|
+
capture: options.capture,
|
|
112
|
+
configPath: options.configPath,
|
|
113
|
+
includeInspectorGaps: options.includeInspectorGaps,
|
|
114
|
+
mockSdk: options.mockSdk,
|
|
115
|
+
openclawPath: options.openclawPath,
|
|
116
|
+
outDir: options.outDir,
|
|
117
|
+
pluginRoot,
|
|
118
|
+
});
|
|
119
|
+
const findings = normalizeReportFindings(report);
|
|
120
|
+
return {
|
|
121
|
+
pluginRoot,
|
|
122
|
+
relativePath: path.relative(options.rootDir ?? process.cwd(), pluginRoot) || ".",
|
|
123
|
+
status: report.status,
|
|
124
|
+
packageName: packageNameFromReport(report),
|
|
125
|
+
targetOpenClaw: report.targetOpenClaw,
|
|
126
|
+
errorCount: findings.filter((finding) => finding.kind === "error").length,
|
|
127
|
+
warningCount: findings.filter((finding) => finding.kind === "warning").length,
|
|
128
|
+
findings,
|
|
129
|
+
};
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return {
|
|
132
|
+
pluginRoot,
|
|
133
|
+
relativePath: path.relative(options.rootDir ?? process.cwd(), pluginRoot) || ".",
|
|
134
|
+
status: "error",
|
|
135
|
+
packageName: path.basename(pluginRoot),
|
|
136
|
+
targetOpenClaw: null,
|
|
137
|
+
errorCount: 1,
|
|
138
|
+
warningCount: 0,
|
|
139
|
+
findings: [
|
|
140
|
+
{
|
|
141
|
+
kind: "error",
|
|
142
|
+
code: "plugin-inspector-batch-failure",
|
|
143
|
+
message: error instanceof Error ? error.message : String(error),
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function normalizeReportFindings(report) {
|
|
151
|
+
return [
|
|
152
|
+
...(report.breakages ?? []).map((finding) => normalizeFinding(finding, "error")),
|
|
153
|
+
...(report.issues ?? []).map((finding) =>
|
|
154
|
+
normalizeFinding(
|
|
155
|
+
finding,
|
|
156
|
+
finding.status === "blocking" || finding.severity === "P0" ? "error" : "warning",
|
|
157
|
+
),
|
|
158
|
+
),
|
|
159
|
+
...(report.warnings ?? []).map((finding) => normalizeFinding(finding, "warning")),
|
|
160
|
+
...(report.suggestions ?? []).map((finding) => normalizeFinding(finding, "warning")),
|
|
161
|
+
];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function normalizeFinding(finding, kind) {
|
|
165
|
+
return {
|
|
166
|
+
kind,
|
|
167
|
+
code: finding.code ?? "plugin-inspector-finding",
|
|
168
|
+
severity: finding.severity,
|
|
169
|
+
issueClass: finding.issueClass,
|
|
170
|
+
message: finding.message ?? finding.title ?? "See plugin report.",
|
|
171
|
+
evidence: finding.evidence,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function findingFrequencyRows(entries) {
|
|
176
|
+
const byCode = new Map();
|
|
177
|
+
for (const entry of entries) {
|
|
178
|
+
const seenForPlugin = new Set();
|
|
179
|
+
for (const finding of entry.findings) {
|
|
180
|
+
const current = byCode.get(finding.code) ?? {
|
|
181
|
+
code: finding.code,
|
|
182
|
+
count: 0,
|
|
183
|
+
plugins: 0,
|
|
184
|
+
errors: 0,
|
|
185
|
+
warnings: 0,
|
|
186
|
+
};
|
|
187
|
+
current.count += 1;
|
|
188
|
+
if (!seenForPlugin.has(finding.code)) {
|
|
189
|
+
current.plugins += 1;
|
|
190
|
+
seenForPlugin.add(finding.code);
|
|
191
|
+
}
|
|
192
|
+
if (finding.kind === "error") current.errors += 1;
|
|
193
|
+
else current.warnings += 1;
|
|
194
|
+
byCode.set(finding.code, current);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return [...byCode.values()].sort((a, b) => b.plugins - a.plugins || b.count - a.count);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function renderBatchMarkdown(report) {
|
|
201
|
+
return [
|
|
202
|
+
"# Plugin Inspector Batch Report",
|
|
203
|
+
"",
|
|
204
|
+
`Generated: ${report.generatedAt}`,
|
|
205
|
+
`Root: ${report.rootDir}`,
|
|
206
|
+
"",
|
|
207
|
+
"## Summary",
|
|
208
|
+
"",
|
|
209
|
+
renderMarkdownTable(
|
|
210
|
+
[
|
|
211
|
+
["Plugins", report.summary.pluginCount],
|
|
212
|
+
["Passed", report.summary.passed],
|
|
213
|
+
["Failed", report.summary.failed],
|
|
214
|
+
["Plugins with errors", report.summary.pluginsWithErrors],
|
|
215
|
+
["Plugins with warnings", report.summary.pluginsWithWarnings],
|
|
216
|
+
["Errors", report.summary.errorCount],
|
|
217
|
+
["Warnings", report.summary.warningCount],
|
|
218
|
+
],
|
|
219
|
+
["Metric", "Value"],
|
|
220
|
+
),
|
|
221
|
+
"",
|
|
222
|
+
"## Finding Frequency",
|
|
223
|
+
"",
|
|
224
|
+
report.findingFrequency.length
|
|
225
|
+
? renderMarkdownTable(
|
|
226
|
+
report.findingFrequency.map((row) => [
|
|
227
|
+
row.code,
|
|
228
|
+
row.plugins,
|
|
229
|
+
row.count,
|
|
230
|
+
row.errors,
|
|
231
|
+
row.warnings,
|
|
232
|
+
]),
|
|
233
|
+
["Code", "Plugins", "Findings", "Errors", "Warnings"],
|
|
234
|
+
)
|
|
235
|
+
: "_No findings._",
|
|
236
|
+
"",
|
|
237
|
+
"## Plugins",
|
|
238
|
+
"",
|
|
239
|
+
report.plugins.length
|
|
240
|
+
? renderMarkdownTable(
|
|
241
|
+
report.plugins.map((plugin) => [
|
|
242
|
+
plugin.packageName,
|
|
243
|
+
plugin.relativePath,
|
|
244
|
+
plugin.status,
|
|
245
|
+
plugin.errorCount,
|
|
246
|
+
plugin.warningCount,
|
|
247
|
+
]),
|
|
248
|
+
["Package", "Path", "Status", "Errors", "Warnings"],
|
|
249
|
+
)
|
|
250
|
+
: "_No plugin roots discovered._",
|
|
251
|
+
].join("\n");
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function runWithConcurrency(items, concurrency, worker) {
|
|
255
|
+
let index = 0;
|
|
256
|
+
const runners = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
257
|
+
while (index < items.length) {
|
|
258
|
+
const item = items[index];
|
|
259
|
+
index += 1;
|
|
260
|
+
await worker(item);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
await Promise.all(runners);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function isPluginRoot(dir) {
|
|
267
|
+
if (existsSync(path.join(dir, "plugin-inspector.config.json"))) return true;
|
|
268
|
+
if (existsSync(path.join(dir, ".plugin-inspector.json"))) return true;
|
|
269
|
+
if (existsSync(path.join(dir, "openclaw.plugin.json"))) return true;
|
|
270
|
+
const packageJsonPath = path.join(dir, "package.json");
|
|
271
|
+
if (!existsSync(packageJsonPath)) return false;
|
|
272
|
+
try {
|
|
273
|
+
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
274
|
+
return Boolean(packageJson.openclaw || packageJson.pluginInspector || packageJson["plugin-inspector"]);
|
|
275
|
+
} catch {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function packageNameFromReport(report) {
|
|
281
|
+
return (
|
|
282
|
+
report.fixtures?.[0]?.package?.packageJson?.name ??
|
|
283
|
+
report.fixtures?.[0]?.package?.name ??
|
|
284
|
+
report.fixtures?.[0]?.name ??
|
|
285
|
+
report.fixtures?.[0]?.id ??
|
|
286
|
+
"plugin"
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function slugForPath(value) {
|
|
291
|
+
return (
|
|
292
|
+
String(value)
|
|
293
|
+
.replaceAll(path.sep, "-")
|
|
294
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
295
|
+
.replace(/^-+|-+$/g, "") || "plugin"
|
|
296
|
+
);
|
|
297
|
+
}
|
package/src/capture-cli.js
CHANGED
|
@@ -11,7 +11,7 @@ try {
|
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
async function run(commandArgs) {
|
|
14
|
-
const entrypoint = commandArgs
|
|
14
|
+
const entrypoint = findEntrypoint(commandArgs);
|
|
15
15
|
const outputPath = readFlag(commandArgs, "--output");
|
|
16
16
|
const pluginRoot = readFlag(commandArgs, "--plugin-root");
|
|
17
17
|
const mockSdk = readMockSdkFlag(commandArgs) ?? true;
|
|
@@ -40,6 +40,17 @@ function readFlag(commandArgs, name) {
|
|
|
40
40
|
return commandArgs[index + 1] ?? null;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
function findEntrypoint(commandArgs) {
|
|
44
|
+
const flagsWithValues = new Set(["--output", "--plugin-root", "--sdk"]);
|
|
45
|
+
const consumedIndexes = new Set();
|
|
46
|
+
for (const [index, arg] of commandArgs.entries()) {
|
|
47
|
+
if (flagsWithValues.has(arg)) {
|
|
48
|
+
consumedIndexes.add(index + 1);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return commandArgs.find((arg, index) => !arg.startsWith("-") && !consumedIndexes.has(index)) ?? null;
|
|
52
|
+
}
|
|
53
|
+
|
|
43
54
|
function readMockSdkFlag(commandArgs) {
|
|
44
55
|
const sdk = readFlag(commandArgs, "--sdk");
|
|
45
56
|
if (sdk === "mock") {
|
package/src/capture-config.js
CHANGED
|
@@ -17,7 +17,10 @@ export async function captureApiOptionsForPlugin(apiOptions = {}, options = {})
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
async function readSamplePluginConfig(pluginRoot) {
|
|
20
|
-
const manifestPath =
|
|
20
|
+
const manifestPath = await findNearestManifestPath(pluginRoot);
|
|
21
|
+
if (!manifestPath) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
21
24
|
let manifest;
|
|
22
25
|
try {
|
|
23
26
|
manifest = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
@@ -29,6 +32,23 @@ async function readSamplePluginConfig(pluginRoot) {
|
|
|
29
32
|
return isPlainObject(sample) && Object.keys(sample).length > 0 ? sample : undefined;
|
|
30
33
|
}
|
|
31
34
|
|
|
35
|
+
async function findNearestManifestPath(pluginRoot) {
|
|
36
|
+
let current = path.resolve(pluginRoot);
|
|
37
|
+
while (true) {
|
|
38
|
+
const manifestPath = path.join(current, "openclaw.plugin.json");
|
|
39
|
+
try {
|
|
40
|
+
await readFile(manifestPath, "utf8");
|
|
41
|
+
return manifestPath;
|
|
42
|
+
} catch {}
|
|
43
|
+
|
|
44
|
+
const parent = path.dirname(current);
|
|
45
|
+
if (parent === current) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
current = parent;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
32
52
|
function sampleJsonSchema(schema, context = {}) {
|
|
33
53
|
if (!isPlainObject(schema)) {
|
|
34
54
|
return undefined;
|
|
@@ -83,6 +103,16 @@ function sampleObjectSchema(schema) {
|
|
|
83
103
|
}
|
|
84
104
|
}
|
|
85
105
|
|
|
106
|
+
if (!hasNonBooleanSample(output, properties)) {
|
|
107
|
+
const key = preferredNestedConfigKey(properties);
|
|
108
|
+
if (key) {
|
|
109
|
+
const value = sampleJsonSchema(properties[key], { key });
|
|
110
|
+
if (value !== undefined) {
|
|
111
|
+
output[key] = value;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
86
116
|
if (Object.keys(output).length === 0 && Number(schema.minProperties ?? 0) > 0) {
|
|
87
117
|
const key = preferredSamplePropertyKey(properties);
|
|
88
118
|
if (key) {
|
|
@@ -96,6 +126,24 @@ function sampleObjectSchema(schema) {
|
|
|
96
126
|
return output;
|
|
97
127
|
}
|
|
98
128
|
|
|
129
|
+
function hasNonBooleanSample(output, properties) {
|
|
130
|
+
return Object.keys(output).some((key) => properties[key]?.type !== "boolean");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function preferredNestedConfigKey(properties) {
|
|
134
|
+
for (const key of ["embedding", "credentials", "auth", "provider", ...Object.keys(properties)]) {
|
|
135
|
+
const schema = properties[key];
|
|
136
|
+
if (!isPlainObject(schema)) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
140
|
+
if ((schema.type === "object" || schema.properties) && (Number(schema.minProperties ?? 0) > 0 || required.length > 0)) {
|
|
141
|
+
return key;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
99
147
|
function preferredSamplePropertyKey(properties) {
|
|
100
148
|
for (const key of ["provider", "model", "apiKey", "id", "name", ...Object.keys(properties)]) {
|
|
101
149
|
if (Object.prototype.hasOwnProperty.call(properties, key)) {
|