@vizzly-testing/cli 0.10.3 → 0.11.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.
Files changed (47) hide show
  1. package/README.md +168 -8
  2. package/claude-plugin/.claude-plugin/.mcp.json +8 -0
  3. package/claude-plugin/.claude-plugin/README.md +114 -0
  4. package/claude-plugin/.claude-plugin/marketplace.json +28 -0
  5. package/claude-plugin/.claude-plugin/plugin.json +14 -0
  6. package/claude-plugin/commands/debug-diff.md +153 -0
  7. package/claude-plugin/commands/setup.md +137 -0
  8. package/claude-plugin/commands/suggest-screenshots.md +111 -0
  9. package/claude-plugin/commands/tdd-status.md +43 -0
  10. package/claude-plugin/mcp/vizzly-server/cloud-api-provider.js +354 -0
  11. package/claude-plugin/mcp/vizzly-server/index.js +861 -0
  12. package/claude-plugin/mcp/vizzly-server/local-tdd-provider.js +422 -0
  13. package/claude-plugin/mcp/vizzly-server/token-resolver.js +185 -0
  14. package/dist/cli.js +64 -0
  15. package/dist/client/index.js +13 -3
  16. package/dist/commands/login.js +195 -0
  17. package/dist/commands/logout.js +71 -0
  18. package/dist/commands/project.js +351 -0
  19. package/dist/commands/run.js +30 -0
  20. package/dist/commands/whoami.js +162 -0
  21. package/dist/plugin-loader.js +9 -15
  22. package/dist/sdk/index.js +16 -4
  23. package/dist/services/api-service.js +50 -7
  24. package/dist/services/auth-service.js +226 -0
  25. package/dist/types/client/index.d.ts +9 -3
  26. package/dist/types/commands/login.d.ts +11 -0
  27. package/dist/types/commands/logout.d.ts +11 -0
  28. package/dist/types/commands/project.d.ts +28 -0
  29. package/dist/types/commands/whoami.d.ts +11 -0
  30. package/dist/types/sdk/index.d.ts +9 -4
  31. package/dist/types/services/api-service.d.ts +2 -1
  32. package/dist/types/services/auth-service.d.ts +59 -0
  33. package/dist/types/utils/browser.d.ts +6 -0
  34. package/dist/types/utils/config-loader.d.ts +1 -1
  35. package/dist/types/utils/config-schema.d.ts +8 -174
  36. package/dist/types/utils/file-helpers.d.ts +18 -0
  37. package/dist/types/utils/global-config.d.ts +84 -0
  38. package/dist/utils/browser.js +44 -0
  39. package/dist/utils/config-loader.js +69 -3
  40. package/dist/utils/file-helpers.js +64 -0
  41. package/dist/utils/global-config.js +259 -0
  42. package/docs/api-reference.md +177 -6
  43. package/docs/authentication.md +334 -0
  44. package/docs/getting-started.md +21 -2
  45. package/docs/plugins.md +27 -0
  46. package/docs/test-integration.md +60 -10
  47. package/package.json +5 -3
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Logout command implementation
3
+ * @param {Object} options - Command options
4
+ * @param {Object} globalOptions - Global CLI options
5
+ */
6
+ export function logoutCommand(options?: any, globalOptions?: any): Promise<void>;
7
+ /**
8
+ * Validate logout options
9
+ * @param {Object} options - Command options
10
+ */
11
+ export function validateLogoutOptions(): any[];
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Project select command - configure project for current directory
3
+ * @param {Object} options - Command options
4
+ * @param {Object} globalOptions - Global CLI options
5
+ */
6
+ export function projectSelectCommand(options?: any, globalOptions?: any): Promise<void>;
7
+ /**
8
+ * Project list command - show all configured projects
9
+ * @param {Object} _options - Command options (unused)
10
+ * @param {Object} globalOptions - Global CLI options
11
+ */
12
+ export function projectListCommand(_options?: any, globalOptions?: any): Promise<void>;
13
+ /**
14
+ * Project token command - show/regenerate token for current directory
15
+ * @param {Object} _options - Command options (unused)
16
+ * @param {Object} globalOptions - Global CLI options
17
+ */
18
+ export function projectTokenCommand(_options?: any, globalOptions?: any): Promise<void>;
19
+ /**
20
+ * Project remove command - remove project configuration for current directory
21
+ * @param {Object} _options - Command options (unused)
22
+ * @param {Object} globalOptions - Global CLI options
23
+ */
24
+ export function projectRemoveCommand(_options?: any, globalOptions?: any): Promise<void>;
25
+ /**
26
+ * Validate project command options
27
+ */
28
+ export function validateProjectOptions(): any[];
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Whoami command implementation
3
+ * @param {Object} options - Command options
4
+ * @param {Object} globalOptions - Global CLI options
5
+ */
6
+ export function whoamiCommand(options?: any, globalOptions?: any): Promise<void>;
7
+ /**
8
+ * Validate whoami options
9
+ * @param {Object} options - Command options
10
+ */
11
+ export function validateWhoamiOptions(): any[];
@@ -81,11 +81,14 @@ export class VizzlySDK {
81
81
  /**
82
82
  * Capture a screenshot
83
83
  * @param {string} name - Screenshot name
84
- * @param {Buffer} imageBuffer - Image data
84
+ * @param {Buffer|string} imageBuffer - Image data as a Buffer, or a file path to an image
85
85
  * @param {import('../types').ScreenshotOptions} [options] - Options
86
86
  * @returns {Promise<void>}
87
+ * @throws {VizzlyError} When server is not running
88
+ * @throws {VizzlyError} When file path is provided but file doesn't exist
89
+ * @throws {VizzlyError} When file cannot be read due to permissions or I/O errors
87
90
  */
88
- screenshot(name: string, imageBuffer: Buffer, options?: any): Promise<void>;
91
+ screenshot(name: string, imageBuffer: Buffer | string, options?: any): Promise<void>;
89
92
  /**
90
93
  * Upload all captured screenshots
91
94
  * @param {import('../types').UploadOptions} [options] - Upload options
@@ -95,10 +98,12 @@ export class VizzlySDK {
95
98
  /**
96
99
  * Run local comparison in TDD mode
97
100
  * @param {string} name - Screenshot name
98
- * @param {Buffer} imageBuffer - Current image
101
+ * @param {Buffer|string} imageBuffer - Current image as a Buffer, or a file path to an image
99
102
  * @returns {Promise<import('../types').ComparisonResult>} Comparison result
103
+ * @throws {VizzlyError} When file path is provided but file doesn't exist
104
+ * @throws {VizzlyError} When file cannot be read due to permissions or I/O errors
100
105
  */
101
- compare(name: string, imageBuffer: Buffer): Promise<any>;
106
+ compare(name: string, imageBuffer: Buffer | string): Promise<any>;
102
107
  }
