@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/dist/run-diagnostics.js
CHANGED
|
@@ -1,26 +1,54 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
-
import {
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { getAdapterSurface } from "./adapters/surface.js";
|
|
6
6
|
import { runnerDir } from "./paths.js";
|
|
7
|
+
import { ensureExtensionConsoleCapture } from "./adapters/extension/console-capture.js";
|
|
7
8
|
const MAX_CAPTURE_BYTES = 512 * 1024;
|
|
8
9
|
const MAX_FINDINGS = 20;
|
|
9
10
|
const MAX_PREVIEW_CHARS = 320;
|
|
11
|
+
function readRunDiagnosticsDocument(diagnosticsPath) {
|
|
12
|
+
if (typeof diagnosticsPath !== "string" || diagnosticsPath.length === 0) return null;
|
|
13
|
+
try {
|
|
14
|
+
const document = JSON.parse(fs.readFileSync(diagnosticsPath, "utf8"));
|
|
15
|
+
return isRunDiagnosticsDocument(document) ? document : null;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function formatRunDiagnosticsForHuman(diagnostics, adapter) {
|
|
21
|
+
if (!diagnostics) {
|
|
22
|
+
return [adapter === "core" ? "N/A \u2014 Core is headless and has no application log source." : "UNAVAILABLE \u2014 application diagnostics could not be collected for this run."];
|
|
23
|
+
}
|
|
24
|
+
const status = diagnostics.status === "clean" ? "CLEAN" : diagnostics.status === "review" ? "REVIEW" : "UNAVAILABLE";
|
|
25
|
+
const lines = [`${status} \u2014 ${diagnostics.note}`];
|
|
26
|
+
for (const finding of diagnostics.findings) {
|
|
27
|
+
const count = finding.count > 1 ? ` \xD7${finding.count}` : "";
|
|
28
|
+
lines.push(`${finding.level.toUpperCase()}${count} \u2014 ${finding.preview}`);
|
|
29
|
+
}
|
|
30
|
+
return lines;
|
|
31
|
+
}
|
|
10
32
|
async function beginRunDiagnostics(adapter, projectRoot) {
|
|
11
33
|
const source = getAdapterSurface(adapter).appLogSource(projectRoot);
|
|
12
34
|
if (!source) return null;
|
|
13
35
|
const mobileIssueBuffer = adapter === "mobile" && armMobileIssueBuffer(projectRoot) ? { projectRoot } : void 0;
|
|
14
|
-
|
|
36
|
+
if (adapter === "extension") {
|
|
37
|
+
await ensureExtensionConsoleCapture(projectRoot).catch(() => void 0);
|
|
38
|
+
}
|
|
15
39
|
const stat = safeStat(source.path);
|
|
16
40
|
return {
|
|
17
41
|
source,
|
|
18
42
|
offset: stat?.size ?? 0,
|
|
19
43
|
...stat ? { inode: stat.ino } : {},
|
|
20
|
-
...mobileIssueBuffer ? { mobileIssueBuffer } : {}
|
|
21
|
-
...extensionConsoleCapture ? { extensionConsoleCapture } : {}
|
|
44
|
+
...mobileIssueBuffer ? { mobileIssueBuffer } : {}
|
|
22
45
|
};
|
|
23
46
|
}
|
|
47
|
+
function isRunDiagnosticsDocument(value) {
|
|
48
|
+
if (!isRecord(value) || value.schemaVersion !== 1 || value.scope !== "recipe-run-application") return false;
|
|
49
|
+
if (value.status !== "clean" && value.status !== "review" && value.status !== "unavailable") return false;
|
|
50
|
+
return typeof value.note === "string" && Array.isArray(value.findings);
|
|
51
|
+
}
|
|
24
52
|
function finishRunDiagnostics(baseline, result) {
|
|
25
53
|
if (!baseline) return result;
|
|
26
54
|
try {
|
|
@@ -56,9 +84,7 @@ function finishRunDiagnostics(baseline, result) {
|
|
|
56
84
|
}
|
|
57
85
|
}
|
|
58
86
|
function stopRunDiagnostics(baseline) {
|
|
59
|
-
|
|
60
|
-
if (!capture || capture.exitCode !== null || capture.signalCode !== null) return;
|
|
61
|
-
capture.kill("SIGTERM");
|
|
87
|
+
void baseline;
|
|
62
88
|
}
|
|
63
89
|
function collectRunDiagnostics(baseline, bufferedIssues) {
|
|
64
90
|
const stat = safeStat(baseline.source.path);
|
|
@@ -157,39 +183,6 @@ function runMobileIssueCommand(projectRoot, command) {
|
|
|
157
183
|
return null;
|
|
158
184
|
}
|
|
159
185
|
}
|
|
160
|
-
async function startExtensionConsoleCapture(source, projectRoot) {
|
|
161
|
-
const cdpPort = process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT;
|
|
162
|
-
if (!cdpPort) return void 0;
|
|
163
|
-
fs.mkdirSync(path.dirname(source.path), { recursive: true });
|
|
164
|
-
const before = safeStat(source.path)?.size ?? 0;
|
|
165
|
-
const script = path.join(runnerDir, "adapters", "extension", "console-tail.mjs");
|
|
166
|
-
const child = spawn(process.execPath, [script, "--cdp-port", cdpPort, "--log", source.path], {
|
|
167
|
-
cwd: projectRoot,
|
|
168
|
-
stdio: "ignore"
|
|
169
|
-
});
|
|
170
|
-
const attached = await waitForExtensionAttachment(source.path, before, child, 3e3);
|
|
171
|
-
if (attached) {
|
|
172
|
-
await delay(50);
|
|
173
|
-
return child;
|
|
174
|
-
}
|
|
175
|
-
if (child.exitCode === null && child.signalCode === null) child.kill("SIGTERM");
|
|
176
|
-
return void 0;
|
|
177
|
-
}
|
|
178
|
-
async function waitForExtensionAttachment(logPath, offset, child, timeoutMs) {
|
|
179
|
-
const deadline = Date.now() + timeoutMs;
|
|
180
|
-
while (Date.now() < deadline && child.exitCode === null && child.signalCode === null) {
|
|
181
|
-
const stat = safeStat(logPath);
|
|
182
|
-
if (stat && stat.size > offset) {
|
|
183
|
-
const appended = readSlice(logPath, offset, Math.min(stat.size - offset, 64 * 1024));
|
|
184
|
-
if (appended.includes("[attached]")) return true;
|
|
185
|
-
}
|
|
186
|
-
await delay(50);
|
|
187
|
-
}
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
function delay(ms) {
|
|
191
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
192
|
-
}
|
|
193
186
|
function redactPreview(value) {
|
|
194
187
|
return value.replace(/\b(Bearer)\s+\S+/giu, "$1 [REDACTED]").replace(/\b(password|passphrase|mnemonic|seed(?:Phrase)?|privateKey|secret|token|authorization)\b\s*[:=]\s*(?:"[^"]*"|'[^']*'|\S+)/giu, "$1=[REDACTED]").replace(/\b(?:0x)?[a-f0-9]{64,}\b/giu, "[REDACTED_HEX]").replace(/(https?:\/\/[^\s?]+)\?\S+/giu, "$1?[REDACTED_QUERY]").slice(0, MAX_PREVIEW_CHARS);
|
|
195
188
|
}
|
|
@@ -267,5 +260,7 @@ export {
|
|
|
267
260
|
beginRunDiagnostics,
|
|
268
261
|
collectRunDiagnostics,
|
|
269
262
|
finishRunDiagnostics,
|
|
263
|
+
formatRunDiagnosticsForHuman,
|
|
264
|
+
readRunDiagnosticsDocument,
|
|
270
265
|
stopRunDiagnostics
|
|
271
266
|
};
|
package/dist/runner.js
CHANGED
|
@@ -58,7 +58,7 @@ async function createMetaMaskRunner(adapter, actionManifest, options = {}) {
|
|
|
58
58
|
const logger = options.quietStdout ? new console.Console(process.stderr) : console;
|
|
59
59
|
return createRecipeRunner({
|
|
60
60
|
actionManifest,
|
|
61
|
-
adapters: [...core, ...ui, ...lifecycle, ...custom],
|
|
61
|
+
adapters: withActionProgress([...core, ...ui, ...lifecycle, ...custom], options.onActionEvent),
|
|
62
62
|
logger,
|
|
63
63
|
recording: {
|
|
64
64
|
targetProvider: createMetaMaskRecordingTargetProvider(adapter)
|
|
@@ -76,6 +76,36 @@ async function createMetaMaskRunner(adapter, actionManifest, options = {}) {
|
|
|
76
76
|
}
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
|
+
function withActionProgress(adapters, onActionEvent) {
|
|
80
|
+
if (!onActionEvent) return adapters;
|
|
81
|
+
return adapters.map((entry) => ({
|
|
82
|
+
...entry,
|
|
83
|
+
async execute(node, context) {
|
|
84
|
+
const event = { nodeId: context.nodeId, action: entry.action };
|
|
85
|
+
const reportProgress = !isAutomaticHudProgress(entry.action, node, context.nodeId);
|
|
86
|
+
if (reportProgress) onActionEvent({ ...event, status: "running" });
|
|
87
|
+
try {
|
|
88
|
+
const result = await entry.execute(node, context);
|
|
89
|
+
if (reportProgress) {
|
|
90
|
+
onActionEvent({ ...event, status: result.status === "fail" ? "failed" : "passed" });
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (reportProgress) onActionEvent({ ...event, status: "failed" });
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
99
|
+
}
|
|
100
|
+
function isAutomaticHudProgress(action, node, nodeId) {
|
|
101
|
+
if (action !== "app.hud" || node === null || typeof node !== "object" || Array.isArray(node)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
const payload = node;
|
|
105
|
+
if (typeof payload.action_name === "string") return true;
|
|
106
|
+
const progress = payload.progress;
|
|
107
|
+
return nodeId === "recipe-complete" && progress !== null && typeof progress === "object" && !Array.isArray(progress) && progress.complete === true;
|
|
108
|
+
}
|
|
79
109
|
function mobileSourceAwareLifecycleAdapters(adapter, lifecycle) {
|
|
80
110
|
if (adapter !== "mobile") return lifecycle;
|
|
81
111
|
return lifecycle.map((entry) => ({
|
|
@@ -120,5 +150,6 @@ export {
|
|
|
120
150
|
createMetaMaskExtensionRunner,
|
|
121
151
|
createMetaMaskMobileRunner,
|
|
122
152
|
createMetaMaskRunner,
|
|
153
|
+
isAutomaticHudProgress,
|
|
123
154
|
mobileSourceAwareLifecycleAdapters
|
|
124
155
|
};
|
package/dist/runtime-context.js
CHANGED
|
@@ -53,6 +53,32 @@ function readExistingContext(contextPath, repoRoot, runtimeDir, adapter) {
|
|
|
53
53
|
return { context, existed: true };
|
|
54
54
|
}
|
|
55
55
|
function validExistingContext(context, repoRoot, runtimeDir, adapter) {
|
|
56
|
+
const allowedFields = /* @__PURE__ */ new Set([
|
|
57
|
+
"schemaVersion",
|
|
58
|
+
"repoRoot",
|
|
59
|
+
"project",
|
|
60
|
+
"slotId",
|
|
61
|
+
"machine",
|
|
62
|
+
"runtimeOwner",
|
|
63
|
+
"strict",
|
|
64
|
+
"platform",
|
|
65
|
+
"runtimeDir",
|
|
66
|
+
"gitDirty",
|
|
67
|
+
"simulator",
|
|
68
|
+
"adbSerial",
|
|
69
|
+
"distDir",
|
|
70
|
+
"extensionId",
|
|
71
|
+
"gitHead",
|
|
72
|
+
"gitBranch",
|
|
73
|
+
"gitCommit",
|
|
74
|
+
"createdAt",
|
|
75
|
+
"session",
|
|
76
|
+
"cdpPort",
|
|
77
|
+
"watcherPort",
|
|
78
|
+
"metroPort",
|
|
79
|
+
"devServerPort"
|
|
80
|
+
]);
|
|
81
|
+
if (Object.keys(context).some((field) => !allowedFields.has(field))) return false;
|
|
56
82
|
if (context.schemaVersion !== void 0 && context.schemaVersion !== 1) return false;
|
|
57
83
|
if (context.repoRoot !== void 0 && (typeof context.repoRoot !== "string" || path.resolve(context.repoRoot) !== repoRoot)) return false;
|
|
58
84
|
if (context.platform !== void 0 && context.platform !== adapter) return false;
|
|
@@ -82,14 +108,6 @@ function validExistingContext(context, repoRoot, runtimeDir, adapter) {
|
|
|
82
108
|
const value = context[field];
|
|
83
109
|
if (value !== void 0 && (!Number.isInteger(value) || Number(value) < 1 || Number(value) > 65535)) return false;
|
|
84
110
|
}
|
|
85
|
-
if (context.runtimeStart !== void 0) {
|
|
86
|
-
if (!context.runtimeStart || typeof context.runtimeStart !== "object" || Array.isArray(context.runtimeStart)) return false;
|
|
87
|
-
const runtimeStart = context.runtimeStart;
|
|
88
|
-
if (runtimeStart.approved !== void 0 && typeof runtimeStart.approved !== "boolean") return false;
|
|
89
|
-
for (const field of ["command", "readyUrl"]) {
|
|
90
|
-
if (runtimeStart[field] !== void 0 && (typeof runtimeStart[field] !== "string" || runtimeStart[field].length === 0)) return false;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
111
|
const forbiddenResources = adapter === "core" ? ["cdpPort", "watcherPort", "metroPort", "devServerPort", "simulator", "adbSerial", "extensionId"] : adapter === "extension" ? ["metroPort", "simulator", "adbSerial"] : ["cdpPort", "extensionId"];
|
|
94
112
|
if (forbiddenResources.some((field) => context[field] !== void 0)) return false;
|
|
95
113
|
return true;
|
package/docs/CHEATSHEET.md
CHANGED
|
@@ -1,61 +1,48 @@
|
|
|
1
|
-
# mm-harness
|
|
1
|
+
# mm-harness cheatsheet
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Run inside a MetaMask checkout. Product, slot, ports, and runtime paths are
|
|
4
|
+
auto-detected.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
## Operate
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
| Situation | Command |
|
|
8
|
+
| Need | Command |
|
|
10
9
|
|---|---|
|
|
11
|
-
|
|
|
12
|
-
|
|
|
13
|
-
|
|
|
14
|
-
|
|
|
15
|
-
|
|
|
16
|
-
|
|
|
17
|
-
|
|
|
18
|
-
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
10
|
+
| Extension | `mm-harness launch` |
|
|
11
|
+
| Extension sidepanel | `mm-harness launch --sidepanel` |
|
|
12
|
+
| iOS / Android | `mm-harness launch ios` / `mm-harness launch android` |
|
|
13
|
+
| Clean rebuild | `mm-harness launch --build` |
|
|
14
|
+
| Runtime status | `mm-harness status` |
|
|
15
|
+
| Logs | `mm-harness logs` |
|
|
16
|
+
| Debugger | `mm-harness debug` |
|
|
17
|
+
| Apply wallet fixture | `mm-harness fixtures set` |
|
|
18
|
+
|
|
19
|
+
Extension log sources: `extension`, `dapp`, `webpack`, `watcher`, `rebuild`.
|
|
20
|
+
Mobile log sources: `app`, `metro`.
|
|
21
|
+
|
|
22
|
+
## Discover and prove
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
mm-harness actions positions
|
|
26
|
+
mm-harness actions --action metamask.wallet.ensure_unlocked
|
|
27
|
+
mm-harness call metamask.wallet.ensure_unlocked
|
|
28
|
+
mm-harness flows
|
|
29
|
+
mm-harness run recipe.json --plan
|
|
30
|
+
mm-harness run recipe.json --artifacts-dir temp/recipe-proof
|
|
30
31
|
```
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
| `mm-harness actions` | Action vocabulary for this adapter |
|
|
35
|
-
| `mm-harness call unlock` | One action through the real engine (fuzzy name ok) |
|
|
36
|
-
| `mm-harness flows` | Reusable flows (personal > team > canonical) |
|
|
37
|
-
| `mm-harness run recipe.json --plan` | Validate + print plan, no device touched; exit 5 if invalid |
|
|
38
|
-
| `mm-harness run recipe.json` | Execute + write evidence (summary / trace / artifacts) |
|
|
39
|
-
|
|
40
|
-
---
|
|
33
|
+
The run result prints diagnostics plus absolute evidence paths. Resume safely with
|
|
34
|
+
`mm-harness last --json`.
|
|
41
35
|
|
|
42
|
-
##
|
|
36
|
+
## Recover
|
|
43
37
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
---
|
|
52
|
-
|
|
53
|
-
## For agents and scripts
|
|
54
|
-
|
|
55
|
-
**`--json`** — machine-readable output on every command; includes `recovered[]`, `mutations[]`, `schemaVersion`.
|
|
56
|
-
|
|
57
|
-
**`--heal`** — `off` (fail fast, repro-preserving) · `infra-only` (heal transport, never wallet; `run`/`call` default) · `auto` (auto-ensure overlay + heal; `launch` default).
|
|
58
|
-
|
|
59
|
-
**Exit codes** — `0` success · `1` recipe/action failed · `2` bad args / missing target · `5` validation failure (fix recipe).
|
|
38
|
+
```bash
|
|
39
|
+
mm-harness doctor
|
|
40
|
+
mm-harness doctor --fix
|
|
41
|
+
mm-harness verify
|
|
42
|
+
mm-harness cleanup
|
|
43
|
+
mm-harness update
|
|
44
|
+
```
|
|
60
45
|
|
|
61
|
-
|
|
46
|
+
For automation, add `--json`. Use `--json-stream` for line-delimited run progress.
|
|
47
|
+
Bad input exits `2`; recipe/action failure exits `1`; recipe validation failure
|
|
48
|
+
exits `5`.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<!-- Generated by scripts/generate-cli-ergonomics-audit.mjs. Do not edit by hand. -->
|
|
2
|
+
# CLI ergonomics audit
|
|
3
|
+
|
|
4
|
+
This matrix tracks the public command surface. “Covered” means the shared preflight contract and
|
|
5
|
+
`tests/contract/cli-teaching-errors.test.sh` exercise the behavior without dispatching runtime work.
|
|
6
|
+
Runtime recovery is claimed only where command-specific failures return a stable `userAction`.
|
|
7
|
+
Success hints are intentionally limited to lifecycle transitions with one truthful next command;
|
|
8
|
+
discovery, read-only, and terminal evidence commands do not invent one. ID prefix support is N/A
|
|
9
|
+
unless a command displays a shortened ID that another command accepts.
|
|
10
|
+
|
|
11
|
+
| Command | Bad flag | First feedback | `--json` recovery | Success next step | Displayed ID prefix |
|
|
12
|
+
|---|---|---|---|---|---|
|
|
13
|
+
| `status` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | structured `next` | N/A: command displays no shortened ID |
|
|
14
|
+
| `checklist` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage errors covered; no runtime recovery claim | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
15
|
+
| `actions` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
16
|
+
| `stop` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | launch `next` when the adapter has one unambiguous launch | N/A: command displays no shortened ID |
|
|
17
|
+
| `call` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
18
|
+
| `flows` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage errors covered; no runtime recovery claim | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
19
|
+
| `run` | covered: `CLI_UNKNOWN_OPTION` | TTY intent before slow work; machine output clean | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
20
|
+
| `last` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
21
|
+
| `doctor` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
22
|
+
| `check` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
23
|
+
| `recipe-quality` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
24
|
+
| `provision` | covered: `CLI_UNKNOWN_OPTION` | TTY intent before slow work; machine output clean | usage + runtime `error.code/message/userAction` | launch `next` after install | N/A: command displays no shortened ID |
|
|
25
|
+
| `install` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | verify `next` | N/A: command displays no shortened ID |
|
|
26
|
+
| `verify` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | status `next` | N/A: command displays no shortened ID |
|
|
27
|
+
| `cleanup` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | install `next` | N/A: command displays no shortened ID |
|
|
28
|
+
| `launch` | covered: `CLI_UNKNOWN_OPTION` | TTY intent before slow work; machine output clean | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
29
|
+
| `logs` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
30
|
+
| `debug` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage errors covered; no runtime recovery claim | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
31
|
+
| `update` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
32
|
+
| `fixtures` | covered: `CLI_UNKNOWN_OPTION` | N/A: no newly introduced slow path | usage + runtime `error.code/message/userAction` | N/A: discovery, read-only, or terminal result | N/A: command displays no shortened ID |
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Human QA
|
|
2
|
+
|
|
3
|
+
Run this from each product checkout before a release. Verify the visible outcome,
|
|
4
|
+
not only the exit code.
|
|
5
|
+
|
|
6
|
+
## Common
|
|
7
|
+
|
|
8
|
+
- [ ] `mm-harness --version` prints the candidate version.
|
|
9
|
+
- [ ] `mm-harness doctor` detects the correct product and reports only current
|
|
10
|
+
configuration.
|
|
11
|
+
- [ ] A misspelled flag explains the error and gives a runnable next command.
|
|
12
|
+
- [ ] `mm-harness actions` and `mm-harness flows` return useful, bounded discovery.
|
|
13
|
+
- [ ] `mm-harness fixtures set` either applies the fixture or names the missing
|
|
14
|
+
file and exact recovery action.
|
|
15
|
+
- [ ] `mm-harness run <safe-recipe> --artifacts-dir temp/qa-proof` prints findings,
|
|
16
|
+
diagnostics, and absolute evidence paths.
|
|
17
|
+
- [ ] `mm-harness last --json` identifies that run without rerunning it.
|
|
18
|
+
- [ ] `mm-harness doctor --json` is one parseable document with no human decoration.
|
|
19
|
+
|
|
20
|
+
## Extension
|
|
21
|
+
|
|
22
|
+
- [ ] `mm-harness launch` opens/reuses the checkout's named Chrome profile.
|
|
23
|
+
- [ ] `mm-harness launch --sidepanel` opens the MetaMask sidepanel without extra
|
|
24
|
+
Extension tabs.
|
|
25
|
+
- [ ] `mm-harness debug` opens connected DevTools for the active MetaMask target.
|
|
26
|
+
- [ ] `mm-harness logs --source extension` shows Extension console events.
|
|
27
|
+
- [ ] `mm-harness logs --source dapp` shows dapp console events separately.
|
|
28
|
+
- [ ] Edit a visible source string twice, waiting for successful incremental
|
|
29
|
+
compilation after each edit; refresh the browser and confirm both changes.
|
|
30
|
+
- [ ] Revert the edit, confirm a third incremental compilation, and verify the
|
|
31
|
+
same watcher and Chrome process were reused.
|
|
32
|
+
|
|
33
|
+
## Mobile
|
|
34
|
+
|
|
35
|
+
- [ ] `mm-harness launch ios` boots/reuses the intended simulator and opens the app.
|
|
36
|
+
- [ ] `mm-harness launch android` targets the intended emulator/device.
|
|
37
|
+
- [ ] `mm-harness logs --source metro` and `--source app` show distinct streams.
|
|
38
|
+
- [ ] `mm-harness debug` opens the usable React Native developer surface.
|
|
39
|
+
- [ ] Change a visible JS string, confirm Metro rebuilds, reload the app, and see
|
|
40
|
+
the change without a native rebuild.
|
|
41
|
+
|
|
42
|
+
## Core
|
|
43
|
+
|
|
44
|
+
- [ ] `mm-harness status`, `doctor`, `actions`, `flows`, and a safe recipe run work
|
|
45
|
+
without browser/device assumptions.
|
|
46
|
+
|
|
47
|
+
Record the command, version, checkout SHA, visible mismatch, exit code, and JSON
|
|
48
|
+
error envelope for any failure.
|
package/docs/CODE-MAP.md
CHANGED
|
@@ -11,7 +11,7 @@ bin/mm-harness bash front door: MM_HARNESS_BIN dev/prod switch,
|
|
|
11
11
|
│ stale-dist warning), deps ensure
|
|
12
12
|
▼
|
|
13
13
|
src/mm-harness-cli.ts commander surface: grouped colored help, per-command
|
|
14
|
-
│ --help
|
|
14
|
+
│ --help and usage errors; delegates in-process
|
|
15
15
|
▼
|
|
16
16
|
src/cli.ts parse + dispatch ONLY — no verb logic lives here
|
|
17
17
|
│
|
package/docs/UX-PRINCIPLES.md
CHANGED
|
@@ -27,6 +27,8 @@ The CLI answers relative to the checkout it is standing in.
|
|
|
27
27
|
Long operations show intent immediately and progress continuously.
|
|
28
28
|
- First line within milliseconds: what is being done, where (device/port), and
|
|
29
29
|
where the full log lives.
|
|
30
|
+
- Add an explicit first line only when work can block before existing output. Fast
|
|
31
|
+
commands use their result as acknowledgement; they do not print a redundant banner.
|
|
30
32
|
- Leaf output streams live to stderr — `--json` reserves stdout for the envelope,
|
|
31
33
|
it never buys silence. Quiet stretches get heartbeats with elapsed time.
|
|
32
34
|
- Each dev server a command starts gets its tmux log-tail window at launch time
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Video demo validation
|
|
2
|
+
|
|
3
|
+
Candidate: local packed `@deeeed/metamask-harness` 0.17.1 installed globally.
|
|
4
|
+
Run commands from the named checkout; product/ports are auto-detected.
|
|
5
|
+
|
|
6
|
+
## Common
|
|
7
|
+
|
|
8
|
+
- [x] `mm-harness --version` prints `0.17.1` from the global npm install.
|
|
9
|
+
- [x] `yarn check` passes.
|
|
10
|
+
- [x] No `package-lock.json`; Yarn state is valid.
|
|
11
|
+
- [x] Current source/docs contain no compatibility or removed-command paths.
|
|
12
|
+
- [x] `doctor` reports current runtime fields only.
|
|
13
|
+
- [x] Recipe results print diagnostics and absolute artifact paths inline.
|
|
14
|
+
|
|
15
|
+
## Extension (`mme-2`)
|
|
16
|
+
|
|
17
|
+
- [x] `mm-harness doctor` reports healthy CDP and active webpack watcher.
|
|
18
|
+
- [x] `mm-harness run runner.smoke --artifacts-dir temp/final-demo-smoke` passes.
|
|
19
|
+
- [x] Smoke prints six absolute evidence paths and `diagnostics: CLEAN`.
|
|
20
|
+
- [x] `mm-harness debug` connects native DevTools to the active slot target.
|
|
21
|
+
- [x] Fullscreen and sidepanel targets are selectable.
|
|
22
|
+
- [x] Two source edits plus revert compiled incrementally with the same watcher;
|
|
23
|
+
browser refresh showed each change without relaunch.
|
|
24
|
+
|
|
25
|
+
## Mobile (`mm-1`, iOS)
|
|
26
|
+
|
|
27
|
+
- [x] `mm-harness install` writes metadata only; product checkout remains clean.
|
|
28
|
+
- [x] `mm-harness fixtures set` reads the canonical fixture and unlocks `dev1`.
|
|
29
|
+
- [x] `mm-harness call metamask.wallet.ensure_unlocked` passes from the global package.
|
|
30
|
+
- [x] `mm-harness verify` passes after fixture setup.
|
|
31
|
+
- [x] `mm-harness status` reports `WalletView`, unlocked wallet, selected account,
|
|
32
|
+
and fixture `READY`.
|
|
33
|
+
- [ ] During the recording, visually confirm the expected simulator is foregrounded.
|
|
34
|
+
|
|
35
|
+
## Core (`core-1`)
|
|
36
|
+
|
|
37
|
+
- [x] `mm-harness doctor` reports the headless controller ready.
|
|
38
|
+
- [x] Canonical wallet fixture is present.
|
|
39
|
+
- [x] `mm-harness run perps.smoke --artifacts-dir temp/final-demo-smoke` passes.
|
|
40
|
+
- [x] Positions, orders, and account state are read through the controller.
|
|
41
|
+
- [x] Evidence prints absolute paths; diagnostics correctly reports `N/A` for headless Core.
|
|
42
|
+
|
|
43
|
+
## Recording sequence
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Extension
|
|
47
|
+
cd /Users/deeeed/farmslot/repos/mme-2
|
|
48
|
+
mm-harness doctor
|
|
49
|
+
mm-harness actions positions
|
|
50
|
+
mm-harness flows
|
|
51
|
+
mm-harness run runner.smoke --artifacts-dir temp/demo
|
|
52
|
+
|
|
53
|
+
# Mobile
|
|
54
|
+
cd /Users/deeeed/farmslot/repos/mm-1
|
|
55
|
+
mm-harness launch ios
|
|
56
|
+
mm-harness fixtures set
|
|
57
|
+
mm-harness call metamask.wallet.ensure_unlocked
|
|
58
|
+
|
|
59
|
+
# Core
|
|
60
|
+
cd /Users/deeeed/farmslot/repos/core-1
|
|
61
|
+
mm-harness run perps.smoke --artifacts-dir temp/demo
|
|
62
|
+
```
|