@deeeed/metamask-harness 0.76.0 → 0.77.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 +6 -0
- package/dist/commands/call.js +3 -7
- package/dist/commands/list-executables.js +12 -22
- package/dist/commands/manifest.js +11 -133
- package/dist/commands/run.js +2 -5
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.77.0 - 2026-10-04
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- The action catalog runs on `@farmslot/recipe-cli` 0.1.2: `actions` (lookup, search, related actions, categories, `--matrix`), `call` name lookup and `call --list` short names, and the missing-capability refusals in `call` and `run` use its catalog helpers instead of mm-harness's own copies. mm-harness still resolves the action manifest with its own policy, and flags and output are unchanged.
|
|
10
|
+
|
|
5
11
|
## 0.76.0 - 2026-10-04
|
|
6
12
|
|
|
7
13
|
### Changed
|
package/dist/commands/call.js
CHANGED
|
@@ -10,10 +10,9 @@ import {
|
|
|
10
10
|
actionLibraryContextArgs,
|
|
11
11
|
actionExampleCommand,
|
|
12
12
|
describeManifestActions,
|
|
13
|
-
|
|
14
|
-
resolveActionCapabilityMatrix,
|
|
15
|
-
resolveActionCapabilityRefusal
|
|
13
|
+
resolveActionCapabilityMatrix
|
|
16
14
|
} from "./manifest.js";
|
|
15
|
+
import { fuzzyResolveActions, resolveActionCapabilityRefusal } from "@farmslot/recipe-cli";
|
|
17
16
|
import {
|
|
18
17
|
parseArgs,
|
|
19
18
|
isRecord,
|
|
@@ -667,10 +666,7 @@ function normalizeFlagName(flag) {
|
|
|
667
666
|
return flag.replace(/^--/u, "");
|
|
668
667
|
}
|
|
669
668
|
function resolveActionName(shortName, names) {
|
|
670
|
-
|
|
671
|
-
const finalSegment = (name) => name.split(".").pop() ?? name;
|
|
672
|
-
const exactSegment = names.filter((name) => finalSegment(name) === shortName);
|
|
673
|
-
const tier = exactSegment.length > 0 ? exactSegment : names.filter((name) => finalSegment(name).includes(shortName));
|
|
669
|
+
const tier = fuzzyResolveActions(names.map((name) => ({ name })), shortName).map(({ name }) => name);
|
|
674
670
|
if (tier.length === 1) return { status: "ok", resolved: tier[0], candidates: tier };
|
|
675
671
|
if (tier.length > 1) return { status: "ambiguous", resolved: "", candidates: tier };
|
|
676
672
|
return { status: "unknown", resolved: "", candidates: [] };
|
|
@@ -6,12 +6,12 @@ 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
8
|
import { actionDomain, describeManifestActions, renderHumanActionCatalog } from "./manifest.js";
|
|
9
|
+
import { shortActionNames } from "@farmslot/recipe-cli";
|
|
9
10
|
import {
|
|
10
11
|
describeRunnableRecipe,
|
|
11
12
|
listRunnableRecipes,
|
|
12
13
|
resolveMetaMaskLibrarySources
|
|
13
14
|
} from "./run-engine.js";
|
|
14
|
-
const finalSegment = (name) => name.split(".").pop() ?? name;
|
|
15
15
|
const recipeDomain = (name) => name.includes(".") ? name.split(".")[0].replaceAll("_", "-") : "general";
|
|
16
16
|
function catalogDescription(description) {
|
|
17
17
|
if (!description) return "";
|
|
@@ -91,27 +91,17 @@ ${out(sort === "domain" ? domainStyles.get(group) : "bold", label)} ${out("dim",
|
|
|
91
91
|
const protocolNames = new Set(getRecipeActionManifestActionNames(manifest));
|
|
92
92
|
const available = describeManifestActions(manifest, actionSources).filter((entry) => protocolNames.has(entry.name)).sort((left, right) => left.name.localeCompare(right.name));
|
|
93
93
|
const described = select(available, actionDomain);
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
name: entry.name,
|
|
106
|
-
short: usableShort,
|
|
107
|
-
description: entry.description,
|
|
108
|
-
fields: entry.fields,
|
|
109
|
-
source: entry.source,
|
|
110
|
-
sourceTier: entry.sourceTier,
|
|
111
|
-
sourceManifest: entry.sourceManifest,
|
|
112
|
-
shadows: entry.shadows
|
|
113
|
-
};
|
|
114
|
-
});
|
|
94
|
+
const shortByName = shortActionNames(available.map((entry) => entry.name));
|
|
95
|
+
const actions = described.map((entry) => ({
|
|
96
|
+
name: entry.name,
|
|
97
|
+
short: shortByName.get(entry.name) ?? null,
|
|
98
|
+
description: entry.description,
|
|
99
|
+
fields: entry.fields,
|
|
100
|
+
source: entry.source,
|
|
101
|
+
sourceTier: entry.sourceTier,
|
|
102
|
+
sourceManifest: entry.sourceManifest,
|
|
103
|
+
shadows: entry.shadows
|
|
104
|
+
}));
|
|
115
105
|
if (json) {
|
|
116
106
|
console.log(JSON.stringify({ schemaVersion: 1, command, action: "list", adapter, actions }, null, 2));
|
|
117
107
|
return EXIT.ok;
|
|
@@ -17,6 +17,15 @@ import {
|
|
|
17
17
|
} from "@farmslot/protocol";
|
|
18
18
|
import { metaMaskActionExecutionCapabilities } from "../recipe-security.js";
|
|
19
19
|
import { closest } from "../command-contract.js";
|
|
20
|
+
import {
|
|
21
|
+
actionCapabilityMatrix,
|
|
22
|
+
actionCategory,
|
|
23
|
+
findRelatedActions,
|
|
24
|
+
fuzzyResolveActions,
|
|
25
|
+
resolveActionCapabilityRefusal,
|
|
26
|
+
searchActions,
|
|
27
|
+
summarizeActionCategories
|
|
28
|
+
} from "@farmslot/recipe-cli";
|
|
20
29
|
const OFFICIAL_ACTIONS = new Set(OFFICIAL_RECIPE_ACTIONS);
|
|
21
30
|
const ACTION_MATRIX_ADAPTERS = ["mobile", "extension", "core", "terminal"];
|
|
22
31
|
function actionLibraryContextArgs(librarySources) {
|
|
@@ -178,51 +187,7 @@ async function resolveActionCapabilityMatrix(librarySources) {
|
|
|
178
187
|
return { adapter, actions: describeManifestActions(manifest, actionSources) };
|
|
179
188
|
})
|
|
180
189
|
);
|
|
181
|
-
|
|
182
|
-
for (const catalog of catalogs) {
|
|
183
|
-
for (const action of catalog.actions) {
|
|
184
|
-
const entry = actions.get(action.name) ?? { entries: [], adapters: /* @__PURE__ */ new Set() };
|
|
185
|
-
entry.entries.push(action);
|
|
186
|
-
entry.adapters.add(catalog.adapter);
|
|
187
|
-
actions.set(action.name, entry);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return [...actions.entries()].map(([name, entry]) => {
|
|
191
|
-
const representative = entry.entries[0];
|
|
192
|
-
const satisfyingAdapters = ACTION_MATRIX_ADAPTERS.filter(
|
|
193
|
-
(adapter) => entry.adapters.has(adapter)
|
|
194
|
-
);
|
|
195
|
-
return {
|
|
196
|
-
name,
|
|
197
|
-
category: representative.category,
|
|
198
|
-
description: entry.entries.map((action) => action.description).find(Boolean) ?? "",
|
|
199
|
-
fields: [...new Set(entry.entries.flatMap((action) => action.fields))].sort(),
|
|
200
|
-
support: Object.fromEntries(
|
|
201
|
-
ACTION_MATRIX_ADAPTERS.map((adapter) => [
|
|
202
|
-
adapter,
|
|
203
|
-
entry.adapters.has(adapter) ? "available" : "unavailable"
|
|
204
|
-
])
|
|
205
|
-
),
|
|
206
|
-
satisfyingAdapters
|
|
207
|
-
};
|
|
208
|
-
}).sort((left, right) => left.name.localeCompare(right.name));
|
|
209
|
-
}
|
|
210
|
-
function resolveActionCapabilityRefusal(action, adapter, matrix) {
|
|
211
|
-
const matches = fuzzyResolveActions(matrix, action);
|
|
212
|
-
if (matches.length !== 1 || matches[0].satisfyingAdapters.includes(adapter)) {
|
|
213
|
-
return void 0;
|
|
214
|
-
}
|
|
215
|
-
return {
|
|
216
|
-
capability: matches[0].name,
|
|
217
|
-
satisfyingAdapters: matches[0].satisfyingAdapters
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
function missingActionCapabilities(adapter, actionNames, matrix) {
|
|
221
|
-
return [...new Set(actionNames)].flatMap((action) => {
|
|
222
|
-
const row = matrix.find((entry) => entry.name === action);
|
|
223
|
-
if (!row || row.satisfyingAdapters.includes(adapter)) return [];
|
|
224
|
-
return [{ capability: row.name, satisfyingAdapters: row.satisfyingAdapters }];
|
|
225
|
-
});
|
|
190
|
+
return actionCapabilityMatrix(catalogs);
|
|
226
191
|
}
|
|
227
192
|
function handleActionMatrixError(json, code, message, userAction, details = {}) {
|
|
228
193
|
if (json) {
|
|
@@ -315,32 +280,6 @@ function renderHumanActionMatrix(actions) {
|
|
|
315
280
|
)
|
|
316
281
|
].join("\n");
|
|
317
282
|
}
|
|
318
|
-
function fuzzyResolveActions(entries, query) {
|
|
319
|
-
const exactFull = entries.filter((e) => e.name === query);
|
|
320
|
-
if (exactFull.length > 0) return exactFull;
|
|
321
|
-
const finalSegment2 = (name) => name.split(".").pop() ?? name;
|
|
322
|
-
const exactSegment = entries.filter((e) => finalSegment2(e.name) === query);
|
|
323
|
-
if (exactSegment.length > 0) return exactSegment;
|
|
324
|
-
return entries.filter((e) => finalSegment2(e.name).includes(query));
|
|
325
|
-
}
|
|
326
|
-
function searchActions(entries, query) {
|
|
327
|
-
const terms = searchTerms(query);
|
|
328
|
-
if (terms.length === 0) return [];
|
|
329
|
-
return entries.map((entry) => {
|
|
330
|
-
const scores = terms.map((term) => actionSearchScore(entry, term));
|
|
331
|
-
return { entry, scores, score: scores.reduce((total, value) => total + value, 0) };
|
|
332
|
-
}).filter(({ score, scores }) => score > 0 && scores.every((value) => value > 0)).sort((left, right) => right.score - left.score || left.entry.name.localeCompare(right.entry.name)).map(({ entry }) => entry);
|
|
333
|
-
}
|
|
334
|
-
function findRelatedActions(entries, selected, limit = 5) {
|
|
335
|
-
const selectedNameTerms = searchTerms(finalSegment(selected.name).replaceAll("_", " "));
|
|
336
|
-
return entries.filter((entry) => entry.name !== selected.name).map((entry) => {
|
|
337
|
-
const nameTerms = new Set(searchTerms(finalSegment(entry.name).replaceAll("_", " ")));
|
|
338
|
-
const sharedScore = selectedNameTerms.filter((term) => nameTerms.has(term)).reduce((score2, term) => score2 + (GENERIC_OPERATION_TERMS.has(term) ? 5 : 30), 0);
|
|
339
|
-
const score = (entry.category === selected.category ? 100 : 0) + sharedScore;
|
|
340
|
-
return { name: entry.name, score };
|
|
341
|
-
}).filter(({ score }) => score > 0).sort((left, right) => right.score - left.score || left.name.localeCompare(right.name)).slice(0, limit).map(({ name }) => name);
|
|
342
|
-
}
|
|
343
|
-
const GENERIC_OPERATION_TERMS = /* @__PURE__ */ new Set(["assert", "call", "close", "ensure", "place", "read", "start", "teardown"]);
|
|
344
283
|
function summarizeDescribedAction(entry) {
|
|
345
284
|
const { schema: _schema, examples: _examples, ...summary } = entry;
|
|
346
285
|
return summary;
|
|
@@ -496,11 +435,6 @@ function actionDomainGroup(entry) {
|
|
|
496
435
|
const match = /^metamask[.]([^.]+)[.]/u.exec(entry.name);
|
|
497
436
|
return match ? `metamask \xB7 ${match[1]}` : "custom";
|
|
498
437
|
}
|
|
499
|
-
function summarizeActionCategories(actions) {
|
|
500
|
-
const counts = /* @__PURE__ */ new Map();
|
|
501
|
-
for (const action of actions) counts.set(action.category, (counts.get(action.category) ?? 0) + 1);
|
|
502
|
-
return [...counts.entries()].map(([name, count]) => ({ name, count })).sort((left, right) => left.name.localeCompare(right.name));
|
|
503
|
-
}
|
|
504
438
|
function describeManifestActions(manifest, actionSources = /* @__PURE__ */ new Map()) {
|
|
505
439
|
const manifestRecord = isRecord(manifest) ? manifest : {};
|
|
506
440
|
const actions = isRecord(manifestRecord.actions) ? manifestRecord.actions : {};
|
|
@@ -521,7 +455,7 @@ function describeManifestAction(name, kind, metadata, source) {
|
|
|
521
455
|
return {
|
|
522
456
|
name,
|
|
523
457
|
kind,
|
|
524
|
-
category: actionCategory(name),
|
|
458
|
+
category: actionCategory(name, ["metamask"]),
|
|
525
459
|
description: typeof record.description === "string" ? record.description : "",
|
|
526
460
|
fields: properties,
|
|
527
461
|
schema,
|
|
@@ -539,72 +473,16 @@ function describeManifestAction(name, kind, metadata, source) {
|
|
|
539
473
|
])]
|
|
540
474
|
};
|
|
541
475
|
}
|
|
542
|
-
function actionCategory(name) {
|
|
543
|
-
const segments = name.split(".");
|
|
544
|
-
if (segments[0] === "metamask" && segments.length > 2) return segments[1] ?? "metamask";
|
|
545
|
-
if (segments[0] === "app" || segments[0] === "cdp") return "runtime";
|
|
546
|
-
if (segments[0] === "ui") return "ui";
|
|
547
|
-
if (name.startsWith("assert_")) return "assertion";
|
|
548
|
-
if (name === "watch_logs" || name === "index_artifacts") return "evidence";
|
|
549
|
-
if (name === "command" || name === "wait" || name === "call" || name === "end") return "control";
|
|
550
|
-
return segments.length > 1 ? segments[0] || "utility" : "utility";
|
|
551
|
-
}
|
|
552
|
-
function actionSearchScore(entry, term) {
|
|
553
|
-
const name = entry.name.toLowerCase();
|
|
554
|
-
const segment = finalSegment(name);
|
|
555
|
-
const nameTerms = searchTerms(name.replaceAll(".", " ").replaceAll("_", " "));
|
|
556
|
-
const fields = entry.fields.map((field) => field.toLowerCase());
|
|
557
|
-
const description = entry.description.toLowerCase();
|
|
558
|
-
if (name === term) return 120;
|
|
559
|
-
if (segment === term) return 110;
|
|
560
|
-
if (entry.category === term) return 100;
|
|
561
|
-
if (fields.includes(term)) return 90;
|
|
562
|
-
if (nameTerms.includes(term)) return 80;
|
|
563
|
-
if (name.includes(term)) return 70;
|
|
564
|
-
if (fields.some((field) => field.includes(term))) return 60;
|
|
565
|
-
if (description.includes(term)) return 50;
|
|
566
|
-
if ([...nameTerms, ...fields].some((candidate) => fuzzyTermMatch(term, candidate))) return 40;
|
|
567
|
-
return 0;
|
|
568
|
-
}
|
|
569
|
-
function fuzzyTermMatch(term, candidate) {
|
|
570
|
-
if (term.length < 4 || candidate.length < 4) return false;
|
|
571
|
-
const threshold = Math.max(1, Math.floor(Math.max(term.length, candidate.length) / 4));
|
|
572
|
-
return levenshtein(term, candidate) <= threshold;
|
|
573
|
-
}
|
|
574
|
-
function searchTerms(value) {
|
|
575
|
-
return value.toLowerCase().match(/[a-z0-9]+/gu) ?? [];
|
|
576
|
-
}
|
|
577
|
-
function finalSegment(name) {
|
|
578
|
-
return name.split(".").pop() ?? name;
|
|
579
|
-
}
|
|
580
|
-
function levenshtein(left, right) {
|
|
581
|
-
const prior = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
582
|
-
for (let leftIndex = 1; leftIndex <= left.length; leftIndex += 1) {
|
|
583
|
-
const current = [leftIndex];
|
|
584
|
-
for (let rightIndex = 1; rightIndex <= right.length; rightIndex += 1) {
|
|
585
|
-
const substitution = (prior[rightIndex - 1] ?? 0) + (left[leftIndex - 1] === right[rightIndex - 1] ? 0 : 1);
|
|
586
|
-
current[rightIndex] = Math.min((current[rightIndex - 1] ?? 0) + 1, (prior[rightIndex] ?? 0) + 1, substitution);
|
|
587
|
-
}
|
|
588
|
-
for (let index = 0; index < current.length; index += 1) prior[index] = current[index] ?? 0;
|
|
589
|
-
}
|
|
590
|
-
return prior[right.length] ?? right.length;
|
|
591
|
-
}
|
|
592
476
|
export {
|
|
593
477
|
ACTION_MATRIX_ADAPTERS,
|
|
594
478
|
actionDomain,
|
|
595
479
|
actionExampleCommand,
|
|
596
480
|
actionLibraryContextArgs,
|
|
597
481
|
describeManifestActions,
|
|
598
|
-
findRelatedActions,
|
|
599
|
-
fuzzyResolveActions,
|
|
600
482
|
handleActions,
|
|
601
|
-
missingActionCapabilities,
|
|
602
483
|
renderHumanActionCatalog,
|
|
603
484
|
renderHumanActionExample,
|
|
604
485
|
renderHumanActionMatrix,
|
|
605
486
|
resolveActionCapabilityMatrix,
|
|
606
|
-
resolveActionCapabilityRefusal,
|
|
607
|
-
searchActions,
|
|
608
|
-
summarizeActionCategories,
|
|
609
487
|
summarizeDescribedAction
|
|
610
488
|
};
|
package/dist/commands/run.js
CHANGED
|
@@ -38,11 +38,8 @@ import { formatRunDiagnosticsForHuman, readRunDiagnosticsDocument } from "../run
|
|
|
38
38
|
import { recipeTrustFailure } from "../recipe-security.js";
|
|
39
39
|
import { ProvenanceDriftError } from "../execution-provenance.js";
|
|
40
40
|
import { recordCommandEvidence } from "../command-journal.js";
|
|
41
|
-
import {
|
|
42
|
-
|
|
43
|
-
missingActionCapabilities,
|
|
44
|
-
resolveActionCapabilityMatrix
|
|
45
|
-
} from "./manifest.js";
|
|
41
|
+
import { actionLibraryContextArgs, resolveActionCapabilityMatrix } from "./manifest.js";
|
|
42
|
+
import { missingActionCapabilities } from "@farmslot/recipe-cli";
|
|
46
43
|
import {
|
|
47
44
|
startRunNetworkObservation
|
|
48
45
|
} from "../network-observation.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deeeed/metamask-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.77.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mm-harness": "bin/mm-harness"
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@farmslot/expo-recipe": "0.13.1",
|
|
26
26
|
"@farmslot/handoff": "^0.3.1",
|
|
27
27
|
"@farmslot/protocol": "0.34.0",
|
|
28
|
-
"@farmslot/recipe-cli": "0.1.
|
|
28
|
+
"@farmslot/recipe-cli": "0.1.2",
|
|
29
29
|
"@farmslot/recipe-harness": "0.22.1",
|
|
30
30
|
"agent-device": "0.19.3",
|
|
31
31
|
"commander": "^12.0.0",
|