@reshotdev/screenshot 0.0.1-beta.2 → 0.0.1-beta.21

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 (81) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +138 -47
  3. package/package.json +27 -16
  4. package/src/commands/auth.js +159 -30
  5. package/src/commands/capture-dom.js +50 -0
  6. package/src/commands/certify.js +62 -0
  7. package/src/commands/compose.js +220 -0
  8. package/src/commands/doctor-release.js +74 -0
  9. package/src/commands/doctor-target.js +108 -0
  10. package/src/commands/drifts.js +16 -69
  11. package/src/commands/import-tests.js +13 -13
  12. package/src/commands/init.js +16 -277
  13. package/src/commands/publish.js +484 -257
  14. package/src/commands/pull.js +302 -35
  15. package/src/commands/refresh.js +166 -0
  16. package/src/commands/run.js +292 -12
  17. package/src/commands/setup-wizard.js +348 -496
  18. package/src/commands/status.js +334 -126
  19. package/src/commands/sync.js +28 -236
  20. package/src/commands/ui.js +1 -1
  21. package/src/commands/variation.js +194 -0
  22. package/src/commands/verify-publish.js +46 -0
  23. package/src/index.js +383 -118
  24. package/src/lib/api-client.js +172 -60
  25. package/src/lib/auto-update/refresh.js +598 -0
  26. package/src/lib/auto-update/scene-runtime.compose.tsx +73 -0
  27. package/src/lib/auto-update/spec.js +89 -0
  28. package/src/lib/capture-engine.js +179 -9
  29. package/src/lib/capture-script-runner.js +639 -214
  30. package/src/lib/certification.js +887 -0
  31. package/src/lib/compose-context.js +156 -0
  32. package/src/lib/compose-pack.js +42 -0
  33. package/src/lib/compose-runtime.js +34 -0
  34. package/src/lib/compose-upload.js +142 -0
  35. package/src/lib/config.js +186 -81
  36. package/src/lib/dom-capture.js +64 -0
  37. package/src/lib/ensure-browser.js +147 -0
  38. package/src/lib/output-path-template.js +3 -3
  39. package/src/lib/record-cdp.js +288 -16
  40. package/src/lib/record-clip.js +83 -3
  41. package/src/lib/record-config.js +1 -5
  42. package/src/lib/release-doctor.js +321 -0
  43. package/src/lib/resolve-targets.js +60 -0
  44. package/src/lib/run-manifest.js +148 -0
  45. package/src/lib/standalone-mode.js +1 -1
  46. package/src/lib/storage-providers.js +5 -5
  47. package/src/lib/style-engine.js +5 -5
  48. package/src/lib/target-contract.js +292 -0
  49. package/src/lib/ui-api-helpers.js +118 -0
  50. package/src/lib/ui-api.js +31 -824
  51. package/src/lib/ui-asset-cleanup.js +62 -0
  52. package/src/lib/ui-output-versions.js +165 -0
  53. package/src/lib/ui-recorder-routes.js +341 -0
  54. package/src/lib/ui-scenario-metadata.js +161 -0
  55. package/vendor/compose/dist/auto-update.cjs +5544 -0
  56. package/vendor/compose/dist/auto-update.mjs +5518 -0
  57. package/vendor/compose/dist/capture.cjs +1450 -0
  58. package/vendor/compose/dist/capture.mjs +1416 -0
  59. package/vendor/compose/dist/eligibility.cjs +5331 -0
  60. package/vendor/compose/dist/eligibility.mjs +5313 -0
  61. package/vendor/compose/dist/index.cjs +2046 -0
  62. package/vendor/compose/dist/index.mjs +1997 -0
  63. package/vendor/compose/dist/jsx-dev-runtime.cjs +55 -0
  64. package/vendor/compose/dist/jsx-dev-runtime.mjs +27 -0
  65. package/vendor/compose/dist/jsx-runtime.cjs +58 -0
  66. package/vendor/compose/dist/jsx-runtime.mjs +31 -0
  67. package/vendor/compose/dist/render.cjs +558 -0
  68. package/vendor/compose/dist/render.mjs +515 -0
  69. package/vendor/compose/dist/verify-cli.cjs +3806 -0
  70. package/vendor/compose/dist/verify-cli.mjs +3812 -0
  71. package/vendor/compose/dist/verify.cjs +3880 -0
  72. package/vendor/compose/dist/verify.mjs +3858 -0
  73. package/web/manager/dist/assets/index-D0S2otug.js +507 -0
  74. package/web/manager/dist/index.html +1 -1
  75. package/src/commands/ci-run.js +0 -123
  76. package/src/commands/ci-setup.js +0 -288
  77. package/src/commands/ingest.js +0 -458
  78. package/src/commands/setup.js +0 -137
  79. package/src/commands/validate-docs.js +0 -529
  80. package/src/lib/playwright-runner.js +0 -252
  81. package/web/manager/dist/assets/index--ZgioErz.js +0 -507
