@vizzly-testing/cli 0.35.3-beta.0 → 0.35.3-beta.1

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
@@ -101,7 +101,7 @@ evidence in one place.
101
101
  ```bash
102
102
  # Cloud context for a build or comparison
103
103
  vizzly context build abc123 --source cloud
104
- vizzly context comparison def456 --source cloud --json
104
+ vizzly context comparison def456 --source cloud --agent --json
105
105
 
106
106
  # Local workspace context from .vizzly/
107
107
  vizzly context build current --source local
@@ -110,9 +110,12 @@ vizzly context screenshot build-detail-screenshots --source local --json
110
110
  vizzly context review-queue --source local --json
111
111
  ```
112
112
 
113
- `--json` is the durable automation path. `--agent` gives a compact handoff for
114
- prompt assembly. Add `--full` when you need the whole payload, or
115
- `--include screenshots,diffs,comments` when compact JSON needs selected detail.
113
+ `--json` is the durable automation path. `--agent` gives you the compact API
114
+ handoff for prompt assembly. Build handoffs contain up to 10 records; use the
115
+ exact next-page command returned in `suggested_commands` to continue safely.
116
+ That command carries the API's opaque `--cursor`. Add
117
+ `--include diffs` for raw diff diagnostics, or `--full` when you need the whole
118
+ payload.
116
119
 
117
120
  Local context is read-only and file-backed. It reads your existing `.vizzly`
118
121
  workspace state from TDD runs, including screenshots, diffs, and saved hotspot
@@ -18,6 +18,11 @@ function isProjectToken(token) {
18
18
  return typeof token === 'string' && token.startsWith('vzt_');
19
19
  }
20
20
 
21
+ /** Prefer the transport cause because Node's top-level fetch message is generic. */
22
+ function getNetworkFailureReason(error) {
23
+ return error?.cause?.code || error?.cause?.message || error?.message || 'request failed';
24
+ }
25
+
21
26
  /**
22
27
  * Create an API client with the given configuration
23
28
  *
@@ -58,10 +63,19 @@ export function createApiClient(options = {}) {
58
63
  contentType: fetchOptions.headers?.['Content-Type'],
59
64
  extra: fetchOptions.headers || {}
60
65
  });
61
- let response = await fetch(url, {
62
- ...fetchOptions,
63
- headers
64
- });
66
+ let response;
67
+ try {
68
+ response = await fetch(url, {
69
+ ...fetchOptions,
70
+ headers
71
+ });
72
+ } catch (error) {
73
+ let apiOrigin = new URL(url).origin;
74
+ let reason = getNetworkFailureReason(error);
75
+ throw new VizzlyError(`Unable to reach Vizzly at ${apiOrigin}: ${reason}`, 'NETWORK_ERROR', {
76
+ apiOrigin
77
+ });
78
+ }
65
79
  if (!response.ok) {
66
80
  let errorBody = await extractErrorBody(response);
67
81
 
@@ -82,7 +96,8 @@ export function createApiClient(options = {}) {
82
96
  // Other errors
83
97
  let error = parseApiError(response.status, errorBody, url);
84
98
  throw new VizzlyError(error.message, error.code, {
85
- status: error.status
99
+ status: error.status,
100
+ details: error.details
86
101
  });
87
102
  }
88
103
  return response.json();
package/dist/api/core.js CHANGED
@@ -232,8 +232,21 @@ export function shouldRetryWithRefresh(status, isRetry, hasRefreshToken) {
232
232
  */
