@deeeed/metamask-harness 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +20 -17
  3. package/{orchestration → adapters}/core/cleanup.sh +2 -2
  4. package/{orchestration → adapters}/core/inject.sh +8 -8
  5. package/{orchestration → adapters}/extension/cleanup.mjs +1 -1
  6. package/{orchestration → adapters}/extension/console-tail.mjs +3 -0
  7. package/{orchestration → adapters}/extension/ensure-browser.sh +4 -4
  8. package/{orchestration → adapters}/extension/inject.mjs +27 -31
  9. package/{orchestration → adapters}/extension/launch.sh +4 -4
  10. package/{orchestration → adapters}/extension/live.sh +5 -5
  11. package/{orchestration → adapters}/extension/readiness.mjs +12 -2
  12. package/{orchestration → adapters}/extension/refresh-build.sh +1 -1
  13. package/{orchestration → adapters}/extension/seed-fixture.sh +1 -1
  14. package/{orchestration → adapters}/extension/start-watch.sh +7 -7
  15. package/{runner → adapters}/extension/verify.sh +11 -11
  16. package/{orchestration → adapters}/extension/wallet-fixture-state.cjs +2 -2
  17. package/{orchestration → adapters}/manifest.json +96 -96
  18. package/{library/actions → adapters}/mobile/bridge-runtime/cdp-bridge.cjs +5 -5
  19. package/{orchestration → adapters}/mobile/cleanup.sh +3 -3
  20. package/{orchestration → adapters}/mobile/inject.sh +22 -24
  21. package/adapters/mobile/open-device.sh +250 -0
  22. package/{orchestration/compat-overlays → adapters/mobile/overlay/compat}/README.md +13 -4
  23. package/adapters/mobile/prewarm-bundle.sh +95 -0
  24. package/adapters/mobile/start-metro.sh +228 -0
  25. package/{runner → adapters}/mobile/verify.sh +6 -6
  26. package/adapters/mobile/wait-for-bridge.sh +108 -0
  27. package/adapters/mobile/yarn-setup.sh +20 -0
  28. package/{orchestration/lib → adapters/shared}/cli-ux.sh +5 -9
  29. package/{orchestration/lib → adapters/shared}/log-tui.mjs +57 -1
  30. package/{orchestration/lib → adapters/shared}/open-debug.mjs +32 -10
  31. package/{orchestration/lib → adapters/shared}/resolve-farmslot-ports.sh +47 -0
  32. package/adapters/shared/sync-wallet-fixture.sh +78 -0
  33. package/bin/mm-harness +5 -6
  34. package/docs/CLI-SPEC.md +41 -2
  35. package/docs/architecture.md +69 -38
  36. package/docs/runtime-file-conventions.md +3 -3
  37. package/library/actions/core/perps/_controller.mjs +1 -1
  38. package/library/actions/extension/platform/cdp.mjs +2 -2
  39. package/library/actions/extension/wallet/ensure_unlocked.mjs +1 -1
  40. package/library/actions/mobile/platform/bridge.mjs +1 -1
  41. package/library/actions/mobile/wallet/ensure_unlocked.mjs +1 -1
  42. package/library/actions/mobile/wallet/setup.mjs +2 -2
  43. package/library/library.json +1 -1
  44. package/package.json +8 -8
  45. package/{orchestration → src/adapters}/extension/runtime-decision.ts +5 -5
  46. package/{orchestration → src/adapters}/extension/runtime.ts +1 -1
  47. package/src/adapters/mobile/prepare.ts +210 -0
  48. package/{orchestration → src/adapters}/mobile/runtime-decision.ts +11 -3
  49. package/{runner/src → src}/adapters.ts +3 -3
  50. package/{orchestration/lib/cli-color.mjs → src/cli-color.ts} +21 -13
  51. package/{orchestration/lib/cli-commands.mjs → src/cli-commands.ts} +52 -45
  52. package/{orchestration/lib/cli-version.mjs → src/cli-version.ts} +35 -17
  53. package/{runner/src → src}/cli.ts +25 -18
  54. package/src/commands/debug.ts +61 -0
  55. package/src/commands/fixtures.ts +196 -0
  56. package/{runner/src → src}/commands/launch.ts +185 -41
  57. package/src/commands/logs.ts +86 -0
  58. package/{runner/src → src}/commands/shared.ts +26 -26
  59. package/{runner/src → src}/harness.ts +97 -10
  60. package/src/index.ts +15 -0
  61. package/{runner/src → src}/live-adapter-contract.ts +1 -1
  62. package/{runner/src → src}/mm-harness-cli.ts +19 -3
  63. package/{runner/src → src}/paths.ts +2 -2
  64. package/{orchestration/lib/progress.mjs → src/progress.ts} +31 -21
  65. package/docs/extension-runtime-commands.md +0 -60
  66. package/docs/harness-cli.md +0 -43
  67. package/orchestration/compat-overlays/mobile/README.md +0 -13
  68. package/orchestration/doctor.mjs +0 -72
  69. package/orchestration/lib/cli-home.mjs +0 -354
  70. package/orchestration/lib/recipe-paths.mjs +0 -26
  71. package/orchestration/mobile/launch.sh +0 -137
  72. package/orchestration/mobile/live.sh +0 -125
  73. package/orchestration/porcelain/metamask-recipe +0 -99
  74. package/orchestration/porcelain/mm-recipe +0 -1591
  75. package/orchestration/porcelain/mme-recipe +0 -1181
  76. package/runner/src/commands/debug.ts +0 -44
  77. package/runner/src/commands/fixtures.ts +0 -99
  78. package/runner/src/commands/logs.ts +0 -60
  79. package/runner/src/index.ts +0 -6
  80. /package/{orchestration → adapters}/extension/launch-browser.cjs +0 -0
  81. /package/{orchestration → adapters}/extension/pin-remote-flags.cjs +0 -0
  82. /package/{orchestration → adapters}/extension/sidepanel-toggle.sh +0 -0
  83. /package/{orchestration → adapters}/extension/snapshot-dist.sh +0 -0
  84. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -0
  85. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/config.cjs +0 -0
  86. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/issue-capture.cjs +0 -0
  87. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/target-discovery.cjs +0 -0
  88. /package/{library/actions → adapters}/mobile/bridge-runtime/lib/ws-client.cjs +0 -0
  89. /package/{library/actions → adapters}/mobile/bridge-runtime/setup-wallet.sh +0 -0
  90. /package/{library/actions/mobile/app-overlay → adapters/mobile/overlay}/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -0
  91. /package/{library/actions/mobile/app-overlay → adapters/mobile/overlay}/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -0
  92. /package/{orchestration/compat-overlays/mobile → adapters/mobile/overlay/compat}/rn81-message-event-source.patch +0 -0
  93. /package/{orchestration/lib → adapters/shared}/activate-repo-node.sh +0 -0
  94. /package/{orchestration/lib → adapters/shared}/ensure-runner-deps.sh +0 -0
  95. /package/{orchestration/lib → adapters/shared}/harness-path.sh +0 -0
  96. /package/{orchestration/lib → adapters/shared}/hash-helpers.sh +0 -0
  97. /package/{orchestration/lib → adapters/shared}/json-field.sh +0 -0
  98. /package/{orchestration/lib → adapters/shared}/path-defaults.json +0 -0
  99. /package/{orchestration → src/adapters}/extension/ensure-ready.ts +0 -0
  100. /package/{orchestration → src/adapters}/extension/extension-id.ts +0 -0
  101. /package/{orchestration → src/adapters}/mobile/deps-markers.ts +0 -0
  102. /package/{runner/src → src}/completions-cache.ts +0 -0
  103. /package/{runner/src → src}/doctor.ts +0 -0
  104. /package/{runner/src → src}/heal-bounds.ts +0 -0
  105. /package/{runner/src → src}/manifest.ts +0 -0
  106. /package/{runner/src → src}/recording-target.ts +0 -0
  107. /package/{runner/src → src}/run-recording.ts +0 -0
  108. /package/{runner/src → src}/runner.ts +0 -0
  109. /package/{runner/src → src}/types.ts +0 -0
