@deeeed/metamask-harness 0.2.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 (158) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/README.md +140 -0
  3. package/bin/mm-harness +99 -0
  4. package/docs/CHEATSHEET.md +61 -0
  5. package/docs/CLI-SPEC.md +915 -0
  6. package/docs/MENTAL-MODEL.md +295 -0
  7. package/docs/architecture.md +367 -0
  8. package/docs/extension-runtime-commands.md +60 -0
  9. package/docs/harness-cli.md +43 -0
  10. package/docs/live-adapter-contract.md +188 -0
  11. package/docs/package-boundaries.md +47 -0
  12. package/docs/perps-flow-catalog.md +235 -0
  13. package/docs/recipe-libraries.md +95 -0
  14. package/docs/runtime-file-conventions.md +36 -0
  15. package/library/actions/core/perps/_controller.mjs +727 -0
  16. package/library/actions/core/perps/assert_orders.mjs +53 -0
  17. package/library/actions/core/perps/assert_positions.mjs +52 -0
  18. package/library/actions/core/perps/close_orders.mjs +97 -0
  19. package/library/actions/core/perps/close_positions.mjs +118 -0
  20. package/library/actions/core/perps/ensure_orders.mjs +40 -0
  21. package/library/actions/core/perps/ensure_positions.mjs +37 -0
  22. package/library/actions/core/perps/place_order.mjs +201 -0
  23. package/library/actions/core/perps/read_account.mjs +30 -0
  24. package/library/actions/core/perps/read_orders.mjs +27 -0
  25. package/library/actions/core/perps/read_positions.mjs +27 -0
  26. package/library/actions/core/perps/start_state.mjs +92 -0
  27. package/library/actions/core/perps/teardown_state.mjs +86 -0
  28. package/library/actions/extension/perps/assert_orders.mjs +11 -0
  29. package/library/actions/extension/perps/assert_positions.mjs +11 -0
  30. package/library/actions/extension/perps/close_orders.mjs +8 -0
  31. package/library/actions/extension/perps/close_positions.mjs +8 -0
  32. package/library/actions/extension/perps/ensure_orders.mjs +4 -0
  33. package/library/actions/extension/perps/ensure_positions.mjs +4 -0
  34. package/library/actions/extension/perps/perps.mjs +730 -0
  35. package/library/actions/extension/perps/place_order.mjs +7 -0
  36. package/library/actions/extension/perps/read_orders.mjs +4 -0
  37. package/library/actions/extension/perps/read_positions.mjs +3 -0
  38. package/library/actions/extension/platform/cdp.mjs +541 -0
  39. package/library/actions/extension/ui/navigate.mjs +44 -0
  40. package/library/actions/extension/wallet/ensure_unlocked.mjs +36 -0
  41. package/library/actions/extension/wallet/read_state.mjs +27 -0
  42. package/library/actions/extension/wallet/select_account.mjs +48 -0
  43. package/library/actions/extension/wallet/setup.mjs +35 -0
  44. package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
  45. package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
  46. package/library/actions/mobile/bridge-runtime/cdp-bridge.cjs +686 -0
  47. package/library/actions/mobile/bridge-runtime/lib/cdp-eval.cjs +110 -0
  48. package/library/actions/mobile/bridge-runtime/lib/config.cjs +39 -0
  49. package/library/actions/mobile/bridge-runtime/lib/issue-capture.cjs +446 -0
  50. package/library/actions/mobile/bridge-runtime/lib/target-discovery.cjs +204 -0
  51. package/library/actions/mobile/bridge-runtime/lib/ws-client.cjs +108 -0
  52. package/library/actions/mobile/bridge-runtime/setup-wallet.sh +442 -0
  53. package/library/actions/mobile/perps/assert_orders.mjs +11 -0
  54. package/library/actions/mobile/perps/assert_positions.mjs +11 -0
  55. package/library/actions/mobile/perps/close_orders.mjs +8 -0
  56. package/library/actions/mobile/perps/close_positions.mjs +8 -0
  57. package/library/actions/mobile/perps/ensure_orders.mjs +4 -0
  58. package/library/actions/mobile/perps/ensure_positions.mjs +4 -0
  59. package/library/actions/mobile/perps/perps.mjs +709 -0
  60. package/library/actions/mobile/perps/place_order.mjs +7 -0
  61. package/library/actions/mobile/perps/read_orders.mjs +4 -0
  62. package/library/actions/mobile/perps/read_positions.mjs +3 -0
  63. package/library/actions/mobile/platform/bridge.mjs +283 -0
  64. package/library/actions/mobile/ui/navigate.mjs +38 -0
  65. package/library/actions/mobile/wallet/ensure_unlocked.mjs +107 -0
  66. package/library/actions/mobile/wallet/home.mjs +35 -0
  67. package/library/actions/mobile/wallet/read_state.mjs +40 -0
  68. package/library/actions/mobile/wallet/select_account.mjs +48 -0
  69. package/library/actions/mobile/wallet/setup.mjs +220 -0
  70. package/library/flows/perps.flows.json +64 -0
  71. package/library/library.json +7 -0
  72. package/library/manifests/core.action-manifest.json +1282 -0
  73. package/library/manifests/extension.action-manifest.json +1749 -0
  74. package/library/manifests/mobile.action-manifest.json +1753 -0
  75. package/library/recipes/action-validation.extension.recipe.json +417 -0
  76. package/library/recipes/action-validation.mobile.recipe.json +422 -0
  77. package/library/recipes/order-lifecycle.core.recipe.json +78 -0
  78. package/library/recipes/perps-lifecycle.recipe.json +194 -0
  79. package/library/recipes/read-markets.core.recipe.json +38 -0
  80. package/library/recipes/smoke.extension.recipe.json +31 -0
  81. package/library/recipes/smoke.mobile.recipe.json +31 -0
  82. package/library/recipes/trading-lifecycle.core.recipe.json +76 -0
  83. package/orchestration/compat-overlays/README.md +19 -0
  84. package/orchestration/compat-overlays/mobile/README.md +13 -0
  85. package/orchestration/compat-overlays/mobile/rn81-message-event-source.patch +42 -0
  86. package/orchestration/core/cleanup.sh +37 -0
  87. package/orchestration/core/inject.sh +154 -0
  88. package/orchestration/doctor.mjs +72 -0
  89. package/orchestration/extension/cleanup.mjs +60 -0
  90. package/orchestration/extension/console-tail.mjs +228 -0
  91. package/orchestration/extension/ensure-browser.sh +416 -0
  92. package/orchestration/extension/ensure-ready.ts +185 -0
  93. package/orchestration/extension/extension-id.ts +107 -0
  94. package/orchestration/extension/inject.mjs +266 -0
  95. package/orchestration/extension/launch-browser.cjs +216 -0
  96. package/orchestration/extension/launch.sh +175 -0
  97. package/orchestration/extension/live.sh +320 -0
  98. package/orchestration/extension/pin-remote-flags.cjs +45 -0
  99. package/orchestration/extension/readiness.mjs +414 -0
  100. package/orchestration/extension/refresh-build.sh +190 -0
  101. package/orchestration/extension/runtime-decision.ts +445 -0
  102. package/orchestration/extension/runtime.ts +407 -0
  103. package/orchestration/extension/seed-fixture.sh +177 -0
  104. package/orchestration/extension/sidepanel-toggle.sh +291 -0
  105. package/orchestration/extension/snapshot-dist.sh +84 -0
  106. package/orchestration/extension/start-watch.sh +339 -0
  107. package/orchestration/extension/wallet-fixture-state.cjs +1086 -0
  108. package/orchestration/lib/activate-repo-node.sh +144 -0
  109. package/orchestration/lib/cli-color.mjs +84 -0
  110. package/orchestration/lib/cli-commands.mjs +243 -0
  111. package/orchestration/lib/cli-home.mjs +354 -0
  112. package/orchestration/lib/cli-ux.sh +252 -0
  113. package/orchestration/lib/cli-version.mjs +123 -0
  114. package/orchestration/lib/ensure-runner-deps.sh +56 -0
  115. package/orchestration/lib/harness-path.sh +55 -0
  116. package/orchestration/lib/hash-helpers.sh +44 -0
  117. package/orchestration/lib/json-field.sh +23 -0
  118. package/orchestration/lib/log-tui.mjs +304 -0
  119. package/orchestration/lib/open-debug.mjs +317 -0
  120. package/orchestration/lib/path-defaults.json +4 -0
  121. package/orchestration/lib/progress.mjs +107 -0
  122. package/orchestration/lib/recipe-paths.mjs +26 -0
  123. package/orchestration/lib/resolve-farmslot-ports.sh +144 -0
  124. package/orchestration/manifest.json +358 -0
  125. package/orchestration/mobile/cleanup.sh +192 -0
  126. package/orchestration/mobile/deps-markers.ts +21 -0
  127. package/orchestration/mobile/inject.sh +681 -0
  128. package/orchestration/mobile/launch.sh +137 -0
  129. package/orchestration/mobile/live.sh +125 -0
  130. package/orchestration/mobile/runtime-decision.ts +292 -0
  131. package/orchestration/porcelain/metamask-recipe +99 -0
  132. package/orchestration/porcelain/mm-recipe +1591 -0
  133. package/orchestration/porcelain/mme-recipe +1181 -0
  134. package/package.json +59 -0
  135. package/runner/extension/verify.sh +511 -0
  136. package/runner/mobile/verify.sh +501 -0
  137. package/runner/src/adapters.ts +601 -0
  138. package/runner/src/cli.ts +1820 -0
  139. package/runner/src/commands/debug.ts +44 -0
  140. package/runner/src/commands/fixtures.ts +99 -0
  141. package/runner/src/commands/launch.ts +397 -0
  142. package/runner/src/commands/logs.ts +60 -0
  143. package/runner/src/commands/shared.ts +138 -0
  144. package/runner/src/completions-cache.ts +86 -0
  145. package/runner/src/doctor.ts +203 -0
  146. package/runner/src/harness.ts +516 -0
  147. package/runner/src/heal-bounds.ts +179 -0
  148. package/runner/src/index.ts +6 -0
  149. package/runner/src/live-adapter-contract.ts +274 -0
  150. package/runner/src/manifest.ts +47 -0
  151. package/runner/src/mm-harness-cli.ts +488 -0
  152. package/runner/src/paths.ts +198 -0
  153. package/runner/src/recording-target.ts +147 -0
  154. package/runner/src/run-recording.ts +329 -0
  155. package/runner/src/runner.ts +108 -0
  156. package/runner/src/types.ts +57 -0
  157. package/scripts/completions.sh +125 -0
  158. package/scripts/install-completions.sh +62 -0
