@deeeed/metamask-harness 0.5.1 → 0.6.1

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.
Files changed (90) hide show
  1. package/CHANGELOG.md +47 -20
  2. package/adapters/core/inject.sh +1 -0
  3. package/adapters/extension/cleanup.mjs +1 -1
  4. package/adapters/extension/inject.mjs +2 -1
  5. package/adapters/extension/launch-browser.cjs +18 -4
  6. package/adapters/extension/live.sh +14 -2
  7. package/adapters/extension/sidepanel-toggle.sh +33 -12
  8. package/adapters/extension/wallet-fixture-state.cjs +38 -24
  9. package/adapters/manifest.json +13 -0
  10. package/adapters/mobile/inject.sh +1 -0
  11. package/adapters/mobile/start-metro.sh +6 -0
  12. package/adapters/mobile/stop-metro.sh +20 -0
  13. package/adapters/shared/cli-ux.sh +20 -2
  14. package/adapters/shared/ensure-runner-deps.sh +30 -0
  15. package/adapters/shared/reap-checkout-metros.sh +58 -0
  16. package/adapters/shared/recipe-harness-root.mjs +23 -0
  17. package/adapters/shared/resolve-farmslot-ports-core.mjs +15 -6
  18. package/bin/mm-harness +45 -4
  19. package/dist/adapters/extension/runtime.js +3 -1
  20. package/dist/adapters/mobile/provision.js +34 -1
  21. package/dist/adapters/slot-ports.js +3 -8
  22. package/dist/cli.js +32 -1463
  23. package/dist/commands/call.js +183 -0
  24. package/dist/commands/completion-candidates.js +58 -0
  25. package/dist/commands/doctor.js +101 -0
  26. package/dist/commands/ensure-ready.js +24 -0
  27. package/dist/commands/flows.js +62 -0
  28. package/dist/commands/launch/extension.js +40 -0
  29. package/dist/commands/{launch.js → launch/index.js} +15 -47
  30. package/dist/commands/launch/mobile.js +10 -0
  31. package/dist/commands/manifest.js +72 -0
  32. package/dist/commands/parse-args.js +189 -0
  33. package/dist/commands/provision.js +136 -0
  34. package/dist/commands/resolve-extension.js +23 -0
  35. package/dist/commands/run-engine.js +341 -0
  36. package/dist/commands/run.js +217 -0
  37. package/dist/commands/runtime-decision.js +58 -0
  38. package/dist/commands/runtime-health.js +25 -0
  39. package/dist/commands/runtime-launch.js +139 -0
  40. package/dist/commands/self-test.js +52 -0
  41. package/dist/commands/stop.js +52 -0
  42. package/dist/harness.js +8 -48
  43. package/dist/mm-harness-cli.js +13 -8
  44. package/docs/CLI-SPEC.md +1 -1
  45. package/docs/CODE-MAP.md +62 -0
  46. package/library/README.md +14 -0
  47. package/library/actions/extension/platform/cdp.mjs +1 -1
  48. package/library/actions/extension/wallet/ensure_unlocked.mjs +6 -0
  49. package/library/actions/mobile/wallet/ensure_unlocked.mjs +13 -1
  50. package/package.json +6 -7
  51. package/src/adapters/core/surface.ts +0 -71
  52. package/src/adapters/extension/ensure-ready.ts +0 -185
  53. package/src/adapters/extension/extension-id.ts +0 -107
  54. package/src/adapters/extension/runtime-decision.ts +0 -445
  55. package/src/adapters/extension/runtime.ts +0 -407
  56. package/src/adapters/extension/surface.ts +0 -88
  57. package/src/adapters/mobile/deps-markers.ts +0 -21
  58. package/src/adapters/mobile/prepare.ts +0 -246
  59. package/src/adapters/mobile/provision.ts +0 -594
  60. package/src/adapters/mobile/runtime-decision.ts +0 -466
  61. package/src/adapters/mobile/surface.ts +0 -71
  62. package/src/adapters/resolve-farmslot-ports.ts +0 -13
  63. package/src/adapters/slot-ports.ts +0 -158
  64. package/src/adapters/surface.ts +0 -117
  65. package/src/adapters.ts +0 -601
  66. package/src/cli-color.ts +0 -92
  67. package/src/cli-commands.ts +0 -250
  68. package/src/cli-version.ts +0 -141
  69. package/src/cli.ts +0 -2091
  70. package/src/commands/debug.ts +0 -65
  71. package/src/commands/fixtures.ts +0 -198
  72. package/src/commands/launch.ts +0 -470
  73. package/src/commands/logs.ts +0 -99
  74. package/src/commands/shared.ts +0 -235
  75. package/src/commands/update.ts +0 -316
  76. package/src/completions-cache.ts +0 -86
  77. package/src/doctor.ts +0 -215
  78. package/src/harness.ts +0 -797
  79. package/src/heal-bounds.ts +0 -198
  80. package/src/index.ts +0 -15
  81. package/src/leaf-invoke.ts +0 -28
  82. package/src/live-adapter-contract.ts +0 -274
  83. package/src/manifest.ts +0 -47
  84. package/src/mm-harness-cli.ts +0 -655
  85. package/src/paths.ts +0 -198
  86. package/src/progress.ts +0 -117
  87. package/src/recording-target.ts +0 -147
  88. package/src/run-recording.ts +0 -329
  89. package/src/runner.ts +0 -108
  90. package/src/types.ts +0 -57