233
233
  export function parseApiError(status, body, url) {
234
234
  let message = `API request failed: ${status}`;
235
+ let responseDetails = null;
236
+ let responseCode = null;
237
+ let responseMessage = body;
235
238
  if (body) {
236
- message += ` - ${body}`;
239
+ try {
240
+ let parsedBody = JSON.parse(body);
241
+ responseDetails = parsedBody.details ?? null;
242
+ responseCode = parsedBody.code ?? parsedBody.details?.code ?? null;
243
+ responseMessage = parsedBody.error ?? parsedBody.message ?? body;
244
+ } catch {
245
+ // Plain-text API errors remain valid and are surfaced unchanged.
246
+ }
247
+ }
248
+ if (responseMessage) {
249
+ message += ` - ${responseMessage}`;
237
250
  }
238
251
  message += ` (URL: ${url})`;
239
252
  let code = 'API_ERROR';
@@ -244,8 +257,9 @@ export function parseApiError(status, body, url) {
244
257
  if (status >= 500) code = 'SERVER_ERROR';
245
258
  return {
246
259
  message,
247
- code,
248
- status
260
+ code: responseCode || code,
261
+ status,
262
+ details: responseDetails
249
263
  };
250
264
  }
251
265
 
package/dist/cli.js CHANGED
@@ -3,17 +3,17 @@ import 'dotenv/config';
3
3
  import { existsSync, statSync } from 'node:fs';
4
4
  import { Option, program } from 'commander';
5
5
  import { apiCommand, validateApiOptions } from './commands/api.js';
6
- import { baselinesCommand, validateBaselinesOptions } from './commands/baselines.js';
6
+ import { baselinesCommand } from './commands/baselines.js';
7
7
  import { buildsCommand, validateBuildsOptions } from './commands/builds.js';
8
8
  import { comparisonsCommand, validateComparisonsOptions } from './commands/comparisons.js';
9
- import { configCommand, validateConfigOptions } from './commands/config-cmd.js';
9
+ import { configCommand } from './commands/config-cmd.js';
10
10
  import { contextBuildCommand, contextComparisonCommand, contextReviewQueueCommand, contextScreenshotCommand, contextSimilarCommand, validateContextBuildOptions, validateContextComparisonOptions, validateContextReviewQueueOptions, validateContextScreenshotOptions, validateContextSimilarOptions } from './commands/context.js';
11
- import { doctorCommand, validateDoctorOptions } from './commands/doctor.js';
11
+ import { doctorCommand } from './commands/doctor.js';
12
12
  import { finalizeCommand, validateFinalizeOptions } from './commands/finalize.js';
13
13
  import { init } from './commands/init.js';
14
- import { loginCommand, validateLoginOptions } from './commands/login.js';
15
- import { logoutCommand, validateLogoutOptions } from './commands/logout.js';
16
- import { orgsCommand, validateOrgsOptions } from './commands/orgs.js';
14
+ import { loginCommand } from './commands/login.js';
15
+ import { logoutCommand } from './commands/logout.js';
16
+ import { orgsCommand } from './commands/orgs.js';
17
17
  import { previewCommand, validatePreviewOptions } from './commands/preview.js';
18
18
  import { projectLinkCommand, validateProjectLinkOptions } from './commands/project.js';
19
19
  import { projectsCommand, validateProjectsOptions } from './commands/projects.js';
@@ -23,7 +23,7 @@ import { statusCommand, validateStatusOptions } from './commands/status.js';
23
23
  import { tddCommand, validateTddOptions } from './commands/tdd.js';
24
24
  import { runDaemonChild, tddListCommand, tddStartCommand, tddStatusCommand, tddStopCommand, validateTddStartOptions } from './commands/tdd-daemon.js';
25
25
  import { uploadCommand, validateUploadOptions } from './commands/upload.js';
26
- import { validateWhoamiOptions, whoamiCommand } from './commands/whoami.js';
26
+ import { whoamiCommand } from './commands/whoami.js';
27
27
  import { createPluginServices } from './plugin-api.js';
28
28
  import { loadPlugins } from './plugin-loader.js';
29
29
  import { createServices } from './services/index.js';
@@ -651,13 +651,13 @@ Examples:
651
651
  await comparisonsCommand(options, globalOptions);
652
652
  });
