state-scenarios-cli 0.2.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.
Files changed (47) 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 +260 -43
  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-MRK4F5SV.js +96 -0
  18. package/dist/init-MRK4F5SV.js.map +1 -0
  19. package/dist/init.d.ts +6 -0
  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/promote-GSF5UXJL.js +78 -0
  28. package/dist/promote-GSF5UXJL.js.map +1 -0
  29. package/dist/promote.d.ts +12 -0
  30. package/dist/record-WEZRD2VT.js +59 -0
  31. package/dist/record-WEZRD2VT.js.map +1 -0
  32. package/dist/record.d.ts +41 -0
  33. package/dist/skills-NQRJHKHG.js +80 -0
  34. package/dist/skills-NQRJHKHG.js.map +1 -0
  35. package/dist/skills.d.ts +13 -0
  36. package/package.json +5 -4
  37. package/skills/create-scenario-map/SKILL.md +88 -0
  38. package/skills/state-scenarios/SKILL.md +91 -0
  39. package/skills/state-scenarios/references/auth.md +52 -0
  40. package/skills/state-scenarios/references/capture.md +45 -0
  41. package/skills/state-scenarios/references/debugging.md +52 -0
  42. package/skills/state-scenarios/references/mcp.md +39 -0
  43. package/skills/state-scenarios/references/media.md +50 -0
  44. package/skills/state-scenarios/references/network.md +67 -0
  45. package/skills/state-scenarios/references/setup.md +94 -0
  46. package/skills/state-scenarios/references/verify.md +47 -0
  47. package/skills/state-scenarios/references/writing-scenarios.md +55 -0
package/dist/cli.js CHANGED
@@ -1,23 +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
 
22
+ init Create <dir> (default src/scenarios) with default.json, a sibling scenario.schema.json,
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)
11
31
  validate Check every scenario parses, resolves and passes --rules
12
32
  list Print scenarios with links (--base-url) for people and agents; --json for machines
13
33
  shoot Open each scenario in Chromium, wait for its ready selector, screenshot it,
14
- 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)
15
41
  suggest Ask Claude which UI states lack scenarios and draft them into --out (default <dir>/proposed).
16
42
  --src dir[,dir] (required), --schema file, --max n, --model (default $STATE_SCENARIOS_MODEL or claude-opus-5-5).
17
43
  Needs ANTHROPIC_API_KEY or \`ant auth login\`; ANTHROPIC_BASE_URL points it at a compatible proxy.
18
44
  --env-file file loads those variables first.
45
+ promote Move <dir>/proposed/*.json into <dir> after validate. --dry-run lists moves only.
19
46
 
20
- Common: --rules file module exporting \`rules\` (ScenarioRule[])`;
47
+ Common: --rules file module exporting \`rules\` (ScenarioRule[])
48
+ --json machine-readable output (list, shoot, doctor)`;
21
49
  var MAX_SOURCE_CHARS = 8e5;
22
50
  var { positionals, values } = parseArgs({
23
51
  allowPositionals: true,
@@ -27,27 +55,123 @@ var { positionals, values } = parseArgs({
27
55
  out: { type: "string" },
28
56
  only: { type: "string" },
29
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" },
30
67
  json: { type: "boolean" },
31
68
  src: { type: "string" },
32
69
  schema: { type: "string" },
33
70
  max: { type: "string" },
34
71
  model: { type: "string" },
35
72
  "env-file": { type: "string" },
73
+ "dry-run": { type: "boolean" },
74
+ "no-skills": { type: "boolean" },
36
75
  help: { type: "boolean", short: "h" }
37
76
  }
38
77
  });
39
- var [command, dir] = positionals;
78
+ var StepsSchema = z.record(
79
+ z.string(),
80
+ z.function({ input: [z.custom()], output: z.unknown() })
81
+ );
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
+ }
40
93
  if (values.help || !command || !dir) {
41
94
  console.log(HELP);
42
- process.exit(command ? 1 : 0);
95
+ process.exit(command ? 2 : 0);
43
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
+ };
44
114
  try {
115
+ if (command === "init") {
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
+ }
131
+ process.exit(0);
132
+ }
45
133
  const rules = values.rules ? (await import(pathToFileURL(resolve(values.rules)).href)).rules : [];
