@openclaw/plugin-inspector 0.1.3 → 0.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 - 2026-04-27
4
+
5
+ ### Added
6
+
7
+ - Add `--allow-execute` as a cross-platform runtime capture opt-in flag.
8
+ - Add `plugin-inspector init --dry-run` for setup previews.
9
+ - Add `plugin-inspector init --json` for machine-readable setup summaries.
10
+ - Add `plugin-inspector init --scripts` for `plugin:check` and `plugin:ci` package scripts.
11
+ - Add public fixture-set report helpers and synthetic probe suite helpers for Crabpot and downstream compatibility suites.
12
+ - Add a Crabpot follow-through release checklist for source refs, package pins, and smoke commands.
13
+
14
+ ### Changed
15
+
16
+ - Make generated runtime CI commands use `--allow-execute` instead of shell-specific inline environment syntax.
17
+ - Make `plugin-inspector init --ci` detect `packageManager` and common lockfiles before generating CI install/run commands.
18
+ - Make `plugin-inspector init` output repo-relative file paths and preflight generated files before writing.
19
+ - Make `plugin-inspector init` infer `sourceRoot: "src"` from package export maps like `"./src/index.js"`.
20
+ - Improve CLI failure summaries with report artifact paths and top blocking findings.
21
+ - Harden mock SDK capture by keeping generated loader fixtures available until subprocess exit.
22
+
23
+ ## 0.2.0 - 2026-04-27
24
+
25
+ ### Added
26
+
27
+ - Add package.json `pluginInspector` config discovery for plugin-root checks.
28
+ - Add `plugin-inspector config` for resolved plugin-root config summaries.
29
+ - Add author-facing `plugin-inspector inspect` plugin-root flow.
30
+ - Add CI-native SARIF and JUnit outputs; `plugin-inspector ci` writes them by default.
31
+
32
+ ### Changed
33
+
34
+ - Make generated CI workflows use one `plugin-inspector ci --no-openclaw --runtime --mock-sdk` command.
35
+ - Harden runtime capture for string handler registrations, parse-capable config schema helpers, and provider auth/catalog SDK mocks.
36
+
3
37
  ## 0.1.3 - 2026-04-27
4
38
 
5
39
  ### Added
package/README.md CHANGED
@@ -24,6 +24,11 @@ Add a local config and GitHub Actions workflow:
24
24
  npx @openclaw/plugin-inspector init --ci
25
25
  ```
26
26
 
27
+ `init --ci` detects `packageManager` and common lockfiles. Pass
28
+ `--package-manager pnpm`, `npm`, `yarn`, or `bun` when you want to override it.
29
+ Add `--scripts` to write `plugin:check` and `plugin:ci` package scripts.
30
+ Use `--dry-run` to preview the files first.
31
+
27
32
  Or install it as a dev dependency:
28
33
 
29
34
  ```bash
@@ -35,17 +40,41 @@ npx plugin-inspector check
35
40
 
36
41
  ```bash
37
42
  npx @openclaw/plugin-inspector check
43
+ npx @openclaw/plugin-inspector inspect
44
+ npx @openclaw/plugin-inspector ci --no-openclaw
45
+ npx @openclaw/plugin-inspector config
38
46
  npx @openclaw/plugin-inspector check --plugin-root ./plugins/weather
39
47
  npx @openclaw/plugin-inspector init --ci --package-manager pnpm
48
+ npx @openclaw/plugin-inspector init --ci --scripts --dry-run
49
+ npx @openclaw/plugin-inspector init --ci --scripts --dry-run --json
40
50
  ```
41
51
 
42
- `check` reads the current directory as one plugin unless `--plugin-root` is set.
43
- It writes:
52
+ `check` and `inspect` read the current directory as one plugin unless
53
+ `--plugin-root` is set. `inspect` is the friendly author-facing alias; `check`
54
+ is kept for scripts. Both write:
44
55
 
45
56
  - `reports/plugin-inspector-report.json`
46
57
  - `reports/plugin-inspector-report.md`
47
58
  - `reports/plugin-inspector-issues.md`
48
59
 
