@deeeed/metamask-harness 0.16.0 → 0.17.1
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.
- package/CHANGELOG.md +44 -0
- package/README.md +90 -137
- package/adapters/core/cleanup.sh +0 -0
- package/adapters/core/inject.sh +0 -0
- package/adapters/extension/cleanup.mjs +0 -0
- package/adapters/extension/console-tail.mjs +51 -24
- package/adapters/extension/ensure-browser.sh +0 -0
- package/adapters/extension/inject.mjs +5 -9
- package/adapters/extension/launch-browser.cjs +0 -0
- package/adapters/extension/launch-webpack.cjs +83 -0
- package/adapters/extension/launch.sh +8 -11
- package/adapters/extension/live.sh +2 -0
- package/adapters/extension/readiness.mjs +0 -0
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/refresh-build.sh +0 -0
- package/adapters/extension/seed-fixture.sh +4 -12
- package/adapters/extension/sidepanel-toggle.sh +4 -1
- package/adapters/extension/snapshot-dist.sh +0 -0
- package/adapters/extension/stamp-runtime-title.cjs +49 -0
- package/adapters/extension/start-watch.sh +16 -4
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/sync-webpack-dist.cjs +105 -0
- package/adapters/extension/verify.sh +1 -1
- package/adapters/extension/wallet-fixture-state.cjs +0 -0
- package/adapters/manifest.json +46 -38
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
- package/adapters/mobile/bridge-runtime/setup-wallet.sh +0 -0
- package/adapters/mobile/cleanup.sh +16 -176
- package/adapters/mobile/inject.sh +88 -671
- package/adapters/mobile/launch-console-forwarder.cjs +39 -0
- package/adapters/mobile/lib/metro-listener.sh +0 -0
- package/adapters/mobile/lib/tmux-viewer.sh +0 -0
- package/adapters/mobile/open-device.sh +0 -0
- package/adapters/mobile/prewarm-bundle.sh +0 -0
- package/adapters/mobile/start-console-forwarder.sh +70 -0
- package/adapters/mobile/start-metro.sh +0 -41
- package/adapters/mobile/stop-metro.sh +0 -0
- package/adapters/mobile/verify.sh +4 -61
- package/adapters/mobile/wait-for-bridge.sh +0 -0
- package/adapters/mobile/yarn-setup.sh +0 -0
- package/adapters/shared/activate-repo-node.sh +0 -0
- package/adapters/shared/activate-repo-ruby.sh +0 -0
- package/adapters/shared/cli-ux.sh +0 -0
- package/adapters/shared/ensure-runner-deps.sh +0 -0
- package/adapters/shared/harness-path.sh +0 -0
- package/adapters/shared/hash-helpers.sh +0 -0
- package/adapters/shared/install-repo-deps.sh +0 -0
- package/adapters/shared/json-field.sh +0 -0
- package/adapters/shared/open-debug.mjs +90 -36
- package/adapters/shared/open-log-window.sh +0 -0
- package/adapters/shared/reap-checkout-metros.sh +0 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +0 -6
- package/adapters/shared/resolve-slot-ports.mjs +0 -0
- package/adapters/shared/resolve-slot-ports.sh +0 -5
- package/adapters/shared/sync-wallet-fixture.sh +0 -0
- package/adapters/shared/tmux-session.sh +0 -0
- package/adapters/shared/tmux-viewer.sh +0 -0
- package/dist/adapters/extension/console-capture.js +84 -0
- package/dist/adapters/extension/runtime-decision.js +14 -2
- package/dist/adapters/extension/surface.js +3 -2
- package/dist/adapters/mobile/prepare.js +22 -7
- package/dist/adapters/mobile/surface.js +1 -1
- package/dist/adapters/resolve-slot-ports.js +0 -2
- package/dist/adapters/slot-ports.js +8 -0
- package/dist/cli.js +5 -11
- package/dist/command-contract.js +441 -0
- package/dist/command-journal.js +225 -0
- package/dist/commands/call.js +43 -20
- package/dist/commands/check.js +9 -3
- package/dist/commands/debug.js +38 -31
- package/dist/commands/device-target.js +27 -12
- package/dist/commands/doctor.js +19 -6
- package/dist/commands/fixtures.js +19 -11
- package/dist/commands/last.js +52 -0
- package/dist/commands/launch/index.js +166 -59
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +114 -29
- package/dist/commands/parse-args.js +1 -0
- package/dist/commands/provision.js +10 -3
- package/dist/commands/run-engine.js +27 -42
- package/dist/commands/run-report.js +1 -1
- package/dist/commands/run.js +243 -41
- package/dist/commands/shared.js +11 -1
- package/dist/commands/status-probe.js +1 -1
- package/dist/commands/status.js +1 -1
- package/dist/commands/stop.js +7 -2
- package/dist/doctor.js +1 -4
- package/dist/harness.js +19 -125
- package/dist/json-stream.js +57 -0
- package/dist/live-adapter-contract.js +6 -20
- package/dist/mm-harness-cli.js +111 -41
- package/dist/paths.js +2 -5
- package/dist/run-diagnostics.js +35 -40
- package/dist/runner.js +32 -1
- package/dist/runtime-context.js +26 -8
- package/docs/CHEATSHEET.md +39 -52
- package/docs/CLI-ERGONOMICS-AUDIT.md +32 -0
- package/docs/CLI-ERGONOMICS-HUMAN-QA.md +48 -0
- package/docs/CODE-MAP.md +1 -1
- package/docs/UX-PRINCIPLES.md +2 -0
- package/docs/VIDEO-DEMO-VALIDATION.md +62 -0
- package/docs/architecture.md +65 -375
- package/docs/live-adapter-contract.md +3 -1
- package/library/actions/core/perps/_controller.mjs +10 -55
- package/library/manifests/mobile.action-manifest.json +1 -1
- package/package.json +3 -2
- package/scripts/completions.sh +0 -0
- package/scripts/install-completions.sh +0 -0
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
- package/adapters/mobile/overlay/compat/README.md +0 -28
- package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
- package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
- package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
- package/adapters/shared/resolve-farmslot-ports.sh +0 -7
- package/docs/CLI-SPEC.md +0 -1102
- package/docs/MENTAL-MODEL.md +0 -295
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- 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.
|
|
8
|
+
- Extension webpack watchers now launch in their own process group, so they remain alive for incremental rebuilds after the initial `launch --build` command exits.
|
|
9
|
+
- 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.
|
|
10
|
+
- 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.
|
|
11
|
+
- 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.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- 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.
|
|
16
|
+
- Mobile fixture setup now requires the current multichain account-tree path, and Core signing requires the canonical checkout wallet fixture.
|
|
17
|
+
|
|
18
|
+
### Removed
|
|
19
|
+
|
|
20
|
+
- 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.
|
|
21
|
+
- Removed the historical CLI migration/spec documents; current behavior is documented in the README, cheatsheet, architecture, and human QA checklist.
|
|
22
|
+
|
|
23
|
+
## 0.17.1 - 2026-07-14
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- 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.
|
|
28
|
+
- 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.
|
|
29
|
+
- 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.
|
|
30
|
+
- 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.
|
|
31
|
+
- 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.
|
|
32
|
+
|
|
33
|
+
## 0.17.0 - 2026-07-14
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- 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.
|
|
38
|
+
- `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.
|
|
39
|
+
- 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.
|
|
40
|
+
- `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.
|
|
41
|
+
- `actions <query>` now searches action names, categories, fields, and descriptions with typo tolerance, while single-action detail includes compact related-action names.
|
|
42
|
+
|
|
43
|
+
### Changed
|
|
44
|
+
|
|
45
|
+
- Action categories now group protocol primitives into useful `assertion`, `control`, `evidence`, and `runtime` domains instead of exposing one category per unnamespaced action.
|
|
46
|
+
- 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`.
|
|
47
|
+
- `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.
|
|
48
|
+
|
|
5
49
|
## 0.16.0 - 2026-07-14
|
|
6
50
|
|
|
7
51
|
### Added
|
package/README.md
CHANGED
|
@@ -1,178 +1,131 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mm-harness
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
4
5
|
|
|
5
|
-
|
|
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.
|
|
6
|
+
It has two jobs:
|
|
14
7
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
PROVE run · doctor run recipes, check readiness
|
|
19
|
-
RUNTIME OVERLAY install · verify · cleanup manage the per-checkout runtime overlay
|
|
20
|
-
```
|
|
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.
|
|
21
11
|
|
|
22
|
-
|
|
23
|
-
|
|
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.
|
|
12
|
+
The generic graph engine lives in `@farmslot/recipe-harness`. This package owns
|
|
13
|
+
only MetaMask-specific actions and runtime control.
|
|
27
14
|
|
|
28
|
-
|
|
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.
|
|
15
|
+
## Install
|
|
32
16
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
17
|
+
```bash
|
|
18
|
+
npm install -g @deeeed/metamask-harness@latest
|
|
19
|
+
mm-harness --version
|
|
20
|
+
mm-harness doctor
|
|
21
|
+
```
|
|
37
22
|
|
|
38
|
-
|
|
39
|
-
logic.
|
|
23
|
+
Use `mm-harness update` to install the latest published version.
|
|
40
24
|
|
|
41
|
-
##
|
|
25
|
+
## Daily workflow
|
|
42
26
|
|
|
43
|
-
|
|
27
|
+
From a MetaMask checkout:
|
|
44
28
|
|
|
45
29
|
```bash
|
|
46
|
-
#
|
|
47
|
-
mm-harness launch
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
30
|
+
# Extension
|
|
31
|
+
mm-harness launch # starts/reuses Chrome and the incremental watcher
|
|
32
|
+
mm-harness launch --sidepanel # open the sidepanel surface
|
|
33
|
+
|
|
34
|
+
# Mobile
|
|
35
|
+
mm-harness launch ios
|
|
36
|
+
mm-harness launch android
|
|
37
|
+
|
|
38
|
+
# Any product
|
|
39
|
+
mm-harness status
|
|
40
|
+
mm-harness logs
|
|
41
|
+
mm-harness debug
|
|
42
|
+
mm-harness fixtures set
|
|
51
43
|
```
|
|
52
44
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
adjacent bugs without claiming the recipe or current change caused them. Core
|
|
58
|
-
is headless, so this is N/A there.
|
|
45
|
+
`launch` installs the checkout-local runtime overlay when needed. On Extension,
|
|
46
|
+
the normal launch keeps `yarn start` running after the command exits. Source
|
|
47
|
+
edits compile incrementally into the isolated runtime; refresh the active page or
|
|
48
|
+
sidepanel to see them. Use `launch --build` only when a clean rebuild is needed.
|
|
59
49
|
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
`debug` opens DevTools for the active Extension target in that checkout's Chrome
|
|
51
|
+
profile. Mobile opens the React Native developer surface.
|
|
62
52
|
|
|
63
|
-
|
|
53
|
+
Filter logs when needed:
|
|
64
54
|
|
|
65
|
-
```
|
|
66
|
-
mm-harness
|
|
67
|
-
mm-harness
|
|
55
|
+
```bash
|
|
56
|
+
mm-harness logs --source extension # Extension console
|
|
57
|
+
mm-harness logs --source dapp # active dapp console
|
|
58
|
+
mm-harness logs --source webpack # Extension compiler
|
|
59
|
+
mm-harness logs --source app # Mobile app events
|
|
60
|
+
mm-harness logs --source metro # Mobile bundler
|
|
68
61
|
```
|
|
69
62
|
|
|
70
|
-
|
|
71
|
-
manifests — the content). src/ = what EXECUTES them (TypeScript CLI/engine glue,
|
|
72
|
-
typed adapters). adapters/ = what CONTROLS the app and its instances (start,
|
|
73
|
-
windows, wallet state, health, ports, parallel).
|
|
74
|
-
|
|
75
|
-
## Useful commands
|
|
63
|
+
## Build and run proof
|
|
76
64
|
|
|
77
65
|
```bash
|
|
78
|
-
|
|
79
|
-
mm-harness actions --
|
|
80
|
-
mm-harness
|
|
81
|
-
mm-harness
|
|
82
|
-
mm-harness call unlock --adapter extension # run one action via the real engine path
|
|
83
|
-
mm-harness flows --json # reusable library flows, with provenance
|
|
84
|
-
|
|
85
|
-
# Readiness + proof
|
|
86
|
-
mm-harness doctor # read-only readiness check (no launch)
|
|
87
|
-
mm-harness doctor --fix # heal the overlay without launching
|
|
88
|
-
mm-harness run recipe.json --plan --adapter mobile # validate + plan, touch nothing
|
|
89
|
-
|
|
90
|
-
# Daily loop (run from inside a checkout)
|
|
91
|
-
mm-harness launch ios # launch (mobile: ios | android)
|
|
92
|
-
mm-harness logs # tail Metro/webpack + app logs
|
|
93
|
-
mm-harness debug # open the debug console
|
|
94
|
-
mm-harness fixtures sync # sync fixtures; `fixtures set` seeds the wallet
|
|
95
|
-
|
|
96
|
-
# Keep the install current
|
|
97
|
-
mm-harness update # upgrade to the published latest (prints old → new)
|
|
98
|
-
mm-harness update --check --json # report only — { current, latest, updateAvailable }
|
|
99
|
-
```
|
|
66
|
+
mm-harness actions positions
|
|
67
|
+
mm-harness actions --action metamask.wallet.ensure_unlocked
|
|
68
|
+
mm-harness call metamask.wallet.ensure_unlocked
|
|
69
|
+
mm-harness flows
|
|
100
70
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
never blocks and never auto-updates. Silence it with `MM_HARNESS_NO_UPDATE_CHECK=1`
|
|
104
|
-
(auto-off in CI).
|
|
105
|
-
|
|
106
|
-
Set `RECIPE_LOG_UI=compact|full|quiet` (default `compact`) and `RECIPE_LOG_EVENTS=10`
|
|
107
|
-
to tune the compact log view during launch and Metro/webpack startup.
|
|
108
|
-
|
|
109
|
-
## Layout
|
|
110
|
-
|
|
111
|
-
```text
|
|
112
|
-
bin/ the mm-harness entrypoint
|
|
113
|
-
src/ what EXECUTES recipes: TypeScript CLI/engine glue +
|
|
114
|
-
typed per-adapter logic (src/adapters/)
|
|
115
|
-
adapters/ what CONTROLS the app and its instances: launch/live/
|
|
116
|
-
watch/windows/wallet state/health/inject/cleanup per
|
|
117
|
-
platform, shared glue, porcelain, overlay payload +
|
|
118
|
-
manifest.json
|
|
119
|
-
library/ what recipes ARE: recipes/ (recipe JSONs), actions/
|
|
120
|
-
(per-platform implementations), manifests/ (capability)
|
|
121
|
-
scripts/ dev tooling (yarn check, local farmslot link, e2e
|
|
122
|
-
validation, adapter-surface-doctor)
|
|
123
|
-
docs/ details when this README is not enough
|
|
71
|
+
mm-harness run path/to/recipe.json --plan
|
|
72
|
+
mm-harness run path/to/recipe.json --artifacts-dir temp/recipe-proof
|
|
124
73
|
```
|
|
125
74
|
|
|
126
|
-
|
|
127
|
-
|
|
75
|
+
`actions` is the executable vocabulary. `call` exercises one action. `flows`
|
|
76
|
+
lists reusable parameterized behavior from personal, team, and bundled recipe
|
|
77
|
+
libraries. A recipe connects those capabilities into a deterministic proof.
|
|
78
|
+
|
|
79
|
+
A run prints its findings, diagnostics, and absolute evidence paths. The artifact
|
|
80
|
+
directory contains the executed recipe, summary, trace, diagnostics when
|
|
81
|
+
applicable, screenshots/logs, and `artifact-manifest.json`. Core is headless, so
|
|
82
|
+
browser/device diagnostics do not apply.
|
|
128
83
|
|
|
129
|
-
|
|
84
|
+
Use `mm-harness last --json` to resume after interruption without repeating a
|
|
85
|
+
side-effecting command.
|
|
130
86
|
|
|
131
|
-
|
|
87
|
+
## Team recipe libraries
|
|
132
88
|
|
|
133
|
-
|
|
134
|
-
- `@farmslot/recipe-harness/runtime/log-analysis` — bundle-log boundaries, unresolved-module scoping
|
|
135
|
-
- `@farmslot/recipe-harness/runtime/metro-probe` — Metro `/status` reachability
|
|
89
|
+
Add a named shared directory with `RECIPE_LIBRARY_PATH`:
|
|
136
90
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
`@farmslot/recipe-harness` before bumping the dependency here.
|
|
91
|
+
```bash
|
|
92
|
+
export RECIPE_LIBRARY_PATH="team=$HOME/shared-library/team-recipes"
|
|
93
|
+
mm-harness flows
|
|
94
|
+
```
|
|
142
95
|
|
|
143
|
-
|
|
96
|
+
Team flows can wrap repeated product behavior while each checklist or task keeps
|
|
97
|
+
its own acceptance criteria. See [recipe libraries](docs/recipe-libraries.md).
|
|
144
98
|
|
|
145
|
-
|
|
146
|
-
harness. To run a **dev** checkout with active pre-release changes alongside it,
|
|
147
|
-
set `MM_HARNESS_BIN` to that checkout's `bin/mm-harness`:
|
|
99
|
+
## Health and recovery
|
|
148
100
|
|
|
149
101
|
```bash
|
|
150
|
-
|
|
151
|
-
|
|
102
|
+
mm-harness doctor # inspect; does not launch
|
|
103
|
+
mm-harness doctor --fix # repair local harness setup; does not launch
|
|
104
|
+
mm-harness verify # verify the installed runtime overlay
|
|
105
|
+
mm-harness cleanup # remove the runtime overlay
|
|
152
106
|
```
|
|
153
107
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
no `PATH` collision — dev is reached only through `MM_HARNESS_BIN` (or a shell
|
|
158
|
-
alias). A per-farm environment can export it so the dev farm dispatches through the
|
|
159
|
-
dev harness while the prod farm leaves it unset.
|
|
108
|
+
Human output gives a next command. `--json` gives stable machine-readable errors
|
|
109
|
+
and keeps stdout free of decoration. `--json-stream` emits line-delimited progress
|
|
110
|
+
for long recipe runs.
|
|
160
111
|
|
|
161
|
-
##
|
|
112
|
+
## Development
|
|
113
|
+
|
|
114
|
+
Use a source checkout without replacing the global install:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
export MM_HARNESS_BIN=/path/to/metamask-harness/bin/mm-harness
|
|
118
|
+
mm-harness --version
|
|
119
|
+
```
|
|
162
120
|
|
|
163
|
-
|
|
164
|
-
falls back to detached `nohup` where possible.
|
|
165
|
-
- Mobile may inject a local development bridge/HUD into older checkouts. Do not
|
|
166
|
-
commit those product patches.
|
|
167
|
-
- Extension does not patch product source; it drives `dist/chrome` through Chrome
|
|
168
|
-
CDP.
|
|
121
|
+
Unset `MM_HARNESS_BIN` to return to the published install.
|
|
169
122
|
|
|
170
|
-
|
|
123
|
+
Validate changes with:
|
|
171
124
|
|
|
172
125
|
```bash
|
|
173
126
|
yarn check
|
|
174
|
-
bash -n bin/mm-harness adapters/porcelain/{metamask-recipe,mm-recipe,mme-recipe} adapters/{mobile,extension,core}/*.sh
|
|
175
|
-
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
|
|
176
127
|
```
|
|
177
128
|
|
|
178
|
-
|
|
129
|
+
Current references: [cheatsheet](docs/CHEATSHEET.md), [architecture](docs/architecture.md),
|
|
130
|
+
[recipe libraries](docs/recipe-libraries.md), [human QA](docs/CLI-ERGONOMICS-HUMAN-QA.md),
|
|
131
|
+
and the checked [video demo validation](docs/VIDEO-DEMO-VALIDATION.md).
|
package/adapters/core/cleanup.sh
CHANGED
|
File without changes
|
package/adapters/core/inject.sh
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -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> [--log <file>]
|
|
11
|
+
// console-tail.mjs --cdp-port <port> [--extension-log <file>] [--dapp-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,9 +19,10 @@
|
|
|
19
19
|
'use strict';
|
|
20
20
|
|
|
21
21
|
import { appendFileSync } from 'node:fs';
|
|
22
|
+
import path from 'node:path';
|
|
22
23
|
|
|
23
24
|
function parseArgs(argv) {
|
|
24
|
-
const args = { cdpPort: null,
|
|
25
|
+
const args = { cdpPort: null, extensionLog: null, dappLog: null };
|
|
25
26
|
for (let i = 0; i < argv.length; i += 1) {
|
|
26
27
|
const arg = argv[i];
|
|
27
28
|
switch (arg) {
|
|
@@ -29,12 +30,16 @@ function parseArgs(argv) {
|
|
|
29
30
|
args.cdpPort = argv[++i];
|
|
30
31
|
break;
|
|
31
32
|
case '--log':
|
|
32
|
-
|
|
33
|
+
case '--extension-log':
|
|
34
|
+
args.extensionLog = argv[++i];
|
|
35
|
+
break;
|
|
36
|
+
case '--dapp-log':
|
|
37
|
+
args.dappLog = argv[++i];
|
|
33
38
|
break;
|
|
34
39
|
case '-h':
|
|
35
40
|
case '--help':
|
|
36
41
|
process.stdout.write(
|
|
37
|
-
'Usage: console-tail.mjs --cdp-port <port> [--log <file>]\n',
|
|
42
|
+
'Usage: console-tail.mjs --cdp-port <port> [--extension-log <file>] [--dapp-log <file>]\n',
|
|
38
43
|
);
|
|
39
44
|
process.exit(0);
|
|
40
45
|
break;
|
|
@@ -51,6 +56,9 @@ if (!Number.isInteger(cdpPort) || cdpPort <= 0) {
|
|
|
51
56
|
throw new Error('console-tail requires a numeric --cdp-port.');
|
|
52
57
|
}
|
|
53
58
|
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
|
+
}
|
|
54
62
|
|
|
55
63
|
if (typeof WebSocket === 'undefined') {
|
|
56
64
|
throw new Error('console-tail requires Node 22+ with a global WebSocket.');
|
|
@@ -65,9 +73,9 @@ const cooldown = new Map();
|
|
|
65
73
|
// Writes formatted console-event strings (never raw HTTP response bodies) to
|
|
66
74
|
// stdout and optionally to a caller-supplied log file. The source is the local
|
|
67
75
|
// Chrome CDP WebSocket (127.0.0.1:<cdpPort>) — a trusted-local endpoint.
|
|
68
|
-
function out(line) {
|
|
76
|
+
function out(line, destination = null) {
|
|
69
77
|
process.stdout.write(`${line}\n`);
|
|
70
|
-
if (
|
|
78
|
+
if (destination) appendFileSync(destination, `${line}\n`);
|
|
71
79
|
}
|
|
72
80
|
|
|
73
81
|
function stamp() {
|
|
@@ -95,6 +103,22 @@ function isExtensionTarget(target) {
|
|
|
95
103
|
return String(target.url || '').startsWith('chrome-extension://');
|
|
96
104
|
}
|
|
97
105
|
|
|
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
|
+
|
|
98
122
|
function previewArg(arg) {
|
|
99
123
|
if (!arg || typeof arg !== 'object') return String(arg);
|
|
100
124
|
if ('value' in arg && arg.value !== undefined) {
|
|
@@ -108,29 +132,29 @@ function previewArg(arg) {
|
|
|
108
132
|
return arg.subtype || arg.type || '?';
|
|
109
133
|
}
|
|
110
134
|
|
|
111
|
-
function emitConsole(label, params) {
|
|
135
|
+
function emitConsole(label, params, destination) {
|
|
112
136
|
const level = String(params.type || 'log').toUpperCase();
|
|
113
137
|
const text = (params.args || []).map(previewArg).join(' ');
|
|
114
|
-
out(`${stamp()} [${label}] ${level} ${text}
|
|
138
|
+
out(`${stamp()} [${label}] ${level} ${text}`, destination);
|
|
115
139
|
}
|
|
116
140
|
|
|
117
|
-
function emitException(label, params) {
|
|
141
|
+
function emitException(label, params, destination) {
|
|
118
142
|
const d = params.exceptionDetails || {};
|
|
119
143
|
const text =
|
|
120
144
|
d.exception?.description ||
|
|
121
145
|
d.exception?.value ||
|
|
122
146
|
d.text ||
|
|
123
147
|
'uncaught exception';
|
|
124
|
-
out(`${stamp()} [${label}] EXCEPTION ${text}
|
|
148
|
+
out(`${stamp()} [${label}] EXCEPTION ${text}`, destination);
|
|
125
149
|
}
|
|
126
150
|
|
|
127
|
-
function emitLogEntry(label, params) {
|
|
151
|
+
function emitLogEntry(label, params, destination) {
|
|
128
152
|
const e = params.entry || {};
|
|
129
153
|
const level = String(e.level || 'log').toUpperCase();
|
|
130
|
-
out(`${stamp()} [${label}] ${level} ${e.text || ''}
|
|
154
|
+
out(`${stamp()} [${label}] ${level} ${e.text || ''}`, destination);
|
|
131
155
|
}
|
|
132
156
|
|
|
133
|
-
function handleMessage(label, data) {
|
|
157
|
+
function handleMessage(label, destination, data) {
|
|
134
158
|
let message;
|
|
135
159
|
try {
|
|
136
160
|
message = JSON.parse(data);
|
|
@@ -141,13 +165,13 @@ function handleMessage(label, data) {
|
|
|
141
165
|
if (message.id != null) return; // command ack, not an event
|
|
142
166
|
switch (message.method) {
|
|
143
167
|
case 'Runtime.consoleAPICalled':
|
|
144
|
-
emitConsole(label, message.params || {});
|
|
168
|
+
emitConsole(label, message.params || {}, destination);
|
|
145
169
|
break;
|
|
146
170
|
case 'Runtime.exceptionThrown':
|
|
147
|
-
emitException(label, message.params || {});
|
|
171
|
+
emitException(label, message.params || {}, destination);
|
|
148
172
|
break;
|
|
149
173
|
case 'Log.entryAdded':
|
|
150
|
-
emitLogEntry(label, message.params || {});
|
|
174
|
+
emitLogEntry(label, message.params || {}, destination);
|
|
151
175
|
break;
|
|
152
176
|
default:
|
|
153
177
|
break;
|
|
@@ -155,9 +179,11 @@ function handleMessage(label, data) {
|
|
|
155
179
|
}
|
|
156
180
|
|
|
157
181
|
function attach(target) {
|
|
158
|
-
const
|
|
182
|
+
const stream = targetStream(target);
|
|
183
|
+
if (!stream) return;
|
|
184
|
+
const label = `${stream.kind}:${shortLabel(target)}`;
|
|
159
185
|
const ws = new WebSocket(target.webSocketDebuggerUrl);
|
|
160
|
-
const entry = { ws, label, opened: false };
|
|
186
|
+
const entry = { ws, label, destination: stream.log, opened: false };
|
|
161
187
|
attached.set(target.id, entry);
|
|
162
188
|
|
|
163
189
|
ws.addEventListener('open', () => {
|
|
@@ -166,16 +192,16 @@ function attach(target) {
|
|
|
166
192
|
for (const method of ['Runtime.enable', 'Log.enable']) {
|
|
167
193
|
ws.send(JSON.stringify({ id: ++id, method, params: {} }));
|
|
168
194
|
}
|
|
169
|
-
out(`${stamp()} [${label}] [attached]
|
|
195
|
+
out(`${stamp()} [${label}] [attached]`, stream.log);
|
|
170
196
|
});
|
|
171
197
|
ws.addEventListener('message', (event) => {
|
|
172
|
-
handleMessage(label, typeof event.data === 'string' ? event.data : '');
|
|
198
|
+
handleMessage(label, stream.log, typeof event.data === 'string' ? event.data : '');
|
|
173
199
|
});
|
|
174
200
|
const drop = () => {
|
|
175
201
|
if (attached.get(target.id) !== entry) return;
|
|
176
202
|
attached.delete(target.id);
|
|
177
203
|
if (entry.opened) {
|
|
178
|
-
out(`${stamp()} [${label}] [reconnect] target detached; will re-attach
|
|
204
|
+
out(`${stamp()} [${label}] [reconnect] target detached; will re-attach`, stream.log);
|
|
179
205
|
} else {
|
|
180
206
|
// Never connected (target gone / not debuggable yet): back off so a dead
|
|
181
207
|
// target is not retried — and logged — on every 2s poll.
|
|
@@ -196,8 +222,9 @@ async function poll() {
|
|
|
196
222
|
// CDP not reachable this tick (browser starting/restarting); retry next poll.
|
|
197
223
|
return;
|
|
198
224
|
}
|
|
199
|
-
|
|
200
|
-
|
|
225
|
+
const targetList = Array.isArray(targets) ? targets : [];
|
|
226
|
+
for (const target of targetList) {
|
|
227
|
+
if (!targetStream(target)) continue;
|
|
201
228
|
if (attached.has(target.id)) continue;
|
|
202
229
|
const until = cooldown.get(target.id);
|
|
203
230
|
if (until !== undefined) {
|
|
@@ -226,6 +253,6 @@ function shutdown() {
|
|
|
226
253
|
process.on('SIGINT', shutdown);
|
|
227
254
|
process.on('SIGTERM', shutdown);
|
|
228
255
|
|
|
229
|
-
out(`${stamp()} [console-tail] streaming extension
|
|
256
|
+
out(`${stamp()} [console-tail] streaming extension and dapp consoles via cdp ${cdpPort}`);
|
|
230
257
|
await poll();
|
|
231
258
|
setInterval(poll, 2000);
|
|
File without changes
|
|
@@ -94,16 +94,16 @@ copyDir(path.join(runnerDir, 'library/recipes'), path.join(harnessDir, 'runner/r
|
|
|
94
94
|
copyFile(path.join(runnerDir, 'library/library.json'), path.join(harnessDir, 'runner/library.json'));
|
|
95
95
|
fs.mkdirSync(path.join(harnessDir, 'runner/flows'), { recursive: true });
|
|
96
96
|
copyDir(path.join(runnerDir, 'library/flows'), path.join(harnessDir, 'runner/flows'));
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
// consumers are unaffected by runner-repo layout changes.
|
|
97
|
+
// The installed overlay has a flat scripts/ runtime API independent of the
|
|
98
|
+
// package's source layout.
|
|
100
99
|
copyFile(path.join(runnerDir, 'adapters/extension/launch-browser.cjs'), path.join(harnessDir, 'scripts/launch-browser.cjs'));
|
|
101
|
-
// installed back-compat alias: callers of the pre-rename installed name
|
|
102
|
-
copyFile(path.join(runnerDir, 'adapters/extension/launch-browser.cjs'), path.join(harnessDir, 'scripts/launch-chrome-detached.cjs'));
|
|
103
100
|
copyFile(path.join(runnerDir, 'adapters/extension/console-tail.mjs'), path.join(harnessDir, 'scripts/console-tail.mjs'));
|
|
104
101
|
copyFile(path.join(runnerDir, 'adapters/extension/launch.sh'), path.join(harnessDir, 'scripts/launch.sh'));
|
|
105
102
|
copyFile(path.join(runnerDir, 'adapters/extension/live.sh'), path.join(harnessDir, 'scripts/live.sh'));
|
|
106
103
|
copyFile(path.join(runnerDir, 'adapters/extension/start-watch.sh'), path.join(harnessDir, 'scripts/start-watch.sh'));
|
|
104
|
+
copyFile(path.join(runnerDir, 'adapters/extension/launch-webpack.cjs'), path.join(harnessDir, 'scripts/launch-webpack.cjs'));
|
|
105
|
+
copyFile(path.join(runnerDir, 'adapters/extension/sync-webpack-dist.cjs'), path.join(harnessDir, 'scripts/sync-webpack-dist.cjs'));
|
|
106
|
+
copyFile(path.join(runnerDir, 'adapters/extension/stamp-runtime-title.cjs'), path.join(harnessDir, 'scripts/stamp-runtime-title.cjs'));
|
|
107
107
|
copyFile(path.join(runnerDir, 'adapters/extension/stop-viewers.sh'), path.join(harnessDir, 'scripts/stop-viewers.sh'));
|
|
108
108
|
copyFile(path.join(runnerDir, 'adapters/extension/snapshot-dist.sh'), path.join(harnessDir, 'scripts/snapshot-dist.sh'));
|
|
109
109
|
copyFile(path.join(runnerDir, 'adapters/extension/pin-remote-flags.cjs'), path.join(harnessDir, 'scripts/pin-remote-flags.cjs'));
|
|
@@ -120,10 +120,6 @@ copyFile(path.join(runnerDir, 'adapters/shared/activate-repo-node.sh'), path.joi
|
|
|
120
120
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports.sh'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports.sh'));
|
|
121
121
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports.mjs'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports.mjs'));
|
|
122
122
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports-core.mjs'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports-core.mjs'));
|
|
123
|
-
// Back-compat shims (one release) so an overlay consumer using the old leaf names still resolves.
|
|
124
|
-
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.sh'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.sh'));
|
|
125
|
-
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.mjs'));
|
|
126
|
-
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports-core.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports-core.mjs'));
|
|
127
123
|
copyFile(path.join(runnerDir, 'adapters/shared/path-defaults.json'), path.join(harnessDir, 'scripts/lib/path-defaults.json'));
|
|
128
124
|
copyFile(path.join(runnerDir, 'adapters/shared/json-field.sh'), path.join(harnessDir, 'scripts/lib/json-field.sh'));
|
|
129
125
|
copyFile(path.join(runnerDir, 'adapters/shared/tmux-session.sh'), path.join(harnessDir, 'scripts/lib/tmux-session.sh'));
|
|
File without changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawn } = require('node:child_process');
|
|
5
|
+
const fs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
|
|
8
|
+
const args = {};
|
|
9
|
+
for (let index = 2; index < process.argv.length; index += 1) {
|
|
10
|
+
const key = process.argv[index];
|
|
11
|
+
if (key === '--help' || key === '-h') {
|
|
12
|
+
process.stdout.write('Usage: launch-webpack.cjs --target <path> --log <path> --pid-file <path> --sync-script <path> --sync-pid-file <path> --sync-log <path> --dist <path> --runtime-dist <path> [--port <port>] [--path-prefix <path>]\n');
|
|
13
|
+
process.exit(0);
|
|
14
|
+
}
|
|
15
|
+
if (!key.startsWith('--') || index + 1 >= process.argv.length) throw new Error(`invalid argument: ${key}`);
|
|
16
|
+
args[key.slice(2)] = process.argv[++index];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function main() {
|
|
20
|
+
for (const required of ['target', 'log', 'pid-file', 'sync-script', 'sync-pid-file', 'sync-log', 'dist', 'runtime-dist']) {
|
|
21
|
+
if (!args[required]) throw new Error(`--${required} is required`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const target = path.resolve(args.target);
|
|
25
|
+
const log = path.resolve(args.log);
|
|
26
|
+
const pidFile = path.resolve(args['pid-file']);
|
|
27
|
+
const syncScript = path.resolve(args['sync-script']);
|
|
28
|
+
const syncPidFile = path.resolve(args['sync-pid-file']);
|
|
29
|
+
const syncLog = path.resolve(args['sync-log']);
|
|
30
|
+
fs.mkdirSync(path.dirname(log), { recursive: true });
|
|
31
|
+
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
|
|
32
|
+
fs.mkdirSync(path.dirname(syncLog), { recursive: true });
|
|
33
|
+
if (!fs.existsSync(syncScript)) throw new Error(`sync script not found: ${syncScript}`);
|
|
34
|
+
const logFd = fs.openSync(log, 'a');
|
|
35
|
+
const syncLogFd = fs.openSync(syncLog, 'a');
|
|
36
|
+
const env = { ...process.env };
|
|
37
|
+
if (args.port) env.PORT = String(args.port);
|
|
38
|
+
if (args['path-prefix']) env.PATH = `${args['path-prefix']}${env.PATH || ''}`;
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const child = spawn('yarn', ['start'], {
|
|
42
|
+
cwd: target,
|
|
43
|
+
detached: true,
|
|
44
|
+
env,
|
|
45
|
+
stdio: ['ignore', logFd, logFd],
|
|
46
|
+
});
|
|
47
|
+
await new Promise((resolve, reject) => {
|
|
48
|
+
child.once('spawn', resolve);
|
|
49
|
+
child.once('error', reject);
|
|
50
|
+
});
|
|
51
|
+
fs.writeFileSync(pidFile, `${child.pid}\n`);
|
|
52
|
+
const syncChild = spawn(process.execPath, [
|
|
53
|
+
syncScript,
|
|
54
|
+
'--watcher-pid', String(child.pid),
|
|
55
|
+
'--watch-log', log,
|
|
56
|
+
'--target', target,
|
|
57
|
+
'--dist', path.resolve(args.dist),
|
|
58
|
+
'--runtime-dist', path.resolve(args['runtime-dist']),
|
|
59
|
+
'--pid-file', syncPidFile,
|
|
60
|
+
], {
|
|
61
|
+
cwd: target,
|
|
62
|
+
detached: true,
|
|
63
|
+
env,
|
|
64
|
+
stdio: ['ignore', syncLogFd, syncLogFd],
|
|
65
|
+
});
|
|
66
|
+
await new Promise((resolve, reject) => {
|
|
67
|
+
syncChild.once('spawn', resolve);
|
|
68
|
+
syncChild.once('error', reject);
|
|
69
|
+
});
|
|
70
|
+
fs.writeFileSync(syncPidFile, `${syncChild.pid}\n`);
|
|
71
|
+
process.stdout.write(`${child.pid}\n`);
|
|
72
|
+
syncChild.unref();
|
|
73
|
+
child.unref();
|
|
74
|
+
} finally {
|
|
75
|
+
fs.closeSync(logFd);
|
|
76
|
+
fs.closeSync(syncLogFd);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
main().catch((error) => {
|
|
81
|
+
process.stderr.write(`launch-webpack: ${error.message}\n`);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
});
|