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