@deeeed/metamask-harness 0.68.0 → 0.69.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 +11 -0
- package/dist/adapters/mobile/prepare.js +7 -4
- package/dist/adapters/mobile/runtime-decision.js +8 -10
- package/dist/command-contract.js +7 -0
- package/dist/commands/call.js +3 -0
- package/dist/commands/launch/index.js +19 -0
- package/dist/commands/list-executables.js +62 -11
- package/dist/commands/manifest.js +11 -4
- package/dist/commands/run.js +3 -0
- package/dist/mm-harness-cli.js +7 -0
- package/docs/RECIPES.md +10 -3
- package/package.json +1 -1
- package/scripts/completions.sh +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.69.0 - 2026-09-30
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Group and color recipe/action listings by domain, show library origins and paths separately, and support domain/source filters and domain/name/library sorting. Keep recipe parameter details in `--describe` and `--json`.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Accept repeated `task init --acceptance` flags at the public CLI boundary so Recipe Cook can record authored criteria in the task document and structured handoff.
|
|
14
|
+
- Report Mobile launch dependency blockers once with the dependency repair command; preserve the readiness cause instead of labelling it app logic or adding a conflicting doctor hint.
|
|
15
|
+
|
|
5
16
|
## 0.68.0 - 2026-09-29
|
|
6
17
|
|
|
7
18
|
### Changed
|
|
@@ -3,6 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { recordDepsBaseline } from "@farmslot/recipe-harness/runtime/deps-readiness";
|
|
4
4
|
import { resolveAndExportMobileToolPaths } from "../../../library/actions/mobile/platform/tool-paths.mjs";
|
|
5
5
|
import { EXIT, spawnScriptStreaming } from "../../commands/shared.js";
|
|
6
|
+
import { shellQuote } from "../../commands/parse-args.js";
|
|
6
7
|
import { runnerDir } from "../../paths.js";
|
|
7
8
|
import {
|
|
8
9
|
decideMobileReadiness,
|
|
@@ -51,12 +52,14 @@ async function prepareMobile(target, opts = {}) {
|
|
|
51
52
|
});
|
|
52
53
|
if (report.decision === "blocked") {
|
|
53
54
|
const reasons = report.reasons.join(" ");
|
|
54
|
-
const next = report.userAction ??
|
|
55
|
+
const next = report.userAction ?? `mm-harness logs --target ${shellQuote(target)} --full`;
|
|
55
56
|
const msg = `mobile prepare blocked: ${reasons}
|
|
56
57
|
Next: ${next}`;
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
return {
|
|
59
|
+
status: EXIT.runtime,
|
|
60
|
+
output: msg,
|
|
61
|
+
readinessBlock: { reasonCode: report.reasonCode, message: reasons, userAction: next }
|
|
62
|
+
};
|
|
60
63
|
}
|
|
61
64
|
const iosAccessibilityChanged = platform === "ios" && report.decision !== "unknown" && enableIosAccessibility(json);
|
|
62
65
|
const rebuildNative = preflightMode === "rebuild-native" || preflightMode === "clean";
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "@farmslot/recipe-harness/runtime/log-analysis";
|
|
13
13
|
import { probeMetroPackager } from "@farmslot/recipe-harness/runtime/metro-probe";
|
|
14
14
|
import { color } from "../../cli-color.js";
|
|
15
|
+
import { shellQuote } from "../../commands/parse-args.js";
|
|
15
16
|
import { recipeRuntimePath } from "../../paths.js";
|
|
16
17
|
import { resolveMobileToolPath } from "../../../library/actions/mobile/platform/tool-paths.mjs";
|
|
17
18
|
import { mobileProductMarkers } from "./deps-markers.js";
|
|
@@ -149,7 +150,7 @@ const launchActions = (target, clearMetro = false) => {
|
|
|
149
150
|
return actions;
|
|
150
151
|
};
|
|
151
152
|
function depsInstallCommand(resolved) {
|
|
152
|
-
return fs.existsSync(path.join(resolved, "node_modules")) ? "
|
|
153
|
+
return fs.existsSync(path.join(resolved, "node_modules")) ? "yarn setup:expo --no-build-ios --no-build-android" : "yarn install --immutable && yarn setup:expo --no-build-ios --no-build-android";
|
|
153
154
|
}
|
|
154
155
|
async function decideMobileReadiness(target, options = {}) {
|
|
155
156
|
const resolved = path.resolve(target);
|
|
@@ -157,18 +158,15 @@ async function decideMobileReadiness(target, options = {}) {
|
|
|
157
158
|
const fast = options.preflightMode === "fast";
|
|
158
159
|
const report = await computeMobileReadiness(resolved, options, fast);
|
|
159
160
|
if (fast && report.decision === "install") {
|
|
160
|
-
const standalone = depsInstallCommand(resolved);
|
|
161
161
|
return {
|
|
162
162
|
...report,
|
|
163
163
|
decision: "blocked",
|
|
164
164
|
reasonCode: "deps-not-ready",
|
|
165
165
|
reasons: [
|
|
166
|
-
|
|
167
|
-
|
|
166
|
+
...report.reasons,
|
|
167
|
+
"Quick launch does not install dependencies."
|
|
168
168
|
],
|
|
169
|
-
userAction: `
|
|
170
|
-
2. Orchestrated slot \u2014 run its deps/prepare phase.
|
|
171
|
-
After the selected action succeeds, re-run \`mm-harness launch <platform>\`.`,
|
|
169
|
+
userAction: `cd ${shellQuote(resolved)} && ${depsInstallCommand(resolved)}`,
|
|
172
170
|
actions: []
|
|
173
171
|
};
|
|
174
172
|
}
|
|
@@ -189,9 +187,9 @@ async function decideMobileReadiness(target, options = {}) {
|
|
|
189
187
|
)
|
|
190
188
|
};
|
|
191
189
|
}
|
|
192
|
-
function writeDepsNote(
|
|
190
|
+
function writeDepsNote() {
|
|
193
191
|
const stream = process.stderr;
|
|
194
|
-
const body =
|
|
192
|
+
const body = "No install baseline is recorded. Required package checks passed; timestamps alone do not establish dependency drift. Reusing the installed dependencies.";
|
|
195
193
|
stream.write(
|
|
196
194
|
`
|
|
197
195
|
${color("warn", "mm-harness note (deps):", { stream })} ${color("dim", body, { stream })}
|
|
@@ -206,7 +204,6 @@ async function computeMobileReadiness(resolved, options, fast) {
|
|
|
206
204
|
let deps = rawDeps;
|
|
207
205
|
if (rawDeps.status === "stale" && !rawDeps.hasBaseline) {
|
|
208
206
|
deps = { installed: rawDeps.installed, status: "current", hasBaseline: false };
|
|
209
|
-
writeDepsNote(resolved);
|
|
210
207
|
}
|
|
211
208
|
if (deps.status === "current") {
|
|
212
209
|
const absent = missingRequiredDeps(resolved);
|
|
@@ -325,6 +322,7 @@ async function computeMobileReadiness(resolved, options, fast) {
|
|
|
325
322
|
actions: installActions(resolved)
|
|
326
323
|
};
|
|
327
324
|
}
|
|
325
|
+
if (rawDeps.status === "stale" && !rawDeps.hasBaseline) writeDepsNote();
|
|
328
326
|
if (metro.status === "down") {
|
|
329
327
|
return {
|
|
330
328
|
schemaVersion: 1,
|
package/dist/command-contract.js
CHANGED
|
@@ -168,6 +168,7 @@ const PUBLIC_COMMAND_CONTRACTS = {
|
|
|
168
168
|
"--title": value(),
|
|
169
169
|
"--task-text": value(),
|
|
170
170
|
"--task-file": value(),
|
|
171
|
+
"--acceptance": value(),
|
|
171
172
|
"--ticket": value(),
|
|
172
173
|
"--source-ref": value(),
|
|
173
174
|
"--surface": value(),
|
|
@@ -241,6 +242,9 @@ const PUBLIC_COMMAND_CONTRACTS = {
|
|
|
241
242
|
call: {
|
|
242
243
|
options: options(HELP, JSON, TARGET, ADAPTER, MOBILE_PLATFORM, DEVICE, RUNTIME_PORTS, RECIPE_RUNTIME, {
|
|
243
244
|
"--list": bool(),
|
|
245
|
+
"--domain": value(),
|
|
246
|
+
"--source": value(),
|
|
247
|
+
"--sort": value(["domain", "name", "library"]),
|
|
244
248
|
"--arg": value()
|
|
245
249
|
}),
|
|
246
250
|
positionals: [{ label: "action" }],
|
|
@@ -253,6 +257,9 @@ const PUBLIC_COMMAND_CONTRACTS = {
|
|
|
253
257
|
run: {
|
|
254
258
|
options: options(HELP, JSON, JSON_STREAM, TARGET, ADAPTER, MOBILE_PLATFORM, DEVICE, RUNTIME_PORTS, RECIPE_RUNTIME, RUN_FUNDING, {
|
|
255
259
|
"--list": bool(),
|
|
260
|
+
"--domain": value(),
|
|
261
|
+
"--source": value(),
|
|
262
|
+
"--sort": value(["domain", "name", "library"]),
|
|
256
263
|
"--plan": bool(),
|
|
257
264
|
"--proof": bool(),
|
|
258
265
|
"--describe": bool(),
|
package/dist/commands/call.js
CHANGED
|
@@ -65,6 +65,9 @@ async function handleCall(argv) {
|
|
|
65
65
|
}
|
|
66
66
|
const { action: shortName, args, rest } = parseCallArgs(argv);
|
|
67
67
|
const { options } = parseArgs(rest, "call");
|
|
68
|
+
if (["domain", "source", "sort"].some((name) => optionString(options, name) !== void 0)) {
|
|
69
|
+
throw usageError("--domain, --source, and --sort require call --list.");
|
|
70
|
+
}
|
|
68
71
|
const json = optionFlag(options, "json");
|
|
69
72
|
if (!shortName) {
|
|
70
73
|
let example = "mm-harness call <action>";
|
|
@@ -320,6 +320,10 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
320
320
|
if (attempt.status === 0) {
|
|
321
321
|
return await settleLaunch(jsonOutput, machine, stream, adapter, mobileTarget, tier, displayMode, target, state, wantWatch, wantVerify, remoteFlagRequest, true, attempt.remoteFeatureFlags);
|
|
322
322
|
}
|
|
323
|
+
const readinessFailure = mobileReadinessFailure(attempt);
|
|
324
|
+
if (readinessFailure) {
|
|
325
|
+
return launchFail(jsonOutput, stream, adapter, mobileTarget, tier, state, target, readinessFailure);
|
|
326
|
+
}
|
|
323
327
|
if (adapter === "extension" && attempt.output.includes("REMOTE_FLAG_NOT_PINNABLE:")) {
|
|
324
328
|
return launchFail(jsonOutput, stream, adapter, mobileTarget, tier, state, target, {
|
|
325
329
|
code: "REMOTE_FLAG_NOT_PINNABLE",
|
|
@@ -503,6 +507,10 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
503
507
|
state.recovered.push(recoveryCode);
|
|
504
508
|
return await settleLaunch(jsonOutput, machine, stream, adapter, mobileTarget, tier, displayMode, target, state, wantWatch, wantVerify, remoteFlagRequest, true, attempt.remoteFeatureFlags);
|
|
505
509
|
}
|
|
510
|
+
const retryReadinessFailure = mobileReadinessFailure(attempt);
|
|
511
|
+
if (retryReadinessFailure) {
|
|
512
|
+
return launchFail(jsonOutput, stream, adapter, mobileTarget, tier, state, target, retryReadinessFailure);
|
|
513
|
+
}
|
|
506
514
|
return launchFail(jsonOutput, stream, adapter, mobileTarget, tier, state, target, {
|
|
507
515
|
code: "SAME_RECOVERY_TWICE",
|
|
508
516
|
message: `launch ${adapter}${mobileTarget ? ` ${mobileTarget}` : ""} failed again after ${recoveryCode} recovery \u2014 refusing to loop.`,
|
|
@@ -511,6 +519,17 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
511
519
|
originalError: attempt.output.trim() || void 0
|
|
512
520
|
});
|
|
513
521
|
}
|
|
522
|
+
function mobileReadinessFailure(result) {
|
|
523
|
+
const block = result.readinessBlock;
|
|
524
|
+
if (!block) return void 0;
|
|
525
|
+
return {
|
|
526
|
+
code: block.reasonCode === "deps-not-ready" ? "MOBILE_DEPENDENCIES_NOT_READY" : "MOBILE_READINESS_BLOCKED",
|
|
527
|
+
message: block.message,
|
|
528
|
+
userAction: block.userAction,
|
|
529
|
+
recoverable: false,
|
|
530
|
+
exitCode: result.status
|
|
531
|
+
};
|
|
532
|
+
}
|
|
514
533
|
function extensionQuickReattachFailed(output) {
|
|
515
534
|
return /\[reattach\]|extension quick reattach|mm-harness launch --build/u.test(output);
|
|
516
535
|
}
|
|
@@ -5,13 +5,14 @@ import { importRecipeProtocol } from "../paths.js";
|
|
|
5
5
|
import { color } from "../cli-color.js";
|
|
6
6
|
import { EXIT } from "./shared.js";
|
|
7
7
|
import { optionFlag, optionString, optionStrings, resolveAdapter, shellQuoteArg } from "./parse-args.js";
|
|
8
|
-
import { describeManifestActions, renderHumanActionCatalog } from "./manifest.js";
|
|
8
|
+
import { actionDomain, describeManifestActions, renderHumanActionCatalog } from "./manifest.js";
|
|
9
9
|
import {
|
|
10
10
|
describeRunnableRecipe,
|
|
11
11
|
listRunnableRecipes,
|
|
12
12
|
resolveMetaMaskLibrarySources
|
|
13
13
|
} from "./run-engine.js";
|
|
14
14
|
const finalSegment = (name) => name.split(".").pop() ?? name;
|
|
15
|
+
const recipeDomain = (name) => name.includes(".") ? name.split(".")[0].replaceAll("_", "-") : "general";
|
|
15
16
|
function catalogDescription(description) {
|
|
16
17
|
if (!description) return "";
|
|
17
18
|
const firstSentence = description.match(/^.*?[.!?](?:\s|$)/u)?.[0]?.trim() ?? description;
|
|
@@ -20,23 +21,64 @@ function catalogDescription(description) {
|
|
|
20
21
|
async function handleListExecutables(command, options) {
|
|
21
22
|
const json = optionFlag(options, "json");
|
|
22
23
|
const { adapter } = resolveAdapter(options);
|
|
24
|
+
const domainFilter = optionString(options, "domain")?.replaceAll("_", "-");
|
|
25
|
+
const sourceFilter = optionString(options, "source");
|
|
26
|
+
const sort = optionString(options, "sort") ?? (json ? "name" : "domain");
|
|
27
|
+
const groupKey = (entry, domain) => sort === "library" ? entry.source : sort === "domain" ? domain : "";
|
|
28
|
+
const select = (available2, domainOf) => available2.filter((entry) => (!domainFilter || domainOf(entry) === domainFilter) && (!sourceFilter || entry.source === sourceFilter)).sort((left, right) => groupKey(left, domainOf(left)).localeCompare(groupKey(right, domainOf(right))) || left.name.localeCompare(right.name));
|
|
29
|
+
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
30
|
+
const stylesFor = (domains) => {
|
|
31
|
+
const palette = ["label", "accent", "ok", "warn"];
|
|
32
|
+
return new Map([...new Set(domains)].sort().map((domain, index) => [domain, palette[index % palette.length]]));
|
|
33
|
+
};
|
|
23
34
|
const librarySources = await resolveMetaMaskLibrarySources(optionStrings(options, "library"));
|
|
35
|
+
const libraryLines = () => {
|
|
36
|
+
const lines = ["Libraries loaded:"];
|
|
37
|
+
for (const source of librarySources ?? []) {
|
|
38
|
+
const name = source.name ?? path.basename(source.root);
|
|
39
|
+
const kind = source.provenance?.kind === "bundled" ? "bundled" : "linked";
|
|
40
|
+
lines.push(` ${out("bold", name)} ${kind} \xB7 ${source.root}`);
|
|
41
|
+
}
|
|
42
|
+
if (domainFilter || sourceFilter) {
|
|
43
|
+
lines.push(`Filter: ${[domainFilter && `domain=${domainFilter}`, sourceFilter && `source=${sourceFilter}`].filter(Boolean).join(" ")}`);
|
|
44
|
+
}
|
|
45
|
+
return lines;
|
|
46
|
+
};
|
|
24
47
|
if (command === "run") {
|
|
25
|
-
const
|
|
48
|
+
const available2 = await listRunnableRecipes(adapter, librarySources);
|
|
49
|
+
const recipes = select(available2, (recipe) => recipeDomain(recipe.name));
|
|
26
50
|
if (json) {
|
|
27
51
|
console.log(JSON.stringify({ schemaVersion: 1, command, action: "list", adapter, recipes }, null, 2));
|
|
28
52
|
return EXIT.ok;
|
|
29
53
|
}
|
|
30
|
-
const
|
|
54
|
+
const domainStyles = stylesFor(available2.map((recipe) => recipeDomain(recipe.name)));
|
|
31
55
|
console.log(out("bold", `runnable recipes (${adapter})`));
|
|
56
|
+
for (const line of libraryLines()) console.log(line);
|
|
32
57
|
console.log(out("comment", "Inspect: mm-harness run <recipe> --describe"));
|
|
58
|
+
if (recipes.length === 0) {
|
|
59
|
+
console.log("No runnable recipes match.");
|
|
60
|
+
console.log(`Available domains: ${[...domainStyles.keys()].join(", ") || "none"}`);
|
|
61
|
+
console.log(`Available sources: ${[...new Set(available2.map((recipe) => recipe.source))].join(", ") || "none"}`);
|
|
62
|
+
}
|
|
63
|
+
const groups = /* @__PURE__ */ new Map();
|
|
33
64
|
for (const recipe of recipes) {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
65
|
+
const group = groupKey(recipe, recipeDomain(recipe.name));
|
|
66
|
+
groups.set(group, [...groups.get(group) ?? [], recipe]);
|
|
67
|
+
}
|
|
68
|
+
for (const [group, entries] of groups) {
|
|
69
|
+
if (group) {
|
|
70
|
+
const label = sort === "library" ? `Library: ${group}` : group;
|
|
71
|
+
console.log(`
|
|
72
|
+
${out(sort === "domain" ? domainStyles.get(group) : "bold", label)} ${out("dim", `(${entries.length})`)}`);
|
|
73
|
+
}
|
|
74
|
+
for (const recipe of entries) {
|
|
75
|
+
const style = domainStyles.get(recipeDomain(recipe.name));
|
|
76
|
+
const shadowed = recipe.shadows.length > 0 ? ` shadows=${recipe.shadows.join(",")}` : "";
|
|
77
|
+
const variant = recipe.variant ? ` variant=${recipe.variant}` : " variant=all";
|
|
78
|
+
const summary = catalogDescription(recipe.description);
|
|
79
|
+
console.log(` ${out(style, recipe.name)} ${out("dim", `library=${recipe.source}${variant}${shadowed}`)}`);
|
|
80
|
+
if (summary) console.log(` ${summary}`);
|
|
81
|
+
}
|
|
40
82
|
}
|
|
41
83
|
return EXIT.ok;
|
|
42
84
|
}
|
|
@@ -47,9 +89,10 @@ async function handleListExecutables(command, options) {
|
|
|
47
89
|
);
|
|
48
90
|
const { getRecipeActionManifestActionNames } = await importRecipeProtocol();
|
|
49
91
|
const protocolNames = new Set(getRecipeActionManifestActionNames(manifest));
|
|
50
|
-
const
|
|
92
|
+
const available = describeManifestActions(manifest, actionSources).filter((entry) => protocolNames.has(entry.name)).sort((left, right) => left.name.localeCompare(right.name));
|
|
93
|
+
const described = select(available, actionDomain);
|
|
51
94
|
const shortCounts = /* @__PURE__ */ new Map();
|
|
52
|
-
for (const entry of
|
|
95
|
+
for (const entry of available) {
|
|
53
96
|
const short = finalSegment(entry.name);
|
|
54
97
|
shortCounts.set(short, (shortCounts.get(short) ?? 0) + 1);
|
|
55
98
|
}
|
|
@@ -73,9 +116,17 @@ async function handleListExecutables(command, options) {
|
|
|
73
116
|
console.log(JSON.stringify({ schemaVersion: 1, command, action: "list", adapter, actions }, null, 2));
|
|
74
117
|
return EXIT.ok;
|
|
75
118
|
}
|
|
119
|
+
const contextLines = libraryLines();
|
|
120
|
+
if (described.length === 0) {
|
|
121
|
+
contextLines.push(`Available domains: ${[...new Set(available.map(actionDomain))].sort().join(", ") || "none"}`);
|
|
122
|
+
contextLines.push(`Available sources: ${[...new Set(available.map((entry) => entry.source))].sort().join(", ") || "none"}`);
|
|
123
|
+
}
|
|
76
124
|
console.log(renderHumanActionCatalog(described, {
|
|
77
125
|
title: `invocable actions (${adapter})`,
|
|
78
126
|
guidance: "Use: mm-harness call <name> \xB7 short names shown first where unambiguous",
|
|
127
|
+
sort,
|
|
128
|
+
domainStyles: stylesFor(available.map(actionDomain)),
|
|
129
|
+
contextLines,
|
|
79
130
|
displayName: (entry) => {
|
|
80
131
|
const short = shortByName.get(entry.name);
|
|
81
132
|
return short ? `${short} (${entry.name})` : `${entry.name} (full name only \u2014 ambiguous short)`;
|
|
@@ -345,16 +345,21 @@ function summarizeDescribedAction(entry) {
|
|
|
345
345
|
const { schema: _schema, examples: _examples, ...summary } = entry;
|
|
346
346
|
return summary;
|
|
347
347
|
}
|
|
348
|
+
function actionDomain(entry) {
|
|
349
|
+
return entry.category.replaceAll("_", "-");
|
|
350
|
+
}
|
|
348
351
|
function renderHumanActionCatalog(actions, options) {
|
|
349
352
|
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
350
353
|
const groups = /* @__PURE__ */ new Map();
|
|
351
354
|
for (const entry of actions) {
|
|
352
|
-
const group = actionDomainGroup(entry);
|
|
355
|
+
const group = options.sort === "name" ? "" : options.sort === "library" ? `Library: ${entry.source}` : options.sort === "domain" ? actionDomain(entry) : actionDomainGroup(entry);
|
|
353
356
|
groups.set(group, [...groups.get(group) ?? [], entry]);
|
|
354
357
|
}
|
|
355
|
-
const lines = [out("bold", options.title), out("comment", options.guidance)];
|
|
358
|
+
const lines = [out("bold", options.title), ...options.contextLines ?? [], out("comment", options.guidance)];
|
|
359
|
+
if (actions.length === 0) lines.push("No callable actions match.");
|
|
356
360
|
for (const [group, entries] of groups) {
|
|
357
|
-
|
|
361
|
+
const groupStyle = options.sort === "domain" ? options.domainStyles?.get(group) ?? "accent" : group === "official" ? "label" : "accent";
|
|
362
|
+
if (group) lines.push("", `${out(groupStyle, group)} ${out("dim", `(${entries.length})`)}`);
|
|
358
363
|
for (const entry of entries) {
|
|
359
364
|
const fieldNames = humanFieldNames(entry);
|
|
360
365
|
const fields = fieldNames.length ? ` ${out("dim", `fields=${fieldNames.join(",")}`)}` : "";
|
|
@@ -362,7 +367,8 @@ function renderHumanActionCatalog(actions, options) {
|
|
|
362
367
|
const description = entry.description ? ` ${out("comment", `\u2014 ${entry.description}`)}` : "";
|
|
363
368
|
const source = ` ${out("dim", `[${entry.source}]`)}`;
|
|
364
369
|
const risk = entry.capabilities.length ? ` ${out("dim", `risk=${entry.capabilities.join(",")}`)}` : "";
|
|
365
|
-
|
|
370
|
+
const nameStyle = options.domainStyles?.get(actionDomain(entry)) ?? "cmd";
|
|
371
|
+
lines.push(` ${out(nameStyle, options.displayName?.(entry) ?? entry.name)}${source}${fields}${cases}${risk}${description}`);
|
|
366
372
|
}
|
|
367
373
|
}
|
|
368
374
|
return lines.join("\n");
|
|
@@ -585,6 +591,7 @@ function levenshtein(left, right) {
|
|
|
585
591
|
}
|
|
586
592
|
export {
|
|
587
593
|
ACTION_MATRIX_ADAPTERS,
|
|
594
|
+
actionDomain,
|
|
588
595
|
actionExampleCommand,
|
|
589
596
|
actionLibraryContextArgs,
|
|
590
597
|
describeManifestActions,
|
package/dist/commands/run.js
CHANGED
|
@@ -147,6 +147,9 @@ async function handleRun(parsed) {
|
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
149
|
async function handleRunInner({ positional, options }, stream) {
|
|
150
|
+
if (!optionFlag(options, "list") && ["domain", "source", "sort"].some((name) => optionString(options, name) !== void 0)) {
|
|
151
|
+
throw usageError("--domain, --source, and --sort require run --list.");
|
|
152
|
+
}
|
|
150
153
|
if (optionFlag(options, "describe") && (optionFlag(options, "list") || optionFlag(options, "plan"))) {
|
|
151
154
|
const message = "--describe cannot be combined with --list or --plan.";
|
|
152
155
|
const userAction = "choose one: mm-harness run --list, mm-harness run <recipe> --describe, or mm-harness run <recipe> --plan";
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -298,6 +298,7 @@ Example:
|
|
|
298
298
|
Task:
|
|
299
299
|
--title <t> Task title (required)
|
|
300
300
|
--task-text <s> | --task-file <p> Task description, inline or from a file
|
|
301
|
+
--acceptance <criterion> Authored acceptance criterion (repeatable, preserved in order)
|
|
301
302
|
--ticket <key> Ticket key
|
|
302
303
|
--source-ref <url> Where the task came from
|
|
303
304
|
|
|
@@ -430,6 +431,9 @@ Example:
|
|
|
430
431
|
mm-harness actions.
|
|
431
432
|
|
|
432
433
|
--list List actions accepted by call for this adapter; no <action> needed
|
|
434
|
+
--domain <name> With --list: filter by action domain, e.g. perps or swap-bridge
|
|
435
|
+
--source <name> With --list: filter by origin, e.g. metamask, perps, or official
|
|
436
|
+
--sort <domain|name|library> With --list: grouping/order (default: domain; JSON: name)
|
|
433
437
|
--arg k=v Action field value (repeatable; equivalent to key=value shorthand)
|
|
434
438
|
--device <udid|serial|name> Mobile only: target this device (sets IOS_SIMULATOR / ADB_SERIAL and updates agentic-runtime.json). Without it, >1 connected mobile device fails fast and lists them. Do not shut down other devices to force selection.
|
|
435
439
|
--cdp-port <port> Extension only: bind the action to this live browser runtime.
|
|
@@ -464,6 +468,9 @@ Remote feature flags (mobile applies at runtime; extension pins at launch):
|
|
|
464
468
|
|
|
465
469
|
Discovery:
|
|
466
470
|
--list List complete recipes accepted by run for this adapter; no <recipe> needed
|
|
471
|
+
--domain <name> With --list: filter by recipe domain, e.g. perps or swap-bridge
|
|
472
|
+
--source <name> With --list: filter by library origin, e.g. metamask or perps
|
|
473
|
+
--sort <domain|name|library> With --list: grouping/order (default: domain; JSON: name)
|
|
467
474
|
--describe Show one recipe's parameters, composed actions/recipes, and runnable command
|
|
468
475
|
--plan Validate + print execution plan, touching nothing. Exit 5 if invalid.
|
|
469
476
|
--proof Preflight behavioral proof bindings before planning/execution; runtime evidence is still required.
|
package/docs/RECIPES.md
CHANGED
|
@@ -542,12 +542,19 @@ selected deterministically from `recipes/<adapter>/<domain>/`. Identical
|
|
|
542
542
|
cross-adapter graphs live once in `recipes/shared/<domain>/`; an adapter-specific
|
|
543
543
|
variant takes precedence. Both resolve as `<domain>.<recipe>`, without a platform
|
|
544
544
|
or `shared` prefix. Keep domains below `core`, `extension`, `mobile`, or `shared`.
|
|
545
|
-
|
|
546
|
-
`*.<adapter>.recipe.json` paths remain readable during migration, but new
|
|
547
|
-
migrated libraries use the adapter-first layout. A file cannot declare its
|
|
545
|
+
|
|
546
|
+
Legacy `*.<adapter>.recipe.json` paths remain readable during migration, but new
|
|
547
|
+
and migrated libraries use the adapter-first layout. A file cannot declare its
|
|
548
548
|
adapter through both forms. Every run records the root recipe, exact resolved
|
|
549
549
|
dependency documents, their digests, call edges, selected sources, and shadows.
|
|
550
550
|
|
|
551
|
+
`mm-harness run --list` and `mm-harness call --list` group recipes/actions by domain
|
|
552
|
+
and show their library origins separately.
|
|
553
|
+
Use `--domain swap-bridge` or `--source perps` to filter, and `--sort name|domain|library`
|
|
554
|
+
to change the order. These options also apply to `--json` output, which keeps name
|
|
555
|
+
ordering unless `--sort` is supplied. Domain filters accept hyphens or underscores,
|
|
556
|
+
so `--domain swap-bridge` includes `metamask.swap_bridge.*` actions.
|
|
557
|
+
|
|
551
558
|
## Direct product access
|
|
552
559
|
|
|
553
560
|
The bundled library is intentionally small. A task may use a verified controller
|
package/package.json
CHANGED
package/scripts/completions.sh
CHANGED
|
@@ -44,8 +44,8 @@ _mmh_flags_for() {
|
|
|
44
44
|
execution-template) printf '%s' "--dir --domain-dir --project-worker --project-name --package-templates --package-id --flow --run-mode --platform --domain --id --provenance --include-shadowed --no-include-shadowed --title --force --json" ;;
|
|
45
45
|
recipe-quality) printf '%s' "--input --output --recipe --task --evidence --params --adapter --library --advisor --advisor-model --advisor-stages --json" ;;
|
|
46
46
|
actions) printf '%s' "--adapter --action --kind --raw --action-manifest --target --json" ;;
|
|
47
|
-
call) printf '%s' "--list --arg --adapter --target --artifacts-dir --action-manifest --heal --json" ;;
|
|
48
|
-
run) printf '%s' "--list --describe --plan --adapter --artifacts-dir --target --library --cdp-port --heal --record-video --json" ;;
|
|
47
|
+
call) printf '%s' "--list --domain --source --sort --arg --adapter --target --artifacts-dir --action-manifest --heal --json" ;;
|
|
48
|
+
run) printf '%s' "--list --domain --source --sort --describe --plan --adapter --artifacts-dir --target --library --cdp-port --heal --record-video --json" ;;
|
|
49
49
|
doctor) printf '%s' "--adapter --target --fix --json" ;;
|
|
50
50
|
install|verify|cleanup) printf '%s' "--adapter --target --json" ;;
|
|
51
51
|
*) printf '%s' "" ;;
|