@deeeed/metamask-harness 0.3.8 → 0.4.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.
- package/CHANGELOG.md +60 -11
- package/adapters/extension/start-watch.sh +8 -1
- package/adapters/manifest.json +8 -0
- package/adapters/mobile/lib/tmux-viewer.sh +38 -0
- package/adapters/mobile/start-metro.sh +4 -18
- package/adapters/mobile/stop-metro.sh +66 -0
- package/adapters/mobile/yarn-setup.sh +14 -2
- package/adapters/shared/resolve-farmslot-ports.sh +21 -0
- package/docs/ADAPTER-SURFACE.md +119 -0
- package/docs/UX-PRINCIPLES.md +64 -0
- package/package.json +13 -4
- package/scripts/completions.sh +6 -3
- package/src/adapters/core/surface.ts +56 -0
- package/src/adapters/extension/surface.ts +71 -0
- package/src/adapters/mobile/prepare.ts +22 -3
- package/src/adapters/mobile/runtime-decision.ts +103 -42
- package/src/adapters/mobile/surface.ts +59 -0
- package/src/adapters/slot-ports.ts +165 -0
- package/src/adapters/surface.ts +82 -0
- package/src/cli.ts +128 -8
- package/src/commands/debug.ts +3 -1
- package/src/commands/fixtures.ts +13 -8
- package/src/commands/launch.ts +32 -105
- package/src/commands/logs.ts +29 -13
- package/src/doctor.ts +25 -13
- package/src/mm-harness-cli.ts +72 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,19 +1,68 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 - 2026-07-04
|
|
4
|
+
|
|
5
|
+
Minor release: the adapter-surface architecture plus the day's feature wave (0.3.6-0.3.9 shipped fixes as they were validated live; this cut marks the surface additions honestly).
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- **AdapterSurface** — one per-platform surface behind a common interface (`src/adapters/surface.ts`: resolveSlotPorts, runtimeStatus, devServer, logSources, hints); doctor/launch/stop/logs/debug/fixtures are surface-backed, so no verb hand-rolls platform branching. Contract: `docs/ADAPTER-SURFACE.md` (never-branch rule, add-a-platform checklist, migration table).
|
|
9
|
+
- **Platform-adaptive `stop` on every adapter** — extension: kills this checkout's webpack watcher (pid file + orphan scan, slot-scoped) and closes its tail window; mobile: port-scoped Metro; core: headless teaching error. Idempotent, outcome-stating summaries.
|
|
10
|
+
- **Doctor reports live runtime state on every adapter** — mobile Metro, extension webpack (via the extension readiness decision), core deps-only — one uniform shape, adapter-scoped colored context rows.
|
|
11
|
+
- **`logs` is multi-source per adapter** — sources and the `--source` default come from the adapter's logSources (extension: webpack/watcher/rebuild/app).
|
|
12
|
+
- **Context-aware examples and tier-visible flows** — `call` usage/help examples use the detected adapter's real manifest actions; `flows` prints a precedence legend decoding each source into personal > team > canonical.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- **BREAKING (agent contract)**: `doctor --json` `runtime.metro` (string) is now `runtime.devServer` `{label, status}` and is absent for core.
|
|
16
|
+
|
|
17
|
+
### Feature wave recap (0.3.6-0.3.9)
|
|
18
|
+
- `mm-harness stop`; live-streamed mobile AND extension launches with upfront intent lines; presence-authoritative readiness (no setup-on-every-launch) with the dev-client running probe; slot-context-first device/port resolution; UTF-8 locale for pod spawns; DEV OVERRIDE banner + SLOT line + colored help; `mmdev-harness` completions; hermetic tmux-stubbed contract suite (now gating CI) + ESLint in `npm run check`; `docs/UX-PRINCIPLES.md` interaction contract.
|
|
19
|
+
|
|
20
|
+
## 0.3.9 - 2026-07-04
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- **Readiness is presence-authoritative** — a no-baseline mtime-only stale verdict no longer re-runs `yarn setup` inside every launch (warning instead; fingerprint-verified drift still installs), the deps baseline is recorded after any successful setup, and the `ready` decision probes that the dev client is actually running on the target device before doing nothing (`launch` launches).
|
|
24
|
+
- **Slot-context-first resolution** — device/ports resolve from the checkout's `agentic-runtime.json` before pool/formula fallbacks, for mobile (simulator, Metro port) and extension (CDP + watcher ports, overwriting stale inherited env); naming formulas no longer hardcode pool prefixes.
|
|
25
|
+
- **UTF-8 locale for pod-triggering spawns** — CocoaPods no longer dies on multibyte podspecs under locale-less orchestrator environments.
|
|
26
|
+
- **Hermetic contract tests** — tmux/xcrun/adb are stubbed; the Metro log-window opens only in a run-owned session, never an unrelated attached one.
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- **`mm-harness stop`** — stop the port-scoped Metro this checkout owns and close its log-tail window; idempotent; teaching error on extension/core.
|
|
30
|
+
- **Extension launch streams live** with an upfront intent line (ports named); mobile and extension long operations are never silent.
|
|
31
|
+
- **Doctor reports live runtime state per platform** (deps currency, Metro on the slot's resolved port, dev-client presence) with adapter-scoped, colored runtime-context rows.
|
|
32
|
+
- **Colored grouped help with DEV OVERRIDE banner and SLOT line**; `mmdev-harness` completions; outcome-stating launch summaries with a `[dev]` marker.
|
|
33
|
+
- **docs/UX-PRINCIPLES.md** — the CLI interaction contract (human surface rules; `--json` envelope purity is exempt and inviolable).
|
|
34
|
+
|
|
35
|
+
## Unreleased
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
- **Mobile preflight no longer re-runs `yarn setup` on every launch** — the deps readiness decision escalated an mtime hint to `stale` and ran the FULL `yarn setup` (submodules + inpage bridge + jetify + pods) inside the launch phase on every invocation in orchestrated (farmslot) contexts. `depsCheck` reports `stale` for a no-baseline checkout whenever a manifest is newer than the install markers; the orchestrator's git phase refreshes tracked-file mtimes on every sync while its deps phase leaves `node_modules` untouched, so "manifest newer than markers" is normal and not proof of drift — yet it duplicated the orchestrator's deps work and turned a fast preflight into 15+ minutes (the behavior that also looked like a hang before 0.3.8's streaming). **Presence is now authoritative:** without a recorded baseline an mtime-only `stale` is trusted as `current` (with a one-line stderr warning), so a churned-but-present checkout launches with zero `yarn` in both tiers. Genuine drift is still caught by the recorded-baseline fingerprint. `mm-harness launch` (quick tier / no `--build`) also dropped `preflightMode` before deciding, so the fast contract never took effect; it is threaded through now.
|
|
39
|
+
- **Fast tier never installs; genuine gaps teach the orchestrator** — in the fast tier (quick launch / orchestrated), deps that genuinely need work (absent markers, an absent required package, a fingerprint drift, or Metro unable to resolve a module) return a teaching block naming the orchestrator deps/prepare phase (plus the standalone `yarn setup:expo` / `launch --build` remedies) instead of an implicit setup — deps are the orchestrator's contract. The standalone/full tier still installs.
|
|
40
|
+
- **Deps baseline recorded the instant a setup leaf succeeds** — `prepareMobile` records the deps fingerprint baseline immediately after any successful `yarn-setup`, unconditionally (previously only a later `record: true` re-decide did, gated on the original decision being `install` and on `RECIPE_UP_INSTALL_ATTEMPTED`). Genuine-drift detection is fingerprint-based and self-sustaining after one good install.
|
|
41
|
+
- **CocoaPods no longer crashes on non-UTF-8 podspecs under a locale-less env** — pod-triggering spawns (`yarn setup` / native `yarn start:*`) now pin a UTF-8 `LANG`/`LC_ALL` when the caller has none. A gateway/tmux parent env without a locale made Ruby parse podspecs as US-ASCII and die on the first multibyte byte (`Invalid react-native-mmkv.podspec: "\xE2" on US-ASCII`).
|
|
42
|
+
- **`launch` no longer reports a dead app as ready** — the mobile readiness decision treated a healthy Metro + successful bundle as `ready` without checking the target device, so a dev client that had crashed or was never opened produced `✓ ready` while nothing was running. The decision now probes the device (`xcrun simctl spawn … launchctl list` / `adb shell ps -A`, fail-closed) and returns `launch`/`app-not-running` (relaunch the client) unless the app is actually alive.
|
|
43
|
+
- **Metro log-tail window no longer leaks into an unrelated tmux session** — running `start-metro` outside a tmux client fell back to `tmux display-message` (the last-attached session, e.g. the user's own) and created its `metro-<port>` window — with a `tail -F` — there, outliving the run. The viewer now opens a window only in a session that belongs to this run (a named slot session that exists, or the current session when actually inside tmux) and otherwise skips it; Metro still runs detached to the log. Extracted to `adapters/mobile/lib/tmux-viewer.sh` so the rule is unit-tested; contract tests stub tmux hermetically so no suite run can create a real window.
|
|
44
|
+
|
|
45
|
+
### Added
|
|
46
|
+
- **Adapter surface — one seam for per-platform behavior** — `src/adapters/surface.ts` defines `AdapterSurface` + a `getAdapterSurface(adapter)` registry that owns the behavior commands used to hand-roll with `if (adapter === …)` ladders (slot-port resolution, read-only runtime status, dev-server describe/stop, log sources, and platform-phrased Next: hints), with thin `mobile`/`extension`/`core` implementations that delegate to the existing readiness/port plumbing (re-homed to `src/adapters/slot-ports.ts`). `doctor`, `launch`, `stop`, `logs`, `debug`, and `fixtures` now resolve platform behavior through the surface instead of branching. This closes the platform-blind class of bug (a platform handled in one command but forgotten in another). Documented in `docs/ADAPTER-SURFACE.md`; enforces `docs/UX-PRINCIPLES.md` principle 1.
|
|
47
|
+
- **`doctor` reports a runtime section for every adapter** — the live runtime readiness line (decision + reasons, deps currency, dev server) now renders for extension (webpack watcher via `decideExtensionReadiness`) and core (deps-presence-only), not just mobile; the render is one uniform shape sourced from the surface.
|
|
48
|
+
- **`mm-harness stop` is adaptive per platform** — mobile stops its port-scoped Metro and closes the log-tail window; extension stops the checkout's webpack watcher (pid file + orphan ps-scan, slot-scoped) and closes its tail window; core teaches the headless path. All idempotent (nothing-to-stop is success), with a uniform summary and `--json` envelope.
|
|
49
|
+
- **`logs` resolves dev-server log sources through the surface** — extension now recognizes the webpack, watcher, and rebuild logs (ordered), not only `webpack.log`; each platform's "nothing running" teaches its own launch hint. `--source` default and valid set come from the adapter's own log sources (extension defaults to `webpack`, not `metro`), and core teaches with platform-neutral wording (no Metro vocabulary). `--source <label>` now prefers that dev-server log.
|
|
50
|
+
- **`call` with no action gives a context-aware example** — the usage error shows the DETECTED adapter and a REAL action from its manifest (a wallet action where one exists, else the universal `command`) plus the scoped `mm-harness actions --adapter <detected>` discovery hint. The static `call --help` example now uses real actions (`ensure_unlocked` / `command`) and points at `mm-harness actions`, instead of a hardcoded `call unlock --adapter extension` naming a non-existent `metamask.wallet.unlock`.
|
|
51
|
+
- **`flows` surfaces the resolution tier per source** — `flows` prints a precedence legend mapping each resolved library source to its tier (`personal` / `team` / `canonical`), derived from the actual resolution, so the engine's `source=<name>` per flow is decodable and LOCAL libraries read as first-class; the tier is also named in the recipe-library-unavailable teaching error.
|
|
52
|
+
- **`stop --help` matches the adaptive behavior** — the help text now describes mobile Metro / extension webpack watcher / core headless, instead of the stale "Metro dev server … extension/core: teaching error".
|
|
53
|
+
- **Slot-context device/port detection** — `resolve-farmslot-ports.sh` reads `agentic-runtime.json` (the mapping the orchestrator's prepare wrote into the checkout) first, so device/port resolution survives pool renames that break any naming formula.
|
|
54
|
+
- **CLI help surface** — grouped/colored `mm-harness` help, a `DEV OVERRIDE ACTIVE` banner when `MM_HARNESS_BIN` is set, and a `SLOT` line when the checkout is a prepared slot; `mmdev-harness` completion alias.
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
- **`doctor --json` runtime dev-server shape is normalized** — the mobile-only `runtime.metro` string is replaced by a platform-labelled `runtime.devServer: { label, status }` (`metro` for mobile, `webpack` for extension; absent for headless core), so agents read one shape across platforms.
|
|
58
|
+
- **`yarn check` runs ESLint** — flat-config ESLint (typescript-eslint recommended on `src/**/*.ts`, `@eslint/js` recommended on the `.mjs`/`.cjs` library) runs after typecheck and comment-hygiene. Dev-only (not in the published `files`); the baseline is zero-warning by config scoping.
|
|
59
|
+
- **Deps install uses a scoped `setup:expo` variant when native isn't wanted** — `adapters/mobile/yarn-setup.sh` gains `--mode full|expo`. `expo` runs `yarn setup:expo --no-build-ios --no-build-android` (JS deps + prebuild, no native — native builds come from `open-device.sh`, never doubled); the full `yarn setup` runs only for a native build (`launch --build`). This is also the standalone Metro-only remedy taught by the fast-tier deps block.
|
|
60
|
+
- **`mm-harness runtime-decision --adapter mobile` accepts `--preflight-mode`** — surfaces the fast (presence, blocks on a genuine gap) vs full (installs) decision for inspection and callers.
|
|
61
|
+
|
|
3
62
|
## 0.3.8 - 2026-07-04
|
|
4
63
|
|
|
5
64
|
### Fixed
|
|
6
|
-
- **Mobile launch streams progress in
|
|
7
|
-
"schemaVersion": 1,
|
|
8
|
-
"command": "launch",
|
|
9
|
-
"status": "fail",
|
|
10
|
-
"exitCode": 2,
|
|
11
|
-
"error": {
|
|
12
|
-
"code": "USAGE",
|
|
13
|
-
"message": "could not detect the MetaMask repo type for /private/tmp/claude-501/-Users-deeeed-dev-metamask-principal-pitch-2025/4645d206-3e7f-482d-a5c1-2274f26a7411/scratchpad/emh-review",
|
|
14
|
-
"userAction": "cd into a MetaMask checkout or pass --target <path>, or force it with --adapter <mobile|extension|core>"
|
|
15
|
-
}
|
|
16
|
-
} is no longer silent (previously buffered leaf output and suppressed it entirely under ). Contract test asserts leaf progress reaches stderr, the envelope stays clean, and the streaming conversion preserves the exit-code contract.
|
|
65
|
+
- **Mobile launch streams progress in `--json` mode** — mobile prepare leaves now run through `spawnScriptStreaming`, which tees the child's stdout+stderr to the parent's stderr live while stdout stays reserved for the JSON envelope. A multi-minute `mm-harness launch --json` is no longer silent (previously `spawnSync` buffered leaf output and suppressed it entirely under `--json`). Contract test asserts leaf progress reaches stderr, the envelope stays clean, and the streaming conversion preserves the exit-code contract.
|
|
17
66
|
|
|
18
67
|
## 0.3.7 - 2026-07-04
|
|
19
68
|
|
|
@@ -243,7 +243,14 @@ printf '%s\n' "$started_pid" > "$watch_pid_file"
|
|
|
243
243
|
tmux_session="${RECIPE_TMUX_SESSION:-}"
|
|
244
244
|
if [ -z "$tmux_session" ]; then
|
|
245
245
|
slot="$(slot_number || true)"
|
|
246
|
-
[ -
|
|
246
|
+
if [ -n "$slot" ] && tmux has-session -t "=mme-$slot" 2>/dev/null; then
|
|
247
|
+
tmux_session="mme-$slot"
|
|
248
|
+
fi
|
|
249
|
+
fi
|
|
250
|
+
# Fall back to the session this run is inside: renamed pools and manual runs in
|
|
251
|
+
# any tmux still get their watcher window; outside tmux there is nowhere to put one.
|
|
252
|
+
if [ -z "$tmux_session" ]; then
|
|
253
|
+
tmux_session="$(tmux display-message -p '#S' 2>/dev/null || true)"
|
|
247
254
|
fi
|
|
248
255
|
if [ -n "$tmux_session" ] && command -v tmux >/dev/null 2>&1 && tmux has-session -t "$tmux_session" 2>/dev/null; then
|
|
249
256
|
tmux_window="webpack-${WATCHER_PORT:-default}"
|
package/adapters/manifest.json
CHANGED
|
@@ -18,6 +18,14 @@
|
|
|
18
18
|
"inputs": "--target --port --clear; env WATCHER_PORT, METRO_PORT, MOBILE_METRO_REQUIRED_ENV, METRO_MAX_WORKERS",
|
|
19
19
|
"outputs": "Metro PID file + log; exit 0 ready / 1 timeout / 2 bad args"
|
|
20
20
|
},
|
|
21
|
+
{
|
|
22
|
+
"id": "mobile/stop-metro",
|
|
23
|
+
"entry": "adapters/mobile/stop-metro.sh",
|
|
24
|
+
"kind": "bash",
|
|
25
|
+
"purpose": "Stop the port-scoped Metro bundler this checkout owns and close its log-tail window (idempotent).",
|
|
26
|
+
"inputs": "--target --port; env WATCHER_PORT",
|
|
27
|
+
"outputs": "progress on stderr; exit 0 stopped-or-nothing / 1 stop failed / 2 bad args"
|
|
28
|
+
},
|
|
21
29
|
{
|
|
22
30
|
"id": "mobile/prewarm-bundle",
|
|
23
31
|
"entry": "adapters/mobile/prewarm-bundle.sh",
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# tmux-viewer — open a read-only tmux window tailing the Metro log, but ONLY in a
|
|
3
|
+
# session that belongs to this run. Sourced by start-metro.sh; kept separate so
|
|
4
|
+
# the session-resolution rule (the part that must never leak into a user session)
|
|
5
|
+
# is unit-testable in isolation.
|
|
6
|
+
#
|
|
7
|
+
# Contract: never create a window in "whatever session happens to be attached".
|
|
8
|
+
# Outside a tmux client `tmux display-message -p '#S'` returns the last-attached
|
|
9
|
+
# session (e.g. the user's own), which would land the window — and its `tail -F` —
|
|
10
|
+
# in an unrelated session that outlives the run. When no run-owned session can be
|
|
11
|
+
# resolved, skip the viewer entirely (Metro still runs detached to the log).
|
|
12
|
+
#
|
|
13
|
+
# Inputs (env/args): $1 = metro log path; PORT, LOG_DIR, and optional `slot` from
|
|
14
|
+
# the caller; TMUX indicates we are inside a tmux client.
|
|
15
|
+
|
|
16
|
+
# shellcheck disable=SC2329 # sourced by start-metro.sh; also sourced by tests.
|
|
17
|
+
start_viewer_window() {
|
|
18
|
+
local metro_log="$1"
|
|
19
|
+
command -v tmux >/dev/null 2>&1 || return 0
|
|
20
|
+
local window="metro-${PORT}"
|
|
21
|
+
local metro_tmux="$LOG_DIR/metro.tmux"
|
|
22
|
+
local session=""
|
|
23
|
+
# (1) the slot session, when one is named and actually exists. The mm-<slot>
|
|
24
|
+
# formula predates the mmdev rename; a session named otherwise simply falls
|
|
25
|
+
# through — has-session gates it, so a wrong name never creates a window.
|
|
26
|
+
if [ -n "${slot:-}" ] && tmux has-session -t "=mm-${slot}" 2>/dev/null; then
|
|
27
|
+
session="mm-${slot}"
|
|
28
|
+
# (2) the current session, but ONLY when we are actually inside a tmux client.
|
|
29
|
+
elif [ -n "${TMUX:-}" ]; then
|
|
30
|
+
session="$(tmux display-message -p '#S' 2>/dev/null || true)"
|
|
31
|
+
fi
|
|
32
|
+
# No run-owned session → skip the viewer; no orphaned tail-window is left behind.
|
|
33
|
+
{ [ -n "$session" ] && tmux has-session -t "=$session" 2>/dev/null; } || return 0
|
|
34
|
+
tmux kill-window -t "${session}:${window}" >/dev/null 2>&1 || true
|
|
35
|
+
tmux new-window -d -t "$session" -n "$window" "exec tail -n +1 -F $(printf '%q' "$metro_log")"
|
|
36
|
+
printf '%s:%s\n' "$session" "$window" > "$metro_tmux"
|
|
37
|
+
printf 'Metro log viewer → tmux %s:%s\n' "$session" "$window" >&2
|
|
38
|
+
}
|
|
@@ -72,24 +72,10 @@ default_metro_workers() {
|
|
|
72
72
|
fi
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
local metro_tmux="$LOG_DIR/metro.tmux"
|
|
80
|
-
local session=""
|
|
81
|
-
if [ -n "${slot:-}" ] && tmux has-session -t "=mm-${slot}" 2>/dev/null; then
|
|
82
|
-
session="mm-${slot}"
|
|
83
|
-
fi
|
|
84
|
-
if [ -z "$session" ]; then
|
|
85
|
-
session="$(tmux display-message -p '#S' 2>/dev/null || true)"
|
|
86
|
-
fi
|
|
87
|
-
[ -n "$session" ] || return 0
|
|
88
|
-
tmux kill-window -t "${session}:${window}" >/dev/null 2>&1 || true
|
|
89
|
-
tmux new-window -d -t "$session" -n "$window" "exec tail -n +1 -F $(printf '%q' "$metro_log")"
|
|
90
|
-
printf '%s:%s\n' "$session" "$window" > "$metro_tmux"
|
|
91
|
-
printf 'Metro log viewer → tmux %s:%s\n' "$session" "$window" >&2
|
|
92
|
-
}
|
|
75
|
+
# Log-tail viewer window (session-safe): sourced so its run-owned-session rule is
|
|
76
|
+
# unit-testable and can never leak a window into an unrelated user session.
|
|
77
|
+
# shellcheck disable=SC1091
|
|
78
|
+
. "$SCRIPT_DIR/lib/tmux-viewer.sh"
|
|
93
79
|
|
|
94
80
|
# --- main ---------------------------------------------------------------------
|
|
95
81
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# stop-metro.sh — stop the Metro dev server this checkout owns and close its
|
|
3
|
+
# log-tail window. Port-scoped like start-metro: only the listener on the
|
|
4
|
+
# resolved port is signalled, so concurrent slots on other ports are untouched.
|
|
5
|
+
# Idempotent: nothing running is a success, not an error.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
TARGET="."
|
|
9
|
+
PORT="${WATCHER_PORT:-}"
|
|
10
|
+
while [ $# -gt 0 ]; do
|
|
11
|
+
case "$1" in
|
|
12
|
+
--target) TARGET="$2"; shift 2 ;;
|
|
13
|
+
--port) PORT="$2"; shift 2 ;;
|
|
14
|
+
-h|--help)
|
|
15
|
+
printf 'Usage: stop-metro.sh [--target <dir>] [--port <port>]\n'
|
|
16
|
+
printf ' --target MetaMask Mobile checkout directory\n'
|
|
17
|
+
printf ' --port Metro port (default: WATCHER_PORT env, else the slot context, else 8081)\n'
|
|
18
|
+
exit 0
|
|
19
|
+
;;
|
|
20
|
+
*) printf 'stop-metro: unknown arg: %s\n' "$1" >&2; exit 2 ;;
|
|
21
|
+
esac
|
|
22
|
+
done
|
|
23
|
+
|
|
24
|
+
TARGET="$(cd "$TARGET" && pwd)"
|
|
25
|
+
|
|
26
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
27
|
+
# shellcheck disable=SC1091
|
|
28
|
+
. "$SCRIPT_DIR/../shared/harness-path.sh"
|
|
29
|
+
if ! command -v recipe_runtime_dir >/dev/null 2>&1; then
|
|
30
|
+
echo "stop-metro: shared lib adapters/shared/harness-path.sh not found; reinstall the runner." >&2
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# No explicit port: resolve the checkout's own (slot context first, then the
|
|
35
|
+
# pool/formula fallbacks) so we stop THIS slot's Metro, not the 8081 default.
|
|
36
|
+
if [ -z "$PORT" ]; then
|
|
37
|
+
# shellcheck disable=SC1091
|
|
38
|
+
. "$SCRIPT_DIR/../shared/resolve-farmslot-ports.sh"
|
|
39
|
+
resolved_port="$(resolve_mobile_runtime_ports "$TARGET" 2>/dev/null | sed -n 's/^WATCHER_PORT=//p' | head -1 || true)"
|
|
40
|
+
PORT="${resolved_port:-8081}"
|
|
41
|
+
fi
|
|
42
|
+
|
|
43
|
+
LOG_DIR="$TARGET/$(recipe_runtime_dir)"
|
|
44
|
+
PID_FILE="$LOG_DIR/metro.pid"
|
|
45
|
+
TMUX_FILE="$LOG_DIR/metro.tmux"
|
|
46
|
+
|
|
47
|
+
# shellcheck disable=SC1091
|
|
48
|
+
. "$SCRIPT_DIR/lib/metro-listener.sh"
|
|
49
|
+
|
|
50
|
+
pids="$(metro_listener_pids 2>/dev/null || true)"
|
|
51
|
+
if [ -n "$pids" ]; then
|
|
52
|
+
stop_metro_listener || exit 1
|
|
53
|
+
printf 'Stopped Metro on port %s (pid %s)\n' "$PORT" "$pids" >&2
|
|
54
|
+
else
|
|
55
|
+
printf 'Metro not running on port %s — nothing to stop\n' "$PORT" >&2
|
|
56
|
+
fi
|
|
57
|
+
rm -f "$PID_FILE"
|
|
58
|
+
|
|
59
|
+
# Close the read-only log-tail window start-metro opened, if it is still there.
|
|
60
|
+
if [ -f "$TMUX_FILE" ] && command -v tmux >/dev/null 2>&1; then
|
|
61
|
+
win="$(cat "$TMUX_FILE" 2>/dev/null || true)"
|
|
62
|
+
if [ -n "$win" ] && tmux kill-window -t "$win" 2>/dev/null; then
|
|
63
|
+
printf 'Closed Metro log window %s\n' "$win" >&2
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
rm -f "$TMUX_FILE"
|
|
@@ -2,19 +2,31 @@
|
|
|
2
2
|
# yarn-setup — install node_modules for MetaMask Mobile.
|
|
3
3
|
#
|
|
4
4
|
# Inputs: --target <metamask-mobile dir> (default $PWD)
|
|
5
|
+
# --mode full|expo (default full)
|
|
5
6
|
# Outputs: yarn install output on stderr; exit 0 pass, 1 install failed, 2 bad args.
|
|
6
7
|
#
|
|
7
8
|
# Single device op: install node_modules. No other sequencing.
|
|
9
|
+
# full — `yarn setup` (JS deps + expo prebuild + native pods/jetify). Use only
|
|
10
|
+
# when a native build is wanted (launch --build / rebuild-native).
|
|
11
|
+
# expo — `yarn setup:expo --no-build-ios --no-build-android` (JS deps + prebuild,
|
|
12
|
+
# no native). The Metro-only remedy: native builds come from open-device.
|
|
8
13
|
set -euo pipefail
|
|
9
14
|
|
|
10
15
|
TARGET="$PWD"
|
|
16
|
+
MODE="full"
|
|
11
17
|
while [ "$#" -gt 0 ]; do
|
|
12
18
|
case "$1" in
|
|
13
19
|
--target) TARGET="$2"; shift 2 ;;
|
|
14
|
-
|
|
20
|
+
--mode) MODE="$2"; shift 2 ;;
|
|
21
|
+
-h|--help) printf 'Usage: yarn-setup.sh [--target <dir>] [--mode full|expo]\n'; exit 0 ;;
|
|
15
22
|
*) printf 'yarn-setup: unknown arg: %s\n' "$1" >&2; exit 2 ;;
|
|
16
23
|
esac
|
|
17
24
|
done
|
|
18
25
|
|
|
19
26
|
TARGET="$(cd "$TARGET" && pwd)"
|
|
20
|
-
cd "$TARGET"
|
|
27
|
+
cd "$TARGET"
|
|
28
|
+
case "$MODE" in
|
|
29
|
+
full) exec yarn setup ;;
|
|
30
|
+
expo) exec yarn setup:expo --no-build-ios --no-build-android ;;
|
|
31
|
+
*) printf 'yarn-setup: unknown --mode: %s (want full|expo)\n' "$MODE" >&2; exit 2 ;;
|
|
32
|
+
esac
|
|
@@ -155,8 +155,29 @@ resolve_mobile_slot_defaults() {
|
|
|
155
155
|
printf 'SLOT_ID=local-mobile-%s\n' "$n"
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
# Slot context the orchestrator's prepare wrote INTO the checkout — the
|
|
159
|
+
# authoritative device/port mapping for this repo (survives pool renames that
|
|
160
|
+
# break any naming formula). Highest-priority detection source.
|
|
161
|
+
resolve_mobile_runtime_context() {
|
|
162
|
+
local repo="$1" ctx
|
|
163
|
+
ctx="$repo/${RECIPE_RUNTIME_DIR:-temp/recipe/runtime}/agentic-runtime.json"
|
|
164
|
+
[ -f "$ctx" ] || return 1
|
|
165
|
+
node -e '
|
|
166
|
+
try {
|
|
167
|
+
const c = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
|
|
168
|
+
if (!c.simulator && !c.metroPort) process.exit(1);
|
|
169
|
+
if (c.metroPort) console.log(`WATCHER_PORT=${c.metroPort}`);
|
|
170
|
+
if (c.simulator) console.log(`IOS_SIMULATOR=${c.simulator}`);
|
|
171
|
+
if (c.slotId) console.log(`SLOT_ID=${c.slotId}`);
|
|
172
|
+
} catch { process.exit(1); }
|
|
173
|
+
' "$ctx" 2>/dev/null
|
|
174
|
+
}
|
|
175
|
+
|
|
158
176
|
resolve_mobile_runtime_ports() {
|
|
159
177
|
local repo="${1:-.}"
|
|
178
|
+
if resolve_mobile_runtime_context "$repo"; then
|
|
179
|
+
return 0
|
|
180
|
+
fi
|
|
160
181
|
if resolve_farmslot_ports_by_repo "$repo"; then
|
|
161
182
|
return 0
|
|
162
183
|
fi
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# The adapter surface — one seam for per-platform behavior
|
|
2
|
+
|
|
3
|
+
`mm-harness` runs against three platforms (mobile | extension | core). The
|
|
4
|
+
danger is that every command re-derives "what does this platform do?" with its
|
|
5
|
+
own `if (adapter === 'mobile') … else if (adapter === 'extension') …` ladder.
|
|
6
|
+
When it does, a platform gets forgotten in one command but not another — the
|
|
7
|
+
class of bug where `doctor` reported a live runtime section for mobile only,
|
|
8
|
+
`stop` refused every non-mobile adapter, and `logs` knew a single extension log
|
|
9
|
+
file instead of all three. Those are *platform-blind* commands.
|
|
10
|
+
|
|
11
|
+
The **adapter surface** is the fix: one interface per platform, resolved through
|
|
12
|
+
a registry, that owns the behavior a command would otherwise branch on.
|
|
13
|
+
|
|
14
|
+
## The interface
|
|
15
|
+
|
|
16
|
+
`src/adapters/surface.ts` defines `AdapterSurface` and the registry
|
|
17
|
+
`getAdapterSurface(adapter)`. Each platform ships one implementation:
|
|
18
|
+
|
|
19
|
+
- `src/adapters/mobile/surface.ts`
|
|
20
|
+
- `src/adapters/extension/surface.ts`
|
|
21
|
+
- `src/adapters/core/surface.ts`
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
interface AdapterSurface {
|
|
25
|
+
readonly adapter: MetaMaskRecipeAdapter;
|
|
26
|
+
readonly headless: boolean; // core runs no app/dev server
|
|
27
|
+
resolveSlotPorts(target: string): void; // context > pool > formula (no-op for core)
|
|
28
|
+
runtimeStatus(target: string): Promise<AdapterRuntimeStatus>; // read-only readiness for doctor
|
|
29
|
+
devServer: {
|
|
30
|
+
describe(): string; // "Metro" | "webpack watcher" | "no dev server (headless)"
|
|
31
|
+
stop(target: string): AdapterDevServerStop; // idempotent, slot-scoped; headless = teach
|
|
32
|
+
};
|
|
33
|
+
logSources(target: string): AdapterLogSource[]; // ordered candidate log files (empty for core)
|
|
34
|
+
hints: { launch: string; relaunch: string }; // platform-phrased Next: hints
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`runtimeStatus` returns a normalized shape so `doctor` renders one line the same
|
|
39
|
+
way for every platform:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
interface AdapterRuntimeStatus {
|
|
43
|
+
decision: string;
|
|
44
|
+
reasonCode?: string;
|
|
45
|
+
reasons: string[];
|
|
46
|
+
deps?: string;
|
|
47
|
+
devServer?: { label: string; status: string }; // absent for headless core
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The implementations are thin: they delegate to the readiness/port plumbing that
|
|
52
|
+
already existed (`decideExtensionReadiness`, `mobileRuntimeStatus`, the slot-port
|
|
53
|
+
resolvers and the webpack-watcher stop, all re-homed to
|
|
54
|
+
`src/adapters/slot-ports.ts`). The surface is an organizing seam, not a rewrite.
|
|
55
|
+
|
|
56
|
+
## The rule
|
|
57
|
+
|
|
58
|
+
**A command never branches on adapter for behavior the surface owns.**
|
|
59
|
+
|
|
60
|
+
- Do not write `if (adapter === 'core')` — ask `surface.headless`.
|
|
61
|
+
- Do not write `adapter === 'mobile' ? metroPorts() : extensionPorts()` — call
|
|
62
|
+
`surface.resolveSlotPorts(target)`.
|
|
63
|
+
- Do not print `adapter === 'mobile' ? 'mm-harness launch ios' : 'mm-harness
|
|
64
|
+
launch'` — use `surface.hints.launch`.
|
|
65
|
+
|
|
66
|
+
A new platform behavior is added by **extending the surface** (a new member on
|
|
67
|
+
the interface plus its three implementations), never by adding another branch to
|
|
68
|
+
a command. TypeScript then makes it impossible to ship a platform that forgot the
|
|
69
|
+
new member, because each implementation is annotated `: AdapterSurface`.
|
|
70
|
+
|
|
71
|
+
What the surface deliberately does **not** own: a command may still branch on
|
|
72
|
+
adapter for a *mechanism* that is genuinely platform-specific and not part of the
|
|
73
|
+
interface — e.g. `fixtures set` uses a shell arm on mobile and the engine path on
|
|
74
|
+
extension. Those branches select a mechanism; they never re-derive readiness,
|
|
75
|
+
ports, log locations, dev-server lifecycle, or Next: phrasing, which are the
|
|
76
|
+
surface's responsibility.
|
|
77
|
+
|
|
78
|
+
## Migration status
|
|
79
|
+
|
|
80
|
+
Every command that used to branch on adapter now resolves through the surface:
|
|
81
|
+
|
|
82
|
+
| command | surface-backed | uses |
|
|
83
|
+
| --- | --- | --- |
|
|
84
|
+
| `doctor` | yes | `resolveSlotPorts` + `runtimeStatus` (mobile/extension/core runtime section) |
|
|
85
|
+
| `launch` | yes | `resolveSlotPorts` |
|
|
86
|
+
| `stop` | yes | `resolveSlotPorts` + `devServer.stop` (+ headless teaching) |
|
|
87
|
+
| `logs` | yes | `logSources` + `hints.launch` + `headless` |
|
|
88
|
+
| `debug` | yes | `headless` + `hints.relaunch` (core teaching); flag semantics stay per-command |
|
|
89
|
+
| `fixtures` | yes | `headless` + `hints` (retry/launch); the set *mechanism* stays per-platform |
|
|
90
|
+
| `run` / `call` | n/a | engine path; core-headless is handled by the heal contract, not the surface |
|
|
91
|
+
| `flows` | no (by design) | recipe-library flows are adapter-global; not a platform-owned behavior |
|
|
92
|
+
| `completion-candidates` | partial | `actions` scope to the detected checkout adapter; `flows` are adapter-global |
|
|
93
|
+
|
|
94
|
+
`flows` is intentionally not surface-backed: a recipe-library flow can compose
|
|
95
|
+
actions across platforms, so flows are adapter-global by design rather than a
|
|
96
|
+
platform-blind gap. `completion-candidates actions` already resolves the adapter
|
|
97
|
+
from the checkout context (cwd/`--target`); only the adapter-global `flows`
|
|
98
|
+
candidates are unscoped, matching the flows model.
|
|
99
|
+
|
|
100
|
+
## How this composes with the UX principles
|
|
101
|
+
|
|
102
|
+
`docs/UX-PRINCIPLES.md` principle 1 (*Context-aware by default* — "output is
|
|
103
|
+
scoped to the platform… help, flag lists, and completion candidates shrink to
|
|
104
|
+
what applies here") states the intent. The adapter surface is its **enforcement
|
|
105
|
+
mechanism**: a command that resolves platform behavior through the surface is
|
|
106
|
+
context-aware by construction, and one that hand-rolls an adapter ladder is the
|
|
107
|
+
exact failure principle 1 warns against. When adding a command or output path,
|
|
108
|
+
satisfy principle 1 by going through the surface.
|
|
109
|
+
|
|
110
|
+
## Adding a platform behavior — checklist
|
|
111
|
+
|
|
112
|
+
1. Add the member to `AdapterSurface` in `src/adapters/surface.ts`.
|
|
113
|
+
2. Implement it in all three `src/adapters/*/surface.ts` (TypeScript will not
|
|
114
|
+
compile until you do).
|
|
115
|
+
3. Have the command call `getAdapterSurface(adapter).<member>` instead of
|
|
116
|
+
branching.
|
|
117
|
+
4. Cover the closed blind spot with a contract test (see
|
|
118
|
+
`tests/contract/adapter-surface.test.sh`), and keep the registry-completeness
|
|
119
|
+
assertion green.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# UX principles — the mm-harness interaction contract
|
|
2
|
+
|
|
3
|
+
Every command surface follows these rules. They are review criteria, not aspirations:
|
|
4
|
+
a PR that adds a command or output path is checked against each one.
|
|
5
|
+
|
|
6
|
+
**Scope: the HUMAN surface.** The agent contract is exempt and inviolable: under
|
|
7
|
+
`--json`, stdout carries exactly one machine-readable envelope — no color, no
|
|
8
|
+
banners, no decorative lines, schema per CLI-SPEC.md. Progress/streaming exists
|
|
9
|
+
for agents too, but only ever on stderr. When a rule below conflicts with envelope
|
|
10
|
+
purity, the envelope wins.
|
|
11
|
+
|
|
12
|
+
## 1. Context-aware by default
|
|
13
|
+
The CLI answers relative to the checkout it is standing in.
|
|
14
|
+
- Adapter is auto-detected everywhere (`--adapter` overrides; a bare command never
|
|
15
|
+
fails on a value detection can supply).
|
|
16
|
+
- The slot context the orchestrator wrote into the checkout
|
|
17
|
+
(`<runtime_dir>/agentic-runtime.json`) is the authoritative source for device,
|
|
18
|
+
ports, and slot identity — naming formulas are last-resort fallbacks and never
|
|
19
|
+
hardcode pool prefixes (they go stale on rename).
|
|
20
|
+
- Output is scoped to the platform: an extension-only field is noise on a mobile
|
|
21
|
+
slot; help, flag lists, and completion candidates shrink to what applies here.
|
|
22
|
+
- Enforcement: platform-specific behavior is resolved through the adapter surface
|
|
23
|
+
(`getAdapterSurface(adapter)`), never a per-command `if (adapter === …)` ladder —
|
|
24
|
+
that ladder is how commands go platform-blind. See docs/ADAPTER-SURFACE.md.
|
|
25
|
+
|
|
26
|
+
## 2. Never silent, never opaque
|
|
27
|
+
Long operations show intent immediately and progress continuously.
|
|
28
|
+
- First line within milliseconds: what is being done, where (device/port), and
|
|
29
|
+
where the full log lives.
|
|
30
|
+
- Leaf output streams live to stderr — `--json` reserves stdout for the envelope,
|
|
31
|
+
it never buys silence. Quiet stretches get heartbeats with elapsed time.
|
|
32
|
+
- Each dev server a command starts gets its tmux log-tail window at launch time
|
|
33
|
+
(window creation is launch-coupled; users re-create separately when they close one).
|
|
34
|
+
|
|
35
|
+
## 3. Summaries state outcomes
|
|
36
|
+
The final line says what actually happened: target device, what the tier meant,
|
|
37
|
+
resulting state — never a bare status word. Dev-override runs are marked
|
|
38
|
+
(`[dev: MM_HARNESS_BIN]`) so it is always evident which bin served.
|
|
39
|
+
|
|
40
|
+
## 4. Dev and prod are explicit
|
|
41
|
+
The global install is prod (`mm-harness`); a dev checkout runs via the documented
|
|
42
|
+
`MM_HARNESS_BIN` switch (conventionally aliased `mmdev-harness`). The dev override
|
|
43
|
+
announces itself in help and summaries. Completions cover both names.
|
|
44
|
+
|
|
45
|
+
## 5. Errors teach the exact escape
|
|
46
|
+
Every failure states the next command for the caller's actual situation. A wrong
|
|
47
|
+
hint (an escape for a different failure mode) is a bug of the same severity as
|
|
48
|
+
the failure itself.
|
|
49
|
+
|
|
50
|
+
## 6. Lifecycle verbs are symmetric and idempotent
|
|
51
|
+
What a command starts, a sibling verb stops (`launch`/`stop`), scoped to what this
|
|
52
|
+
checkout owns (port-scoped, session-scoped) — never "whatever is running".
|
|
53
|
+
Nothing-to-do is success. Tests never leak into the operator's real tmux.
|
|
54
|
+
|
|
55
|
+
## 7. Color is consistent and optional
|
|
56
|
+
All human output styles through `cli-color.ts` (headers, ok/warn/err states,
|
|
57
|
+
provenance, "Next:" hints); `NO_COLOR`/non-TTY degrade to plain text. No surface
|
|
58
|
+
invents its own ANSI.
|
|
59
|
+
|
|
60
|
+
## 8. Doctor is the status surface
|
|
61
|
+
`doctor` reports, per platform: live runtime state (deps currency, dev-server
|
|
62
|
+
up/stale on the slot's resolved port, app/browser presence), the scoped runtime
|
|
63
|
+
context with provenance (`[file]`/`[env]`/`[default]`), and the exact customize
|
|
64
|
+
hint per row. Read-only unless `--fix`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deeeed/metamask-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mm-harness": "bin/mm-harness"
|
|
@@ -17,12 +17,21 @@
|
|
|
17
17
|
"@farmslot/protocol": "^0.7.2",
|
|
18
18
|
"@farmslot/recipe-harness": "^0.3.3",
|
|
19
19
|
"commander": "^12.0.0",
|
|
20
|
-
"tsx": "^4.
|
|
21
|
-
"viem": "^2.
|
|
20
|
+
"tsx": "^4.23.0",
|
|
21
|
+
"viem": "^2.54.3"
|
|
22
|
+
},
|
|
23
|
+
"resolutions": {
|
|
24
|
+
"esbuild": "0.28.1",
|
|
25
|
+
"undici": "6.27.0",
|
|
26
|
+
"ws": "8.21.0"
|
|
22
27
|
},
|
|
23
28
|
"devDependencies": {
|
|
29
|
+
"@eslint/js": "^9",
|
|
24
30
|
"@types/node": "^22.0.0",
|
|
25
|
-
"
|
|
31
|
+
"eslint": "^9",
|
|
32
|
+
"globals": "^17.7.0",
|
|
33
|
+
"typescript": "^5.6.0",
|
|
34
|
+
"typescript-eslint": "^8"
|
|
26
35
|
},
|
|
27
36
|
"main": "./src/index.ts",
|
|
28
37
|
"types": "./src/index.ts",
|
package/scripts/completions.sh
CHANGED
|
@@ -29,12 +29,13 @@ _mmh_bin() {
|
|
|
29
29
|
return 1
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
_mmh_commands="launch logs debug fixtures actions call flows run doctor install verify cleanup completions"
|
|
32
|
+
_mmh_commands="launch stop logs debug fixtures actions call flows run doctor install verify cleanup completions"
|
|
33
33
|
|
|
34
34
|
# Per-command flags (static, from the mm-harness surface).
|
|
35
35
|
_mmh_flags_for() {
|
|
36
36
|
case "$1" in
|
|
37
37
|
launch) printf '%s' "--build --watch --verify --sidepanel --fullscreen --runway --device --cdp-port --watcher-port --heal --adapter --target --json" ;;
|
|
38
|
+
stop) printf '%s' "--port --target --json" ;;
|
|
38
39
|
logs) printf '%s' "--full --events --source --adapter --target --json" ;;
|
|
39
40
|
debug) printf '%s' "--worker --dev-menu --adapter --target --json" ;;
|
|
40
41
|
fixtures) printf '%s' "--fixture --adapter --target --json" ;;
|
|
@@ -83,7 +84,9 @@ if [ -n "${BASH_VERSION:-}" ]; then
|
|
|
83
84
|
*) COMPREPLY=($(compgen -W "$(_mmh_flags_for "$cmd")" -- "$cur")) ;;
|
|
84
85
|
esac
|
|
85
86
|
}
|
|
87
|
+
# mmdev-harness is the documented dev-checkout alias; complete it identically.
|
|
86
88
|
complete -F _mmh_complete_bash mm-harness 2>/dev/null || true
|
|
89
|
+
complete -F _mmh_complete_bash mmdev-harness 2>/dev/null || true
|
|
87
90
|
fi
|
|
88
91
|
|
|
89
92
|
# ── Zsh ─────────────────────────────────────────────────────────────────
|
|
@@ -117,9 +120,9 @@ if [ -n "${ZSH_VERSION:-}" ]; then
|
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
if (( $+functions[compdef] )); then
|
|
120
|
-
compdef _mmh_complete_zsh mm-harness
|
|
123
|
+
compdef _mmh_complete_zsh mm-harness mmdev-harness
|
|
121
124
|
else
|
|
122
125
|
autoload -Uz compinit 2>/dev/null && compinit -u 2>/dev/null
|
|
123
|
-
(( $+functions[compdef] )) && compdef _mmh_complete_zsh mm-harness
|
|
126
|
+
(( $+functions[compdef] )) && compdef _mmh_complete_zsh mm-harness mmdev-harness
|
|
124
127
|
fi
|
|
125
128
|
fi
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Core surface: headless. No app, no dev server — dependency presence is the
|
|
2
|
+
// only runtime signal, and lifecycle commands teach the reachable headless path.
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { depsCheck } from '@farmslot/recipe-harness/runtime/deps-readiness';
|
|
6
|
+
|
|
7
|
+
import type {
|
|
8
|
+
AdapterDevServerStop,
|
|
9
|
+
AdapterLogSource,
|
|
10
|
+
AdapterRuntimeStatus,
|
|
11
|
+
AdapterSurface,
|
|
12
|
+
} from '../surface.ts';
|
|
13
|
+
|
|
14
|
+
export const coreSurface: AdapterSurface = {
|
|
15
|
+
adapter: 'core',
|
|
16
|
+
headless: true,
|
|
17
|
+
|
|
18
|
+
resolveSlotPorts(): void {
|
|
19
|
+
// Headless: no ports or device to resolve.
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
async runtimeStatus(target: string): Promise<AdapterRuntimeStatus> {
|
|
23
|
+
const deps = depsCheck(path.resolve(target));
|
|
24
|
+
const ready = deps.status === 'current';
|
|
25
|
+
return {
|
|
26
|
+
decision: ready ? 'ready' : 'install',
|
|
27
|
+
reasonCode: ready ? 'deps-present' : `deps-${deps.status}`,
|
|
28
|
+
reasons: [
|
|
29
|
+
ready
|
|
30
|
+
? 'Core is headless; dependencies are installed. Run recipes with mm-harness run.'
|
|
31
|
+
: 'Core is headless; dependencies are not fully installed.',
|
|
32
|
+
],
|
|
33
|
+
deps: deps.status,
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
devServer: {
|
|
38
|
+
describe: () => 'no dev server (headless)',
|
|
39
|
+
stop(): AdapterDevServerStop {
|
|
40
|
+
return {
|
|
41
|
+
kind: 'headless',
|
|
42
|
+
message: 'core is headless — no dev server runs for a core checkout',
|
|
43
|
+
userAction: 'use mm-harness verify (readiness) or mm-harness run (execute a recipe)',
|
|
44
|
+
};
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
logSources(): AdapterLogSource[] {
|
|
49
|
+
return [];
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
hints: {
|
|
53
|
+
launch: 'mm-harness run <recipe> # run recipes against the headless core',
|
|
54
|
+
relaunch: 'mm-harness verify',
|
|
55
|
+
},
|
|
56
|
+
};
|