653
653
  let contextCmd = program.command('context').description('Fetch build and diff context').showHelpAfterError('(Run vizzly context --help for available commands)').showSuggestionAfterError();
654
- 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', `
654
+ 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 instead of compact context').option('--cursor <cursor>', 'Continue compact evidence from an opaque API cursor').option('--include <items>', 'Add raw diff diagnostics to compact context: diffs').addHelpText('after', `
655
655
  Examples:
656
656
  $ vizzly context build abc123 --source cloud
657
657
  $ vizzly context build current --source local
658
658
  $ vizzly context build current --source local --agent
659
659
  $ vizzly context build abc123 --source cloud --agent --json
660
- $ vizzly context build abc123 --source cloud --agent --json --include diffs,comments
660
+ $ vizzly context build abc123 --source cloud --agent --json --include diffs
661
661
  $ vizzly context build abc123 --source cloud --agent --json --full
662
662
  `).action(async (buildId, options) => {
663
663
  let globalOptions = getGlobalOptions();
@@ -667,12 +667,12 @@ Examples:
667
667
  }
668
668
  await contextBuildCommand(buildId, options, globalOptions);
669
669
  });
670
- contextCmd.command('comparison').description('Fetch a comparison context bundle').argument('<comparison-id>', 'Comparison ID to fetch context for').option('--source <source>', 'Context source: auto, cloud, or local', 'auto').option('--similar-limit <n>', 'Maximum similar fingerprint matches to return (1-50)', Number).option('--recent-limit <n>', 'Maximum recent same-name comparisons to return (1-50)', Number).option('--window-size <n>', 'Historical hotspot analysis window size (1-50)', Number).addHelpText('after', `
670
+ contextCmd.command('comparison').description('Fetch a comparison context bundle').argument('<comparison-id>', 'Comparison 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 comparison context instead of compact context').option('--cursor <cursor>', 'Continue compact history from an opaque API cursor').option('--include <items>', 'Add detail to compact context: diffs').addHelpText('after', `
671
671
  Examples:
672
672
  $ vizzly context comparison def456 --source cloud
673
673
  $ vizzly context comparison def456 --source local
674
- $ vizzly context comparison def456 --source cloud --similar-limit 5 --recent-limit 5
675
674
  $ vizzly context comparison def456 --source cloud --json
675
+ $ vizzly context comparison def456 --source cloud --agent --json
676
676
  `).action(async (comparisonId, options) => {
677
677
  let globalOptions = getGlobalOptions();
678
678
  const validationErrors = validateContextComparisonOptions(options);
@@ -724,12 +724,6 @@ Examples:
724
724
  });
725
725
  program.command('config').description('Display current configuration').argument('[key]', 'Specific config key to get (dot notation, e.g., comparison.threshold)').action(async (key, options) => {
726
726
  let globalOptions = getGlobalOptions();
727
-
728
- // Validate options
729
- const validationErrors = validateConfigOptions(options);
730
- if (validationErrors.length > 0) {
731
- reportValidationErrors(validationErrors);
732
- }
733
727
  await configCommand(key, options, globalOptions);
734
728
  });
735
729
  program.command('baselines').description('List and query local TDD baselines').option('--name <pattern>', 'Filter baselines by name (supports wildcards)').option('--info <name>', 'Get detailed info for a specific baseline').addHelpText('after', `
@@ -742,12 +736,6 @@ Examples:
742
736
  Note: Baselines are stored locally in .vizzly/baselines/ during TDD mode.
743
737
  `).action(async options => {
744
738
  let globalOptions = getGlobalOptions();
745
-
746
- // Validate options
747
- const validationErrors = validateBaselinesOptions(options);
748
- if (validationErrors.length > 0) {
749
- reportValidationErrors(validationErrors);
750
- }
751
739
  await baselinesCommand(options, globalOptions);
752
740
  });
