@vizzly-testing/cli 0.31.0 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,70 +1,73 @@
1
1
  # Vizzly CLI
2
2
 
3
- > Visual review for agents and teams
3
+ > Visual regression testing from your terminal
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/@vizzly-testing/cli.svg)](https://www.npmjs.com/package/@vizzly-testing/cli)
5
+ [![package version](https://img.shields.io/npm/v/@vizzly-testing/cli.svg)](https://www.npmjs.com/package/@vizzly-testing/cli)
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
 
8
- Vizzly keeps the visual truth behind your UI: approved baselines, meaningful diffs, review state, comments, and preview links in one place. That makes it useful for humans reviewing product changes and for LLM agents that need to understand what the UI is supposed to look like before they edit it.
8
+ <p align="center">
9
+ <img src="./docs/assets/vizzly-mascot-inspector.png" alt="Vizzly bear inspecting a screenshot" width="220" />
10
+ </p>
9
11
 
10
- Unlike tools that re-render components in isolation, Vizzly captures screenshots directly from your functional tests: the real thing. Whether you're validating AI-generated code or testing manual changes, you get reviewed visual evidence before anything hits production.
12
+ Vizzly is a visual testing and regression platform for teams that ship UI. It
13
+ captures screenshots from your real tests, compares them to approved baselines,
14
+ and gives you useful review data: meaningful diffs, comments, approvals, build
15
+ status, and preview links.
11
16
 
12
- ## Why Vizzly?
13
-
14
- **Local TDD workflow.** See changes as you type, not after CI. The `vizzly tdd` command runs a local dashboard that compares screenshots instantly and exposes the current workspace as local context.
15
-
16
- **Meaningful diff metadata.** Vizzly stores rich diff evidence: changed regions, cluster metadata, fingerprints, hotspots, confirmed regions, and image URLs. Agents can inspect what changed instead of guessing from a pass/fail label.
17
-
18
- **Any screenshot source.** Playwright, Cypress, Puppeteer, Selenium, native mobile apps, or even design mockups. If you can capture it, Vizzly can compare it.
19
-
20
- **Approved baselines as truth.** Cloud context carries human review state. Local context carries the downloaded or generated baseline metadata. That is the bridge between TDD locally and collaborative review in Vizzly.
17
+ This package is the CLI and local SDK surface. Use it to run local visual TDD,
18
+ upload screenshots from CI, generate static reports, and fetch machine-readable
19
+ build or diff data for scripts, automation, and coding agents.
21
20
 
22
21
  ## Quick Start
23
22
 
24
- Requires Node.js 22+.
23
+ You need Node.js 22+.
25
24
 
26
25
  ```bash
27
- npm install -g @vizzly-testing/cli
26
+ pnpm install -g @vizzly-testing/cli
28
27
 
29
28
  vizzly init
30
29
  ```
31
30
 
32
- ### Local Development (TDD Mode)
31
+ ### Start Local TDD
33
32
 
34
- Start the TDD server, run your tests in watch mode, and see visual diffs instantly at `http://localhost:47392`.
33
+ Start the TDD server, run your tests, and open the dashboard at
34
+ `http://localhost:47392`.
35
35
 
36
36
  ```bash
37
37
  vizzly tdd start
38
- npm test -- --watch
38
+ pnpm test -- --watch
39
39
  ```
40
40
 
41
- The dashboard shows diffs in real-time. Accept or reject changes right from the UI.
41
+ The dashboard shows screenshots, baselines, and diffs as they arrive. Accept or
42
+ reject changes right from the UI.
42
43
 
43
- ### Cloud Integration (CI/CD)
44
+ ### Run With Cloud Review
44
45
 
45
- For team collaboration and CI pipelines:
46
+ Use cloud builds when you want shared baselines, team review, and CI status:
46
47
 
47
48
  ```bash
48
49
  vizzly login
49
- vizzly run "npm test" --wait
50
+ vizzly run "pnpm test" --wait
50
51
  ```
51
52
 
52
- The `--wait` flag polls for results and exits with code 1 if visual differences are detected.
53
+ `--wait` blocks until Vizzly finishes processing the build. It exits with code
54
+ `1` when visual differences need review.
53
55
 
54
- For CI environments, set your project token:
56
+ In CI, use a project token:
55
57
 
56
58
  ```bash
57
59
  export VIZZLY_TOKEN=your-project-token
58
- vizzly run "npm test" --wait
60
+ vizzly run "pnpm test" --wait
59
61
  ```
60
62
 
61
- ### Visual Context For Agents
63
+ ### Inspect Builds And Diffs
62
64
 
63
- Use `vizzly context` when you want Vizzly to act like a visual context store, not just a test runner.
65
+ Use `vizzly context` when you need build, comparison, screenshot, or review
66
+ queue data from the terminal.
64
67
 
65
- This is especially useful for LLM agents, automation, and quick debugging loops. Instead of
66
- making a bunch of narrow API calls, you can ask for one build, comparison, screenshot, or review
67
- queue bundle and get the evidence in one place.
68
+ This is useful for scripts, coding agents, and debugging loops. Instead of
69
+ making a pile of narrow API calls, ask for one focused bundle and get the
70
+ evidence in one place.
68
71
 
69
72
  ```bash
70
73
  # Cloud context for a build or comparison
@@ -75,15 +78,19 @@ vizzly context comparison def456 --json
75
78
  vizzly context build current --source local
76
79
  vizzly context build current --source local --agent
77
80
  vizzly context screenshot build-detail-screenshots --source local --json
81
+ vizzly context review-queue --source local --json
78
82
  ```
79
83
 
80
- `--json` is the durable automation path. `--agent` gives a compact handoff for prompt assembly and local dogfooding. Add `--full` when an agent really needs the complete build context payload, or `--include screenshots,diffs,comments` when the compact JSON needs selected detail.
84
+ `--json` is the durable automation path. `--agent` gives a compact handoff for
85
+ prompt assembly. Add `--full` when you need the whole payload, or
86
+ `--include screenshots,diffs,comments` when compact JSON needs selected detail.
81
87
 
82
- Local context is read-only and file-backed. It reads your existing `.vizzly` workspace state from
83
- TDD runs, including screenshots, diffs, and any saved hotspot or region metadata.
88
+ Local context is read-only and file-backed. It reads your existing `.vizzly`
89
+ workspace state from TDD runs, including screenshots, diffs, and saved hotspot
90
+ or region metadata.
84
91
 
85
- Cloud context is also read-only right now. That is intentional. It keeps the trust model simple:
86
- Vizzly helps you see and inspect visual changes, while people still decide what gets approved.
92
+ Cloud context is also read-only right now. That is intentional. Vizzly helps you
93
+ see and inspect visual changes, while people still decide what gets approved.
87
94
 
88
95
  ## Capture Screenshots
89
96
 
@@ -103,7 +110,14 @@ test('homepage looks correct', async ({ page }) => {
103
110
  });
104
111
  ```
105
112
 
106
- The client SDK is lightweight—it just POSTs to the local server. Works with any test runner. SDKs available for [JavaScript](https://docs.vizzly.dev/integration/sdk/javascript), [Ruby](https://docs.vizzly.dev/integration/sdk/ruby), [Swift](https://docs.vizzly.dev/integration/sdk/swift), and [more](https://docs.vizzly.dev/integration/sdk/overview).
113
+ The client SDK is lightweight. It posts screenshots to the local Vizzly server
114
+ or the cloud build wrapper. It works with any test runner.
115
+
116
+ SDKs are available for
117
+ [JavaScript](https://docs.vizzly.dev/integration/sdk/javascript),
118
+ [Ruby](https://docs.vizzly.dev/integration/sdk/ruby),
119
+ [Swift](https://docs.vizzly.dev/integration/sdk/swift), and
120
+ [more](https://docs.vizzly.dev/integration/sdk/overview).
107
121
 
108
122
  Already saving screenshots to disk? Pass the file path instead:
109
123
 
@@ -131,31 +145,34 @@ Or create `vizzly.config.js` manually:
131
145
  ```javascript
132
146
  export default {
133
147
  comparison: {
134
- threshold: 2.0 // CIEDE2000 Delta E (0=exact, 2=recommended)
135
- }
148
+ // CIEDE2000 Delta E. 0 is exact. 2 is a good default.
149
+ threshold: 2.0,
150
+ },
136
151
  };
137
152
  ```
138
153
 
139
154
  ## Commands
140
155
 
141
- | Command | Description |
142
- |---------|-------------|
143
- | `vizzly tdd start` | Start local TDD server with dashboard |
144
- | `vizzly tdd run "cmd"` | Run tests once, generate static report |
145
- | `vizzly run "cmd"` | Run tests with cloud integration |
146
- | `vizzly context ...` | Fetch visual context bundles for builds, comparisons, and screenshots |
147
- | `vizzly upload <dir>` | Upload existing screenshots |
148
- | `vizzly login` | Authenticate via browser |
149
- | `vizzly doctor` | Validate local setup |
156
+ | Command | What it does |
157
+ | --- | --- |
158
+ | `vizzly tdd start` | Start the local TDD server and dashboard. |
159
+ | `vizzly tdd run "cmd"` | Run tests once and generate a static visual report. |
160
+ | `vizzly run "cmd"` | Run tests with cloud build and review integration. |
161
+ | `vizzly context ...` | Fetch visual context for builds, comparisons, screenshots, and review queues. |
162
+ | `vizzly upload <dir>` | Upload an existing folder of screenshots. |
163
+ | `vizzly login` | Authenticate through the browser. |
164
+ | `vizzly doctor` | Validate your local setup. |
150
165
 
151
166
  ## Documentation
152
167
 
153
- Full documentation at **[docs.vizzly.dev](https://docs.vizzly.dev)**—including framework guides,
154
- CI/CD setup, configuration reference, and more.
168
+ Full documentation lives at **[docs.vizzly.dev](https://docs.vizzly.dev)**.
169
+ Start there for framework guides, CI setup, SDK examples, and the configuration
170
+ reference.
155
171
 
156
172
  ## Contributing
157
173
 
158
- Found a bug? Have an idea? [Open an issue](https://github.com/vizzly-testing/cli/issues) or submit a PR.
174
+ Found a bug or have an idea?
175
+ [Open an issue](https://github.com/vizzly-testing/cli/issues) or send a PR.
159
176
 
160
177
  ## License
161
178
 
package/dist/cli.js CHANGED
@@ -36,7 +36,7 @@ import { saveUserPath } from './utils/global-config.js';
36
36
  import * as output from './utils/output.js';
37
37
  import { getPackageVersion } from './utils/package-info.js';
38
38
 
39
- // Custom help formatting with Observatory design system
39
+ // Custom help formatting with BearDen design system
40
40
  const formatHelp = (cmd, helper) => {
41
41
  let c = colors;
42
42
  let lines = [];
@@ -49,7 +49,7 @@ const formatHelp = (cmd, helper) => {
49
49
  // Cute grizzly bear mascot with square eyes (like the Vizzly logo!)
50
50
  lines.push(c.brand.amber(' ʕ□ᴥ□ʔ'));
51
51
  lines.push(` ${c.brand.amber(c.bold('vizzly'))} ${c.dim(`v${version}`)}`);
52
- lines.push(` ${c.gray('Visual review for agents and teams')}`);
52
+ lines.push(` ${c.gray('Visual regression testing from your terminal')}`);
53
53
  } else {
54
54
  // Compact header for subcommands
55
55
  lines.push(` ${c.brand.amber(c.bold('vizzly'))} ${c.white(cmd.name())}`);
@@ -177,7 +177,7 @@ const formatHelp = (cmd, helper) => {
177
177
  lines.push(` ${c.gray('$')} ${c.white('vizzly tdd start')}`);
178
178
  lines.push('');
179
179
  lines.push(` ${c.dim('# CI pipeline')}`);
180
- lines.push(` ${c.gray('$')} ${c.white('vizzly run "npm test" --wait')}`);
180
+ lines.push(` ${c.gray('$')} ${c.white('vizzly run "pnpm test" --wait')}`);
181
181
  lines.push('');
182
182
  }
183
183
 
@@ -252,7 +252,7 @@ function normalizeJsonArgv(argv, commandNames) {
252
252
  }
253
253
  return normalizedArgv;
254
254
  }
255
- program.name('vizzly').description('Vizzly CLI for visual review').version(getPackageVersion()).option('-c, --config <path>', 'Config file path').option('--token <token>', 'Vizzly API token').option('-v, --verbose', 'Verbose output (shorthand for --log-level debug)').option('--log-level <level>', 'Log level: debug, info, warn, error (default: info, or VIZZLY_LOG_LEVEL env var)').option('--json [fields]', 'JSON output, optionally specify fields (e.g., --json id,status,branch)').option('--color', 'Force colored output (even in non-TTY)').option('--no-color', 'Disable colored output').option('--strict', 'Fail on any error (default: be resilient, warn on non-critical issues)').configureHelp({
255
+ program.name('vizzly').description('Vizzly CLI for visual regression testing').version(getPackageVersion()).option('-c, --config <path>', 'Config file path').option('--token <token>', 'Vizzly API token').option('-v, --verbose', 'Verbose output (shorthand for --log-level debug)').option('--log-level <level>', 'Log level: debug, info, warn, error (default: info, or VIZZLY_LOG_LEVEL env var)').option('--json [fields]', 'JSON output, optionally specify fields (e.g., --json id,status,branch)').option('--color', 'Force colored output (even in non-TTY)').option('--no-color', 'Disable colored output').option('--strict', 'Fail on any error (default: be resilient, warn on non-critical issues)').configureHelp({
256
256
  formatHelp
257
257
  });
258
258
 
@@ -411,7 +411,7 @@ tddCmd.command('run <command>').description('Run tests once in TDD mode with loc
411
411
  const reportResult = await generateStaticReport(process.cwd());
412
412
  if (reportResult.success) {
413
413
  const reportUrl = getReportFileUrl(reportResult.reportPath);
414
- output.print(` ${colors.brand.textTertiary('→')} Report: ${colors.blue(reportUrl)}`);
414
+ output.print(` ${colors.brand.textTertiary('→')} Report: ${colors.info(reportUrl)}`);
415
415
  output.blank();
416
416
 
417
417
  // Open report in browser unless --no-open
@@ -512,8 +512,8 @@ Examples:
512
512
  }
513
513
  await comparisonsCommand(options, globalOptions);
514
514
  });
515
- let contextCmd = program.command('context').description('Fetch build context for agents and reviewers');
516
- contextCmd.command('build').description('Fetch build context for agents and reviewers').argument('<build-id>', 'Build ID to fetch context for').option('--source <source>', 'Context source: auto, cloud, or local', 'auto').option('--agent', 'Output compact context for LLM agents').option('--full', 'Return the full build context payload with --agent --json').option('--include <items>', 'Add detail to compact agent JSON: screenshots,diffs,comments').addHelpText('after', `
515
+ let contextCmd = program.command('context').description('Fetch build and diff context');
516
+ contextCmd.command('build').description('Fetch build context').argument('<build-id>', 'Build ID to fetch context for').option('--source <source>', 'Context source: auto, cloud, or local', 'auto').option('--agent', 'Output compact context for LLM agents').option('--full', 'Return the full build context payload with --agent --json').option('--include <items>', 'Add detail to compact agent JSON: screenshots,diffs,comments').addHelpText('after', `
517
517
  Examples:
518
518
  $ vizzly context build abc123
519
519
  $ vizzly context build current --source local
@@ -890,7 +890,7 @@ program.command('whoami').description('Show current authentication status and us
890
890
  });
891
891
 
892
892
  // Save user's PATH for menubar app (non-blocking, runs in background)
893
- // This auto-configures the menubar app so it can find npx/node
893
+ // This auto-configures the menubar app so it can find package runners/node
894
894
  saveUserPath().catch(() => {});
895
895
  let commandNames = new Set(program.commands.map(command => command.name()));
896
896
  let normalizedArgv = normalizeJsonArgv(process.argv, commandNames);
@@ -192,6 +192,7 @@ function createSimpleClient(serverUrl) {
192
192
  let isFilePath = typeof imageBuffer === 'string';
193
193
  let image = isFilePath ? imageBuffer : imageBuffer.toString('base64');
194
194
  let type = isFilePath ? 'file-path' : 'base64';
195
+ let requestTimeout = options.requestTimeout || DEFAULT_TIMEOUT_MS;
195
196
  let properties = createScreenshotProperties(options);
196
197
  let httpStart = Date.now();
197
198
  let {
@@ -203,7 +204,7 @@ function createSimpleClient(serverUrl) {
203
204
  image,
204
205
  type,
205
206
  properties
206
- }, DEFAULT_TIMEOUT_MS);
207
+ }, requestTimeout);
207
208
  let httpMs = Date.now() - httpStart;
208
209
  if (shouldLogClient('debug')) {
209
210
  console.debug(`[vizzly-client] ${name} HTTP completed in ${httpMs}ms`);
@@ -234,11 +234,11 @@ export async function doctorCommand(options = {}, globalOptions = {}, deps = {})
234
234
  output.printErr(` ${colors.dim('─'.repeat(52))}`);
235
235
  for (let item of contextItems) {
236
236
  if (item.type === 'success') {
237
- output.printErr(` ${colors.green('✓')} ${colors.gray(item.label)} ${colors.white(item.value)}`);
237
+ output.printErr(` ${colors.success('✓')} ${colors.brand.textMuted(item.label)} ${colors.white(item.value)}`);
238
238
  } else if (item.type === 'warning') {
239
- output.printErr(` ${colors.yellow('!')} ${colors.gray(item.label)} ${colors.yellow(item.value)}`);
239
+ output.printErr(` ${colors.yellow('!')} ${colors.brand.textMuted(item.label)} ${colors.yellow(item.value)}`);
240
240
  } else {
241
- output.printErr(` ${colors.dim('○')} ${colors.gray(item.label)} ${colors.dim(item.value)}`);
241
+ output.printErr(` ${colors.dim('○')} ${colors.brand.textMuted(item.label)} ${colors.dim(item.value)}`);
242
242
  }
243
243
  }
244
244
  }
@@ -246,7 +246,7 @@ export async function doctorCommand(options = {}, globalOptions = {}, deps = {})
246
246
  // Footer with links
247
247
  output.printErr('');
248
248
  output.printErr(` ${colors.dim('─'.repeat(52))}`);
249
- output.printErr(` ${colors.dim('Docs')} ${colors.cyan(colors.underline('docs.vizzly.dev'))} ${colors.dim('GitHub')} ${colors.cyan(colors.underline('github.com/vizzly-testing/cli'))}`);
249
+ output.printErr(` ${colors.dim('Docs')} ${colors.info(colors.underline('docs.vizzly.dev'))} ${colors.dim('GitHub')} ${colors.info(colors.underline('github.com/vizzly-testing/cli'))}`);
250
250
 
251
251
  // Emit structured data in verbose mode (in addition to visual output)
252
252
  if (globalOptions.verbose) {
@@ -160,7 +160,7 @@ function writeHumanCreatedOutput(output, plugins) {
160
160
  function showNextSteps(output) {
161
161
  output.blank();
162
162
  output.labelValue('Next steps', '');
163
- output.list(['Set your API token: export VIZZLY_TOKEN="your-api-key"', 'Run your tests with Vizzly: npx vizzly run "npm test"', 'Upload screenshots: npx vizzly upload ./screenshots']);
163
+ output.list(['Set your API token: export VIZZLY_TOKEN="your-api-key"', 'Run your tests with Vizzly: pnpm exec vizzly run "pnpm test"', 'Upload screenshots: pnpm exec vizzly upload ./screenshots']);
164
164
  }
165
165
  async function writeConfigFile({
166
166
  configPath,
@@ -335,7 +335,7 @@ export async function previewCommand(path, options = {}, globalOptions = {}, dep
335
335
  output.blank();
336
336
  output.print(' Run visual tests first, then upload your preview:');
337
337
  output.blank();
338
- output.print(' vizzly run "npm test"');
338
+ output.print(' vizzly run "pnpm test"');
339
339
  output.print(' vizzly preview ./dist');
340
340
  output.blank();
341
341
  output.print(' Or specify a build explicitly:');
@@ -591,13 +591,13 @@ export async function previewCommand(path, options = {}, globalOptions = {}, dep
591
591
  output.print(` ${colors.brand.textTertiary('Files')} ${colors.white(result.uploaded)} (${formatBytes(result.totalBytes)} compressed)`);
592
592
  if (result.reusedBlobs > 0) {
593
593
  let savedBytes = result.totalBytes - result.newBytes;
594
- output.print(` ${colors.brand.textTertiary('Deduped')} ${colors.green(result.reusedBlobs)} files (saved ${formatBytes(savedBytes)})`);
594
+ output.print(` ${colors.brand.textTertiary('Deduped')} ${colors.success(result.reusedBlobs)} files (saved ${formatBytes(savedBytes)})`);
595
595
  }
596
596
  if (result.basePath) {
597
597
  output.print(` ${colors.brand.textTertiary('Base path')} ${colors.dim(result.basePath)}`);
598
598
  }
599
599
  output.blank();
600
- output.print(` ${colors.brand.textTertiary('Preview')} ${colors.cyan(colors.underline(result.previewUrl))}`);
600
+ output.print(` ${colors.brand.textTertiary('Preview')} ${colors.info(colors.underline(result.previewUrl))}`);
601
601
 
602
602
  // Open in browser if requested
603
603
  if (options.open) {
@@ -348,7 +348,7 @@ export async function runCommand(testCommand, options = {}, globalOptions = {},
348
348
  getTokenContext
349
349
  });
350
350
  if (displayUrl) {
351
- output.print(` ${colors.brand.textTertiary('Results')} ${colors.cyan(colors.underline(displayUrl))}`);
351
+ output.print(` ${colors.brand.textTertiary('Results')} ${colors.info(colors.underline(displayUrl))}`);
352
352
  } else {
353
353
  output.print(` ${colors.brand.textTertiary('Build')} ${colors.dim(result.buildId)}`);
354
354
  }
@@ -435,7 +435,7 @@ export async function tddStartCommand(options = {}, globalOptions = {}) {
435
435
  if (globalOptions.verbose) {
436
436
  output.blank();
437
437
  output.print(` ${colors.brand.textTertiary('Next steps')}`);
438
- output.print(` ${colors.brand.textMuted('1.')} Run tests in watch mode ${colors.brand.textMuted('(npm test -- --watch)')}`);
438
+ output.print(` ${colors.brand.textMuted('1.')} Run tests in watch mode ${colors.brand.textMuted('(pnpm test -- --watch)')}`);
439
439
  output.print(` ${colors.brand.textMuted('2.')} Review visual changes in the dashboard`);
440
440
  output.print(` ${colors.brand.textMuted('3.')} Accept or reject baseline updates`);
441
441
  }