@@ -1,5 +1,40 @@
1
1
  # MetaMask Recipe Runner Architecture
2
2
 
3
+ **Control flows DOWN only — a DAG, never a loop.**
4
+
5
+ ```
6
+ bin/mm-harness ← entry (thin bootstrap)
7
+
8
+
9
+ ┌───────────────────────────────────────────────┐
10
+ │ src/ THE BRAIN — all TypeScript │
11
+ │ CLI · decisions · state machines · run │
12
+ │ engine · healing/errors · evidence │
13
+ └───────────────────────────────────────────────┘
14
+ │ spawns │ reads
15
+ ▼ ▼
16
+ adapters/<platform>/ library/
17
+ DEVICE LEAVES (shell) VOCABULARY (data)
18
+ one op · args in · recipes · actions ·
19
+ result out · exit flows — pure JSON/mjs
20
+ (yarn-setup, start-metro,
21
+ open-device, xcrun, adb,
22
+ webpack, chrome)
23
+ ```
24
+
25
+ | dir | is | rule |
26
+ |---|---|---|
27
+ | `bin/` | entry | one thin bootstrap |
28
+ | `src/` | the brain | ALL TypeScript, ALL decisions/sequencing |
29
+ | `adapters/<platform>/` | device leaves | one op each · never orchestrates · **never calls `mm-harness`** |
30
+ | `library/` | vocabulary | pure recipe/action/flow data |
31
+
32
+ **The one rule everything follows:** nothing under `adapters/` or `library/` ever calls `mm-harness`. TS orchestrates and spawns leaves; leaves do one device op and exit; vocabulary is data. (External callers — farm hooks, skills, overlay delegate — invoking `mm-harness` at the top level is fine; the ban is on the harness's OWN scripts calling back in.)
33
+
34
+ **Why:** every past mess (half-wired porcelain, commands referenced-but-unbuilt, stub tests faking the thing under test) came from control looping back up. Down-only makes that class of bug unrepresentable — and means less code.
35
+
36
+ ---
37
+
3
38
  This repo is the MetaMask-specific adapter layer for Recipe Protocol v1. It is
4
39
  not the protocol engine, not a skills repo, not a slot farm, and not product
5
40
  code. Its job is to publish reviewable MetaMask capabilities and run them
@@ -21,9 +56,9 @@ doing more than parsing flags and calling `mm-harness`, that logic
21
56
  belongs in this runner instead. Concrete example in flight: the
22
57
  `recipe-harness` skill currently locates and execs runner internals in
23
58
  hundreds of lines of bash (resolve runner source, dispatch to
24
- `orchestration/<platform>/{inject,cleanup}.{sh,mjs}` — `.sh` for Mobile/Core,
59
+ `adapters/<platform>/{inject,cleanup}.{sh,mjs}` — `.sh` for Mobile/Core,
25
60
  `.mjs` for Extension); the fix is a
26
- [`mm-harness <install|verify|cleanup|live>`](harness-cli.md) command here, so the
61
+ [`mm-harness <install|verify|cleanup|live>`](CLI-SPEC.md) command here, so the
27
62
  skill thins to a few lines that call it. Same pattern applies to every other
28
63
  `recipe-*` skill over time.
29
64
 
@@ -32,10 +67,10 @@ skill thins to a few lines that call it. Same pattern applies to every other
32
67
 
33
68
  | Extension | Where | Use for | Why |
34
69
  |---|---|---|---|
35
- | `.ts` | `runner/src/**` | CLI parsing, manifests, adapter binding, runtime decisions — typed/testable/shared logic | Compiled and typed; the maintainable core that everything else should grow into |
36
- | `.mjs` | `library/actions/**`, `orchestration/**`, `scripts/**` | Standalone action adapters and injected runtime helpers | Run-anywhere with zero build step, whether invoked from a published package or a raw checkout |
70
+ | `.ts` | `src/**` | CLI parsing, manifests, adapter binding, runtime decisions — typed/testable/shared logic | Compiled and typed; the maintainable core that everything else should grow into |
71
+ | `.mjs` | `library/actions/**`, `adapters/**`, `scripts/**` | Standalone action adapters and injected runtime helpers | Run-anywhere with zero build step, whether invoked from a published package or a raw checkout |
37
72
  | `.cjs` | scattered, quarantined | Compatibility islands: portable `require()` from shell, bridge code for older runtime contexts | Only when the runtime context makes ESM brittle — not a default choice |
38
- | `.sh` | `bin/`, `orchestration/{mobile,extension,core}/`, `runner/{mobile,extension}/`, `orchestration/lib/` | Thin OS/device glue at the edge: `simctl`, `adb`, Chrome process flags, git-exclude, filesystem setup | Must run before/without Node business logic; never recipe graph execution or MetaMask domain semantics |
73
+ | `.sh` | `bin/`, `adapters/{mobile,extension,core}/`, `adapters/shared/` | Thin OS/device glue at the edge: `simctl`, `adb`, Chrome process flags, git-exclude, filesystem setup | Must run before/without Node business logic; never recipe graph execution or MetaMask domain semantics |
39
74
 
40
75
  The mix is bash-heavy today because sandbox setup crosses OS/device boundaries
41
76
  (see "Why there are shell scripts" below) — that is current reality, not the
@@ -85,15 +120,15 @@ summary.json · trace.json · artifact-manifest.json · screenshots/logs
85
120
 
86
121
  **Dependency direction is one-way and provable.** The runner *depends on*
87
122
  `@farmslot/recipe-harness` + `@farmslot/protocol` (see `package.json`), and
88
- `runner/src/**` *imports* the generic contracts rather than forking them:
123
+ `src/**` *imports* the generic contracts rather than forking them:
89
124
 
90
- - `runner/src/cli.ts` imports `RecipeRunResult` from `@farmslot/recipe-harness`
125
+ - `src/cli.ts` imports `RecipeRunResult` from `@farmslot/recipe-harness`
91
126
  and delegates library `flows` to the harness CLI (`importRecipeHarnessCli`).
92
- - `runner/src/runner.ts` wires `metamask.*` adapters INTO the farmslot
127
+ - `src/runner.ts` wires `metamask.*` adapters INTO the farmslot
93
128
  `RecipeRunner`; it does not implement graph execution.
94
- - `runner/src/manifest.ts` loads *MetaMask* manifests typed by the *farmslot*
129
+ - `src/manifest.ts` loads *MetaMask* manifests typed by the *farmslot*
95
130
  protocol document type.
96
- - `runner/src/recording-target.ts` is a MetaMask provider over the farmslot
131
+ - `src/recording-target.ts` is a MetaMask provider over the farmslot
97
132
  `RecordingTarget` contract.
98
133
 
99
134
  **The invariant both repos assert:** farmslot stays MetaMask-free; the runner
@@ -104,7 +139,7 @@ generic (farmslot); encodes MetaMask app knowledge = runner.
104
139
  > Note on naming: "harness" refers to the tool (`mm-harness`) and the generic
105
140
  > engine package (`@farmslot/recipe-harness`). The per-checkout files the tool
106
141
  > installs into a target are the **runtime overlay** (`install`/`verify`/
107
- > `cleanup`/`live`), never "the harness" — see [harness-cli.md](harness-cli.md).
142
+ > `cleanup`/`live`), never "the harness" — see [CLI-SPEC.md](CLI-SPEC.md).
108
143
 
