@vizzly-testing/cli 0.36.0 → 0.37.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 +23 -17
- package/dist/api/client.js +14 -2
- package/dist/cli.js +40 -15
- package/dist/commands/api.js +72 -25
- package/dist/commands/init.js +4 -2
- package/dist/commands/run.js +5 -5
- package/dist/commands/status.js +2 -2
- package/dist/commands/tdd.js +1 -1
- package/dist/reporter/reporter-bundle.iife.js +38 -38
- package/package.json +8 -7
- package/skills/vizzly/SKILL.md +17 -9
- package/skills/vizzly/references/cli-context.md +49 -15
- package/skills/vizzly/references/setup-ci.md +2 -2
package/README.md
CHANGED
|
@@ -62,7 +62,7 @@ For a one-off local check, wrap the test command with `tdd run`:
|
|
|
62
62
|
|
|
63
63
|
```bash
|
|
64
64
|
vizzly tdd run "pnpm test" --no-open
|
|
65
|
-
vizzly context build current --source local --
|
|
65
|
+
vizzly context build current --source local --json
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
That run writes review data under `.vizzly/` and prints a context command you
|
|
@@ -96,38 +96,44 @@ vizzly run "pnpm test" --wait
|
|
|
96
96
|
|
|
97
97
|
### Inspect Builds And Diffs
|
|
98
98
|
|
|
99
|
-
Use `vizzly context`
|
|
100
|
-
|
|
99
|
+
Use `vizzly context` for human summaries and local `.vizzly` evidence. For cloud
|
|
100
|
+
reviews, agents should discover and call the public API through its schema:
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
```bash
|
|
103
|
+
vizzly api schema --json
|
|
104
|
+
vizzly api schema sdk.listBuilds --json
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The `context build --agent` and `context comparison --agent` formats are
|
|
108
|
+
deprecated and scheduled for removal in v0.38.0. Use the schema-driven API for
|
|
109
|
+
cloud evidence; use `vizzly context ... --json` for local evidence.
|
|
105
110
|
|
|
106
111
|
```bash
|
|
107
|
-
#
|
|
112
|
+
# Human-readable cloud build context
|
|
108
113
|
vizzly context build abc123 --source cloud
|
|
109
|
-
|
|
114
|
+
|
|
115
|
+
# Discover cloud evidence operations for an agent workflow
|
|
116
|
+
vizzly api schema sdk.getBuildContext --json
|
|
117
|
+
vizzly api schema getComparisonContext --json
|
|
110
118
|
|
|
111
119
|
# Local workspace context from .vizzly/
|
|
112
120
|
vizzly context build current --source local
|
|
113
|
-
vizzly context build current --source local --
|
|
121
|
+
vizzly context build current --source local --json
|
|
114
122
|
vizzly context screenshot build-detail-screenshots --source local --json
|
|
115
123
|
vizzly context review-queue --source local --json
|
|
116
124
|
```
|
|
117
125
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
`--include diffs` for raw diff diagnostics, or `--full` when you need the whole
|
|
123
|
-
payload.
|
|
126
|
+
Use `vizzly api schema` to inspect cloud operation inputs, output fields, and
|
|
127
|
+
pagination before making requests. The old compact `--agent` handoff remains
|
|
128
|
+
available during deprecation, but new cloud workflows should use the schema.
|
|
129
|
+
Use `--json` for machine-readable local context.
|
|
124
130
|
|
|
125
131
|
Local context is read-only and file-backed. It reads your existing `.vizzly`
|
|
126
132
|
workspace state from TDD runs, including screenshots, diffs, and saved hotspot
|
|
127
133
|
or region metadata.
|
|
128
134
|
|
|
129
|
-
|
|
130
|
-
|
|
135
|
+
The `context` commands are read-only. The schema-discovered review API also
|
|
136
|
+
supports explicit decisions when a task asks for them.
|
|
131
137
|
|
|
132
138
|
## Capture Screenshots
|
|
133
139
|
|
package/dist/api/client.js
CHANGED
|
@@ -56,6 +56,11 @@ export function createApiClient(options = {}) {
|
|
|
56
56
|
* @returns {Promise<Object>} Parsed JSON response
|
|
57
57
|
*/
|
|
58
58
|
async function request(endpoint, fetchOptions = {}, isRetry = false) {
|
|
59
|
+
let {
|
|
60
|
+
responseType = 'json',
|
|
61
|
+
retryAuthentication = true,
|
|
62
|
+
...httpOptions
|
|
63
|
+
} = fetchOptions;
|
|
59
64
|
let url = buildApiUrl(baseUrl, endpoint);
|
|
60
65
|
let headers = buildRequestHeaders({
|
|
61
66
|
token,
|
|
@@ -66,7 +71,7 @@ export function createApiClient(options = {}) {
|
|
|
66
71
|
let response;
|
|
67
72
|
try {
|
|
68
73
|
response = await fetch(url, {
|
|
69
|
-
...
|
|
74
|
+
...httpOptions,
|
|
70
75
|
headers
|
|
71
76
|
});
|
|
72
77
|
} catch (error) {
|
|
@@ -80,7 +85,7 @@ export function createApiClient(options = {}) {
|
|
|
80
85
|
let errorBody = await extractErrorBody(response);
|
|
81
86
|
|
|
82
87
|
// Handle 401 with token refresh
|
|
83
|
-
if (shouldRetryWithRefresh(response.status, isRetry, !isProjectToken(token) && (await hasRefreshToken()))) {
|
|
88
|
+
if (retryAuthentication && shouldRetryWithRefresh(response.status, isRetry, !isProjectToken(token) && (await hasRefreshToken()))) {
|
|
84
89
|
let refreshed = await attemptTokenRefresh();
|
|
85
90
|
if (refreshed) {
|
|
86
91
|
token = refreshed;
|
|
@@ -100,6 +105,12 @@ export function createApiClient(options = {}) {
|
|
|
100
105
|
details: error.details
|
|
101
106
|
});
|
|
102
107
|
}
|
|
108
|
+
if (responseType === 'response') return response;
|
|
109
|
+
if (response.status === 204 || httpOptions.method === 'HEAD') return null;
|
|
110
|
+
let contentType = response.headers?.get?.('content-type');
|
|
111
|
+
if (contentType && !/application\/(?:[\w.+-]+\+)?json\b/i.test(contentType)) {
|
|
112
|
+
throw new VizzlyError('This response contains file data. Use api --output <file> to download it.', 'BINARY_RESPONSE');
|
|
113
|
+
}
|
|
103
114
|
return response.json();
|
|
104
115
|
}
|
|
105
116
|
|
|
@@ -122,6 +133,7 @@ export function createApiClient(options = {}) {
|
|
|
122
133
|
let refreshUrl = buildApiUrl(baseUrl, '/api/auth/cli/refresh');
|
|
123
134
|
let response = await fetch(refreshUrl, {
|
|
124
135
|
method: 'POST',
|
|
136
|
+
redirect: 'error',
|
|
125
137
|
headers: {
|
|
126
138
|
'Content-Type': 'application/json',
|
|
127
139
|
'User-Agent': userAgent
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import 'dotenv/config';
|
|
3
3
|
import { existsSync, statSync } from 'node:fs';
|
|
4
4
|
import { Option, program } from 'commander';
|
|
5
|
-
import { apiCommand, validateApiOptions } from './commands/api.js';
|
|
5
|
+
import { apiCommand, apiSchemaCommand, validateApiOptions } from './commands/api.js';
|
|
6
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';
|
|
@@ -357,6 +357,10 @@ function getGlobalOptions() {
|
|
|
357
357
|
noColor: options.noColor || options.color === false
|
|
358
358
|
};
|
|
359
359
|
}
|
|
360
|
+
function warnDeprecatedContextAgentOption() {
|
|
361
|
+
let warning = ['`vizzly context --agent` is deprecated and will be removed in v0.38.0.', 'For cloud reviews, use `vizzly api schema --json` and follow the', 'discovered operations. For local evidence, use', '`vizzly context ... --json`.'].join(' ');
|
|
362
|
+
output.warn(warning);
|
|
363
|
+
}
|
|
360
364
|
function reportValidationErrors(errors) {
|
|
361
365
|
if (output.isJson()) {
|
|
362
366
|
output.error('Validation errors', null, {
|
|
@@ -651,30 +655,38 @@ Examples:
|
|
|
651
655
|
await comparisonsCommand(options, globalOptions);
|
|
652
656
|
});
|
|
653
657
|
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', '
|
|
658
|
+
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', 'Deprecated; use API schema for cloud review.').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
659
|
Examples:
|
|
656
660
|
$ vizzly context build abc123 --source cloud
|
|
657
661
|
$ vizzly context build current --source local
|
|
658
|
-
$ vizzly
|
|
659
|
-
$ vizzly context build
|
|
660
|
-
|
|
661
|
-
|
|
662
|
+
$ vizzly api schema --json
|
|
663
|
+
$ vizzly context build current --source local --json
|
|
664
|
+
|
|
665
|
+
Deprecation:
|
|
666
|
+
--agent will be removed in v0.38.0. Use the API schema for cloud reviews;
|
|
667
|
+
use --json without --agent for local evidence.
|
|
662
668
|
`).action(async (buildId, options) => {
|
|
663
669
|
let globalOptions = getGlobalOptions();
|
|
670
|
+
if (options.agent) warnDeprecatedContextAgentOption();
|
|
664
671
|
const validationErrors = validateContextBuildOptions(options);
|
|
665
672
|
if (validationErrors.length > 0) {
|
|
666
673
|
reportValidationErrors(validationErrors);
|
|
667
674
|
}
|
|
668
675
|
await contextBuildCommand(buildId, options, globalOptions);
|
|
669
676
|
});
|
|
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', '
|
|
677
|
+
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', 'Deprecated; use API schema for cloud review.').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
678
|
Examples:
|
|
672
679
|
$ vizzly context comparison def456 --source cloud
|
|
673
680
|
$ vizzly context comparison def456 --source local
|
|
674
681
|
$ vizzly context comparison def456 --source cloud --json
|
|
675
|
-
$ vizzly
|
|
682
|
+
$ vizzly api schema getComparisonContext --json
|
|
683
|
+
|
|
684
|
+
Deprecation:
|
|
685
|
+
--agent will be removed in v0.38.0. Use the API schema for cloud reviews;
|
|
686
|
+
use --json without --agent for local evidence.
|
|
676
687
|
`).action(async (comparisonId, options) => {
|
|
677
688
|
let globalOptions = getGlobalOptions();
|
|
689
|
+
if (options.agent) warnDeprecatedContextAgentOption();
|
|
678
690
|
const validationErrors = validateContextComparisonOptions(options);
|
|
679
691
|
if (validationErrors.length > 0) {
|
|
680
692
|
reportValidationErrors(validationErrors);
|
|
@@ -738,7 +750,7 @@ Note: Baselines are stored locally in .vizzly/baselines/ during TDD mode.
|
|
|
738
750
|
let globalOptions = getGlobalOptions();
|
|
739
751
|
await baselinesCommand(options, globalOptions);
|
|
740
752
|
});
|
|
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
|
|
753
|
+
let api = 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; discover supported requests with api schema', 'GET').option('-d, --data <json>', 'JSON body, @file, or @- for stdin').option('-o, --output <file>', 'Write response bytes to a new file').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', `
|
|
742
754
|
Examples:
|
|
743
755
|
$ vizzly api /api/sdk/builds # List builds
|
|
744
756
|
$ vizzly api /api/sdk/builds -q limit=5 # With query params
|
|
@@ -746,8 +758,10 @@ Examples:
|
|
|
746
758
|
$ vizzly api /api/sdk/builds/abc123/comments -X POST -d '{"content":"Looks good"}'
|
|
747
759
|
$ vizzly api /api/sdk/builds/abc123/comments -X POST -d '{"content":"Nice!"}'
|
|
748
760
|
|
|
749
|
-
|
|
750
|
-
|
|
761
|
+
Discover operations: vizzly api schema, then vizzly api schema <operation-id>.
|
|
762
|
+
Use the method, version header, parameters and body described there.
|
|
763
|
+
JSON output places the API payload under data.response. Image downloads use --output.
|
|
764
|
+
Writes are never automatically retried after authentication failures.
|
|
751
765
|
`).action(async (endpoint, options) => {
|
|
752
766
|
let globalOptions = getGlobalOptions();
|
|
753
767
|
|
|
@@ -758,6 +772,17 @@ Most operations have dedicated commands (builds, comparisons, approve, etc.).
|
|
|
758
772
|
}
|
|
759
773
|
await apiCommand(endpoint, options, globalOptions);
|
|
760
774
|
});
|
|
775
|
+
api.command('schema [operation-id]').description('Discover supported API operations and their request/response schemas').option('--full', 'Download full OpenAPI (requires --output)').option('-q, --query <param>', 'Schema view=request, fields, response, or full', (value, previous) => [...(previous || []), value]).option('-o, --output <file>', 'Write the schema to a new file').action(async (operationId, options) => {
|
|
776
|
+
options = {
|
|
777
|
+
...api.opts(),
|
|
778
|
+
...options
|
|
779
|
+
};
|
|
780
|
+
if (options.full && !options.output) {
|
|
781
|
+
reportValidationErrors(['--full requires --output to avoid dumping the entire schema.']);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
await apiSchemaCommand(operationId, options, getGlobalOptions());
|
|
785
|
+
});
|
|
761
786
|
program.command('approve').description('Approve a comparison').argument('<comparison-id>', 'Comparison ID to approve').option('-m, --comment <message>', 'Optional comment explaining the approval').addHelpText('after', `
|
|
762
787
|
Examples:
|
|
763
788
|
$ vizzly approve abc123-def456-7890 # Approve a comparison
|
|
@@ -909,11 +934,11 @@ program.command('whoami').description('Show current authentication status and us
|
|
|
909
934
|
await whoamiCommand(options, globalOptions);
|
|
910
935
|
});
|
|
911
936
|
|
|
912
|
-
// Save
|
|
913
|
-
//
|
|
914
|
-
saveUserPath().catch(() => {});
|
|
937
|
+
// Save PATH for the menubar app before commands read or update credentials
|
|
938
|
+
// in the same config file.
|
|
939
|
+
await saveUserPath().catch(() => {});
|
|
915
940
|
let commandNames = new Set(program.commands.map(command => command.name()));
|
|
916
|
-
let nestedCommandNames = new Map(program.commands.map(command => [command.name(), new Set(command.commands.map(subcommand => subcommand.name()))]));
|
|
941
|
+
let nestedCommandNames = new Map(program.commands.map(command => [command.name(), new Set(command.registeredArguments.length ? [] : command.commands.map(subcommand => subcommand.name()))]));
|
|
917
942
|
let normalizedArgv = normalizeJsonArgv(process.argv, commandNames);
|
|
918
943
|
let normalizedGlobals = extractGlobalOptionsFromArgv(normalizedArgv, commandNames);
|
|
919
944
|
output.configure({
|
package/dist/commands/api.js
CHANGED
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
* API command - raw API access for power users
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { open, readFile, unlink } from 'node:fs/promises';
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
import { Readable } from 'node:stream';
|
|
8
|
+
import { pipeline } from 'node:stream/promises';
|
|
5
9
|
import { createApiClient as defaultCreateApiClient } from '../api/index.js';
|
|
6
10
|
import { loadConfig as defaultLoadConfig } from '../utils/config-loader.js';
|
|
7
11
|
import * as defaultOutput from '../utils/output.js';
|
|
8
|
-
let
|
|
12
|
+
let API_METHODS = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
9
13
|
function createApiCommandDeps(deps = {}) {
|
|
10
14
|
return {
|
|
11
15
|
loadConfig: deps.loadConfig || defaultLoadConfig,
|
|
@@ -31,9 +35,6 @@ export function normalizeApiEndpoint(endpoint) {
|
|
|
31
35
|
export function normalizeApiMethod(method = 'GET') {
|
|
32
36
|
return method.toUpperCase();
|
|
33
37
|
}
|
|
34
|
-
export function isAllowedPostEndpoint(endpoint) {
|
|
35
|
-
return ALLOWED_POST_ENDPOINTS.some(pattern => pattern.test(endpoint));
|
|
36
|
-
}
|
|
37
38
|
export function parseApiHeaders(headerOption) {
|
|
38
39
|
let headers = {};
|
|
39
40
|
let headerList = Array.isArray(headerOption) ? headerOption : [headerOption];
|
|
@@ -69,15 +70,14 @@ export function validateApiRequest({
|
|
|
69
70
|
hasData = false
|
|
70
71
|
}) {
|
|
71
72
|
let errors = [];
|
|
72
|
-
if (method
|
|
73
|
-
errors.push(`
|
|
74
|
-
return errors;
|
|
73
|
+
if (!API_METHODS.includes(method)) {
|
|
74
|
+
errors.push(`Unsupported HTTP method: ${method}`);
|
|
75
75
|
}
|
|
76
|
-
if (
|
|
77
|
-
errors.push(
|
|
76
|
+
if (hasData && ['GET', 'HEAD'].includes(method)) {
|
|
77
|
+
errors.push('Request data requires a method other than GET or HEAD.');
|
|
78
78
|
}
|
|
79
|
-
if (
|
|
80
|
-
errors.push('
|
|
79
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(endpoint) || endpoint.startsWith('//')) {
|
|
80
|
+
errors.push('Use an API path on the configured Vizzly server.');
|
|
81
81
|
}
|
|
82
82
|
return errors;
|
|
83
83
|
}
|
|
@@ -88,7 +88,7 @@ export function buildApiRequest({
|
|
|
88
88
|
let normalizedEndpoint = normalizeApiEndpoint(endpoint);
|
|
89
89
|
let method = normalizeApiMethod(options.method || 'GET');
|
|
90
90
|
let errors = validateApiRequest({
|
|
91
|
-
endpoint
|
|
91
|
+
endpoint,
|
|
92
92
|
method,
|
|
93
93
|
hasData: options.data !== undefined
|
|
94
94
|
});
|
|
@@ -104,7 +104,7 @@ export function buildApiRequest({
|
|
|
104
104
|
let requestOptions = {
|
|
105
105
|
method
|
|
106
106
|
};
|
|
107
|
-
if (options.data
|
|
107
|
+
if (options.data !== undefined && !['GET', 'HEAD'].includes(method)) {
|
|
108
108
|
headers['Content-Type'] = headers['Content-Type'] || 'application/json';
|
|
109
109
|
requestOptions.body = options.data;
|
|
110
110
|
}
|
|
@@ -144,13 +144,24 @@ export async function apiCommand(endpoint, options = {}, globalOptions = {}, dep
|
|
|
144
144
|
};
|
|
145
145
|
let config = await loadConfig(globalOptions.config, allOptions);
|
|
146
146
|
|
|
147
|
-
//
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
// A linked project's upload credential must not hide an existing user login.
|
|
148
|
+
// Explicit file/env/--token credentials still take precedence.
|
|
149
|
+
let token = config.linkedProject ? config.userToken || config.apiKey : config.apiKey || config.userToken;
|
|
150
|
+
if (!token && !options.schemaDiscovery) {
|
|
151
|
+
output.error('Authentication required. Run vizzly login or set VIZZLY_TOKEN.');
|
|
150
152
|
output.cleanup();
|
|
151
153
|
exit(1);
|
|
152
154
|
return;
|
|
153
155
|
}
|
|
156
|
+
if (options.data?.startsWith('@')) {
|
|
157
|
+
let source = options.data.slice(1);
|
|
158
|
+
let data = source === '-' ? await readStdin() : await readFile(source, 'utf8');
|
|
159
|
+
options = {
|
|
160
|
+
...options,
|
|
161
|
+
data
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (options.data !== undefined) JSON.parse(options.data);
|
|
154
165
|
let {
|
|
155
166
|
errors,
|
|
156
167
|
method,
|
|
@@ -164,10 +175,7 @@ export async function apiCommand(endpoint, options = {}, globalOptions = {}, dep
|
|
|
164
175
|
displayMethod = method;
|
|
165
176
|
if (errors.length > 0) {
|
|
166
177
|
output.error(errors[0]);
|
|
167
|
-
|
|
168
|
-
output.hint('Use GET for queries, or use dedicated commands (vizzly approve, vizzly reject, vizzly comment)');
|
|
169
|
-
}
|
|
170
|
-
output.hint('Most raw API use should stay read-only; prefer dedicated commands for mutations.');
|
|
178
|
+
output.hint('Use vizzly api schema to discover supported operations.');
|
|
171
179
|
output.cleanup();
|
|
172
180
|
exit(1);
|
|
173
181
|
return;
|
|
@@ -177,10 +185,31 @@ export async function apiCommand(endpoint, options = {}, globalOptions = {}, dep
|
|
|
177
185
|
output.startSpinner(`${method} ${normalizedEndpoint}`);
|
|
178
186
|
let client = createApiClient({
|
|
179
187
|
baseUrl: config.apiUrl,
|
|
180
|
-
token
|
|
181
|
-
command: 'api'
|
|
188
|
+
token,
|
|
189
|
+
command: 'api',
|
|
190
|
+
allowNoToken: Boolean(options.schemaDiscovery)
|
|
191
|
+
});
|
|
192
|
+
let response = await client.request(normalizedEndpoint, {
|
|
193
|
+
...requestOptions,
|
|
194
|
+
redirect: 'error',
|
|
195
|
+
retryAuthentication: ['GET', 'HEAD'].includes(method),
|
|
196
|
+
responseType: options.output ? 'response' : 'json'
|
|
182
197
|
});
|
|
183
|
-
|
|
198
|
+
if (options.output) {
|
|
199
|
+
let path = resolve(options.output);
|
|
200
|
+
let file = await open(path, 'wx');
|
|
201
|
+
try {
|
|
202
|
+
await pipeline(response.body || Readable.from([]), file.createWriteStream());
|
|
203
|
+
} catch (error) {
|
|
204
|
+
await file.close();
|
|
205
|
+
await unlink(path);
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
response = {
|
|
209
|
+
file: path,
|
|
210
|
+
contentType: response.headers.get('content-type')
|
|
211
|
+
};
|
|
212
|
+
}
|
|
184
213
|
output.stopSpinner();
|
|
185
214
|
|
|
186
215
|
// Output response
|
|
@@ -240,12 +269,30 @@ export function validateApiOptions(endpoint, options = {}) {
|
|
|
240
269
|
if (!endpoint || endpoint.trim() === '') {
|
|
241
270
|
return errors;
|
|
242
271
|
}
|
|
243
|
-
let normalizedEndpoint = normalizeApiEndpoint(endpoint);
|
|
244
272
|
let method = normalizeApiMethod(options.method || 'GET');
|
|
245
273
|
errors.push(...validateApiRequest({
|
|
246
|
-
endpoint
|
|
274
|
+
endpoint,
|
|
247
275
|
method,
|
|
248
276
|
hasData: options.data !== undefined
|
|
249
277
|
}));
|
|
250
278
|
return errors;
|
|
279
|
+
}
|
|
280
|
+
async function readStdin() {
|
|
281
|
+
let chunks = [];
|
|
282
|
+
for await (let chunk of process.stdin) chunks.push(chunk);
|
|
283
|
+
return Buffer.concat(chunks.map(chunk => Buffer.from(chunk))).toString('utf8');
|
|
284
|
+
}
|
|
285
|
+
export async function apiSchemaCommand(operationId, options, globalOptions) {
|
|
286
|
+
let endpoint = '/api/sdk/schema';
|
|
287
|
+
if (operationId) {
|
|
288
|
+
endpoint += `/${encodeURIComponent(operationId)}`;
|
|
289
|
+
if (options.full) options = {
|
|
290
|
+
...options,
|
|
291
|
+
query: [...(options.query || []), 'view=full']
|
|
292
|
+
};
|
|
293
|
+
} else if (options.full) endpoint += '/openapi';
|
|
294
|
+
return apiCommand(endpoint, {
|
|
295
|
+
...options,
|
|
296
|
+
schemaDiscovery: true
|
|
297
|
+
}, globalOptions);
|
|
251
298
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -223,8 +223,10 @@ For user-facing changes, use the repo-local Vizzly skill at
|
|
|
223
223
|
|
|
224
224
|
- Inspect existing visual evidence before and after the change.
|
|
225
225
|
- Use the repository's established Vizzly command and owning user workflow.
|
|
226
|
-
-
|
|
227
|
-
diff images
|
|
226
|
+
- For cloud reviews, discover supported requests with
|
|
227
|
+
\`vizzly api schema --json\` and inspect baseline, current, and diff images
|
|
228
|
+
together.
|
|
229
|
+
- For local evidence, use \`vizzly context ... --json\`.
|
|
228
230
|
|
|
229
231
|
Treat Vizzly diffs as review evidence. Do not approve, reject, or replace
|
|
230
232
|
evidence unless the task explicitly asks for that mutation.
|
package/dist/commands/run.js
CHANGED
|
@@ -61,19 +61,19 @@ export async function resolveBuildDisplayUrl({
|
|
|
61
61
|
/**
|
|
62
62
|
* Build the follow-up command for a cloud run.
|
|
63
63
|
*
|
|
64
|
-
* JSON consumers
|
|
65
|
-
*
|
|
64
|
+
* JSON consumers start with the public schema so they can discover the current
|
|
65
|
+
* request shape. Human output keeps the readable context summary.
|
|
66
66
|
*
|
|
67
67
|
* @param {string} buildId - Cloud build ID to inspect.
|
|
68
68
|
* @param {Object} options - Command output options.
|
|
69
69
|
* @param {boolean} [options.structured=false] - Include machine-readable JSON.
|
|
70
|
-
* @returns {string}
|
|
70
|
+
* @returns {string} Cloud context or schema discovery command.
|
|
71
71
|
*/
|
|
72
72
|
function buildContextCommand(buildId, {
|
|
73
73
|
structured = false
|
|
74
74
|
} = {}) {
|
|
75
|
-
|
|
76
|
-
return `vizzly context build ${buildId} --
|
|
75
|
+
if (structured) return 'vizzly api schema sdk.getBuildContext --json';
|
|
76
|
+
return `vizzly context build ${buildId} --source cloud`;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
/**
|
package/dist/commands/status.js
CHANGED
|
@@ -94,8 +94,8 @@ export function createStatusSuggestedCommands(build = {}) {
|
|
|
94
94
|
return [];
|
|
95
95
|
}
|
|
96
96
|
return [{
|
|
97
|
-
label: '
|
|
98
|
-
command:
|
|
97
|
+
label: 'Discover build context API',
|
|
98
|
+
command: 'vizzly api schema sdk.getBuildContext --json'
|
|
99
99
|
}, {
|
|
100
100
|
label: 'List comparisons',
|
|
101
101
|
command: `vizzly --json comparisons --build ${build.id}`
|
package/dist/commands/tdd.js
CHANGED
|
@@ -31,7 +31,7 @@ function buildLocalContextCommand({
|
|
|
31
31
|
structured = false
|
|
32
32
|
} = {}) {
|
|
33
33
|
let jsonFlag = structured ? ' --json' : '';
|
|
34
|
-
return `vizzly context build current --source local
|
|
34
|
+
return `vizzly context build current --source local${jsonFlag}`;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|