@vizzly-testing/cli 0.29.5 → 0.30.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 +28 -0
- package/dist/api/endpoints.js +59 -0
- package/dist/api/index.js +1 -1
- package/dist/auth/core.js +1 -1
- package/dist/auth/index.js +2 -1
- package/dist/cli.js +156 -30
- package/dist/commands/context.js +559 -0
- package/dist/commands/logout.js +1 -1
- package/dist/commands/whoami.js +1 -1
- package/dist/config/core.js +1 -1
- package/dist/context/local-workspace-provider.js +412 -0
- package/dist/context/provider-resolver.js +32 -0
- package/dist/reporter/reporter-bundle.css +2 -1
- package/dist/reporter/reporter-bundle.iife.js +119 -119
- package/dist/reporter-ssr/ssr-entry.js +605 -509
- package/dist/sdk/index.js +1 -1
- package/dist/server/routers/assets.js +4 -0
- package/dist/server/routers/events.js +50 -10
- package/dist/tdd/core/hotspot-coverage.js +1 -1
- package/dist/tdd/services/hotspot-service.js +1 -1
- package/dist/tdd/services/region-service.js +1 -1
- package/dist/types/sdk.d.ts +7 -7
- package/dist/utils/colors.js +46 -44
- package/dist/utils/global-config.js +2 -2
- package/dist/utils/report-data.js +31 -0
- package/dist/utils/security.js +3 -2
- package/package.json +4 -4
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context commands - fetch visual context bundles for agents and reviewers
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createApiClient as defaultCreateApiClient, getBuildContext as defaultGetBuildContext, getComparisonContext as defaultGetComparisonContext, getReviewQueueContext as defaultGetReviewQueueContext, getScreenshotContext as defaultGetScreenshotContext, getSimilarFingerprintContext as defaultGetSimilarFingerprintContext } from '../api/index.js';
|
|
6
|
+
import { createLocalWorkspaceContextProvider as defaultCreateLocalWorkspaceContextProvider } from '../context/local-workspace-provider.js';
|
|
7
|
+
import { resolveContextSource as defaultResolveContextSource } from '../context/provider-resolver.js';
|
|
8
|
+
import { loadConfig as defaultLoadConfig } from '../utils/config-loader.js';
|
|
9
|
+
import * as defaultOutput from '../utils/output.js';
|
|
10
|
+
function buildAuthErrorMessage() {
|
|
11
|
+
return 'API token required. Use --token, set VIZZLY_TOKEN, or run "vizzly login"';
|
|
12
|
+
}
|
|
13
|
+
function buildSourceErrorMessage() {
|
|
14
|
+
return '--source must be one of: auto, cloud, local';
|
|
15
|
+
}
|
|
16
|
+
function validateLimitRange(value, flagName, {
|
|
17
|
+
min = 1,
|
|
18
|
+
max
|
|
19
|
+
}) {
|
|
20
|
+
if (value == null) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
if (!Number.isInteger(value) || value < min || value > max) {
|
|
24
|
+
return [`${flagName} must be a number between ${min} and ${max}`];
|
|
25
|
+
}
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
function validateOffset(value) {
|
|
29
|
+
if (value == null) {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
33
|
+
return ['--offset must be a non-negative number'];
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
function validateSourceOption(value) {
|
|
38
|
+
if (value == null) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
if (!['auto', 'cloud', 'local'].includes(value)) {
|
|
42
|
+
return [buildSourceErrorMessage()];
|
|
43
|
+
}
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
function validateScopedProjectOptions(options = {}) {
|
|
47
|
+
let errors = [];
|
|
48
|
+
if (options.org && !options.project) {
|
|
49
|
+
errors.push('--org requires --project');
|
|
50
|
+
}
|
|
51
|
+
return errors;
|
|
52
|
+
}
|
|
53
|
+
function createClient(config, createApiClient) {
|
|
54
|
+
return createApiClient({
|
|
55
|
+
baseUrl: config.apiUrl,
|
|
56
|
+
token: config.apiKey,
|
|
57
|
+
command: 'context'
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async function loadContextConfig(globalOptions, options, deps) {
|
|
61
|
+
let {
|
|
62
|
+
loadConfig = defaultLoadConfig,
|
|
63
|
+
requireApiKey = true,
|
|
64
|
+
output = defaultOutput,
|
|
65
|
+
exit = code => process.exit(code)
|
|
66
|
+
} = deps;
|
|
67
|
+
let allOptions = {
|
|
68
|
+
...globalOptions,
|
|
69
|
+
...options
|
|
70
|
+
};
|
|
71
|
+
let config = await loadConfig(globalOptions.config, allOptions);
|
|
72
|
+
if (requireApiKey && !config.apiKey) {
|
|
73
|
+
output.error(buildAuthErrorMessage());
|
|
74
|
+
output.cleanup();
|
|
75
|
+
exit(1);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
return config;
|
|
79
|
+
}
|
|
80
|
+
function createCloudContextProvider(config, deps = {}) {
|
|
81
|
+
let {
|
|
82
|
+
createApiClient = defaultCreateApiClient,
|
|
83
|
+
getBuildContext = defaultGetBuildContext,
|
|
84
|
+
getComparisonContext = defaultGetComparisonContext,
|
|
85
|
+
getScreenshotContext = defaultGetScreenshotContext,
|
|
86
|
+
getSimilarFingerprintContext = defaultGetSimilarFingerprintContext,
|
|
87
|
+
getReviewQueueContext = defaultGetReviewQueueContext
|
|
88
|
+
} = deps;
|
|
89
|
+
let client = createClient(config, createApiClient);
|
|
90
|
+
return {
|
|
91
|
+
source: 'cloud',
|
|
92
|
+
async getBuildContext(buildId) {
|
|
93
|
+
return await getBuildContext(client, buildId);
|
|
94
|
+
},
|
|
95
|
+
async getComparisonContext(comparisonId, query) {
|
|
96
|
+
return await getComparisonContext(client, comparisonId, query);
|
|
97
|
+
},
|
|
98
|
+
async getScreenshotContext(screenshotName, query) {
|
|
99
|
+
return await getScreenshotContext(client, screenshotName, query);
|
|
100
|
+
},
|
|
101
|
+
async getSimilarFingerprintContext(fingerprintHash, query) {
|
|
102
|
+
return await getSimilarFingerprintContext(client, fingerprintHash, query);
|
|
103
|
+
},
|
|
104
|
+
async getReviewQueueContext(query) {
|
|
105
|
+
return await getReviewQueueContext(client, query);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function buildLocalFingerprintCapabilityError() {
|
|
110
|
+
let error = new Error('Local workspace context does not support fingerprint similarity yet. Use --source cloud for this query.');
|
|
111
|
+
error.code = 'LOCAL_WORKSPACE_CONTEXT';
|
|
112
|
+
return error;
|
|
113
|
+
}
|
|
114
|
+
function shouldExplainLocalSimilarityGap(requestedSource, command, localProvider) {
|
|
115
|
+
return requestedSource === 'auto' && command === 'similar' && localProvider.isAvailable();
|
|
116
|
+
}
|
|
117
|
+
async function loadContextRuntime(command, target, globalOptions, options, deps = {}) {
|
|
118
|
+
let {
|
|
119
|
+
createLocalWorkspaceContextProvider = defaultCreateLocalWorkspaceContextProvider,
|
|
120
|
+
resolveContextSource = defaultResolveContextSource,
|
|
121
|
+
output = defaultOutput,
|
|
122
|
+
exit = code => process.exit(code)
|
|
123
|
+
} = deps;
|
|
124
|
+
let config = await loadContextConfig(globalOptions, options, {
|
|
125
|
+
...deps,
|
|
126
|
+
output,
|
|
127
|
+
exit,
|
|
128
|
+
requireApiKey: false
|
|
129
|
+
});
|
|
130
|
+
let requestedSource = options.source || 'auto';
|
|
131
|
+
let projectRoot = deps.projectRoot || process.cwd();
|
|
132
|
+
let localProvider = createLocalWorkspaceContextProvider({
|
|
133
|
+
projectRoot
|
|
134
|
+
});
|
|
135
|
+
let source = resolveContextSource({
|
|
136
|
+
requestedSource,
|
|
137
|
+
command,
|
|
138
|
+
target,
|
|
139
|
+
projectRoot
|
|
140
|
+
}, {
|
|
141
|
+
createLocalWorkspaceContextProvider
|
|
142
|
+
});
|
|
143
|
+
if (source === 'cloud' && !config.apiKey) {
|
|
144
|
+
if (shouldExplainLocalSimilarityGap(requestedSource, command, localProvider)) {
|
|
145
|
+
throw buildLocalFingerprintCapabilityError();
|
|
146
|
+
}
|
|
147
|
+
output.error(buildAuthErrorMessage());
|
|
148
|
+
output.cleanup();
|
|
149
|
+
exit(1);
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
let provider = source === 'local' ? localProvider : createCloudContextProvider(config, deps);
|
|
153
|
+
return {
|
|
154
|
+
config,
|
|
155
|
+
source,
|
|
156
|
+
provider
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function buildScopeQuery(options = {}, query = {}) {
|
|
160
|
+
let scopedQuery = {
|
|
161
|
+
...query
|
|
162
|
+
};
|
|
163
|
+
if (options.project) {
|
|
164
|
+
scopedQuery.project = options.project;
|
|
165
|
+
}
|
|
166
|
+
if (options.org) {
|
|
167
|
+
scopedQuery.organization = options.org;
|
|
168
|
+
}
|
|
169
|
+
return scopedQuery;
|
|
170
|
+
}
|
|
171
|
+
function getStatusTone(colors, status) {
|
|
172
|
+
if (status === 'changed' || status === 'pending' || status === 'failed') {
|
|
173
|
+
return colors.brand.warning;
|
|
174
|
+
}
|
|
175
|
+
if (status === 'approved' || status === 'completed' || status === 'identical') {
|
|
176
|
+
return colors.brand.success;
|
|
177
|
+
}
|
|
178
|
+
if (status === 'rejected' || status === 'error') {
|
|
179
|
+
return colors.brand.error;
|
|
180
|
+
}
|
|
181
|
+
return colors.brand.info;
|
|
182
|
+
}
|
|
183
|
+
function getComparisonDisplayState(comparison = {}) {
|
|
184
|
+
return comparison.result || comparison.status || 'unknown';
|
|
185
|
+
}
|
|
186
|
+
function formatConfirmedRegionLabels(regions = []) {
|
|
187
|
+
return regions.map(region => region.label).filter(Boolean).slice(0, 3).join(' · ');
|
|
188
|
+
}
|
|
189
|
+
function printComparisonList(output, comparisons = [], {
|
|
190
|
+
limit = 5
|
|
191
|
+
} = {}) {
|
|
192
|
+
let colors = output.getColors();
|
|
193
|
+
for (let comparison of comparisons.slice(0, limit)) {
|
|
194
|
+
let displayState = getComparisonDisplayState(comparison);
|
|
195
|
+
let statusTone = getStatusTone(colors, displayState);
|
|
196
|
+
let screenshotName = comparison.screenshot?.name || comparison.name || comparison.id;
|
|
197
|
+
let diffPercentage = comparison.diff_percentage == null ? null : `${comparison.diff_percentage}%`;
|
|
198
|
+
let fingerprint = comparison.analysis?.fingerprint_hash || null;
|
|
199
|
+
let details = [];
|
|
200
|
+
if (diffPercentage) {
|
|
201
|
+
details.push(diffPercentage);
|
|
202
|
+
}
|
|
203
|
+
if (fingerprint) {
|
|
204
|
+
details.push(`fp:${fingerprint}`);
|
|
205
|
+
}
|
|
206
|
+
if (comparison.build_branch) {
|
|
207
|
+
details.push(comparison.build_branch);
|
|
208
|
+
}
|
|
209
|
+
output.print(` ${colors.bold(screenshotName)} ${statusTone(displayState.toUpperCase())}`);
|
|
210
|
+
if (details.length > 0) {
|
|
211
|
+
output.print(` ${colors.dim(details.join(' · '))}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function displayBuildContext(output, context) {
|
|
216
|
+
output.header('context', 'build');
|
|
217
|
+
let colors = output.getColors();
|
|
218
|
+
let buildTone = getStatusTone(colors, context.build.status);
|
|
219
|
+
output.print(` ${colors.bold(context.build.name || context.build.id)} ${buildTone((context.build.status || 'unknown').toUpperCase())}`);
|
|
220
|
+
output.print(` ${colors.dim(`@${context.scope.organization.slug}/${context.scope.project.slug}`)}`);
|
|
221
|
+
output.blank();
|
|
222
|
+
output.labelValue('Comparisons', String(context.comparisons.length));
|
|
223
|
+
output.labelValue('Review', `${context.summary.review.pending || 0} pending · ${context.summary.review.approved || 0} approved · ${context.summary.review.rejected || 0} rejected`);
|
|
224
|
+
output.labelValue('Memory', `${context.review.comments.length} build comments · ${context.review.assignments.length} assignments`);
|
|
225
|
+
if (context.preview) {
|
|
226
|
+
output.labelValue('Preview', `${context.preview.status}${context.preview.preview_url ? ' · available' : ''}`);
|
|
227
|
+
}
|
|
228
|
+
if (context.links?.build_url) {
|
|
229
|
+
output.labelValue('Build URL', context.links.build_url);
|
|
230
|
+
}
|
|
231
|
+
if (context.comparisons.length > 0) {
|
|
232
|
+
output.blank();
|
|
233
|
+
output.print(' Comparisons');
|
|
234
|
+
printComparisonList(output, context.comparisons);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function countScreenshotCommentEntries(groups = []) {
|
|
238
|
+
return groups.reduce((total, group) => total + (group.comments?.length || 0), 0);
|
|
239
|
+
}
|
|
240
|
+
function displayComparisonContext(output, context) {
|
|
241
|
+
output.header('context', 'comparison');
|
|
242
|
+
let colors = output.getColors();
|
|
243
|
+
let displayState = getComparisonDisplayState(context.comparison);
|
|
244
|
+
let statusTone = getStatusTone(colors, displayState);
|
|
245
|
+
let screenshotName = context.comparison.screenshot?.name || context.comparison.id;
|
|
246
|
+
let analysis = context.comparison.analysis || {};
|
|
247
|
+
let confirmedRegionLabels = formatConfirmedRegionLabels(context.history.confirmed_regions);
|
|
248
|
+
output.print(` ${colors.bold(screenshotName)} ${statusTone(displayState.toUpperCase())}`);
|
|
249
|
+
output.print(` ${colors.dim(`@${context.scope.organization.slug}/${context.scope.project.slug}`)}`);
|
|
250
|
+
output.blank();
|
|
251
|
+
output.labelValue('Eyes', `${analysis.diff_image_url ? 'baseline/current/diff' : 'comparison metadata only'}`);
|
|
252
|
+
output.labelValue('Memory', `${context.history.similar_by_fingerprint.length} similar · ${context.history.recent_by_name.length} recent · ${context.history.confirmed_regions.length} confirmed regions`);
|
|
253
|
+
output.labelValue('Review', `${context.review.build_comments.length} build comments · ${countScreenshotCommentEntries(context.review.screenshot_comments)} screenshot comments`);
|
|
254
|
+
if (analysis.fingerprint_hash) {
|
|
255
|
+
output.labelValue('Fingerprint', analysis.fingerprint_hash);
|
|
256
|
+
}
|
|
257
|
+
if (confirmedRegionLabels) {
|
|
258
|
+
output.labelValue('Known Regions', confirmedRegionLabels);
|
|
259
|
+
}
|
|
260
|
+
if (context.links?.comparison_url) {
|
|
261
|
+
output.labelValue('Comparison URL', context.links.comparison_url);
|
|
262
|
+
}
|
|
263
|
+
if (context.history.similar_by_fingerprint.length > 0) {
|
|
264
|
+
output.blank();
|
|
265
|
+
output.print(' Similar Diffs');
|
|
266
|
+
printComparisonList(output, context.history.similar_by_fingerprint);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function displayScreenshotContext(output, context) {
|
|
270
|
+
output.header('context', 'screenshot');
|
|
271
|
+
let colors = output.getColors();
|
|
272
|
+
let confirmedRegionLabels = formatConfirmedRegionLabels(context.confirmed_regions);
|
|
273
|
+
output.print(` ${colors.bold(context.screenshot.name)}`);
|
|
274
|
+
output.print(` ${colors.dim(`@${context.scope.organization.slug}/${context.scope.project.slug}`)}`);
|
|
275
|
+
output.blank();
|
|
276
|
+
output.labelValue('Memory', `${context.history.recent_comparisons.length} recent comparisons · ${context.confirmed_regions.length} confirmed regions`);
|
|
277
|
+
output.labelValue('Hotspots', `${context.hotspot_analysis.total_builds_analyzed} builds analyzed · ${context.hotspot_analysis.confidence}`);
|
|
278
|
+
if (confirmedRegionLabels) {
|
|
279
|
+
output.labelValue('Known Regions', confirmedRegionLabels);
|
|
280
|
+
}
|
|
281
|
+
if (context.history.recent_comparisons.length > 0) {
|
|
282
|
+
output.blank();
|
|
283
|
+
output.print(' Recent Comparisons');
|
|
284
|
+
printComparisonList(output, context.history.recent_comparisons);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function displayFingerprintContext(output, context) {
|
|
288
|
+
output.header('context', 'similar');
|
|
289
|
+
let colors = output.getColors();
|
|
290
|
+
let fingerprintHash = context.fingerprint?.hash || context.fingerprint_hash || 'unknown';
|
|
291
|
+
let comparisons = context.comparisons || context.matches || [];
|
|
292
|
+
output.print(` ${colors.bold(fingerprintHash)}`);
|
|
293
|
+
output.print(` ${colors.dim(`@${context.scope.organization.slug}/${context.scope.project.slug}`)}`);
|
|
294
|
+
output.blank();
|
|
295
|
+
output.labelValue('Matches', String(comparisons.length));
|
|
296
|
+
if (comparisons.length > 0) {
|
|
297
|
+
output.blank();
|
|
298
|
+
output.print(' Similar Diffs');
|
|
299
|
+
printComparisonList(output, comparisons, {
|
|
300
|
+
limit: 10
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function displayReviewQueueContext(output, context) {
|
|
305
|
+
output.header('context', 'review');
|
|
306
|
+
let colors = output.getColors();
|
|
307
|
+
output.print(` ${colors.bold(`${context.summary.total} pending comparisons`)}`);
|
|
308
|
+
output.print(` ${colors.dim(`@${context.scope.organization.slug}/${context.scope.project.slug}`)}`);
|
|
309
|
+
output.blank();
|
|
310
|
+
output.labelValue('Queue', `${context.summary.changed} changed · ${context.summary.new} new · ${context.summary.builds} builds`);
|
|
311
|
+
if (context.comparisons.length > 0) {
|
|
312
|
+
output.blank();
|
|
313
|
+
output.print(' Needs Review');
|
|
314
|
+
printComparisonList(output, context.comparisons, {
|
|
315
|
+
limit: 10
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
export async function contextBuildCommand(buildId, options = {}, globalOptions = {}, deps = {}) {
|
|
320
|
+
let {
|
|
321
|
+
output = defaultOutput,
|
|
322
|
+
exit = code => process.exit(code)
|
|
323
|
+
} = deps;
|
|
324
|
+
output.configure({
|
|
325
|
+
json: globalOptions.json,
|
|
326
|
+
verbose: globalOptions.verbose,
|
|
327
|
+
color: !globalOptions.noColor
|
|
328
|
+
});
|
|
329
|
+
try {
|
|
330
|
+
let runtime = await loadContextRuntime('build', buildId, globalOptions, options, {
|
|
331
|
+
...deps,
|
|
332
|
+
output,
|
|
333
|
+
exit
|
|
334
|
+
});
|
|
335
|
+
if (!runtime) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
output.startSpinner('Fetching build context...');
|
|
339
|
+
let context = await runtime.provider.getBuildContext(buildId);
|
|
340
|
+
output.stopSpinner();
|
|
341
|
+
if (globalOptions.json) {
|
|
342
|
+
output.data(context);
|
|
343
|
+
output.cleanup();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
displayBuildContext(output, context);
|
|
347
|
+
output.cleanup();
|
|
348
|
+
} catch (error) {
|
|
349
|
+
output.stopSpinner();
|
|
350
|
+
output.error('Failed to fetch build context', error);
|
|
351
|
+
output.cleanup();
|
|
352
|
+
exit(1);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
export async function contextComparisonCommand(comparisonId, options = {}, globalOptions = {}, deps = {}) {
|
|
356
|
+
let {
|
|
357
|
+
output = defaultOutput,
|
|
358
|
+
exit = code => process.exit(code)
|
|
359
|
+
} = deps;
|
|
360
|
+
output.configure({
|
|
361
|
+
json: globalOptions.json,
|
|
362
|
+
verbose: globalOptions.verbose,
|
|
363
|
+
color: !globalOptions.noColor
|
|
364
|
+
});
|
|
365
|
+
try {
|
|
366
|
+
let runtime = await loadContextRuntime('comparison', comparisonId, globalOptions, options, {
|
|
367
|
+
...deps,
|
|
368
|
+
output,
|
|
369
|
+
exit
|
|
370
|
+
});
|
|
371
|
+
if (!runtime) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
let query = {
|
|
375
|
+
similarLimit: options.similarLimit,
|
|
376
|
+
recentLimit: options.recentLimit,
|
|
377
|
+
windowSize: options.windowSize
|
|
378
|
+
};
|
|
379
|
+
output.startSpinner('Fetching comparison context...');
|
|
380
|
+
let context = await runtime.provider.getComparisonContext(comparisonId, query);
|
|
381
|
+
output.stopSpinner();
|
|
382
|
+
if (globalOptions.json) {
|
|
383
|
+
output.data(context);
|
|
384
|
+
output.cleanup();
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
displayComparisonContext(output, context);
|
|
388
|
+
output.cleanup();
|
|
389
|
+
} catch (error) {
|
|
390
|
+
output.stopSpinner();
|
|
391
|
+
output.error('Failed to fetch comparison context', error);
|
|
392
|
+
output.cleanup();
|
|
393
|
+
exit(1);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
export async function contextScreenshotCommand(screenshotName, options = {}, globalOptions = {}, deps = {}) {
|
|
397
|
+
let {
|
|
398
|
+
output = defaultOutput,
|
|
399
|
+
exit = code => process.exit(code)
|
|
400
|
+
} = deps;
|
|
401
|
+
output.configure({
|
|
402
|
+
json: globalOptions.json,
|
|
403
|
+
verbose: globalOptions.verbose,
|
|
404
|
+
color: !globalOptions.noColor
|
|
405
|
+
});
|
|
406
|
+
try {
|
|
407
|
+
let runtime = await loadContextRuntime('screenshot', screenshotName, globalOptions, options, {
|
|
408
|
+
...deps,
|
|
409
|
+
output,
|
|
410
|
+
exit
|
|
411
|
+
});
|
|
412
|
+
if (!runtime) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
let query = buildScopeQuery(options, {
|
|
416
|
+
recentLimit: options.recentLimit,
|
|
417
|
+
windowSize: options.windowSize
|
|
418
|
+
});
|
|
419
|
+
output.startSpinner('Fetching screenshot context...');
|
|
420
|
+
let context = await runtime.provider.getScreenshotContext(screenshotName, query);
|
|
421
|
+
output.stopSpinner();
|
|
422
|
+
if (globalOptions.json) {
|
|
423
|
+
output.data(context);
|
|
424
|
+
output.cleanup();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
displayScreenshotContext(output, context);
|
|
428
|
+
output.cleanup();
|
|
429
|
+
} catch (error) {
|
|
430
|
+
output.stopSpinner();
|
|
431
|
+
output.error('Failed to fetch screenshot context', error);
|
|
432
|
+
output.cleanup();
|
|
433
|
+
exit(1);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
export async function contextSimilarCommand(fingerprintHash, options = {}, globalOptions = {}, deps = {}) {
|
|
437
|
+
let {
|
|
438
|
+
output = defaultOutput,
|
|
439
|
+
exit = code => process.exit(code)
|
|
440
|
+
} = deps;
|
|
441
|
+
output.configure({
|
|
442
|
+
json: globalOptions.json,
|
|
443
|
+
verbose: globalOptions.verbose,
|
|
444
|
+
color: !globalOptions.noColor
|
|
445
|
+
});
|
|
446
|
+
try {
|
|
447
|
+
let runtime = await loadContextRuntime('similar', fingerprintHash, globalOptions, options, {
|
|
448
|
+
...deps,
|
|
449
|
+
output,
|
|
450
|
+
exit
|
|
451
|
+
});
|
|
452
|
+
if (!runtime) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
let query = buildScopeQuery(options, {
|
|
456
|
+
limit: options.limit
|
|
457
|
+
});
|
|
458
|
+
output.startSpinner('Fetching similar visual context...');
|
|
459
|
+
let context = await runtime.provider.getSimilarFingerprintContext(fingerprintHash, query);
|
|
460
|
+
output.stopSpinner();
|
|
461
|
+
if (globalOptions.json) {
|
|
462
|
+
output.data(context);
|
|
463
|
+
output.cleanup();
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
displayFingerprintContext(output, context);
|
|
467
|
+
output.cleanup();
|
|
468
|
+
} catch (error) {
|
|
469
|
+
output.stopSpinner();
|
|
470
|
+
output.error('Failed to fetch similar visual context', error);
|
|
471
|
+
output.cleanup();
|
|
472
|
+
exit(1);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
export async function contextReviewQueueCommand(options = {}, globalOptions = {}, deps = {}) {
|
|
476
|
+
let {
|
|
477
|
+
output = defaultOutput,
|
|
478
|
+
exit = code => process.exit(code)
|
|
479
|
+
} = deps;
|
|
480
|
+
output.configure({
|
|
481
|
+
json: globalOptions.json,
|
|
482
|
+
verbose: globalOptions.verbose,
|
|
483
|
+
color: !globalOptions.noColor
|
|
484
|
+
});
|
|
485
|
+
try {
|
|
486
|
+
let runtime = await loadContextRuntime('review-queue', null, globalOptions, options, {
|
|
487
|
+
...deps,
|
|
488
|
+
output,
|
|
489
|
+
exit
|
|
490
|
+
});
|
|
491
|
+
if (!runtime) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
let query = buildScopeQuery(options, {
|
|
495
|
+
limit: options.limit,
|
|
496
|
+
offset: options.offset
|
|
497
|
+
});
|
|
498
|
+
output.startSpinner('Fetching review queue context...');
|
|
499
|
+
let context = await runtime.provider.getReviewQueueContext(query);
|
|
500
|
+
output.stopSpinner();
|
|
501
|
+
if (globalOptions.json) {
|
|
502
|
+
output.data(context);
|
|
503
|
+
output.cleanup();
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
displayReviewQueueContext(output, context);
|
|
507
|
+
output.cleanup();
|
|
508
|
+
} catch (error) {
|
|
509
|
+
output.stopSpinner();
|
|
510
|
+
output.error('Failed to fetch review queue context', error);
|
|
511
|
+
output.cleanup();
|
|
512
|
+
exit(1);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
export function validateContextBuildOptions(_options = {}) {
|
|
516
|
+
return validateSourceOption(_options.source);
|
|
517
|
+
}
|
|
518
|
+
export function validateContextComparisonOptions(options = {}) {
|
|
519
|
+
let errors = [];
|
|
520
|
+
errors.push(...validateSourceOption(options.source));
|
|
521
|
+
errors.push(...validateLimitRange(options.similarLimit, '--similar-limit', {
|
|
522
|
+
max: 50
|
|
523
|
+
}));
|
|
524
|
+
errors.push(...validateLimitRange(options.recentLimit, '--recent-limit', {
|
|
525
|
+
max: 50
|
|
526
|
+
}));
|
|
527
|
+
errors.push(...validateLimitRange(options.windowSize, '--window-size', {
|
|
528
|
+
max: 50
|
|
529
|
+
}));
|
|
530
|
+
return errors;
|
|
531
|
+
}
|
|
532
|
+
export function validateContextScreenshotOptions(options = {}) {
|
|
533
|
+
let errors = validateScopedProjectOptions(options);
|
|
534
|
+
errors.push(...validateSourceOption(options.source));
|
|
535
|
+
errors.push(...validateLimitRange(options.recentLimit, '--recent-limit', {
|
|
536
|
+
max: 50
|
|
537
|
+
}));
|
|
538
|
+
errors.push(...validateLimitRange(options.windowSize, '--window-size', {
|
|
539
|
+
max: 50
|
|
540
|
+
}));
|
|
541
|
+
return errors;
|
|
542
|
+
}
|
|
543
|
+
export function validateContextSimilarOptions(options = {}) {
|
|
544
|
+
let errors = validateScopedProjectOptions(options);
|
|
545
|
+
errors.push(...validateSourceOption(options.source));
|
|
546
|
+
errors.push(...validateLimitRange(options.limit, '--limit', {
|
|
547
|
+
max: 50
|
|
548
|
+
}));
|
|
549
|
+
return errors;
|
|
550
|
+
}
|
|
551
|
+
export function validateContextReviewQueueOptions(options = {}) {
|
|
552
|
+
let errors = validateScopedProjectOptions(options);
|
|
553
|
+
errors.push(...validateSourceOption(options.source));
|
|
554
|
+
errors.push(...validateLimitRange(options.limit, '--limit', {
|
|
555
|
+
max: 100
|
|
556
|
+
}));
|
|
557
|
+
errors.push(...validateOffset(options.offset));
|
|
558
|
+
return errors;
|
|
559
|
+
}
|
package/dist/commands/logout.js
CHANGED
|
@@ -21,7 +21,7 @@ export async function logoutCommand(options = {}, globalOptions = {}) {
|
|
|
21
21
|
try {
|
|
22
22
|
// Check if user is logged in
|
|
23
23
|
let auth = await getAuthTokens();
|
|
24
|
-
if (!auth
|
|
24
|
+
if (!auth?.accessToken) {
|
|
25
25
|
if (globalOptions.json) {
|
|
26
26
|
output.data({
|
|
27
27
|
loggedOut: false,
|
package/dist/commands/whoami.js
CHANGED
|
@@ -21,7 +21,7 @@ export async function whoamiCommand(options = {}, globalOptions = {}) {
|
|
|
21
21
|
try {
|
|
22
22
|
// Check if user is logged in
|
|
23
23
|
let auth = await getAuthTokens();
|
|
24
|
-
if (!auth
|
|
24
|
+
if (!auth?.accessToken) {
|
|
25
25
|
if (globalOptions.json) {
|
|
26
26
|
output.data({
|
|
27
27
|
authenticated: false
|
package/dist/config/core.js
CHANGED
|
@@ -390,7 +390,7 @@ export function serializeConfig(config, filepath) {
|
|
|
390
390
|
* @returns {{ config: Object|null, filepath: string|null }}
|
|
391
391
|
*/
|
|
392
392
|
export function extractCosmiconfigResult(result) {
|
|
393
|
-
if (!result
|
|
393
|
+
if (!result?.config) {
|
|
394
394
|
return {
|
|
395
395
|
config: null,
|
|
396
396
|
filepath: null
|