@deeeed/metamask-harness 0.7.0 → 0.7.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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.1
4
+
5
+ ### Added
6
+ - **`doctor --expect-live`** — exit-coded runtime liveness for hooks and scripts: exit 0 only when the adapter runtime decision is `ready` (extension: watcher + CDP; mobile: metro + bridge; core: deps), non-zero with a teaching escape otherwise. Fails closed on every degraded path; reuses the doctor runtime probe, no duplicated checks.
7
+
8
+ ### Fixed
9
+ - **Per-command `--help` documents every implemented flag** (`call/run --list`, `--library`, `--action-manifest`, `logs --window`, `actions --action`, `launch --url`, `doctor --runtime-dir`, `stop --adapter`) and tab-completion matches — the CLI self-teaches its own surface. A contract test guards help/implementation parity.
10
+ - **Injected runner shims resolve at run time** instead of a path baked at inject time: `MM_HARNESS_BIN` → global `mm-harness` on PATH → the recorded install-time path as last resort → teaching escape. A slot copied to or shared with another machine no longer points at a missing absolute path.
11
+ - Removed two parsed-but-unused `launch` flags (`--yes`, `--json-stream`).
12
+
3
13
  ## 0.7.0
4
14
 
5
15
  The "version people use": every remaining identified fix/improvement batched on the
@@ -88,8 +88,13 @@ install_v1_runner_assets() {
88
88
  if [ -n "$METAMASK_RUNNER_PROTOCOL_ROOT" ]; then
89
89
  printf 'export FARMSLOT_ROOT=${FARMSLOT_ROOT:-%s}\n' "$runner_protocol_root_q"
90
90
  fi
91
- printf 'if [ -n "${MM_HARNESS_BIN:-}" ] && [ "$MM_HARNESS_BIN" != %s ]; then echo "mm-harness overlay: %s: %s (reinstall to repoint: mm-harness install)" >&2; fi\n' "$runner_exec_q" 'delegating to the runner pinned at install time' "$runner_exec_q"
92
- printf 'exec %s "$@"\n' "$runner_exec_q"
91
+ # Resolve the runner at run time (override > global install > install-time
92
+ # record); the recorded path is last resort so a moved checkout is not pinned.
93
+ printf '%s\n' 'if [ -n "${MM_HARNESS_BIN:-}" ] && [ -x "${MM_HARNESS_BIN}" ]; then exec "${MM_HARNESS_BIN}" "$@"; fi'
94
+ printf '%s\n' 'if command -v mm-harness >/dev/null 2>&1; then exec mm-harness "$@"; fi'
95
+ printf 'if [ -x %s ]; then exec %s "$@"; fi\n' "$runner_exec_q" "$runner_exec_q"
96
+ printf '%s\n' 'echo "mm-harness not found. Next: npm i -g @deeeed/metamask-harness" >&2'
97
+ printf '%s\n' 'exit 127'
93
98
  } > "$HARNESS_DIR/runner/bin/mm-harness"
94
99
  chmod +x "$HARNESS_DIR/runner/bin/mm-harness"
95
100
  if [ -n "$METAMASK_RUNNER_PROTOCOL_ROOT" ]; then
@@ -68,12 +68,19 @@ fs.mkdirSync(path.join(harnessDir, 'runner/recipes'), { recursive: true });
68
68
  fs.rmSync(path.join(harnessDir, 'scripts'), { recursive: true, force: true });
69
69
  fs.mkdirSync(path.join(harnessDir, 'scripts/lib'), { recursive: true });
70
70
 