60
+ Use CI-native outputs when you want annotations or test-summary ingestion:
61
+
62
+ ```bash
63
+ plugin-inspector inspect --no-openclaw --sarif --junit
64
+ ```
65
+
66
+ That also writes:
67
+
68
+ - `reports/plugin-inspector.sarif`
69
+ - `reports/plugin-inspector.junit.xml`
70
+
71
+ `config` prints the resolved plugin root, fixture id, seams, and capture
72
+ settings before CI runs:
73
+
74
+ ```bash
75
+ plugin-inspector config --json
76
+ ```
77
+
49
78
  Use `--no-openclaw` when CI should not compare against a local OpenClaw
50
79
  checkout:
51
80
 
@@ -53,8 +82,9 @@ checkout:
53
82
  plugin-inspector check --no-openclaw
54
83
  ```
55
84
 
56
- Use `plugin-inspector.config.json` when CI needs stable fixture metadata,
57
- expected seams, or runtime capture defaults:
85
+ Use either `plugin-inspector.config.json` or a `package.json`
86
+ `pluginInspector` block when CI needs stable fixture metadata, expected seams,
87
+ or runtime capture defaults:
58
88
 
59
89
  ```json
60
90
  {
@@ -83,10 +113,34 @@ Then run:
83
113
  plugin-inspector check --config plugin-inspector.config.json
84
114
  ```
85
115
 
116
+ For a single plugin package, the same config can live in `package.json`:
117
+
118
+ ```json
119
+ {
120
+ "scripts": {
121
+ "plugin:check": "plugin-inspector inspect --no-openclaw"
122
+ },
123
+ "pluginInspector": {
124
+ "version": 1,
125
+ "plugin": {
126
+ "id": "weather",
127
+ "priority": "high",
128
+ "seams": ["dynamic-tool"],
129
+ "sourceRoot": "src"
130
+ },
131
+ "capture": {
132
+ "mockSdk": true
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
86
138
  `init --ci` writes this shape for you, plus
87
139
  `.github/workflows/plugin-inspector.yml`. Copy-ready examples also live in
88
140
  `examples/plugin-inspector.config.json` and
89
- `examples/github-actions-plugin-inspector.yml`.
141
+ `examples/package-json-plugin-inspector.json` and
142
+ `examples/github-actions-plugin-inspector.yml`. SARIF/JUnit CI consumption
143
+ examples live alongside them.
90
144
 
91
145
  ## Runtime Capture
92
146
 
@@ -95,9 +149,13 @@ the registrations made during `register(api)`. It is opt-in because it executes
95
149
  plugin code:
96
150
 
97
151
  ```bash
98
- PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 npx @openclaw/plugin-inspector check --runtime --mock-sdk
152
+ npx @openclaw/plugin-inspector check --runtime --mock-sdk --allow-execute
99
153
  ```
100
154
 
155
+ `--allow-execute` is the explicit guard for modes that import plugin code. The
156
+ older `PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1` environment guard still works for
157
+ custom harnesses.
158
+
101
159
  By default, runtime capture uses a generated mock for `openclaw/plugin-sdk` and
102
160
  common external packages so plugin code can load in clean CI without OpenClaw
103
161
  installed. Use `--real-sdk` only when the plugin workspace already has real SDK
@@ -111,7 +169,7 @@ Runtime capture writes:
111
169
  You can also capture one entrypoint directly:
112
170
 
113
171
  ```bash
114
- PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector capture ./dist/index.js --mock-sdk
172
+ plugin-inspector capture ./dist/index.js --mock-sdk --allow-execute
115
173
  ```
116
174
 
117
175
  ## CI
@@ -121,8 +179,8 @@ Minimal package scripts:
121
179
  ```json
122
180
  {
123
181
  "scripts": {
124
- "plugin:check": "plugin-inspector check --no-openclaw",
125
- "plugin:check:runtime": "PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 plugin-inspector check --no-openclaw --runtime --mock-sdk"
182
+ "plugin:check": "plugin-inspector inspect --no-openclaw",
183
+ "plugin:ci": "plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute"
126
184
  }
127
185
  }
128
186
  ```
@@ -147,8 +205,7 @@ jobs:
147
205
  node-version: 24
148
206
  cache: npm
149
207
  - run: npm ci
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
208
+ - run: npx @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute
152
209
  - uses: actions/upload-artifact@v5
153
210
  if: always()
154
211
  with:
@@ -156,6 +213,18 @@ jobs:
156
213
  path: reports/plugin-inspector-*
157
214
  ```
158
215
 
216
+ `ci` writes the normal report, CI summary, SARIF, and JUnit files by default.
217
+ Pass `--no-sarif` or `--no-junit` only if your CI surface cannot consume them.
218
+
219
+ For GitHub code scanning, use
220
+ `examples/github-actions-code-scanning.yml`; it uploads
221
+ `reports/plugin-inspector.sarif` through CodeQL's SARIF upload action.
222
+
223
+ For CI test-summary UIs, point JUnit ingestion at
224
+ `reports/plugin-inspector.junit.xml`. Copy-ready GitLab and CircleCI examples
225
+ live in `examples/gitlab-ci-plugin-inspector.yml` and
226
+ `examples/circleci-plugin-inspector.yml`.
227
+
159
228
  ## Fixture Suites
160
229
 
161
230
  Fixture-set configs are still supported for crabpot-style compatibility suites:
@@ -0,0 +1,19 @@
1
+ version: 2.1
2
+
3
+ jobs:
4
+ plugin-inspector:
5
+ docker:
6
+ - image: cimg/node:24.0
7
+ steps:
8
+ - checkout
9
+ - run: npm ci
10
+ - run: npx @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute
11
+ - store_test_results:
12
+ path: reports
13
+ - store_artifacts:
14
+ path: reports
15
+
16
+ workflows:
17
+ plugin-inspector:
18
+ jobs:
19
+ - plugin-inspector
@@ -0,0 +1,31 @@
1
+ name: plugin-inspector
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ permissions:
9
+ contents: read
10
+ security-events: write
11
+
12
+ jobs:
13
+ check:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v5
17
+ - uses: actions/setup-node@v5
18
+ with:
19
+ node-version: 24
20
+ cache: npm
21
+ - run: npm ci
22
+ - run: npx @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute
23
+ - uses: github/codeql-action/upload-sarif@v3
24
+ if: always()
25
+ with:
26
+ sarif_file: reports/plugin-inspector.sarif
27
+ - uses: actions/upload-artifact@v5
28
+ if: always()
29
+ with:
30
+ name: plugin-inspector-reports
31
+ path: reports/plugin-inspector-*
@@ -15,8 +15,7 @@ jobs:
15
15
  node-version: 24
16
16
  cache: npm
17
17
  - run: npm ci
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
18
+ - run: npx @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute
20
19
  - uses: actions/upload-artifact@v5
21
20
  if: always()
22
21
  with:
@@ -0,0 +1,11 @@
1
+ plugin_inspector:
2
+ image: node:24
3
+ script:
4
+ - npm ci
5
+ - npx @openclaw/plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute
6
+ artifacts:
7
+ when: always
8
+ paths:
9
+ - reports/plugin-inspector-*
10
+ reports:
11
+ junit: reports/plugin-inspector.junit.xml
@@ -0,0 +1,21 @@
1
+ {
2
+ "scripts": {
3
+ "plugin:check": "plugin-inspector inspect --no-openclaw",
4
+ "plugin:ci": "plugin-inspector ci --no-openclaw --runtime --mock-sdk --allow-execute"
5
+ },
6
+ "pluginInspector": {
7
+ "version": 1,
8
+ "plugin": {
9
+ "id": "weather",
10
+ "priority": "high",
11
+ "seams": ["dynamic-tool"],
12
+ "sourceRoot": "src",
13
+ "expect": {
14
+ "registrations": ["registerTool"]
15
+ }
16
+ },
17
+ "capture": {
18
+ "mockSdk": true
19
+ }
20
+ }
21
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openclaw/plugin-inspector",
3
- "version": "0.1.3",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "Offline compatibility inspector for OpenClaw plugins.",
6
6
  "type": "module",
@@ -36,6 +36,8 @@
36
36
  "./ref-diff": "./src/ref-diff.js",
37
37
  "./runtime-capture-report": "./src/runtime-capture-report.js",
38
38
  "./runtime-profile": "./src/runtime-profile.js",
39
+ "./synthetic-probe-suite": "./src/synthetic-probe-suite.js",
40
+ "./synthetic-probes": "./src/synthetic-probes.js",
39
41
  "./workspace-plan": "./src/workspace-plan.js"
40
42
  },
41
43
  "files": [
@@ -47,6 +49,7 @@
47
49
  ],
48
50
  "scripts": {
49
51
  "check": "npm test && npm pack --dry-run",
52
+ "release:crabpot": "node scripts/check-crabpot-followthrough.mjs",
50
53
  "release:local": "npm run check",
51
54
  "test": "node --test test/*.test.js"
52
55
  },
package/src/advanced.js CHANGED
@@ -33,6 +33,14 @@ export {
33
33
  renderCiSummaryMarkdown,
34
34
  writeCiSummary,
35
35
  } from "./ci-summary.js";
36
+ export {
37
+ buildSarifReport,
38
+ defaultJunitPath,
39
+ defaultSarifPath,
40
+ renderJunitXml,
41
+ reportFindings,
42
+ writeCiOutputArtifacts,
43
+ } from "./ci-outputs.js";
36
44
  export {
37
45
  buildContractProbes,
38
46
  contractProbeRules,
@@ -122,13 +130,16 @@ export {
122
130
  loadPluginRootConfig,
123
131
  normalizeInspectorConfig,
124
132
  normalizePluginRootConfig,
133
+ packageJsonConfigKeys,
125
134
  packageId,
126
135
  validateInspectorConfig,
127
136
  } from "./config.js";
128
137
  export {
129
138
  buildPluginInspectorConfig,
139
+ defaultInitPackageScripts,
130
140
  defaultInitConfigPath,
131
141
  defaultInitWorkflowPath,
142
+ detectPackageManager,
132
143
  renderGithubActionsWorkflow,
133
144
  writePluginInspectorInit,
134
145
  } from "./init.js";
@@ -187,6 +198,7 @@ export {
187
198
  validateSyntheticProbePlan,
188
199
  writeSyntheticProbePlan,
189
200
  } from "./synthetic-probes.js";
201
+ export { buildSyntheticProbePlanFromReport } from "./synthetic-probe-suite.js";
190
202
  export {
191
203
  buildWorkspacePlan,
192
204
  defaultWorkspacePlanOptions,
@@ -194,3 +206,10 @@ export {
194
206
  validateWorkspacePlan,
195
207
  writeWorkspacePlan,
196
208
  } from "./workspace-plan.js";
209
+ export {
210
+ inspectCompatibilityFixtureSetConfig,
211
+ renderFixtureSetIssuesReport,
212
+ renderFixtureSetMarkdownReport,
213
+ runFixtureSetReport,
214
+ writeFixtureSetReports,
215
+ } from "./api.js";
package/src/api.js CHANGED
@@ -1,9 +1,11 @@
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 { renderCompatibilityIssuesReport, renderCompatibilityMarkdownReport } from "./compatibility-report.js";
4
5
  import { writePluginInspectorInit } from "./init.js";
5
6
  import { captureEntrypoint } from "./inspector.js";
6
7
  import { renderTextSummary, writeCompatibilityReport } from "./report.js";
8
+ import { writeCiOutputArtifacts } from "./ci-outputs.js";
7
9
  import { buildRuntimeCaptureReport, writeRuntimeCaptureReport } from "./runtime-capture-report.js";
8
10
  import { inspectCompatibilityFixtureSet, inspectFixtureSet } from "./inspector.js";
9
11
 
@@ -34,6 +36,15 @@ export async function inspectFixtureSetConfig(options = {}) {
34
36
  return inspectFixtureSet(config, { generatedAt: options.generatedAt });
35
37
  }
36
38
 
39
+ export async function inspectCompatibilityFixtureSetConfig(options = {}) {
40
+ const config = await loadFixtureSetConfig(options);
41
+ return inspectCompatibilityFixtureSet(config, {
42
+ generatedAt: options.generatedAt,
43
+ openclawPath: options.openclawPath,
44
+ targetOpenClaw: options.targetOpenClaw,
45
+ });
46
+ }
47
+
37
48
  export async function writePluginReports(report, options = {}) {
38
49
  return writeCompatibilityReport(report, {
39
50
  basename: options.basename,
@@ -44,6 +55,38 @@ export async function writePluginReports(report, options = {}) {
44
55
  });
45
56
  }
46
57
 
58
+ export async function writeFixtureSetReports(report, options = {}) {
59
+ return writeCompatibilityReport(report, {
60
+ basename: options.basename,
61
+ check: options.check,
62
+ cwd: options.cwd,
63
+ formatEvidence: options.formatEvidence,
64
+ issuesBasename: options.issuesBasename,
65
+ issuesPath: options.issuesPath,
66
+ issuesTitle: options.issuesTitle,
67
+ jsonPath: options.jsonPath,
68
+ markdownPath: options.markdownPath,
69
+ markdownTitle: options.markdownTitle,
70
+ outDir: options.outDir,
71
+ severityLabels: options.severityLabels,
72
+ title: options.title,
73
+ });
74
+ }
75
+
76
+ export function renderFixtureSetMarkdownReport(report, options = {}) {
77
+ return renderCompatibilityMarkdownReport(report, options);
78
+ }
79
+
80
+ export function renderFixtureSetIssuesReport(report, options = {}) {
81
+ return renderCompatibilityIssuesReport(report, options);
82
+ }
83
+
84
+ export async function runFixtureSetReport(options = {}) {
85
+ const report = await inspectCompatibilityFixtureSetConfig(options);
86
+ const paths = options.write === false ? null : await writeFixtureSetReports(report, options);
87
+ return { report, paths };
88
+ }
89
+
47
90
  export async function runPluginCheck(options = {}) {
48
91
  const outDir = options.outDir ?? "reports";
49
92
  const config = await loadPluginConfig(options);
@@ -54,8 +97,8 @@ export async function runPluginCheck(options = {}) {
54
97
  const mockSdk = options.mockSdk ?? config.capture?.mockSdk ?? true;
55
98
 
56
99
  if (capture === true) {
57
- if (process.env.PLUGIN_INSPECTOR_EXECUTE_ISOLATED !== "1") {
58
- throw new Error("runtime capture imports plugin code; rerun with PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 in an isolated workspace");
100
+ if (!executionAllowed(options)) {
101
+ throw new Error("runtime capture imports plugin code; rerun with PLUGIN_INSPECTOR_EXECUTE_ISOLATED=1 or --allow-execute in an isolated workspace");
59
102
  }
60
103
  const runtimeCapture = await buildRuntimeCaptureReport({
61
104
  mockSdk,
@@ -88,4 +131,18 @@ export async function setupPluginInspector(options = {}) {
88
131
  return writePluginInspectorInit(options);
89
132
  }
90
133
 
91
- export { createCaptureApi, renderTextSummary };
134
+ export { createCaptureApi, renderTextSummary, writeCiOutputArtifacts };
135
+
136
+ function executionAllowed(options) {
137
+ return options.allowExecution === true || process.env.PLUGIN_INSPECTOR_EXECUTE_ISOLATED === "1";
138
+ }
139
+
140
+ async function loadFixtureSetConfig(options) {
141
+ if (options.config) {
142
+ return {
143
+ ...options.config,
144
+ rootDir: options.config.rootDir ?? options.rootDir ?? options.cwd ?? process.cwd(),
145
+ };
146
+ }
147
+ return loadInspectorConfig(options.configPath, { cwd: options.cwd ?? options.rootDir });
148
+ }
@@ -1,6 +1,6 @@
1
1
  export const defaultCaptureApiRegistrarProfiles = {
2
2
  registerChannel: {
3
- returnValue: ({ args }) => registrationObject(args, { id: "channel" }),
3
+ returnValue: ({ args }) => channelRegistrationObject(args),
4
4
  },
5
5
  registerCli: {
6
6
  returnValue: ({ args }) => registrationObject(args, { name: "cli" }),
@@ -12,7 +12,7 @@ export const defaultCaptureApiRegistrarProfiles = {
12
12
  returnValue: ({ args }) => registrationObject(args, { id: "context-engine" }),
13
13
  },
14
14
  registerGatewayMethod: {
15
- returnValue: ({ args }) => registrationObject(args, { name: "gateway.method" }),
15
+ returnValue: ({ args }) => gatewayMethodRegistrationObject(args),
16
16
  },
17
17
  registerHook: {
18
18
  returnValue: ({ api }) => api,
@@ -37,9 +37,10 @@ export const defaultCaptureApiRegistrarProfiles = {
37
37
  },
38
38
  registerService: {
39
39
  returnValue: ({ args }) => ({
40
- ...registrationObject(args, { name: "service" }),
40
+ ...registrationObject(args, { id: "service", name: "service" }),
41
41
  start: async () => undefined,
42
42
  stop: async () => undefined,
43
+ dispose: async () => undefined,
43
44
  }),
44
45
  },
45
46
  registerSpeechProvider: {
@@ -125,6 +126,7 @@ export function createCaptureApi(options = {}) {
125
126
 
126
127
  export function createCaptureContext(options = {}) {
127
128
  return {
129
+ registrationMode: options.registrationMode ?? "full",
128
130
  config: options.config ?? {},
129
131
  logger: options.logger ?? console,
130
132
  pluginConfig: options.pluginConfig ?? {},
@@ -142,6 +144,12 @@ export function createCaptureContext(options = {}) {
142
144
  },
143
145
  gateway: options.gateway ?? {
144
146
  baseUrl: "http://127.0.0.1:0",
147
+ async call(method, params) {
148
+ return { ok: true, method, params };
149
+ },
150
+ respond(ok, result, error) {
151
+ return { ok, result, ...(error ? { error } : {}) };
152
+ },
145
153
  registerRoute(route) {
146
154
  return {
147
155
  ...route,
@@ -223,6 +231,7 @@ function createStoreContext() {
223
231
 
224
232
  function registrationObject(args, defaults) {
225
233
  const first = args[0];
234
+ const callable = firstCallable(args);
226
235
  if (first && typeof first === "object") {
227
236
  return {
228
237
  ...defaults,
@@ -232,13 +241,59 @@ function registrationObject(args, defaults) {
232
241
  };
233
242
  }
234
243
  if (typeof first === "string") {
235
- return {
244
+ return withCallableDefaults({
236
245
  ...defaults,
237
246
  name: first,
238
247
  id: defaults.id,
248
+ }, callable);
249
+ }
250
+ return withCallableDefaults({ ...defaults }, callable);
251
+ }
252
+
253
+ function channelRegistrationObject(args) {
254
+ const first = args[0];
255
+ const registration = registrationObject(args, { id: "channel" });
256
+ if (first?.plugin && typeof first.plugin === "object") {
257
+ return {
258
+ ...registration,
259
+ id: objectId(first.plugin) ?? registration.id,
260
+ plugin: first.plugin,
239
261
  };
240
262
  }
241
- return { ...defaults };
263
+ return registration;
264
+ }
265
+
266
+ function gatewayMethodRegistrationObject(args) {
267
+ const [method, handler, options] = args;
268
+ const registration = registrationObject(args, { name: "gateway.method" });
269
+ if (typeof method !== "string") {
270
+ return registration;
271
+ }
272
+ return {
273
+ ...registration,
274
+ name: method,
275
+ method,
276
+ handler: typeof handler === "function" ? handler : registration.handler,
277
+ run: typeof handler === "function" ? handler : registration.run,
278
+ execute: typeof handler === "function" ? handler : registration.execute,
279
+ scope: options?.scope ?? registration.scope,
280
+ };
281
+ }
282
+
283
+ function firstCallable(args) {
284
+ return args.find((arg) => typeof arg === "function");
285
+ }
286
+
287
+ function withCallableDefaults(value, callable) {
288
+ if (!callable) {
289
+ return value;
290
+ }
291
+ return {
292
+ ...value,
293
+ handler: typeof value.handler === "function" ? value.handler : callable,
294
+ run: typeof value.run === "function" ? value.run : callable,
295
+ execute: typeof value.execute === "function" ? value.execute : callable,
296
+ };
242
297
  }
243
298
 
244
299
  function summarizeArguments(args) {