109
144
  ## One-minute model
110
145
 
@@ -141,8 +176,8 @@ is the main way to understand the repository.
141
176
 
142
177
  | Subsystem | Question it answers | Primary files | Should contain | Should not contain |
143
178
  |---|---|---|---|---|
144
- | Recipe capability/execution | “What can a MetaMask recipe do, and how does a node execute?” | `library/manifests/`, `library/recipes/`, `runner/src/runner.ts`, `runner/src/adapters.ts`, `runner/src/live-adapter-contract.ts`, `library/actions/` | action manifests, domain actions, UI transport binding, adapter outputs, proof semantics | Metro startup, Chrome process flags, simulator boot, git-exclude/rsync cleanup |
145
- | Runtime lifecycle / sandbox helpers | “How do I give an agent an isolated app session that is ready to inspect or run recipes?” | `bin/mm-recipe`, `bin/mme-recipe`, `orchestration/{mobile,extension,core}/`, `runner/{mobile,extension}/`, `orchestration/lib/` | install/sync harness, start/reuse Metro or Chrome, prewarm bundles, open Extension full-screen or popup-style, prepare dedicated profiles/fixtures, check build/runtime health, cleanup local files | new recipe schema, graph traversal, MetaMask business semantics, task-specific proof logic |
179
+ | Recipe capability/execution | “What can a MetaMask recipe do, and how does a node execute?” | `library/manifests/`, `library/recipes/`, `src/runner.ts`, `src/adapters.ts`, `src/live-adapter-contract.ts`, `library/actions/` | action manifests, domain actions, UI transport binding, adapter outputs, proof semantics | Metro startup, Chrome process flags, simulator boot, git-exclude/rsync cleanup |
180
+ | Runtime lifecycle / sandbox helpers | “How do I give an agent an isolated app session that is ready to inspect or run recipes?” | `adapters/porcelain/`, `adapters/{mobile,extension,core}/`, `adapters/shared/` | install/sync harness, start/reuse Metro or Chrome, prewarm bundles, open Extension full-screen or popup-style, prepare dedicated profiles/fixtures, check build/runtime health, cleanup local files | new recipe schema, graph traversal, MetaMask business semantics, task-specific proof logic |
146
181
 
147
182
  When reviewing a change, first decide which subsystem it touches. Recipe changes
148
183
  should be validated against manifests and action artifacts. Runtime lifecycle changes should be validated by install/launch/live/verify
@@ -168,29 +203,26 @@ describes **how an agent should work**, it belongs in skills.
168
203
 
169
204
  | Path | Responsibility |
170
205
  |---|---|