71
+ // Resolve the runner at run time so a moved or shared checkout is never bound to
72
+ // an install-time absolute path: explicit override, then a global install, then
73
+ // the runner recorded at install time as a last resort; teach install on miss.
74
+ const pinnedBin = shellQuote(path.join(runnerDir, 'bin/mm-harness'));
71
75
  const delegate = [
72
76
  '#!/usr/bin/env bash',
73
77
  'set -euo pipefail',
74
78
  protocolRoot ? `export FARMSLOT_ROOT=\${FARMSLOT_ROOT:-${shellQuote(protocolRoot)}}` : null,
75
- `if [ -n "\${MM_HARNESS_BIN:-}" ] && [ "\$MM_HARNESS_BIN" != ${shellQuote(path.join(runnerDir, 'bin/mm-harness'))} ]; then echo "mm-harness overlay: delegating to the runner pinned at install time: ${shellQuote(path.join(runnerDir, 'bin/mm-harness'))} (reinstall to repoint: mm-harness install)" >&2; fi`,
76
- `exec ${shellQuote(path.join(runnerDir, 'bin/mm-harness'))} "$@"`,
79
+ 'if [ -n "${MM_HARNESS_BIN:-}" ] && [ -x "${MM_HARNESS_BIN}" ]; then exec "${MM_HARNESS_BIN}" "$@"; fi',
80
+ 'if command -v mm-harness >/dev/null 2>&1; then exec mm-harness "$@"; fi',
81
+ `if [ -x ${pinnedBin} ]; then exec ${pinnedBin} "$@"; fi`,
82
+ 'echo "mm-harness not found. Next: npm i -g @deeeed/metamask-harness" >&2',
83
+ 'exit 127',
77
84
  ].filter(Boolean).join('\n') + '\n';
78
85
  fs.writeFileSync(path.join(harnessDir, 'runner/bin/mm-harness'), delegate, { mode: 0o755 });
79
86
  if (protocolRoot) fs.writeFileSync(path.join(harnessDir, 'runner/.farmslot-root'), `${protocolRoot}\n`);
@@ -187,8 +187,13 @@ install_v1_runner_assets() {
187
187
  if [ -n "$METAMASK_RUNNER_PROTOCOL_ROOT" ]; then
188
188
  printf 'export FARMSLOT_ROOT=${FARMSLOT_ROOT:-%s}\n' "$runner_protocol_root_q"
189
189
  fi
190
- printf 'if [ -n "${MM_HARNESS_BIN:-}" ] && [ "$MM_HARNESS_BIN" != %s ]; then echo "mm-harness overlay: %s: %s (reinstall to repoint: mm-harness install)" >&2; fi\n' "$runner_exec_q" 'delegating to the runner pinned at install time' "$runner_exec_q"
191
- printf 'exec %s "$@"\n' "$runner_exec_q"
190
+ # Resolve the runner at run time (override > global install > install-time
191
+ # record); the recorded path is last resort so a moved checkout is not pinned.
192
+ printf '%s\n' 'if [ -n "${MM_HARNESS_BIN:-}" ] && [ -x "${MM_HARNESS_BIN}" ]; then exec "${MM_HARNESS_BIN}" "$@"; fi'
193
+ printf '%s\n' 'if command -v mm-harness >/dev/null 2>&1; then exec mm-harness "$@"; fi'
194
+ printf 'if [ -x %s ]; then exec %s "$@"; fi\n' "$runner_exec_q" "$runner_exec_q"
195
+ printf '%s\n' 'echo "mm-harness not found. Next: npm i -g @deeeed/metamask-harness" >&2'
196
+ printf '%s\n' 'exit 127'
192
197
  } > "$HARNESS_DIR/runner/bin/mm-harness"
193
198
  chmod +x "$HARNESS_DIR/runner/bin/mm-harness"
194
199
  if [ -n "$METAMASK_RUNNER_PROTOCOL_ROOT" ]; then
