@deeeed/metamask-harness 0.17.5 → 0.19.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 +1059 -0
- package/README.md +91 -140
- package/adapters/core/inject.sh +1 -6
- package/adapters/extension/console-tail.mjs +55 -24
- package/adapters/extension/ensure-browser.sh +6 -1
- package/adapters/extension/inject.mjs +7 -13
- package/adapters/extension/launch-browser.cjs +7 -1
- package/adapters/extension/launch-webpack.cjs +83 -0
- package/adapters/extension/launch.sh +8 -11
- package/adapters/extension/lib/chrome-args.cjs +8 -1
- package/adapters/extension/lib/macos-focus.cjs +32 -0
- package/adapters/extension/live.sh +12 -21
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/seed-fixture.sh +4 -12
- package/adapters/extension/sidepanel-toggle.sh +4 -1
- package/adapters/extension/stamp-runtime-title.cjs +58 -0
- package/adapters/extension/start-watch.sh +17 -4
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/sync-webpack-dist.cjs +107 -0
- package/adapters/extension/verify.sh +2 -2
- package/adapters/manifest.json +53 -37
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +16 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
- package/adapters/mobile/cleanup.sh +16 -176
- package/adapters/mobile/inject.sh +91 -671
- package/adapters/mobile/launch-console-forwarder.cjs +39 -0
- package/adapters/mobile/open-device.sh +45 -7
- package/adapters/mobile/start-console-forwarder.sh +70 -0
- package/adapters/mobile/start-metro.sh +0 -41
- package/adapters/mobile/verify.sh +34 -83
- package/adapters/mobile/wait-for-bridge.sh +22 -8
- package/adapters/shared/harness-source-fingerprint.mjs +14 -12
- package/adapters/shared/resolve-slot-ports-core.mjs +17 -6
- package/adapters/shared/resolve-slot-ports.sh +17 -5
- package/adapters/shared/update-check-worker.mjs +43 -0
- package/bin/mm-harness +21 -2
- package/dist/adapters/core/surface.js +4 -1
- package/dist/adapters/extension/console-capture.js +95 -0
- package/dist/adapters/extension/product-config.js +110 -0
- package/dist/adapters/extension/runtime-decision.js +20 -2
- package/dist/adapters/extension/surface.js +22 -3
- package/dist/adapters/harness-freshness.js +49 -0
- package/dist/adapters/mobile/perps-env.js +43 -3
- package/dist/adapters/mobile/prepare.js +39 -7
- package/dist/adapters/mobile/surface.js +5 -2
- package/dist/adapters/resolve-slot-ports.js +2 -2
- package/dist/adapters/slot-ports.js +13 -16
- package/dist/adapters.js +56 -16
- package/dist/checkout-lock.js +27 -2
- package/dist/cli-color.js +19 -0
- package/dist/cli-commands.js +1 -1
- package/dist/cli.js +7 -18
- package/dist/command-contract.js +462 -0
- package/dist/command-journal.js +263 -0
- package/dist/commands/call.js +252 -57
- package/dist/commands/check.js +10 -4
- package/dist/commands/completion-candidates.js +17 -22
- package/dist/commands/device-target.js +27 -12
- package/dist/commands/doctor.js +106 -25
- package/dist/commands/fixtures.js +142 -35
- package/dist/commands/last.js +60 -0
- package/dist/commands/launch/extension.js +40 -17
- package/dist/commands/launch/index.js +207 -63
- package/dist/commands/launch/mobile.js +2 -0
- package/dist/commands/list-executables.js +187 -29
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +286 -35
- package/dist/commands/parse-args.js +68 -4
- package/dist/commands/provision.js +10 -4
- package/dist/commands/run-engine.js +772 -264
- package/dist/commands/run-report.js +12 -3
- package/dist/commands/run.js +378 -85
- package/dist/commands/shared.js +76 -4
- package/dist/commands/status-probe.js +4 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/stop.js +7 -2
- package/dist/commands/update.js +16 -28
- package/dist/completions-cache.js +1 -1
- package/dist/doctor.js +57 -10
- package/dist/harness.js +47 -137
- package/dist/heal-bounds.js +7 -2
- package/dist/json-stream.js +57 -0
- package/dist/live-adapter-contract.js +138 -32
- package/dist/manifest.js +161 -1
- package/dist/mm-harness-cli.js +146 -73
- package/dist/paths.js +2 -5
- package/dist/recipe-security.js +178 -0
- package/dist/run-diagnostics.js +261 -0
- package/dist/run-recording.js +1 -1
- package/dist/runner.js +140 -9
- package/docs/CONTRIBUTING.md +136 -0
- package/docs/QA.md +184 -0
- package/docs/RECIPES.md +122 -0
- package/docs/SECURITY.md +88 -0
- package/library/README.md +7 -3
- package/library/actions/core/perps/_controller.mjs +43 -56
- package/library/actions/core/perps/assert_orders.mjs +6 -7
- package/library/actions/core/perps/assert_positions.mjs +6 -7
- package/library/actions/core/perps/close_orders.mjs +2 -0
- package/library/actions/core/perps/close_positions.mjs +2 -0
- package/library/actions/core/perps/ensure_orders.mjs +4 -2
- package/library/actions/core/perps/ensure_positions.mjs +4 -2
- package/library/actions/core/perps/place_order.mjs +7 -3
- package/library/actions/core/perps/read_account.mjs +2 -2
- package/library/actions/core/perps/read_orders.mjs +2 -1
- package/library/actions/core/perps/read_positions.mjs +2 -1
- package/library/actions/core/wallet/list_accounts.mjs +95 -0
- package/library/actions/extension/perps/assert_orders.mjs +2 -1
- package/library/actions/extension/perps/assert_positions.mjs +2 -1
- package/library/actions/extension/perps/perps.mjs +43 -14
- package/library/actions/extension/platform/cdp.mjs +1 -0
- package/library/actions/extension/wallet/list_accounts.mjs +41 -0
- package/library/actions/mobile/perps/assert_orders.mjs +2 -1
- package/library/actions/mobile/perps/assert_positions.mjs +2 -1
- package/library/actions/mobile/perps/perps.mjs +40 -12
- package/library/actions/mobile/wallet/list_accounts.mjs +37 -0
- package/library/library.json +1 -1
- package/library/manifests/core.action-manifest.json +1214 -396
- package/library/manifests/extension.action-manifest.json +1635 -728
- package/library/manifests/mobile.action-manifest.json +1740 -727
- package/library/recipes/app/lifecycle.android-smoke.mobile.recipe.json +63 -81
- package/library/recipes/perps/clean-market-testnet.core.recipe.json +44 -0
- package/library/recipes/perps/clean-market-testnet.recipe.json +49 -0
- package/library/recipes/perps/lifecycle.recipe.json +136 -180
- package/library/recipes/perps/order-lifecycle.core.recipe.json +71 -67
- package/library/recipes/perps/performance.background-resume.mobile.recipe.json +51 -67
- package/library/recipes/perps/performance.cold-start.mobile.recipe.json +51 -67
- package/library/recipes/perps/performance.mobile.recipe.json +37 -51
- package/library/recipes/perps/performance.warm-start.mobile.recipe.json +44 -59
- package/library/recipes/perps/read-markets.core.recipe.json +29 -31
- package/library/recipes/perps/smoke.core.recipe.json +29 -32
- package/library/recipes/perps/smoke.extension.recipe.json +41 -44
- package/library/recipes/perps/smoke.mobile.recipe.json +42 -44
- package/library/recipes/perps/trading-lifecycle.core.recipe.json +69 -65
- package/library/recipes/runner/action-validation.extension.recipe.json +312 -398
- package/library/recipes/runner/action-validation.mobile.recipe.json +316 -402
- package/library/recipes/runner/smoke.core.recipe.json +25 -0
- package/library/recipes/runner/smoke.extension.recipe.json +23 -24
- package/library/recipes/runner/smoke.mobile.recipe.json +23 -24
- package/library/recipes/wallet/smoke.extension.recipe.json +40 -0
- package/library/recipes/wallet/smoke.mobile.recipe.json +40 -0
- package/package.json +12 -5
- package/scripts/completions.sh +8 -11
- package/scripts/validate-human-outcomes.mjs +169 -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/dist/adapters/extension/harness-freshness.js +0 -39
- package/dist/commands/flows.js +0 -62
- package/docs/ADAPTER-SURFACE.md +0 -119
- package/docs/CHEATSHEET.md +0 -61
- package/docs/CLI-SPEC.md +0 -1098
- package/docs/CODE-MAP.md +0 -62
- package/docs/DEBUG-HANDOVER.md +0 -36
- package/docs/MENTAL-MODEL.md +0 -295
- package/docs/UX-PRINCIPLES.md +0 -64
- package/docs/architecture.md +0 -398
- package/docs/live-adapter-contract.md +0 -188
- package/docs/package-boundaries.md +0 -47
- package/docs/perps-flow-catalog.md +0 -235
- package/docs/recipe-libraries.md +0 -301
- package/docs/runtime-file-conventions.md +0 -36
- package/library/flows/perps.flows.json +0 -64
package/dist/commands/call.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
import os from "node:os";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
4
3
|
import path from "node:path";
|
|
5
|
-
import {
|
|
4
|
+
import { resolveActionManifest } from "../manifest.js";
|
|
6
5
|
import { importRecipeProtocol } from "../paths.js";
|
|
7
|
-
import { recipeRunning } from "../heal-bounds.js";
|
|
6
|
+
import { conciseFailureForHuman, recipeRunning } from "../heal-bounds.js";
|
|
8
7
|
import { color } from "../cli-color.js";
|
|
9
|
-
import { EXIT } from "./shared.js";
|
|
10
|
-
import { describeManifestActions, fuzzyResolveActions } from "./manifest.js";
|
|
8
|
+
import { checkoutBusyOut, EXIT, usageOut } from "./shared.js";
|
|
9
|
+
import { actionExampleCommand, describeManifestActions, fuzzyResolveActions } from "./manifest.js";
|
|
11
10
|
import {
|
|
12
11
|
parseArgs,
|
|
13
12
|
isRecord,
|
|
14
13
|
optionFlag,
|
|
15
14
|
optionString,
|
|
15
|
+
optionStrings,
|
|
16
16
|
resolveAdapter,
|
|
17
17
|
runtimeOptionsFromCli,
|
|
18
|
+
shellQuote,
|
|
18
19
|
usageError
|
|
19
20
|
} from "./parse-args.js";
|
|
20
21
|
import { getAdapterSurface } from "../adapters/surface.js";
|
|
@@ -27,11 +28,16 @@ import {
|
|
|
27
28
|
prepareHeal,
|
|
28
29
|
recoverRunInfra,
|
|
29
30
|
resolveMetaMaskLibrarySources,
|
|
31
|
+
preflightRecipe,
|
|
30
32
|
runRecipe,
|
|
31
33
|
synthesizeOneNodeRecipe,
|
|
32
34
|
validateRecipeAdapterAware
|
|
33
35
|
} from "./run-engine.js";
|
|
34
36
|
import { acquireCheckoutLock } from "../checkout-lock.js";
|
|
37
|
+
import { formatRunDiagnosticsForHuman, readRunDiagnosticsDocument } from "../run-diagnostics.js";
|
|
38
|
+
import { recipeTrustFailure } from "../recipe-security.js";
|
|
39
|
+
import { isSensitiveKey, recordCommandEvidence, redactStructuredValue } from "../command-journal.js";
|
|
40
|
+
import { closest } from "../command-contract.js";
|
|
35
41
|
async function handleCall(argv) {
|
|
36
42
|
if (argv.includes("--list")) {
|
|
37
43
|
const { options: options2 } = parseArgs(argv, "call");
|
|
@@ -50,7 +56,8 @@ async function handleCall(argv) {
|
|
|
50
56
|
let discovery = "mm-harness actions";
|
|
51
57
|
try {
|
|
52
58
|
const { adapter: adapter2 } = resolveAdapter(options);
|
|
53
|
-
const
|
|
59
|
+
const librarySources2 = await resolveMetaMaskLibrarySources(optionStrings(options, "library"));
|
|
60
|
+
const { manifest: manifest2 } = await resolveActionManifest(adapter2, optionString(options, "actionManifest"), librarySources2);
|
|
54
61
|
const { getRecipeActionManifestActionNames: getRecipeActionManifestActionNames2 } = await importRecipeProtocol();
|
|
55
62
|
const exampleAction = pickCallExampleAction(getRecipeActionManifestActionNames2(manifest2));
|
|
56
63
|
example = `mm-harness call ${exampleAction} --adapter ${adapter2}`;
|
|
@@ -59,47 +66,70 @@ async function handleCall(argv) {
|
|
|
59
66
|
}
|
|
60
67
|
const message = `call requires <action>. Example: ${example}`;
|
|
61
68
|
const userAction = `${example} # see the vocabulary: ${discovery}`;
|
|
62
|
-
if (json) console.log(JSON.stringify({
|
|
69
|
+
if (json) console.log(JSON.stringify({
|
|
70
|
+
schemaVersion: 1,
|
|
71
|
+
command: "call",
|
|
72
|
+
status: "fail",
|
|
73
|
+
error: { code: "CLI_MISSING_POSITIONAL", message, userAction },
|
|
74
|
+
exitCode: EXIT.usage
|
|
75
|
+
}, null, 2));
|
|
63
76
|
else console.error(`${message}
|
|
64
77
|
See the vocabulary: ${discovery}`);
|
|
65
78
|
return EXIT.usage;
|
|
66
79
|
}
|
|
67
80
|
const { adapter, target } = resolveAdapter(options);
|
|
81
|
+
if (!fs.existsSync(target)) {
|
|
82
|
+
return usageOut(
|
|
83
|
+
json,
|
|
84
|
+
"call",
|
|
85
|
+
`target does not exist: ${target}`,
|
|
86
|
+
"pass --target <metamask-checkout> pointing to an existing checkout"
|
|
87
|
+
);
|
|
88
|
+
}
|
|
68
89
|
getAdapterSurface(adapter).resolveSlotPorts(target);
|
|
69
90
|
const dtResult = applyDeviceTargeting("call", adapter, options, { gate: true, rerun: "" });
|
|
70
91
|
if ("code" in dtResult) {
|
|
71
|
-
if (json) console.log(JSON.stringify({ schemaVersion: 1, command: "call", adapter, error: { code: dtResult.code, message: dtResult.message } }, null, 2));
|
|
72
|
-
else console.error(`\u2717 call: ${dtResult.message}
|
|
92
|
+
if (json) console.log(JSON.stringify({ schemaVersion: 1, command: "call", adapter, error: { code: dtResult.code, message: dtResult.message, userAction: dtResult.userAction } }, null, 2));
|
|
93
|
+
else console.error(`\u2717 call: ${dtResult.message}
|
|
94
|
+
Next: ${dtResult.userAction}`);
|
|
73
95
|
return EXIT.usage;
|
|
74
96
|
}
|
|
75
97
|
if (recipeRunning(target)) {
|
|
76
98
|
const msg = "a recipe is currently running \u2014 refusing to start while another recipe executes.";
|
|
99
|
+
const userAction = `inspect the checkout state with: mm-harness status --target ${shellQuote(target)} --json; retry after the active recipe finishes`;
|
|
77
100
|
if (json) {
|
|
78
|
-
console.log(JSON.stringify({ schemaVersion: 1, command: "call", status: "fail", recoverable: false, error: { code: "RECIPE_RUNNING", message: msg } }, null, 2));
|
|
101
|
+
console.log(JSON.stringify({ schemaVersion: 1, command: "call", status: "fail", recoverable: false, error: { code: "RECIPE_RUNNING", message: msg, userAction } }, null, 2));
|
|
79
102
|
} else {
|
|
80
|
-
console.error(`\u2717 mm-harness call: ${msg}
|
|
103
|
+
console.error(`\u2717 mm-harness call: ${msg}
|
|
104
|
+
Next: ${userAction}`);
|
|
81
105
|
}
|
|
82
106
|
return EXIT.bounded;
|
|
83
107
|
}
|
|
84
108
|
const actionManifestOverride = optionString(options, "actionManifest");
|
|
85
|
-
const
|
|
109
|
+
const librarySources = await resolveMetaMaskLibrarySources(optionStrings(options, "library"));
|
|
110
|
+
const { manifest, actionSources } = await resolveActionManifest(adapter, actionManifestOverride, librarySources);
|
|
86
111
|
const { getRecipeActionManifestActionNames } = await importRecipeProtocol();
|
|
87
112
|
const names = getRecipeActionManifestActionNames(manifest);
|
|
88
113
|
const resolution = resolveActionName(shortName, names);
|
|
89
114
|
if (resolution.status === "unknown") {
|
|
90
|
-
const message =
|
|
91
|
-
|
|
92
|
-
if (json) console.log(JSON.stringify({ schemaVersion: 1, command: "call", adapter, action: shortName, error: { code: "ACTION_UNKNOWN", message } }, null, 2));
|
|
93
|
-
else console.error(message
|
|
115
|
+
const message = `unknown action "${shortName}" for the ${adapter} adapter.`;
|
|
116
|
+
const userAction = `mm-harness actions --adapter ${adapter} --json`;
|
|
117
|
+
if (json) console.log(JSON.stringify({ schemaVersion: 1, command: "call", adapter, action: shortName, error: { code: "ACTION_UNKNOWN", message, userAction } }, null, 2));
|
|
118
|
+
else console.error(`\u2717 call: ${message}
|
|
119
|
+
Next: ${userAction}`);
|
|
94
120
|
return EXIT.usage;
|
|
95
121
|
}
|
|
96
122
|
if (resolution.status === "ambiguous") {
|
|
97
|
-
const message =
|
|
98
|
-
|
|
99
|
-
|
|
123
|
+
const message = `"${shortName}" is ambiguous: ${resolution.candidates.join(", ")} \u2014 use the full name.`;
|
|
124
|
+
const userAction = `mm-harness actions --action ${resolution.candidates[0]} --adapter ${adapter} --json`;
|
|
125
|
+
if (json) console.log(JSON.stringify({ schemaVersion: 1, command: "call", adapter, action: shortName, error: { code: "ACTION_AMBIGUOUS", message, candidates: resolution.candidates, userAction } }, null, 2));
|
|
126
|
+
else console.error(`\u2717 call: ${message}
|
|
127
|
+
Next: ${userAction}`);
|
|
100
128
|
return EXIT.usage;
|
|
101
129
|
}
|
|
102
130
|
const resolvedAction = resolution.resolved;
|
|
131
|
+
const describedAction = describeManifestActions(manifest, actionSources).find((entry) => entry.name === resolvedAction);
|
|
132
|
+
const defaultsUsed = actionDefaultsUsed(describedAction, args);
|
|
103
133
|
const depsBlock = adapter === "core" && resolvedAction.startsWith("metamask.perps.") ? coreDependencyBlock(target) : null;
|
|
104
134
|
if (depsBlock) {
|
|
105
135
|
if (json) {
|
|
@@ -111,21 +141,91 @@ async function handleCall(argv) {
|
|
|
111
141
|
return EXIT.usage;
|
|
112
142
|
}
|
|
113
143
|
const recipe = synthesizeOneNodeRecipe(resolvedAction, args);
|
|
114
|
-
const validation = await validateRecipeAdapterAware(recipe, manifest);
|
|
144
|
+
const validation = await validateRecipeAdapterAware(adapter, recipe, manifest, librarySources);
|
|
115
145
|
if (validation.status === "invalid") {
|
|
116
|
-
const message =
|
|
117
|
-
|
|
146
|
+
const message = `call ${resolvedAction}: recipe validation failed`;
|
|
147
|
+
const parameterHelp = parameterValidationHelp(describedAction, validation.findings, args);
|
|
148
|
+
const userAction = describedAction ? actionExampleCommand(
|
|
149
|
+
describedAction,
|
|
150
|
+
adapter,
|
|
151
|
+
target,
|
|
152
|
+
process.env.MM_HARNESS_INVOKED_AS ?? process.env.MM_HARNESS_EXECUTABLE ?? "mm-harness"
|
|
153
|
+
) ?? `mm-harness actions --action ${resolvedAction} --adapter ${adapter}` : `mm-harness actions --action ${resolvedAction} --adapter ${adapter}`;
|
|
154
|
+
if (json) console.log(JSON.stringify({ schemaVersion: 1, command: "call", adapter, action: shortName, resolvedAction, args: redactCallValue(args), findings: validation.findings, ...parameterHelp.length > 0 ? { parameterHelp } : {}, error: { code: "RECIPE_VALIDATION_FAILED", message, userAction } }, null, 2));
|
|
118
155
|
else {
|
|
119
|
-
console.error(message);
|
|
120
|
-
|
|
156
|
+
console.error(`\u2717 ${message}`);
|
|
157
|
+
const taught = new Set(parameterHelp.map((entry) => `${entry.issue}:${entry.name}`));
|
|
158
|
+
for (const entry of parameterHelp) console.error(renderParameterValidationHelp(entry));
|
|
159
|
+
for (const finding of validation.findings) {
|
|
160
|
+
const name = finding.path.split(".").at(-1);
|
|
161
|
+
const issue = parameterIssue(finding.code);
|
|
162
|
+
if (issue && name && taught.has(`${issue}:${name}`)) continue;
|
|
163
|
+
console.error(` ${finding.code} ${finding.path} \u2014 ${finding.message}`);
|
|
164
|
+
}
|
|
165
|
+
console.error(` Next: ${userAction}`);
|
|
166
|
+
}
|
|
167
|
+
return EXIT.validation;
|
|
168
|
+
}
|
|
169
|
+
const artifactsDir = optionString(options, "artifactsDir") ?? defaultCallArtifactsDir(target, resolvedAction, args);
|
|
170
|
+
recordCommandEvidence(artifactsDir);
|
|
171
|
+
const requestedRuntimeOptions = runtimeOptionsFromCli(options);
|
|
172
|
+
const callRuntimeOptions = {
|
|
173
|
+
...requestedRuntimeOptions,
|
|
174
|
+
...librarySources ? { librarySources } : {},
|
|
175
|
+
autoHud: false,
|
|
176
|
+
suppressLibraryResolutionLogs: true,
|
|
177
|
+
stdoutIsMachineContract: json,
|
|
178
|
+
source: requestedRuntimeOptions.source ?? {
|
|
179
|
+
kind: "operator",
|
|
180
|
+
trust: "trusted",
|
|
181
|
+
name: "mm-harness call"
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
let preflightedExecution;
|
|
185
|
+
try {
|
|
186
|
+
preflightedExecution = await preflightRecipe(
|
|
187
|
+
adapter,
|
|
188
|
+
recipe,
|
|
189
|
+
artifactsDir,
|
|
190
|
+
target,
|
|
191
|
+
actionManifestOverride,
|
|
192
|
+
callRuntimeOptions
|
|
193
|
+
);
|
|
194
|
+
} catch (error) {
|
|
195
|
+
const trustFailure = recipeTrustFailure(error);
|
|
196
|
+
if (!trustFailure) throw error;
|
|
197
|
+
const planDigest = trustFailure.details?.recipeDigest;
|
|
198
|
+
if (planDigest) {
|
|
199
|
+
trustFailure.userAction = `review the plan, then rerun this call with --artifacts-dir ${shellQuote(artifactsDir)} --approve-plan ${shellQuote(planDigest)}; managed callers must keep the same artifact directory and execution environment, then set FARMSLOT_RECIPE_APPROVE_PLAN=${shellQuote(planDigest)}`;
|
|
200
|
+
}
|
|
201
|
+
if (json) {
|
|
202
|
+
console.log(JSON.stringify({
|
|
203
|
+
schemaVersion: 1,
|
|
204
|
+
command: "call",
|
|
205
|
+
status: "fail",
|
|
206
|
+
error: trustFailure,
|
|
207
|
+
exitCode: EXIT.validation
|
|
208
|
+
}, null, 2));
|
|
209
|
+
} else {
|
|
210
|
+
console.error(`\u2717 mm-harness call: ${trustFailure.message}`);
|
|
211
|
+
const blocked = trustFailure.details?.blocked;
|
|
212
|
+
if (blocked?.length) {
|
|
213
|
+
console.error(" Restricted plan nodes:");
|
|
214
|
+
for (const node of blocked.slice(0, 10)) {
|
|
215
|
+
const implementation = node.implementation?.digest ? ` implementation=${node.implementation.kind ?? "custom"}@${node.implementation.digest}` : "";
|
|
216
|
+
console.error(
|
|
217
|
+
` - ${node.nodeId}: ${node.action} [${node.capabilities.join(", ")}] source=${node.source}${implementation}`
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
if (blocked.length > 10) console.error(` - \u2026 ${blocked.length - 10} more (use --json)`);
|
|
221
|
+
}
|
|
222
|
+
console.error(` Next: ${trustFailure.userAction}`);
|
|
121
223
|
}
|
|
122
224
|
return EXIT.validation;
|
|
123
225
|
}
|
|
124
226
|
const lock = acquireCheckoutLock(target, "call");
|
|
125
227
|
if ("message" in lock) {
|
|
126
|
-
|
|
127
|
-
else console.error(`\u2717 mm-harness call: ${lock.message}`);
|
|
128
|
-
return EXIT.bounded;
|
|
228
|
+
return checkoutBusyOut(json, "call", lock.message, lock.path);
|
|
129
229
|
}
|
|
130
230
|
try {
|
|
131
231
|
const prepared = await prepareHeal(adapter, target, options, json, {
|
|
@@ -133,27 +233,41 @@ async function handleCall(argv) {
|
|
|
133
233
|
});
|
|
134
234
|
if (typeof prepared === "number") return prepared;
|
|
135
235
|
const { state, heal } = prepared;
|
|
136
|
-
const scratch = await mkdtemp(path.join(os.tmpdir(), "mm-harness-call-"));
|
|
137
|
-
const recipeFile = path.join(scratch, "call.recipe.json");
|
|
138
|
-
fs.writeFileSync(recipeFile, `${JSON.stringify(recipe, null, 2)}
|
|
139
|
-
`);
|
|
140
|
-
const artifactsDir = optionString(options, "artifactsDir") ?? path.join(scratch, "artifacts");
|
|
141
|
-
const librarySources = await resolveMetaMaskLibrarySources(optionString(options, "library"));
|
|
142
|
-
const callRuntimeOptions = {
|
|
143
|
-
...runtimeOptionsFromCli(options),
|
|
144
|
-
...librarySources ? { librarySources } : {},
|
|
145
|
-
stdoutIsMachineContract: json
|
|
146
|
-
};
|
|
147
236
|
const { result, violation } = await executeWithHealBounds(
|
|
148
|
-
() =>
|
|
237
|
+
() => {
|
|
238
|
+
const execution = preflightedExecution;
|
|
239
|
+
preflightedExecution = void 0;
|
|
240
|
+
return runRecipe(
|
|
241
|
+
adapter,
|
|
242
|
+
recipe,
|
|
243
|
+
artifactsDir,
|
|
244
|
+
target,
|
|
245
|
+
actionManifestOverride,
|
|
246
|
+
callRuntimeOptions,
|
|
247
|
+
execution
|
|
248
|
+
);
|
|
249
|
+
},
|
|
149
250
|
adapter,
|
|
150
251
|
target,
|
|
151
252
|
heal,
|
|
152
253
|
state,
|
|
153
254
|
() => recoverRunInfra(adapter, target, json)
|
|
154
255
|
);
|
|
155
|
-
if (violation !== null)
|
|
256
|
+
if (violation !== null) {
|
|
257
|
+
const conciseFailure = violation.originalError ? conciseFailureForHuman(violation.originalError) : "";
|
|
258
|
+
const example = describedAction ? actionExampleCommand(
|
|
259
|
+
describedAction,
|
|
260
|
+
adapter,
|
|
261
|
+
target,
|
|
262
|
+
process.env.MM_HARNESS_INVOKED_AS ?? process.env.MM_HARNESS_EXECUTABLE ?? "mm-harness"
|
|
263
|
+
) : void 0;
|
|
264
|
+
const taughtViolation = violation.code === "APP_LOGIC_FAILURE" && conciseFailure.includes(" requires ") && example ? { ...violation, userAction: example } : violation;
|
|
265
|
+
return emitHealViolation(json, "call", result, taughtViolation, state, adapter);
|
|
266
|
+
}
|
|
156
267
|
const callOutput = readCallOutput(result.tracePath);
|
|
268
|
+
const safeArgs = redactCallValue(args);
|
|
269
|
+
const safeCallOutput = redactCallValue(callOutput);
|
|
270
|
+
const failureUserAction = `mm-harness last --target ${shellQuote(target)} --json`;
|
|
157
271
|
if (json) {
|
|
158
272
|
console.log(
|
|
159
273
|
JSON.stringify(
|
|
@@ -163,30 +277,43 @@ async function handleCall(argv) {
|
|
|
163
277
|
adapter,
|
|
164
278
|
action: shortName,
|
|
165
279
|
resolvedAction,
|
|
166
|
-
args,
|
|
280
|
+
args: safeArgs,
|
|
281
|
+
...Object.keys(defaultsUsed).length > 0 ? { defaultsUsed } : {},
|
|
167
282
|
status: result.status,
|
|
168
283
|
summaryPath: result.summaryPath,
|
|
169
284
|
tracePath: result.tracePath,
|
|
170
285
|
artifactManifestPath: result.artifactManifestPath,
|
|
171
|
-
...
|
|
286
|
+
...result.diagnosticsPath ? { diagnosticsPath: result.diagnosticsPath } : {},
|
|
287
|
+
...result.sideFindings ? { sideFindings: result.sideFindings } : {},
|
|
288
|
+
...callOutput !== void 0 ? { output: safeCallOutput } : {},
|
|
172
289
|
recovered: state.recovered,
|
|
173
290
|
mutations: state.mutations,
|
|
174
|
-
exitCode: result.status === "pass" ? EXIT.ok : EXIT.runtime
|
|
291
|
+
exitCode: result.status === "pass" ? EXIT.ok : EXIT.runtime,
|
|
292
|
+
...result.status === "fail" ? { error: { code: "ACTION_EXECUTION_FAILED", message: `${resolvedAction} failed; inspect the persisted result and evidence paths`, userAction: failureUserAction } } : {}
|
|
175
293
|
},
|
|
176
294
|
null,
|
|
177
295
|
2
|
|
178
296
|
)
|
|
179
297
|
);
|
|
180
298
|
} else {
|
|
181
|
-
const
|
|
182
|
-
Result:
|
|
183
|
-
${formatCallOutput(callOutput)}` : "";
|
|
299
|
+
const renderedInputs = Object.keys(args).length > 0 ? formatCallOutput(safeArgs) : "";
|
|
184
300
|
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
301
|
+
const diagnostics = formatRunDiagnosticsForHuman(
|
|
302
|
+
readRunDiagnosticsDocument(result.diagnosticsPath),
|
|
303
|
+
adapter
|
|
304
|
+
);
|
|
305
|
+
const renderedDiagnostics = `${out("label", "Diagnostics:")}
|
|
306
|
+
${diagnostics.map((line) => ` ${line}`).join("\n")}
|
|
307
|
+
`;
|
|
185
308
|
console.log(
|
|
186
|
-
`${out("label", "call")} ${out("cmd", resolvedAction)}: ${out(result.status === "pass" ? "ok" : "err", result.status)}${
|
|
309
|
+
`${out("label", "call")} ${out("cmd", resolvedAction)}: ${out(result.status === "pass" ? "ok" : "err", result.status)}${renderedInputs ? `
|
|
310
|
+
${out("label", "Inputs:")}
|
|
311
|
+
${renderedInputs}` : ""}${Object.keys(defaultsUsed).length > 0 ? `
|
|
312
|
+
${renderDefaultsUsed(defaultsUsed, process.stdout)}` : ""}${callOutput !== void 0 ? `
|
|
187
313
|
${out("label", "Result:")}
|
|
188
|
-
${formatCallOutput(
|
|
189
|
-
|
|
314
|
+
${formatCallOutput(safeCallOutput)}` : ""}
|
|
315
|
+
` + renderedDiagnostics + `${out("label", "Artifacts:")} ${out("path", result.artifactManifestPath)}` + (result.status === "fail" ? `
|
|
316
|
+
Next: ${failureUserAction}` : "")
|
|
190
317
|
);
|
|
191
318
|
}
|
|
192
319
|
return result.status === "pass" ? EXIT.ok : EXIT.runtime;
|
|
@@ -194,6 +321,59 @@ ${out("label", "Artifacts:")} ${out("path", result.artifactManifestPath)}`
|
|
|
194
321
|
lock.release();
|
|
195
322
|
}
|
|
196
323
|
}
|
|
324
|
+
function parameterValidationHelp(action, findings, args) {
|
|
325
|
+
const schema = isRecord(action?.schema) ? action.schema : {};
|
|
326
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
327
|
+
return findings.flatMap((finding) => {
|
|
328
|
+
const issue = parameterIssue(finding.code);
|
|
329
|
+
if (!issue) return [];
|
|
330
|
+
const name = finding.path.split(".").at(-1);
|
|
331
|
+
const property = name && isRecord(properties[name]) ? properties[name] : void 0;
|
|
332
|
+
if (!name || !property) return [];
|
|
333
|
+
const validValues = Array.isArray(property.enum) ? property.enum : void 0;
|
|
334
|
+
const received = Object.hasOwn(args, name) ? args[name] : void 0;
|
|
335
|
+
const suggestion = issue === "invalid" && received !== void 0 && validValues ? closest(String(received), validValues.map(String)) : void 0;
|
|
336
|
+
return [{
|
|
337
|
+
issue,
|
|
338
|
+
name,
|
|
339
|
+
...typeof property.type === "string" ? { type: property.type } : {},
|
|
340
|
+
...validValues ? { validValues } : {},
|
|
341
|
+
...typeof property.description === "string" ? { description: property.description } : {},
|
|
342
|
+
...issue === "invalid" && received !== void 0 ? { received } : {},
|
|
343
|
+
...suggestion ? { suggestion } : {}
|
|
344
|
+
}];
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
function parameterIssue(code) {
|
|
348
|
+
if (code === "recipe.missing_param") return "missing";
|
|
349
|
+
if (code === "recipe.invalid_param_value_enum") return "invalid";
|
|
350
|
+
return void 0;
|
|
351
|
+
}
|
|
352
|
+
function renderParameterValidationHelp(parameter) {
|
|
353
|
+
const out = (style, text) => color(style, text, { stream: process.stderr });
|
|
354
|
+
const heading = parameter.issue === "missing" ? `${out("cmd", parameter.name)}${parameter.type ? ` (${parameter.type}, required)` : " (required)"}` : `${out("cmd", parameter.name)} received ${out("err", JSON.stringify(parameter.received) ?? String(parameter.received))}`;
|
|
355
|
+
const values = parameter.validValues?.length ? ` Valid values: ${parameter.validValues.map((value) => out("accent", String(value))).join(", ")}.` : "";
|
|
356
|
+
const suggestion = parameter.suggestion ? ` Did you mean ${out("accent", JSON.stringify(parameter.suggestion))}?` : "";
|
|
357
|
+
const description = parameter.description ? ` ${parameter.description}` : "";
|
|
358
|
+
return ` ${heading}.${values}${suggestion}${description}`;
|
|
359
|
+
}
|
|
360
|
+
function actionDefaultsUsed(action, args) {
|
|
361
|
+
const schema = isRecord(action?.schema) ? action.schema : {};
|
|
362
|
+
const properties = isRecord(schema.properties) ? schema.properties : {};
|
|
363
|
+
return Object.fromEntries(
|
|
364
|
+
Object.entries(properties).filter(([name, entry]) => !Object.hasOwn(args, name) && isRecord(entry) && Object.hasOwn(entry, "default")).map(([name, entry]) => [name, entry.default])
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
function renderDefaultsUsed(defaults, stream) {
|
|
368
|
+
const out = (style, text) => color(style, text, { stream });
|
|
369
|
+
const values = Object.entries(defaults).map(([name, value]) => `${out("cmd", name)}=${out("accent", JSON.stringify(value) ?? String(value))}`).join(", ");
|
|
370
|
+
return `${out("label", "Defaults used:")} ${values}`;
|
|
371
|
+
}
|
|
372
|
+
function defaultCallArtifactsDir(target, action, args) {
|
|
373
|
+
const actionStem = action.replace(/[^a-zA-Z0-9._-]/gu, "_");
|
|
374
|
+
const digest = createHash("sha256").update(JSON.stringify({ action, args })).digest("hex").slice(0, 12);
|
|
375
|
+
return path.join(target, "temp", "recipe", "calls", `${actionStem}-${digest}`);
|
|
376
|
+
}
|
|
197
377
|
function readCallOutput(tracePath) {
|
|
198
378
|
try {
|
|
199
379
|
const trace = JSON.parse(fs.readFileSync(tracePath, "utf8"));
|
|
@@ -209,20 +389,27 @@ function formatCallOutput(value) {
|
|
|
209
389
|
if (typeof value === "number" || typeof value === "boolean" || value === null) return String(value);
|
|
210
390
|
return JSON.stringify(value, null, 2);
|
|
211
391
|
}
|
|
392
|
+
function redactCallValue(value, key = "") {
|
|
393
|
+
return key && isSensitiveKey(key) ? "<redacted>" : redactStructuredValue(value);
|
|
394
|
+
}
|
|
212
395
|
async function handleCallHelp(argv, genericHelp) {
|
|
213
396
|
const { action: shortName, rest } = parseCallArgs(argv.filter((arg) => arg !== "--help" && arg !== "-h"));
|
|
214
397
|
const { options } = parseArgs(rest, "call");
|
|
215
398
|
let adapter;
|
|
216
399
|
let manifest;
|
|
400
|
+
let actionSources = /* @__PURE__ */ new Map();
|
|
217
401
|
try {
|
|
218
402
|
({ adapter } = resolveAdapter(options));
|
|
219
|
-
|
|
403
|
+
const librarySources = await resolveMetaMaskLibrarySources(optionStrings(options, "library"));
|
|
404
|
+
const resolution = await resolveActionManifest(adapter, optionString(options, "actionManifest"), librarySources);
|
|
405
|
+
manifest = resolution.manifest;
|
|
406
|
+
actionSources = resolution.actionSources;
|
|
220
407
|
} catch {
|
|
221
408
|
process.stdout.write(`${genericHelp}
|
|
222
409
|
`);
|
|
223
410
|
return EXIT.ok;
|
|
224
411
|
}
|
|
225
|
-
const matches = shortName ? fuzzyResolveActions(describeManifestActions(manifest), shortName) : [];
|
|
412
|
+
const matches = shortName ? fuzzyResolveActions(describeManifestActions(manifest, actionSources), shortName) : [];
|
|
226
413
|
if (matches.length === 0) {
|
|
227
414
|
process.stdout.write(`${genericHelp}
|
|
228
415
|
`);
|
|
@@ -250,7 +437,8 @@ function renderCallActionHelp(entry) {
|
|
|
250
437
|
);
|
|
251
438
|
const lines = [`mm-harness call ${entry.name} [key=value ...] [--arg k=v ...] [flags]`, ""];
|
|
252
439
|
if (entry.description) lines.push(` ${entry.description}`, "");
|
|
253
|
-
|
|
440
|
+
lines.push(` Source: ${entry.source}${entry.sourceManifest ? ` (${entry.sourceManifest})` : ""}`, "");
|
|
441
|
+
const names = Object.keys(properties).filter((name) => name !== "action" && name !== "next").sort();
|
|
254
442
|
if (names.length === 0) {
|
|
255
443
|
lines.push(" Fields: (none)");
|
|
256
444
|
} else {
|
|
@@ -262,7 +450,8 @@ function renderCallActionHelp(entry) {
|
|
|
262
450
|
const req = required.has(name) ? " (required)" : "";
|
|
263
451
|
const desc = typeof prop.description === "string" ? ` \u2014 ${prop.description}` : "";
|
|
264
452
|
const enumVals = Array.isArray(prop.enum) ? ` [one of: ${prop.enum.join(", ")}]` : "";
|
|
265
|
-
|
|
453
|
+
const defaultValue = Object.hasOwn(prop, "default") ? ` [default: ${JSON.stringify(prop.default)}]` : "";
|
|
454
|
+
lines.push(` ${name.padEnd(width)} ${type}${req}${defaultValue}${desc}${enumVals}`);
|
|
266
455
|
}
|
|
267
456
|
}
|
|
268
457
|
const examples = renderCallExamples(short, entry.examples);
|
|
@@ -320,7 +509,7 @@ function parseCallValue(value) {
|
|
|
320
509
|
const number = Number(value);
|
|
321
510
|
if (Number.isFinite(number)) return number;
|
|
322
511
|
}
|
|
323
|
-
if (value.startsWith("[") && value.endsWith("]") || value.startsWith("{") && value.endsWith("}")) {
|
|
512
|
+
if (value.startsWith("[") && value.endsWith("]") || value.startsWith("{") && value.endsWith("}") || value.startsWith('"') && value.endsWith('"')) {
|
|
324
513
|
try {
|
|
325
514
|
return JSON.parse(value);
|
|
326
515
|
} catch {
|
|
@@ -340,6 +529,7 @@ function isForwardedOptionValue(argv, index) {
|
|
|
340
529
|
}
|
|
341
530
|
const VALUE_TAKING_CALL_FLAGS = /* @__PURE__ */ new Set([
|
|
342
531
|
"action-manifest",
|
|
532
|
+
"approve-plan",
|
|
343
533
|
"adapter",
|
|
344
534
|
"artifacts-dir",
|
|
345
535
|
"cdp-port",
|
|
@@ -348,8 +538,11 @@ const VALUE_TAKING_CALL_FLAGS = /* @__PURE__ */ new Set([
|
|
|
348
538
|
"library",
|
|
349
539
|
"metro-port",
|
|
350
540
|
"platform",
|
|
351
|
-
"project-root",
|
|
352
541
|
"slot",
|
|
542
|
+
"source-digest",
|
|
543
|
+
"source-kind",
|
|
544
|
+
"source-name",
|
|
545
|
+
"source-trust",
|
|
353
546
|
"target",
|
|
354
547
|
"validation-runtime-dir",
|
|
355
548
|
"watcher-port"
|
|
@@ -373,6 +566,8 @@ function pickCallExampleAction(names) {
|
|
|
373
566
|
return names[0] ?? "command";
|
|
374
567
|
}
|
|
375
568
|
export {
|
|
569
|
+
defaultCallArtifactsDir,
|
|
376
570
|
handleCall,
|
|
377
|
-
handleCallHelp
|
|
571
|
+
handleCallHelp,
|
|
572
|
+
redactCallValue
|
|
378
573
|
};
|
package/dist/commands/check.js
CHANGED
|
@@ -3,7 +3,7 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { detectAdapter } from "../harness.js";
|
|
5
5
|
import { EXIT, usageOut } from "./shared.js";
|
|
6
|
-
import { optionFlag, optionString, parseArgs } from "./parse-args.js";
|
|
6
|
+
import { optionFlag, optionString, parseArgs, shellQuote } from "./parse-args.js";
|
|
7
7
|
const USAGE = "mm-harness check diff [--fix] [--target <repo>] [--adapter mobile|extension|core] [--base <ref>] [--profile fast|full] [--artifacts-dir <dir>] [--json]";
|
|
8
8
|
const JS_TS_EXT_RE = /\.(?:c|m)?jsx?$|\.tsx?$/u;
|
|
9
9
|
const FORMAT_EXT_RE = /\.(?:c|m)?jsx?$|\.tsx?$|\.json$|\.md$|\.css$|\.scss$|\.ya?ml$/u;
|
|
@@ -18,7 +18,7 @@ async function handleCheck(argv) {
|
|
|
18
18
|
const message = action ? `unknown action '${action}'` : "missing action";
|
|
19
19
|
return usageOut(json, "check", message, USAGE);
|
|
20
20
|
}
|
|
21
|
-
const target = path.resolve(optionString(options, "target") ??
|
|
21
|
+
const target = path.resolve(optionString(options, "target") ?? process.cwd());
|
|
22
22
|
const explicitAdapter = optionString(options, "adapter") ?? optionString(options, "platform");
|
|
23
23
|
const detected = explicitAdapter ?? detectAdapter(target);
|
|
24
24
|
if (detected && !["mobile", "extension", "core"].includes(detected)) {
|
|
@@ -111,6 +111,7 @@ async function handleCheck(argv) {
|
|
|
111
111
|
const status = [...fixes, ...checks].some((check) => check.status === "fail") ? "fail" : "pass";
|
|
112
112
|
const exitCode = status === "pass" ? EXIT.ok : EXIT.validation;
|
|
113
113
|
const guidance = policyFailed ? ["Remove the newly added eslint-disable directive(s). Fix the underlying violation with existing named styles, project tokens, or another established source pattern; then rerun mm-harness check diff [--fix]. Other checks were skipped until this policy failure is removed."] : checks.some((check) => check.id === "eslint" && check.status === "fail") ? ["Fix the reported violations in changed source using existing project patterns and tokens; do not suppress rules, hide violations behind indirection, weaken configuration, or bypass check diff. Then rerun mm-harness check diff [--fix]."] : [];
|
|
114
|
+
const failureUserAction = guidance[0] ?? `inspect ${path.join(artifactsDir, "validation-summary.json")}, fix the failing check, then retry: mm-harness check diff --target ${shellQuote(target)} --base ${shellQuote(baseRef)} --profile ${profile} --json`;
|
|
114
115
|
const summary = {
|
|
115
116
|
schemaVersion: 1,
|
|
116
117
|
command: "check",
|
|
@@ -129,6 +130,7 @@ async function handleCheck(argv) {
|
|
|
129
130
|
fixes,
|
|
130
131
|
checks,
|
|
131
132
|
guidance,
|
|
133
|
+
...status === "fail" ? { error: { code: "CHECK_DIFF_FAILED", message: "one or more bounded checks failed", userAction: failureUserAction } } : {},
|
|
132
134
|
artifacts: {
|
|
133
135
|
dir: artifactsDir,
|
|
134
136
|
summaryJson: path.join(artifactsDir, "validation-summary.json"),
|
|
@@ -518,10 +520,14 @@ function renderHuman(summary) {
|
|
|
518
520
|
if (check.status === "fail" && check.logPath) console.log(` log: ${check.logPath}`);
|
|
519
521
|
}
|
|
520
522
|
for (const item of summary.guidance) console.log(` Next: ${item}`);
|
|
523
|
+
if (summary.guidance.length === 0 && summary.status === "fail" && summary.error?.userAction) {
|
|
524
|
+
console.log(` Next: ${summary.error.userAction}`);
|
|
525
|
+
}
|
|
521
526
|
console.log(`Artifacts: ${summary.artifacts.summaryJson}`);
|
|
522
527
|
}
|
|
523
528
|
function failEnvelope(input) {
|
|
524
529
|
const exitCode = EXIT.usage;
|
|
530
|
+
const userAction = USAGE;
|
|
525
531
|
const body = {
|
|
526
532
|
schemaVersion: 1,
|
|
527
533
|
command: "check",
|
|
@@ -534,11 +540,11 @@ function failEnvelope(input) {
|
|
|
534
540
|
baseRef: input.baseRef,
|
|
535
541
|
baseSource: "unknown",
|
|
536
542
|
artifacts: { dir: input.artifactsDir },
|
|
537
|
-
error: { code: input.code, message: input.message }
|
|
543
|
+
error: { code: input.code, message: input.message, userAction }
|
|
538
544
|
};
|
|
539
545
|
if (input.json) console.log(JSON.stringify(body, null, 2));
|
|
540
546
|
else console.error(`\u2717 check diff: ${input.message}
|
|
541
|
-
Next: ${
|
|
547
|
+
Next: ${userAction}`);
|
|
542
548
|
return exitCode;
|
|
543
549
|
}
|
|
544
550
|
export {
|
|
@@ -1,25 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveActionManifest } from "../manifest.js";
|
|
2
2
|
import {
|
|
3
3
|
invalidateCompletionCache,
|
|
4
4
|
readFreshCandidates,
|
|
5
5
|
writeCompletionCandidates
|
|
6
6
|
} from "../completions-cache.js";
|
|
7
|
-
import {
|
|
7
|
+
import { importRecipeProtocol } from "../paths.js";
|
|
8
8
|
import { EXIT } from "./shared.js";
|
|
9
9
|
import {
|
|
10
10
|
parseArgs,
|
|
11
11
|
optionFlag,
|
|
12
12
|
optionString,
|
|
13
|
+
optionStrings,
|
|
13
14
|
resolveAdapter,
|
|
14
15
|
targetPath
|
|
15
16
|
} from "./parse-args.js";
|
|
16
|
-
import { resolveMetaMaskLibrarySources } from "./run-engine.js";
|
|
17
|
+
import { listRunnableRecipes, resolveMetaMaskLibrarySources } from "./run-engine.js";
|
|
17
18
|
async function handleCompletionCandidates(argv) {
|
|
18
19
|
const kind = argv[0];
|
|
19
20
|
const { options } = parseArgs(argv.slice(1), "completion-candidates");
|
|
20
21
|
const json = optionFlag(options, "json");
|
|
21
|
-
if (kind !== "actions" && kind !== "
|
|
22
|
-
console.error("completion-candidates requires <actions|
|
|
22
|
+
if (kind !== "actions" && kind !== "recipes") {
|
|
23
|
+
console.error("completion-candidates requires <actions|recipes>.");
|
|
23
24
|
return EXIT.usage;
|
|
24
25
|
}
|
|
25
26
|
const target = targetPath(options);
|
|
@@ -27,25 +28,19 @@ async function handleCompletionCandidates(argv) {
|
|
|
27
28
|
let candidates;
|
|
28
29
|
if (kind === "actions") {
|
|
29
30
|
adapter = resolveAdapter(options).adapter;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
writeCompletionCandidates(target, "actions", candidates);
|
|
36
|
-
}
|
|
31
|
+
const sources = await resolveMetaMaskLibrarySources(optionStrings(options, "library"));
|
|
32
|
+
const { manifest } = await resolveActionManifest(adapter, optionString(options, "actionManifest"), sources);
|
|
33
|
+
const { getRecipeActionManifestActionNames } = await importRecipeProtocol();
|
|
34
|
+
candidates = getRecipeActionManifestActionNames(manifest);
|
|
35
|
+
writeCompletionCandidates(target, "actions", candidates);
|
|
37
36
|
} else {
|
|
38
|
-
|
|
37
|
+
adapter = resolveAdapter(options).adapter;
|
|
38
|
+
const cacheKey = `recipes:${adapter}`;
|
|
39
|
+
candidates = readFreshCandidates(target, cacheKey);
|
|
39
40
|
if (!candidates) {
|
|
40
|
-
const sources = await resolveMetaMaskLibrarySources(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
} else {
|
|
44
|
-
const harness = await importRecipeHarness();
|
|
45
|
-
const resolution = await harness.loadRecipeLibraries(sources);
|
|
46
|
-
candidates = [...resolution.flows.keys()].sort();
|
|
47
|
-
writeCompletionCandidates(target, "flows", candidates);
|
|
48
|
-
}
|
|
41
|
+
const sources = await resolveMetaMaskLibrarySources(optionStrings(options, "library")).catch(() => void 0);
|
|
42
|
+
candidates = sources ? (await listRunnableRecipes(adapter, sources)).map((recipe) => recipe.name) : [];
|
|
43
|
+
writeCompletionCandidates(target, cacheKey, candidates);
|
|
49
44
|
}
|
|
50
45
|
}
|
|
51
46
|
if (json) console.log(JSON.stringify({ schemaVersion: 1, kind, adapter, candidates }, null, 2));
|