171
- | `bin/metamask-recipe` | Public binary. Dispatches to typed CLI and platform convenience commands. |
172
- | `bin/mm-recipe` | Mobile convenience/runtime UX: start/reuse Metro, prewarm bundle, launch app, query bridge, setup wallet, screenshot. |
173
- | `bin/mme-recipe` | Extension convenience/runtime UX: install, health, decision, ready, watch/refresh/reopen, run recipes. |
174
- | `runner/src/cli.ts` | Typed command handlers: manifests, actions, doctor, runtime health/decision/launch, `run`, self-test. |
175
- | `runner/src/runner.ts` | Creates the Recipe runner by combining shared core/ui adapters with MetaMask live adapters. Enables the Recipe HUD metadata. |
176
- | `runner/src/adapters.ts` | MetaMask adapter binding and `ui.*` transport selection for Mobile vs Extension. Refuses static placeholders for live-only proof actions. |
177
- | `runner/src/live-adapter-contract.ts` | Script adapter contract and lookup rules for `library/actions/<platform>/<domain>/*.mjs`. |
206
+ | `src/cli.ts` | Typed command handlers: manifests, actions, doctor, runtime health/decision/launch, `run`. |
207
+ | `src/runner.ts` | Creates the Recipe runner by combining shared core/ui adapters with MetaMask live adapters. Enables the Recipe HUD metadata. |
208
+ | `src/adapters.ts` | MetaMask adapter binding and `ui.*` transport selection for Mobile vs Extension. Refuses static placeholders for live-only proof actions. |
209
+ | `src/live-adapter-contract.ts` | Script adapter contract and lookup rules for `library/actions/<platform>/<domain>/*.mjs`. |
178
210
  | `library/manifests/*.action-manifest.json` | Reviewable capability contract. A recipe may only call declared actions. |
179
211
  | `library/actions/mobile/` | Mobile action implementations. Talks to the runner bridge and app-exposed `globalThis.__AGENTIC__` hooks. |
180
212
  | `library/actions/extension/` | Extension action implementations. Talks to Chrome/extension pages over CDP. |
181
- | `orchestration/mobile/inject.sh` | Installs/syncs the Mobile runtime overlay under the configured harness root and protects cleanup/git-exclude behavior. |
182
- | `orchestration/extension/inject.mjs` | Installs/syncs Extension runtime helpers under the configured harness root. |
183
- | `orchestration/mobile/` + `runner/mobile/` | Runner-owned Mobile launch/live/inject/cleanup and verify helpers copied into installed harnesses. |
184
- | `orchestration/extension/` + `runner/extension/` | Runner-owned Extension launch/live/watch/windows/wallet-state/readiness helpers (orchestration) and verify (recipe) copied into installed harnesses. |
185
- | `orchestration/lib/path-defaults.json` | Single source for default `recipeHarnessRoot` and `recipeRuntimeDir`. |
186
- | `orchestration/lib/harness-path.sh`, `orchestration/lib/recipe-paths.mjs`, `runner/src/paths.ts` | Shell, standalone Node, and TypeScript accessors for those defaults plus validation. |
213
+ | `adapters/mobile/inject.sh` | Installs/syncs the Mobile runtime overlay under the configured harness root and protects cleanup/git-exclude behavior. |
214
+ | `adapters/extension/inject.mjs` | Installs/syncs Extension runtime helpers under the configured harness root. |
215
+ | `adapters/mobile/` | Runner-owned Mobile launch/live/inject/cleanup and verify helpers copied into installed harnesses. |
216
+ | `adapters/extension/` | Runner-owned Extension launch/live/watch/windows/wallet-state/readiness helpers (orchestration) and verify (recipe) copied into installed harnesses. |
217
+ | `adapters/shared/path-defaults.json` | Single source for default `recipeHarnessRoot` and `recipeRuntimeDir`. |
218
+ | `adapters/shared/harness-path.sh`, `src/paths.ts` | Shell and TypeScript accessors for those defaults plus validation. |
187
219
  | `library/recipes/` | Reusable smoke/action-validation recipes only. Task-specific proof recipes stay task-local. |
188
220
  | `library/library.json`, `library/flows/` | Canonical recipe library: minimal, stable flows recipes can `call`. Personal/team libraries shadow it by default — see [Recipe Libraries](recipe-libraries.md). |
189
221
  | `docs/` | Runner architecture, contracts, and operational conventions. |
190
222
 
191
223
  ## Runtime paths and installed harnesses
192
224
 
193
- Defaults are centralized in `orchestration/lib/path-defaults.json`:
225
+ Defaults are centralized in `adapters/shared/path-defaults.json`:
194
226
 
195
227
  ```json
196
228
  {
@@ -210,7 +242,7 @@ Install commands write a small runtime package into the target checkout:
210
242
  <target>/<recipeHarnessRoot>/<adapter>/
211
243
  manifest.json installed source/revision/cleanup metadata
212
244
  action-manifest.json snapshot of the adapter manifest
213
- runner/bin/metamask-recipe delegate back to the resolved runner source
245
+ runner/bin/mm-harness delegate back to the resolved runner source
214
246
  runner/recipes/ reusable recipe snapshot
215
247
  scripts/ adapter runtime helpers copied from this repo
216
248
  ```
@@ -222,10 +254,10 @@ this runner.
222
254
  ## Recipe execution vs sandbox lifecycle
223
255
 
224
256
  `mm-harness run <recipe.json> --adapter ...` is the recipe path. It creates
225
- a shared Recipe runner (`runner/src/runner.ts`), validates the recipe against the
257
+ a shared Recipe runner (`src/runner.ts`), validates the recipe against the
226
258
  manifest, executes nodes, and writes artifacts. If a bug is about action fields,
227
259
  trace output, adapter semantics, or whether a recipe proves a claim, start in
228
- `library/manifests/`, `runner/src/`, `library/actions/`, and `library/recipes/`.
260
+ `library/manifests/`, `src/`, `library/actions/`, and `library/recipes/`.
229
261
 
230
262
  `prepare`, `launch`, `live`, `verify`, `status`, `decision`, and `ready` are
231
263
  sandbox lifecycle paths. They give the agent a reproducible local app session:
