state-scenarios-cli 0.3.0 → 0.5.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.
Files changed (45) hide show
  1. package/README.md +10 -1
  2. package/dist/browser.d.ts +27 -0
  3. package/dist/chunk-GCYOLAFU.js +40 -0
  4. package/dist/chunk-GCYOLAFU.js.map +1 -0
  5. package/dist/chunk-O6QTNAAZ.js +67 -0
  6. package/dist/chunk-O6QTNAAZ.js.map +1 -0
  7. package/dist/chunk-YFNROT3R.js +115 -0
  8. package/dist/chunk-YFNROT3R.js.map +1 -0
  9. package/dist/cli.js +264 -44
  10. package/dist/cli.js.map +1 -1
  11. package/dist/doctor-URGIFA3W.js +200 -0
  12. package/dist/doctor-URGIFA3W.js.map +1 -0
  13. package/dist/doctor.d.ts +21 -0
  14. package/dist/errors-G7ODRBLT.js +11 -0
  15. package/dist/errors-G7ODRBLT.js.map +1 -0
  16. package/dist/errors.d.ts +33 -0
  17. package/dist/{init-VLZQEJRD.js → init-MRK4F5SV.js} +2 -1
  18. package/dist/init-MRK4F5SV.js.map +1 -0
  19. package/dist/init.d.ts +5 -1
  20. package/dist/inspect-LN4EACWA.js +13 -0
  21. package/dist/inspect-LN4EACWA.js.map +1 -0
  22. package/dist/inspect.d.ts +63 -0
  23. package/dist/mcp-HRQIWAIR.js +342 -0
  24. package/dist/mcp-HRQIWAIR.js.map +1 -0
  25. package/dist/mcp-protocol.d.ts +51 -0
  26. package/dist/mcp.d.ts +19 -0
  27. package/dist/record-HXIYGMWA.js +65 -0
  28. package/dist/record-HXIYGMWA.js.map +1 -0
  29. package/dist/record.d.ts +45 -0
  30. package/dist/skills-NQRJHKHG.js +80 -0
  31. package/dist/skills-NQRJHKHG.js.map +1 -0
  32. package/dist/skills.d.ts +13 -0
  33. package/package.json +5 -4
  34. package/skills/create-scenario-map/SKILL.md +88 -0
  35. package/skills/state-scenarios/SKILL.md +91 -0
  36. package/skills/state-scenarios/references/auth.md +52 -0
  37. package/skills/state-scenarios/references/capture.md +45 -0
  38. package/skills/state-scenarios/references/debugging.md +52 -0
  39. package/skills/state-scenarios/references/mcp.md +39 -0
  40. package/skills/state-scenarios/references/media.md +60 -0
  41. package/skills/state-scenarios/references/network.md +67 -0
  42. package/skills/state-scenarios/references/setup.md +94 -0
  43. package/skills/state-scenarios/references/verify.md +47 -0
  44. package/skills/state-scenarios/references/writing-scenarios.md +55 -0
  45. package/dist/init-VLZQEJRD.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1,26 +1,53 @@
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
- and print the wiring snippet to paste into your entry file
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, or to { element, steps } to crop that one scenario.
40
+ --format mp4 (default), gif or webm; --out, --only, --element, --fps (gif default 12),
41
+ --width <px> to downscale, --hold <ms> on the last frame (default 800).
42
+ mp4, gif, --element, --fps and --width need ffmpeg on PATH (or FFMPEG_PATH)
17
43
  suggest Ask Claude which UI states lack scenarios and draft them into --out (default <dir>/proposed).
18
44
  --src dir[,dir] (required), --schema file, --max n, --model (default $STATE_SCENARIOS_MODEL or claude-opus-5-5).
