@deeeed/metamask-harness 0.17.3 → 0.17.5

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 (114) hide show
  1. package/CHANGELOG.md +6 -96
  2. package/README.md +136 -96
  3. package/adapters/extension/console-tail.mjs +24 -51
  4. package/adapters/extension/ensure-browser.sh +7 -1
  5. package/adapters/extension/inject.mjs +15 -5
  6. package/adapters/extension/launch-browser.cjs +4 -3
  7. package/adapters/extension/launch.sh +11 -8
  8. package/adapters/extension/lib/chrome-args.cjs +24 -0
  9. package/adapters/extension/live.sh +1 -2
  10. package/adapters/extension/reattach.sh +0 -1
  11. package/adapters/extension/seed-fixture.sh +12 -4
  12. package/adapters/extension/sidepanel-toggle.sh +1 -4
  13. package/adapters/extension/start-watch.sh +4 -17
  14. package/adapters/extension/stop-viewers.sh +1 -1
  15. package/adapters/extension/verify.sh +1 -1
  16. package/adapters/manifest.json +45 -53
  17. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -16
  18. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
  19. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +1 -3
  20. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +3 -2
  21. package/adapters/mobile/cleanup.sh +176 -16
  22. package/adapters/mobile/inject.sh +671 -90
  23. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
  24. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
  25. package/adapters/mobile/overlay/compat/README.md +28 -0
  26. package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +42 -0
  27. package/adapters/mobile/start-metro.sh +41 -0
  28. package/adapters/mobile/verify.sh +61 -4
  29. package/adapters/mobile/wait-for-bridge.sh +8 -22
  30. package/adapters/shared/harness-source-fingerprint.mjs +49 -0
  31. package/adapters/shared/install-repo-deps.sh +1 -5
  32. package/adapters/shared/open-debug.mjs +184 -103
  33. package/adapters/shared/resolve-farmslot-ports-core.mjs +3 -0
  34. package/adapters/shared/resolve-farmslot-ports.mjs +5 -0
  35. package/adapters/shared/resolve-farmslot-ports.sh +7 -0
  36. package/adapters/shared/resolve-slot-ports-core.mjs +6 -0
  37. package/adapters/shared/resolve-slot-ports.sh +7 -2
  38. package/bin/mm-harness +0 -7
  39. package/dist/adapters/extension/harness-freshness.js +39 -0
  40. package/dist/adapters/extension/runtime-decision.js +4 -73
  41. package/dist/adapters/extension/surface.js +2 -3
  42. package/dist/adapters/mobile/perps-env.js +3 -43
  43. package/dist/adapters/mobile/prepare.js +7 -22
  44. package/dist/adapters/mobile/source-freshness.js +26 -41
  45. package/dist/adapters/mobile/surface.js +1 -1
  46. package/dist/adapters/resolve-slot-ports.js +2 -0
  47. package/dist/adapters/slot-ports.js +0 -16
  48. package/dist/adapters.js +5 -12
  49. package/dist/cli-commands.js +1 -1
  50. package/dist/cli.js +12 -6
  51. package/dist/commands/call.js +22 -56
  52. package/dist/commands/check.js +3 -9
  53. package/dist/commands/checklist.js +4 -1
  54. package/dist/commands/debug.js +31 -38
  55. package/dist/commands/device-target.js +12 -27
  56. package/dist/commands/doctor.js +23 -77
  57. package/dist/commands/fixtures.js +17 -27
  58. package/dist/commands/launch/extension.js +2 -0
  59. package/dist/commands/launch/index.js +59 -167
  60. package/dist/commands/logs.js +6 -8
  61. package/dist/commands/manifest.js +25 -151
  62. package/dist/commands/parse-args.js +0 -2
  63. package/dist/commands/provision.js +3 -10
  64. package/dist/commands/run-engine.js +69 -71
  65. package/dist/commands/run-report.js +3 -12
  66. package/dist/commands/run.js +35 -245
  67. package/dist/commands/shared.js +3 -55
  68. package/dist/commands/status-probe.js +1 -1
  69. package/dist/commands/status.js +1 -1
  70. package/dist/commands/stop.js +2 -7
  71. package/dist/commands/update.js +28 -16
  72. package/dist/doctor.js +4 -1
  73. package/dist/harness.js +134 -43
  74. package/dist/heal-bounds.js +1 -1
  75. package/dist/live-adapter-contract.js +20 -6
  76. package/dist/mm-harness-cli.js +48 -123
  77. package/dist/paths.js +5 -2
  78. package/dist/runner.js +2 -37
  79. package/dist/runtime-context.js +8 -26
  80. package/docs/ADAPTER-SURFACE.md +112 -19
  81. package/docs/CHEATSHEET.md +52 -39
  82. package/docs/CLI-SPEC.md +1098 -0
  83. package/docs/CODE-MAP.md +1 -1
  84. package/docs/DEBUG-HANDOVER.md +36 -0
  85. package/docs/MENTAL-MODEL.md +295 -0
  86. package/docs/UX-PRINCIPLES.md +1 -3
  87. package/docs/architecture.md +375 -65
  88. package/docs/live-adapter-contract.md +2 -4
  89. package/docs/recipe-libraries.md +275 -45
  90. package/library/README.md +0 -4
  91. package/library/actions/core/perps/_controller.mjs +55 -10
  92. package/library/actions/mobile/platform/bridge.mjs +1 -5
  93. package/library/manifests/core.action-manifest.json +7 -7
  94. package/library/manifests/mobile.action-manifest.json +3 -28
  95. package/package.json +1 -4
  96. package/adapters/extension/launch-webpack.cjs +0 -83
  97. package/adapters/extension/stamp-runtime-title.cjs +0 -49
  98. package/adapters/extension/sync-webpack-dist.cjs +0 -105
  99. package/adapters/mobile/launch-console-forwarder.cjs +0 -39
  100. package/adapters/mobile/start-console-forwarder.sh +0 -70
  101. package/adapters/shared/update-check-worker.mjs +0 -35
  102. package/dist/adapters/extension/console-capture.js +0 -93
  103. package/dist/command-contract.js +0 -441
  104. package/dist/command-journal.js +0 -225
  105. package/dist/commands/last.js +0 -52
  106. package/dist/json-stream.js +0 -57
  107. package/dist/run-diagnostics.js +0 -261
  108. package/docs/CLI-ERGONOMICS-AUDIT.md +0 -32
  109. package/docs/CLI-ERGONOMICS-HUMAN-QA.md +0 -64
  110. package/docs/VIDEO-DEMO-VALIDATION.md +0 -74
  111. package/library/recipes/runner/smoke.core.recipe.json +0 -27
  112. package/library/recipes/wallet/smoke.extension.recipe.json +0 -42
  113. package/library/recipes/wallet/smoke.mobile.recipe.json +0 -42
  114. package/scripts/validate-human-outcomes.mjs +0 -169