@@ -233,8 +265,7 @@ Mobile with Metro/dev-client/simulator and the bridge online; Extension with an
233
265
  isolated browser profile, unpacked extension loaded, and a known home/popup-style
234
266
  UI target. If a bug is about Metro, bundle prewarm, simulator launch, Chrome CDP,
235
267
  Extension full-screen vs popup presentation, build freshness, wallet fixture
236
- placement, git-exclude, or cleanup, start in `bin/mm-recipe`, `bin/mme-recipe`,
237
- `orchestration/{mobile,extension,core}/` and `runner/{mobile,extension}/`.
268
+ placement, git-exclude, or cleanup, start in `adapters/{mobile,extension,core}/`.
238
269
 
239
270
  Do not put recipe graph traversal into shell scripts. Shell scripts may prepare
240
271
  or inspect the sandboxed runtime, then delegate graph execution to
@@ -247,7 +278,7 @@ These names are easy to mix up; they are different concerns.
247
278
  | Term | What it is | Why it exists |
248
279
  |---|---|---|
249
280
  | Recipe HUD | A visual overlay driven by Recipe runner metadata (`intent`, current node, status). | Makes screenshots/videos explain what the recipe is doing without exposing secrets. |
250
- | Mobile bridge | Runner-side CDP/Hermes bridge process under `library/actions/mobile/bridge-runtime/`. | Lets the runner call app-exposed commands, read state, press UI targets, and capture status from React Native. |
281
+ | Mobile bridge | Runner-side CDP/Hermes bridge process under `adapters/mobile/bridge-runtime/`. | Lets the runner call app-exposed commands, read state, press UI targets, and capture status from React Native. |
251
282
  | `globalThis.__AGENTIC__` | Development-only in-app command surface exposed by the Mobile overlay/patch. | Gives the bridge a stable API for route/status/wallet/UI operations when the app lacks a built-in automation API. |
252
283
  | Extension CDP hooks | Chrome DevTools Protocol access to extension pages/background state hooks. | Lets the runner inspect/drive the unpacked Extension without patching product source. |
253
284
 
@@ -260,10 +291,10 @@ Mobile has the most moving parts because React Native does not expose a browser
260
291
  DOM by default.
261
292
 
262
293
  ```text
263
- mm-recipe / orchestration/mobile/*.sh
264
- starts/reuses Metro, prewarms bundle, launches iOS/Android dev client
294
+ mm-harness launch ios|android
295
+ prepareMobile() in-process yarn-setup / start-metro / open-device leaves
265
296
 
266
- library/actions/mobile/bridge-runtime/cdp-bridge.cjs
297
+ adapters/mobile/bridge-runtime/cdp-bridge.cjs
267
298
  │ connects to Hermes / RN debug runtime
268
299
 
269
300
  globalThis.__AGENTIC__ inside the app
@@ -295,7 +326,7 @@ Extension does not need an in-product source patch. The runner works through an
295
326
  unpacked `dist/chrome` build and Chrome CDP.
296
327
 
297
328
  ```text
