state-scenarios-cli 0.3.0 → 0.4.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/README.md +10 -1
- package/dist/browser.d.ts +27 -0
- package/dist/chunk-GCYOLAFU.js +40 -0
- package/dist/chunk-GCYOLAFU.js.map +1 -0
- package/dist/chunk-O6QTNAAZ.js +67 -0
- package/dist/chunk-O6QTNAAZ.js.map +1 -0
- package/dist/chunk-YFNROT3R.js +115 -0
- package/dist/chunk-YFNROT3R.js.map +1 -0
- package/dist/cli.js +240 -44
- package/dist/cli.js.map +1 -1
- package/dist/doctor-URGIFA3W.js +200 -0
- package/dist/doctor-URGIFA3W.js.map +1 -0
- package/dist/doctor.d.ts +21 -0
- package/dist/errors-G7ODRBLT.js +11 -0
- package/dist/errors-G7ODRBLT.js.map +1 -0
- package/dist/errors.d.ts +33 -0
- package/dist/{init-VLZQEJRD.js → init-MRK4F5SV.js} +2 -1
- package/dist/init-MRK4F5SV.js.map +1 -0
- package/dist/init.d.ts +5 -1
- package/dist/inspect-LN4EACWA.js +13 -0
- package/dist/inspect-LN4EACWA.js.map +1 -0
- package/dist/inspect.d.ts +63 -0
- package/dist/mcp-HRQIWAIR.js +342 -0
- package/dist/mcp-HRQIWAIR.js.map +1 -0
- package/dist/mcp-protocol.d.ts +51 -0
- package/dist/mcp.d.ts +19 -0
- package/dist/record-WEZRD2VT.js +59 -0
- package/dist/record-WEZRD2VT.js.map +1 -0
- package/dist/record.d.ts +41 -0
- package/dist/skills-NQRJHKHG.js +80 -0
- package/dist/skills-NQRJHKHG.js.map +1 -0
- package/dist/skills.d.ts +13 -0
- package/package.json +5 -4
- package/skills/create-scenario-map/SKILL.md +88 -0
- package/skills/state-scenarios/SKILL.md +91 -0
- package/skills/state-scenarios/references/auth.md +52 -0
- package/skills/state-scenarios/references/capture.md +45 -0
- package/skills/state-scenarios/references/debugging.md +52 -0
- package/skills/state-scenarios/references/mcp.md +39 -0
- package/skills/state-scenarios/references/media.md +50 -0
- package/skills/state-scenarios/references/network.md +67 -0
- package/skills/state-scenarios/references/setup.md +94 -0
- package/skills/state-scenarios/references/verify.md +47 -0
- package/skills/state-scenarios/references/writing-scenarios.md +55 -0
- package/dist/init-VLZQEJRD.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,26 +1,51 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
checkServer,
|
|
4
|
+
contextOptions,
|
|
5
|
+
launchChromium,
|
|
6
|
+
scenarioLink
|
|
7
|
+
} from "./chunk-O6QTNAAZ.js";
|
|
8
|
+
import {
|
|
9
|
+
CliError
|
|
10
|
+
} from "./chunk-GCYOLAFU.js";
|
|
2
11
|
|
|
3
12
|
// src/cli.ts
|
|
4
|
-
import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
13
|
+
import { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "fs";
|
|
14
|
+
import { tmpdir } from "os";
|
|
5
15
|
import { join, relative, resolve } from "path";
|
|
6
16
|
import { pathToFileURL } from "url";
|
|
7
17
|
import { parseArgs } from "util";
|
|
8
18
|
import { createCatalog, scenarioJsonSchema } from "state-scenarios";
|
|
19
|
+
import { z } from "zod";
|
|
9
20
|
var HELP = `Usage: state-scenarios <command> <scenarios-dir> [options]
|
|
10
21
|
|
|
11
22
|
init Create <dir> (default src/scenarios) with default.json, a sibling scenario.schema.json,
|
|
12
|
-
|
|
23
|
+
print the wiring snippet, install the agent skills into .agents/skills and .claude/skills,
|
|
24
|
+
and register the MCP server in .mcp.json. --no-skills skips the last two; --base-url sets the
|
|
25
|
+
URL the MCP server opens (default http://localhost:5173, or :3000 for Next.js)
|
|
26
|
+
guide Print the agent skill for this version: guide [topic]. guide alone lists the topics
|
|
27
|
+
doctor Check the wiring one step at a time and print the fix for each problem: scenarios, dev server,
|
|
28
|
+
MSW worker, session start, ready selector, save route, ffmpeg, skills and MCP. --base-url (required)
|
|
29
|
+
mcp Serve an MCP server on stdio for coding agents: list, open and drive scenarios, check ready
|
|
30
|
+
selectors, and save what the app shows as a scenario. --base-url (required)
|
|
13
31
|
validate Check every scenario parses, resolves and passes --rules
|
|
14
32
|
list Print scenarios with links (--base-url) for people and agents; --json for machines
|
|
15
33
|
shoot Open each scenario in Chromium, wait for its ready selector, screenshot it,
|
|
16
|
-
and report requests it didn't mock. --base-url (required), --out, --only a,b, --no-strict
|
|
34
|
+
and report requests it didn't mock. --base-url (required), --out, --only a,b, --no-strict,
|
|
35
|
+
--element <selector> to capture one element instead of the full page.
|
|
36
|
+
Each failed scenario gets <out>/<name>.md: what failed and how to fix it
|
|
37
|
+
record Open each scenario that has steps, run them, and save a video with a visible cursor.
|
|
38
|
+
--base-url and --steps (required): a module whose default export maps scenario names
|
|
39
|
+
to async (page) => {} steps. --format mp4 (default), gif or webm; --out, --only, --element.
|
|
40
|
+
mp4, gif and --element need ffmpeg on PATH (or FFMPEG_PATH)
|
|
17
41
|
suggest Ask Claude which UI states lack scenarios and draft them into --out (default <dir>/proposed).
|
|
18
42
|
--src dir[,dir] (required), --schema file, --max n, --model (default $STATE_SCENARIOS_MODEL or claude-opus-5-5).
|
|
19
43
|
Needs ANTHROPIC_API_KEY or \`ant auth login\`; ANTHROPIC_BASE_URL points it at a compatible proxy.
|
|
20
44
|
--env-file file loads those variables first.
|
|
21
45
|
promote Move <dir>/proposed/*.json into <dir> after validate. --dry-run lists moves only.
|
|
22
46
|
|
|
23
|
-
Common: --rules file module exporting \`rules\` (ScenarioRule[])
|
|
47
|
+
Common: --rules file module exporting \`rules\` (ScenarioRule[])
|
|
48
|
+
--json machine-readable output (list, shoot, doctor)`;
|
|
24
49
|
var MAX_SOURCE_CHARS = 8e5;
|
|
25
50
|
var { positionals, values } = parseArgs({
|
|
26
51
|
allowPositionals: true,
|
|
@@ -30,6 +55,15 @@ var { positionals, values } = parseArgs({
|
|
|
30
55
|
out: { type: "string" },
|
|
31
56
|
only: { type: "string" },
|
|
32
57
|
"no-strict": { type: "boolean" },
|
|
58
|
+
"no-panel": { type: "boolean" },
|
|
59
|
+
viewport: { type: "string" },
|
|
60
|
+
scale: { type: "string" },
|
|
61
|
+
"color-scheme": { type: "string" },
|
|
62
|
+
element: { type: "string" },
|
|
63
|
+
locale: { type: "string" },
|
|
64
|
+
timezone: { type: "string" },
|
|
65
|
+
steps: { type: "string" },
|
|
66
|
+
format: { type: "string" },
|
|
33
67
|
json: { type: "boolean" },
|
|
34
68
|
src: { type: "string" },
|
|
35
69
|
schema: { type: "string" },
|
|
@@ -37,18 +71,63 @@ var { positionals, values } = parseArgs({
|
|
|
37
71
|
model: { type: "string" },
|
|
38
72
|
"env-file": { type: "string" },
|
|
39
73
|
"dry-run": { type: "boolean" },
|
|
74
|
+
"no-skills": { type: "boolean" },
|
|
40
75
|
help: { type: "boolean", short: "h" }
|
|
41
76
|
}
|
|
42
77
|
});
|
|
78
|
+
var StepsSchema = z.record(
|
|
79
|
+
z.string(),
|
|
80
|
+
z.function({ input: [z.custom()], output: z.unknown() })
|
|
81
|
+
);
|
|
43
82
|
var [command, dir = command === "init" ? "src/scenarios" : void 0] = positionals;
|
|
83
|
+
if (command === "guide") {
|
|
84
|
+
const { guide } = await import("./skills-NQRJHKHG.js");
|
|
85
|
+
try {
|
|
86
|
+
console.log(guide(positionals[1]));
|
|
87
|
+
process.exit(0);
|
|
88
|
+
} catch (err) {
|
|
89
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
90
|
+
process.exit(2);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
44
93
|
if (values.help || !command || !dir) {
|
|
45
94
|
console.log(HELP);
|
|
46
|
-
process.exit(command ?
|
|
95
|
+
process.exit(command ? 2 : 0);
|
|
47
96
|
}
|
|
97
|
+
var loadCatalog = (rules) => {
|
|
98
|
+
try {
|
|
99
|
+
return createCatalog(
|
|
100
|
+
readdirSync(dir).filter((f) => f.endsWith(".json")).map(
|
|
101
|
+
(f) => JSON.parse(readFileSync(join(dir, f), "utf8"))
|
|
102
|
+
),
|
|
103
|
+
{ rules }
|
|
104
|
+
);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
throw new CliError("INVALID_SCENARIO", err instanceof Error ? err.message : String(err));
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var requireBaseUrl = () => {
|
|
110
|
+
const url = values["base-url"];
|
|
111
|
+
if (!url) throw new CliError("INVALID_ARGUMENT", `${command} needs --base-url, e.g. http://localhost:5173`);
|
|
112
|
+
return url;
|
|
113
|
+
};
|
|
48
114
|
try {
|
|
49
115
|
if (command === "init") {
|
|
50
|
-
const { initScenarios } = await import("./init-
|
|
51
|
-
initScenarios(dir);
|
|
116
|
+
const { initScenarios } = await import("./init-MRK4F5SV.js");
|
|
117
|
+
const { stack } = initScenarios(dir);
|
|
118
|
+
if (!values["no-skills"]) {
|
|
119
|
+
const { installSkills, registerMcp } = await import("./skills-NQRJHKHG.js");
|
|
120
|
+
const url = values["base-url"] ?? (stack === "next" ? "http://localhost:3000" : "http://localhost:5173");
|
|
121
|
+
const args = ["mcp", dir, "--base-url", url];
|
|
122
|
+
console.log("");
|
|
123
|
+
for (const line of [...installSkills(process.cwd()), ...registerMcp(process.cwd(), args)]) {
|
|
124
|
+
console.log(line);
|
|
125
|
+
}
|
|
126
|
+
console.log(`
|
|
127
|
+
Codex: codex mcp add state-scenarios -- npx state-scenarios ${args.join(" ")}
|
|
128
|
+
For agents that need a pointer, add to AGENTS.md (or CLAUDE.md):
|
|
129
|
+
UI states use state-scenarios; read .agents/skills/state-scenarios/SKILL.md before adding or checking one.`);
|
|
130
|
+
}
|
|
52
131
|
process.exit(0);
|
|
53
132
|
}
|
|
54
133
|
const rules = values.rules ? (await import(pathToFileURL(resolve(values.rules)).href)).rules : [];
|
|
@@ -57,11 +136,42 @@ try {
|
|
|
57
136
|
const result = promoteScenarios(dir, { rules, dryRun: !!values["dry-run"] });
|
|
58
137
|
process.exit(result.skipped.length ? 1 : 0);
|
|
59
138
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
139
|
+
if (command === "mcp") {
|
|
140
|
+
const { serveMcp } = await import("./mcp-HRQIWAIR.js");
|
|
141
|
+
const { version } = z.object({ version: z.string() }).parse(
|
|
142
|
+
JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"))
|
|
143
|
+
);
|
|
144
|
+
await serveMcp({
|
|
145
|
+
dir,
|
|
146
|
+
baseURL: requireBaseUrl(),
|
|
147
|
+
loadCatalog: () => loadCatalog(rules),
|
|
148
|
+
flags: values,
|
|
149
|
+
version
|
|
150
|
+
});
|
|
151
|
+
process.exit(0);
|
|
152
|
+
}
|
|
153
|
+
if (command === "doctor") {
|
|
154
|
+
const { runDoctor } = await import("./doctor-URGIFA3W.js");
|
|
155
|
+
const { EXIT_CODES, ERROR_CLASSES } = await import("./errors-G7ODRBLT.js");
|
|
156
|
+
const checks = await runDoctor({
|
|
157
|
+
dir,
|
|
158
|
+
baseURL: requireBaseUrl(),
|
|
159
|
+
loadCatalog: () => loadCatalog(rules),
|
|
160
|
+
flags: values
|
|
161
|
+
});
|
|
162
|
+
if (values.json) console.log(JSON.stringify(checks, null, 2));
|
|
163
|
+
else {
|
|
164
|
+
for (const c of checks) {
|
|
165
|
+
console.log(
|
|
166
|
+
`${{ ok: "\u2713", warn: "!", fail: "\u2717" }[c.status]} ${c.name.padEnd(16)} ${c.code ? `[${c.code}] ` : ""}${c.detail}`
|
|
167
|
+
);
|
|
168
|
+
if (c.fix && c.status !== "ok") console.log(` ${" ".repeat(16)} fix: ${c.fix}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const failed = checks.filter((c) => c.status === "fail");
|
|
172
|
+
process.exit(Math.max(0, ...failed.map((c) => c.code ? EXIT_CODES[ERROR_CLASSES[c.code]] : 1)));
|
|
173
|
+
}
|
|
174
|
+
const catalog = loadCatalog(rules);
|
|
65
175
|
switch (command) {
|
|
66
176
|
case "validate": {
|
|
67
177
|
const problems = catalog.check();
|
|
@@ -91,41 +201,46 @@ ${" ".repeat(33)}${r.url}` : ""}`
|
|
|
91
201
|
break;
|
|
92
202
|
}
|
|
93
203
|
case "shoot": {
|
|
94
|
-
const baseURL =
|
|
95
|
-
if (!baseURL) throw new Error("shoot needs --base-url, e.g. http://localhost:5173");
|
|
204
|
+
const baseURL = requireBaseUrl();
|
|
96
205
|
const out = values.out ?? "scenario-shots";
|
|
97
206
|
const only = values.only?.split(",");
|
|
207
|
+
for (const n of only ?? []) {
|
|
208
|
+
if (!catalog.get(n)) throw new CliError("UNKNOWN_SCENARIO", `--only names unknown scenario "${n}"`);
|
|
209
|
+
}
|
|
98
210
|
mkdirSync(out, { recursive: true });
|
|
99
|
-
|
|
100
|
-
const {
|
|
101
|
-
const browser = await
|
|
211
|
+
await checkServer(baseURL);
|
|
212
|
+
const { inspectScenario, failurePage } = await import("./inspect-LN4EACWA.js");
|
|
213
|
+
const browser = await launchChromium();
|
|
102
214
|
const report = [];
|
|
103
215
|
for (const s of catalog.list.filter((s2) => !only || only.includes(s2.name))) {
|
|
104
|
-
const
|
|
216
|
+
const scenario = catalog.resolve(s);
|
|
217
|
+
const context = await browser.newContext({ baseURL, ...contextOptions(scenario, values) });
|
|
105
218
|
const page = await context.newPage();
|
|
106
219
|
page.setDefaultTimeout(1e4);
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
consoleErrors.push(m.text());
|
|
112
|
-
}
|
|
220
|
+
const result = await inspectScenario(page, scenario, {
|
|
221
|
+
link: scenarioLink(baseURL, scenario),
|
|
222
|
+
strict: !values["no-strict"],
|
|
223
|
+
panel: !values["no-panel"]
|
|
113
224
|
});
|
|
114
|
-
page.on("pageerror", (err) => pageErrors.push(err.message));
|
|
115
|
-
let error = null;
|
|
116
|
-
try {
|
|
117
|
-
await openScenario(page, s.name, {
|
|
118
|
-
path: catalog.resolve(s).path,
|
|
119
|
-
strict: !values["no-strict"]
|
|
120
|
-
});
|
|
121
|
-
} catch (err) {
|
|
122
|
-
error = err instanceof Error ? err.message.split("\n")[0] : String(err);
|
|
123
|
-
}
|
|
124
225
|
const screenshot = join(out, `${s.name}.png`);
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
226
|
+
let captured = false;
|
|
227
|
+
if (values.element && !result.error) {
|
|
228
|
+
try {
|
|
229
|
+
await page.locator(values.element).first().screenshot({ path: screenshot });
|
|
230
|
+
captured = true;
|
|
231
|
+
} catch (err) {
|
|
232
|
+
result.codes.push("ELEMENT_NOT_FOUND");
|
|
233
|
+
result.ok = false;
|
|
234
|
+
result.error = `--element: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (!captured) await page.screenshot({ path: screenshot, fullPage: true });
|
|
238
|
+
const details = join(out, `${s.name}.md`);
|
|
239
|
+
rmSync(details, { force: true });
|
|
240
|
+
if (!result.ok) {
|
|
241
|
+
writeFileSync(details, failurePage(result, { screenshot, appOrigin: new URL(baseURL).origin }));
|
|
242
|
+
}
|
|
243
|
+
report.push({ ...result, screenshot, details: result.ok ? null : details });
|
|
129
244
|
await context.close();
|
|
130
245
|
}
|
|
131
246
|
await browser.close();
|
|
@@ -133,16 +248,96 @@ ${" ".repeat(33)}${r.url}` : ""}`
|
|
|
133
248
|
else {
|
|
134
249
|
for (const r of report) {
|
|
135
250
|
console.log(`${r.ok ? "\u2713" : "\u2717"} ${r.name.padEnd(32)} ${r.screenshot}`);
|
|
136
|
-
if (r.
|
|
251
|
+
if (r.ok) continue;
|
|
252
|
+
console.log(` ${r.codes.join(", ")}${r.error ? `: ${r.error}` : ""}`);
|
|
137
253
|
for (const u of r.unhandled) console.log(` unmocked: ${u}`);
|
|
138
254
|
for (const e of r.pageErrors) console.log(` uncaught: ${e}`);
|
|
139
255
|
for (const e of r.consoleErrors) console.log(` console.error: ${e}`);
|
|
256
|
+
console.log(` \u276F details ${r.details}`);
|
|
140
257
|
}
|
|
141
258
|
}
|
|
142
259
|
process.exit(report.every((r) => r.ok) ? 0 : 1);
|
|
143
260
|
}
|
|
261
|
+
case "record": {
|
|
262
|
+
const baseURL = requireBaseUrl();
|
|
263
|
+
if (!values.steps) {
|
|
264
|
+
throw new CliError("INVALID_ARGUMENT", "record needs --steps, e.g. --steps scenarios/steps.ts");
|
|
265
|
+
}
|
|
266
|
+
const format = values.format ?? "mp4";
|
|
267
|
+
if (format !== "mp4" && format !== "gif" && format !== "webm") {
|
|
268
|
+
throw new CliError("INVALID_ARGUMENT", `--format must be mp4, gif or webm, not "${format}"`);
|
|
269
|
+
}
|
|
270
|
+
const steps = StepsSchema.parse((await import(pathToFileURL(resolve(values.steps)).href)).default);
|
|
271
|
+
const only = values.only?.split(",");
|
|
272
|
+
const names = Object.keys(steps).filter((n) => !only || only.includes(n));
|
|
273
|
+
for (const n of names) {
|
|
274
|
+
if (!catalog.get(n)) throw new CliError("UNKNOWN_SCENARIO", `--steps names unknown scenario "${n}"`);
|
|
275
|
+
}
|
|
276
|
+
const out = values.out ?? "scenario-videos";
|
|
277
|
+
mkdirSync(out, { recursive: true });
|
|
278
|
+
const raw = mkdtempSync(join(tmpdir(), "state-scenarios-"));
|
|
279
|
+
await checkServer(baseURL);
|
|
280
|
+
const { openScenario, showCursor } = await import("state-scenarios/playwright");
|
|
281
|
+
const { encodeVideo, visibleCrop } = await import("./record-WEZRD2VT.js");
|
|
282
|
+
const browser = await launchChromium({ slowMo: 150 });
|
|
283
|
+
let exitCode = 0;
|
|
284
|
+
const cropTo = async (page, selector, viewport) => {
|
|
285
|
+
const element = page.locator(selector).first();
|
|
286
|
+
const box = await element.scrollIntoViewIfNeeded({ timeout: 5e3 }).then(
|
|
287
|
+
() => element.boundingBox(),
|
|
288
|
+
() => null
|
|
289
|
+
);
|
|
290
|
+
const visible = box && visibleCrop(box, viewport);
|
|
291
|
+
if (!visible) throw new CliError("ELEMENT_NOT_FOUND", `--element "${selector}" isn't visible`);
|
|
292
|
+
if (visible.clipped) {
|
|
293
|
+
console.log(
|
|
294
|
+
` --element "${selector}" (${Math.round(box.width)}x${Math.round(box.height)}) is larger than the viewport; the video shows the visible part. Use a larger --viewport to fit it.`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
return visible.crop;
|
|
298
|
+
};
|
|
299
|
+
for (const [name, run] of Object.entries(steps).filter(([n]) => names.includes(n))) {
|
|
300
|
+
const resolved = catalog.resolve(catalog.get(name) ?? { name });
|
|
301
|
+
const options = contextOptions(resolved, values);
|
|
302
|
+
const context = await browser.newContext({
|
|
303
|
+
baseURL,
|
|
304
|
+
...options,
|
|
305
|
+
recordVideo: { dir: raw, size: options.viewport }
|
|
306
|
+
});
|
|
307
|
+
const page = await context.newPage();
|
|
308
|
+
const began = Date.now();
|
|
309
|
+
const file = join(out, `${name}.${format}`);
|
|
310
|
+
try {
|
|
311
|
+
await showCursor(page);
|
|
312
|
+
await openScenario(page, name, { path: resolved.path, strict: !values["no-strict"], panel: false });
|
|
313
|
+
const crop = values.element ? await cropTo(page, values.element, options.viewport) : void 0;
|
|
314
|
+
const start = (Date.now() - began) / 1e3;
|
|
315
|
+
await Promise.resolve(run(page)).catch((err) => {
|
|
316
|
+
throw new CliError("STEPS_FAILED", `steps failed: ${err.message.split("\n")[0]}`);
|
|
317
|
+
});
|
|
318
|
+
await page.waitForTimeout(800);
|
|
319
|
+
await context.close();
|
|
320
|
+
const video = page.video();
|
|
321
|
+
if (!video) throw new Error("Playwright recorded no video");
|
|
322
|
+
encodeVideo({ input: await video.path(), output: file, format, start, crop });
|
|
323
|
+
console.log(`\u2713 ${name.padEnd(32)} ${file}`);
|
|
324
|
+
} catch (err) {
|
|
325
|
+
exitCode = Math.max(exitCode, err instanceof CliError ? err.exitCode : 1);
|
|
326
|
+
await context.close();
|
|
327
|
+
const code = err instanceof CliError ? `[${err.code}] ` : "";
|
|
328
|
+
console.log(
|
|
329
|
+
`\u2717 ${name.padEnd(32)} ${code}${err instanceof Error ? err.message.split("\n")[0] : String(err)}`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
await browser.close();
|
|
334
|
+
rmSync(raw, { recursive: true, force: true });
|
|
335
|
+
process.exit(exitCode);
|
|
336
|
+
}
|
|
144
337
|
case "suggest": {
|
|
145
|
-
if (!values.src)
|
|
338
|
+
if (!values.src) {
|
|
339
|
+
throw new CliError("INVALID_ARGUMENT", "suggest needs --src, e.g. --src src/pages,src/components");
|
|
340
|
+
}
|
|
146
341
|
const sources = values.src.split(",").flatMap((d) => walk(d));
|
|
147
342
|
const size = sources.reduce((n, f) => n + f.content.length, 0);
|
|
148
343
|
if (size > MAX_SOURCE_CHARS) {
|
|
@@ -187,7 +382,7 @@ ${result.states.length} states detected, ${covered} have scenarios, ${result.sta
|
|
|
187
382
|
if (result.accepted.length) mkdirSync(out, { recursive: true });
|
|
188
383
|
for (const { scenario, covers } of result.accepted) {
|
|
189
384
|
const file = join(out, `${scenario.name}.json`);
|
|
190
|
-
const $schema = values.schema ? relative(out, values.schema) : void 0;
|
|
385
|
+
const $schema = values.schema ? relative(out, values.schema).replaceAll("\\", "/") : void 0;
|
|
191
386
|
writeFileSync(file, `${JSON.stringify({ $schema, ...scenario }, null, 2)}
|
|
192
387
|
`);
|
|
193
388
|
console.log(`+ ${file} (${covers})`);
|
|
@@ -205,13 +400,14 @@ Review the drafts, then: state-scenarios promote ${dir}`
|
|
|
205
400
|
console.error(`Unknown command "${command}"
|
|
206
401
|
|
|
207
402
|
${HELP}`);
|
|
208
|
-
process.exit(
|
|
403
|
+
process.exit(2);
|
|
209
404
|
}
|
|
210
405
|
} catch (err) {
|
|
211
406
|
const message = err instanceof Error ? err.message : String(err);
|
|
212
407
|
const hint = /authentication/i.test(message) ? "\nSet ANTHROPIC_API_KEY, or run `ant auth login`." : "";
|
|
213
|
-
|
|
214
|
-
|
|
408
|
+
const code = err instanceof CliError ? `[${err.code}] ` : "";
|
|
409
|
+
console.error(`state-scenarios ${command}: ${code}${message}${hint}`);
|
|
410
|
+
process.exit(err instanceof CliError ? err.exitCode : 1);
|
|
215
411
|
}
|
|
216
412
|
function walk(path) {
|
|
217
413
|
if (statSync(path).isDirectory()) {
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';\nimport { join, relative, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { parseArgs } from 'node:util';\nimport { createCatalog, scenarioJsonSchema, type ScenarioRule } from 'state-scenarios';\n\nconst HELP = `Usage: state-scenarios <command> <scenarios-dir> [options]\n\n init Create <dir> (default src/scenarios) with default.json, a sibling scenario.schema.json,\n and print the wiring snippet to paste into your entry file\n validate Check every scenario parses, resolves and passes --rules\n list Print scenarios with links (--base-url) for people and agents; --json for machines\n shoot Open each scenario in Chromium, wait for its ready selector, screenshot it,\n and report requests it didn't mock. --base-url (required), --out, --only a,b, --no-strict\n suggest Ask Claude which UI states lack scenarios and draft them into --out (default <dir>/proposed).\n --src dir[,dir] (required), --schema file, --max n, --model (default $STATE_SCENARIOS_MODEL or claude-opus-5-5).\n Needs ANTHROPIC_API_KEY or \\`ant auth login\\`; ANTHROPIC_BASE_URL points it at a compatible proxy.\n --env-file file loads those variables first.\n promote Move <dir>/proposed/*.json into <dir> after validate. --dry-run lists moves only.\n\nCommon: --rules file module exporting \\`rules\\` (ScenarioRule[])`;\n\nconst MAX_SOURCE_CHARS = 800_000;\n\nconst { positionals, values } = parseArgs({\n allowPositionals: true,\n options: {\n rules: { type: 'string' },\n 'base-url': { type: 'string' },\n out: { type: 'string' },\n only: { type: 'string' },\n 'no-strict': { type: 'boolean' },\n json: { type: 'boolean' },\n src: { type: 'string' },\n schema: { type: 'string' },\n max: { type: 'string' },\n model: { type: 'string' },\n 'env-file': { type: 'string' },\n 'dry-run': { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n },\n});\n\nconst [command, dir = command === 'init' ? 'src/scenarios' : undefined] = positionals;\n\nif (values.help || !command || !dir) {\n console.log(HELP);\n process.exit(command ? 1 : 0);\n}\n\ntry {\n if (command === 'init') {\n const { initScenarios } = await import('./init.js');\n initScenarios(dir);\n process.exit(0);\n }\n\n const rules: ScenarioRule[] = values.rules\n ? (await import(pathToFileURL(resolve(values.rules)).href)).rules\n : [];\n\n if (command === 'promote') {\n const { promoteScenarios } = await import('./promote.js');\n const result = promoteScenarios(dir, { rules, dryRun: !!values['dry-run'] });\n process.exit(result.skipped.length ? 1 : 0);\n }\n\n const files = readdirSync(dir).filter((f) => f.endsWith('.json'));\n\n const catalog = createCatalog(\n files.map((f) => JSON.parse(readFileSync(join(dir, f), 'utf8'))),\n { rules },\n );\n\n switch (command) {\n case 'validate': {\n const problems = catalog.check();\n\n for (const p of problems) console.error(`✗ ${p}\\n`);\n console.log(`${catalog.list.length - problems.length}/${catalog.list.length} scenarios valid`);\n process.exit(problems.length ? 1 : 0);\n }\n\n case 'list': {\n const base = values['base-url'];\n\n const rows = catalog.list.map((s) => ({\n name: s.name,\n description: s.description ?? '',\n tags: s.tags ?? [],\n ready: s.ready ?? null,\n url: base\n ? `${base.replace(/\\/$/, '')}${catalog.resolve(s).path ?? '/'}?scenario=${\n encodeURIComponent(s.name)\n }`\n : null,\n }));\n\n if (values.json) console.log(JSON.stringify(rows, null, 2));\n else {\n for (const r of rows) {\n console.log(\n `${r.name.padEnd(32)} ${r.description}${r.url ? `\\n${' '.repeat(33)}${r.url}` : ''}`,\n );\n }\n }\n\n break;\n }\n\n case 'shoot': {\n const baseURL = values['base-url'];\n\n if (!baseURL) throw new Error('shoot needs --base-url, e.g. http://localhost:5173');\n const out = values.out ?? 'scenario-shots';\n const only = values.only?.split(',');\n mkdirSync(out, { recursive: true });\n\n const { chromium } = await import('@playwright/test');\n const { openScenario, unhandledRequests } = await import('state-scenarios/playwright');\n const browser = await chromium.launch();\n const report = [];\n\n for (const s of catalog.list.filter((s) => !only || only.includes(s.name))) {\n const context = await browser.newContext({ baseURL });\n const page = await context.newPage();\n page.setDefaultTimeout(10_000);\n // Uncaught exceptions and console errors both fail the run. Browser network logs\n // (\"Failed to load resource\") are excluded: error scenarios cause them on purpose,\n // and requests the scenario didn't mock are reported as `unhandled` instead.\n const consoleErrors: string[] = [];\n const pageErrors: string[] = [];\n page.on('console', (m) => {\n if (m.type() === 'error' && !m.text().startsWith('Failed to load resource')) {\n consoleErrors.push(m.text());\n }\n });\n page.on('pageerror', (err) => pageErrors.push(err.message));\n let error: string | null = null;\n\n try {\n await openScenario(page, s.name, {\n path: catalog.resolve(s).path,\n strict: !values['no-strict'],\n });\n } catch (err) {\n error = err instanceof Error ? err.message.split('\\n')[0] : String(err);\n }\n\n const screenshot = join(out, `${s.name}.png`);\n await page.screenshot({ path: screenshot, fullPage: true });\n const unhandled = await unhandledRequests(page);\n const ok = !error && !unhandled.length && !consoleErrors.length && !pageErrors.length;\n report.push({ name: s.name, ok, screenshot, unhandled, consoleErrors, pageErrors, error });\n await context.close();\n }\n\n await browser.close();\n\n if (values.json) console.log(JSON.stringify(report, null, 2));\n else {\n for (const r of report) {\n console.log(`${r.ok ? '✓' : '✗'} ${r.name.padEnd(32)} ${r.screenshot}`);\n\n if (r.error) console.log(` error: ${r.error}`);\n\n for (const u of r.unhandled) console.log(` unmocked: ${u}`);\n\n for (const e of r.pageErrors) console.log(` uncaught: ${e}`);\n\n for (const e of r.consoleErrors) console.log(` console.error: ${e}`);\n }\n }\n\n process.exit(report.every((r) => r.ok) ? 0 : 1);\n }\n\n case 'suggest': {\n if (!values.src) throw new Error('suggest needs --src, e.g. --src src/pages,src/components');\n const sources = values.src.split(',').flatMap((d) => walk(d));\n const size = sources.reduce((n, f) => n + f.content.length, 0);\n\n if (size > MAX_SOURCE_CHARS) {\n throw new Error(\n `--src is ${size.toLocaleString()} characters; narrow it to the screens you want covered`,\n );\n }\n\n const jsonSchema = values.schema\n ? JSON.parse(readFileSync(values.schema, 'utf8'))\n : scenarioJsonSchema();\n\n if (values['env-file']) process.loadEnvFile(values['env-file']);\n const { default: Anthropic } = await import('@anthropic-ai/sdk');\n const model = values.model ?? process.env.STATE_SCENARIOS_MODEL ?? undefined;\n const { suggestScenarios } = await import('./suggest.js');\n console.error(\n `Reading ${sources.length} files (${size.toLocaleString()} chars) and ${catalog.list.length} scenarios with ${\n model ?? 'claude-opus-5-5'\n }…`,\n );\n\n const result = await suggestScenarios(\n { sources, catalog, jsonSchema, max: Number(values.max ?? 8), model },\n {\n client: new Anthropic({\n baseURL: process.env.ANTHROPIC_BASE_URL || undefined,\n // Some Anthropic-compatible proxies require a session id and a user agent.\n defaultHeaders: {\n 'x-opencode-session': `state-scenarios-${Date.now()}`,\n 'User-Agent': 'state-scenarios/0.0 (suggest)',\n },\n }),\n },\n );\n\n console.log('\\nCandidate UI states (not exhaustive)\\n');\n\n for (const s of result.states) {\n const mark = s.covered_by ? '✓' : '✗';\n console.log(\n `${mark} ${`${s.area} / ${s.state}`.padEnd(56)} ${s.covered_by ?? `uncovered (${s.priority})`}`,\n );\n }\n\n const covered = result.states.filter((s) => s.covered_by).length;\n console.log(\n `\\n${result.states.length} states detected, ${covered} have scenarios, ${\n result.states.length - covered\n } uncovered`,\n );\n\n const out = values.out ?? join(dir, 'proposed');\n\n if (result.accepted.length) mkdirSync(out, { recursive: true });\n\n for (const { scenario, covers } of result.accepted) {\n const file = join(out, `${scenario.name}.json`);\n const $schema = values.schema ? relative(out, values.schema) : undefined;\n writeFileSync(file, `${JSON.stringify({ $schema, ...scenario }, null, 2)}\\n`);\n console.log(`+ ${file} (${covers})`);\n }\n\n for (const r of result.rejected) console.log(`- rejected proposal for ${r.covers}: ${r.error}`);\n\n if (result.accepted.length) {\n console.log(\n `\\nReview the drafts, then: state-scenarios promote ${dir}`,\n );\n }\n\n break;\n }\n\n default:\n console.error(`Unknown command \"${command}\"\\n\\n${HELP}`);\n process.exit(1);\n }\n} catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const hint = /authentication/i.test(message) ? '\\nSet ANTHROPIC_API_KEY, or run `ant auth login`.' : '';\n console.error(`state-scenarios ${command}: ${message}${hint}`);\n process.exit(1);\n}\n\nfunction walk(path: string): { path: string; content: string; }[] {\n if (statSync(path).isDirectory()) {\n return readdirSync(path)\n .filter((f) => !['node_modules', 'dist', 'scenarios'].includes(f))\n .flatMap((f) => walk(join(path, f)));\n }\n\n const isSource = /\\.(tsx?|jsx?|vue|svelte)$/.test(path) && !/\\.(d|test|spec)\\.\\w+$/.test(path);\n\n return isSource ? [{ path, content: readFileSync(path, 'utf8') }] : [];\n}\n"],"mappings":"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","names":["s"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport type { Page } from '@playwright/test';\nimport { mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join, relative, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport { parseArgs } from 'node:util';\nimport { type Catalog, createCatalog, scenarioJsonSchema, type ScenarioRule } from 'state-scenarios';\nimport { z } from 'zod';\nimport { checkServer, contextOptions, launchChromium, scenarioLink } from './browser.js';\nimport { CliError } from './errors.js';\n\nconst HELP = `Usage: state-scenarios <command> <scenarios-dir> [options]\n\n init Create <dir> (default src/scenarios) with default.json, a sibling scenario.schema.json,\n print the wiring snippet, install the agent skills into .agents/skills and .claude/skills,\n and register the MCP server in .mcp.json. --no-skills skips the last two; --base-url sets the\n URL the MCP server opens (default http://localhost:5173, or :3000 for Next.js)\n guide Print the agent skill for this version: guide [topic]. guide alone lists the topics\n doctor Check the wiring one step at a time and print the fix for each problem: scenarios, dev server,\n MSW worker, session start, ready selector, save route, ffmpeg, skills and MCP. --base-url (required)\n mcp Serve an MCP server on stdio for coding agents: list, open and drive scenarios, check ready\n selectors, and save what the app shows as a scenario. --base-url (required)\n validate Check every scenario parses, resolves and passes --rules\n list Print scenarios with links (--base-url) for people and agents; --json for machines\n shoot Open each scenario in Chromium, wait for its ready selector, screenshot it,\n and report requests it didn't mock. --base-url (required), --out, --only a,b, --no-strict,\n --element <selector> to capture one element instead of the full page.\n Each failed scenario gets <out>/<name>.md: what failed and how to fix it\n record Open each scenario that has steps, run them, and save a video with a visible cursor.\n --base-url and --steps (required): a module whose default export maps scenario names\n to async (page) => {} steps. --format mp4 (default), gif or webm; --out, --only, --element.\n mp4, gif and --element need ffmpeg on PATH (or FFMPEG_PATH)\n suggest Ask Claude which UI states lack scenarios and draft them into --out (default <dir>/proposed).\n --src dir[,dir] (required), --schema file, --max n, --model (default $STATE_SCENARIOS_MODEL or claude-opus-5-5).\n Needs ANTHROPIC_API_KEY or \\`ant auth login\\`; ANTHROPIC_BASE_URL points it at a compatible proxy.\n --env-file file loads those variables first.\n promote Move <dir>/proposed/*.json into <dir> after validate. --dry-run lists moves only.\n\nCommon: --rules file module exporting \\`rules\\` (ScenarioRule[])\n --json machine-readable output (list, shoot, doctor)`;\n\nconst MAX_SOURCE_CHARS = 800_000;\n\nconst { positionals, values } = parseArgs({\n allowPositionals: true,\n options: {\n rules: { type: 'string' },\n 'base-url': { type: 'string' },\n out: { type: 'string' },\n only: { type: 'string' },\n 'no-strict': { type: 'boolean' },\n 'no-panel': { type: 'boolean' },\n viewport: { type: 'string' },\n scale: { type: 'string' },\n 'color-scheme': { type: 'string' },\n element: { type: 'string' },\n locale: { type: 'string' },\n timezone: { type: 'string' },\n steps: { type: 'string' },\n format: { type: 'string' },\n json: { type: 'boolean' },\n src: { type: 'string' },\n schema: { type: 'string' },\n max: { type: 'string' },\n model: { type: 'string' },\n 'env-file': { type: 'string' },\n 'dry-run': { type: 'boolean' },\n 'no-skills': { type: 'boolean' },\n help: { type: 'boolean', short: 'h' },\n },\n});\n\n/** `--steps` module: scenario name to the steps recorded after it opens. */\nconst StepsSchema = z.record(\n z.string(),\n z.function({ input: [z.custom<Page>()], output: z.unknown() }),\n);\n\nconst [command, dir = command === 'init' ? 'src/scenarios' : undefined] = positionals;\n\nif (command === 'guide') {\n const { guide } = await import('./skills.js');\n\n try {\n console.log(guide(positionals[1]));\n process.exit(0);\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(2);\n }\n}\n\nif (values.help || !command || !dir) {\n console.log(HELP);\n process.exit(command ? 2 : 0);\n}\n\n/** Read the scenarios folder; called again by long-running commands to see new files. */\nconst loadCatalog = (rules: ScenarioRule[]): Catalog => {\n try {\n return createCatalog(\n readdirSync(dir).filter((f) => f.endsWith('.json')).map((f) =>\n JSON.parse(readFileSync(join(dir, f), 'utf8'))\n ),\n { rules },\n );\n } catch (err) {\n throw new CliError('INVALID_SCENARIO', err instanceof Error ? err.message : String(err));\n }\n};\n\nconst requireBaseUrl = () => {\n const url = values['base-url'];\n\n if (!url) throw new CliError('INVALID_ARGUMENT', `${command} needs --base-url, e.g. http://localhost:5173`);\n\n return url;\n};\n\ntry {\n if (command === 'init') {\n const { initScenarios } = await import('./init.js');\n const { stack } = initScenarios(dir);\n\n if (!values['no-skills']) {\n const { installSkills, registerMcp } = await import('./skills.js');\n\n const url = values['base-url']\n ?? (stack === 'next' ? 'http://localhost:3000' : 'http://localhost:5173');\n\n const args = ['mcp', dir, '--base-url', url];\n console.log('');\n\n for (const line of [...installSkills(process.cwd()), ...registerMcp(process.cwd(), args)]) {\n console.log(line);\n }\n\n console.log(`\nCodex: codex mcp add state-scenarios -- npx state-scenarios ${args.join(' ')}\nFor agents that need a pointer, add to AGENTS.md (or CLAUDE.md):\n UI states use state-scenarios; read .agents/skills/state-scenarios/SKILL.md before adding or checking one.`);\n }\n\n process.exit(0);\n }\n\n const rules: ScenarioRule[] = values.rules\n ? (await import(pathToFileURL(resolve(values.rules)).href)).rules\n : [];\n\n if (command === 'promote') {\n const { promoteScenarios } = await import('./promote.js');\n const result = promoteScenarios(dir, { rules, dryRun: !!values['dry-run'] });\n process.exit(result.skipped.length ? 1 : 0);\n }\n\n if (command === 'mcp') {\n const { serveMcp } = await import('./mcp.js');\n\n const { version } = z.object({ version: z.string() }).parse(\n JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')),\n );\n\n await serveMcp({\n dir,\n baseURL: requireBaseUrl(),\n loadCatalog: () => loadCatalog(rules),\n flags: values,\n version,\n });\n process.exit(0);\n }\n\n if (command === 'doctor') {\n const { runDoctor } = await import('./doctor.js');\n const { EXIT_CODES, ERROR_CLASSES } = await import('./errors.js');\n\n const checks = await runDoctor({\n dir,\n baseURL: requireBaseUrl(),\n loadCatalog: () => loadCatalog(rules),\n flags: values,\n });\n\n if (values.json) console.log(JSON.stringify(checks, null, 2));\n else {\n for (const c of checks) {\n console.log(\n `${{ ok: '✓', warn: '!', fail: '✗' }[c.status]} ${c.name.padEnd(16)} ${\n c.code ? `[${c.code}] ` : ''\n }${c.detail}`,\n );\n\n if (c.fix && c.status !== 'ok') console.log(` ${' '.repeat(16)} fix: ${c.fix}`);\n }\n }\n\n const failed = checks.filter((c) => c.status === 'fail');\n process.exit(Math.max(0, ...failed.map((c) => (c.code ? EXIT_CODES[ERROR_CLASSES[c.code]] : 1))));\n }\n\n const catalog = loadCatalog(rules);\n\n switch (command) {\n case 'validate': {\n const problems = catalog.check();\n\n for (const p of problems) console.error(`✗ ${p}\\n`);\n console.log(`${catalog.list.length - problems.length}/${catalog.list.length} scenarios valid`);\n process.exit(problems.length ? 1 : 0);\n }\n\n case 'list': {\n const base = values['base-url'];\n\n const rows = catalog.list.map((s) => ({\n name: s.name,\n description: s.description ?? '',\n tags: s.tags ?? [],\n ready: s.ready ?? null,\n url: base\n ? `${base.replace(/\\/$/, '')}${catalog.resolve(s).path ?? '/'}?scenario=${\n encodeURIComponent(s.name)\n }`\n : null,\n }));\n\n if (values.json) console.log(JSON.stringify(rows, null, 2));\n else {\n for (const r of rows) {\n console.log(\n `${r.name.padEnd(32)} ${r.description}${r.url ? `\\n${' '.repeat(33)}${r.url}` : ''}`,\n );\n }\n }\n\n break;\n }\n\n case 'shoot': {\n const baseURL = requireBaseUrl();\n const out = values.out ?? 'scenario-shots';\n const only = values.only?.split(',');\n\n for (const n of only ?? []) {\n if (!catalog.get(n)) throw new CliError('UNKNOWN_SCENARIO', `--only names unknown scenario \"${n}\"`);\n }\n\n mkdirSync(out, { recursive: true });\n await checkServer(baseURL);\n\n const { inspectScenario, failurePage } = await import('./inspect.js');\n const browser = await launchChromium();\n const report = [];\n\n for (const s of catalog.list.filter((s) => !only || only.includes(s.name))) {\n const scenario = catalog.resolve(s);\n const context = await browser.newContext({ baseURL, ...contextOptions(scenario, values) });\n const page = await context.newPage();\n page.setDefaultTimeout(10_000);\n\n // Uncaught exceptions and console errors fail the run too.\n const result = await inspectScenario(page, scenario, {\n link: scenarioLink(baseURL, scenario),\n strict: !values['no-strict'],\n panel: !values['no-panel'],\n });\n\n const screenshot = join(out, `${s.name}.png`);\n let captured = false;\n\n if (values.element && !result.error) {\n try {\n await page.locator(values.element).first().screenshot({ path: screenshot });\n captured = true;\n } catch (err) {\n result.codes.push('ELEMENT_NOT_FOUND');\n result.ok = false;\n result.error = `--element: ${err instanceof Error ? err.message.split('\\n')[0] : String(err)}`;\n }\n }\n\n // The full page shows what went wrong when the scenario or the element failed.\n if (!captured) await page.screenshot({ path: screenshot, fullPage: true });\n\n // A page per failure, written for whoever fixes it; a stale one would mislead.\n const details = join(out, `${s.name}.md`);\n rmSync(details, { force: true });\n\n if (!result.ok) {\n writeFileSync(details, failurePage(result, { screenshot, appOrigin: new URL(baseURL).origin }));\n }\n\n report.push({ ...result, screenshot, details: result.ok ? null : details });\n await context.close();\n }\n\n await browser.close();\n\n if (values.json) console.log(JSON.stringify(report, null, 2));\n else {\n for (const r of report) {\n console.log(`${r.ok ? '✓' : '✗'} ${r.name.padEnd(32)} ${r.screenshot}`);\n\n if (r.ok) continue;\n console.log(` ${r.codes.join(', ')}${r.error ? `: ${r.error}` : ''}`);\n\n for (const u of r.unhandled) console.log(` unmocked: ${u}`);\n\n for (const e of r.pageErrors) console.log(` uncaught: ${e}`);\n\n for (const e of r.consoleErrors) console.log(` console.error: ${e}`);\n console.log(` ❯ details ${r.details}`);\n }\n }\n\n process.exit(report.every((r) => r.ok) ? 0 : 1);\n }\n\n case 'record': {\n const baseURL = requireBaseUrl();\n\n if (!values.steps) {\n throw new CliError('INVALID_ARGUMENT', 'record needs --steps, e.g. --steps scenarios/steps.ts');\n }\n\n const format = values.format ?? 'mp4';\n\n if (format !== 'mp4' && format !== 'gif' && format !== 'webm') {\n throw new CliError('INVALID_ARGUMENT', `--format must be mp4, gif or webm, not \"${format}\"`);\n }\n\n const steps = StepsSchema.parse((await import(pathToFileURL(resolve(values.steps)).href)).default);\n const only = values.only?.split(',');\n const names = Object.keys(steps).filter((n) => !only || only.includes(n));\n\n for (const n of names) {\n if (!catalog.get(n)) throw new CliError('UNKNOWN_SCENARIO', `--steps names unknown scenario \"${n}\"`);\n }\n\n const out = values.out ?? 'scenario-videos';\n mkdirSync(out, { recursive: true });\n const raw = mkdtempSync(join(tmpdir(), 'state-scenarios-'));\n\n await checkServer(baseURL);\n const { openScenario, showCursor } = await import('state-scenarios/playwright');\n const { encodeVideo, visibleCrop } = await import('./record.js');\n // slowMo gives the drawn cursor time to glide to each target before the click lands.\n const browser = await launchChromium({ slowMo: 150 });\n let exitCode = 0;\n\n /**\n * Where the element sits in the video once the scenario is ready. Scrolled into view first:\n * video holds only the viewport. Measured before the steps run, so steps that scroll the page\n * move the element out of the crop.\n */\n const cropTo = async (page: Page, selector: string, viewport: { width: number; height: number; }) => {\n const element = page.locator(selector).first();\n\n const box = await element.scrollIntoViewIfNeeded({ timeout: 5000 }).then(\n () => element.boundingBox(),\n () => null,\n );\n\n const visible = box && visibleCrop(box, viewport);\n\n if (!visible) throw new CliError('ELEMENT_NOT_FOUND', `--element \"${selector}\" isn't visible`);\n\n if (visible.clipped) {\n console.log(\n ` --element \"${selector}\" (${Math.round(box.width)}x${\n Math.round(box.height)\n }) is larger than the viewport; `\n + `the video shows the visible part. Use a larger --viewport to fit it.`,\n );\n }\n\n return visible.crop;\n };\n\n for (const [name, run] of Object.entries(steps).filter(([n]) => names.includes(n))) {\n const resolved = catalog.resolve(catalog.get(name) ?? { name });\n const options = contextOptions(resolved, values);\n\n // Playwright records video at CSS-pixel size whatever the scale, so use a larger --viewport for sharper video.\n const context = await browser.newContext({\n baseURL,\n ...options,\n recordVideo: { dir: raw, size: options.viewport },\n });\n\n const page = await context.newPage();\n const began = Date.now();\n const file = join(out, `${name}.${format}`);\n\n try {\n await showCursor(page);\n await openScenario(page, name, { path: resolved.path, strict: !values['no-strict'], panel: false });\n const crop = values.element ? await cropTo(page, values.element, options.viewport) : undefined;\n\n const start = (Date.now() - began) / 1000;\n await Promise.resolve(run(page)).catch((err: Error) => {\n throw new CliError('STEPS_FAILED', `steps failed: ${err.message.split('\\n')[0]}`);\n });\n await page.waitForTimeout(800); // hold the final frame\n await context.close();\n\n const video = page.video();\n\n if (!video) throw new Error('Playwright recorded no video');\n encodeVideo({ input: await video.path(), output: file, format, start, crop });\n console.log(`✓ ${name.padEnd(32)} ${file}`);\n } catch (err) {\n exitCode = Math.max(exitCode, err instanceof CliError ? err.exitCode : 1);\n await context.close();\n const code = err instanceof CliError ? `[${err.code}] ` : '';\n console.log(\n `✗ ${name.padEnd(32)} ${code}${err instanceof Error ? err.message.split('\\n')[0] : String(err)}`,\n );\n }\n }\n\n await browser.close();\n rmSync(raw, { recursive: true, force: true });\n process.exit(exitCode);\n }\n\n case 'suggest': {\n if (!values.src) {\n throw new CliError('INVALID_ARGUMENT', 'suggest needs --src, e.g. --src src/pages,src/components');\n }\n\n const sources = values.src.split(',').flatMap((d) => walk(d));\n const size = sources.reduce((n, f) => n + f.content.length, 0);\n\n if (size > MAX_SOURCE_CHARS) {\n throw new Error(\n `--src is ${size.toLocaleString()} characters; narrow it to the screens you want covered`,\n );\n }\n\n const jsonSchema = values.schema\n ? JSON.parse(readFileSync(values.schema, 'utf8'))\n : scenarioJsonSchema();\n\n if (values['env-file']) process.loadEnvFile(values['env-file']);\n const { default: Anthropic } = await import('@anthropic-ai/sdk');\n const model = values.model ?? process.env.STATE_SCENARIOS_MODEL ?? undefined;\n const { suggestScenarios } = await import('./suggest.js');\n console.error(\n `Reading ${sources.length} files (${size.toLocaleString()} chars) and ${catalog.list.length} scenarios with ${\n model ?? 'claude-opus-5-5'\n }…`,\n );\n\n const result = await suggestScenarios(\n { sources, catalog, jsonSchema, max: Number(values.max ?? 8), model },\n {\n client: new Anthropic({\n baseURL: process.env.ANTHROPIC_BASE_URL || undefined,\n // Some Anthropic-compatible proxies require a session id and a user agent.\n defaultHeaders: {\n 'x-opencode-session': `state-scenarios-${Date.now()}`,\n 'User-Agent': 'state-scenarios/0.0 (suggest)',\n },\n }),\n },\n );\n\n console.log('\\nCandidate UI states (not exhaustive)\\n');\n\n for (const s of result.states) {\n const mark = s.covered_by ? '✓' : '✗';\n console.log(\n `${mark} ${`${s.area} / ${s.state}`.padEnd(56)} ${s.covered_by ?? `uncovered (${s.priority})`}`,\n );\n }\n\n const covered = result.states.filter((s) => s.covered_by).length;\n console.log(\n `\\n${result.states.length} states detected, ${covered} have scenarios, ${\n result.states.length - covered\n } uncovered`,\n );\n\n const out = values.out ?? join(dir, 'proposed');\n\n if (result.accepted.length) mkdirSync(out, { recursive: true });\n\n for (const { scenario, covers } of result.accepted) {\n const file = join(out, `${scenario.name}.json`);\n const $schema = values.schema ? relative(out, values.schema).replaceAll('\\\\', '/') : undefined;\n writeFileSync(file, `${JSON.stringify({ $schema, ...scenario }, null, 2)}\\n`);\n console.log(`+ ${file} (${covers})`);\n }\n\n for (const r of result.rejected) console.log(`- rejected proposal for ${r.covers}: ${r.error}`);\n\n if (result.accepted.length) {\n console.log(\n `\\nReview the drafts, then: state-scenarios promote ${dir}`,\n );\n }\n\n break;\n }\n\n default:\n console.error(`Unknown command \"${command}\"\\n\\n${HELP}`);\n process.exit(2);\n }\n} catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const hint = /authentication/i.test(message) ? '\\nSet ANTHROPIC_API_KEY, or run `ant auth login`.' : '';\n const code = err instanceof CliError ? `[${err.code}] ` : '';\n console.error(`state-scenarios ${command}: ${code}${message}${hint}`);\n process.exit(err instanceof CliError ? err.exitCode : 1);\n}\n\nfunction walk(path: string): { path: string; content: string; }[] {\n if (statSync(path).isDirectory()) {\n return readdirSync(path)\n .filter((f) => !['node_modules', 'dist', 'scenarios'].includes(f))\n .flatMap((f) => walk(join(path, f)));\n }\n\n const isSource = /\\.(tsx?|jsx?|vue|svelte)$/.test(path) && !/\\.(d|test|spec)\\.\\w+$/.test(path);\n\n return isSource ? [{ path, content: readFileSync(path, 'utf8') }] : [];\n}\n"],"mappings":"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","names":["s"]}
|