@openclaw/plugin-inspector 0.1.1 → 0.1.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/CHANGELOG.md +11 -0
- package/README.md +82 -41
- package/examples/github-actions-plugin-inspector.yml +2 -2
- package/examples/plugin-inspector.config.json +3 -0
- package/package.json +1 -1
- package/src/advanced.js +9 -0
- package/src/api.js +14 -8
- package/src/capture-api.js +180 -12
- package/src/cli.js +64 -5
- package/src/config.js +16 -2
- package/src/index.js +1 -0
- package/src/init.js +150 -0
- package/src/inspector.js +89 -14
- package/src/mock-sdk-capture-runner.js +110 -17
- package/src/runtime-capture-report.js +11 -1
- package/src/sdk-mock.js +1266 -13
- package/src/synthetic-probes.js +153 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2 - 2026-04-27
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Add public SDK subpath mocks, registrar return profiles, richer lifecycle capture probes, and plugin-root `init`/`--plugin-root` CLI flows.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Harden runtime capture for TypeScript entrypoints, extensionless local imports, mocked external packages, namespace imports, async output capture, and explicit mock/real SDK lanes.
|
|
12
|
+
- Refresh README and CI examples around `npx @openclaw/plugin-inspector check --no-openclaw` and opt-in runtime capture.
|
|
13
|
+
|
|
3
14
|
## 0.1.1 - 2026-04-27
|
|
4
15
|
|
|
5
16
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,36 +1,46 @@
|
|
|
1
1
|
<img src="docs/plugin-inspector-banner.jpg" alt="openclaw plugin inspector banner"/>
|
|
2
2
|
|
|
3
|
-
#
|
|
3
|
+
# OpenClaw Plugin Inspector
|
|
4
4
|
|
|
5
|
-
`plugin-inspector` is the
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
`plugin-inspector` is the offline compatibility check for OpenClaw plugins. Run
|
|
6
|
+
it from a plugin root to inspect package metadata, `openclaw.plugin.json`, SDK
|
|
7
|
+
imports, `api.on(...)`, `api.register*`, and optional runtime registration
|
|
8
|
+
capture.
|
|
8
9
|
|
|
9
|
-
##
|
|
10
|
+
## Quick Start
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
From a plugin package directory:
|
|
12
13
|
|
|
13
14
|
```bash
|
|
14
|
-
|
|
15
|
+
npx @openclaw/plugin-inspector
|
|
15
16
|
```
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
That runs `check`, writes report artifacts to `reports/`, and exits non-zero
|
|
19
|
+
when compatibility breakages are found.
|
|
20
|
+
|
|
21
|
+
Add a local config and GitHub Actions workflow:
|
|
18
22
|
|
|
19
23
|
```bash
|
|
20
|
-
npx @openclaw/plugin-inspector
|
|
24
|
+
npx @openclaw/plugin-inspector init --ci
|
|
21
25
|
```
|
|
22
26
|
|
|
23
|
-
|
|
27
|
+
Or install it as a dev dependency:
|
|
24
28
|
|
|
25
|
-
|
|
29
|
+
```bash
|
|
30
|
+
npm install --save-dev @openclaw/plugin-inspector
|
|
31
|
+
npx plugin-inspector check
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Commands
|
|
26
35
|
|
|
27
36
|
```bash
|
|
28
|
-
plugin-inspector check
|
|
37
|
+
npx @openclaw/plugin-inspector check
|
|
38
|
+
npx @openclaw/plugin-inspector check --plugin-root ./plugins/weather
|
|
39
|
+
npx @openclaw/plugin-inspector init --ci --package-manager pnpm
|
|
29
40
|
```
|
|
30
41
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`api.register*`, and writes:
|
|
42
|
+
`check` reads the current directory as one plugin unless `--plugin-root` is set.
|
|
43
|
+
It writes:
|
|
34
44
|
|
|
35
45
|
- `reports/plugin-inspector-report.json`
|
|
36
46
|
- `reports/plugin-inspector-report.md`
|
|
@@ -43,8 +53,8 @@ checkout:
|
|
|
43
53
|
plugin-inspector check --no-openclaw
|
|
44
54
|
```
|
|
45
55
|
|
|
46
|
-
Use
|
|
47
|
-
expected seams:
|
|
56
|
+
Use `plugin-inspector.config.json` when CI needs stable fixture metadata,
|
|
57
|
+
expected seams, or runtime capture defaults:
|
|
48
58
|
|
|
49
59
|
```json
|
|
50
60
|
{
|
|
@@ -58,6 +68,9 @@ expected seams:
|
|
|
58
68
|
"registrations": ["registerTool"]
|
|
59
69
|
}
|
|
60
70
|
},
|
|
71
|
+
"capture": {
|
|
72
|
+
"mockSdk": true
|
|
73
|
+
},
|
|
61
74
|
"openclaw": {
|
|
62
75
|
"defaultCheckoutPath": "../openclaw"
|
|
63
76
|
}
|
|
@@ -70,48 +83,51 @@ Then run:
|
|
|
70
83
|
plugin-inspector check --config plugin-inspector.config.json
|
|
71
84
|
```
|
|
72
85
|
|
|
73
|
-
|
|
86
|
+
`init --ci` writes this shape for you, plus
|
|
87
|
+
`.github/workflows/plugin-inspector.yml`. Copy-ready examples also live in
|
|
88
|
+
`examples/plugin-inspector.config.json` and
|
|
74
89
|
`examples/github-actions-plugin-inspector.yml`.
|
|
75
90
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
```bash
|
|
79
|
-
plugin-inspector report --config crabpot.config.json --out reports
|
|
80
|
-
```
|
|
91
|
+
## Runtime Capture
|
|
81
92
|
|
|
82
|
-
|
|
93
|
+
Runtime capture imports plugin entrypoints in an isolated subprocess and records
|
|
94
|
+
the registrations made during `register(api)`. It is opt-in because it executes
|
|
95
|
+
plugin code:
|
|
83
96
|
|
|
84
97
|
```bash
|
|
85
|
-
PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector
|
|
98
|
+
PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector check --runtime --mock-sdk
|
|
86
99
|
```
|
|
87
100
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
```
|
|
101
|
+
By default, runtime capture uses a generated mock for `openclaw/plugin-sdk` and
|
|
102
|
+
common external packages so plugin code can load in clean CI without OpenClaw
|
|
103
|
+
installed. Use `--real-sdk` only when the plugin workspace already has real SDK
|
|
104
|
+
dependencies installed and you intentionally want to test that path.
|
|
93
105
|
|
|
94
|
-
Runtime capture
|
|
95
|
-
declared OpenClaw package entrypoints, calls their `register(api)` function with
|
|
96
|
-
the capture API, and writes:
|
|
106
|
+
Runtime capture writes:
|
|
97
107
|
|
|
98
108
|
- `reports/plugin-inspector-runtime-capture.json`
|
|
99
109
|
- `reports/plugin-inspector-runtime-capture.md`
|
|
100
110
|
|
|
101
|
-
|
|
111
|
+
You can also capture one entrypoint directly:
|
|
102
112
|
|
|
103
|
-
|
|
113
|
+
```bash
|
|
114
|
+
PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector capture ./dist/index.js --mock-sdk
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## CI
|
|
118
|
+
|
|
119
|
+
Minimal package scripts:
|
|
104
120
|
|
|
105
121
|
```json
|
|
106
122
|
{
|
|
107
123
|
"scripts": {
|
|
108
124
|
"plugin:check": "plugin-inspector check --no-openclaw",
|
|
109
|
-
"plugin:check:runtime": "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector check --no-openclaw --
|
|
125
|
+
"plugin:check:runtime": "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector check --no-openclaw --runtime --mock-sdk"
|
|
110
126
|
}
|
|
111
127
|
}
|
|
112
128
|
```
|
|
113
129
|
|
|
114
|
-
GitHub Actions:
|
|
130
|
+
GitHub Actions without a local dev dependency:
|
|
115
131
|
|
|
116
132
|
```yaml
|
|
117
133
|
name: plugin-inspector
|
|
@@ -131,8 +147,8 @@ jobs:
|
|
|
131
147
|
node-version: 24
|
|
132
148
|
cache: npm
|
|
133
149
|
- run: npm ci
|
|
134
|
-
- run:
|
|
135
|
-
- run:
|
|
150
|
+
- run: npx @openclaw/plugin-inspector check --no-openclaw
|
|
151
|
+
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector check --no-openclaw --runtime --mock-sdk
|
|
136
152
|
- uses: actions/upload-artifact@v5
|
|
137
153
|
if: always()
|
|
138
154
|
with:
|
|
@@ -140,6 +156,31 @@ jobs:
|
|
|
140
156
|
path: reports/plugin-inspector-*
|
|
141
157
|
```
|
|
142
158
|
|
|
159
|
+
## Fixture Suites
|
|
160
|
+
|
|
161
|
+
Fixture-set configs are still supported for crabpot-style compatibility suites:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
plugin-inspector report --config crabpot.config.json --out reports
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Use fixture suites when one repo wants to inspect many plugins. Use plugin-root
|
|
168
|
+
`check` for normal plugin CI.
|
|
169
|
+
|
|
170
|
+
## Mocking Model
|
|
171
|
+
|
|
172
|
+
Default inspection is static, offline, and credential-free. Runtime capture is
|
|
173
|
+
the only mode that imports plugin code.
|
|
174
|
+
|
|
175
|
+
When `--mock-sdk` is enabled, the inspector generates temporary modules for
|
|
176
|
+
`openclaw/plugin-sdk` subpaths and unresolved external packages discovered in
|
|
177
|
+
the plugin import graph. The mock SDK captures registrations; it does not call
|
|
178
|
+
network services, launch OpenClaw, run provider SDKs, or emulate service
|
|
179
|
+
lifecycle side effects.
|
|
180
|
+
|
|
181
|
+
Use the mock lane for plugin compatibility CI. Keep live provider/service tests
|
|
182
|
+
in the plugin repo behind their own credentials and explicit opt-in flags.
|
|
183
|
+
|
|
143
184
|
## Scope
|
|
144
185
|
|
|
145
186
|
Default inspection is offline and credential-free. It reads manifests, package
|
|
@@ -148,5 +189,5 @@ metadata, and source files, then reports observed `api.on(...)`,
|
|
|
148
189
|
OpenClaw target checkout parsing is limited to public compatibility registries,
|
|
149
190
|
SDK package exports, manifest types, hooks, and captured registrar metadata.
|
|
150
191
|
|
|
151
|
-
Cold import capture
|
|
152
|
-
Live lanes
|
|
192
|
+
Cold import capture, synthetic contract probes, and runtime capture are explicit
|
|
193
|
+
opt-in modes. Live lanes stay credential-gated and must never run in default CI.
|
|
@@ -15,8 +15,8 @@ jobs:
|
|
|
15
15
|
node-version: 24
|
|
16
16
|
cache: npm
|
|
17
17
|
- run: npm ci
|
|
18
|
-
- run:
|
|
19
|
-
- run:
|
|
18
|
+
- run: npx @openclaw/plugin-inspector check --no-openclaw
|
|
19
|
+
- run: PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector check --no-openclaw --runtime --mock-sdk
|
|
20
20
|
- uses: actions/upload-artifact@v5
|
|
21
21
|
if: always()
|
|
22
22
|
with:
|
package/package.json
CHANGED
package/src/advanced.js
CHANGED
|
@@ -116,12 +116,21 @@ export {
|
|
|
116
116
|
defaultPluginRootConfigFiles,
|
|
117
117
|
fixtureCheckoutPath,
|
|
118
118
|
fixtureSourceRoot,
|
|
119
|
+
inferPluginSeams,
|
|
119
120
|
loadInspectorConfig,
|
|
120
121
|
loadPluginRootConfig,
|
|
121
122
|
normalizeInspectorConfig,
|
|
122
123
|
normalizePluginRootConfig,
|
|
124
|
+
packageId,
|
|
123
125
|
validateInspectorConfig,
|
|
124
126
|
} from "./config.js";
|
|
127
|
+
export {
|
|
128
|
+
buildPluginInspectorConfig,
|
|
129
|
+
defaultInitConfigPath,
|
|
130
|
+
defaultInitWorkflowPath,
|
|
131
|
+
renderGithubActionsWorkflow,
|
|
132
|
+
writePluginInspectorInit,
|
|
133
|
+
} from "./init.js";
|
|
125
134
|
export {
|
|
126
135
|
buildPlatformProbes,
|
|
127
136
|
defaultPlatformTargets,
|
package/src/api.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { createCaptureApi } from "./capture-api.js";
|
|
3
3
|
import { loadInspectorConfig, loadPluginRootConfig } from "./config.js";
|
|
4
|
+
import { writePluginInspectorInit } from "./init.js";
|
|
4
5
|
import { captureEntrypoint } from "./inspector.js";
|
|
5
6
|
import { renderTextSummary, writeCompatibilityReport } from "./report.js";
|
|
6
7
|
import { buildRuntimeCaptureReport, writeRuntimeCaptureReport } from "./runtime-capture-report.js";
|
|
@@ -45,24 +46,25 @@ export async function writePluginReports(report, options = {}) {
|
|
|
45
46
|
|
|
46
47
|
export async function runPluginCheck(options = {}) {
|
|
47
48
|
const outDir = options.outDir ?? "reports";
|
|
48
|
-
const
|
|
49
|
-
const
|
|
49
|
+
const config = await loadPluginConfig(options);
|
|
50
|
+
const report = await inspectPluginRoot({ ...options, config });
|
|
51
|
+
const paths = await writePluginReports(report, { ...options, pluginRoot: config.rootDir, outDir });
|
|
50
52
|
const result = { report, paths };
|
|
53
|
+
const capture = options.capture ?? config.capture?.runtime ?? false;
|
|
54
|
+
const mockSdk = options.mockSdk ?? config.capture?.mockSdk ?? true;
|
|
51
55
|
|
|
52
|
-
if (
|
|
56
|
+
if (capture === true) {
|
|
53
57
|
if (process.env.PLUGIN_INSPECTOR_EXECUTE_ISOLATED !== "1") {
|
|
54
58
|
throw new Error("runtime capture imports plugin code; rerun with PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 in an isolated workspace");
|
|
55
59
|
}
|
|
56
|
-
const config = await loadPluginConfig(options);
|
|
57
60
|
const runtimeCapture = await buildRuntimeCaptureReport({
|
|
58
|
-
mockSdk
|
|
61
|
+
mockSdk,
|
|
59
62
|
report,
|
|
60
63
|
rootDir: config.rootDir,
|
|
61
64
|
});
|
|
62
|
-
const outputRoot = options.cwd ?? options.pluginRoot ?? process.cwd();
|
|
63
65
|
const runtimeCapturePaths = await writeRuntimeCaptureReport(runtimeCapture, {
|
|
64
|
-
jsonPath: path.resolve(
|
|
65
|
-
markdownPath: path.resolve(
|
|
66
|
+
jsonPath: path.resolve(config.rootDir, outDir, "plugin-inspector-runtime-capture.json"),
|
|
67
|
+
markdownPath: path.resolve(config.rootDir, outDir, "plugin-inspector-runtime-capture.md"),
|
|
66
68
|
});
|
|
67
69
|
result.runtimeCapture = runtimeCapture;
|
|
68
70
|
result.runtimeCapturePaths = runtimeCapturePaths;
|
|
@@ -82,4 +84,8 @@ export async function capturePluginEntrypoint(entrypoint, options = {}) {
|
|
|
82
84
|
return captureEntrypoint(entrypoint, options);
|
|
83
85
|
}
|
|
84
86
|
|
|
87
|
+
export async function setupPluginInspector(options = {}) {
|
|
88
|
+
return writePluginInspectorInit(options);
|
|
89
|
+
}
|
|
90
|
+
|
|
85
91
|
export { createCaptureApi, renderTextSummary };
|
package/src/capture-api.js
CHANGED
|
@@ -1,15 +1,68 @@
|
|
|
1
|
+
export const defaultCaptureApiRegistrarProfiles = {
|
|
2
|
+
registerChannel: {
|
|
3
|
+
returnValue: ({ args }) => registrationObject(args, { id: "channel" }),
|
|
4
|
+
},
|
|
5
|
+
registerCli: {
|
|
6
|
+
returnValue: ({ args }) => registrationObject(args, { name: "cli" }),
|
|
7
|
+
},
|
|
8
|
+
registerCommand: {
|
|
9
|
+
returnValue: ({ args }) => registrationObject(args, { name: "command" }),
|
|
10
|
+
},
|
|
11
|
+
registerContextEngine: {
|
|
12
|
+
returnValue: ({ args }) => registrationObject(args, { id: "context-engine" }),
|
|
13
|
+
},
|
|
14
|
+
registerGatewayMethod: {
|
|
15
|
+
returnValue: ({ args }) => registrationObject(args, { name: "gateway.method" }),
|
|
16
|
+
},
|
|
17
|
+
registerHook: {
|
|
18
|
+
returnValue: ({ api }) => api,
|
|
19
|
+
},
|
|
20
|
+
registerHttpRoute: {
|
|
21
|
+
returnValue: ({ args }) => ({
|
|
22
|
+
...registrationObject(args, { method: "GET", path: "/" }),
|
|
23
|
+
unregister() {},
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
registerInteractiveHandler: {
|
|
27
|
+
returnValue: ({ args }) => registrationObject(args, { id: "interactive-handler" }),
|
|
28
|
+
},
|
|
29
|
+
registerMemoryPromptSection: {
|
|
30
|
+
returnValue: ({ args }) => registrationObject(args, { id: "memory-prompt-section" }),
|
|
31
|
+
},
|
|
32
|
+
registerMemoryRuntime: {
|
|
33
|
+
returnValue: ({ args }) => registrationObject(args, { id: "memory-runtime" }),
|
|
34
|
+
},
|
|
35
|
+
registerProvider: {
|
|
36
|
+
returnValue: ({ args }) => registrationObject(args, { id: "provider" }),
|
|
37
|
+
},
|
|
38
|
+
registerService: {
|
|
39
|
+
returnValue: ({ args }) => ({
|
|
40
|
+
...registrationObject(args, { name: "service" }),
|
|
41
|
+
start: async () => undefined,
|
|
42
|
+
stop: async () => undefined,
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
registerSpeechProvider: {
|
|
46
|
+
returnValue: ({ args }) => registrationObject(args, { id: "speech-provider" }),
|
|
47
|
+
},
|
|
48
|
+
registerTool: {
|
|
49
|
+
returnValue: ({ args }) => registrationObject(args, { name: "tool" }),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
1
53
|
export function createCaptureApi(options = {}) {
|
|
2
54
|
const captured = [];
|
|
3
55
|
const retained = [];
|
|
4
|
-
const
|
|
56
|
+
const registrarProfiles = {
|
|
57
|
+
...defaultCaptureApiRegistrarProfiles,
|
|
58
|
+
...(options.registrarProfiles ?? {}),
|
|
59
|
+
};
|
|
60
|
+
const knownRegistrars = new Set(options.knownRegistrars ?? Object.keys(registrarProfiles));
|
|
5
61
|
const retainHandlers = options.retainHandlers === true;
|
|
6
62
|
|
|
7
63
|
const api = new Proxy(
|
|
8
64
|
{
|
|
9
|
-
|
|
10
|
-
logger: options.logger ?? console,
|
|
11
|
-
pluginConfig: options.pluginConfig ?? {},
|
|
12
|
-
runtime: options.runtime ?? {},
|
|
65
|
+
...createCaptureContext(options),
|
|
13
66
|
on(name, handler) {
|
|
14
67
|
const captureIndex =
|
|
15
68
|
captured.push({
|
|
@@ -42,6 +95,7 @@ export function createCaptureApi(options = {}) {
|
|
|
42
95
|
}
|
|
43
96
|
if (typeof property === "string" && isRegistrarProperty(property)) {
|
|
44
97
|
return (...args) => {
|
|
98
|
+
const returnValue = registrationReturnValue(property, args, { api, registrarProfiles });
|
|
45
99
|
const captureIndex =
|
|
46
100
|
captured.push({
|
|
47
101
|
kind: "registration",
|
|
@@ -54,10 +108,11 @@ export function createCaptureApi(options = {}) {
|
|
|
54
108
|
kind: "registration",
|
|
55
109
|
name: property,
|
|
56
110
|
arguments: args,
|
|
111
|
+
returnValue,
|
|
57
112
|
captureIndex,
|
|
58
113
|
});
|
|
59
114
|
}
|
|
60
|
-
return
|
|
115
|
+
return returnValue;
|
|
61
116
|
};
|
|
62
117
|
}
|
|
63
118
|
return undefined;
|
|
@@ -68,19 +123,122 @@ export function createCaptureApi(options = {}) {
|
|
|
68
123
|
return api;
|
|
69
124
|
}
|
|
70
125
|
|
|
126
|
+
export function createCaptureContext(options = {}) {
|
|
127
|
+
return {
|
|
128
|
+
config: options.config ?? {},
|
|
129
|
+
logger: options.logger ?? console,
|
|
130
|
+
pluginConfig: options.pluginConfig ?? {},
|
|
131
|
+
runtime: options.runtime ?? createRuntimeContext(options),
|
|
132
|
+
secrets: options.secrets ?? createSecretContext(options),
|
|
133
|
+
store: options.store ?? createStoreContext(options),
|
|
134
|
+
paths: options.paths ?? {
|
|
135
|
+
cacheDir: ".plugin-inspector/cache",
|
|
136
|
+
configDir: ".plugin-inspector/config",
|
|
137
|
+
dataDir: ".plugin-inspector/data",
|
|
138
|
+
},
|
|
139
|
+
agent: options.agent ?? {
|
|
140
|
+
id: "plugin-inspector-agent",
|
|
141
|
+
accountId: "default",
|
|
142
|
+
},
|
|
143
|
+
gateway: options.gateway ?? {
|
|
144
|
+
baseUrl: "http://127.0.0.1:0",
|
|
145
|
+
registerRoute(route) {
|
|
146
|
+
return {
|
|
147
|
+
...route,
|
|
148
|
+
unregister() {},
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
fetch: options.fetch ?? (async () => ({ ok: true, status: 200, json: async () => ({}), text: async () => "" })),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
71
156
|
function isRegistrarProperty(property) {
|
|
72
157
|
return property.startsWith("register") || property.startsWith("define");
|
|
73
158
|
}
|
|
74
159
|
|
|
75
|
-
function registrationReturnValue(name, args) {
|
|
76
|
-
|
|
160
|
+
function registrationReturnValue(name, args, context) {
|
|
161
|
+
const profile = context.registrarProfiles[name];
|
|
162
|
+
if (profile?.returnValue) {
|
|
163
|
+
return profile.returnValue({ name, args, api: context.api });
|
|
164
|
+
}
|
|
165
|
+
return registrationObject(args, {});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function createRuntimeContext(options) {
|
|
169
|
+
const runtime = options.runtime ?? {};
|
|
170
|
+
return {
|
|
171
|
+
...runtime,
|
|
172
|
+
agent: runtime.agent ?? {},
|
|
173
|
+
env: options.env ?? {},
|
|
174
|
+
logger: options.logger ?? console,
|
|
175
|
+
now: () => new Date(0),
|
|
176
|
+
tts: runtime.tts ?? {},
|
|
177
|
+
state: {
|
|
178
|
+
resolveStateDir: () => options.stateDir ?? process.cwd(),
|
|
179
|
+
...(runtime.state ?? {}),
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function createSecretContext(options) {
|
|
185
|
+
const secrets = new Map(Object.entries(options.secretValues ?? {}));
|
|
186
|
+
return {
|
|
187
|
+
async get(name) {
|
|
188
|
+
return secrets.get(name) ?? null;
|
|
189
|
+
},
|
|
190
|
+
async has(name) {
|
|
191
|
+
return secrets.has(name);
|
|
192
|
+
},
|
|
193
|
+
async require(name) {
|
|
194
|
+
if (!secrets.has(name)) {
|
|
195
|
+
throw new Error(`Missing mocked secret: ${name}`);
|
|
196
|
+
}
|
|
197
|
+
return secrets.get(name);
|
|
198
|
+
},
|
|
199
|
+
async resolve(value) {
|
|
200
|
+
return typeof value === "string" && value.startsWith("secret:") ? (secrets.get(value.slice(7)) ?? null) : value;
|
|
201
|
+
},
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function createStoreContext() {
|
|
206
|
+
const values = new Map();
|
|
207
|
+
return {
|
|
208
|
+
async delete(key) {
|
|
209
|
+
return values.delete(key);
|
|
210
|
+
},
|
|
211
|
+
async get(key) {
|
|
212
|
+
return values.get(key);
|
|
213
|
+
},
|
|
214
|
+
async list() {
|
|
215
|
+
return [...values.keys()].sort();
|
|
216
|
+
},
|
|
217
|
+
async set(key, value) {
|
|
218
|
+
values.set(key, value);
|
|
219
|
+
return value;
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function registrationObject(args, defaults) {
|
|
225
|
+
const first = args[0];
|
|
226
|
+
if (first && typeof first === "object") {
|
|
77
227
|
return {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
228
|
+
...defaults,
|
|
229
|
+
...first,
|
|
230
|
+
name: objectName(first) ?? defaults.name,
|
|
231
|
+
id: objectId(first) ?? defaults.id,
|
|
81
232
|
};
|
|
82
233
|
}
|
|
83
|
-
|
|
234
|
+
if (typeof first === "string") {
|
|
235
|
+
return {
|
|
236
|
+
...defaults,
|
|
237
|
+
name: first,
|
|
238
|
+
id: defaults.id,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return { ...defaults };
|
|
84
242
|
}
|
|
85
243
|
|
|
86
244
|
function summarizeArguments(args) {
|
|
@@ -113,3 +271,13 @@ function objectName(value) {
|
|
|
113
271
|
}
|
|
114
272
|
return typeof value.id === "string" ? value.id : null;
|
|
115
273
|
}
|
|
274
|
+
|
|
275
|
+
function objectId(value) {
|
|
276
|
+
if (!value || typeof value !== "object") {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
if (typeof value.id === "string") {
|
|
280
|
+
return value.id;
|
|
281
|
+
}
|
|
282
|
+
return typeof value.name === "string" ? value.name : null;
|
|
283
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
captureEntrypoint,
|
|
8
8
|
inspectFixtureSet,
|
|
9
9
|
loadInspectorConfig,
|
|
10
|
+
writePluginInspectorInit,
|
|
10
11
|
writeArtifacts,
|
|
11
12
|
writeReport,
|
|
12
13
|
} from "./advanced.js";
|
|
@@ -20,6 +21,8 @@ try {
|
|
|
20
21
|
printHelp();
|
|
21
22
|
} else if (command === "check") {
|
|
22
23
|
await runCheck(commandArgs);
|
|
24
|
+
} else if (command === "init") {
|
|
25
|
+
await runInit(commandArgs);
|
|
23
26
|
} else if (command === "inspect" || command === "report" || command === "ci") {
|
|
24
27
|
await runReport(command, commandArgs);
|
|
25
28
|
} else if (command === "capture") {
|
|
@@ -34,11 +37,13 @@ try {
|
|
|
34
37
|
|
|
35
38
|
async function runCheck(commandArgs) {
|
|
36
39
|
const configPath = readFlag(commandArgs, "--config");
|
|
40
|
+
const pluginRoot = readFlag(commandArgs, "--plugin-root") ?? readFlag(commandArgs, "--root");
|
|
37
41
|
const outDir = readFlag(commandArgs, "--out") ?? "reports";
|
|
38
42
|
const openclawPath = commandArgs.includes("--no-openclaw") ? false : readFlag(commandArgs, "--openclaw");
|
|
39
43
|
const json = commandArgs.includes("--json");
|
|
40
|
-
const capture = commandArgs
|
|
41
|
-
const
|
|
44
|
+
const capture = readRuntimeFlag(commandArgs);
|
|
45
|
+
const mockSdk = readMockSdkFlag(commandArgs);
|
|
46
|
+
const { report } = await runPluginCheck({ configPath, pluginRoot, outDir, openclawPath, capture, mockSdk });
|
|
42
47
|
|
|
43
48
|
if (json) {
|
|
44
49
|
console.log(JSON.stringify(report, null, 2));
|
|
@@ -51,6 +56,25 @@ async function runCheck(commandArgs) {
|
|
|
51
56
|
}
|
|
52
57
|
}
|
|
53
58
|
|
|
59
|
+
async function runInit(commandArgs) {
|
|
60
|
+
const pluginRoot = readFlag(commandArgs, "--plugin-root") ?? readFlag(commandArgs, "--root");
|
|
61
|
+
const configPath = readFlag(commandArgs, "--config") ?? undefined;
|
|
62
|
+
const workflowPath = readFlag(commandArgs, "--workflow") ?? undefined;
|
|
63
|
+
const packageManager = readFlag(commandArgs, "--package-manager") ?? "npm";
|
|
64
|
+
const result = await writePluginInspectorInit({
|
|
65
|
+
pluginRoot,
|
|
66
|
+
configPath,
|
|
67
|
+
workflowPath,
|
|
68
|
+
packageManager,
|
|
69
|
+
ci: commandArgs.includes("--ci"),
|
|
70
|
+
force: commandArgs.includes("--force"),
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
for (const filePath of result.written) {
|
|
74
|
+
console.log(`wrote ${filePath}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
54
78
|
async function runReport(command, commandArgs) {
|
|
55
79
|
const configPath = readFlag(commandArgs, "--config");
|
|
56
80
|
const outDir = readFlag(commandArgs, "--out") ?? "reports";
|
|
@@ -75,7 +99,7 @@ async function runCapture(commandArgs) {
|
|
|
75
99
|
const entrypoint = commandArgs.find((arg) => !arg.startsWith("-"));
|
|
76
100
|
const outputPath = readFlag(commandArgs, "--output");
|
|
77
101
|
const pluginRoot = readFlag(commandArgs, "--plugin-root");
|
|
78
|
-
const mockSdk = commandArgs.includes("--mock-sdk");
|
|
102
|
+
const mockSdk = readMockSdkFlag(commandArgs) ?? commandArgs.includes("--mock-sdk");
|
|
79
103
|
if (!entrypoint) {
|
|
80
104
|
throw new Error("capture requires an entrypoint path");
|
|
81
105
|
}
|
|
@@ -100,14 +124,49 @@ function readFlag(commandArgs, name) {
|
|
|
100
124
|
return commandArgs[index + 1] ?? null;
|
|
101
125
|
}
|
|
102
126
|
|
|
127
|
+
function readRuntimeFlag(commandArgs) {
|
|
128
|
+
if (commandArgs.includes("--runtime") || commandArgs.includes("--capture")) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
if (commandArgs.includes("--no-runtime") || commandArgs.includes("--no-capture")) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function readMockSdkFlag(commandArgs) {
|
|
138
|
+
const sdk = readFlag(commandArgs, "--sdk");
|
|
139
|
+
if (sdk === "mock") {
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
if (sdk === "real") {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (sdk && !["mock", "real"].includes(sdk)) {
|
|
146
|
+
throw new Error("--sdk must be mock or real");
|
|
147
|
+
}
|
|
148
|
+
if (commandArgs.includes("--mock-sdk")) {
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
if (commandArgs.includes("--real-sdk")) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
103
157
|
function printHelp() {
|
|
104
158
|
console.log(`plugin-inspector
|
|
105
159
|
|
|
106
160
|
Usage:
|
|
107
|
-
plugin-inspector
|
|
161
|
+
plugin-inspector
|
|
162
|
+
plugin-inspector check [--plugin-root <path>] [--config <path>] [--out <dir>] [--openclaw <path>] [--no-openclaw] [--runtime] [--mock-sdk|--real-sdk] [--json]
|
|
163
|
+
plugin-inspector init [--plugin-root <path>] [--config <path>] [--ci] [--package-manager npm|pnpm|yarn|bun] [--force]
|
|
108
164
|
plugin-inspector report --config <path> [--out <dir>] [--check] [--json]
|
|
109
165
|
plugin-inspector inspect --config <path> [--out <dir>] [--check] [--json]
|
|
110
166
|
plugin-inspector ci --config <path> [--out <dir>]
|
|
111
|
-
PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector capture <entrypoint> [--mock-sdk] [--plugin-root <path>] [--output <path>]
|
|
167
|
+
PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector capture <entrypoint> [--mock-sdk|--real-sdk] [--plugin-root <path>] [--output <path>]
|
|
168
|
+
|
|
169
|
+
Default check runs from the current plugin root and writes reports/ unless --out is set.
|
|
170
|
+
Runtime capture is opt-in because it imports plugin code; use --runtime with PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1.
|
|
112
171
|
`);
|
|
113
172
|
}
|