19
45
  Needs ANTHROPIC_API_KEY or \`ant auth login\`; ANTHROPIC_BASE_URL points it at a compatible proxy.
20
46
  --env-file file loads those variables first.
21
47
  promote Move <dir>/proposed/*.json into <dir> after validate. --dry-run lists moves only.
22
48
 
23
- Common: --rules file module exporting \`rules\` (ScenarioRule[])`;
49
+ Common: --rules file module exporting \`rules\` (ScenarioRule[])
50
+ --json machine-readable output (list, shoot, doctor)`;
24
51
  var MAX_SOURCE_CHARS = 8e5;
25
52
  var { positionals, values } = parseArgs({
26
53
  allowPositionals: true,
@@ -30,6 +57,18 @@ var { positionals, values } = parseArgs({
30
57
  out: { type: "string" },
31
58
  only: { type: "string" },
32
59
  "no-strict": { type: "boolean" },
60
+ "no-panel": { type: "boolean" },
61
+ viewport: { type: "string" },
62
+ scale: { type: "string" },
63
+ "color-scheme": { type: "string" },
64
+ element: { type: "string" },
65
+ locale: { type: "string" },
66
+ timezone: { type: "string" },
67
+ steps: { type: "string" },
68
+ format: { type: "string" },
69
+ fps: { type: "string" },
70
+ width: { type: "string" },
71
+ hold: { type: "string" },
33
72
  json: { type: "boolean" },
34
73
  src: { type: "string" },
35
74
  schema: { type: "string" },
@@ -37,18 +76,78 @@ var { positionals, values } = parseArgs({
37
76
  model: { type: "string" },
38
77
  "env-file": { type: "string" },
39
78
  "dry-run": { type: "boolean" },
79
+ "no-skills": { type: "boolean" },
40
80
  help: { type: "boolean", short: "h" }
41
81
  }
42
82
  });
83
+ var Steps = z.function({ input: [z.custom()], output: z.unknown() });
84
+ var StepsSchema = z.record(
85
+ z.string(),
86
+ z.union([
87
+ Steps.transform((steps) => ({ steps, element: void 0 })),
88
+ z.object({
89
+ element: z.string().optional(),
90
+ steps: Steps
91
+ })
92
+ ])
93
+ );
94
+ var positive = (flag) => {
95
+ const text = values[flag];
96
+ const n = Number(text);
97
+ if (text !== void 0 && !(Number.isFinite(n) && n > 0)) {
98
+ throw new CliError("INVALID_ARGUMENT", `--${flag} must be a positive number, not "${text}"`);
99
+ }
100
+ return text === void 0 ? void 0 : n;
101
+ };
43
102
  var [command, dir = command === "init" ? "src/scenarios" : void 0] = positionals;
103
+ if (command === "guide") {
104
+ const { guide } = await import("./skills-NQRJHKHG.js");
105
+ try {
106
+ console.log(guide(positionals[1]));
107
+ process.exit(0);
108
+ } catch (err) {
109
+ console.error(err instanceof Error ? err.message : String(err));
110
+ process.exit(2);
111
+ }
112
+ }
44
113
  if (values.help || !command || !dir) {
45
114
  console.log(HELP);
46
- process.exit(command ? 1 : 0);
115
+ process.exit(command ? 2 : 0);
47
116
  }
117
+ var loadCatalog = (rules) => {
118
+ try {
119
+ return createCatalog(
120
+ readdirSync(dir).filter((f) => f.endsWith(".json")).map(
121
+ (f) => JSON.parse(readFileSync(join(dir, f), "utf8"))
122
+ ),
123
+ { rules }
124
+ );
125
+ } catch (err) {
126
+ throw new CliError("INVALID_SCENARIO", err instanceof Error ? err.message : String(err));
127
+ }
128
+ };
129
+ var requireBaseUrl = () => {
130
+ const url = values["base-url"];
131
+ if (!url) throw new CliError("INVALID_ARGUMENT", `${command} needs --base-url, e.g. http://localhost:5173`);
132
+ return url;
133
+ };
48
134
  try {
49
135
  if (command === "init") {
50
- const { initScenarios } = await import("./init-VLZQEJRD.js");
51
- initScenarios(dir);
136
+ const { initScenarios } = await import("./init-MRK4F5SV.js");
137
+ const { stack } = initScenarios(dir);
138
+ if (!values["no-skills"]) {
139
+ const { installSkills, registerMcp } = await import("./skills-NQRJHKHG.js");
140
+ const url = values["base-url"] ?? (stack === "next" ? "http://localhost:3000" : "http://localhost:5173");
141
+ const args = ["mcp", dir, "--base-url", url];
142
+ console.log("");
143
+ for (const line of [...installSkills(process.cwd()), ...registerMcp(process.cwd(), args)]) {
144
+ console.log(line);
145
+ }
146
+ console.log(`
147
+ Codex: codex mcp add state-scenarios -- npx state-scenarios ${args.join(" ")}
148
+ For agents that need a pointer, add to AGENTS.md (or CLAUDE.md):
149
+ UI states use state-scenarios; read .agents/skills/state-scenarios/SKILL.md before adding or checking one.`);
150
+ }
52
151
  process.exit(0);
53
152
  }
54
153
  const rules = values.rules ? (await import(pathToFileURL(resolve(values.rules)).href)).rules : [];
@@ -57,11 +156,42 @@ try {
57
156
  const result = promoteScenarios(dir, { rules, dryRun: !!values["dry-run"] });
58
157
  process.exit(result.skipped.length ? 1 : 0);
59
158
  }
60
- const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
61
- const catalog = createCatalog(
62
- files.map((f) => JSON.parse(readFileSync(join(dir, f), "utf8"))),
63
- { rules }
64
- );
159
+ if (command === "mcp") {
160
+ const { serveMcp } = await import("./mcp-HRQIWAIR.js");
161
+ const { version } = z.object({ version: z.string() }).parse(
162
+ JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"))
163
+ );
164
+ await serveMcp({
165
+ dir,
166
+ baseURL: requireBaseUrl(),
167
+ loadCatalog: () => loadCatalog(rules),
168
+ flags: values,
169
+ version
170
+ });
171
+ process.exit(0);
172
+ }
173
+ if (command === "doctor") {
174
+ const { runDoctor } = await import("./doctor-URGIFA3W.js");
175
+ const { EXIT_CODES, ERROR_CLASSES } = await import("./errors-G7ODRBLT.js");
176
+ const checks = await runDoctor({
177
+ dir,
178
+ baseURL: requireBaseUrl(),
179
+ loadCatalog: () => loadCatalog(rules),
180
+ flags: values
181
+ });
182
+ if (values.json) console.log(JSON.stringify(checks, null, 2));
183
+ else {
184
+ for (const c of checks) {
185
+ console.log(
186
+ `${{ ok: "\u2713", warn: "!", fail: "\u2717" }[c.status]} ${c.name.padEnd(16)} ${c.code ? `[${c.code}] ` : ""}${c.detail}`
187
+ );
188
+ if (c.fix && c.status !== "ok") console.log(` ${" ".repeat(16)} fix: ${c.fix}`);
189
+ }
190
+ }
191
+ const failed = checks.filter((c) => c.status === "fail");
192
+ process.exit(Math.max(0, ...failed.map((c) => c.code ? EXIT_CODES[ERROR_CLASSES[c.code]] : 1)));
193
+ }
194
+ const catalog = loadCatalog(rules);
65
195
  switch (command) {
66
196
  case "validate": {
67
197
  const problems = catalog.check();
@@ -91,41 +221,46 @@ ${" ".repeat(33)}${r.url}` : ""}`
91
221
  break;
92
222
  }
93
223
  case "shoot": {
94
- const baseURL = values["base-url"];
95
- if (!baseURL) throw new Error("shoot needs --base-url, e.g. http://localhost:5173");
224
+ const baseURL = requireBaseUrl();
96
225
  const out = values.out ?? "scenario-shots";
97
226
  const only = values.only?.split(",");
227
+ for (const n of only ?? []) {
228
+ if (!catalog.get(n)) throw new CliError("UNKNOWN_SCENARIO", `--only names unknown scenario "${n}"`);
229
+ }
98
230
  mkdirSync(out, { recursive: true });
99
- const { chromium } = await import("@playwright/test");
100
- const { openScenario, unhandledRequests } = await import("state-scenarios/playwright");
101
- const browser = await chromium.launch();
231
+ await checkServer(baseURL);
232
+ const { inspectScenario, failurePage } = await import("./inspect-LN4EACWA.js");
233
+ const browser = await launchChromium();
102
234
  const report = [];
103
235
  for (const s of catalog.list.filter((s2) => !only || only.includes(s2.name))) {
104
- const context = await browser.newContext({ baseURL });
236
+ const scenario = catalog.resolve(s);
237
+ const context = await browser.newContext({ baseURL, ...contextOptions(scenario, values) });
105
238
  const page = await context.newPage();
106
239
  page.setDefaultTimeout(1e4);
107
- const consoleErrors = [];
108
- const pageErrors = [];
109
- page.on("console", (m) => {
110
- if (m.type() === "error" && !m.text().startsWith("Failed to load resource")) {
111
- consoleErrors.push(m.text());
112
- }
240
+ const result = await inspectScenario(page, scenario, {
241
+ link: scenarioLink(baseURL, scenario),
242
+ strict: !values["no-strict"],
243
+ panel: !values["no-panel"]
113
244
  });
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
245
  const screenshot = join(out, `${s.name}.png`);
125
- await page.screenshot({ path: screenshot, fullPage: true });
126
- const unhandled = await unhandledRequests(page);
127
- const ok = !error && !unhandled.length && !consoleErrors.length && !pageErrors.length;
128
- report.push({ name: s.name, ok, screenshot, unhandled, consoleErrors, pageErrors, error });
246
+ let captured = false;
247
+ if (values.element && !result.error) {
248
+ try {
249
+ await page.locator(values.element).first().screenshot({ path: screenshot });
250
+ captured = true;
251
+ } catch (err) {
252
+ result.codes.push("ELEMENT_NOT_FOUND");
253
+ result.ok = false;
254
+ result.error = `--element: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`;
255
+ }
256
+ }
257
+ if (!captured) await page.screenshot({ path: screenshot, fullPage: true });
258
+ const details = join(out, `${s.name}.md`);
259
+ rmSync(details, { force: true });
260
+ if (!result.ok) {
261
+ writeFileSync(details, failurePage(result, { screenshot, appOrigin: new URL(baseURL).origin }));
262
+ }
263
+ report.push({ ...result, screenshot, details: result.ok ? null : details });
129
264
  await context.close();
130
265
  }
131
266
  await browser.close();
@@ -133,16 +268,100 @@ ${" ".repeat(33)}${r.url}` : ""}`
133
268
  else {
134
269
  for (const r of report) {
135
270
  console.log(`${r.ok ? "\u2713" : "\u2717"} ${r.name.padEnd(32)} ${r.screenshot}`);
136
- if (r.error) console.log(` error: ${r.error}`);
271
+ if (r.ok) continue;
272
+ console.log(` ${r.codes.join(", ")}${r.error ? `: ${r.error}` : ""}`);
137
273
  for (const u of r.unhandled) console.log(` unmocked: ${u}`);
138
274
  for (const e of r.pageErrors) console.log(` uncaught: ${e}`);
139
275
  for (const e of r.consoleErrors) console.log(` console.error: ${e}`);
276
+ console.log(` \u276F details ${r.details}`);
140
277
  }
141
278
  }
142
279
  process.exit(report.every((r) => r.ok) ? 0 : 1);
143
280
  }
281
+ case "record": {
282
+ const baseURL = requireBaseUrl();
283
+ if (!values.steps) {
284
+ throw new CliError("INVALID_ARGUMENT", "record needs --steps, e.g. --steps scenarios/steps.ts");
285
+ }
286
+ const format = values.format ?? "mp4";
287
+ if (format !== "mp4" && format !== "gif" && format !== "webm") {
288
+ throw new CliError("INVALID_ARGUMENT", `--format must be mp4, gif or webm, not "${format}"`);
289
+ }
290
+ const steps = StepsSchema.parse((await import(pathToFileURL(resolve(values.steps)).href)).default);
291
+ const only = values.only?.split(",");
292
+ const names = Object.keys(steps).filter((n) => !only || only.includes(n));
293
+ for (const n of names) {
294
+ if (!catalog.get(n)) throw new CliError("UNKNOWN_SCENARIO", `--steps names unknown scenario "${n}"`);
295
+ }
296
+ const fps = positive("fps");
297
+ const width = positive("width");
298
+ const hold = positive("hold") ?? 800;
299
+ const out = values.out ?? "scenario-videos";
300
+ mkdirSync(out, { recursive: true });
301
+ const raw = mkdtempSync(join(tmpdir(), "state-scenarios-"));
302
+ await checkServer(baseURL);
303
+ const { openScenario, showCursor } = await import("state-scenarios/playwright");
304
+ const { encodeVideo, visibleCrop } = await import("./record-HXIYGMWA.js");
305
+ const browser = await launchChromium({ slowMo: 150 });
306
+ let exitCode = 0;
307
+ const cropTo = async (page, selector, viewport) => {
308
+ const element = page.locator(selector).first();
309
+ const box = await element.scrollIntoViewIfNeeded({ timeout: 5e3 }).then(
310
+ () => element.boundingBox(),
311
+ () => null
312
+ );
313
+ const visible = box && visibleCrop(box, viewport);
314
+ if (!visible) throw new CliError("ELEMENT_NOT_FOUND", `element "${selector}" isn't visible`);
315
+ if (visible.clipped) {
316
+ console.log(
317
+ ` 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.`
318
+ );
319
+ }
320
+ return visible.crop;
321
+ };
322
+ for (const [name, entry] of Object.entries(steps).filter(([n]) => names.includes(n))) {
323
+ const { element = values.element, steps: run } = entry;
324
+ const resolved = catalog.resolve(catalog.get(name) ?? { name });
325
+ const options = contextOptions(resolved, values);
326
+ const context = await browser.newContext({
327
+ baseURL,
328
+ ...options,
329
+ recordVideo: { dir: raw, size: options.viewport }
330
+ });
331
+ const page = await context.newPage();
332
+ const began = Date.now();
333
+ const file = join(out, `${name}.${format}`);
334
+ try {
335
+ await showCursor(page);
336
+ await openScenario(page, name, { path: resolved.path, strict: !values["no-strict"], panel: false });
337
+ const crop = element ? await cropTo(page, element, options.viewport) : void 0;
338
+ const start = (Date.now() - began) / 1e3;
339
+ await Promise.resolve(run(page)).catch((err) => {
340
+ throw new CliError("STEPS_FAILED", `steps failed: ${err.message.split("\n")[0]}`);
341
+ });
342
+ await page.waitForTimeout(hold);
343
+ await context.close();
344
+ const video = page.video();
345
+ if (!video) throw new Error("Playwright recorded no video");
346
+ encodeVideo({ input: await video.path(), output: file, format, start, crop, fps, width });
347
+ console.log(`\u2713 ${name.padEnd(32)} ${file}`);
348
+ } catch (err) {
349
+ exitCode = Math.max(exitCode, err instanceof CliError ? err.exitCode : 1);
350
+ await context.close();
351
+ const code = err instanceof CliError ? `[${err.code}] ` : "";
352
+ console.log(
353
+ `\u2717 ${name.padEnd(32)} ${code}${err instanceof Error ? err.message.split("\n")[0] : String(err)}`
354
+ );
355
+ }
356
+ }
357
+ await browser.close();
358
+ rmSync(raw, { recursive: true, force: true });
359
+ process.exit(exitCode);
360
+ }
144
361
  case "suggest": {
145
- if (!values.src) throw new Error("suggest needs --src, e.g. --src src/pages,src/components");
362
+ if (!values.src) {
363
+ throw new CliError("INVALID_ARGUMENT", "suggest needs --src, e.g. --src src/pages,src/components");
364
+ }
146
365
  const sources = values.src.split(",").flatMap((d) => walk(d));
147
366
  const size = sources.reduce((n, f) => n + f.content.length, 0);
148
367
  if (size > MAX_SOURCE_CHARS) {
@@ -187,7 +406,7 @@ ${result.states.length} states detected, ${covered} have scenarios, ${result.sta
187
406
  if (result.accepted.length) mkdirSync(out, { recursive: true });
188
407
  for (const { scenario, covers } of result.accepted) {
189
408
  const file = join(out, `${scenario.name}.json`);
190
- const $schema = values.schema ? relative(out, values.schema) : void 0;
409
+ const $schema = values.schema ? relative(out, values.schema).replaceAll("\\", "/") : void 0;
191
410
  writeFileSync(file, `${JSON.stringify({ $schema, ...scenario }, null, 2)}
192
411
  `);
193
412
  console.log(`+ ${file} (${covers})`);
@@ -205,13 +424,14 @@ Review the drafts, then: state-scenarios promote ${dir}`
205
424
  console.error(`Unknown command "${command}"
206
425
 
207
426
  ${HELP}`);
208
- process.exit(1);
427
+ process.exit(2);
209
428
  }
210
429
  } catch (err) {
211
430
  const message = err instanceof Error ? err.message : String(err);
212
431
  const hint = /authentication/i.test(message) ? "\nSet ANTHROPIC_API_KEY, or run `ant auth login`." : "";
213
- console.error(`state-scenarios ${command}: ${message}${hint}`);
214
- process.exit(1);
432
+ const code = err instanceof CliError ? `[${err.code}] ` : "";
433
+ console.error(`state-scenarios ${command}: ${code}${message}${hint}`);
434
+ process.exit(err instanceof CliError ? err.exitCode : 1);
215
435
  }
216
436
  function walk(path) {
217
437
  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, or to { element, steps } to crop that one scenario.\n --format mp4 (default), gif or webm; --out, --only, --element, --fps (gif default 12),\n --width <px> to downscale, --hold <ms> on the last frame (default 800).\n mp4, gif, --element, --fps and --width 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 fps: { type: 'string' },\n width: { type: 'string' },\n hold: { 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\nconst Steps = z.function({ input: [z.custom<Page>()], output: z.unknown() });\n\n/** `--steps` module: scenario name to the steps recorded after it opens, or to `{ element, steps }`. */\nconst StepsSchema = z.record(\n z.string(),\n z.union([\n Steps.transform((steps) => ({ steps, element: undefined })),\n z.object({\n element: z.string().optional(),\n steps: Steps,\n }),\n ]),\n);\n\n/** A number flag, undefined when not passed. */\nconst positive = (flag: 'fps' | 'width' | 'hold') => {\n const text = values[flag];\n const n = Number(text);\n\n if (text !== undefined && !(Number.isFinite(n) && n > 0)) {\n throw new CliError('INVALID_ARGUMENT', `--${flag} must be a positive number, not \"${text}\"`);\n }\n\n return text === undefined ? undefined : n;\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 fps = positive('fps');\n const width = positive('width');\n const hold = positive('hold') ?? 800;\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, entry] of Object.entries(steps).filter(([n]) => names.includes(n))) {\n const { element = values.element, steps: run } = entry;\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 = element ? await cropTo(page, 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(hold); // 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, fps, width });\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"]}