package/src/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // index.js - Reshot CLI entry point
4
- // Consolidated command structure for ease of use while maintaining power features
5
4
 
6
5
  require("dotenv").config({
7
6
  path: require("path").join(__dirname, "..", ".env"),
@@ -9,13 +8,15 @@ require("dotenv").config({
9
8
 
10
9
  const { Command } = require("commander");
11
10
  const chalk = require("chalk");
11
+ const fs = require("fs-extra");
12
+ const path = require("path");
12
13
  const pkg = require("../package.json");
13
14
 
14
15
  const program = new Command();
15
16
 
16
17
  program
17
18
  .name("reshot")
18
- .description("Visual documentation that stays in sync")
19
+ .description("Visual capture, publishing, and governance for your UI")
19
20
  .version(pkg.version);
20
21
 
21
22
  // ============================================================================
@@ -25,26 +26,78 @@ program
25
26
  // Setup: Interactive wizard for initial configuration
26
27
  program
27
28
  .command("setup")
28
- .description("Interactive setup wizard (auth, config, features)")
29
- .option("--offline", "Skip authentication (local-only mode)")
29
+ .description("Set up the local or hosted workflow from scratch")
30
+ .option("--offline", "Stay local-only and skip hosted authentication")
31
+ .option("--project <id>", "Connect setup to an existing Reshot project")
32
+ .option("--token <token>", "Publish token for non-interactive project linking")
33
+ .option("--no-studio", "Skip offering to launch Studio after setup")
30
34
  .option("--force", "Force re-initialization even if already set up")
31
35
  .action(async (options) => {
32
36
  try {
33
37
  const setupWizard = require("./commands/setup-wizard");
34
- await setupWizard(options);
38
+ // Commander stores `--no-studio` as `options.studio === false`, not
39
+ // `options.noStudio`. Normalize it so the flag is honored end-to-end.
40
+ await setupWizard({ ...options, noStudio: options.studio === false });
35
41
  } catch (error) {
36
42
  console.error(chalk.red("Error:"), error.message);
37
43
  process.exit(1);
38
44
  }
39
45
  });
40
46
 
41
- // Sync: Upload traces and documentation to platform
47
+ program
48
+ .command("record-clip [target]")
49
+ .description("Record a scenario as a summary MP4 (alias for `reshot run --format summary-video`)")
50
+ .option("-s, --scenarios <keys>", "Comma-separated scenario keys")
51
+ .option("--out <dir>", "Copy the generated MP4 and metadata into this directory")
52
+ .option("--no-headless", "Run browser in visible mode")
53
+ .option("--debug", "Enable verbose debug logging")
54
+ .action(async (target, options) => {
55
+ if (options.debug) {
56
+ process.env.RESHOT_DEBUG = "1";
57
+ }
58
+ try {
59
+ const runCommand = require("./commands/run");
60
+ const scenarioKeys = resolveRecordClipScenarioKeys(target, options);
61
+ const result = await runCommand({
62
+ scenarioKeys,
63
+ headless: options.headless,
64
+ format: "summary-video",
65
+ noExit: true,
66
+ });
67
+
68
+ if (options.out && result?.success !== false) {
69
+ await copyRecordClipOutputs(result, target, options.out);
70
+ }
71
+
72
+ if (result?.success === false) {
73
+ process.exit(1);
74
+ }
75
+ } catch (error) {
76
+ console.error(chalk.red("Error:"), error.message);
77
+ if (options.debug && error.stack) {
78
+ console.error(chalk.gray(error.stack));
79
+ }
80
+ process.exit(1);
81
+ }
82
+ });
83
+
84
+ program.addHelpText(
85
+ "after",
86
+ `
87
+ Primary workflow:
88
+ reshot setup Configure the project and choose local-only or hosted mode
89
+ reshot run Run configured scenarios with fail-fast preflight checks
90
+ reshot publish Upload captured assets to the Reshot platform
91
+ reshot pull Pull approved CDN-backed asset references into your repo
92
+ reshot doctor target Audit certified-target readiness before capture
93
+ `,
94
+ );
95
+
96
+ // Sync: Upload Playwright traces to platform
42
97
  program
43
98
  .command("sync")
44
- .description("Upload Playwright traces and documentation to Reshot")
99
+ .description("Upload Playwright traces to Reshot")
45
100
  .option("--trace-dir <path>", "Path to test-results directory")
46
- .option("--traces", "Sync traces only")
47
- .option("--docs", "Sync documentation only")
48
101
  .option("--dry-run", "Preview what would be synced")
49
102
  .option("-v, --verbose", "Show detailed output")
50
103
  .action(async (options) => {
@@ -52,8 +105,6 @@ program
52
105
  const syncCommand = require("./commands/sync");
53
106
  await syncCommand({
54
107
  traceDir: options.traceDir,
55
- traces: options.traces,
56
- docs: options.docs,
57
108
  dryRun: options.dryRun,
58
109
  verbose: options.verbose,
59
110
  });
@@ -63,7 +114,7 @@ program
63
114
  }
64
115
  });
65
116
 
66
- // Status: View project status, sync jobs, and drift summary
117
+ // Status: View project status and drift summary
67
118
  program
68
119
  .command("status")
69
120
  .description("View project status, sync history, and drift summary")
@@ -99,36 +150,16 @@ program
99
150
  }
100
151
  });