298
- mme-recipe / orchestration/extension/*.sh
329
+ mme-recipe / adapters/extension/*.sh
299
330
  │ checks dist freshness, build health, fixture/profile state
300
331
 
301
332
  Chrome for Testing with --load-extension=<runtime-dist>
@@ -329,7 +360,7 @@ Allowed in shell:
329
360
  Not allowed in shell:
330
361
 
331
362
  - Recipe v1 graph execution;
332
- - MetaMask domain semantics that can live in `runner/src/**/*.ts` or `library/actions/**/*.mjs`;
363
+ - MetaMask domain semantics that can live in `src/**/*.ts` or `library/actions/**/*.mjs`;
333
364
  - duplicated action manifest logic;
334
365
  - product-specific business decisions beyond runtime boot/health checks.
335
366
 
@@ -6,9 +6,9 @@ package without requiring target app builds to transpile runner code.
6
6
 
7
7
  ## Extension rules
8
8
 
9
- - `runner/src/**/*.ts` — typed runner core: CLI parsing, manifests, adapter binding,
9
+ - `src/**/*.ts` — typed runner core: CLI parsing, manifests, adapter binding,
10
10
  runtime decisions, and shared helper logic.
11
- - `library/actions/**/*.mjs`, `orchestration/**/*.mjs`, and `scripts/**/*.mjs` —
11
+ - `library/actions/**/*.mjs`, `adapters/**/*.mjs`, and `scripts/**/*.mjs` —
12
12
  standalone ESM modules that Node executes directly with no build step. Use
13
13
  these for action adapters and small injected/runtime helpers.
14
14
  - `*.cjs` — compatibility islands only. Keep these quarantined for helper code
@@ -31,6 +31,6 @@ The mix is intentional only when the boundary is clear:
31
31
  4. Shell stays at the edge for host/device commands.
32
32
 
33
33
  If a file crosses those boundaries, move the logic inward: shell should call a
34
- Node module, standalone `.mjs` should become typed `runner/src/**/*.ts` when it grows
34
+ Node module, standalone `.mjs` should become typed `src/**/*.ts` when it grows
35
35
  shared domain logic, and new compatibility needs should be documented before
36
36
  adding another `.cjs` file.
@@ -8,7 +8,7 @@ import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
8
8
  // this adapter under the bundled tsx (see commandFor/importsSourceTypescript in
9
9
  // src/live-adapter-contract.ts). The core adapter dynamic-imports the perps
10
10
  // controller TypeScript at runtime, so it MUST run under tsx, not plain node.
11
- import { walletFixturePath } from '../../../../runner/src/paths.ts';
11
+ import { walletFixturePath } from '../../../../src/index.ts';
12
12
 
13
13
  // Shared headless instantiation for the MetaMask `core` adapter.
14
14
  //
@@ -10,8 +10,8 @@ import {
10
10
  extensionIdPath,
11
11
  recipeHarnessPath,
12
12
  walletFixturePath,
13
- } from '../../../../runner/src/paths.ts';
14
- import { captureActiveRecipeRecordingSnapshot } from '../../../../runner/src/run-recording.ts';
13
+ captureActiveRecipeRecordingSnapshot,
14
+ } from '../../../../src/index.ts';
15
15
 
16
16
  // Resolve the Farmslot harness through normal package dependencies by default.
17
17
  // Local Farmslot source is only a dev override handled by src/paths.ts.
@@ -1,6 +1,6 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
3
- import { walletFixturePath } from '../../../../runner/src/paths.ts';
3
+ import { walletFixturePath } from '../../../../src/index.ts';
4
4
 
5
5
  async function fixturePassword(projectRoot) {
6
6
  const candidates = [
@@ -31,7 +31,7 @@ function bridgeScript(input) {
31
31
  }
32
32
 
33
33
  function runtimeDir() {
34
- return fileURLToPath(new URL('../bridge-runtime', import.meta.url));
34
+ return fileURLToPath(new URL('../../../../adapters/mobile/bridge-runtime', import.meta.url));
35
35
  }
36
36
 
37
37
  export function bridgeEnv(input) {
@@ -1,6 +1,6 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { bridgeCommand, runAdapter } from '../platform/bridge.mjs';
3
- import { walletFixturePath } from '../../../../runner/src/paths.ts';
3
+ import { walletFixturePath } from '../../../../src/index.ts';
4
4
 
5
5
  async function fixturePassword(projectRoot) {
6
6
  const candidates = [
@@ -3,7 +3,7 @@ import { spawn } from 'node:child_process';
3
3
  import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { bridgeCommand, bridgeEnv, runAdapter } from '../platform/bridge.mjs';
6
- import { walletFixturePath } from '../../../../runner/src/paths.ts';
6
+ import { walletFixturePath } from '../../../../src/index.ts';
7
7
 
8
8
  async function fixtureProfile(projectRoot) {
9
9
  const candidates = [
@@ -109,7 +109,7 @@ function setupWalletScript() {
109
109
  if (process.env.METAMASK_RECIPE_MOBILE_SETUP_WALLET_SCRIPT) {
110
110
  return process.env.METAMASK_RECIPE_MOBILE_SETUP_WALLET_SCRIPT;
111
111
  }
112
- return fileURLToPath(new URL('../bridge-runtime/setup-wallet.sh', import.meta.url));
112
+ return fileURLToPath(new URL('../../../../adapters/mobile/bridge-runtime/setup-wallet.sh', import.meta.url));
113
113
  }
114
114
 
115
115
  function runSetupWallet(input, fixture) {
@@ -2,6 +2,6 @@
2
2
  "kind": "recipe-library",
3
3
  "schema_version": 1,
4
4
  "name": "metamask",
5
- "owner": "metamask-recipe-runner",
5
+ "owner": "mm-harness-runner",
6
6
  "description": "Canonical MetaMask recipe library: minimal, stable flows every recipe can compose. Team and personal libraries layer on top and win resolution by default."
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"
@@ -8,7 +8,7 @@
8
8
  "scripts": {
9
9
  "dev:link-farmslot": "node scripts/link-local-farmslot.mjs",
10
10
  "check": "node scripts/check.mjs",
11
- "self-test": "orchestration/porcelain/metamask-recipe self-test",
11
+ "self-test": "bin/mm-harness self-test",
12
12
  "manifest:mobile": "bin/mm-harness actions --raw --adapter mobile --json",
13
13
  "manifest:extension": "bin/mm-harness actions --raw --adapter extension --json",
14
14
  "check:syntax": "find . -name '*.mjs' -print0 | xargs -0 -n1 node --check"
@@ -24,12 +24,12 @@
24
24
  "@types/node": "^22.0.0",
25
25
  "typescript": "^5.6.0"
26
26
  },
27
- "main": "./runner/src/index.ts",
28
- "types": "./runner/src/index.ts",
27
+ "main": "./src/index.ts",
28
+ "types": "./src/index.ts",
29
29
  "exports": {
30
30
  ".": {
31
- "types": "./runner/src/index.ts",
32
- "import": "./runner/src/index.ts"
31
+ "types": "./src/index.ts",
32
+ "import": "./src/index.ts"
33
33
  }
34
34
  },
35
35
  "publishConfig": {
@@ -37,8 +37,8 @@
37
37
  },
38
38
  "files": [
39
39
  "bin",
40
- "runner",
41
- "orchestration",
40
+ "src",
41
+ "adapters",
42
42
  "library",
43
43
  "scripts/completions.sh",
44
44
  "scripts/install-completions.sh",
@@ -3,7 +3,6 @@
3
3
  import { execFileSync } from 'node:child_process';
4
4
  import crypto from 'node:crypto';
5
5
  import fs from 'node:fs';
6
- import os from 'node:os';
7
6
  import path from 'node:path';
8
7
  import {
9
8
  depsCheck,
@@ -11,7 +10,7 @@ import {
11
10
  recordDepsBaseline,
12
11
  type DepsCheck,
13
12
  } from '@farmslot/recipe-harness/runtime/deps-readiness';
14
- import { recipeWatchLogCandidates } from '../../runner/src/paths.ts';
13
+ import { recipeHarnessPath, recipeWatchLogCandidates } from '../../paths.ts';
15
14
 
16
15
  /**
17
16
  * Deterministic runtime-readiness decision for the MetaMask Extension.
@@ -181,10 +180,11 @@ function webpackFingerprint(target: string): { gitHead: string; fingerprint: str
181
180
  return { gitHead: git(target, ['rev-parse', 'HEAD']) ?? 'unknown', fingerprint: hash.digest('hex') };
182
181
  }
183
182
 
184
- // ── per-checkout baseline store (machine-local; never written into the repo) ──
183
+ // ── per-checkout baseline store (checkout-local; not written into the repo) ──
184
+ // State lives under the checkout's own harness dir so it is scoped to the
185
+ // checkout, survives /tmp cleanup, and is not in a world-writable directory.
185
186
  function stateDir(target: string): string {
186
- const key = crypto.createHash('sha1').update(path.resolve(target)).digest('hex').slice(0, 16);
187
- return path.join(os.tmpdir(), 'metamask-recipe-decision', key);
187
+ return recipeHarnessPath(target, 'extension', 'decision-state');
188
188
  }
189
189
 
190
190
  function readBaseline(target: string, name: string): Record<string, unknown> | null {
@@ -4,7 +4,7 @@ import { spawn } from 'node:child_process';
4
4
  import fs from 'node:fs';
5
5
  import path from 'node:path';
6
6
 
7
- import { importRecipeHarnessRuntimeBrowserExtension, importRecipeHarnessRuntimeCdp, recipeRuntimeDir, resolveLocalProtocolRoot, resolveRequiredLocalProtocolRoot, runnerDir } from '../../runner/src/paths.ts';
7
+ import { importRecipeHarnessRuntimeBrowserExtension, importRecipeHarnessRuntimeCdp, recipeRuntimeDir, resolveLocalProtocolRoot, resolveRequiredLocalProtocolRoot, runnerDir } from '../../paths.ts';
8
8
 
9
9
  // Runtime health uses package dependencies. Launching a host-managed validation
10
10
  // browser remains a dev-only path because it needs pool/project scripts from a
@@ -0,0 +1,210 @@
1
+ // Mobile runtime prepare — in-process decision-and-action loop.
2
+ //
3
+ // Replaces the retired shell orchestrators (prepare-device.sh + launch.sh).
4
+ // All sequencing is in TypeScript; leaf shell scripts handle single OS ops.
5
+ // Control flows down only: TS → adapters/mobile/{yarn-setup,start-metro,open-device}.sh
6
+ // No leaf script may call back into mm-harness (A1).
7
+
8
+ import path from 'node:path';
9
+
10
+ import { EXIT, spawnScript } from '../../commands/shared.ts';
11
+ import type { ScriptResult } from '../../commands/shared.ts';
12
+ import { runnerDir } from '../../paths.ts';
13
+ import {
14
+ decideMobileReadiness,
15
+ type MobileRuntimeDecisionReport,
16
+ type MobileRuntimeDecisionOptions,
17
+ } from './runtime-decision.ts';
18
+
19
+ export { type MobileRuntimeDecisionReport };
20
+
21
+ export interface PrepareMobileOptions extends MobileRuntimeDecisionOptions {
22
+ /** Pass through to leaf-script spawnScript calls so output is suppressed in --json mode. */
23
+ json?: boolean;
24
+ /**
25
+ * Passed to open-device.sh --preflight-mode.
26
+ * 'fast' = error if not installed; 'auto' = install if missing; 'rebuild-native'/'clean' = always rebuild.
27
+ * Defaults to 'fast'.
28
+ */
29
+ preflightMode?: string;
30
+ /**
31
+ * Force --clear on start-metro (reset bundle cache).
32
+ * Used by the setup-wallet clean-Metro retry path (M3).
33
+ */
34
+ clearMetro?: boolean;
35
+ }
36
+
37
+ /**
38
+ * Read current runtime readiness without executing any action.
39
+ * No side effects: just calls decideMobileReadiness and returns the report.
40
+ */
41
+ export async function mobileRuntimeStatus(
42
+ target: string,
43
+ opts: PrepareMobileOptions = {},
44
+ ): Promise<MobileRuntimeDecisionReport> {
45
+ return decideMobileReadiness(target, {
46
+ watcherPort: opts.watcherPort,
47
+ metroLog: opts.metroLog,
48
+ platform: opts.platform,
49
+ record: opts.record,
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Prepare the mobile runtime: run the readiness decision then execute each
55
+ * action in sequence via single-purpose leaf shell scripts. Returns a
56
+ * ScriptResult so the launch heal loop can classify outcomes uniformly.
57
+ */
58
+ export async function prepareMobile(
59
+ target: string,
60
+ opts: PrepareMobileOptions = {},
61
+ ): Promise<ScriptResult> {
62
+ const json = opts.json ?? false;
63
+ const platform = opts.platform ?? 'ios';
64
+ const preflightMode = opts.preflightMode ?? 'fast';
65
+ const clearMetro = opts.clearMetro ?? false;
66
+
67
+ const report = await decideMobileReadiness(target, {
68
+ watcherPort: opts.watcherPort,
69
+ metroLog: opts.metroLog,
70
+ platform,
71
+ record: opts.record,
72
+ });
73
+
74
+ if (report.decision === 'blocked') {
75
+ const reasons = report.reasons.join(' ');
76
+ const msg = `mobile prepare blocked: ${reasons}\n Next: fix the bundle error in app code before retrying recipe up.`;
77
+ if (!json) process.stderr.write(`${msg}\n`);
78
+ return { status: EXIT.runtime, output: msg };
79
+ }
80
+
81
+ if (report.decision === 'ready') {
82
+ return { status: 0, output: '' };
83
+ }
84
+
85
+ if (report.decision === 'unknown') {
86
+ const msg = 'mobile prepare: runtime state unknown\n Next: run mm-harness verify --adapter mobile --target <checkout>';
87
+ if (!json) process.stderr.write(`${msg}\n`);
88
+ return { status: EXIT.runtime, output: msg };
89
+ }
90
+
91
+ // decision is 'install' or 'launch' — execute each action in sequence.
92
+ // clearMetro forces --clear on start-metro regardless of what decideMobileReadiness set.
93
+ const actions = clearMetro
94
+ ? report.actions.map((a) =>
95
+ a.id === 'start-metro' && !a.argv?.includes('--clear')
96
+ ? { ...a, argv: [...(a.argv ?? []), '--clear'] }
97
+ : a,
98
+ )
99
+ : report.actions;
100
+ for (const action of actions) {
101
+ const result = dispatchAction(action, target, platform, json, preflightMode);
102
+ if (result.status !== 0) return result;
103
+ }
104
+
105
+ // After install: re-decide and run appropriate actions.
106
+ // RECIPE_UP_INSTALL_ATTEMPTED guards against infinite loops if yarn-setup was a no-op.
107
+ if (report.decision === 'install' && !process.env['RECIPE_UP_INSTALL_ATTEMPTED']) {
108
+ process.env['RECIPE_UP_INSTALL_ATTEMPTED'] = '1';
109
+ const postInstall = await decideMobileReadiness(target, {
110
+ watcherPort: opts.watcherPort,
111
+ metroLog: opts.metroLog,
112
+ platform,
113
+ record: true,
114
+ });
115
+ switch (postInstall.decision) {
116
+ case 'install': {
117
+ // Yarn setup ran but runtime is still unresolved — teach and fail.
118
+ const msg =
119
+ `mobile prepare: dependencies installed but runtime is still unresolved ` +
120
+ `(${postInstall.reasons.join('; ')})\n` +
121
+ ` Next: inspect the checkout — node_modules may be incomplete or yarn.lock drifted.`;
122
+ if (!json) process.stderr.write(`${msg}\n`);
123
+ return { status: EXIT.runtime, output: msg };
124
+ }
125
+ case 'ready': {
126
+ // Deps resolved and runtime appears ready — still confirm bridge is live.
127
+ const bridge = dispatchAction({ id: 'wait-for-bridge', cwd: target }, target, platform, json, preflightMode);
128
+ if (bridge.status !== 0) return bridge;
129
+ break;
130
+ }
131
+ case 'launch': {
132
+ for (const action of postInstall.actions) {
133
+ const result = dispatchAction(action, target, platform, json, preflightMode);
134
+ if (result.status !== 0) return result;
135
+ }
136
+ break;
137
+ }
138
+ default: {
139
+ // 'blocked' or 'unknown' after install — surface as error.
140
+ const msg =
141
+ `mobile prepare: post-install state is ${postInstall.decision}: ${postInstall.reasons.join('; ')}\n` +
142
+ ` Next: run mm-harness verify --adapter mobile --target <checkout>`;
143
+ if (!json) process.stderr.write(`${msg}\n`);
144
+ return { status: EXIT.runtime, output: msg };
145
+ }
146
+ }
147
+ }
148
+
149
+ return { status: 0, output: '' };
150
+ }
151
+
152
+ function dispatchAction(
153
+ action: { id: string; argv?: string[]; cwd?: string },
154
+ target: string,
155
+ platform: string,
156
+ json: boolean,
157
+ preflightMode = 'fast',
158
+ ): ScriptResult {
159
+ const cwd = action.cwd ?? target;
160
+ switch (action.id) {
161
+ case 'yarn-setup': {
162
+ // Leaf: install node_modules (deps missing or stale).
163
+ const leaf = path.join(runnerDir, 'adapters/mobile/yarn-setup.sh');
164
+ return spawnScript(leaf, ['--target', cwd], target, json);
165
+ }
166
+ case 'start-metro': {
167
+ // Leaf: ensure Metro is running; argv may carry --clear for cache reset.
168
+ const leaf = path.join(runnerDir, 'adapters/mobile/start-metro.sh');
169
+ const extra: string[] = action.argv ?? [];
170
+ return spawnScript(leaf, ['--target', cwd, ...extra], target, json);
171
+ }
172
+ case 'prewarm-bundle': {
173
+ // Leaf: curl the bundle URL before opening the dev client.
174
+ const leaf = path.join(runnerDir, 'adapters/mobile/prewarm-bundle.sh');
175
+ return spawnScript(leaf, ['--platform', platform, '--target', cwd], target, json);
176
+ }
177
+ case 'wait-for-bridge': {
178
+ // Leaf: poll CDP bridge until a route target registers.
179
+ const leaf = path.join(runnerDir, 'adapters/mobile/wait-for-bridge.sh');
180
+ return spawnScript(leaf, ['--target', cwd], target, json);
181
+ }
182
+ case 'clear-metro-cache': {
183
+ // Legacy alias for start-metro --clear (kept for backward compat).
184
+ const leaf = path.join(runnerDir, 'adapters/mobile/start-metro.sh');
185
+ return spawnScript(leaf, ['--target', cwd, '--clear'], target, json);
186
+ }
187
+ case 'launch-mobile-runtime': {
188
+ // Leaf: open the MetaMask Mobile dev client on the simulator/device.
189
+ const leaf = path.join(runnerDir, 'adapters/mobile/open-device.sh');
190
+ return spawnScript(
191
+ leaf,
192
+ ['--platform', platform, '--target', cwd, '--preflight-mode', preflightMode],
193
+ target,
194
+ json,
195
+ );
196
+ }
197
+ case 'rebuild-native-dev-client': {
198
+ // Native rebuild required — the dev client binary predates a native Nitro
199
+ // module. Inform the caller; a full native build is not automated here.
200
+ const msg =
201
+ 'rebuild-native-dev-client required: native module mismatch detected.\n' +
202
+ ' Next: yarn start:ios or yarn start:android to rebuild the native dev client, ' +
203
+ 'then re-run mm-harness launch.';
204
+ if (!json) process.stderr.write(`${msg}\n`);
205
+ return { status: EXIT.runtime, output: msg };
206
+ }
207
+ default:
208
+ return { status: 0, output: '' };
209
+ }
210
+ }
@@ -86,10 +86,18 @@ const installActions = (target: string): MobileRuntimeDecisionAction[] => [
86
86
 
87
87
  const launchActions = (target: string, clearMetro = false): MobileRuntimeDecisionAction[] => {
88
88
  const actions: MobileRuntimeDecisionAction[] = [];
89
- if (clearMetro) {
90
- actions.push({ id: 'clear-metro-cache', argv: ['yarn', 'expo', 'start', '--clear'], cwd: target });
91
- }
89
+ // start-metro ensures Metro is running; --clear resets the bundle cache.
90
+ actions.push({
91
+ id: 'start-metro',
92
+ argv: clearMetro ? ['--clear'] : [],
93
+ cwd: target,
94
+ });
95
+ // Prewarm the bundle before opening the dev client so the app connects instantly.
96
+ actions.push({ id: 'prewarm-bundle', cwd: target });
97
+ // Open the dev client via the expo-development-client deep link.
92
98
  actions.push({ id: 'launch-mobile-runtime' });
99
+ // Poll the CDP bridge until a route target registers (confirms app is bridged).
100
+ actions.push({ id: 'wait-for-bridge', cwd: target });
93
101
  return actions;
94
102
  };
95
103