103
108
  export { loadConfig } from "../utils/config-loader.js";
104
109
  export { createLogger } from "../utils/logger.js";
@@ -11,9 +11,10 @@ export class ApiService {
11
11
  * Make an API request
12
12
  * @param {string} endpoint - API endpoint
13
13
  * @param {Object} options - Fetch options
14
+ * @param {boolean} isRetry - Internal flag to prevent infinite retry loops
14
15
  * @returns {Promise<Object>} Response data
15
16
  */
16
- request(endpoint: string, options?: any): Promise<any>;
17
+ request(endpoint: string, options?: any, isRetry?: boolean): Promise<any>;
17
18
  /**
18
19
  * Get build information
19
20
  * @param {string} buildId - Build ID
@@ -0,0 +1,59 @@
1
+ /**
2
+ * AuthService class for CLI authentication
3
+ */
4
+ export class AuthService {
5
+ constructor(options?: {});
6
+ baseUrl: any;
7
+ userAgent: string;
8
+ /**
9
+ * Make an unauthenticated API request
10
+ * @param {string} endpoint - API endpoint
11
+ * @param {Object} options - Fetch options
12
+ * @returns {Promise<Object>} Response data
13
+ */
14
+ request(endpoint: string, options?: any): Promise<any>;
15
+ /**
16
+ * Make an authenticated API request
17
+ * @param {string} endpoint - API endpoint
18
+ * @param {Object} options - Fetch options
19
+ * @returns {Promise<Object>} Response data
20
+ */
21
+ authenticatedRequest(endpoint: string, options?: any): Promise<any>;
22
+ /**
23
+ * Initiate OAuth device flow
24
+ * @returns {Promise<Object>} Device code, user code, verification URL
25
+ */
26
+ initiateDeviceFlow(): Promise<any>;
27
+ /**
28
+ * Poll for device authorization
29
+ * @param {string} deviceCode - Device code from initiate
30
+ * @returns {Promise<Object>} Token data or pending status
31
+ */
32
+ pollDeviceAuthorization(deviceCode: string): Promise<any>;
33
+ /**
34
+ * Complete device flow and save tokens
35
+ * @param {Object} tokenData - Token response from poll
36
+ * @returns {Promise<Object>} Token data with user info
37
+ */
38
+ completeDeviceFlow(tokenData: any): Promise<any>;
39
+ /**
40
+ * Refresh access token using refresh token
41
+ * @returns {Promise<Object>} New tokens
42
+ */
43
+ refresh(): Promise<any>;
44
+ /**
45
+ * Logout and revoke tokens
46
+ * @returns {Promise<void>}
47
+ */
48
+ logout(): Promise<void>;
49
+ /**
50
+ * Get current user information
51
+ * @returns {Promise<Object>} User and organization data
52
+ */
53
+ whoami(): Promise<any>;
54
+ /**
55
+ * Check if user is authenticated
56
+ * @returns {Promise<boolean>} True if authenticated
57
+ */
58
+ isAuthenticated(): Promise<boolean>;
59
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Open a URL in the default browser
3
+ * @param {string} url - URL to open
4
+ * @returns {Promise<boolean>} True if successful
5
+ */
6
+ export function openBrowser(url: string): Promise<boolean>;
@@ -19,7 +19,7 @@ export function loadConfig(configPath?: any, cliOverrides?: {}): Promise<{
19
19
  openReport: boolean;
20
20
  };
21
21
  plugins: any[];
22
- apiKey: string;
22
+ apiKey: any;
23
23
  apiUrl: string;
24
24
  }>;
25
25
  export function getScreenshotPaths(config: any): any[];
@@ -21,197 +21,31 @@ export let vizzlyConfigSchema: z.ZodDefault<z.ZodObject<{
21
21
  server: z.ZodDefault<z.ZodObject<{
22
22
  port: z.ZodDefault<z.ZodNumber>;
23
23
  timeout: z.ZodDefault<z.ZodNumber>;
24
- }, "strip", z.ZodTypeAny, {
25
- port?: number;
26
- timeout?: number;
27
- }, {
28
- port?: number;
29
- timeout?: number;
30
- }>>;
24
+ }, z.core.$strip>>;
31
25
  build: z.ZodDefault<z.ZodObject<{
32
26
  name: z.ZodDefault<z.ZodString>;
33
27
  environment: z.ZodDefault<z.ZodString>;
34
28
  branch: z.ZodOptional<z.ZodString>;
35
29
  commit: z.ZodOptional<z.ZodString>;
36
30
  message: z.ZodOptional<z.ZodString>;
37
- }, "strip", z.ZodTypeAny, {
38
- name?: string;
39
- message?: string;
40
- environment?: string;
41
- branch?: string;
42
- commit?: string;
43
- }, {
44
- name?: string;
45
- message?: string;
46
- environment?: string;
47
- branch?: string;
48
- commit?: string;
49
- }>>;
31
+ }, z.core.$strip>>;
50
32
  upload: z.ZodDefault<z.ZodObject<{
51
- screenshotsDir: z.ZodDefault<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
33
+ screenshotsDir: z.ZodDefault<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
52
34
  batchSize: z.ZodDefault<z.ZodNumber>;
53
35
  timeout: z.ZodDefault<z.ZodNumber>;
54
- }, "strip", z.ZodTypeAny, {
55
- timeout?: number;
56
- screenshotsDir?: string | string[];
57
- batchSize?: number;
58
- }, {
59
- timeout?: number;
60
- screenshotsDir?: string | string[];
61
- batchSize?: number;
62
- }>>;
36
+ }, z.core.$strip>>;
63
37
  comparison: z.ZodDefault<z.ZodObject<{
64
38
  threshold: z.ZodDefault<z.ZodNumber>;
65
- }, "strip", z.ZodTypeAny, {
66
- threshold?: number;
67
- }, {
68
- threshold?: number;
69
- }>>;
39
+ }, z.core.$strip>>;
70
40
  tdd: z.ZodDefault<z.ZodObject<{
71
41
  openReport: z.ZodDefault<z.ZodBoolean>;
72
- }, "strip", z.ZodTypeAny, {
73
- openReport?: boolean;
74
- }, {
75
- openReport?: boolean;
76
- }>>;
77
- plugins: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
42
+ }, z.core.$strip>>;
43
+ plugins: z.ZodDefault<z.ZodArray<z.ZodString>>;
78
44
  parallelId: z.ZodOptional<z.ZodString>;
