@deeeed/metamask-harness 0.5.1 → 0.6.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.
Files changed (89) hide show
  1. package/CHANGELOG.md +37 -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/stop-metro.sh +20 -0
  12. package/adapters/shared/cli-ux.sh +20 -2
  13. package/adapters/shared/ensure-runner-deps.sh +30 -0
  14. package/adapters/shared/reap-checkout-metros.sh +53 -0
  15. package/adapters/shared/recipe-harness-root.mjs +23 -0
  16. package/adapters/shared/resolve-farmslot-ports-core.mjs +15 -6
  17. package/bin/mm-harness +45 -4
  18. package/dist/adapters/extension/runtime.js +3 -1
  19. package/dist/adapters/mobile/provision.js +34 -1
  20. package/dist/adapters/slot-ports.js +3 -8
  21. package/dist/cli.js +32 -1463
  22. package/dist/commands/call.js +183 -0
  23. package/dist/commands/completion-candidates.js +58 -0
  24. package/dist/commands/doctor.js +101 -0
  25. package/dist/commands/ensure-ready.js +24 -0
  26. package/dist/commands/flows.js +62 -0
  27. package/dist/commands/launch/extension.js +40 -0
  28. package/dist/commands/{launch.js → launch/index.js} +15 -47
  29. package/dist/commands/launch/mobile.js +10 -0
  30. package/dist/commands/manifest.js +72 -0
  31. package/dist/commands/parse-args.js +189 -0
  32. package/dist/commands/provision.js +136 -0
  33. package/dist/commands/resolve-extension.js +23 -0
  34. package/dist/commands/run-engine.js +341 -0
  35. package/dist/commands/run.js +217 -0
  36. package/dist/commands/runtime-decision.js +58 -0
  37. package/dist/commands/runtime-health.js +25 -0
  38. package/dist/commands/runtime-launch.js +139 -0
  39. package/dist/commands/self-test.js +52 -0
  40. package/dist/commands/stop.js +52 -0
  41. package/dist/harness.js +8 -48
  42. package/dist/mm-harness-cli.js +13 -8
  43. package/docs/CLI-SPEC.md +1 -1
  44. package/docs/CODE-MAP.md +62 -0
  45. package/library/README.md +14 -0
  46. package/library/actions/extension/platform/cdp.mjs +1 -1
  47. package/library/actions/extension/wallet/ensure_unlocked.mjs +6 -0
  48. package/library/actions/mobile/wallet/ensure_unlocked.mjs +13 -1
  49. package/package.json +6 -7
  50. package/src/adapters/core/surface.ts +0 -71
  51. package/src/adapters/extension/ensure-ready.ts +0 -185
  52. package/src/adapters/extension/extension-id.ts +0 -107
  53. package/src/adapters/extension/runtime-decision.ts +0 -445
  54. package/src/adapters/extension/runtime.ts +0 -407
  55. package/src/adapters/extension/surface.ts +0 -88
  56. package/src/adapters/mobile/deps-markers.ts +0 -21
  57. package/src/adapters/mobile/prepare.ts +0 -246
  58. package/src/adapters/mobile/provision.ts +0 -594
  59. package/src/adapters/mobile/runtime-decision.ts +0 -466
  60. package/src/adapters/mobile/surface.ts +0 -71
  61. package/src/adapters/resolve-farmslot-ports.ts +0 -13
  62. package/src/adapters/slot-ports.ts +0 -158
  63. package/src/adapters/surface.ts +0 -117
  64. package/src/adapters.ts +0 -601
  65. package/src/cli-color.ts +0 -92
  66. package/src/cli-commands.ts +0 -250
  67. package/src/cli-version.ts +0 -141
  68. package/src/cli.ts +0 -2091
  69. package/src/commands/debug.ts +0 -65
  70. package/src/commands/fixtures.ts +0 -198
  71. package/src/commands/launch.ts +0 -470
  72. package/src/commands/logs.ts +0 -99
  73. package/src/commands/shared.ts +0 -235
  74. package/src/commands/update.ts +0 -316
  75. package/src/completions-cache.ts +0 -86
  76. package/src/doctor.ts +0 -215
  77. package/src/harness.ts +0 -797
  78. package/src/heal-bounds.ts +0 -198
  79. package/src/index.ts +0 -15
  80. package/src/leaf-invoke.ts +0 -28
  81. package/src/live-adapter-contract.ts +0 -274
  82. package/src/manifest.ts +0 -47
  83. package/src/mm-harness-cli.ts +0 -655
  84. package/src/paths.ts +0 -198
  85. package/src/progress.ts +0 -117
  86. package/src/recording-target.ts +0 -147
  87. package/src/run-recording.ts +0 -329
  88. package/src/runner.ts +0 -108
  89. 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
- }