753
741
  program.command('api').description('Make raw API requests (for power users)').argument('<endpoint>', 'API endpoint (e.g., /api/sdk/builds)').option('-X, --method <method>', 'HTTP method (GET or POST for approve/reject/comment)', 'GET').option('-d, --data <json>', 'Request body (JSON)').option('-H, --header <header>', 'Add header (key:value), can be repeated', (val, prev) => prev ? [...prev, val] : [val]).option('-q, --query <param>', 'Add query param (key=value), can be repeated', (val, prev) => prev ? [...prev, val] : [val]).addHelpText('after', `
@@ -833,10 +821,6 @@ Note: Shows organizations from your user account (via vizzly login)
833
821
  or the single organization for a project token.
834
822
  `).action(async options => {
835
823
  let globalOptions = getGlobalOptions();
836
- const validationErrors = validateOrgsOptions(options);
837
- if (validationErrors.length > 0) {
838
- reportValidationErrors(validationErrors);
839
- }
840
824
  await orgsCommand(options, globalOptions);
841
825
  });
842
826
  program.command('projects').description('List projects you have access to').option('--org <slug>', 'Filter by organization slug').option('--limit <n>', 'Maximum results to return (1-250)', Number, 50).option('--offset <n>', 'Skip first N results', Number, 0).addHelpText('after', `
@@ -910,42 +894,18 @@ program.command('preview').description('Upload static files as a preview for a b
910
894
  });
911
895
  program.command('doctor').description('Run diagnostics to check your environment and configuration').option('--api', 'Include API connectivity checks').action(async options => {
912
896
  let globalOptions = getGlobalOptions();
913
-
914
- // Validate options
915
- const validationErrors = validateDoctorOptions(options);
916
- if (validationErrors.length > 0) {
917
- reportValidationErrors(validationErrors);
918
- }
919
897
  await doctorCommand(options, globalOptions);
920
898
  });
921
899
  program.command('login').description('Authenticate with your Vizzly account').option('--api-url <url>', 'API URL override').action(async options => {
922
900
  let globalOptions = getGlobalOptions();
923
-
924
- // Validate options
925
- const validationErrors = validateLoginOptions(options);
926
- if (validationErrors.length > 0) {
927
- reportValidationErrors(validationErrors);
928
- }
929
901
  await loginCommand(options, globalOptions);
930
902
  });
931
903
  program.command('logout').description('Clear stored authentication tokens').option('--api-url <url>', 'API URL override').action(async options => {
932
904
  let globalOptions = getGlobalOptions();
933
-
934
- // Validate options
935
- const validationErrors = validateLogoutOptions(options);
936
- if (validationErrors.length > 0) {
937
- reportValidationErrors(validationErrors);
938
- }
939
905
  await logoutCommand(options, globalOptions);
940
906
  });
941
907
  program.command('whoami').description('Show current authentication status and user information').option('--api-url <url>', 'API URL override').action(async options => {
942
908
  let globalOptions = getGlobalOptions();
943
-
944
- // Validate options
945
- const validationErrors = validateWhoamiOptions(options);
946
- if (validationErrors.length > 0) {
947
- reportValidationErrors(validationErrors);
948
- }
949
909
  await whoamiCommand(options, globalOptions);
950
910
  });
951
911
 
@@ -286,11 +286,4 @@ export function formatBytes(bytes) {
286
286
  if (bytes < 1024) return `${bytes} B`;
287
287
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
288
288
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
289
- }
290
-
291
- /**
292
- * Validate baselines command options
293
- */
294
- export function validateBaselinesOptions() {
295
- return [];
296
289
  }
@@ -163,11 +163,4 @@ function getNestedValue(obj, path) {
163
163
  current = current[part];
164
164
  }
165
165
  return current;
166
- }
167
-
168
- /**
169
- * Validate config command options
170
- */
171
- export function validateConfigOptions() {
172
- return [];
173
166
  }