@deeeed/metamask-harness 0.4.0 → 0.5.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 (52) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/adapters/core/surface.js +53 -0
  3. package/dist/adapters/extension/ensure-ready.js +109 -0
  4. package/dist/adapters/extension/extension-id.js +62 -0
  5. package/dist/adapters/extension/runtime-decision.js +305 -0
  6. package/dist/adapters/extension/runtime.js +324 -0
  7. package/dist/adapters/extension/surface.js +69 -0
  8. package/dist/adapters/mobile/deps-markers.js +22 -0
  9. package/dist/adapters/mobile/prepare.js +146 -0
  10. package/dist/adapters/mobile/provision.js +465 -0
  11. package/dist/adapters/mobile/runtime-decision.js +315 -0
  12. package/dist/adapters/mobile/surface.js +54 -0
  13. package/dist/adapters/slot-ports.js +146 -0
  14. package/dist/adapters/surface.js +14 -0
  15. package/dist/adapters.js +485 -0
  16. package/dist/cli-color.js +79 -0
  17. package/dist/cli-commands.js +224 -0
  18. package/dist/cli-version.js +111 -0
  19. package/dist/cli.js +1571 -0
  20. package/dist/commands/debug.js +56 -0
  21. package/dist/commands/fixtures.js +153 -0
  22. package/dist/commands/launch.js +325 -0
  23. package/dist/commands/logs.js +73 -0
  24. package/dist/commands/shared.js +157 -0
  25. package/dist/commands/update.js +243 -0
  26. package/dist/completions-cache.js +53 -0
  27. package/dist/doctor.js +169 -0
  28. package/dist/harness.js +627 -0
  29. package/dist/heal-bounds.js +120 -0
  30. package/dist/index.js +25 -0
  31. package/dist/leaf-invoke.js +19 -0
  32. package/dist/live-adapter-contract.js +240 -0
  33. package/dist/manifest.js +37 -0
  34. package/dist/mm-harness-cli.js +521 -0
  35. package/dist/paths.js +179 -0
  36. package/dist/progress.js +94 -0
  37. package/dist/recording-target.js +133 -0
  38. package/dist/run-recording.js +271 -0
  39. package/dist/runner.js +88 -0
  40. package/dist/types.js +0 -0
  41. package/docs/CLI-SPEC.md +26 -3
  42. package/package.json +5 -1
  43. package/src/adapters/core/surface.ts +15 -0
  44. package/src/adapters/extension/surface.ts +20 -3
  45. package/src/adapters/mobile/provision.ts +594 -0
  46. package/src/adapters/mobile/surface.ts +16 -4
  47. package/src/adapters/slot-ports.ts +1 -1
  48. package/src/adapters/surface.ts +35 -0
  49. package/src/cli-commands.ts +1 -1
  50. package/src/cli.ts +149 -6
  51. package/src/harness.ts +140 -3
  52. package/src/mm-harness-cli.ts +52 -5