101
152
 
102
- // Validate: Check configuration and documentation bindings
103
- program
104
- .command("validate")
105
- .description("Validate configuration and documentation bindings")
106
- .option("--strict", "Fail on warnings (zombie links, unbound files)")
107
- .option("--fix", "Attempt to auto-fix issues where possible")
108
- .option("-v, --verbose", "Show detailed validation output")
109
- .action(async (options) => {
110
- try {
111
- const validateCommand = require("./commands/validate-docs");
112
- if (typeof validateCommand.validateDocSync === "function") {
113
- await validateCommand.validateDocSync(options);
114
- } else {
115
- await validateCommand.validateDocs(options);
116
- }
117
- } catch (error) {
118
- console.error(chalk.red("Error:"), error.message);
119
- process.exit(1);
120
- }
121
- });
122
-
123
153
  // ============================================================================
124
154
  // VISUAL CAPTURE COMMANDS
125
155
  // ============================================================================
126
156
 
127
157
  // Run: Execute scenarios from config (automated visual capture)
128
158
  program
129
- .command("run")
159
+ .command("run [target]")
130
160
  .description("Execute visual capture scenarios from config")
131
161
  .option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
162
+ .option("--scenario <keys>", "Alias for --scenarios")
132
163
  .option("--no-headless", "Run browser in visible mode")
133
164
  .option("--variant <json>", "Override variant configuration as JSON")
134
165
  .option("--all-variants", "Run all configured variant combinations")
@@ -148,15 +179,13 @@ program
148
179
  .option("--no-style", "Disable style processing")
149
180
  .option("--cloud", "Compare against cloud baselines")
150
181
  .option("--debug", "Enable verbose debug logging")