package/CHANGELOG.md CHANGED
@@ -2,99 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
- ## 0.17.3 - 2026-07-15
6
-
7
- ### Added
8
-
9
- - Added a real-checkout human-outcome gate covering live readiness, runtime verification, debugger target resolution, smoke evidence, and unchanged tracked product source across Extension, Mobile, and Core.
10
- - Added generic Core runner smoke and fixture-backed wallet smoke recipes so the reusable examples are not tied to Perps.
11
- - Added nine-slot isolation coverage for platform ports, runtime files, logs, and headless Core behavior.
12
-
13
- ### Changed
14
-
15
- - `doctor` now reports CDP or simulator/ADB screenshot fallback when `capture-helper` is absent or unhealthy, while identifying video as unavailable separately.
16
- - Action, flow, and recipe discovery/documentation now use one concise scope model; the default recipe-library and adapter-surface references were reduced to current behavior.
17
- - Fixture discovery now uses one canonical checkout path instead of legacy fallback locations.
18
-
19
- ### Fixed
20
-
21
- - Mobile launch now waits for two consecutive platform-matched agentic bridge snapshots, preventing a transient startup route from being reported ready before an immediate verify.
22
- - `verify --json` keeps stdout to one machine-readable JSON document even when the platform harness must first be installed.
23
- - Mobile install metadata safely preserves checkout paths containing shell-significant characters.
24
- - Cleanup discovery no longer advertises a removed compatibility flag, and Core recovery guidance now points to the canonical wallet fixture.
25
- - Mobile console forwarding now uses canonical checkout paths, readiness rejects impossible one-poll stability checks, and Extension debug handles malformed WebSocket frames without crashing.
26
- - Extension watch refreshes remove stale runtime files, delay file replacement until transfer completion, allow slower console attachment, and stop the detached webpack process group.
27
-
28
- ## 0.17.2 - 2026-07-15
29
-
30
- ### Fixed
31
-
32
- - Passive update discovery now runs in a detached, once-daily registry probe, so fast commands populate the cache without adding latency or contaminating machine output.
33
-
34
- ## 0.17.1 - 2026-07-15
35
-
36
- ### Fixed
37
-
38
- - Extension `debug` now uses Chrome's native DevTools command for the active slot target and opens directly on Console; `debug --worker` targets the service worker without creating a frontend browser tab.
39
- - Extension webpack watchers now launch in their own process group, so they remain alive for incremental rebuilds after the initial `launch --build` command exits.
40
- - Successful Extension watch rebuilds now update the isolated loaded runtime in place, so a normal refresh of the current fullscreen or sidepanel UI shows source edits without another harness build or relaunch.
41
- - Extension slot titles are stamped into the isolated runtime after snapshots and successful rebuilds, preserving the slot identifier across an operator's normal browser refresh without changing product source.
42
- - Extension sidepanel opening no longer waits for the harness-owned user-gesture button to pass visual actionability checks; occluded wallet loading surfaces cannot block the trusted click.
43
- - Human recipe results now print an absolute-path artifact inventory and expand run-scoped application diagnostics inline; single-action calls show the same `CLEAN`/`REVIEW` diagnostics instead of requiring another query.
44
- - Extension console collection now starts after every successful public launch path, including quick reattach, and persists as one checkout-scoped process until `stop`. MetaMask UI/service-worker events and dapp-page events are routed to separate `extension-console.log` and `dapp-console.log` files; bare `logs` defaults to MetaMask while `--source dapp` and `--source webpack` remain explicit.
45
- - Mobile CDP-forwarded app events now write to `app-console.log`, so `logs --source app` and run diagnostics are no longer mixed with Metro bundler output; warm and cold launches start a process-group-detached collector only after bridge readiness so capture remains live after the launch command exits.
46
- - Extension `debug` now opens Chrome's local DevTools frontend inside the exact slot-owned profile, targets the MetaMask extension page rather than the dapp, and verifies the debugging connection before reporting success.
47
- - Mobile fixture application now fails immediately when the checkout fixture is absent, rejects the misplaced `fixtures set --dev` flag with the correct initialization command, and never prints a success message after failure.
48
-
49
- ### Changed
50
-
51
- - Current documentation now teaches the auto-detected workflow and verified human outcomes only: launch, incremental refresh, target-specific debugging/logs, fixture setup, capability discovery, and proof evidence.
52
- - Mobile fixture setup now requires the current multichain account-tree path, and Core signing requires the canonical checkout wallet fixture.
53
-
54
- ### Removed
55
-
56
- - Removed compatibility-only runtime-start fields, hidden `live` execution, removed-command handlers, undomained recipe aliases, flat/non-ESM action lookup, old port-resolver filenames, duplicate Extension launcher naming, obsolete fixture search locations, and the alternate Mobile product-source bridge. Mobile install now requires the current product-owned bridge and writes metadata only.
57
- - Removed the historical CLI migration/spec documents; current behavior is documented in the README, cheatsheet, architecture, and human QA checklist.
58
-
59
- ## 0.17.0 - 2026-07-14
60
-
61
- ### Added
62
-
63
- - Command-specific JSON failures now preserve a structured `userAction` across device targeting, action calls, recipe validation/execution, checks, and dev-server stop. Successful install/verify/cleanup, provision, and stop transitions add `next` only when one truthful follow-up exists; discovery and terminal evidence results remain quiet.
64
- - `run` and `launch` now implement the documented `--json-stream` contract: line-flushed JSONL phase/progress events, per-node recipe status, compact evidence paths, and one terminal `complete` event. Stream mode keeps stdout machine-clean and takes precedence when combined with `--json`; existing `--json` output is unchanged.
65
- - Interactive Mobile launch, Runway provisioning, and recipe execution now print contextual intent before their first potentially slow readiness or validation probe. The new lines are suppressed for `--json` and non-TTY stdout.
66
- - `last --json` reads an atomic, mode-`0600`, per-checkout resumability journal for significant runtime/proof commands, including redacted args, verdict, evidence paths, and timestamps. Interrupted commands remain `running`; discovery commands do not overwrite the record.
67
- - `actions <query>` now searches action names, categories, fields, and descriptions with typo tolerance, while single-action detail includes compact related-action names.
68
-
69
- ### Changed
70
-
71
- - Action categories now group protocol primitives into useful `assertion`, `control`, `evidence`, and `runtime` domains instead of exposing one category per unnamespaced action.
72
- - Public commands now reject unknown commands/options, missing values and positionals, invalid enums, and excess positionals before dispatch. Human errors include valid command-local options, a closest match when available, and a runnable example; `--json` returns stable `CLI_*` codes with `error.message` and `error.userAction`.
73
- - `docs/CLI-ERGONOMICS-AUDIT.md` is generated from the public command contract so every command remains covered by the shared bad-flag and JSON usage-error policy as the surface evolves.
74
-
75
- ## 0.16.0 - 2026-07-14
76
-
77
- ### Added
78
-
79
- - Mobile and Extension recipe runs now write bounded, redacted, non-blocking side findings to `diagnostics.json`: Mobile combines its in-app issue buffer with run-scoped log bytes, while Extension owns a CDP console subscription for the run. Core remains N/A because it is headless.
80
- - `actions --categories` now lists compact category counts, and `actions --category <name>` returns a bounded action/schema view so agents can discover capabilities without loading the complete adapter catalog.
81
-
82
- ## 0.15.2 - 2026-07-13
83
-
84
- ### Fixed
85
-
86
- - Mobile `ui.press text=<visible text>` now invokes the app bridge's text-content press path, while `test_id`, `testID`, and `selector` continue to use exact testID matching; ambiguous target combinations fail before bridge execution.
87
-
88
- ## 0.15.1 - 2026-07-13
89
-
90
- ### Fixed
91
-
92
- - `doctor --fix` recovery `nextActions` (numbered fixture choices, overlay install, runtime-context retry) now embed the resolved `--adapter` and absolute `--target`, shell-quoted, so each command works verbatim from any cwd. They are also prefixed with the resolved `bin/mm-harness` executable path, so they work in task-local installs where `mm-harness` is not on PATH.
93
-
94
- ## 0.15.0 - 2026-07-13
5
+ ## 0.17.5 - 2026-07-16
95
6
 