@@ -1,329 +0,0 @@
1
- import { spawn, spawnSync, type ChildProcessWithoutNullStreams } from 'node:child_process';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
-
5
- import type { RecipeRunResult } from '@farmslot/recipe-harness';
6
-
7
- import {
8
- captureHelperSupportsRecordSessionSnapshots,
9
- resolveExtensionBrowserPid,
10
- } from './recording-target.ts';
11
- import type { MetaMaskRecipeAdapter } from './types.ts';
12
-
13
- export interface RecipeRecordingOptions {
14
- record?: boolean;
15
- cdpPort?: string;
16
- }
17
-
18
- interface ActiveRecipeRecording {
19
- child: ChildProcessWithoutNullStreams;
20
- outputPath: string;
21
- relativePath: string;
22
- pid: number;
23
- stdout: string;
24
- stderr: string;
25
- exited: boolean;
26
- exitCode: number | null;
27
- error?: Error;
28
- stderrBuffer: string;
29
- pendingSnapshots: Map<string, PendingRecordingSnapshot>;
30
- }
31
-
32
- interface PendingRecordingSnapshot {
33
- outputPath: string;
34
- timer: NodeJS.Timeout;
35
- resolve: (event: Record<string, unknown>) => void;
36
- reject: (error: Error) => void;
37
- }
38
-
39
- const activeRecordingsByPid = new Map<number, ActiveRecipeRecording>();
40
-
41
- export async function startRecipeRecording(
42
- adapter: MetaMaskRecipeAdapter,
43
- projectRoot: string,
44
- artifactsDir: string,
45
- options: RecipeRecordingOptions,
46
- ): Promise<ActiveRecipeRecording | undefined> {
47
- if (!options.record) return undefined;
48
- if (process.platform !== 'darwin') {
49
- console.error(
50
- 'WARN: --record uses capture-helper and is currently supported only on macOS; continuing without video.',
51
- );
52
- return undefined;
53
- }
54
-
55
- if (adapter !== 'extension') return undefined;
56
-
57
- const cdpPort = options.cdpPort ?? process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT;
58
- if (!captureHelperSupportsRecordSessionSnapshots(projectRoot)) {
59
- console.error(
60
- 'WARN: framed extension recording requires capture-helper capability record_session_snapshot; falling back to harness --record-video.',
61
- );
62
- return undefined;
63
- }
64
- const pid = resolveExtensionBrowserPid(projectRoot, artifactsDir, cdpPort);
65
- if (!pid) {
66
- console.error(
67
- `WARN: framed extension recording could not resolve browser PID from CDP port ${cdpPort ?? '<unset>'}; falling back to harness --record-video.`,
68
- );
69
- return undefined;
70
- }
71
- const recordArgs = ['record', '--framed', '--pid', String(pid)];
72
-
73
- const relativePath = 'videos/full-run.mp4';
74
- const outputPath = path.join(artifactsDir, relativePath);
75
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
76
- fs.rmSync(outputPath, { force: true });
77
- const child = spawn(captureHelperPath(), [...recordArgs, '--output', outputPath], {
78
- cwd: projectRoot,
79
- env: process.env,
80
- stdio: ['pipe', 'pipe', 'pipe'],
81
- });
82
- const recording: ActiveRecipeRecording = {
83
- child,
84
- outputPath,
85
- relativePath,
86
- pid,
87
- stdout: '',
88
- stderr: '',
89
- exited: false,
90
- exitCode: null,
91
- stderrBuffer: '',
92
- pendingSnapshots: new Map(),
93
- };
94
- child.stdout.on('data', (chunk) => {
95
- recording.stdout += String(chunk);
96
- });
97
- child.stderr.on('data', (chunk) => {
98
- recording.stderr += String(chunk);
99
- handleRecordingStderr(recording, String(chunk));
100
- });
101
- child.on('error', (error) => {
102
- recording.error = error;
103
- recording.stderr += error.message;
104
- recording.exited = true;
105
- });
106
- child.on('close', (exitCode) => {
107
- recording.exited = true;
108
- recording.exitCode = exitCode;
109
- activeRecordingsByPid.delete(recording.pid);
110
- rejectPendingSnapshots(recording, new Error(`capture-helper recording exited before snapshot completed (code=${exitCode ?? 'unknown'})`));
111
- });
112
-
113
- await sleep(750);
114
- if (recording.exited) {
115
- console.error(
116
- `WARN: capture-helper record exited before the recipe started (code=${recording.exitCode ?? 'unknown'}): ${recording.stderr || recording.stdout}`,
117
- );
118
- return undefined;
119
- }
120
- activeRecordingsByPid.set(pid, recording);
121
- console.error(`INFO: recording recipe video with capture-helper pid=${pid} output=${outputPath}`);
122
- return recording;
123
- }
124
-
125
- export async function captureActiveRecipeRecordingSnapshot(
126
- pid: number,
127
- outputPath: string,
128
- timeoutMs = 30_000,
129
- ): Promise<Record<string, unknown> | undefined> {
130
- const recording = activeRecordingsByPid.get(pid);
131
- if (!recording || recording.exited) return undefined;
132
- fs.mkdirSync(path.dirname(outputPath), { recursive: true });
133
- fs.rmSync(outputPath, { force: true });
134
- return new Promise<Record<string, unknown>>((resolve, reject) => {
135
- const timer = setTimeout(() => {
136
- recording.pendingSnapshots.delete(outputPath);
137
- reject(new Error(`capture-helper record session snapshot timed out after ${timeoutMs}ms: ${outputPath}`));
138
- }, timeoutMs);
139
- recording.pendingSnapshots.set(outputPath, { outputPath, timer, resolve, reject });
140
- recording.child.stdin.write(`snapshot ${outputPath}\n`, (error) => {
141
- if (!error) return;
142
- clearTimeout(timer);
143
- recording.pendingSnapshots.delete(outputPath);
144
- reject(error);
145
- });
146
- });
147
- }
148
-
149
- export async function stopRecipeRecording(
150
- recording: ActiveRecipeRecording | undefined,
151
- result?: RecipeRunResult,
152
- ): Promise<void> {
153
- if (!recording) return;
154
- if (!recording.exited) {
155
- recording.child.stdin.end('stop\n');
156
- await waitForRecordingExit(recording, 15_000);
157
- }
158
- if (!recording.exited) {
159
- recording.child.kill('SIGINT');
160
- await waitForRecordingExit(recording, 5_000);
161
- }
162
- if (!recording.exited) {
163
- recording.child.kill('SIGTERM');
164
- await waitForRecordingExit(recording, 3_000);
165
- }
166
- const validation = validateRecordingArtifact(recording);
167
- if (validation.ok === false) {
168
- try {
169
- fs.rmSync(recording.outputPath, { force: true });
170
- } catch (error) {
171
- console.error(
172
- `WARN: could not remove unusable capture-helper video ${recording.outputPath}: ${error instanceof Error ? error.message : String(error)}`,
173
- );
174
- }
175
- console.error(
176
- `WARN: capture-helper recording did not produce a usable video artifact: ${validation.reason}`,
177
- );
178
- if (result) removeRecordingArtifactFromManifest(result, recording.relativePath);
179
- return;
180
- }
181
- if (result) addRecordingArtifactToManifest(result, recording);
182
- }
183
-
184
- async function waitForRecordingExit(recording: ActiveRecipeRecording, timeoutMs: number): Promise<void> {
185
- if (recording.exited) return;
186
- await new Promise<void>((resolve) => {
187
- const timer = setTimeout(resolve, timeoutMs);
188
- recording.child.once('close', () => {
189
- clearTimeout(timer);
190
- resolve();
191
- });
192
- });
193
- }
194
-
195
- function handleRecordingStderr(recording: ActiveRecipeRecording, chunk: string): void {
196
- recording.stderrBuffer += chunk;
197
- let newlineIndex = recording.stderrBuffer.indexOf('\n');
198
- while (newlineIndex !== -1) {
199
- const line = recording.stderrBuffer.slice(0, newlineIndex).trim();
200
- recording.stderrBuffer = recording.stderrBuffer.slice(newlineIndex + 1);
201
- if (line) handleRecordingEventLine(recording, line);
202
- newlineIndex = recording.stderrBuffer.indexOf('\n');
203
- }
204
- }
205
-
206
- function handleRecordingEventLine(recording: ActiveRecipeRecording, line: string): void {
207
- let event: Record<string, unknown>;
208
- try {
209
- const parsed = JSON.parse(line);
210
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return;
211
- event = parsed as Record<string, unknown>;
212
- } catch {
213
- // capture-helper may emit human-readable stderr lines; only JSON event
214
- // records are relevant for in-session screenshot correlation.
215
- return;
216
- }
217
- const output = typeof event.output === 'string' ? event.output : undefined;
218
- if (!output) return;
219
- const pending = recording.pendingSnapshots.get(output);
220
- if (!pending) return;
221
- if (event.type === 'snapshot') {
222
- clearTimeout(pending.timer);
223
- recording.pendingSnapshots.delete(output);
224
- pending.resolve(event);
225
- return;
226
- }
227
- if (event.type === 'error') {
228
- clearTimeout(pending.timer);
229
- recording.pendingSnapshots.delete(output);
230
- pending.reject(new Error(String(event.message ?? `capture-helper snapshot failed: ${output}`)));
231
- }
232
- }
233
-
234
- function rejectPendingSnapshots(recording: ActiveRecipeRecording, error: Error): void {
235
- for (const pending of recording.pendingSnapshots.values()) {
236
- clearTimeout(pending.timer);
237
- pending.reject(error);
238
- }
239
- recording.pendingSnapshots.clear();
240
- }
241
-
242
- function validateRecordingArtifact(recording: ActiveRecipeRecording): { ok: true } | { ok: false; reason: string } {
243
- if (!fs.existsSync(recording.outputPath)) {
244
- return { ok: false, reason: `missing output ${recording.outputPath}` };
245
- }
246
- const size = fs.statSync(recording.outputPath).size;
247
- if (size === 0) {
248
- return { ok: false, reason: `empty output ${recording.outputPath}` };
249
- }
250
- const recorderOutput = `${recording.stdout}\n${recording.stderr}`;
251
- if (!recorderOutput.includes('record_complete')) {
252
- return {
253
- ok: false,
254
- reason: `capture-helper did not report record_complete for ${recording.outputPath}: ${recorderOutput.trim() || 'no recorder output'}`,
255
- };
256
- }
257
-
258
- const ffprobe = spawnSync(
259
- 'ffprobe',
260
- ['-hide_banner', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', recording.outputPath],
261
- { encoding: 'utf8' },
262
- );
263
- if (ffprobe.error && (ffprobe.error as NodeJS.ErrnoException).code === 'ENOENT') {
264
- return { ok: true };
265
- }
266
- if (ffprobe.error) {
267
- return { ok: false, reason: `ffprobe failed for ${recording.outputPath}: ${ffprobe.error.message}` };
268
- }
269
- if (ffprobe.status !== 0) {
270
- return {
271
- ok: false,
272
- reason: `invalid MP4 ${recording.outputPath}: ${ffprobe.stderr.trim() || ffprobe.stdout.trim() || `ffprobe exited ${ffprobe.status}`}`,
273
- };
274
- }
275
- const durationSeconds = Number(ffprobe.stdout.trim());
276
- if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) {
277
- return { ok: false, reason: `MP4 has no positive duration: ${recording.outputPath}` };
278
- }
279
- return { ok: true };
280
- }
281
-
282
- function addRecordingArtifactToManifest(result: RecipeRunResult, recording: ActiveRecipeRecording): void {
283
- const manifestPath = result.artifactManifestPath;
284
- if (!manifestPath || !fs.existsSync(manifestPath)) {
285
- console.error(`WARN: cannot add video artifact to missing artifact manifest: ${manifestPath ?? '<unset>'}`);
286
- return;
287
- }
288
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as {
289
- artifacts?: Array<Record<string, unknown>>;
290
- };
291
- if (!Array.isArray(manifest.artifacts)) manifest.artifacts = [];
292
- manifest.artifacts = manifest.artifacts.filter((artifact) => artifact.path !== recording.relativePath);
293
- manifest.artifacts.push({
294
- path: recording.relativePath,
295
- type: 'video',
296
- label: 'Full recipe replay video',
297
- category: 'evidence',
298
- mimeType: 'video/mp4',
299
- record: 'full_run',
300
- metadata: {
301
- provider: 'capture-helper',
302
- mode: 'full_run',
303
- pid: recording.pid,
304
- },
305
- });
306
- fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
307
- }
308
-
309
- function removeRecordingArtifactFromManifest(result: RecipeRunResult, relativePath: string): void {
310
- const manifestPath = result.artifactManifestPath;
311
- if (!manifestPath || !fs.existsSync(manifestPath)) return;
312
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as {
313
- artifacts?: Array<Record<string, unknown>>;
314
- };
315
- if (!Array.isArray(manifest.artifacts)) return;
316
- const nextArtifacts = manifest.artifacts.filter((artifact) => artifact.path !== relativePath);
317
- if (nextArtifacts.length === manifest.artifacts.length) return;
318
- manifest.artifacts = nextArtifacts;
319
- fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
320
- }
321
-
322
-
323
- function captureHelperPath(): string {
324
- return process.env.CAPTURE_HELPER_PATH || 'capture-helper';
325
- }
326
-
327
- function sleep(ms: number): Promise<void> {
328
- return new Promise((resolve) => setTimeout(resolve, ms));
329
- }
package/src/runner.ts DELETED
@@ -1,108 +0,0 @@
1
- import { execSync } from 'node:child_process';
2
-
3
- import type { RecipeActionManifestDocument } from '@farmslot/protocol';
4
- import type { ActionAdapter, RecipeRunner } from '@farmslot/recipe-harness';
5
-
6
- import { createMetaMaskAdapters, createMetaMaskUiTransport } from './adapters.ts';
7
- import { loadMetaMaskExtensionActionManifest, loadMetaMaskMobileActionManifest } from './manifest.ts';
8
- import { createMetaMaskRecordingTargetProvider } from './recording-target.ts';
9
- import { importRecipeHarness, importRecipeHarnessRuntimeCdp, importRecipeHarnessRuntimeReactNativeBridge, runnerDir } from './paths.ts';
10
- import type { CreateMetaMaskRunnerOptions, MetaMaskRecipeAdapter } from './types.ts';
11
-
12
- export async function createMetaMaskMobileRunner(
13
- options: CreateMetaMaskRunnerOptions = {},
14
- ): Promise<RecipeRunner> {
15
- return createMetaMaskRunner(
16
- 'mobile',
17
- options.actionManifest ?? loadMetaMaskMobileActionManifest(),
18
- );
19
- }
20
-
21
- export async function createMetaMaskExtensionRunner(
22
- options: CreateMetaMaskRunnerOptions = {},
23
- ): Promise<RecipeRunner> {
24
- return createMetaMaskRunner(
25
- 'extension',
26
- options.actionManifest ?? loadMetaMaskExtensionActionManifest(),
27
- );
28
- }
29
-
30
- export async function createMetaMaskRunner(
31
- adapter: MetaMaskRecipeAdapter,
32
- actionManifest: RecipeActionManifestDocument,
33
- options: { quietStdout?: boolean } = {},
34
- ): Promise<RecipeRunner> {
35
- const {
36
- createRecipeRunner,
37
- createStandardCoreAdapters,
38
- createStandardUiAdapters,
39
- } = await importRecipeHarness();
40
- const { createCdpWebUiTransport } = await importRecipeHarnessRuntimeCdp();
41
- const { createReactNativeBridgeUiTransport } = await importRecipeHarnessRuntimeReactNativeBridge();
42
- const actions = [
43
- ...actionManifest.supported_official_actions,
44
- ...(actionManifest.custom_actions ?? []).map((entry: { name: string }) => entry.name),
45
- ];
46
- const declaredActions = new Set(actions);
47
- const core = createStandardCoreAdapters({ actions });
48
- const projectOwnedOfficialActions = new Set(['app.status']);
49
- const ui = createStandardUiAdapters({
50
- actions: actions.filter((action) => !projectOwnedOfficialActions.has(action)),
51
- transport: createMetaMaskUiTransport(adapter, {
52
- createCdpWebUiTransport,
53
- createReactNativeBridgeUiTransport,
54
- }),
55
- });
56
- const existing = new Set([...core, ...ui].map((entry) => entry.action));
57
- const custom: ActionAdapter[] = createMetaMaskAdapters(adapter).filter(
58
- (entry) => declaredActions.has(entry.action) && !existing.has(entry.action),
59
- );
60
- const autoHudDisabled = process.env.METAMASK_RECIPE_AUTO_HUD === '0' || process.env.METAMASK_RECIPE_AUTO_HUD === 'false';
61
- // In --json mode stdout is the machine contract; route every engine log
62
- // (e.g. the "Recipe libraries: …" resolution line, which uses logger.info →
63
- // stdout on the plain console) to stderr so stdout stays parseable JSON.
64
- const logger = options.quietStdout ? new console.Console(process.stderr) : console;
65
- return createRecipeRunner({
66
- actionManifest,
67
- adapters: [...core, ...ui, ...custom],
68
- logger,
69
- recording: {
70
- targetProvider: createMetaMaskRecordingTargetProvider(adapter),
71
- },
72
- runner: runnerProvenance(),
73
- hud: autoHudDisabled
74
- ? false
75
- : {
76
- enabled: true,
77
- display: {
78
- layout: 'docked-bottom',
79
- position: 'bottom',
80
- showTitle: false,
81
- showDebug: false,
82
- maxDetailLines: 2,
83
- },
84
- },
85
- });
86
- }
87
-
88
- function runnerProvenance() {
89
- return {
90
- source: runnerDir,
91
- git_ref: runnerGitRef(),
92
- name: '@metamask/recipe-runner',
93
- };
94
- }
95
-
96
- function runnerGitRef(): string {
97
- try {
98
- return execSync('git rev-parse HEAD', {
99
- cwd: runnerDir,
100
- encoding: 'utf8',
101
- stdio: ['ignore', 'pipe', 'ignore'],
102
- }).trim();
103
- } catch {
104
- // Standalone packaged runners may be copied without a .git directory; keep
105
- // artifact packages valid while source/ref still identifies the runner.
106
- return 'unknown';
107
- }
108
- }
package/src/types.ts DELETED
@@ -1,57 +0,0 @@
1
- import type { RecipeActionManifestDocument, RecipeValidationResult } from '@farmslot/protocol';
2
-
3
- export type MetaMaskRecipeAdapter = 'mobile' | 'extension' | 'core';
4
-
5
- export interface CreateMetaMaskRunnerOptions {
6
- actionManifest?: RecipeActionManifestDocument;
7
- }
8
-
9
- export type RecipeHarnessModule = typeof import('@farmslot/recipe-harness');
10
- export type RecipeHarnessBrowserExtensionModule = typeof import('@farmslot/recipe-harness/runtime/browser-extension');
11
- export type RecipeHarnessCdpModule = typeof import('@farmslot/recipe-harness/runtime/cdp');
12
- export type RecipeHarnessReactNativeBridgeModule = typeof import('@farmslot/recipe-harness/runtime/react-native-bridge');
13
- export type RecipeProtocolModule = typeof import('@farmslot/protocol');
14
-
15
- export interface MetaMaskDoctorReport {
16
- schemaVersion: 1;
17
- protocolVersion: 'v1';
18
- runner_protocol_version: 1;
19
- status: 'pass' | 'fail';
20
- checks: Array<{ id: string; status: 'pass' | 'fail'; message: string }>;
21
- adapter: MetaMaskRecipeAdapter;
22
- target: string;
23
- compatibilityMode:
24
- | 'bridge present'
25
- | 'injected bridge present'
26
- | 'bridge injectable'
27
- | 'product-local harness present'
28
- | 'runner bridge with injected app bridge'
29
- | 'runner bridge with app bridge'
30
- | 'runner bridge available; app bridge not installed'
31
- | 'headless controller (no bridge)'
32
- | 'unsupported/no bridge';
33
- runner: {
34
- name: '@metamask/recipe-runner';
35
- runnerDir: string;
36
- actionManifestPath: string;
37
- harnessPackage: '@farmslot/recipe-harness';
38
- };
39
- shape: Record<string, unknown>;
40
- fixture: Record<string, unknown>;
41
- runtimeContext: MetaMaskRuntimeContextReport;
42
- manifestValidation: RecipeValidationResult['summary'] | Record<string, unknown>;
43
- }
44
-
45
- export interface MetaMaskRuntimeContextField {
46
- value: string | null;
47
- source: 'file' | 'env' | 'default';
48
- envVar: string | null;
49
- customize: string;
50
- }
51
-
52
- export interface MetaMaskRuntimeContextReport {
53
- file: string;
54
- fileExists: boolean;
55
- envOverride: string | null;
56
- fields: Record<string, MetaMaskRuntimeContextField>;
57
- }