@deeeed/metamask-harness 0.5.0 → 0.6.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 +51 -20
- package/adapters/core/inject.sh +1 -0
- package/adapters/extension/cleanup.mjs +1 -1
- package/adapters/extension/inject.mjs +4 -1
- package/adapters/extension/launch-browser.cjs +18 -4
- package/adapters/extension/live.sh +14 -2
- package/adapters/extension/refresh-build.sh +2 -2
- package/adapters/extension/sidepanel-toggle.sh +33 -12
- package/adapters/extension/start-watch.sh +2 -2
- package/adapters/extension/wallet-fixture-state.cjs +38 -24
- package/adapters/manifest.json +32 -3
- package/adapters/mobile/inject.sh +1 -0
- package/adapters/mobile/start-metro.sh +1 -1
- package/adapters/mobile/stop-metro.sh +20 -0
- package/adapters/mobile/verify.sh +1 -1
- package/adapters/shared/activate-repo-node.sh +1 -1
- package/adapters/shared/cli-ux.sh +44 -25
- package/adapters/shared/ensure-runner-deps.sh +30 -0
- package/adapters/shared/log-tui.mjs +4 -4
- package/adapters/shared/open-debug.mjs +1 -1
- package/adapters/shared/reap-checkout-metros.sh +53 -0
- package/adapters/shared/recipe-harness-root.mjs +23 -0
- package/adapters/shared/resolve-farmslot-ports-core.mjs +205 -0
- package/adapters/shared/resolve-farmslot-ports.mjs +20 -0
- package/adapters/shared/resolve-farmslot-ports.sh +19 -126
- package/bin/mm-harness +45 -4
- package/dist/adapters/extension/runtime.js +3 -1
- package/dist/adapters/mobile/provision.js +34 -1
- package/dist/adapters/mobile/runtime-decision.js +3 -1
- package/dist/adapters/resolve-farmslot-ports.js +22 -0
- package/dist/adapters/slot-ports.js +18 -29
- package/dist/cli.js +32 -1463
- package/dist/commands/call.js +183 -0
- package/dist/commands/completion-candidates.js +58 -0
- package/dist/commands/doctor.js +101 -0
- package/dist/commands/ensure-ready.js +24 -0
- package/dist/commands/flows.js +62 -0
- package/dist/commands/launch/extension.js +40 -0
- package/dist/commands/{launch.js → launch/index.js} +15 -47
- package/dist/commands/launch/mobile.js +10 -0
- package/dist/commands/manifest.js +72 -0
- package/dist/commands/parse-args.js +189 -0
- package/dist/commands/provision.js +136 -0
- package/dist/commands/resolve-extension.js +23 -0
- package/dist/commands/run-engine.js +341 -0
- package/dist/commands/run.js +217 -0
- package/dist/commands/runtime-decision.js +58 -0
- package/dist/commands/runtime-health.js +25 -0
- package/dist/commands/runtime-launch.js +139 -0
- package/dist/commands/self-test.js +52 -0
- package/dist/commands/stop.js +52 -0
- package/dist/harness.js +8 -48
- package/dist/mm-harness-cli.js +13 -8
- package/docs/CLI-SPEC.md +1 -1
- package/docs/CODE-MAP.md +62 -0
- package/library/README.md +14 -0
- package/library/actions/core/perps/_controller.mjs +1 -1
- package/library/actions/extension/platform/cdp.mjs +2 -2
- package/library/actions/extension/wallet/ensure_unlocked.mjs +7 -1
- package/library/actions/harness-exports.mjs +27 -0
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +14 -2
- package/library/actions/mobile/wallet/setup.mjs +1 -1
- package/package.json +6 -7
- package/src/adapters/core/surface.ts +0 -71
- package/src/adapters/extension/ensure-ready.ts +0 -185
- package/src/adapters/extension/extension-id.ts +0 -107
- package/src/adapters/extension/runtime-decision.ts +0 -445
- package/src/adapters/extension/runtime.ts +0 -407
- package/src/adapters/extension/surface.ts +0 -88
- package/src/adapters/mobile/deps-markers.ts +0 -21
- package/src/adapters/mobile/prepare.ts +0 -246
- package/src/adapters/mobile/provision.ts +0 -594
- package/src/adapters/mobile/runtime-decision.ts +0 -459
- package/src/adapters/mobile/surface.ts +0 -71
- package/src/adapters/slot-ports.ts +0 -165
- package/src/adapters/surface.ts +0 -117
- package/src/adapters.ts +0 -601
- package/src/cli-color.ts +0 -92
- package/src/cli-commands.ts +0 -250
- package/src/cli-version.ts +0 -141
- package/src/cli.ts +0 -2091
- package/src/commands/debug.ts +0 -65
- package/src/commands/fixtures.ts +0 -198
- package/src/commands/launch.ts +0 -470
- package/src/commands/logs.ts +0 -99
- package/src/commands/shared.ts +0 -235
- package/src/commands/update.ts +0 -316
- package/src/completions-cache.ts +0 -86
- package/src/doctor.ts +0 -215
- package/src/harness.ts +0 -797
- package/src/heal-bounds.ts +0 -198
- package/src/index.ts +0 -15
- package/src/leaf-invoke.ts +0 -28
- package/src/live-adapter-contract.ts +0 -274
- package/src/manifest.ts +0 -47
- package/src/mm-harness-cli.ts +0 -655
- package/src/paths.ts +0 -198
- package/src/progress.ts +0 -117
- package/src/recording-target.ts +0 -147
- package/src/run-recording.ts +0 -329
- package/src/runner.ts +0 -108
- package/src/types.ts +0 -57
package/src/doctor.ts
DELETED
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
import { color } from './cli-color.ts';
|
|
5
|
-
import { readRuntimeContextField, resolveRuntimeContextPath } from './harness.ts';
|
|
6
|
-
import { manifestPath, readJson, recipeHarnessRoot, recipeRuntimeDir, runnerDir } from './paths.ts';
|
|
7
|
-
import type {
|
|
8
|
-
MetaMaskDoctorReport,
|
|
9
|
-
MetaMaskRecipeAdapter,
|
|
10
|
-
MetaMaskRuntimeContextField,
|
|
11
|
-
MetaMaskRuntimeContextReport,
|
|
12
|
-
} from './types.ts';
|
|
13
|
-
|
|
14
|
-
export function repoShape(target: string): Record<string, unknown> {
|
|
15
|
-
const exists = (rel) => fs.existsSync(path.join(target, rel));
|
|
16
|
-
const packageInfo = readPackageInfo(target);
|
|
17
|
-
const extensionProject =
|
|
18
|
-
packageInfo.name === 'metamask-crx' ||
|
|
19
|
-
(exists('app/manifest') && exists('app/scripts') && exists('ui'));
|
|
20
|
-
const mobileProject =
|
|
21
|
-
packageInfo.name === 'metamask' && exists('app/core') && (exists('ios') || exists('android'));
|
|
22
|
-
return {
|
|
23
|
-
packageName: packageInfo.name,
|
|
24
|
-
packageJsonStatus: packageInfo.status,
|
|
25
|
-
packageJsonError: packageInfo.error,
|
|
26
|
-
extensionProject,
|
|
27
|
-
mobileProject,
|
|
28
|
-
agenticService: exists('app/dev-tools/AgenticService/AgenticService.ts'),
|
|
29
|
-
mobileProductHarness: false,
|
|
30
|
-
mobileBridgeScript: true,
|
|
31
|
-
extensionRuntime: exists(`${recipeHarnessRoot()}/extension`),
|
|
32
|
-
injectedHarness: exists(`${recipeHarnessRoot()}/mobile`) || exists(`${recipeHarnessRoot()}/extension`),
|
|
33
|
-
walletFixture:
|
|
34
|
-
exists(`${recipeRuntimeDir()}/wallet-fixture.json`),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function compatibilityMode(adapter: MetaMaskRecipeAdapter, target: string) {
|
|
39
|
-
const shape = repoShape(target);
|
|
40
|
-
if (adapter === 'core') {
|
|
41
|
-
return fs.existsSync(path.join(target, 'packages/perps-controller/src/index.ts'))
|
|
42
|
-
? 'headless controller (no bridge)'
|
|
43
|
-
: 'unsupported/no bridge';
|
|
44
|
-
}
|
|
45
|
-
if (adapter === 'mobile') {
|
|
46
|
-
if (shape.injectedHarness && shape.agenticService) return 'runner bridge with injected app bridge';
|
|
47
|
-
if (shape.agenticService) return 'runner bridge with app bridge';
|
|
48
|
-
return 'runner bridge available; app bridge not installed';
|
|
49
|
-
}
|
|
50
|
-
if (!shape.extensionProject) return 'unsupported/no bridge';
|
|
51
|
-
if (shape.extensionRuntime || shape.injectedHarness) return 'bridge present';
|
|
52
|
-
return 'bridge injectable';
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function readPackageInfo(target: string) {
|
|
56
|
-
const packageJsonPath = path.join(target, 'package.json');
|
|
57
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
58
|
-
return { status: 'missing', name: null, error: null };
|
|
59
|
-
}
|
|
60
|
-
try {
|
|
61
|
-
const data = readJsonObject(packageJsonPath);
|
|
62
|
-
return {
|
|
63
|
-
status: 'valid',
|
|
64
|
-
name: typeof data.name === 'string' ? data.name : null,
|
|
65
|
-
error: null,
|
|
66
|
-
};
|
|
67
|
-
} catch (error) {
|
|
68
|
-
return {
|
|
69
|
-
status: 'invalid',
|
|
70
|
-
name: null,
|
|
71
|
-
error: error instanceof Error ? error.message : String(error),
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function fixtureSummary(target: string): Record<string, unknown> {
|
|
77
|
-
const candidates = [
|
|
78
|
-
`${recipeRuntimeDir()}/wallet-fixture.json`,
|
|
79
|
-
];
|
|
80
|
-
const rel = candidates.find((candidate) => fs.existsSync(path.join(target, candidate)));
|
|
81
|
-
if (!rel) return { status: 'missing', path: null };
|
|
82
|
-
try {
|
|
83
|
-
const data = readJsonObject(path.join(target, rel));
|
|
84
|
-
return {
|
|
85
|
-
status: Array.isArray(data.accounts) && data.accounts.length > 0 ? 'ready' : 'incomplete',
|
|
86
|
-
path: rel,
|
|
87
|
-
accountCount: Array.isArray(data.accounts) ? data.accounts.length : 0,
|
|
88
|
-
hasPassword: typeof data.password === 'string' && data.password.length > 0,
|
|
89
|
-
};
|
|
90
|
-
} catch (error) {
|
|
91
|
-
return { status: 'invalid', path: rel, error: error instanceof Error ? error.message : String(error) };
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// The runtime-context fields the harness hydrates from agentic-runtime.json,
|
|
96
|
-
// each paired with the env var(s) that override the file value and the one-line
|
|
97
|
-
// customize hint. `envVars` lists the harness precedence (first set wins); `envVar`
|
|
98
|
-
// is the canonical name reported. Order matches the contract table (CLI-SPEC.md).
|
|
99
|
-
const RUNTIME_CONTEXT_FIELDS: ReadonlyArray<{
|
|
100
|
-
key: string;
|
|
101
|
-
envVars: readonly string[];
|
|
102
|
-
envVar: string | null;
|
|
103
|
-
customize: string;
|
|
104
|
-
adapters: readonly MetaMaskRecipeAdapter[];
|
|
105
|
-
}> = [
|
|
106
|
-
{ key: 'slotId', envVars: ['RECIPE_SLOT_ID'], envVar: 'RECIPE_SLOT_ID', customize: 'farmslot dispatch writes this', adapters: ['mobile', 'extension', 'core'] },
|
|
107
|
-
{ key: 'extensionId', envVars: ['RECIPE_HARNESS_EXTENSION_ID'], envVar: 'RECIPE_HARNESS_EXTENSION_ID', customize: 'auto-resolved; edit file to pin', adapters: ['extension'] },
|
|
108
|
-
{ key: 'cdpPort', envVars: ['RECIPE_CDP_PORT', 'CDP_PORT'], envVar: 'CDP_PORT', customize: 'edit file or pass --cdp-port', adapters: ['extension'] },
|
|
109
|
-
{ key: 'runtimeStart.approved', envVars: ['RECIPE_RUNTIME_START_APPROVED'], envVar: 'RECIPE_RUNTIME_START_APPROVED', customize: 'edit file (true/false)', adapters: ['mobile', 'extension'] },
|
|
110
|
-
{ key: 'runtimeStart.command', envVars: [], envVar: null, customize: 'edit file', adapters: ['mobile', 'extension'] },
|
|
111
|
-
{ key: 'runtimeStart.readyUrl', envVars: ['RECIPE_RUNTIME_READY_URL'], envVar: 'RECIPE_RUNTIME_READY_URL', customize: 'edit file', adapters: ['mobile', 'extension'] },
|
|
112
|
-
];
|
|
113
|
-
|
|
114
|
-
// Report every runtime-context field with its current value and where it came from
|
|
115
|
-
// (live env override > file > unset default), reusing the harness's own file reader
|
|
116
|
-
// so doctor and dispatch agree on resolution. Absent file → fileExists:false and
|
|
117
|
-
// every field falls back to env or default; the path shows where it WOULD live.
|
|
118
|
-
// An adapter scopes the report to that platform's fields — an extension-only row
|
|
119
|
-
// (cdpPort, extensionId) is noise on a mobile or core slot.
|
|
120
|
-
export function runtimeContextSummary(target: string, adapter?: MetaMaskRecipeAdapter): MetaMaskRuntimeContextReport {
|
|
121
|
-
const contextPath = resolveRuntimeContextPath(target);
|
|
122
|
-
const envOverride = process.env.RECIPE_RUNTIME_CONTEXT ?? null;
|
|
123
|
-
const fileExists = fs.existsSync(contextPath);
|
|
124
|
-
const file = envOverride ?? path.relative(target, contextPath);
|
|
125
|
-
const fields: Record<string, MetaMaskRuntimeContextField> = {};
|
|
126
|
-
const specs = adapter
|
|
127
|
-
? RUNTIME_CONTEXT_FIELDS.filter((spec) => spec.adapters.includes(adapter))
|
|
128
|
-
: RUNTIME_CONTEXT_FIELDS;
|
|
129
|
-
for (const spec of specs) {
|
|
130
|
-
const envValue = spec.envVars
|
|
131
|
-
.map((name) => process.env[name])
|
|
132
|
-
.find((value) => value !== undefined && value !== '');
|
|
133
|
-
if (envValue !== undefined) {
|
|
134
|
-
fields[spec.key] = { value: envValue, source: 'env', envVar: spec.envVar, customize: spec.customize };
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
const fileValue = fileExists ? readRuntimeContextField(contextPath, spec.key) : undefined;
|
|
138
|
-
fields[spec.key] = fileValue !== undefined
|
|
139
|
-
? { value: fileValue, source: 'file', envVar: spec.envVar, customize: spec.customize }
|
|
140
|
-
: { value: null, source: 'default', envVar: spec.envVar, customize: spec.customize };
|
|
141
|
-
}
|
|
142
|
-
return { file, fileExists, envOverride, fields };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Human-readable runtime-context section for `doctor` without --json.
|
|
146
|
-
export function renderRuntimeContext(runtimeContext: MetaMaskRuntimeContextReport): string {
|
|
147
|
-
// Human render: set values read bright with their provenance highlighted;
|
|
148
|
-
// unset defaults read dim so the eye lands on what is actually configured.
|
|
149
|
-
const out = (style: string, text: string) => color(style, text, { stream: process.stdout });
|
|
150
|
-
const lines: string[] = [];
|
|
151
|
-
lines.push(
|
|
152
|
-
runtimeContext.fileExists
|
|
153
|
-
? `${out('label', 'runtime-context:')} ${runtimeContext.file} ${out('ok', '(present)')}`
|
|
154
|
-
: `${out('label', 'runtime-context:')} ${runtimeContext.file} ${out('dim', '(absent — written by farmslot prepare/dispatch)')}`,
|
|
155
|
-
);
|
|
156
|
-
for (const [key, field] of Object.entries(runtimeContext.fields)) {
|
|
157
|
-
const isSet = field.value !== undefined && field.value !== null && field.value !== '';
|
|
158
|
-
const value = isSet ? out('ok', String(field.value)) : out('dim', '(unset)');
|
|
159
|
-
const origin = field.source === 'env' && field.envVar ? `env ${field.envVar}` : field.source;
|
|
160
|
-
const originTag = isSet ? out('accent', `[${origin}]`) : out('dim', `[${origin}]`);
|
|
161
|
-
lines.push(` ${key.padEnd(22)} ${value} ${originTag} ${out('dim', `— ${field.customize}`)}`);
|
|
162
|
-
}
|
|
163
|
-
return lines.join('\n');
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function createDoctorReport(
|
|
167
|
-
adapter: MetaMaskRecipeAdapter,
|
|
168
|
-
target: string,
|
|
169
|
-
manifestValidation: { summary?: { errors?: number } & Record<string, unknown> },
|
|
170
|
-
actionManifestPath = manifestPath(adapter),
|
|
171
|
-
): MetaMaskDoctorReport {
|
|
172
|
-
const mode = compatibilityMode(adapter, target);
|
|
173
|
-
const manifestErrors = Number(manifestValidation.summary?.errors ?? 0);
|
|
174
|
-
const status = manifestErrors > 0 ? 'fail' : 'pass';
|
|
175
|
-
const checks = [
|
|
176
|
-
{
|
|
177
|
-
id: 'manifest',
|
|
178
|
-
status: manifestErrors === 0 ? 'pass' : 'fail',
|
|
179
|
-
message: manifestErrors === 0 ? 'Action manifest is valid Recipe v1.' : `Action manifest has ${manifestErrors} validation error(s).`,
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
id: 'bridge',
|
|
183
|
-
status: mode === 'unsupported/no bridge' ? 'fail' : 'pass',
|
|
184
|
-
message: mode === 'unsupported/no bridge' ? `No ${adapter} bridge is available for this checkout.` : `${adapter} compatibility mode: ${mode}.`,
|
|
185
|
-
},
|
|
186
|
-
] as const;
|
|
187
|
-
return {
|
|
188
|
-
schemaVersion: 1,
|
|
189
|
-
protocolVersion: 'v1',
|
|
190
|
-
runner_protocol_version: 1,
|
|
191
|
-
status,
|
|
192
|
-
checks: [...checks],
|
|
193
|
-
adapter,
|
|
194
|
-
target,
|
|
195
|
-
runner: {
|
|
196
|
-
name: '@metamask/recipe-runner',
|
|
197
|
-
runnerDir,
|
|
198
|
-
actionManifestPath,
|
|
199
|
-
harnessPackage: '@farmslot/recipe-harness',
|
|
200
|
-
},
|
|
201
|
-
compatibilityMode: mode,
|
|
202
|
-
shape: repoShape(target),
|
|
203
|
-
fixture: fixtureSummary(target),
|
|
204
|
-
runtimeContext: runtimeContextSummary(target, adapter),
|
|
205
|
-
manifestValidation: manifestValidation.summary,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function readJsonObject(file: string): Record<string, unknown> {
|
|
210
|
-
const value = readJson(file);
|
|
211
|
-
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
212
|
-
throw new Error(`Expected JSON object in ${file}`);
|
|
213
|
-
}
|
|
214
|
-
return value as Record<string, unknown>;
|
|
215
|
-
}
|