79
45
  baselineBuildId: z.ZodOptional<z.ZodString>;
80
46
  baselineComparisonId: z.ZodOptional<z.ZodString>;
81
47
  eager: z.ZodOptional<z.ZodBoolean>;
82
48
  wait: z.ZodOptional<z.ZodBoolean>;
83
49
  allowNoToken: z.ZodOptional<z.ZodBoolean>;
84
- }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
85
- apiKey: z.ZodOptional<z.ZodString>;
86
- apiUrl: z.ZodOptional<z.ZodString>;
87
- server: z.ZodDefault<z.ZodObject<{
88
- port: z.ZodDefault<z.ZodNumber>;
89
- timeout: z.ZodDefault<z.ZodNumber>;
90
- }, "strip", z.ZodTypeAny, {
91
- port?: number;
92
- timeout?: number;
93
- }, {
94
- port?: number;
95
- timeout?: number;
96
- }>>;
97
- build: z.ZodDefault<z.ZodObject<{
98
- name: z.ZodDefault<z.ZodString>;
99
- environment: z.ZodDefault<z.ZodString>;
100
- branch: z.ZodOptional<z.ZodString>;
101
- commit: z.ZodOptional<z.ZodString>;
102
- message: z.ZodOptional<z.ZodString>;
103
- }, "strip", z.ZodTypeAny, {
104
- name?: string;
105
- message?: string;
106
- environment?: string;
107
- branch?: string;
108
- commit?: string;
109
- }, {
110
- name?: string;
111
- message?: string;
112
- environment?: string;
113
- branch?: string;
114
- commit?: string;
115
- }>>;
116
- upload: z.ZodDefault<z.ZodObject<{
117
- screenshotsDir: z.ZodDefault<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
118
- batchSize: z.ZodDefault<z.ZodNumber>;
119
- timeout: z.ZodDefault<z.ZodNumber>;
120
- }, "strip", z.ZodTypeAny, {
121
- timeout?: number;
122
- screenshotsDir?: string | string[];
123
- batchSize?: number;
124
- }, {
125
- timeout?: number;
126
- screenshotsDir?: string | string[];
127
- batchSize?: number;
128
- }>>;
129
- comparison: z.ZodDefault<z.ZodObject<{
130
- threshold: z.ZodDefault<z.ZodNumber>;
131
- }, "strip", z.ZodTypeAny, {
132
- threshold?: number;
133
- }, {
134
- threshold?: number;
135
- }>>;
136
- tdd: z.ZodDefault<z.ZodObject<{
137
- openReport: z.ZodDefault<z.ZodBoolean>;
138
- }, "strip", z.ZodTypeAny, {
139
- openReport?: boolean;
140
- }, {
141
- openReport?: boolean;
142
- }>>;
143
- plugins: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
144
- parallelId: z.ZodOptional<z.ZodString>;
145
- baselineBuildId: z.ZodOptional<z.ZodString>;
146
- baselineComparisonId: z.ZodOptional<z.ZodString>;
147
- eager: z.ZodOptional<z.ZodBoolean>;
148
- wait: z.ZodOptional<z.ZodBoolean>;
149
- allowNoToken: z.ZodOptional<z.ZodBoolean>;
150
- }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
151
- apiKey: z.ZodOptional<z.ZodString>;
152
- apiUrl: z.ZodOptional<z.ZodString>;
153
- server: z.ZodDefault<z.ZodObject<{
154
- port: z.ZodDefault<z.ZodNumber>;
155
- timeout: z.ZodDefault<z.ZodNumber>;
156
- }, "strip", z.ZodTypeAny, {
157
- port?: number;
158
- timeout?: number;
159
- }, {
160
- port?: number;
161
- timeout?: number;
162
- }>>;
163
- build: z.ZodDefault<z.ZodObject<{
164
- name: z.ZodDefault<z.ZodString>;
165
- environment: z.ZodDefault<z.ZodString>;
166
- branch: z.ZodOptional<z.ZodString>;
167
- commit: z.ZodOptional<z.ZodString>;
168
- message: z.ZodOptional<z.ZodString>;
169
- }, "strip", z.ZodTypeAny, {
170
- name?: string;
171
- message?: string;
172
- environment?: string;
173
- branch?: string;
174
- commit?: string;
175
- }, {
176
- name?: string;
177
- message?: string;
178
- environment?: string;
179
- branch?: string;
180
- commit?: string;
181
- }>>;
182
- upload: z.ZodDefault<z.ZodObject<{
183
- screenshotsDir: z.ZodDefault<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
184
- batchSize: z.ZodDefault<z.ZodNumber>;
185
- timeout: z.ZodDefault<z.ZodNumber>;
186
- }, "strip", z.ZodTypeAny, {
187
- timeout?: number;
188
- screenshotsDir?: string | string[];
189
- batchSize?: number;
190
- }, {
191
- timeout?: number;
192
- screenshotsDir?: string | string[];
193
- batchSize?: number;
194
- }>>;
195
- comparison: z.ZodDefault<z.ZodObject<{
196
- threshold: z.ZodDefault<z.ZodNumber>;
197
- }, "strip", z.ZodTypeAny, {
198
- threshold?: number;
199
- }, {
200
- threshold?: number;
201
- }>>;
202
- tdd: z.ZodDefault<z.ZodObject<{
203
- openReport: z.ZodDefault<z.ZodBoolean>;
204
- }, "strip", z.ZodTypeAny, {
205
- openReport?: boolean;
206
- }, {
207
- openReport?: boolean;
208
- }>>;
209
- plugins: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
210
- parallelId: z.ZodOptional<z.ZodString>;
211
- baselineBuildId: z.ZodOptional<z.ZodString>;
212
- baselineComparisonId: z.ZodOptional<z.ZodString>;
213
- eager: z.ZodOptional<z.ZodBoolean>;
214
- wait: z.ZodOptional<z.ZodBoolean>;
215
- allowNoToken: z.ZodOptional<z.ZodBoolean>;
216
- }, z.ZodTypeAny, "passthrough">>>;
50
+ }, z.core.$loose>>;
217
51
  import { z } from 'zod';
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Resolve image buffer from file path or return buffer as-is
3
+ * Handles both Buffer inputs and file path strings, with proper validation and error handling
4
+ *
5
+ * @param {Buffer|string} imageBufferOrPath - Image data as Buffer or file path
6
+ * @param {string} contextName - Context for error messages (e.g., 'screenshot', 'compare')
7
+ * @returns {Buffer} The image buffer
8
+ * @throws {VizzlyError} When file not found, unreadable, or invalid input type
9
+ *
10
+ * @example
11
+ * // With Buffer
12
+ * const buffer = resolveImageBuffer(myBuffer, 'screenshot');
13
+ *
14
+ * @example
15
+ * // With file path
16
+ * const buffer = resolveImageBuffer('./my-image.png', 'screenshot');
17
+ */
18
+ export function resolveImageBuffer(imageBufferOrPath: Buffer | string, contextName: string): Buffer;
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Get the path to the global Vizzly directory
3
+ * @returns {string} Path to ~/.vizzly
4
+ */
5
+ export function getGlobalConfigDir(): string;
6
+ /**
7
+ * Get the path to the global config file
8
+ * @returns {string} Path to ~/.vizzly/config.json
9
+ */
10
+ export function getGlobalConfigPath(): string;
11
+ /**
12
+ * Load the global configuration
13
+ * @returns {Promise<Object>} Global config object
14
+ */
15
+ export function loadGlobalConfig(): Promise<any>;
16
+ /**
17
+ * Save the global configuration
18
+ * @param {Object} config - Configuration object to save
19
+ * @returns {Promise<void>}
20
+ */
21
+ export function saveGlobalConfig(config: any): Promise<void>;
22
+ /**
23
+ * Clear all global configuration
24
+ * @returns {Promise<void>}
25
+ */
26
+ export function clearGlobalConfig(): Promise<void>;
27
+ /**
28
+ * Get authentication tokens from global config
29
+ * @returns {Promise<Object|null>} Token object with accessToken, refreshToken, expiresAt, user, or null if not found
30
+ */
31
+ export function getAuthTokens(): Promise<any | null>;
32
+ /**
33
+ * Save authentication tokens to global config
34
+ * @param {Object} auth - Auth object with accessToken, refreshToken, expiresAt, user
35
+ * @returns {Promise<void>}
36
+ */
37
+ export function saveAuthTokens(auth: any): Promise<void>;
38
+ /**
39
+ * Clear authentication tokens from global config
40
+ * @returns {Promise<void>}
41
+ */
42
+ export function clearAuthTokens(): Promise<void>;
43
+ /**
44
+ * Check if authentication tokens exist and are not expired
45
+ * @returns {Promise<boolean>} True if valid tokens exist
46
+ */
47
+ export function hasValidTokens(): Promise<boolean>;
48
+ /**
49
+ * Get the access token from global config if available
50
+ * @returns {Promise<string|null>} Access token or null
51
+ */
52
+ export function getAccessToken(): Promise<string | null>;
53
+ /**
54
+ * Get project mapping for a directory
55
+ * Walks up the directory tree to find the closest mapping
56
+ * @param {string} directoryPath - Absolute path to project directory
57
+ * @returns {Promise<Object|null>} Project data or null
58
+ */
59
+ export function getProjectMapping(directoryPath: string): Promise<any | null>;
60
+ /**
61
+ * Save project mapping for a directory
62
+ * @param {string} directoryPath - Absolute path to project directory
63
+ * @param {Object} projectData - Project configuration
64
+ * @param {string} projectData.token - Project API token (vzt_...)
65
+ * @param {string} projectData.projectSlug - Project slug
66
+ * @param {string} projectData.organizationSlug - Organization slug
67
+ * @param {string} projectData.projectName - Project name
68
+ */
69
+ export function saveProjectMapping(directoryPath: string, projectData: {
70
+ token: string;
71
+ projectSlug: string;
72
+ organizationSlug: string;
73
+ projectName: string;
74
+ }): Promise<void>;
75
+ /**
76
+ * Get all project mappings
77
+ * @returns {Promise<Object>} Map of directory paths to project data
78
+ */
79
+ export function getProjectMappings(): Promise<any>;
80
+ /**
81
+ * Delete project mapping for a directory
82
+ * @param {string} directoryPath - Absolute path to project directory
83
+ */
84
+ export function deleteProjectMapping(directoryPath: string): Promise<void>;
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Browser utilities for opening URLs
3
+ */
4
+
5
+ import { execFile } from 'child_process';
6
+ import { platform } from 'os';
7
+
8
+ /**
9
+ * Open a URL in the default browser
10
+ * @param {string} url - URL to open
11
+ * @returns {Promise<boolean>} True if successful
12
+ */
13
+ export async function openBrowser(url) {
14
+ return new Promise(resolve => {
15
+ let command;
16
+ let args;
17
+ let os = platform();
18
+ switch (os) {
19
+ case 'darwin':
20
+ // macOS
21
+ command = 'open';
22
+ args = [url];
23
+ break;
24
+ case 'win32':
25
+ // Windows
26
+ command = 'cmd.exe';
27
+ args = ['/c', 'start', '""', url];
28
+ break;
29
+ default:
30
+ // Linux and others
31
+ command = 'xdg-open';
32
+ args = [url];
33
+ break;
34
+ }
35
+ execFile(command, args, error => {
36
+ if (error) {
37
+ // Browser opening failed, but don't throw - user can manually open
38
+ resolve(false);
39
+ } else {
40
+ resolve(true);
41
+ }
42
+ });
43
+ });
44
+ }
@@ -2,9 +2,11 @@ import { cosmiconfigSync } from 'cosmiconfig';
2
2
  import { resolve } from 'path';