96
7
  ### Changed
97
8
 
9
+ - Mobile proof preflight now waits for the requested platform's bridge target after an automatic source-change restart; a bridge transition fails with a typed `MOBILE_BRIDGE_NOT_READY` result instead of letting the recipe race into `NO_TARGET`.
10
+ - Extension proof recovery now gives the checkout-owned runtime-dist synchronizer a short bounded handoff window after reattach, avoiding a false stale-runtime failure while still surfacing a real unhealthy build.
98
11
  - Extension `status` and `doctor` now report the webpack dev server as `up` only when a target-owned watcher is alive; historical successful build logs no longer imply liveness after `stop`.
99
12
  - Mobile overlay freshness no longer reports `pass` when every checkable runner overlay file is absent from the target; missing or drifted files are classified before the empty-overlay success case.
100
13
  - Mobile live verification now compares AgenticService/HUD freshness against the canonical `app/dev-tools/AgenticService` runner and product paths instead of skipping the check through the obsolete `app/core/AgenticService` layout.
@@ -116,6 +29,8 @@
116
29
  - Public Extension `stop` now tears down the checkout-owned watcher, Chrome/CDP profile processes, viewer tails/windows, and stale runtime PID/lock markers; explicit `--port` overrides only the watcher port and no longer hides the runtime-context CDP owner.
117
30
  - Tmux log viewers now require an explicit `RECIPE_TMUX_SESSION` or checkout `agentic-runtime.json` session; merely running from another tmux session never creates, sweeps, or marker-closes windows there, while stale markers are discarded and file logs remain available.
118
31
  - Extension `run`/`call --heal auto` now rebuild or reload before proof when source, `dist/chrome`, and the loaded runtime snapshot are out of sync; healing-off runs stop before execution with the exact `launch --build --verify` recovery command.