@@ -11,10 +11,10 @@ const SPEC = {
11
11
  { name: "ports", desc: "Slot ports and runtime paths", flags: ["--json"] },
12
12
  { name: "up", desc: "Decide + run minimum work to reach ready", flags: ["--json", "--dry-run"] },
13
13
  { name: "sync", desc: "Refresh harness + canonicalize wallet fixture", flags: ["--json"] },
14
- { name: "logs", aliases: ["tail"], desc: "Compact build events or full log", flags: ["--full", "-f"] },
14
+ { name: "logs", aliases: ["tail"], desc: "Compact build events or full log", flags: ["--full", "-f", "--window", "--events", "--source", "--json"] },
15
15
  { name: "debug", aliases: ["devtools", "inspect"], desc: "Open DevTools UI", flags: ["--json", "--no-open"] },
16
16
  { name: "actions", desc: "List runnable recipe actions", flags: ["--json"] },
17
- { name: "doctor", desc: "Check harness/orchestration health", flags: ["--json", "--target", "--adapter", "--runtime-dir"] },
17
+ { name: "doctor", desc: "Check harness/orchestration health", flags: ["--json", "--target", "--adapter", "--runtime-dir", "--expect-live", "--cdp-port"] },
18
18
  { name: "run", desc: "Execute a proof recipe", args: ["recipe.json"], flags: ["--list"] },
19
19
  { name: "interactive", aliases: ["menu"], desc: "Interactive command menu" },
20
20
  { name: "prepare", desc: "Install harness (+ optional validate)", flags: ["--target", "--runtime-dir", "--json"] },
@@ -7,7 +7,7 @@ import { assertAdapter, runnerDir } from "../paths.js";
7
7
  import { getAdapterSurface } from "../adapters/surface.js";
8
8
  import { loadActionManifest, validateManifest } from "../manifest.js";
9
9
  import { ensureOverlay, newHealState, recipeRunning } from "../heal-bounds.js";
10
- import { ADAPTER_DETECT_NEXT, usageOut } from "./shared.js";
10
+ import { ADAPTER_DETECT_NEXT, EXIT, usageOut } from "./shared.js";
11
11
  import {
12
12
  actionManifestPathOption,
13
13
  applyRuntimeDirOption,
@@ -15,10 +15,24 @@ import {
15
15
  optionString,
16
16
  targetPath
17
17
  } from "./parse-args.js";
18
+ function applyDoctorRuntimePorts(options) {
19
+ const cdpPort = optionString(options, "cdpPort");
20
+ if (cdpPort) {
21
+ process.env.CDP_PORT = cdpPort;
22
+ process.env.RECIPE_CDP_PORT = cdpPort;
23
+ }
24
+ const watcherPort = optionString(options, "watcherPort") ?? optionString(options, "port") ?? optionString(options, "metroPort");
25
+ if (watcherPort) {
26
+ process.env.WATCHER_PORT = watcherPort;
27
+ process.env.METRO_PORT = watcherPort;
28
+ process.env.RECIPE_WATCHER_PORT = watcherPort;
29
+ }
30
+ }
18
31
  async function handleDoctor({ options }) {
19
32
  applyRuntimeDirOption(options);
20
33
  const target = targetPath(options);
21
34
  const json = optionFlag(options, "json");
35
+ const expectLive = optionFlag(options, "expectLive");
22
36
  const explicitAdapter = optionString(options, "adapter") ?? optionString(options, "platform");
23
37
  const adapter = explicitAdapter ?? detectAdapter(target);
24
38
  if (!adapter) {
@@ -40,11 +54,13 @@ async function handleDoctor({ options }) {
40
54
  try {
41
55
  const surface = getAdapterSurface(adapter);
42
56
  surface.resolveSlotPorts(target);
57
+ applyDoctorRuntimePorts(options);
43
58
  runtime = await surface.runtimeStatus(target);
44
59
  } catch {
45
60
  }
46
61
  const orphanMetros = adapter === "mobile" ? detectOrphanMetros(target, process.env.WATCHER_PORT) : [];
47
62
  const capture = captureHelperHealth();
63
+ if (expectLive) return emitExpectLive(adapter, target, runtime, result, orphanMetros, capture, json);
48
64
  if (json) console.log(JSON.stringify({ ...result, runtime, orphanMetros, capture }, null, 2));
49
65
  else {
50
66
  const out = (style, text) => color(style, text, { stream: process.stdout });
@@ -78,6 +94,23 @@ async function handleDoctor({ options }) {
78
94
  }
79
95
  return result.status === "pass" ? 0 : 1;
80
96
  }
97
+ function emitExpectLive(adapter, target, runtime, result, orphanMetros, capture, json) {
98
+ const live = runtime?.decision === "ready";
99
+ if (json) {
100
+ console.log(JSON.stringify({ ...result, runtime, orphanMetros, capture }, null, 2));
101
+ return live ? EXIT.ok : EXIT.runtime;
102
+ }
103
+ const out = (style, text) => color(style, text, { stream: process.stdout });
104
+ if (live) {
105
+ console.log(`${out("ok", "live")} ${out("bold", adapter)} runtime ready`);
106
+ return EXIT.ok;
107
+ }
108
+ const detail = runtime ? `${runtime.decision}${runtime.reasonCode ? ` (${runtime.reasonCode})` : ""}` : "runtime probe unavailable";
109
+ console.error(`${out("err", "not-live")} ${out("bold", adapter)} ${detail} ${out("dim", target)}`);
110
+ for (const reason of runtime?.reasons ?? []) console.error(` ${out("dim", reason)}`);
111
+ console.error(` Next: ${getAdapterSurface(adapter).hints.relaunch}`);
112
+ return EXIT.runtime;
113
+ }
81
114
  function captureHelperHealth() {
82
115
  if (process.platform !== "darwin") return null;
83
116
  const bin = process.env.CAPTURE_HELPER_PATH || "capture-helper";
@@ -32,9 +32,7 @@ const LAUNCH_BOOLEANS = /* @__PURE__ */ new Set([
32
32
  "sidepanel",
33
33
  "fullscreen",
34
34
  "runway",
35
- "json",
36
- "jsonStream",
37
- "yes"
35
+ "json"
38
36
  ]);
39
37
  async function handleLaunch(argv) {
40
38
  const { positional, options } = parseFlags(argv, LAUNCH_BOOLEANS);
@@ -28,6 +28,7 @@ function parseArgs(argv, command) {
28
28
  "fix",
29
29
  "force",
30
30
  "resolveOnly",
31
+ "expectLive",
31
32
  "help"
32
33
  ]);
33
34
  for (let i = 0; i < argv.length; i += 1) {
@@ -18,6 +18,7 @@ const REAL = [
18
18
 
19
19
  List the action vocabulary + field schemas for the checkout adapter.
20
20
 
21
+ --action <name> Describe one action; fuzzy-resolves like call (short or full name)
21
22
  --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
22
23
  --target <path> Checkout path (default: cwd)
23
24
  --raw Dump raw action registry JSON
@@ -25,6 +26,7 @@ const REAL = [
25
26
 
26
27
  Example:
27
28
  mm-harness actions --adapter mobile
29
+ mm-harness actions --adapter mobile --action assert_orders
28
30
  mm-harness actions --adapter extension --raw`
29
31
  },
30
32
  {
@@ -41,6 +43,7 @@ Example:
41
43
  core headless \u2014 no dev server to stop (teaching error)
42
44
 
43
45
  --port <port> Dev-server port (default: the checkout's slot context)
46
+ --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
44
47
  --target <path> Checkout path (default: cwd)
45
48
  --json Machine-readable output
46
49
 
@@ -59,11 +62,13 @@ Example:
59
62
  if unique; ambiguous = exit 2. Actions differ per adapter \u2014 list this checkout's
60
63
  with: mm-harness actions.
61
64
 
65
+ --list List everything invocable for the adapter (actions + flows); no <action> needed
62
66
  --arg k=v Action field value (repeatable)
63
67
  --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
64
68
  --target <path> Checkout path (default: cwd)
65
69
  --artifacts-dir <dir> Where to write evidence (default: temp dir)
66
70
  --action-manifest <path> Override the action manifest
71
+ --library <name=path> Add/override a recipe-library source (repeatable)
67
72
  --heal <off|infra-only|auto> Healing policy (default: infra-only); auto-ensures the overlay
68
73
  --json Machine-readable output
69
74
 
@@ -96,10 +101,13 @@ Example:
96
101
 
97
102
  Validate + run a recipe and write evidence (summary / trace / artifacts).
98
103
 
104
+ --list List everything invocable for the adapter (actions + flows); no <recipe> needed
99
105
  --plan Validate + print execution plan, touching nothing. Exit 5 if invalid.
100
106
  --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
101
107
  --target <path> Checkout path (default: cwd)
102
108
  --artifacts-dir <dir> Where to write evidence (required unless --plan)
109
+ --action-manifest <path> Override the action manifest
110
+ --library <name=path> Add/override a recipe-library source (repeatable)
103
111
  --heal <off|infra-only|auto> Healing policy (default: infra-only); auto-ensures the overlay
104
112
  --json Machine-readable output
105
113
  --record-video=full-run Record a video of the run
@@ -118,13 +126,17 @@ Example:
118
126
  sections so there is no hunting for files.
119
127
 
120
128
  --fix Repair the overlay/runtime-context WITHOUT launching (no fixture reseed); --json adds fixed[]/failed[]
129
+ --expect-live Exit 0 iff the runtime is live (extension: watcher+CDP; mobile: Metro+bridge; core: deps), non-zero + teaching escape otherwise
130
+ --cdp-port <port> Extension CDP port for the liveness probe (env: CDP_PORT / RECIPE_CDP_PORT)
121
131
  --adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
122
132
  --target <path> Checkout path (default: cwd)
133
+ --runtime-dir <dir> Runtime dir containing agentic-runtime.json (relative to target)
123
134
  --json Machine-readable output
124
135
 
125
136
  Example:
126
137
  mm-harness doctor
127
138
  mm-harness doctor --fix --json
139
+ mm-harness doctor --adapter extension --target /path/to/checkout --cdp-port 6662 --expect-live
128
140
  mm-harness doctor --adapter mobile --target /path/to/checkout`
129
141
  },
130
142
  {
@@ -238,6 +250,7 @@ Example:
238
250
  --runway Post-launch runway check (mobile only; teaching error elsewhere)
239
251
  --watch Persistent webpack watcher then relaunch (extension only)
240
252
  --sidepanel | --fullscreen Extension display mode (default --fullscreen)
253
+ --url <dapp-url> Open a dapp in the main tab beside the sidepanel (extension only)
241
254
  --heal <off|infra-only|auto> Healing policy (default auto); bounds always enforced
242
255
  --device <udid|name> Target simulator/device (env: IOS_SIMULATOR / ADB_SERIAL)
243
256
  --cdp-port <port> Extension CDP port (env: CDP_PORT / RECIPE_CDP_PORT)
@@ -261,6 +274,7 @@ Example:
261
274
  Teaching error if nothing is running (points at launch).
262
275
 
263
276
  --full Raw log tail (default = compact) (env: RECIPE_LOG_UI)
277
+ --window (Re)open the read-only tmux tail window for the resolved dev-server port (leaves the process untouched)
264
278
  --events <n> Compact event count (default 10) (env: RECIPE_LOG_EVENTS)
265
279
  --source <label> Log source per adapter \u2014 mobile: metro|app (default metro);
266
280
  extension: webpack|watcher|rebuild|app (default webpack).
@@ -271,7 +285,8 @@ Example:
271
285
 
272
286
  Example:
273
287
  mm-harness logs
274
- mm-harness logs --full`
288
+ mm-harness logs --full
289
+ mm-harness logs --window`
275
290
  },
276
291
  {
277
292
  name: "debug",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"