@vizzly-testing/cli 0.10.2 → 0.11.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/.claude-plugin/.mcp.json +8 -0
- package/.claude-plugin/README.md +114 -0
- package/.claude-plugin/commands/debug-diff.md +153 -0
- package/.claude-plugin/commands/setup.md +137 -0
- package/.claude-plugin/commands/suggest-screenshots.md +111 -0
- package/.claude-plugin/commands/tdd-status.md +43 -0
- package/.claude-plugin/marketplace.json +28 -0
- package/.claude-plugin/mcp/vizzly-server/cloud-api-provider.js +354 -0
- package/.claude-plugin/mcp/vizzly-server/index.js +861 -0
- package/.claude-plugin/mcp/vizzly-server/local-tdd-provider.js +422 -0
- package/.claude-plugin/mcp/vizzly-server/token-resolver.js +185 -0
- package/.claude-plugin/plugin.json +14 -0
- package/README.md +168 -8
- package/dist/cli.js +64 -0
- package/dist/client/index.js +13 -3
- package/dist/commands/login.js +195 -0
- package/dist/commands/logout.js +71 -0
- package/dist/commands/project.js +351 -0
- package/dist/commands/run.js +30 -0
- package/dist/commands/whoami.js +162 -0
- package/dist/plugin-loader.js +4 -2
- package/dist/sdk/index.js +16 -4
- package/dist/services/api-service.js +50 -7
- package/dist/services/auth-service.js +226 -0
- package/dist/services/tdd-service.js +2 -1
- package/dist/types/client/index.d.ts +9 -3
- package/dist/types/commands/login.d.ts +11 -0
- package/dist/types/commands/logout.d.ts +11 -0
- package/dist/types/commands/project.d.ts +28 -0
- package/dist/types/commands/whoami.d.ts +11 -0
- package/dist/types/sdk/index.d.ts +9 -4
- package/dist/types/services/api-service.d.ts +2 -1
- package/dist/types/services/auth-service.d.ts +59 -0
- package/dist/types/utils/browser.d.ts +6 -0
- package/dist/types/utils/config-loader.d.ts +1 -1
- package/dist/types/utils/config-schema.d.ts +8 -174
- package/dist/types/utils/file-helpers.d.ts +18 -0
- package/dist/types/utils/global-config.d.ts +84 -0
- package/dist/utils/browser.js +44 -0
- package/dist/utils/config-loader.js +69 -3
- package/dist/utils/file-helpers.js +64 -0
- package/dist/utils/global-config.js +259 -0
- package/docs/api-reference.md +177 -6
- package/docs/authentication.md +334 -0
- package/docs/getting-started.md +21 -2
- package/docs/plugins.md +27 -0
- package/docs/test-integration.md +60 -10
- package/package.json +5 -3
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global User Configuration Utilities
|
|
3
|
+
* Manages ~/.vizzly/config.json for storing authentication tokens
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
import { join, dirname, parse } from 'path';
|
|
8
|
+
import { readFile, writeFile, mkdir, chmod } from 'fs/promises';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get the path to the global Vizzly directory
|
|
13
|
+
* @returns {string} Path to ~/.vizzly
|
|
14
|
+
*/
|
|
15
|
+
export function getGlobalConfigDir() {
|
|
16
|
+
return join(homedir(), '.vizzly');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the path to the global config file
|
|
21
|
+
* @returns {string} Path to ~/.vizzly/config.json
|
|
22
|
+
*/
|
|
23
|
+
export function getGlobalConfigPath() {
|
|
24
|
+
return join(getGlobalConfigDir(), 'config.json');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Ensure the global config directory exists with proper permissions
|
|
29
|
+
* @returns {Promise<void>}
|
|
30
|
+
*/
|
|
31
|
+
async function ensureGlobalConfigDir() {
|
|
32
|
+
let dir = getGlobalConfigDir();
|
|
33
|
+
if (!existsSync(dir)) {
|
|
34
|
+
await mkdir(dir, {
|
|
35
|
+
recursive: true,
|
|
36
|
+
mode: 0o700
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Load the global configuration
|
|
43
|
+
* @returns {Promise<Object>} Global config object
|
|
44
|
+
*/
|
|
45
|
+
export async function loadGlobalConfig() {
|
|
46
|
+
try {
|
|
47
|
+
let configPath = getGlobalConfigPath();
|
|
48
|
+
if (!existsSync(configPath)) {
|
|
49
|
+
return {};
|
|
50
|
+
}
|
|
51
|
+
let content = await readFile(configPath, 'utf-8');
|
|
52
|
+
return JSON.parse(content);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
// If file doesn't exist or is corrupted, return empty config
|
|
55
|
+
if (error.code === 'ENOENT') {
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Log warning about corrupted config but don't crash
|
|
60
|
+
console.warn('Warning: Global config file is corrupted, ignoring');
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Save the global configuration
|
|
67
|
+
* @param {Object} config - Configuration object to save
|
|
68
|
+
* @returns {Promise<void>}
|
|
69
|
+
*/
|
|
70
|
+
export async function saveGlobalConfig(config) {
|
|
71
|
+
await ensureGlobalConfigDir();
|
|
72
|
+
let configPath = getGlobalConfigPath();
|
|
73
|
+
let content = JSON.stringify(config, null, 2);
|
|
74
|
+
|
|
75
|
+
// Write file with secure permissions (owner read/write only)
|
|
76
|
+
await writeFile(configPath, content, {
|
|
77
|
+
mode: 0o600
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Ensure permissions are set correctly (in case umask interfered)
|
|
81
|
+
try {
|
|
82
|
+
await chmod(configPath, 0o600);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// On Windows, chmod may not work as expected, but that's okay
|
|
85
|
+
if (process.platform !== 'win32') {
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Clear all global configuration
|
|
93
|
+
* @returns {Promise<void>}
|
|
94
|
+
*/
|
|
95
|
+
export async function clearGlobalConfig() {
|
|
96
|
+
await saveGlobalConfig({});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get authentication tokens from global config
|
|
101
|
+
* @returns {Promise<Object|null>} Token object with accessToken, refreshToken, expiresAt, user, or null if not found
|
|
102
|
+
*/
|
|
103
|
+
export async function getAuthTokens() {
|
|
104
|
+
let config = await loadGlobalConfig();
|
|
105
|
+
if (!config.auth || !config.auth.accessToken) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
return config.auth;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Save authentication tokens to global config
|
|
113
|
+
* @param {Object} auth - Auth object with accessToken, refreshToken, expiresAt, user
|
|
114
|
+
* @returns {Promise<void>}
|
|
115
|
+
*/
|
|
116
|
+
export async function saveAuthTokens(auth) {
|
|
117
|
+
let config = await loadGlobalConfig();
|
|
118
|
+
config.auth = {
|
|
119
|
+
accessToken: auth.accessToken,
|
|
120
|
+
refreshToken: auth.refreshToken,
|
|
121
|
+
expiresAt: auth.expiresAt,
|
|
122
|
+
user: auth.user
|
|
123
|
+
};
|
|
124
|
+
await saveGlobalConfig(config);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Clear authentication tokens from global config
|
|
129
|
+
* @returns {Promise<void>}
|
|
130
|
+
*/
|
|
131
|
+
export async function clearAuthTokens() {
|
|
132
|
+
let config = await loadGlobalConfig();
|
|
133
|
+
delete config.auth;
|
|
134
|
+
await saveGlobalConfig(config);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check if authentication tokens exist and are not expired
|
|
139
|
+
* @returns {Promise<boolean>} True if valid tokens exist
|
|
140
|
+
*/
|
|
141
|
+
export async function hasValidTokens() {
|
|
142
|
+
let auth = await getAuthTokens();
|
|
143
|
+
if (!auth || !auth.accessToken) {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check if token is expired
|
|
148
|
+
if (auth.expiresAt) {
|
|
149
|
+
let expiresAt = new Date(auth.expiresAt);
|
|
150
|
+
let now = new Date();
|
|
151
|
+
|
|
152
|
+
// Consider expired if within 5 minutes of expiry
|
|
153
|
+
let bufferMs = 5 * 60 * 1000;
|
|
154
|
+
if (now.getTime() >= expiresAt.getTime() - bufferMs) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get the access token from global config if available
|
|
163
|
+
* @returns {Promise<string|null>} Access token or null
|
|
164
|
+
*/
|
|
165
|
+
export async function getAccessToken() {
|
|
166
|
+
let auth = await getAuthTokens();
|
|
167
|
+
return auth?.accessToken || null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get project mapping for a directory
|
|
172
|
+
* Walks up the directory tree to find the closest mapping
|
|
173
|
+
* @param {string} directoryPath - Absolute path to project directory
|
|
174
|
+
* @returns {Promise<Object|null>} Project data or null
|
|
175
|
+
*/
|
|
176
|
+
export async function getProjectMapping(directoryPath) {
|
|
177
|
+
let config = await loadGlobalConfig();
|
|
178
|
+
if (!config.projects) {
|
|
179
|
+
if (process.env.DEBUG_CONFIG) {
|
|
180
|
+
console.log('[MAPPING] No projects in global config');
|
|
181
|
+
}
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Walk up the directory tree looking for a mapping
|
|
186
|
+
let currentPath = directoryPath;
|
|
187
|
+
let {
|
|
188
|
+
root
|
|
189
|
+
} = parse(currentPath);
|
|
190
|
+
if (process.env.DEBUG_CONFIG) {
|
|
191
|
+
console.log('[MAPPING] Starting lookup from:', currentPath);
|
|
192
|
+
console.log('[MAPPING] Available mappings:', Object.keys(config.projects));
|
|
193
|
+
}
|
|
194
|
+
while (currentPath !== root) {
|
|
195
|
+
if (process.env.DEBUG_CONFIG) {
|
|
196
|
+
console.log('[MAPPING] Checking:', currentPath);
|
|
197
|
+
}
|
|
198
|
+
if (config.projects[currentPath]) {
|
|
199
|
+
if (process.env.DEBUG_CONFIG) {
|
|
200
|
+
console.log('[MAPPING] Found match at:', currentPath);
|
|
201
|
+
}
|
|
202
|
+
return config.projects[currentPath];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Move to parent directory
|
|
206
|
+
let parentPath = dirname(currentPath);
|
|
207
|
+
if (parentPath === currentPath) {
|
|
208
|
+
// We've reached the root
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
currentPath = parentPath;
|
|
212
|
+
}
|
|
213
|
+
if (process.env.DEBUG_CONFIG) {
|
|
214
|
+
console.log('[MAPPING] No mapping found');
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Save project mapping for a directory
|
|
221
|
+
* @param {string} directoryPath - Absolute path to project directory
|
|
222
|
+
* @param {Object} projectData - Project configuration
|
|
223
|
+
* @param {string} projectData.token - Project API token (vzt_...)
|
|
224
|
+
* @param {string} projectData.projectSlug - Project slug
|
|
225
|
+
* @param {string} projectData.organizationSlug - Organization slug
|
|
226
|
+
* @param {string} projectData.projectName - Project name
|
|
227
|
+
*/
|
|
228
|
+
export async function saveProjectMapping(directoryPath, projectData) {
|
|
229
|
+
let config = await loadGlobalConfig();
|
|
230
|
+
if (!config.projects) {
|
|
231
|
+
config.projects = {};
|
|
232
|
+
}
|
|
233
|
+
config.projects[directoryPath] = {
|
|
234
|
+
...projectData,
|
|
235
|
+
createdAt: new Date().toISOString()
|
|
236
|
+
};
|
|
237
|
+
await saveGlobalConfig(config);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get all project mappings
|
|
242
|
+
* @returns {Promise<Object>} Map of directory paths to project data
|
|
243
|
+
*/
|
|
244
|
+
export async function getProjectMappings() {
|
|
245
|
+
let config = await loadGlobalConfig();
|
|
246
|
+
return config.projects || {};
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Delete project mapping for a directory
|
|
251
|
+
* @param {string} directoryPath - Absolute path to project directory
|
|
252
|
+
*/
|
|
253
|
+
export async function deleteProjectMapping(directoryPath) {
|
|
254
|
+
let config = await loadGlobalConfig();
|
|
255
|
+
if (config.projects && config.projects[directoryPath]) {
|
|
256
|
+
delete config.projects[directoryPath];
|
|
257
|
+
await saveGlobalConfig(config);
|
|
258
|
+
}
|
|
259
|
+
}
|
package/docs/api-reference.md
CHANGED
|
@@ -12,7 +12,7 @@ Capture a screenshot for visual regression testing.
|
|
|
12
12
|
|
|
13
13
|
**Parameters:**
|
|
14
14
|
- `name` (string) - Unique screenshot identifier
|
|
15
|
-
- `imageBuffer` (Buffer) - PNG image data as Buffer
|
|
15
|
+
- `imageBuffer` (Buffer | string) - PNG image data as Buffer, or file path to an image
|
|
16
16
|
- `options` (object, optional) - Configuration and metadata
|
|
17
17
|
|
|
18
18
|
**Options:**
|
|
@@ -20,7 +20,7 @@ Capture a screenshot for visual regression testing.
|
|
|
20
20
|
{
|
|
21
21
|
// Comparison settings
|
|
22
22
|
threshold: 0.01, // Pixel difference threshold (0-1)
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
// Metadata for organization (all optional)
|
|
25
25
|
properties: {
|
|
26
26
|
browser: 'chrome', // Browser name
|
|
@@ -39,7 +39,10 @@ Capture a screenshot for visual regression testing.
|
|
|
39
39
|
|
|
40
40
|
**Returns:** `Promise<void>`
|
|
41
41
|
|
|
42
|
-
**
|
|
42
|
+
**Examples:**
|
|
43
|
+
|
|
44
|
+
Using a Buffer:
|
|
45
|
+
|
|
43
46
|
```javascript
|
|
44
47
|
import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
|
|
45
48
|
|
|
@@ -54,6 +57,31 @@ await vizzlyScreenshot('homepage', screenshot, {
|
|
|
54
57
|
});
|
|
55
58
|
```
|
|
56
59
|
|
|
60
|
+
Using a file path:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
|
|
64
|
+
|
|
65
|
+
// Save screenshot to file
|
|
66
|
+
await page.screenshot({ path: './screenshots/homepage.png' });
|
|
67
|
+
|
|
68
|
+
// Send to Vizzly using file path
|
|
69
|
+
await vizzlyScreenshot('homepage', './screenshots/homepage.png', {
|
|
70
|
+
threshold: 0.02,
|
|
71
|
+
properties: {
|
|
72
|
+
browser: 'chrome',
|
|
73
|
+
viewport: '1920x1080',
|
|
74
|
+
component: 'hero-section'
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**File Path Support:**
|
|
80
|
+
- Accepts both absolute and relative paths
|
|
81
|
+
- Automatically reads the file and converts to Buffer internally
|
|
82
|
+
- Throws error if file doesn't exist or cannot be read
|
|
83
|
+
- Works with any PNG image file
|
|
84
|
+
|
|
57
85
|
### `vizzlyFlush()`
|
|
58
86
|
|
|
59
87
|
Wait for all queued screenshots to be processed.
|
|
@@ -201,10 +229,24 @@ Stop the Vizzly server and cleanup resources.
|
|
|
201
229
|
**Returns:** `Promise<void>`
|
|
202
230
|
|
|
203
231
|
##### `screenshot(name, imageBuffer, options)`
|
|
204
|
-
Capture a screenshot
|
|
232
|
+
Capture a screenshot.
|
|
233
|
+
|
|
234
|
+
**Parameters:**
|
|
235
|
+
- `name` (string) - Unique screenshot identifier
|
|
236
|
+
- `imageBuffer` (Buffer | string) - PNG image data as Buffer, or file path to an image
|
|
237
|
+
- `options` (object, optional) - Configuration and metadata
|
|
205
238
|
|
|
206
239
|
**Returns:** `Promise<void>`
|
|
207
240
|
|
|
241
|
+
**Example:**
|
|
242
|
+
```javascript
|
|
243
|
+
// Using a Buffer
|
|
244
|
+
await vizzly.screenshot('homepage', buffer);
|
|
245
|
+
|
|
246
|
+
// Using a file path
|
|
247
|
+
await vizzly.screenshot('homepage', './screenshots/homepage.png');
|
|
248
|
+
```
|
|
249
|
+
|
|
208
250
|
##### `upload(options)`
|
|
209
251
|
Upload screenshots to Vizzly.
|
|
210
252
|
|
|
@@ -224,8 +266,21 @@ Upload screenshots to Vizzly.
|
|
|
224
266
|
##### `compare(name, imageBuffer)`
|
|
225
267
|
Run local comparison (TDD mode).
|
|
226
268
|
|
|
269
|
+
**Parameters:**
|
|
270
|
+
- `name` (string) - Screenshot name
|
|
271
|
+
- `imageBuffer` (Buffer | string) - PNG image data as Buffer, or file path to an image
|
|
272
|
+
|
|
227
273
|
**Returns:** `Promise<ComparisonResult>`
|
|
228
274
|
|
|
275
|
+
**Example:**
|
|
276
|
+
```javascript
|
|
277
|
+
// Using a Buffer
|
|
278
|
+
const result = await vizzly.compare('homepage', buffer);
|
|
279
|
+
|
|
280
|
+
// Using a file path
|
|
281
|
+
const result = await vizzly.compare('homepage', './screenshots/homepage.png');
|
|
282
|
+
```
|
|
283
|
+
|
|
229
284
|
##### `getConfig()`
|
|
230
285
|
Get current SDK configuration.
|
|
231
286
|
|
|
@@ -271,6 +326,117 @@ vizzly.on('comparison:failed', (error) => {
|
|
|
271
326
|
|
|
272
327
|
## CLI Commands
|
|
273
328
|
|
|
329
|
+
### Authentication Commands
|
|
330
|
+
|
|
331
|
+
#### `vizzly login`
|
|
332
|
+
|
|
333
|
+
Authenticate using OAuth 2.0 device flow.
|
|
334
|
+
|
|
335
|
+
**Options:**
|
|
336
|
+
- `--json` - Machine-readable JSON output
|
|
337
|
+
- `--verbose` - Verbose output
|
|
338
|
+
|
|
339
|
+
**Exit Codes:**
|
|
340
|
+
- `0` - Login successful
|
|
341
|
+
- `1` - Login failed
|
|
342
|
+
|
|
343
|
+
**Example:**
|
|
344
|
+
```bash
|
|
345
|
+
vizzly login
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### `vizzly logout`
|
|
349
|
+
|
|
350
|
+
Clear stored authentication tokens.
|
|
351
|
+
|
|
352
|
+
**Options:**
|
|
353
|
+
- `--json` - Machine-readable JSON output
|
|
354
|
+
- `--verbose` - Verbose output
|
|
355
|
+
|
|
356
|
+
**Exit Codes:**
|
|
357
|
+
- `0` - Logout successful
|
|
358
|
+
- `1` - Logout failed
|
|
359
|
+
|
|
360
|
+
**Example:**
|
|
361
|
+
```bash
|
|
362
|
+
vizzly logout
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### `vizzly whoami`
|
|
366
|
+
|
|
367
|
+
Display current user and authentication status.
|
|
368
|
+
|
|
369
|
+
**Options:**
|
|
370
|
+
- `--json` - Machine-readable JSON output
|
|
371
|
+
|
|
372
|
+
**Exit Codes:**
|
|
373
|
+
- `0` - Success
|
|
374
|
+
- `1` - Not authenticated or error
|
|
375
|
+
|
|
376
|
+
**Example:**
|
|
377
|
+
```bash
|
|
378
|
+
vizzly whoami
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
#### `vizzly project:select`
|
|
382
|
+
|
|
383
|
+
Configure project-specific token for current directory.
|
|
384
|
+
|
|
385
|
+
**Options:**
|
|
386
|
+
- `--json` - Machine-readable JSON output
|
|
387
|
+
|
|
388
|
+
**Exit Codes:**
|
|
389
|
+
- `0` - Project configured successfully
|
|
390
|
+
- `1` - Configuration failed
|
|
391
|
+
|
|
392
|
+
**Example:**
|
|
393
|
+
```bash
|
|
394
|
+
cd /path/to/project
|
|
395
|
+
vizzly project:select
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
#### `vizzly project:list`
|
|
399
|
+
|
|
400
|
+
Show all configured projects.
|
|
401
|
+
|
|
402
|
+
**Exit Codes:**
|
|
403
|
+
- `0` - Success
|
|
404
|
+
- `1` - Error
|
|
405
|
+
|
|
406
|
+
**Example:**
|
|
407
|
+
```bash
|
|
408
|
+
vizzly project:list
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
#### `vizzly project:token`
|
|
412
|
+
|
|
413
|
+
Display project token for current directory.
|
|
414
|
+
|
|
415
|
+
**Options:**
|
|
416
|
+
- `--json` - Machine-readable JSON output
|
|
417
|
+
|
|
418
|
+
**Exit Codes:**
|
|
419
|
+
- `0` - Success
|
|
420
|
+
- `1` - No project configured or error
|
|
421
|
+
|
|
422
|
+
**Example:**
|
|
423
|
+
```bash
|
|
424
|
+
vizzly project:token
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### `vizzly project:remove`
|
|
428
|
+
|
|
429
|
+
Remove project configuration for current directory.
|
|
430
|
+
|
|
431
|
+
**Exit Codes:**
|
|
432
|
+
- `0` - Success
|
|
433
|
+
- `1` - No project configured or error
|
|
434
|
+
|
|
435
|
+
**Example:**
|
|
436
|
+
```bash
|
|
437
|
+
vizzly project:remove
|
|
438
|
+
```
|
|
439
|
+
|
|
274
440
|
### `vizzly upload <path>`
|
|
275
441
|
|
|
276
442
|
Upload screenshots from a directory.
|
|
@@ -502,9 +668,14 @@ Configuration loaded via cosmiconfig in this order:
|
|
|
502
668
|
|
|
503
669
|
### Environment Variables
|
|
504
670
|
|
|
671
|
+
**Authentication:**
|
|
672
|
+
- `VIZZLY_TOKEN` - API authentication token (project token or access token)
|
|
673
|
+
- For local development: Use `vizzly login` instead of manually managing tokens
|
|
674
|
+
- For CI/CD: Use project tokens from environment variables
|
|
675
|
+
- Token priority: CLI flag → env var → project mapping → user access token
|
|
676
|
+
|
|
505
677
|
**Core Configuration:**
|
|
506
|
-
- `
|
|
507
|
-
- `VIZZLY_API_URL` - API base URL override
|
|
678
|
+
- `VIZZLY_API_URL` - API base URL override (default: `https://app.vizzly.dev`)
|
|
508
679
|
- `VIZZLY_LOG_LEVEL` - Logger level (`debug`, `info`, `warn`, `error`)
|
|
509
680
|
|
|
510
681
|
**Parallel Builds:**
|