32
+ - Extension slots now launch with `--remote-allow-origins=http://127.0.0.1:<cdpPort>` (scoped to the slot's own debug origin, never `*`) so the local DevTools frontend can attach; without it Chrome 403s every browser DevTools WebSocket on the debug port. The debug-port flag trio (address + port + allow-origins) is declared once in a shared `chrome-args.cjs` and consumed by both the fresh launcher and the reopen path, so the origin allow-list can never drift away from the port.
33
+ - Extension `launch` now refreshes a slot's injected harness overlay from the runner whenever they diverge: `inject` stamps a source fingerprint into the slot manifest and each launch re-injects on mismatch (or when never installed). A runner change — e.g. a launcher flag — reaches every slot on the next launch instead of silently running a stale injected copy.
119
34
  - Extension launch, readiness, reattach, actions, and screenshots preserve the operator's keyboard focus by default while retaining a headed, isolated CDP browser; macOS app launch uses Launch Services in the background, and explicit window focus remains available with `MM_HARNESS_FOCUS_BROWSER=1`.
120
35
  - `mm-harness check diff --fix` now repairs only changed files with the checkout's direct ESLint and oxfmt/Prettier binaries, then runs the normal bounded validation, so agents do not need to guess repository lint scripts.
121
36
  - Recipe-run infra healing now performs the adapter's runtime recovery before retrying instead of only recording a recovery label and immediately rerunning the recipe.
@@ -130,12 +45,7 @@
130
45
  - `fixtures init --from <path>|--dev` initializes the canonical checkout wallet fixture explicitly; `--dev` is a disposable public test wallet and is never suitable for real funds.
131
46
 
132
47
  ### Fixed
133
- - Missing checkout targets now return the structured usage envelope before lock acquisition in `run` and `doctor`; recovery bounds distinguish a consumed successful repair, adapter helpers preserve explicit runtime/device pins, checklist-only runtime resolution is lazy, and fixture finalization is time-bounded.
134
- - Checkout contention now uses one bounded JSON contract across `call`, `launch`, `doctor --fix`, `fixtures`, and `run`: exit 4 with `SANDBOX_BUSY` and an actionable lock path.
135
- - Extension loaded-snapshot checks now use size/mtime comparison instead of full checksums, fall back to an in-process comparison when `rsync` is unavailable, and report comparison failures without forcing a misleading relaunch.
136
- - Mobile Perps environment repair now publishes `.js.env` atomically without following destination symlinks, and Mobile source preflight returns an actionable `MOBILE_SOURCE_UNAVAILABLE` error for non-Git targets.
137
- - Mobile no longer translates the Extension-only `visible=true` wait alias into an unsupported enum; `call --arg` accepts quoted JSON strings for numeric-looking string values.
138
- - Timed streaming leaves now settle as soon as a SIGTERM-terminated direct child exits, reap SIGTERM-ignoring descendants, and forward Ctrl+C/SIGTERM to detached child trees before the harness exits.
48
+ - Extension `debug` now opens a working, offline DevTools console attached to the running slot's extension page: it launches the locally-served bundled DevTools frontend in its own window inside the slot's Chromium instead of Chrome's hosted `chrome-devtools-frontend.appspot.com` URL, which Chrome's WebSocket origin check rejected (the console showed "Debugging connection was closed"). A slot launched before this change fails a pre-open origin probe and reports the exact relaunch command instead of opening a dead console.
139
49
  - `doctor --fix` now schema-validates, quarantines, and recreates malformed, semantically invalid, or cross-adapter-contaminated checkout runtime context without touching product data; wallet-fixture recovery commands use `--force` only for invalid or incomplete existing fixtures, while a missing fixture retains the safe unforced initialization command.
140
50
  - `logs --source app` now resolves the adapter-owned application console (`extension-console.log` on Extension and the CDP-forwarded `metro.log` on Mobile); an absent console returns `APP_LOG_UNAVAILABLE` instead of falling back to a dev-server log or reporting a usage error.
141
51
  - Doctor JSON now retains runtime probe failures, marks every diagnostic check as required or optional, derives its status from required checks, and reports runtime readiness separately.
package/README.md CHANGED
@@ -1,137 +1,177 @@
1
- # mm-harness
1
+ # MetaMask Recipe Runner — `mm-harness`
2
2
 
3
- `mm-harness` gives people and agents one CLI for operating MetaMask Mobile,
4
- Extension, and Core. Run it inside a checkout; the product is auto-detected.
3
+ Architecture: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
5
4
 
6
- It has two jobs:
5
+ **`mm-harness` is built for the agent but easy for the human to use and
6
+ understand.** It is the MetaMask recipe harness: one front door to launch the app,
7
+ prove behavior with recipes, and manage the per-checkout runtime overlay. It is a
8
+ thin MetaMask adapter over the generic `@farmslot/recipe-harness` engine — the
9
+ runner never re-implements graph execution, it binds to it. The agent gets full
10
+ depth (explicit flags, `--json`, ports, build tiers); the human gets short
11
+ commands, positional targets, good defaults, completions, and teaching
12
+ help/errors. Run it from inside a MetaMask Mobile/Extension/Core checkout and the
13
+ platform is auto-detected.
7
14
 
8
- - operate the app with the same controls a developer needs: launch, logs, debug,
9
- fixtures, actions, and reusable flows;
10
- - execute deterministic recipes and save reviewable proof.
11
-
12
- The generic graph engine lives in `@farmslot/recipe-harness`. This package owns
13
- only MetaMask-specific actions and runtime control.
15
+ ```
16
+ DAILY LOOP launch · logs · debug · fixtures what a teammate runs many times a day
17
+ DISCOVER actions · call · flows compose recipes from the vocabulary + flow library
18
+ PROVE run · doctor run recipes, check readiness
19
+ RUNTIME OVERLAY install · verify · cleanup manage the per-checkout runtime overlay
20
+ ```
14
21
 
15
- One mental model:
22
+ **One bin, no per-platform binaries.** `mm-harness` is the only command. Grammar:
23
+ `mm-harness <command> [target] [flags]` — the human types the bare command
24
+ (platform auto-detected) or a positional target (`mm-harness launch ios`); depth
25
+ lives in FLAGS with good defaults, never in extra command names. It ships shell
26
+ completions (zsh + bash) for commands, targets, and flags.
16
27
 
17
- - **Action** one typed operation, such as reading wallet state.
18
- - **Flow** a reusable, parameterized sequence of actions.
19
- - **Recipe** the complete executable proof for a task or regression.
28
+ Naming: **"harness" is the tool** (`mm-harness`) and the generic engine package
29
+ (`@farmslot/recipe-harness`); the files it installs into a checkout are the
30
+ **runtime overlay**. Read [MENTAL-MODEL.md](docs/MENTAL-MODEL.md) first (the
31
+ before→after map), then [CLI-SPEC.md](docs/CLI-SPEC.md) for the full contract.
20
32
 
21
- ## Install
33
+ One package, two responsibilities:
22
34
 
23
- ```bash
24
- npm install -g @deeeed/metamask-harness@latest
25
- mm-harness --version
26
- mm-harness doctor
27
- ```
35
+ - **Recipe layer:** manifests, recipes, Mobile/Extension adapters, proof output.
36
+ - **Runtime layer:** start/reuse Metro or Chrome, seed fixtures, wait for readiness.
28
37
 
29
- Use `mm-harness update` to install the latest published version.
38
+ Farmslot and skills are wrappers around this CLI; they should not copy runner
39
+ logic.
30
40
 
31
- ## Daily workflow
41
+ ## Quick start
32
42
 
33
- From a MetaMask checkout:
43
+ Run from inside a MetaMask checkout (platform auto-detected):
34
44
 
35
45
  ```bash
36
- # Extension
37
- mm-harness launch # starts/reuses Chrome and the incremental watcher
38
- mm-harness launch --sidepanel # open the sidepanel surface
39
-
40
- # Mobile
41
- mm-harness launch ios
42
- mm-harness launch android
43
-
44
- # Any product
45
- mm-harness status
46
- mm-harness logs
47
- mm-harness debug
48
- mm-harness fixtures set
49
- ```
46
+ # 1) Launch the app (installs the runtime overlay on first use, boots Metro/build)
47
+ mm-harness launch ios # mobile: ios | android; extension: just `launch`
50
48
 
51
- `launch` installs the checkout-local runtime overlay when needed. On Extension,
52
- the normal launch keeps `yarn start` running after the command exits. Source
53
- edits compile incrementally into the isolated runtime; refresh the active page or
54
- sidepanel to see them. Use `launch --build` only when a clean rebuild is needed.
49
+ # 2) Run a recipe and write evidence
50
+ mm-harness run recipe.json --adapter mobile --artifacts-dir /tmp/recipe-artifacts --json
51
+ ```
55
52
 
56
- `debug` opens DevTools for the active Extension target in that checkout's Chrome
57
- profile. Mobile opens the React Native developer surface.
53
+ Outputs: `summary.json`, `trace.json`, screenshots, logs, and an artifact
54
+ manifest.
58
55
 
59
- Filter logs when needed:
56
+ ## Mental model
60
57
 
61
- ```bash
62
- mm-harness logs --source extension # Extension console
63
- mm-harness logs --source dapp # active dapp console
64
- mm-harness logs --source webpack # Extension compiler
65
- mm-harness logs --source app # Mobile app events
66
- mm-harness logs --source metro # Mobile bundler
58
+ ```text
59
+ mm-harness launch # runtime/orchestration: the app is ready
60
+ mm-harness run # runner/proof: actions execute and evidence is saved
67
61
  ```
68
62
 
69
- ## Build and run proof
63
+ Do not mix those layers. library/ = what recipes ARE (recipes, actions,
64
+ manifests — the content). src/ = what EXECUTES them (TypeScript CLI/engine glue,
65
+ typed adapters). adapters/ = what CONTROLS the app and its instances (start,
66
+ windows, wallet state, health, ports, parallel).
67
+
68
+ ## Useful commands
70
69
 
71
70
  ```bash
72
- mm-harness actions positions
73
- mm-harness actions --action metamask.wallet.ensure_unlocked
74
- mm-harness call metamask.wallet.ensure_unlocked
75
- mm-harness flows
76
-
77
- mm-harness run path/to/recipe.json --plan
78
- mm-harness run path/to/recipe.json --artifacts-dir temp/recipe-proof
79
- mm-harness run wallet.smoke --artifacts-dir temp/wallet-proof
71
+ # Capabilities (compose recipes from the vocabulary + flow library)
72
+ mm-harness actions --adapter mobile --json # the action vocabulary
73
+ mm-harness actions --adapter mobile --raw # raw action-manifest dump
74
+ mm-harness call unlock --adapter extension # run one action via the real engine path
75
+ mm-harness flows --json # reusable library flows, with provenance
76
+
77
+ # Readiness + proof
78
+ mm-harness doctor # read-only readiness check (no launch)
79
+ mm-harness doctor --fix # heal the overlay without launching
80
+ mm-harness run recipe.json --plan --adapter mobile # validate + plan, touch nothing
81
+
82
+ # Daily loop (run from inside a checkout)
83
+ mm-harness launch ios # launch (mobile: ios | android)
84
+ mm-harness logs # tail Metro/webpack + app logs
85
+ mm-harness debug # open the debug console
86
+ mm-harness fixtures sync # sync fixtures; `fixtures set` seeds the wallet
87
+
88
+ # Keep the install current
89
+ mm-harness update # upgrade to the published latest (prints old → new)
90
+ mm-harness update --check --json # report only — { current, latest, updateAvailable }
80
91
  ```
81
92
 
82
- `call` exercises one action. A recipe composes actions and flows into a
83
- deterministic proof.
93
+ ### Human validation rule
94
+
95
+ An exit code or `--json` result is not sufficient for a UI/debug command. After
96
+ `launch --verify`, confirm the debugger is visibly connected to the current
97
+ slot's app/profile. Repeat this check on every slot/platform that will be used
98
+ before release.
99
+
100
+ A passive once-a-day nudge prints one `stderr` line when a newer version is
101
+ published (`mm-harness <cur> → <latest> available · run: mm-harness update`). It
102
+ never blocks and never auto-updates. Silence it with `MM_HARNESS_NO_UPDATE_CHECK=1`
103
+ (auto-off in CI).
104
+
105
+ Set `RECIPE_LOG_UI=compact|full|quiet` (default `compact`) and `RECIPE_LOG_EVENTS=10`
106
+ to tune the compact log view during launch and Metro/webpack startup.
107
+
108
+ ## Layout
109
+
110
+ ```text
111
+ bin/ the mm-harness entrypoint
112
+ src/ what EXECUTES recipes: TypeScript CLI/engine glue +
113
+ typed per-adapter logic (src/adapters/)
114
+ adapters/ what CONTROLS the app and its instances: launch/live/
115
+ watch/windows/wallet state/health/inject/cleanup per
116
+ platform, shared glue, porcelain, overlay payload +
117
+ manifest.json
118
+ library/ what recipes ARE: recipes/ (recipe JSONs), actions/
119
+ (per-platform implementations), manifests/ (capability)
120
+ scripts/ dev tooling (yarn check, local farmslot link, e2e
121
+ validation, adapter-surface-doctor)
122
+ docs/ details when this README is not enough
123
+ ```
84
124
 
85
- A run prints its findings, diagnostics, and absolute evidence paths. The artifact
86
- directory contains the executed recipe, summary, trace, diagnostics when
87
- applicable, screenshots/logs, and `artifact-manifest.json`. Core is headless, so
88
- browser/device diagnostics do not apply.
125
+ Defaults for installed harness/runtime paths live in
126
+ `adapters/shared/path-defaults.json`.
89
127
 
90
- Use `mm-harness last --json` to resume after interruption without repeating a
91
- side-effecting command.
128
+ ## Runtime-readiness ownership
92
129
 
93
- ## Team recipe libraries
130
+ Generic readiness mechanics live in `@farmslot/recipe-harness` (0.3+):
94
131
 
95
- Add a named shared directory with `RECIPE_LIBRARY_PATH`:
132
+ - `@farmslot/recipe-harness/runtime/deps-readiness` install fingerprint + baseline
133
+ - `@farmslot/recipe-harness/runtime/log-analysis` — bundle-log boundaries, unresolved-module scoping
134
+ - `@farmslot/recipe-harness/runtime/metro-probe` — Metro `/status` reachability
96
135
 
97
- ```bash
98
- export RECIPE_LIBRARY_PATH="team=$HOME/shared-library/team-recipes"
99
- mm-harness flows
100
- ```
136
+ This repo adds **MetaMask adapters only**: `adapters/mobile/deps-markers.ts`,
137
+ `adapters/mobile/runtime-decision.ts`, `adapters/extension/runtime-decision.ts`,
138
+ and shell launch in `adapters/porcelain/mm-recipe` / `adapters/porcelain/mme-recipe`. Do not re-copy harness
139
+ primitives into `adapters/shared/`. CI installs harness from npm — publish
140
+ `@farmslot/recipe-harness` before bumping the dependency here.
101
141
 
102
- Team flows can wrap repeated product behavior while each checklist or task keeps
103
- its own acceptance criteria. See [recipe libraries](docs/recipe-libraries.md).
142
+ ## Dev/prod harness (`MM_HARNESS_BIN`)
104
143
 
105
- ## Health and recovery
144
+ The global npm install owns the `mm-harness` on `PATH` — that is the **prod**
145
+ harness. To run a **dev** checkout with active pre-release changes alongside it,
146
+ set `MM_HARNESS_BIN` to that checkout's `bin/mm-harness`:
106
147
 
107
148
  ```bash
108
- mm-harness doctor # inspect; does not launch
109
- mm-harness doctor --fix # repair local harness setup; does not launch
110
- mm-harness verify # verify the installed runtime overlay
111
- mm-harness cleanup # remove the runtime overlay
149
+ export MM_HARNESS_BIN=/path/to/checkout/bin/mm-harness # dev: this checkout owns the run
150
+ unset MM_HARNESS_BIN # prod: the installed/global bin
112
151
  ```
113
152
 
114
- Human output gives a next command. `--json` gives stable machine-readable errors
115
- and keeps stdout free of decoration. `--json-stream` emits line-delimited progress
116
- for long recipe runs.
153
+ `MM_HARNESS_BIN` is the single override point: when set, the entrypoint hands the
154
+ whole invocation to that path before doing any dependency work; when unset it
155
+ resolves the installed bin. A dev checkout is never installed globally, so there is
156
+ no `PATH` collision — dev is reached only through `MM_HARNESS_BIN` (or a shell
157
+ alias). A per-farm environment can export it so the dev farm dispatches through the
158
+ dev harness while the prod farm leaves it unset.
117
159
 
118
- ## Development
119
-
120
- Use a source checkout without replacing the global install:
121
-
122
- ```bash
123
- export MM_HARNESS_BIN=/path/to/metamask-harness/bin/mm-harness
124
- mm-harness --version
125
- ```
160
+ ## Runtime notes
126
161
 
127
- Unset `MM_HARNESS_BIN` to return to the published install.
162
+ - `tmux` is recommended for long-lived Metro/webpack processes; standalone use
163
+ falls back to detached `nohup` where possible.
164
+ - Mobile may inject a local development bridge/HUD into older checkouts. Do not
165
+ commit those product patches.
166
+ - Extension does not patch product source; it drives `dist/chrome` through Chrome
167
+ CDP.
128
168
 
129
- Validate changes with:
169
+ ## Validate changes
130
170
 
131
171
  ```bash
132
172
  yarn check
173
+ bash -n bin/mm-harness adapters/porcelain/{metamask-recipe,mm-recipe,mme-recipe} adapters/{mobile,extension,core}/*.sh
174
+ node --check adapters/extension/inject.mjs adapters/extension/cleanup.mjs adapters/extension/readiness.mjs adapters/extension/launch-browser.cjs adapters/shared/open-debug.mjs adapters/shared/cli-home.mjs adapters/shared/log-tui.mjs adapters/shared/progress.mjs adapters/shared/recipe-paths.mjs scripts/check.mjs
133
175
  ```
134
176
 
135
- Current references: [cheatsheet](docs/CHEATSHEET.md), [recipe libraries](docs/recipe-libraries.md),
136
- and [human QA](docs/CLI-ERGONOMICS-HUMAN-QA.md). Maintainers also have the
137
- [architecture](docs/architecture.md) and [video gate](docs/VIDEO-DEMO-VALIDATION.md).
177
+ More detail: [Architecture](docs/architecture.md), [Package boundaries](docs/package-boundaries.md), [Runtime file conventions](docs/runtime-file-conventions.md).
@@ -8,7 +8,7 @@
8
8
  // the tab shows live extension logs (the equivalent of the Metro/webpack tabs).
9
9
  //
10
10
  // Usage:
11
- // console-tail.mjs --cdp-port <port> [--extension-log <file>] [--dapp-log <file>]
11
+ // console-tail.mjs --cdp-port <port> [--log <file>]
12
12
  //
13
13
  // Resilience: MV3 service workers cycle on idle and UI pages open/close, so we
14
14
  // re-poll /json/list every ~2s and (re)attach to any extension target that is
@@ -19,10 +19,9 @@
19
19
  'use strict';
20
20
 
21
21
  import { appendFileSync } from 'node:fs';
22
- import path from 'node:path';
23
22
 
24
23
  function parseArgs(argv) {
25
- const args = { cdpPort: null, extensionLog: null, dappLog: null };
24
+ const args = { cdpPort: null, log: null };
26
25
  for (let i = 0; i < argv.length; i += 1) {
27
26
  const arg = argv[i];
28
27
  switch (arg) {
@@ -30,16 +29,12 @@ function parseArgs(argv) {
30
29
  args.cdpPort = argv[++i];
31
30
  break;
32
31
  case '--log':
33
- case '--extension-log':
34
- args.extensionLog = argv[++i];
35
- break;
36
- case '--dapp-log':
37
- args.dappLog = argv[++i];
32
+ args.log = argv[++i];
38
33
  break;
39
34
  case '-h':
40
35
  case '--help':
41
36
  process.stdout.write(
42
- 'Usage: console-tail.mjs --cdp-port <port> [--extension-log <file>] [--dapp-log <file>]\n',
37
+ 'Usage: console-tail.mjs --cdp-port <port> [--log <file>]\n',
43
38
  );
44
39
  process.exit(0);
45
40
  break;
@@ -56,9 +51,6 @@ if (!Number.isInteger(cdpPort) || cdpPort <= 0) {
56
51
  throw new Error('console-tail requires a numeric --cdp-port.');
57
52
  }
58
53
  const HOST = `127.0.0.1:${cdpPort}`;
59
- if (args.extensionLog && !args.dappLog) {
60
- args.dappLog = path.join(path.dirname(path.resolve(args.extensionLog)), 'dapp-console.log');
61
- }
62
54
 
63
55
  if (typeof WebSocket === 'undefined') {
64
56
  throw new Error('console-tail requires Node 22+ with a global WebSocket.');
@@ -73,9 +65,9 @@ const cooldown = new Map();
73
65
  // Writes formatted console-event strings (never raw HTTP response bodies) to
74
66
  // stdout and optionally to a caller-supplied log file. The source is the local
75
67
  // Chrome CDP WebSocket (127.0.0.1:<cdpPort>) — a trusted-local endpoint.
76
- function out(line, destination = null) {
68
+ function out(line) {
77
69
  process.stdout.write(`${line}\n`);
78
- if (destination) appendFileSync(destination, `${line}\n`);
70
+ if (args.log) appendFileSync(args.log, `${line}\n`);
79
71
  }
80
72
 
81
73
  function stamp() {
@@ -103,22 +95,6 @@ function isExtensionTarget(target) {
103
95
  return String(target.url || '').startsWith('chrome-extension://');
104
96
  }
105
97
 
106
- function isDappTarget(target) {
107
- if (!target || !target.webSocketDebuggerUrl || target.type !== 'page') return false;
108
- try {
109
- const url = new URL(String(target?.url || ''));
110
- return url.protocol === 'http:' || url.protocol === 'https:';
111
- } catch {
112
- return false;
113
- }
114
- }
115
-
116
- function targetStream(target) {
117
- if (isExtensionTarget(target)) return { kind: 'extension', log: args.extensionLog };
118
- if (isDappTarget(target)) return { kind: 'dapp', log: args.dappLog };
119
- return null;
120
- }
121
-
122
98
  function previewArg(arg) {
123
99
  if (!arg || typeof arg !== 'object') return String(arg);
124
100
  if ('value' in arg && arg.value !== undefined) {
@@ -132,29 +108,29 @@ function previewArg(arg) {
132
108
  return arg.subtype || arg.type || '?';
133
109
  }
134
110
 
135
- function emitConsole(label, params, destination) {
111
+ function emitConsole(label, params) {
136
112
  const level = String(params.type || 'log').toUpperCase();
137
113
  const text = (params.args || []).map(previewArg).join(' ');
138
- out(`${stamp()} [${label}] ${level} ${text}`, destination);
114
+ out(`${stamp()} [${label}] ${level} ${text}`);
139
115
  }
140
116
 
141
- function emitException(label, params, destination) {
117
+ function emitException(label, params) {
142
118
  const d = params.exceptionDetails || {};
143
119
  const text =
144
120
  d.exception?.description ||
145
121
  d.exception?.value ||
146
122
  d.text ||
147
123
  'uncaught exception';
148
- out(`${stamp()} [${label}] EXCEPTION ${text}`, destination);
124
+ out(`${stamp()} [${label}] EXCEPTION ${text}`);
149
125
  }
150
126
 
151
- function emitLogEntry(label, params, destination) {
127
+ function emitLogEntry(label, params) {
152
128
  const e = params.entry || {};
153
129
  const level = String(e.level || 'log').toUpperCase();
154
- out(`${stamp()} [${label}] ${level} ${e.text || ''}`, destination);
130
+ out(`${stamp()} [${label}] ${level} ${e.text || ''}`);
155
131
  }
156
132
 
157
- function handleMessage(label, destination, data) {
133
+ function handleMessage(label, data) {
158
134
  let message;
159
135
  try {
160
136
  message = JSON.parse(data);
@@ -165,13 +141,13 @@ function handleMessage(label, destination, data) {
165
141
  if (message.id != null) return; // command ack, not an event
166
142
  switch (message.method) {
167
143
  case 'Runtime.consoleAPICalled':
168
- emitConsole(label, message.params || {}, destination);
144
+ emitConsole(label, message.params || {});
169
145
  break;
170
146
  case 'Runtime.exceptionThrown':
171
- emitException(label, message.params || {}, destination);
147
+ emitException(label, message.params || {});
172
148
  break;
173
149
  case 'Log.entryAdded':
174
- emitLogEntry(label, message.params || {}, destination);
150
+ emitLogEntry(label, message.params || {});
175
151
  break;
176
152
  default:
177
153
  break;
@@ -179,11 +155,9 @@ function handleMessage(label, destination, data) {
179
155
  }
180
156
 
181
157
  function attach(target) {
182
- const stream = targetStream(target);
183
- if (!stream) return;
184
- const label = `${stream.kind}:${shortLabel(target)}`;
158
+ const label = shortLabel(target);
185
159
  const ws = new WebSocket(target.webSocketDebuggerUrl);
186
- const entry = { ws, label, destination: stream.log, opened: false };
160
+ const entry = { ws, label, opened: false };
187
161
  attached.set(target.id, entry);
188
162
 
189
163
  ws.addEventListener('open', () => {
@@ -192,16 +166,16 @@ function attach(target) {
192
166
  for (const method of ['Runtime.enable', 'Log.enable']) {
193
167
  ws.send(JSON.stringify({ id: ++id, method, params: {} }));
194
168
  }
195
- out(`${stamp()} [${label}] [attached]`, stream.log);
169
+ out(`${stamp()} [${label}] [attached]`);
196
170
  });
197
171
  ws.addEventListener('message', (event) => {
198
- handleMessage(label, stream.log, typeof event.data === 'string' ? event.data : '');
172
+ handleMessage(label, typeof event.data === 'string' ? event.data : '');
199
173
  });
200
174
  const drop = () => {
201
175
  if (attached.get(target.id) !== entry) return;
202
176
  attached.delete(target.id);
203
177
  if (entry.opened) {
204
- out(`${stamp()} [${label}] [reconnect] target detached; will re-attach`, stream.log);
178
+ out(`${stamp()} [${label}] [reconnect] target detached; will re-attach`);
205
179
  } else {
206
180
  // Never connected (target gone / not debuggable yet): back off so a dead
207
181
  // target is not retried — and logged — on every 2s poll.
@@ -222,9 +196,8 @@ async function poll() {
222
196
  // CDP not reachable this tick (browser starting/restarting); retry next poll.
223
197
  return;
224
198
  }
225
- const targetList = Array.isArray(targets) ? targets : [];
226
- for (const target of targetList) {
227
- if (!targetStream(target)) continue;
199
+ for (const target of Array.isArray(targets) ? targets : []) {
200
+ if (!isExtensionTarget(target)) continue;
228
201
  if (attached.has(target.id)) continue;
229
202
  const until = cooldown.get(target.id);
230
203
  if (until !== undefined) {
@@ -253,6 +226,6 @@ function shutdown() {
253
226
  process.on('SIGINT', shutdown);
254
227
  process.on('SIGTERM', shutdown);
255
228
 
256
- out(`${stamp()} [console-tail] streaming extension and dapp consoles via cdp ${cdpPort}`);
229
+ out(`${stamp()} [console-tail] streaming extension console via cdp ${cdpPort}`);
257
230
  await poll();
258
231
  setInterval(poll, 2000);
@@ -277,6 +277,7 @@ const fs = require('fs');
277
277
  const http = require('http');
278
278
  const { execSync, execFileSync } = require('child_process');
279
279
  let chromium; try { chromium = require('@playwright/test').chromium; } catch { chromium = require('playwright').chromium; }
280
+ const { remoteDebuggingArgs } = require(path.join('${SCRIPT_DIR}', 'lib/chrome-args.cjs'));
280
281
 
281
282
  const SLOT_ID = '${SLOT_ID}';
282
283
  const AGENT_DIR = '${AGENT_DIR}';
@@ -309,7 +310,12 @@ const resumeWebpack = () => {
309
310
  '--no-default-browser-check',
310
311
  '--window-size=420,800',
311
312
  ];
312
- if (CDP_PORT) args.push('--remote-debugging-port=' + CDP_PORT);
313
+ // Debug-port trio (address + port + scoped allow-origins) from the shared module
314
+ // so the DevTools origin allow-list can never drift away from the port; without
315
+ // it Chrome 403s every browser DevTools WebSocket and mm-harness debug can't attach.
316
+ if (CDP_PORT) {
317
+ args.push(...remoteDebuggingArgs(CDP_PORT));
318
+ }
313
319
 
314
320
  const chromiumApp = path.dirname(path.dirname(path.dirname(chromium.executablePath())));
315
321
  execFileSync('open', ['-g', '-n', '-a', chromiumApp, '--args', ...args], { stdio: 'ignore' });