@@ -0,0 +1,6 @@
1
+ // Keep the package entrypoint narrow. CLI internals import path/runtime helpers
2
+ // directly so consumers see only the runner factory, manifest, and doctor API.
3
+ export { createDoctorReport } from './doctor.ts';
4
+ export { loadActionManifest, validateManifest } from './manifest.ts';
5
+ export { createMetaMaskExtensionRunner, createMetaMaskMobileRunner, createMetaMaskRunner } from './runner.ts';
6
+ export type { CreateMetaMaskRunnerOptions, MetaMaskDoctorReport, MetaMaskRecipeAdapter } from './types.ts';
@@ -0,0 +1,274 @@
1
+ import { access, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { spawn } from 'node:child_process';
6
+
7
+ import { resolveRequiredLocalProtocolRoot, runnerDir } from './paths.ts';
8
+
9
+ function actionFileStem(action: string) {
10
+ return String(action).replace(/[^a-zA-Z0-9._-]/g, '_');
11
+ }
12
+
13
+ function actionParts(action: string) {
14
+ const parts = String(action).split('.').filter(Boolean);
15
+ if (parts.length >= 3 && parts[0] === 'metamask') {
16
+ return { family: parts[1], localName: parts.slice(2).join('.') };
17
+ }
18
+ if (parts.length >= 2) {
19
+ return { family: parts[0], localName: parts.slice(1).join('.') };
20
+ }
21
+ return { family: 'actions', localName: String(action) };
22
+ }
23
+
24
+ function candidateStems(action: string) {
25
+ const stem = actionFileStem(action);
26
+ const { localName } = actionParts(action);
27
+ const localStem = actionFileStem(localName);
28
+ const stems = [stem];
29
+ if (localStem && localStem !== stem) stems.push(localStem);
30
+ return stems;
31
+ }
32
+
33
+ function candidateFamilies(action: string) {
34
+ const { family } = actionParts(action);
35
+ const families = [family];
36
+ return [...new Set(families)];
37
+ }
38
+
39
+ function candidatePaths(platform: string, action: string) {
40
+ const stems = candidateStems(action);
41
+ const families = candidateFamilies(action);
42
+ const roots = [
43
+ process.env.METAMASK_RECIPE_LIVE_ADAPTER_DIR,
44
+ path.join(runnerDir, 'library/actions'),
45
+ ].filter(Boolean);
46
+ const files = [];
47
+ for (const root of roots) {
48
+ for (const family of families) {
49
+ for (const candidateStem of stems) {
50
+ pushCandidateFiles(files, root, platform, family, candidateStem);
51
+ }
52
+ pushDomainDispatcherFiles(files, root, platform, family);
53
+ }
54
+ for (const candidateStem of stems) {
55
+ pushFlatCandidateFiles(files, root, platform, candidateStem);
56
+ }
57
+ }
58
+ return files;
59
+ }
60
+
61
+ function pushCandidateFiles(files: string[], root: string, platform: string, family: string, stem: string) {
62
+ for (const extension of ['mjs', 'js', 'sh']) {
63
+ files.push(path.join(root, platform, family, `${stem}.${extension}`));
64
+ files.push(path.join(root, 'shared', family, `${stem}.${extension}`));
65
+ }
66
+ }
67
+
68
+
69
+ function pushDomainDispatcherFiles(files: string[], root: string, platform: string, family: string) {
70
+ for (const extension of ['mjs', 'js', 'sh']) {
71
+ files.push(path.join(root, platform, family, `${family}.${extension}`));
72
+ files.push(path.join(root, 'shared', family, `${family}.${extension}`));
73
+ }
74
+ }
75
+
76
+ function pushFlatCandidateFiles(files: string[], root: string, platform: string, stem: string) {
77
+ for (const extension of ['mjs', 'js', 'sh']) {
78
+ files.push(path.join(root, platform, `${stem}.${extension}`));
79
+ files.push(path.join(root, 'shared', `${stem}.${extension}`));
80
+ }
81
+ }
82
+
83
+ async function firstExecutablePath(paths: string[]) {
84
+ for (const file of paths) {
85
+ try {
86
+ await access(file);
87
+ return file;
88
+ } catch (error) {
89
+ // Try the next candidate path. Missing optional live adapters are reported by the caller.
90
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+
96
+ function runProcess(
97
+ command: string,
98
+ args: string[],
99
+ options: { cwd: string; env: NodeJS.ProcessEnv; timeoutMs?: number },
100
+ ): Promise<{ exitCode: number | null; stdout: string; stderr: string; timedOut: boolean }> {
101
+ return new Promise((resolve, reject) => {
102
+ let settled = false;
103
+ const child = spawn(command, args, {
104
+ cwd: options.cwd,
105
+ env: options.env,
106
+ stdio: ['ignore', 'pipe', 'pipe'],
107
+ });
108
+ let stdout = '';
109
+ let stderr = '';
110
+ child.stdout.on('data', (chunk) => {
111
+ stdout += chunk;
112
+ });
113
+ child.stderr.on('data', (chunk) => {
114
+ stderr += chunk;
115
+ });
116
+ const timeout = options.timeoutMs
117
+ ? setTimeout(() => {
118
+ if (settled) return;
119
+ settled = true;
120
+ child.kill('SIGTERM');
121
+ setTimeout(() => {
122
+ if (!child.killed) child.kill('SIGKILL');
123
+ }, 1000);
124
+ resolve({ exitCode: null, stdout, stderr, timedOut: true });
125
+ }, options.timeoutMs)
126
+ : undefined;
127
+ child.on('error', (error) => {
128
+ if (settled) return;
129
+ settled = true;
130
+ if (timeout) clearTimeout(timeout);
131
+ reject(error);
132
+ });
133
+ child.on('close', (exitCode) => {
134
+ if (settled) return;
135
+ settled = true;
136
+ if (timeout) clearTimeout(timeout);
137
+ resolve({ exitCode, stdout, stderr, timedOut: false });
138
+ });
139
+ });
140
+ }
141
+
142
+ function commandFor(file: string) {
143
+ if (file.endsWith('.sh')) return { command: 'bash', args: [file] };
144
+ if (file.endsWith('.mjs') || file.endsWith('.js')) {
145
+ if (!importsSourceTypescript(file)) return { command: process.execPath, args: [file] };
146
+ }
147
+ const localTsx = path.join(runnerDir, 'node_modules/.bin/tsx');
148
+ const tsxBin =
149
+ process.env.TSX_BIN ||
150
+ (existsSync(localTsx)
151
+ ? localTsx
152
+ : path.join(
153
+ resolveRequiredLocalProtocolRoot('TypeScript live adapter execution'),
154
+ 'node_modules/.bin/tsx',
155
+ ));
156
+ return { command: tsxBin, args: [file] };
157
+ }
158
+
159
+ function importsSourceTypescript(file: string): boolean {
160
+ return importsSourceTypescriptFrom(file, new Set());
161
+ }
162
+
163
+ function importsSourceTypescriptFrom(file: string, visited: Set<string>): boolean {
164
+ const absolute = path.resolve(file);
165
+ if (visited.has(absolute)) return false;
166
+ visited.add(absolute);
167
+ const source = readFileSync(file, 'utf8');
168
+ const importPattern = /(?:from\s+|import\(\s*)['"]([^'"]+)['"]/gu;
169
+ for (const match of source.matchAll(importPattern)) {
170
+ const specifier = match[1] ?? '';
171
+ if (specifier.endsWith('.ts')) return true;
172
+ if (!specifier.startsWith('.')) continue;
173
+ if (!specifier.endsWith('.mjs') && !specifier.endsWith('.js')) continue;
174
+ if (importsSourceTypescriptFrom(path.resolve(path.dirname(absolute), specifier), visited)) {
175
+ return true;
176
+ }
177
+ }
178
+ return false;
179
+ }
180
+
181
+ export async function resolveLiveAdapter(platform: string, action: string) {
182
+ return firstExecutablePath(candidatePaths(platform, action));
183
+ }
184
+
185
+ // Workspace packages the headless core adapter imports as runtime values. In an
186
+ // unbuilt MetaMask/core checkout these have no dist/, so their package "exports"
187
+ // (which point only at dist) cannot resolve. We point tsx at the package src via
188
+ // a generated tsconfig paths map so the adapter can import the controller without
189
+ // building or modifying the core checkout.
190
+ const CORE_WORKSPACE_PACKAGES = [
191
+ '@metamask/base-controller',
192
+ '@metamask/messenger',
193
+ '@metamask/controller-utils',
194
+ '@metamask/keyring-controller',
195
+ ];
196
+
197
+ async function platformAdapterEnv(
198
+ platform: string,
199
+ projectRoot: string,
200
+ tempDir: string,
201
+ ): Promise<NodeJS.ProcessEnv> {
202
+ if (platform !== 'core') return {};
203
+ const paths: Record<string, string[]> = {};
204
+ for (const pkg of CORE_WORKSPACE_PACKAGES) {
205
+ const packageDir = path.join(projectRoot, 'packages', pkg.replace('@metamask/', ''));
206
+ const distEntry = path.join(packageDir, 'dist/index.cjs');
207
+ if (existsSync(distEntry)) continue; // built package resolves normally
208
+ const srcDir = path.join(packageDir, 'src');
209
+ if (!existsSync(path.join(srcDir, 'index.ts'))) continue;
210
+ paths[pkg] = [path.join(srcDir, 'index.ts')];
211
+ paths[`${pkg}/*`] = [path.join(srcDir, '*')];
212
+ }
213
+ if (Object.keys(paths).length === 0) return {};
214
+ const tsconfigPath = path.join(tempDir, 'core-adapter.tsconfig.json');
215
+ await writeFile(
216
+ tsconfigPath,
217
+ `${JSON.stringify({ compilerOptions: { baseUrl: projectRoot, paths } }, null, 2)}\n`,
218
+ );
219
+ return { TSX_TSCONFIG_PATH: tsconfigPath };
220
+ }
221
+
222
+ export async function runLiveAdapterScript({ platform, action, node, context }: { platform: string; action: string; node: Record<string, any>; context: any }) {
223
+ const script = await resolveLiveAdapter(platform, action);
224
+ if (!script) return null;
225
+
226
+ const tempDir = await mkdtemp(path.join(os.tmpdir(), 'metamask-recipe-live-adapter-'));
227
+ const inputPath = path.join(tempDir, 'input.json');
228
+ const outputPath = path.join(tempDir, 'output.json');
229
+ const input = {
230
+ schemaVersion: 1,
231
+ platform,
232
+ action,
233
+ node,
234
+ context: {
235
+ nodeId: context.nodeId,
236
+ projectRoot: context.projectRoot,
237
+ artifactsDir: context.artifactsDir,
238
+ },
239
+ outputPath,
240
+ };
241
+ await writeFile(inputPath, `${JSON.stringify(input, null, 2)}\n`);
242
+ const command = commandFor(script);
243
+ const platformEnv = await platformAdapterEnv(platform, context.projectRoot, tempDir);
244
+ const result = await runProcess(command.command, [...command.args, inputPath], {
245
+ cwd: context.projectRoot,
246
+ env: {
247
+ ...process.env,
248
+ ...context.env,
249
+ ...platformEnv,
250
+ METAMASK_RECIPE_ADAPTER_INPUT: inputPath,
251
+ METAMASK_RECIPE_ADAPTER_OUTPUT: outputPath,
252
+ },
253
+ timeoutMs: Number(node.live_adapter_timeout_ms ?? node.timeout_ms ?? 60000),
254
+ });
255
+ try {
256
+ if (result.timedOut) {
257
+ throw new Error(`Live adapter ${script} timed out after ${Number(node.live_adapter_timeout_ms ?? node.timeout_ms ?? 60000)}ms.`);
258
+ }
259
+ if (result.exitCode !== 0) {
260
+ throw new Error(`Live adapter ${script} exited ${result.exitCode}: ${result.stderr || result.stdout}`);
261
+ }
262
+ let parsed = null;
263
+ try {
264
+ parsed = JSON.parse(await readFile(outputPath, 'utf8'));
265
+ } catch (_error) {
266
+ const stdout = result.stdout.trim();
267
+ if (!stdout) throw new Error(`Live adapter ${script} did not write JSON output.`);
268
+ parsed = JSON.parse(stdout);
269
+ }
270
+ return { script, result: parsed };
271
+ } finally {
272
+ await rm(tempDir, { recursive: true, force: true });
273
+ }
274
+ }
@@ -0,0 +1,47 @@
1
+ import path from 'node:path';
2
+
3
+ import type { RecipeActionManifestDocument, RecipeValidationResult } from '@farmslot/protocol';
4
+
5
+ import { manifestPath, readJson, importRecipeProtocol } from './paths.ts';
6
+ import type { MetaMaskRecipeAdapter } from './types.ts';
7
+
8
+ export function loadMetaMaskMobileActionManifest(): RecipeActionManifestDocument {
9
+ return asActionManifest(readJson(manifestPath('mobile')));
10
+ }
11
+
12
+ export function loadMetaMaskExtensionActionManifest(): RecipeActionManifestDocument {
13
+ return asActionManifest(readJson(manifestPath('extension')));
14
+ }
15
+
16
+ export function loadMetaMaskCoreActionManifest(): RecipeActionManifestDocument {
17
+ return asActionManifest(readJson(manifestPath('core')));
18
+ }
19
+
20
+ export function loadActionManifest(
21
+ adapter: MetaMaskRecipeAdapter,
22
+ overridePath?: string,
23
+ ): RecipeActionManifestDocument {
24
+ if (overridePath) return asActionManifest(readJson(path.resolve(overridePath)));
25
+ if (adapter === 'mobile') return loadMetaMaskMobileActionManifest();
26
+ if (adapter === 'core') return loadMetaMaskCoreActionManifest();
27
+ return loadMetaMaskExtensionActionManifest();
28
+ }
29
+
30
+ export async function validateManifest(
31
+ manifest: RecipeActionManifestDocument,
32
+ ): Promise<RecipeValidationResult> {
33
+ const { validateRecipeActionManifestDocument } = await importRecipeProtocol();
34
+ const result = validateRecipeActionManifestDocument(manifest);
35
+ if (result.status === 'invalid') {
36
+ throw new Error(
37
+ `Manifest invalid: ${result.findings
38
+ .map((finding) => `${finding.code} ${finding.path}`)
39
+ .join(', ')}`,
40
+ );
41
+ }
42
+ return result;
43
+ }
44
+
45
+ function asActionManifest(value: unknown): RecipeActionManifestDocument {
46
+ return value as RecipeActionManifestDocument;
47
+ }