151
- .action(async (options) => {
182
+ .action(async (target, options) => {
152
183
  if (options.debug) {
153
184
  process.env.RESHOT_DEBUG = "1";
154
185
  }
155
186
  try {
156
187
  const runCommand = require("./commands/run");
157
- const scenarioKeys = options.scenarios
158
- ? options.scenarios.split(",").map((s) => s.trim())
159
- : null;
188
+ const scenarioKeys = resolveScenarioKeysFromTarget(target, options);
160
189
  await runCommand({
161
190
  scenarioKeys,
162
191
  headless: options.headless,
@@ -179,19 +208,96 @@ program
179
208
  }
180
209
  });
181
210
 
211
+ // Capture: Compatibility alias for older docs and snippets
212
+ program
213
+ .command("capture")
214
+ .description("Compatibility alias for `reshot run` (prefer `reshot run` and `reshot publish`)")
215
+ .option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
216
+ .option("--mode <mode>", "Compatibility mode: preview | publish", "preview")
217
+ .option("--config <path>", "Compatibility config path (defaults to reshot.config.json)")
218
+ .option("--base-url <url>", "Temporary base URL override for compatibility flows")
219
+ .option("--tag <tag>", "Version tag when mode=publish")
220
+ .option("-m, --message <message>", "Publish message when mode=publish")
221
+ .option("--no-headless", "Run browser in visible mode")
222
+ .option("-c, --concurrency <n>", "Number of parallel browser workers", parseInt)
223
+ .option("--debug", "Enable verbose debug logging")
224
+ .action(async (options) => {
225
+ if (options.debug) {
226
+ process.env.RESHOT_DEBUG = "1";
227
+ }
228
+ if (options.config && options.config !== "reshot.config.json") {
229
+ console.log(
230
+ chalk.yellow(
231
+ "Compatibility mode currently uses reshot.config.json from the working directory.",
232
+ ),
233
+ );
234
+ }
235
+ if (options.baseUrl) {
236
+ process.env.RESHOT_BASE_URL = options.baseUrl;
237
+ console.log(
238
+ chalk.gray(
239
+ `Using temporary base URL override for this run: ${options.baseUrl}`,
240
+ ),
241
+ );
242
+ }
243
+ try {
244
+ const runCommand = require("./commands/run");
245
+ const scenarioKeys = options.scenarios
246
+ ? options.scenarios.split(",").map((s) => s.trim())
247
+ : null;
248
+ await runCommand({
249
+ scenarioKeys,
250
+ headless: options.headless,
251
+ concurrency: options.concurrency,
252
+ });
253
+
254
+ if (String(options.mode || "preview").toLowerCase() === "publish") {
255
+ const publishCommand = require("./commands/publish");
256
+ await publishCommand({
257
+ tag: options.tag,
258
+ message: options.message,
259
+ });
260
+ }
261
+ } catch (error) {
262
+ console.error(chalk.red("Error:"), error.message);
263
+ if (options.debug && error.stack) {
264
+ console.error(chalk.gray(error.stack));
265
+ }
266
+ process.exit(1);
267
+ }
268
+ });
269
+
182
270
  // Record: Interactive visual recording from live browser
183
271
  program
184
272
  .command("record [title]")
185
273
  .description("Interactively record visuals from a live browser session")
274
+ .option("--name <title>", "Compatibility alias for the scenario title")
186
275
  .option("--browser", "Launch Chrome with remote debugging before recording")
187
276
  .option("-p, --port <port>", "Chrome debugging port (default: 9222)")
188
277
  .option("--url <url>", "URL to open when launching browser")
278
+ .option("--refresh-session", "Only refresh the auth session (no recording prompts)")
189
279
  .option("--debug", "Enable verbose debug logging")
190
280
  .action(async (title, options) => {
191
281
  if (options.debug) {
192
282
  process.env.RESHOT_DEBUG = "1";
193
283
  }
284
+ const resolvedTitle = title || options.name;
194
285
  try {
286
+ // If --refresh-session, just sync the session and exit
287
+ if (options.refreshSession) {
288
+ const { autoSyncSessionFromCDP, getDefaultSessionPath } = require("./lib/record-cdp");
289
+ const sessionPath = getDefaultSessionPath();
290
+ console.log(chalk.gray(" Syncing session from active browser..."));
291
+ const result = await autoSyncSessionFromCDP(sessionPath);
292
+ if (result.synced) {
293
+ console.log(chalk.green(" ✔ Session refreshed at " + sessionPath));
294
+ } else {
295
+ console.log(chalk.yellow(" ⚠ No active CDP browser found. Launch Chrome with remote debugging first:"));
296
+ console.log(chalk.gray(" reshot record --browser --refresh-session"));
297
+ }
298
+ return;
299
+ }
300
+
195
301
  // If --browser flag, launch Chrome first
196
302
  if (options.browser) {
197
303
  const chromeCommand = require("./commands/chrome");
@@ -204,7 +310,7 @@ program
204
310
  }
205
311
 
206
312
  const recordCommand = require("./commands/record");
207
- await recordCommand(title);
313
+ await recordCommand(resolvedTitle);
208
314
  } catch (error) {
209
315
  console.error(chalk.red("Error:"), error.message);
210
316
  if (options.debug && error.stack) {
@@ -233,6 +339,18 @@ program
233
339
  }
234
340
  });
235
341
 
342
+ // Compose: Render a local JSX composition into a video pack
343
+ const { registerCompose } = require("./commands/compose");
344
+ registerCompose(program);
345
+
346
+ // Capture-DOM: Capture a self-contained DOM reconstruction artifact from a URL
347
+ const { registerCaptureDom } = require("./commands/capture-dom");
348
+ registerCaptureDom(program);
349
+
350
+ // Refresh: Phase 5 auto-update loop — recapture, drift-check, re-publish or flag
351
+ const { registerRefresh } = require("./commands/refresh");
352
+ registerRefresh(program);
353
+
236
354
  // ============================================================================
237
355
  // PUBLISHING & INTEGRATION COMMANDS
238
356
  // ============================================================================
@@ -246,28 +364,58 @@ program
246
364
  .option("--video <path>", "Explicit video file to upload with this publish")
247
365
  .option("--dry-run", "Preview without uploading")
248
366
  .option("-f, --force", "Skip confirmation prompts")
367
+ .option("--all-output", "Publish from the full .reshot/output tree instead of the latest successful run manifest")
249
368
  .option("--output-json", "Write structured result to .reshot/output/publish-result.json")
369
+ .option("--auto-approve", "Automatically approve published visuals (skip review queue)")
370
+ .option("--skip-release-doctor", "Skip the composed release gate precheck")
250
371
  .action(async (options) => {
251
372
  try {
252
373
  const publishCommand = require("./commands/publish");
253
- await publishCommand({
374
+ const result = await publishCommand({
254
375
  ...options,
255
376
  outputJson: options.outputJson,
377
+ autoApprove: options.autoApprove,
378
+ skipReleaseDoctor: options.skipReleaseDoctor,
256
379
  });
380
+ if (result && result.success === false) {
381
+ process.exit(1);
382
+ }
257
383
  } catch (error) {
258
384
  console.error(chalk.red("Error:"), error.message);
259
385
  process.exit(1);
260
386
  }
261
387
  });
262
388
 
263
- // Pull: Generate asset map for build pipelines
389
+ // Variation: Render a variation from a captured DOM scene (MHTML).
390
+ // Beta — see docs/variation-pipeline.md.
391
+ program
392
+ .command("variation")
393
+ .description("Render a variation from a captured DOM scene (beta)")
394
+ .option("-s, --source <path>", "Path to source .mhtml (overrides --scenario/--capture)")
395
+ .option("--scenario <key>", "Scenario key under .reshot/output/")
396
+ .option("--capture <key>", "Capture key (e.g., 'observation-detail')")
397
+ .option("--theme <name>", "Theme variant: light | dark", "light")
398
+ .option("-m, --manifest <path>", "Path to variation manifest (.json)")
399
+ .option("-o, --output <path>", "Output PNG path")
400
+ .option("--no-headless", "Run browser visibly for debugging")
401
+ .action(async (options) => {
402
+ try {
403
+ const variationCommand = require("./commands/variation");
404
+ await variationCommand(options);
405
+ } catch (error) {
406
+ console.error(chalk.red("Error:"), error.message);
407
+ process.exit(1);
408
+ }
409
+ });
410
+
411
+ // Pull: Generate asset map for local workflows
264
412
  program
265
413
  .command("pull")
266
- .description("Pull asset map for your build pipeline")
414
+ .description("Pull asset map for your capture workflow")
267
415
  .option("-f, --format <format>", "Output format: json, ts, csv", "json")
268
416
  .option("-o, --output <path>", "Output file path")
269
417
  .option("--full", "Include full metadata in TypeScript output")
270
- .option("--status <status>", "Filter: approved, pending, all", "approved")
418
+ .option("--status <status>", "Filter: approved, pending, all", "all")
271
419
  .action(async (options) => {
272
420
  try {
273
421
  const pullCommand = require("./commands/pull");
@@ -283,50 +431,105 @@ program
283
431
  }
284
432
  });
285
433
 
286
- // CI: CI/CD integration commands
287
- const ciCommand = program
288
- .command("ci")
289
- .description("CI/CD integration commands");
434
+ // Doctor: Validate target contract and readiness
435
+ const doctor = program
436
+ .command("doctor")
437
+ .description("Validate target configuration and readiness")
438
+ .option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
439
+ .option("--timeout <ms>", "Per-step timeout in milliseconds (default 15000)")
440
+ .option("--json", "Output JSON report")
441
+ .action(async (options) => {
442
+ // Bare `reshot doctor` runs the target readiness audit by default instead
443
+ // of just printing help (audit run-10 F5). Use `doctor release` for the
444
+ // full release gate.
445
+ try {
446
+ const doctorTargetCommand = require("./commands/doctor-target");
447
+ await doctorTargetCommand(options || {});
448
+ } catch (error) {
449
+ console.error(chalk.red("Error:"), error.message);
450
+ process.exit(1);
451
+ }
452
+ });
290
453
 
291
- ciCommand
292
- .command("setup")
293
- .description("Interactive CI/CD setup wizard (GitHub Actions, etc.)")
294
- .action(async () => {
454
+ doctor
455
+ .command("target")
456
+ .description("Audit the certified target contract before capture")
457
+ .option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
458
+ .option("--timeout <ms>", "Per-step timeout in milliseconds (default 15000)")
459
+ .option("--json", "Output JSON report")
460
+ .action(async (options) => {
295
461
  try {
296
- const ciSetup = require("./commands/ci-setup");
297
- await ciSetup();
462
+ const doctorTargetCommand = require("./commands/doctor-target");
463
+ await doctorTargetCommand(options);
298
464
  } catch (error) {
299
465
  console.error(chalk.red("Error:"), error.message);
300
466
  process.exit(1);
301
467
  }
302
468
  });
303
469
 
304
- ciCommand
305
- .command("run")
306
- .description("Run capture + publish in one step (CI-optimized)")
307
- .option("-c, --config <path>", "Path to docsync.config.json")
308
- .option("--tag <tag>", "Version tag for publish")
309
- .option("-m, --message <message>", "Commit message for publish")
310
- .option("--dry-run", "Preview without uploading")
311
- .option("--no-publish", "Run capture only, skip publish")
470
+ doctor
471
+ .command("release")
472
+ .description("Run the combined release gate: preflight, target doctor, and docs assets")
473
+ .option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
474
+ .option("--json", "Output JSON report")
475
+ .action(async (options) => {
476
+ try {
477
+ const doctorReleaseCommand = require("./commands/doctor-release");
478
+ await doctorReleaseCommand(options);
479
+ } catch (error) {
480
+ console.error(chalk.red("Error:"), error.message);
481
+ process.exit(1);
482
+ }
483
+ });
484
+
485
+ // Verify: Publish/pull/hosted delivery verification
486
+ const verify = program.command("verify").description("Verify publish and delivery flows");
487
+
488
+ verify
489
+ .command("publish")
490
+ .description("Verify publish, pull, and hosted asset delivery")
491
+ .option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
492
+ .option("--tag <tag>", "Version tag for verification publish")
493
+ .option("-m, --message <message>", "Publish message override")
494
+ .option("--json", "Output JSON report")
495
+ .action(async (options) => {
496
+ try {
497
+ const verifyPublishCommand = require("./commands/verify-publish");
498
+ await verifyPublishCommand(options);
499
+ } catch (error) {
500
+ console.error(chalk.red("Error:"), error.message);
501
+ process.exit(1);
502
+ }
503
+ });
504
+
505
+ // Certify: full certified-target release gate
506
+ program
507
+ .command("certify")
508
+ .description("Run the full certified target pipeline")
509
+ .option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
510
+ .option("--tag <tag>", "Version tag for certification publish")
511
+ .option("-m, --message <message>", "Publish message override")
512
+ .option("--skip-release-doctor", "Skip the composed release gate precheck")
513
+ .option("--json", "Output JSON report")
312
514
  .action(async (options) => {
313
515
  try {
314
- const ciRun = require("./commands/ci-run");
315
- await ciRun(options);
516
+ const certifyCommand = require("./commands/certify");
517
+ await certifyCommand(options);
316
518
  } catch (error) {
317
519
  console.error(chalk.red("Error:"), error.message);
318
520
  process.exit(1);
319
521
  }
320
522
  });
321
523
 
524
+
322
525
  // ============================================================================
323
526
  // DRIFT MANAGEMENT COMMANDS
324
527
  // ============================================================================
325
528
 
326
- // Drifts: View and manage documentation drifts
529
+ // Drifts: View and manage visual drifts
327
530
  program
328
531
  .command("drifts [action] [id]")
329
- .description("View and manage documentation drifts")
532
+ .description("View and manage visual drifts")
330
533
  .addHelpText(
331
534
  "after",
332
535
  `
@@ -339,7 +542,6 @@ Actions:
339
542
  sync <id> Mark as manually synced (external_host)
340
543
  approve-all Approve all pending drifts
341
544
  reject-all Reject all pending drifts
342
- validate Validate journey bindings
343
545
  `,
344
546
  )
345
547
  .option("--status <status>", "Filter: PENDING, APPROVED, REJECTED, IGNORED")
@@ -348,7 +550,6 @@ Actions:
348
550
  .action(async (action, id, options) => {
349
551
  try {
350
552
  const driftsCommand = require("./commands/drifts");
351
- // Map action/id to the expected subcommand/args format
352
553
  const subcommand = action || "list";
353
554
  const args = id ? [id] : [];
354
555
  await driftsCommand(subcommand, args, options);
@@ -363,9 +564,25 @@ Actions:
363
564
  // ============================================================================
364
565
 
365
566
  // Auth: Standalone authentication (for re-auth scenarios)
366
- program
367
- .command("auth", { hidden: true })
368
- .description("Authenticate with Reshot Cloud")
567
+ const auth = program
568
+ .command("auth")
569
+ .description(
570
+ "Link this CLI to a Reshot project. Opens a browser to approve the session and stores a project API key locally.",
571
+ )
572
+ .addHelpText(
573
+ "after",
574
+ `
575
+ Authentication paths:
576
+ Interactive (default) Opens your browser, you approve the session, and the
577
+ CLI saves a project API key to .reshot/settings.json.
578
+ Non-interactive (CI) Set RESHOT_API_KEY and RESHOT_PROJECT_ID and the CLI
579
+ links without any browser or prompt.
580
+
581
+ Examples:
582
+ reshot auth Browser-based login
583
+ RESHOT_API_KEY=… RESHOT_PROJECT_ID=… reshot auth Headless / CI login
584
+ `,
585
+ )
369
586
  .action(async () => {
370
587
  try {
371
588
  const authCommand = require("./commands/auth");
@@ -376,87 +593,135 @@ program
376
593
  }
377
594
  });
378
595
 
379
- // Init: Standalone initialization (for manual config)
380
- program
381
- .command("init", { hidden: true })
382
- .description("Initialize Reshot configuration")
596
+ auth
597
+ .command("login")
598
+ .description(
599
+ "Alias for `reshot auth` — opens the browser approval flow (or uses RESHOT_API_KEY in CI).",
600
+ )
383
601
  .action(async () => {
384
602
  try {
385
- const initCommand = require("./commands/init");
386
- await initCommand();
603
+ const authCommand = require("./commands/auth");
604
+ await authCommand();
387
605
  } catch (error) {
388
606
  console.error(chalk.red("Error:"), error.message);
389
607
  process.exit(1);
390
608
  }
391
609
  });
392
610
 
393
- // Chrome: Launch Chrome with debugging (absorbed into record --browser)
394
611
  program
395
- .command("chrome", { hidden: true })
396
- .description("Launch Chrome with remote debugging")
397
- .option("-p, --port <port>", "Remote debugging port", "9222")
398
- .option("--url <url>", "URL to open after launch", "about:blank")
399
- .action(async (options) => {
612
+ .command("login")
613
+ .description(
614
+ "Alias for `reshot auth` link this CLI to a project via browser approval (or RESHOT_API_KEY in CI).",
615
+ )
616
+ .action(async () => {
400
617
  try {
401
- const chromeCommand = require("./commands/chrome");
402
- await chromeCommand(options);
618
+ const authCommand = require("./commands/auth");
619
+ await authCommand();
403
620
  } catch (error) {
404
621
  console.error(chalk.red("Error:"), error.message);
405
622
  process.exit(1);
406
623
  }
407
624
  });
408
625
 
409
- // Ingest: Alias for sync (backward compatibility)
626
+ // Init: Standalone initialization (for manual config)
410
627
  program
411
- .command("ingest", { hidden: true })
412
- .description("Upload traces and docs (alias for sync)")
413
- .option("--trace-dir <path>", "Path to test-results directory")
414
- .option("--dry-run", "Preview what would be synced")
415
- .action(async (options) => {
416
- console.log(
417
- chalk.yellow("Note: 'ingest' is now 'sync'. Running sync...\n"),
418
- );
628
+ .command("init", { hidden: true })
629
+ .description("Initialize Reshot configuration")
630
+ .action(async () => {
419
631
  try {
420
- const syncCommand = require("./commands/sync");
421
- await syncCommand({
422
- traceDir: options.traceDir,
423
- dryRun: options.dryRun,
424
- });
632
+ const initCommand = require("./commands/init");
633
+ await initCommand();
425
634
  } catch (error) {
426
635
  console.error(chalk.red("Error:"), error.message);
427
636
  process.exit(1);
428
637
  }
429
638
  });
430
639
 
431
- // UI: Alias for studio (backward compatibility)
640
+ // Chrome: Launch Chrome with debugging (absorbed into record --browser)
432
641
  program
433
- .command("ui", { hidden: true })
434
- .description("Launch Reshot Studio (alias for studio)")
435
- .option("--port <port>", "Port for web UI", "4300")
436
- .option("--host <host>", "Host for web UI", "127.0.0.1")
437
- .option("--no-open", "Do not automatically open browser")
642
+ .command("chrome", { hidden: true })
643
+ .description("Launch Chrome with remote debugging")
644
+ .option("-p, --port <port>", "Remote debugging port", "9222")
645
+ .option("--url <url>", "URL to open after launch", "about:blank")
438
646
  .action(async (options) => {
439
647
  try {
440
- const uiCommand = require("./commands/ui");
441
- await uiCommand(options);
648
+ const chromeCommand = require("./commands/chrome");
649
+ await chromeCommand(options);
442
650
  } catch (error) {
443
651
  console.error(chalk.red("Error:"), error.message);
444
652
  process.exit(1);
445
653
  }
446
654
  });
447
655
 
448
- // Init-docs: Alias hidden (merged into setup wizard)
449
- program
450
- .command("init-docs", { hidden: true })
451
- .description("Initialize documentation sync (use setup instead)")
452
- .action(async () => {
453
- try {
454
- const initDocsCommand = require("./commands/init-docs");
455
- await initDocsCommand();
456
- } catch (error) {
457
- console.error(chalk.red("Error:"), error.message);
458
- process.exit(1);
459
- }
656
+ function resolveRecordClipScenarioKeys(target, options = {}) {
657
+ return resolveScenarioKeysFromTarget(target, options);
658
+ }
659
+
660
+ function resolveScenarioKeysFromTarget(target, options = {}) {
661
+ // Accept the singular `--scenario` as an alias for `--scenarios` — an easy
662
+ // trap that otherwise hard-fails the run (audit run-10 F2).
663
+ const scenarioList = options.scenarios || options.scenario;
664
+ if (scenarioList) {
665
+ return scenarioList.split(",").map((value) => value.trim()).filter(Boolean);
666
+ }
667
+
668
+ if (!target) {
669
+ return null;
670
+ }
671
+
672
+ const absoluteTarget = path.resolve(process.cwd(), target);
673
+ if (!fs.existsSync(absoluteTarget)) {
674
+ return [target];
675
+ }
676
+
677
+ const source = fs.readFileSync(absoluteTarget, "utf8");
678
+ const config = require("./lib/config").readConfig();
679
+ const scenarios = config.scenarios || [];
680
+ const mentioned = scenarios.find((scenario) => source.includes(scenario.key));
681
+ if (mentioned) {
682
+ return [mentioned.key];
683
+ }
684
+
685
+ const basename = path.basename(target).replace(/\.(spec\.)?[cm]?[tj]sx?$/i, "");
686
+ const byName = scenarios.find((scenario) => {
687
+ const normalizedKey = String(scenario.key || "").replace(/^dogfood-/, "");
688
+ return scenario.key === basename || normalizedKey === basename;
460
689
  });
690
+ if (byName) {
691
+ return [byName.key];
692
+ }
693
+
694
+ throw new Error(
695
+ `Could not map ${target} to a configured scenario. Add the scenario key to the spec file or pass --scenarios <key>.`,
696
+ );
697
+ }
698
+
699
+ async function copyRecordClipOutputs(result, target, outDir) {
700
+ const firstScenario = (result.results || []).find((item) => item?.success !== false);
701
+ const outputDir = firstScenario?.outputDir;
702
+ if (!outputDir) {
703
+ throw new Error("No summary-video output directory was produced.");
704
+ }
705
+
706
+ const slug = target
707
+ ? path.basename(target).replace(/\.(spec\.)?[cm]?[tj]sx?$/i, "")
708
+ : firstScenario.key || "summary-video";
709
+ const destinationDir = path.resolve(process.cwd(), outDir);
710
+ await fs.ensureDir(destinationDir);
711
+
712
+ const copies = [
713
+ ["summary-video.mp4", `${slug}.mp4`],
714
+ ["summary-video.metadata.json", `${slug}.metadata.json`],
715
+ ["sentinels.json", `${slug}.sentinels.json`],
716
+ ];
717
+
718
+ for (const [fromName, toName] of copies) {
719
+ const sourcePath = path.join(outputDir, fromName);
720
+ if (await fs.pathExists(sourcePath)) {
721
+ await fs.copy(sourcePath, path.join(destinationDir, toName));
722
+ console.log(chalk.gray(` copied ${toName}`));
723
+ }
724
+ }
725
+ }
461
726
 
462
727
  program.parse(process.argv);