package/dist/runner.js ADDED
@@ -0,0 +1,88 @@
1
+ import { execSync } from "node:child_process";
2
+ import { createMetaMaskAdapters, createMetaMaskUiTransport } from "./adapters.js";
3
+ import { loadMetaMaskExtensionActionManifest, loadMetaMaskMobileActionManifest } from "./manifest.js";
4
+ import { createMetaMaskRecordingTargetProvider } from "./recording-target.js";
5
+ import { importRecipeHarness, importRecipeHarnessRuntimeCdp, importRecipeHarnessRuntimeReactNativeBridge, runnerDir } from "./paths.js";
6
+ async function createMetaMaskMobileRunner(options = {}) {
7
+ return createMetaMaskRunner(
8
+ "mobile",
9
+ options.actionManifest ?? loadMetaMaskMobileActionManifest()
10
+ );
11
+ }
12
+ async function createMetaMaskExtensionRunner(options = {}) {
13
+ return createMetaMaskRunner(
14
+ "extension",
15
+ options.actionManifest ?? loadMetaMaskExtensionActionManifest()
16
+ );
17
+ }
18
+ async function createMetaMaskRunner(adapter, actionManifest, options = {}) {
19
+ const {
20
+ createRecipeRunner,
21
+ createStandardCoreAdapters,
22
+ createStandardUiAdapters
23
+ } = await importRecipeHarness();
24
+ const { createCdpWebUiTransport } = await importRecipeHarnessRuntimeCdp();
25
+ const { createReactNativeBridgeUiTransport } = await importRecipeHarnessRuntimeReactNativeBridge();
26
+ const actions = [
27
+ ...actionManifest.supported_official_actions,
28
+ ...(actionManifest.custom_actions ?? []).map((entry) => entry.name)
29
+ ];
30
+ const declaredActions = new Set(actions);
31
+ const core = createStandardCoreAdapters({ actions });
32
+ const projectOwnedOfficialActions = /* @__PURE__ */ new Set(["app.status"]);
33
+ const ui = createStandardUiAdapters({
34
+ actions: actions.filter((action) => !projectOwnedOfficialActions.has(action)),
35
+ transport: createMetaMaskUiTransport(adapter, {
36
+ createCdpWebUiTransport,
37
+ createReactNativeBridgeUiTransport
38
+ })
39
+ });
40
+ const existing = new Set([...core, ...ui].map((entry) => entry.action));
41
+ const custom = createMetaMaskAdapters(adapter).filter(
42
+ (entry) => declaredActions.has(entry.action) && !existing.has(entry.action)
43
+ );
44
+ const autoHudDisabled = process.env.METAMASK_RECIPE_AUTO_HUD === "0" || process.env.METAMASK_RECIPE_AUTO_HUD === "false";
45
+ const logger = options.quietStdout ? new console.Console(process.stderr) : console;
46
+ return createRecipeRunner({
47
+ actionManifest,
48
+ adapters: [...core, ...ui, ...custom],
49
+ logger,
50
+ recording: {
51
+ targetProvider: createMetaMaskRecordingTargetProvider(adapter)
52
+ },
53
+ runner: runnerProvenance(),
54
+ hud: autoHudDisabled ? false : {
55
+ enabled: true,
56
+ display: {
57
+ layout: "docked-bottom",
58
+ position: "bottom",
59
+ showTitle: false,
60
+ showDebug: false,
61
+ maxDetailLines: 2
62
+ }
63
+ }
64
+ });
65
+ }
66
+ function runnerProvenance() {
67
+ return {
68
+ source: runnerDir,
69
+ git_ref: runnerGitRef(),
70
+ name: "@metamask/recipe-runner"
71
+ };
72
+ }
73
+ function runnerGitRef() {
74
+ try {
75
+ return execSync("git rev-parse HEAD", {
76
+ cwd: runnerDir,
77
+ encoding: "utf8",
78
+ stdio: ["ignore", "pipe", "ignore"]
79
+ }).trim();
80
+ } catch {
81
+ return "unknown";
82
+ }
83
+ }
84
+ export {
85
+ createMetaMaskExtensionRunner,
86
+ createMetaMaskMobileRunner,
87
+ createMetaMaskRunner
88
+ };
package/dist/types.js ADDED
File without changes
package/docs/CLI-SPEC.md CHANGED
@@ -304,6 +304,30 @@ Recovery is silent in human mode. With `--json`: `"recovered": ["metro.restarted
304
304
  **Maps-to:** `sync` ← C:`sync`, D:`sync`,`update\|sync-runtime`; `set` ← C:`setup-wallet\|wallet-setup`,`unlock`,`setup:ios/android`(wallet half).
305
305
  **Extension `set`:** [GAP] no standalone path today — the first-run wallet is seeded by the legacy `mme-recipe up`; the end-state home is `fixtures set` (launch / `launch --verify` never seed).
306
306
 
307
+
308
+ ## `provision` (REAL → thin mobile setup path)
309
+
310
+ **Synopsis:** `mm-harness provision [runway ios] [flags]`
311
+
312
+ Installs the cached Runway iOS dev client onto a prepared mobile slot. It does not install JavaScript dependencies or start Metro; launch owns those at dispatch time. The legacy `install --runway` compatibility path maps to the same adapter surface; new farm callers should use `provision`.
313
+
314
+ | Flag | Type | Default | ENV (agent) | Audience | Description |
315
+ |---|---|---|---|---|---|
316
+ | `--adapter <mobile\|extension\|core>` | adapter | auto | — | agent | Mobile installs Runway; extension/core teach |
317
+ | `--target <path>` | path | cwd | — | agent | Slot checkout |
318
+ | `--platform <ios>` | string | `ios` | `PLATFORM` | agent | Provisioned platform |
319
+ | `--simulator <name\|udid>` / `--device <name\|udid>` | string | runtime context | `IOS_SIMULATOR` | agent | Target simulator |
320
+ | `--runtime <id>` | string | runtime context | `IOS_RUNTIME` | agent | iOS runtime for simulator creation |
321
+ | `--device-type <id>` | string | runtime context | `IOS_DEVICE_TYPE` | agent | Device type for simulator creation |
322
+ | `--branch <ref>` / `--default-branch <ref>` / `--run <id>` | string | git branch / main | — | agent | Runway artifact selection |
323
+ | `--cache-root <dir>` | path | XDG cache | — | agent | Shared artifact cache |
324
+ | `--slot <id>` | string | runtime context | — | agent | Farm slot id recorded in the provision baseline |
325
+ | `--watcher-port <port>` | number | runtime context | `WATCHER_PORT`/`METRO_PORT` | agent | Metro/watcher port carried through context and rerun hints |
326
+ | `--runtime-dir <dir>` | relative path | `temp/recipe/runtime` | `RECIPE_RUNTIME_DIR` | agent | Runtime directory containing `agentic-runtime.json` and receiving `runway-provision.json` |
327
+ | `--force` | bool | false | — | agent | Reinstall even when the bundle is already present |
328
+ | `--resolve-only` | bool | false | — | agent | Resolve run/revision/artifact metadata only; no simulator creation, cache write, download, install, or baseline write |
329
+ | `--json` | bool | false | — | agent | Machine envelope on stdout; progress/errors on stderr |
330
+
307
331
  ## `run` (ROUTES-NOW → main PROVE verb)
308
332
 
309
333
  **Synopsis:** `mm-harness run <recipe.json> [flags]` — validate (adapter-aware) + execute a recipe, write evidence.
@@ -714,9 +738,9 @@ a flag + default (`[ENV-GAP]`); internal/agent-only vars are KEEP-INTERNAL;
714
738
  **Implemented:** the `[ENV-GAP]` flags exist with resolution order **flag > env >
715
739
  config/default** (the flag sets the env var the porcelain reads, so it wins; an
716
740
  absent flag leaves the agent/CI env untouched):
717
- - `launch --device` (`IOS_SIMULATOR` for ios · `ADB_SERIAL`/`ANDROID_SERIAL`/`ANDROID_DEVICE` for android)
741
+ - `launch --device` (`IOS_SIMULATOR` for ios · `ADB_SERIAL`/`ANDROID_SERIAL`/`ANDROID_DEVICE` for android); `provision --device` is an alias for `--simulator`
718
742
  - `launch --cdp-port` (`CDP_PORT`/`RECIPE_CDP_PORT`) · `launch --watcher-port` (`WATCHER_PORT`/`METRO_PORT`/`RECIPE_WATCHER_PORT`) — numeric, teaching error otherwise
719
- - `launch --build` covers `MOBILE_PREFLIGHT_MODE` (tier) · `logs --full`/`--events` cover `RECIPE_LOG_UI`/`RECIPE_LOG_EVENTS`
743
+ - `launch --build` covers `MOBILE_PREFLIGHT_MODE` (tier) · `provision --slot --runtime-dir --watcher-port` accepts farm slot context · `logs --full`/`--events` cover `RECIPE_LOG_UI`/`RECIPE_LOG_EVENTS`
720
744
  - `fixtures set --fixture` (`RECIPE_WALLET_FIXTURE`) · `run`/`flows --library` (`RECIPE_LIBRARY_PATH`)
721
745
  - `MM_PASSWORD` stays flag-less by design — `fixtures set` reads the password FROM the fixture.
722
746
 
@@ -951,4 +975,3 @@ cheapest next action so the path converges rather than rebuilding from scratch):
951
975
  tab (closing strays, reopening if needed) and confirms it with the health probe.
952
976
  - **Health probe** — read-only liveness probe for the running extension over CDP;
953
977
  the final gate of convergence and the standalone answer for `verify`.
954
-
package/package.json CHANGED
@@ -1,11 +1,13 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"
7
7
  },
8
8
  "scripts": {
9
+ "build": "node scripts/build-dist.mjs",
10
+ "prepack": "npm run build",
9
11
  "dev:link-farmslot": "node scripts/link-local-farmslot.mjs",
10
12
  "check": "node scripts/check.mjs",
11
13
  "self-test": "bin/mm-harness self-test",
@@ -28,6 +30,7 @@
28
30
  "devDependencies": {
29
31
  "@eslint/js": "^9",
30
32
  "@types/node": "^22.0.0",
33
+ "esbuild": "0.28.1",
31
34
  "eslint": "^9",
32
35
  "globals": "^17.7.0",
33
36
  "typescript": "^5.6.0",
@@ -46,6 +49,7 @@
46
49
  },
47
50
  "files": [
48
51
  "bin",
52
+ "dist",
49
53
  "src",
50
54
  "adapters",
51
55
  "library",
@@ -8,6 +8,7 @@ import type {
8
8
  AdapterDevServerStop,
9
9
  AdapterLogSource,
10
10
  AdapterRuntimeStatus,
11
+ AdapterRunwayProvisionResult,
11
12
  AdapterSurface,
12
13
  } from '../surface.ts';
13
14
 
@@ -34,6 +35,20 @@ export const coreSurface: AdapterSurface = {
34
35
  };
35
36
  },
36
37
 
38
+ runwayProvision: {
39
+ async run(target, options): Promise<AdapterRunwayProvisionResult> {
40
+ return {
41
+ schemaVersion: 1,
42
+ command: 'provision',
43
+ adapter: 'core',
44
+ target: path.resolve(target),
45
+ status: 'fail',
46
+ exitCode: 2,
47
+ error: { code: 'UNSUPPORTED_ADAPTER', message: 'core is headless and has no mobile simulator to provision.', userAction: options.rerunCommand || 'mm-harness run <recipe> --adapter core' },
48
+ };
49
+ },
50
+ },
51
+
37
52
  devServer: {
38
53
  describe: () => 'no dev server (headless)',
39
54
  stop(): AdapterDevServerStop {
@@ -1,6 +1,7 @@
1
1
  // Extension surface: delegates to the existing extension readiness/port plumbing
2
2
  // and the webpack watcher stop.
3
3
  import { spawnSync } from 'node:child_process';
4
+ import path from 'node:path';
4
5
 
5
6
  import { recipeRuntimePath } from '../../paths.ts';
6
7
  import { resolveExtensionSlotPorts, stopExtensionWatcher } from '../slot-ports.ts';
@@ -9,6 +10,7 @@ import type {
9
10
  AdapterDevServerStop,
10
11
  AdapterLogSource,
11
12
  AdapterRuntimeStatus,
13
+ AdapterRunwayProvisionResult,
12
14
  AdapterSurface,
13
15
  } from '../surface.ts';
14
16
 
@@ -40,11 +42,28 @@ export const extensionSurface: AdapterSurface = {
40
42
  };
41
43
  },
42
44
 
45
+ runwayProvision: {
46
+ async run(target, options): Promise<AdapterRunwayProvisionResult> {
47
+ return {
48
+ schemaVersion: 1,
49
+ command: 'provision',
50
+ adapter: 'extension',
51
+ target: path.resolve(target),
52
+ status: 'fail',
53
+ exitCode: 2,
54
+ error: {
55
+ code: 'UNSUPPORTED_ADAPTER',
56
+ message: 'extension uses a browser runtime; Runway mobile provisioning is not supported.',
57
+ userAction: options.rerunCommand || 'mm-harness provision --adapter extension',
58
+ },
59
+ };
60
+ },
61
+ },
62
+
43
63
  devServer: {
44
64
  describe: () => 'webpack watcher',
45
65
  stop(target: string): AdapterDevServerStop {
46
66
  const signalled = stopExtensionWatcher(target);
47
- // Best-effort: close the webpack tail window this checkout's watcher owned.
48
67
  const port = process.env.WATCHER_PORT ?? 'default';
49
68
  spawnSync('tmux', ['kill-window', '-t', `webpack-${port}`], { stdio: 'ignore', timeout: 2000 });
50
69
  const summary = signalled > 0
@@ -55,8 +74,6 @@ export const extensionSurface: AdapterSurface = {
55
74
  },
56
75
 
57
76
  logSources(target: string): AdapterLogSource[] {
58
- // Most-relevant first: the live webpack log, the harness-owned watcher log,
59
- // then the quick-relaunch rebuild log. `logs` tails the first that exists.
60
77
  return [
61
78
  { label: 'webpack', path: recipeRuntimePath(target, 'webpack.log') },
62
79
  { label: 'watcher', path: recipeRuntimePath(target, 'recipe-harness-webpack.log') },