3
3
  import { getApiToken, getApiUrl, getParallelId } from './environment-config.js';
4
4
  import { validateVizzlyConfigWithDefaults } from './config-schema.js';
5
+ import { getAccessToken, getProjectMapping } from './global-config.js';
5
6
  const DEFAULT_CONFIG = {
6
7
  // API Configuration
7
- apiKey: getApiToken(),
8
+ apiKey: undefined,
9
+ // Will be set from env, global config, or CLI overrides
8
10
  apiUrl: getApiUrl(),
9
11
  // Server Configuration (for run command)
10
12
  server: {
@@ -70,16 +72,80 @@ export async function loadConfig(configPath = null, cliOverrides = {}) {
70
72
  // Merge validated file config
71
73
  mergeConfig(config, validatedFileConfig);
72
74
 
73
- // 3. Override with environment variables
75
+ // 3. Check project mapping for current directory (if no CLI flag)
76
+ if (!cliOverrides.token) {
77
+ const currentDir = process.cwd();
78
+ if (process.env.DEBUG_CONFIG) {
79
+ console.log('[CONFIG] Looking up project mapping for:', currentDir);
80
+ }
81
+ const projectMapping = await getProjectMapping(currentDir);
82
+ if (projectMapping && projectMapping.token) {
83
+ // Handle both string tokens and token objects (backward compatibility)
84
+ let token;
85
+ if (typeof projectMapping.token === 'string') {
86
+ token = projectMapping.token;
87
+ } else if (typeof projectMapping.token === 'object' && projectMapping.token.token) {
88
+ // Handle nested token object from old API responses
89
+ token = projectMapping.token.token;
90
+ } else {
91
+ token = String(projectMapping.token);
92
+ }
93
+ config.apiKey = token;
94
+ config.projectSlug = projectMapping.projectSlug;
95
+ config.organizationSlug = projectMapping.organizationSlug;
96
+
97
+ // Debug logging
98
+ if (process.env.DEBUG_CONFIG) {
99
+ console.log('[CONFIG] Found project mapping:', {
100
+ dir: currentDir,
101
+ projectSlug: projectMapping.projectSlug,
102
+ hasToken: !!projectMapping.token,
103
+ tokenType: typeof projectMapping.token,
104
+ tokenPrefix: token ? token.substring(0, 8) + '***' : 'none'
105
+ });
106
+ console.log('[CONFIG] Set config.apiKey to:', config.apiKey ? config.apiKey.substring(0, 8) + '***' : 'NONE');
107
+ }
108
+ } else if (process.env.DEBUG_CONFIG) {
109
+ console.log('[CONFIG] No project mapping found for:', currentDir);
110
+ }
111
+ }
112
+
113
+ // 3.5. Check global config for user access token (if no CLI flag)
114
+ if (!config.apiKey && !cliOverrides.token) {
115
+ const globalToken = await getAccessToken();
116
+ if (globalToken) {
117
+ config.apiKey = globalToken;
118
+ }
119
+ }
120
+
121
+ // 4. Override with environment variables (higher priority than fallbacks)
74
122
  const envApiKey = getApiToken();
75
123
  const envApiUrl = getApiUrl();
76
124
  const envParallelId = getParallelId();
125
+ if (process.env.DEBUG_CONFIG) {
126
+ console.log('[CONFIG] Step 4 - env vars:', JSON.stringify({
127
+ hasEnvApiKey: !!envApiKey,
128
+ envApiKeyPrefix: envApiKey ? envApiKey.substring(0, 8) + '***' : 'none',
129
+ configApiKeyBefore: config.apiKey ? config.apiKey.substring(0, 8) + '***' : 'NONE'
130
+ }));
131
+ }
77
132
  if (envApiKey) config.apiKey = envApiKey;
78
133
  if (envApiUrl !== 'https://app.vizzly.dev') config.apiUrl = envApiUrl;
79
134
  if (envParallelId) config.parallelId = envParallelId;
80
135
 
81
- // 4. Apply CLI overrides (highest priority)
136
+ // 5. Apply CLI overrides (highest priority)
137
+ if (process.env.DEBUG_CONFIG) {
138
+ console.log('[CONFIG] Step 5 - before CLI overrides:', {
139
+ configApiKey: config.apiKey ? config.apiKey.substring(0, 8) + '***' : 'NONE',
140
+ cliToken: cliOverrides.token ? cliOverrides.token.substring(0, 8) + '***' : 'none'
141
+ });
142
+ }
82
143
  applyCLIOverrides(config, cliOverrides);
144
+ if (process.env.DEBUG_CONFIG) {
145
+ console.log('[CONFIG] Step 6 - after CLI overrides:', {
146
+ configApiKey: config.apiKey ? config.apiKey.substring(0, 8) + '***' : 'NONE'
147
+ });
148
+ }
83
149
  return config;
84
150
  }
85
151