46
- const files = readdirSync(dir).filter((f) => f.endsWith(".json"));
47
- const catalog = createCatalog(
48
- files.map((f) => JSON.parse(readFileSync(join(dir, f), "utf8"))),
49
- { rules }
50
- );
134
+ if (command === "promote") {
135
+ const { promoteScenarios } = await import("./promote-GSF5UXJL.js");
136
+ const result = promoteScenarios(dir, { rules, dryRun: !!values["dry-run"] });
137
+ process.exit(result.skipped.length ? 1 : 0);
138
+ }
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);
51
175
  switch (command) {
52
176
  case "validate": {
53
177
  const problems = catalog.check();
@@ -68,45 +192,55 @@ try {
68
192
  if (values.json) console.log(JSON.stringify(rows, null, 2));
69
193
  else {
70
194
  for (const r of rows) {
71
- console.log(`${r.name.padEnd(32)} ${r.description}${r.url ? `
72
- ${" ".repeat(33)}${r.url}` : ""}`);
195
+ console.log(
196
+ `${r.name.padEnd(32)} ${r.description}${r.url ? `
197
+ ${" ".repeat(33)}${r.url}` : ""}`
198
+ );
73
199
  }
74
200
  }
75
201
  break;
76
202
  }
77
203
  case "shoot": {
78
- const baseURL = values["base-url"];
79
- if (!baseURL) throw new Error("shoot needs --base-url, e.g. http://localhost:5173");
204
+ const baseURL = requireBaseUrl();
80
205
  const out = values.out ?? "scenario-shots";
81
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
+ }
82
210
  mkdirSync(out, { recursive: true });
83
- const { chromium } = await import("@playwright/test");
84
- const { openScenario, unhandledRequests } = await import("state-scenarios/playwright");
85
- const browser = await chromium.launch();
211
+ await checkServer(baseURL);
212
+ const { inspectScenario, failurePage } = await import("./inspect-LN4EACWA.js");
213
+ const browser = await launchChromium();
86
214
  const report = [];
87
215
  for (const s of catalog.list.filter((s2) => !only || only.includes(s2.name))) {
88
- const context = await browser.newContext({ baseURL });
216
+ const scenario = catalog.resolve(s);
217
+ const context = await browser.newContext({ baseURL, ...contextOptions(scenario, values) });
89
218
  const page = await context.newPage();
90
219
  page.setDefaultTimeout(1e4);
91
- const consoleErrors = [];
92
- const pageErrors = [];
93
- page.on("console", (m) => {
94
- if (m.type() === "error" && !m.text().startsWith("Failed to load resource")) {
95
- consoleErrors.push(m.text());
96
- }
220
+ const result = await inspectScenario(page, scenario, {
221
+ link: scenarioLink(baseURL, scenario),
222
+ strict: !values["no-strict"],
223
+ panel: !values["no-panel"]
97
224
  });
98
- page.on("pageerror", (err) => pageErrors.push(err.message));
99
- let error = null;
100
- try {
101
- await openScenario(page, s.name, { path: catalog.resolve(s).path, strict: !values["no-strict"] });
102
- } catch (err) {
103
- error = err instanceof Error ? err.message.split("\n")[0] : String(err);
104
- }
105
225
  const screenshot = join(out, `${s.name}.png`);
106
- await page.screenshot({ path: screenshot, fullPage: true });
107
- const unhandled = await unhandledRequests(page);
108
- const ok = !error && !unhandled.length && !consoleErrors.length && !pageErrors.length;
109
- report.push({ name: s.name, ok, screenshot, unhandled, consoleErrors, pageErrors, error });
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 });
110
244
  await context.close();
111
245
  }
112
246
  await browser.close();
@@ -114,16 +248,96 @@ ${" ".repeat(33)}${r.url}` : ""}`);
114
248
  else {
115
249
  for (const r of report) {
116
250
  console.log(`${r.ok ? "\u2713" : "\u2717"} ${r.name.padEnd(32)} ${r.screenshot}`);
117
- if (r.error) console.log(` error: ${r.error}`);
251
+ if (r.ok) continue;
252
+ console.log(` ${r.codes.join(", ")}${r.error ? `: ${r.error}` : ""}`);
118
253
  for (const u of r.unhandled) console.log(` unmocked: ${u}`);
119
254
  for (const e of r.pageErrors) console.log(` uncaught: ${e}`);
120
255
  for (const e of r.consoleErrors) console.log(` console.error: ${e}`);
256
+ console.log(` \u276F details ${r.details}`);
121
257
  }
122
258
  }
123
259
  process.exit(report.every((r) => r.ok) ? 0 : 1);
124
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
+ }
125
337
  case "suggest": {
126
- if (!values.src) throw new Error("suggest needs --src, e.g. --src src/pages,src/components");
338
+ if (!values.src) {
339
+ throw new CliError("INVALID_ARGUMENT", "suggest needs --src, e.g. --src src/pages,src/components");
340
+ }
127
341
  const sources = values.src.split(",").flatMap((d) => walk(d));
128
342
  const size = sources.reduce((n, f) => n + f.content.length, 0);
129
343
  if (size > MAX_SOURCE_CHARS) {
@@ -168,15 +382,17 @@ ${result.states.length} states detected, ${covered} have scenarios, ${result.sta
168
382
  if (result.accepted.length) mkdirSync(out, { recursive: true });
169
383
  for (const { scenario, covers } of result.accepted) {
170
384
  const file = join(out, `${scenario.name}.json`);
171
- const $schema = values.schema ? relative(out, values.schema) : void 0;
385
+ const $schema = values.schema ? relative(out, values.schema).replaceAll("\\", "/") : void 0;
172
386
  writeFileSync(file, `${JSON.stringify({ $schema, ...scenario }, null, 2)}
173
387
  `);
174
388
  console.log(`+ ${file} (${covers})`);
175
389
  }
176
390
  for (const r of result.rejected) console.log(`- rejected proposal for ${r.covers}: ${r.error}`);
177
391
  if (result.accepted.length) {
178
- console.log(`
179
- Review the drafts, then move the ones you want into ${dir}.`);
392
+ console.log(
393
+ `
394
+ Review the drafts, then: state-scenarios promote ${dir}`
395
+ );
180
396
  }
181
397
  break;
182
398
  }
@@ -184,13 +400,14 @@ Review the drafts, then move the ones you want into ${dir}.`);
184
400
  console.error(`Unknown command "${command}"
185
401
 
186
402
  ${HELP}`);
187
- process.exit(1);
403
+ process.exit(2);
188
404
  }
189
405
  } catch (err) {
190
406
  const message = err instanceof Error ? err.message : String(err);
191
407
  const hint = /authentication/i.test(message) ? "\nSet ANTHROPIC_API_KEY, or run `ant auth login`." : "";
192
- console.error(`state-scenarios ${command}: ${message}${hint}`);
193
- process.exit(1);
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);
194
411
  }
195
412
  function walk(path) {
196
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 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\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 help: { type: 'boolean', short: 'h' },\n },\n});\n\nconst [command, dir] = positionals;\n\nif (values.help || !command || !dir) {\n console.log(HELP);\n process.exit(command ? 1 : 0);\n}\n\ntry {\n const rules: ScenarioRule[] = values.rules\n ? (await import(pathToFileURL(resolve(values.rules)).href)).rules\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 {for (const r of rows) {\n console.log(`${r.name.padEnd(32)} ${r.description}${r.url ? `\\n${' '.repeat(33)}${r.url}` : ''}`);\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, { path: catalog.resolve(s).path, strict: !values['no-strict'] });\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(`\\nReview the drafts, then move the ones you want into ${dir}.`);\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"]}