@vizzly-testing/cli 0.20.0 → 0.20.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/dist/api/client.js +134 -0
  2. package/dist/api/core.js +341 -0
  3. package/dist/api/endpoints.js +314 -0
  4. package/dist/api/index.js +19 -0
  5. package/dist/auth/client.js +91 -0
  6. package/dist/auth/core.js +176 -0
  7. package/dist/auth/index.js +30 -0
  8. package/dist/auth/operations.js +148 -0
  9. package/dist/cli.js +178 -3
  10. package/dist/client/index.js +144 -77
  11. package/dist/commands/doctor.js +121 -36
  12. package/dist/commands/finalize.js +49 -18
  13. package/dist/commands/init.js +13 -18
  14. package/dist/commands/login.js +49 -55
  15. package/dist/commands/logout.js +17 -9
  16. package/dist/commands/project.js +100 -71
  17. package/dist/commands/run.js +189 -95
  18. package/dist/commands/status.js +101 -66
  19. package/dist/commands/tdd-daemon.js +61 -32
  20. package/dist/commands/tdd.js +104 -98
  21. package/dist/commands/upload.js +78 -34
  22. package/dist/commands/whoami.js +44 -42
  23. package/dist/config/core.js +438 -0
  24. package/dist/config/index.js +13 -0
  25. package/dist/config/operations.js +327 -0
  26. package/dist/index.js +1 -1
  27. package/dist/project/core.js +295 -0
  28. package/dist/project/index.js +13 -0
  29. package/dist/project/operations.js +393 -0
  30. package/dist/reporter/reporter-bundle.css +1 -1
  31. package/dist/reporter/reporter-bundle.iife.js +16 -16
  32. package/dist/screenshot-server/core.js +157 -0
  33. package/dist/screenshot-server/index.js +11 -0
  34. package/dist/screenshot-server/operations.js +183 -0
  35. package/dist/sdk/index.js +3 -2
  36. package/dist/server/handlers/api-handler.js +14 -5
  37. package/dist/server/handlers/tdd-handler.js +191 -53
  38. package/dist/server/http-server.js +9 -3
  39. package/dist/server/routers/baseline.js +58 -0
  40. package/dist/server/routers/dashboard.js +10 -6
  41. package/dist/server/routers/screenshot.js +32 -0
  42. package/dist/server-manager/core.js +186 -0
  43. package/dist/server-manager/index.js +81 -0
  44. package/dist/server-manager/operations.js +209 -0
  45. package/dist/services/build-manager.js +2 -69
  46. package/dist/services/index.js +21 -48
  47. package/dist/services/screenshot-server.js +40 -74
  48. package/dist/services/server-manager.js +45 -80
  49. package/dist/services/test-runner.js +90 -250
  50. package/dist/services/uploader.js +56 -358
  51. package/dist/tdd/core/hotspot-coverage.js +112 -0
  52. package/dist/tdd/core/signature.js +101 -0
  53. package/dist/tdd/index.js +19 -0
  54. package/dist/tdd/metadata/baseline-metadata.js +103 -0
  55. package/dist/tdd/metadata/hotspot-metadata.js +93 -0
  56. package/dist/tdd/services/baseline-downloader.js +151 -0
  57. package/dist/tdd/services/baseline-manager.js +166 -0
  58. package/dist/tdd/services/comparison-service.js +230 -0
  59. package/dist/tdd/services/hotspot-service.js +71 -0
  60. package/dist/tdd/services/result-service.js +123 -0
  61. package/dist/tdd/tdd-service.js +1145 -0
  62. package/dist/test-runner/core.js +255 -0
  63. package/dist/test-runner/index.js +13 -0
  64. package/dist/test-runner/operations.js +483 -0
  65. package/dist/types/client.d.ts +25 -2
  66. package/dist/uploader/core.js +396 -0
  67. package/dist/uploader/index.js +11 -0
  68. package/dist/uploader/operations.js +412 -0
  69. package/dist/utils/colors.js +187 -39
  70. package/dist/utils/config-loader.js +3 -6
  71. package/dist/utils/context.js +228 -0
  72. package/dist/utils/output.js +449 -14
  73. package/docs/api-reference.md +173 -8
  74. package/docs/tui-elements.md +560 -0
  75. package/package.json +13 -13
  76. package/dist/services/api-service.js +0 -412
  77. package/dist/services/auth-service.js +0 -226
  78. package/dist/services/config-service.js +0 -369
  79. package/dist/services/html-report-generator.js +0 -455
  80. package/dist/services/project-service.js +0 -326
  81. package/dist/services/report-generator/report.css +0 -411
  82. package/dist/services/report-generator/viewer.js +0 -102
  83. package/dist/services/static-report-generator.js +0 -207
  84. package/dist/services/tdd-service.js +0 -1437
@@ -0,0 +1,412 @@
1
+ /**
2
+ * Uploader Operations - I/O operations with dependency injection
3
+ *
4
+ * Each operation takes its dependencies as parameters for testability.
5
+ */
6
+
7
+ import { buildBuildInfo, buildCompletedProgress, buildDeduplicationProgress, buildFileMetadata, buildProcessingProgress, buildScanningProgress, buildScreenshotPattern, buildUploadingProgress, buildUploadResult, buildWaitResult, DEFAULT_SHA_CHECK_BATCH_SIZE, extractStatusCodeFromError, fileToScreenshotFormat, getElapsedTime, isTimedOut, partitionFilesByExistence, validateApiKey, validateDirectoryStats, validateFilesFound, validateScreenshotsDir } from './core.js';
8
+
9
+ // ============================================================================
10
+ // File Discovery
11
+ // ============================================================================
12
+
13
+ /**
14
+ * Find all PNG screenshots in a directory
15
+ * @param {Object} options - Options
16
+ * @param {string} options.directory - Directory to search
17
+ * @param {Object} options.deps - Dependencies
18
+ * @param {Function} options.deps.glob - Glob function
19
+ * @returns {Promise<Array<string>>} Array of file paths
20
+ */
21
+ export async function findScreenshots({
22
+ directory,
23
+ deps
24
+ }) {
25
+ let {
26
+ glob
27
+ } = deps;
28
+ let pattern = buildScreenshotPattern(directory);
29
+ return glob(pattern, {
30
+ absolute: true
31
+ });
32
+ }
33
+
34
+ // ============================================================================
35
+ // File Processing
36
+ // ============================================================================
37
+
38
+ /**
39
+ * Process files to extract metadata and compute hashes
40
+ * @param {Object} options - Options
41
+ * @param {Array<string>} options.files - File paths
42
+ * @param {AbortSignal} options.signal - Abort signal
43
+ * @param {Function} options.onProgress - Progress callback
44
+ * @param {Object} options.deps - Dependencies
45
+ * @param {Function} options.deps.readFile - File read function
46
+ * @param {Function} options.deps.createError - Error factory
47
+ * @returns {Promise<Array>} File metadata array
48
+ */
49
+ export async function processFiles({
50
+ files,
51
+ signal,
52
+ onProgress,
53
+ deps
54
+ }) {
55
+ let {
56
+ readFile,
57
+ createError
58
+ } = deps;
59
+ let results = [];
60
+ let count = 0;
61
+ for (let filePath of files) {
62
+ if (signal.aborted) {
63
+ throw createError('Operation cancelled', 'UPLOAD_CANCELLED');
64
+ }
65
+ let buffer = await readFile(filePath);
66
+ let metadata = buildFileMetadata(filePath, buffer);
67
+ results.push(metadata);
68
+ count++;
69
+ if (count % 10 === 0 || count === files.length) {
70
+ onProgress(count);
71
+ }
72
+ }
73
+ return results;
74
+ }
75
+
76
+ // ============================================================================
77
+ // SHA Checking / Deduplication
78
+ // ============================================================================
79
+
80
+ /**
81
+ * Check which files already exist on the server
82
+ * @param {Object} options - Options
83
+ * @param {Array} options.fileMetadata - File metadata array
84
+ * @param {Object} options.client - API client
85
+ * @param {AbortSignal} options.signal - Abort signal
86
+ * @param {string} options.buildId - Build ID
87
+ * @param {Object} options.deps - Dependencies
88
+ * @param {Function} options.deps.checkShas - SHA check API function
89
+ * @param {Function} options.deps.createError - Error factory
90
+ * @param {Object} options.deps.output - Output utilities
91
+ * @returns {Promise<{ toUpload: Array, existing: Array, screenshots: Array }>}
92
+ */
93
+ export async function checkExistingFiles({
94
+ fileMetadata,
95
+ client,
96
+ signal,
97
+ buildId,
98
+ deps
99
+ }) {
100
+ let {
101
+ checkShas,
102
+ createError,
103
+ output
104
+ } = deps;
105
+ let existingShas = new Set();
106
+ let allScreenshots = [];
107
+ for (let i = 0; i < fileMetadata.length; i += DEFAULT_SHA_CHECK_BATCH_SIZE) {
108
+ if (signal.aborted) {
109
+ throw createError('Operation cancelled', 'UPLOAD_CANCELLED');
110
+ }
111
+ let batch = fileMetadata.slice(i, i + DEFAULT_SHA_CHECK_BATCH_SIZE);
112
+ let screenshotBatch = batch.map(fileToScreenshotFormat);
113
+ try {
114
+ let res = await checkShas(client, screenshotBatch, buildId);
115
+ let {
116
+ existing = [],
117
+ screenshots = []
118
+ } = res || {};
119
+ for (let sha of existing) {
120
+ existingShas.add(sha);
121
+ }
122
+ allScreenshots.push(...screenshots);
123
+ } catch (error) {
124
+ output.debug('upload', 'SHA check failed, continuing without deduplication', {
125
+ error: error.message
126
+ });
127
+ }
128
+ }
129
+ let partitioned = partitionFilesByExistence(fileMetadata, existingShas);
130
+ return {
131
+ toUpload: partitioned.toUpload,
132
+ existing: partitioned.existing,
133
+ screenshots: allScreenshots
134
+ };
135
+ }
136
+
137
+ // ============================================================================
138
+ // File Upload
139
+ // ============================================================================
140
+
141
+ /**
142
+ * Upload files to Vizzly
143
+ * @param {Object} options - Options
144
+ * @param {Array} options.toUpload - Files to upload
145
+ * @param {string} options.buildId - Build ID
146
+ * @param {Object} options.client - API client
147
+ * @param {AbortSignal} options.signal - Abort signal
148
+ * @param {number} options.batchSize - Batch size
149
+ * @param {Function} options.onProgress - Progress callback
150
+ * @param {Object} options.deps - Dependencies
151
+ * @param {Function} options.deps.createError - Error factory
152
+ * @returns {Promise<{ buildId: string, url: string|null }>}
153
+ */
154
+ export async function uploadFiles({
155
+ toUpload,
156
+ buildId,
157
+ client,
158
+ signal,
159
+ batchSize,
160
+ onProgress,
161
+ deps
162
+ }) {
163
+ let {
164
+ createError
165
+ } = deps;
166
+ let result = null;
167
+ if (toUpload.length === 0) {
168
+ return {
169
+ buildId,
170
+ url: null
171
+ };
172
+ }
173
+ for (let i = 0; i < toUpload.length; i += batchSize) {
174
+ if (signal.aborted) {
175
+ throw createError('Operation cancelled', 'UPLOAD_CANCELLED');
176
+ }
177
+ let batch = toUpload.slice(i, i + batchSize);
178
+ let form = new FormData();
179
+ form.append('build_id', buildId);
180
+ for (let file of batch) {
181
+ let blob = new Blob([file.buffer], {
182
+ type: 'image/png'
183
+ });
184
+ form.append('screenshots', blob, file.filename);
185
+ }
186
+ try {
187
+ result = await client.request('/api/sdk/upload', {
188
+ method: 'POST',
189
+ body: form,
190
+ signal,
191
+ headers: {}
192
+ });
193
+ } catch (err) {
194
+ throw createError(`Upload failed: ${err.message}`, 'UPLOAD_FAILED', {
195
+ batch: Math.floor(i / batchSize) + 1
196
+ });
197
+ }
198
+ onProgress(i + batch.length);
199
+ }
200
+ return {
201
+ buildId,
202
+ url: result?.build?.url || result?.url
203
+ };
204
+ }
205
+
206
+ // ============================================================================
207
+ // Build Waiting
208
+ // ============================================================================
209
+
210
+ /**
211
+ * Wait for a build to complete
212
+ * @param {Object} options - Options
213
+ * @param {string} options.buildId - Build ID
214
+ * @param {number} options.timeout - Timeout in ms
215
+ * @param {AbortSignal} options.signal - Abort signal
216
+ * @param {Object} options.client - API client
217
+ * @param {Object} options.deps - Dependencies
218
+ * @param {Function} options.deps.createError - Error factory
219
+ * @param {Function} options.deps.createTimeoutError - Timeout error factory
220
+ * @returns {Promise<Object>} Build result
221
+ */
222
+ export async function waitForBuild({
223
+ buildId,
224
+ timeout,
225
+ signal,
226
+ client,
227
+ deps
228
+ }) {
229
+ let {
230
+ createError,
231
+ createTimeoutError
232
+ } = deps;
233
+ let startTime = Date.now();
234
+ while (!isTimedOut(startTime, timeout)) {
235
+ if (signal.aborted) {
236
+ throw createError('Operation cancelled', 'UPLOAD_CANCELLED', {
237
+ buildId
238
+ });
239
+ }
240
+ let resp;
241
+ try {
242
+ resp = await client.request(`/api/sdk/builds/${buildId}`, {
243
+ signal
244
+ });
245
+ } catch (err) {
246
+ let code = extractStatusCodeFromError(err?.message);
247
+ throw createError(`Failed to check build status: ${code}`, 'BUILD_STATUS_FAILED');
248
+ }
249
+ let build = resp?.build ?? resp;
250
+ if (build.status === 'completed') {
251
+ return buildWaitResult(build);
252
+ }
253
+ if (build.status === 'failed') {
254
+ throw createError(`Build failed: ${build.error || 'Unknown error'}`, 'BUILD_FAILED');
255
+ }
256
+ }
257
+ throw createTimeoutError(`Build timed out after ${timeout}ms`, {
258
+ buildId,
259
+ timeout,
260
+ elapsed: getElapsedTime(startTime)
261
+ });
262
+ }
263
+
264
+ // ============================================================================
265
+ // Main Upload Operation
266
+ // ============================================================================
267
+
268
+ /**
269
+ * Upload screenshots to Vizzly
270
+ * @param {Object} options - Options
271
+ * @param {Object} options.uploadOptions - Upload options (screenshotsDir, buildName, etc.)
272
+ * @param {Object} options.config - Configuration
273
+ * @param {AbortSignal} options.signal - Abort signal
274
+ * @param {number} options.batchSize - Batch size
275
+ * @param {Object} options.deps - Dependencies
276
+ * @returns {Promise<Object>} Upload result
277
+ */
278
+ export async function upload({
279
+ uploadOptions,
280
+ config,
281
+ signal,
282
+ batchSize,
283
+ deps
284
+ }) {
285
+ let {
286
+ client,
287
+ createBuild,
288
+ getDefaultBranch,
289
+ glob,
290
+ readFile,
291
+ stat,
292
+ checkShas,
293
+ createError,
294
+ createValidationError,
295
+ createUploadError,
296
+ output
297
+ } = deps;
298
+ let {
299
+ screenshotsDir,
300
+ onProgress = () => {}
301
+ } = uploadOptions;
302
+ try {
303
+ // Validate API key
304
+ let apiKeyValidation = validateApiKey(config.apiKey);
305
+ if (!apiKeyValidation.valid) {
306
+ throw createValidationError(apiKeyValidation.error, {
307
+ config: {
308
+ apiKey: config.apiKey,
309
+ apiUrl: config.apiUrl
310
+ }
311
+ });
312
+ }
313
+
314
+ // Validate screenshots directory
315
+ let dirValidation = validateScreenshotsDir(screenshotsDir);
316
+ if (!dirValidation.valid) {
317
+ throw createValidationError(dirValidation.error);
318
+ }
319
+ let stats = await stat(screenshotsDir);
320
+ let statsValidation = validateDirectoryStats(stats, screenshotsDir);
321
+ if (!statsValidation.valid) {
322
+ throw createValidationError(statsValidation.error);
323
+ }
324
+
325
+ // Find screenshots
326
+ let files = await findScreenshots({
327
+ directory: screenshotsDir,
328
+ deps: {
329
+ glob
330
+ }
331
+ });
332
+ let filesValidation = validateFilesFound(files, screenshotsDir);
333
+ if (!filesValidation.valid) {
334
+ throw createUploadError(filesValidation.error, filesValidation.context);
335
+ }
336
+ onProgress(buildScanningProgress(files.length));
337
+
338
+ // Process files
339
+ let fileMetadata = await processFiles({
340
+ files,
341
+ signal,
342
+ onProgress: current => onProgress(buildProcessingProgress(current, files.length)),
343
+ deps: {
344
+ readFile,
345
+ createError
346
+ }
347
+ });
348
+
349
+ // Create build
350
+ let defaultBranch = await getDefaultBranch();
351
+ let buildInfo = buildBuildInfo(uploadOptions, defaultBranch);
352
+ let build = await createBuild(client, buildInfo);
353
+ let buildId = build.id;
354
+
355
+ // Check existing files
356
+ let {
357
+ toUpload,
358
+ existing,
359
+ screenshots
360
+ } = await checkExistingFiles({
361
+ fileMetadata,
362
+ client,
363
+ signal,
364
+ buildId,
365
+ deps: {
366
+ checkShas,
367
+ createError,
368
+ output
369
+ }
370
+ });
371
+ onProgress(buildDeduplicationProgress(toUpload.length, existing.length, files.length));
372
+
373
+ // Upload files
374
+ let result = await uploadFiles({
375
+ toUpload,
376
+ existing,
377
+ screenshots,
378
+ buildId,
379
+ buildInfo,
380
+ client,
381
+ signal,
382
+ batchSize,
383
+ onProgress: current => onProgress(buildUploadingProgress(current, toUpload.length)),
384
+ deps: {
385
+ createError
386
+ }
387
+ });
388
+ onProgress(buildCompletedProgress(result.buildId, result.url));
389
+ return buildUploadResult({
390
+ buildId: result.buildId,
391
+ url: result.url,
392
+ total: files.length,
393
+ uploaded: toUpload.length,
394
+ skipped: existing.length
395
+ });
396
+ } catch (error) {
397
+ output.debug('upload', 'failed', {
398
+ error: error.message
399
+ });
400
+
401
+ // Re-throw if already a VizzlyError
402
+ if (error.name?.includes('Error') && error.code) {
403
+ throw error;
404
+ }
405
+
406
+ // Wrap unknown errors
407
+ throw createUploadError(`Upload failed: ${error.message}`, {
408
+ originalError: error.message,
409
+ stack: error.stack
410
+ });
411
+ }
412
+ }
@@ -1,66 +1,214 @@
1
- // Zero-dependency color helper using raw ANSI codes.
2
- // Detects terminal color support and emits codes only when enabled.
1
+ // Color utility using ansis for rich terminal styling.
2
+ // Detects terminal color support and provides chainable color functions.
3
3
 
4
+ import ansis from 'ansis';
5
+
6
+ // =============================================================================
7
+ // Vizzly Observatory Design System Colors
8
+ // Aligned with @vizzly-testing/observatory color tokens
9
+ // =============================================================================
10
+
11
+ export let brand = {
12
+ // Primary brand color - Amber is Observatory's signature
13
+ amber: '#F59E0B',
14
+ // Primary brand, actions, highlights
15
+ amberLight: '#FBBF24',
16
+ // Hover states, emphasis
17
+
18
+ // Accent colors (semantic)
19
+ success: '#10B981',
20
+ // Approved, passed, active (--accent-success)
21
+ warning: '#F59E0B',
22
+ // Pending, attention (--accent-warning)
23
+ danger: '#EF4444',
24
+ // Rejected, failed, errors (--accent-danger)
25
+ info: '#3B82F6',
26
+ // Processing, informational (--accent-info)
27
+
28
+ // Surface colors (dark theme)
29
+ bg: '#0F172A',
30
+ // Page background (--vz-bg)
31
+ surface: '#1A2332',
32
+ // Cards, panels (--vz-surface)
33
+ elevated: '#1E293B',
34
+ // Dropdowns, modals (--vz-elevated)
35
+ border: '#374151',
36
+ // Primary borders (--vz-border)
37
+ borderSubtle: '#2D3748',
38
+ // Subtle dividers (--vz-border-subtle)
39
+
40
+ // Text hierarchy
41
+ textPrimary: '#FFFFFF',
42
+ // Headings, important (--text-primary)
43
+ textSecondary: '#9CA3AF',
44
+ // Body text (--text-secondary)
45
+ textTertiary: '#6B7280',
46
+ // Captions, metadata (--text-tertiary)
47
+ textMuted: '#4B5563',
48
+ // Disabled, placeholders (--text-muted)
49
+
50
+ // Legacy aliases (for backward compatibility)
51
+ green: '#10B981',
52
+ red: '#EF4444',
53
+ cyan: '#06B6D4',
54
+ // Still useful for links in terminals
55
+ slate: '#64748B',
56
+ dark: '#1E293B'
57
+ };
4
58
  function supportsColorDefault() {
5
59
  // Respect NO_COLOR: https://no-color.org/
6
60
  if ('NO_COLOR' in process.env) return false;
7
61
 
8
62
  // Respect FORCE_COLOR if set to a truthy value (except '0')
9
63
  if ('FORCE_COLOR' in process.env) {
10
- const v = process.env.FORCE_COLOR;
64
+ let v = process.env.FORCE_COLOR;
11
65
  if (v && v !== '0') return true;
12
66
  if (v === '0') return false;
13
67
  }
14
68
 
69
+ // COLORTERM indicates truecolor support
70
+ if (process.env.COLORTERM === 'truecolor' || process.env.COLORTERM === '24bit') {
71
+ return true;
72
+ }
73
+
15
74
  // If stdout is not a TTY, assume no color
16
75
  if (!process.stdout || !process.stdout.isTTY) return false;
17
76
 
18
77
  // Prefer getColorDepth when available
19
78
  try {
20
- const depth = typeof process.stdout.getColorDepth === 'function' ? process.stdout.getColorDepth() : 1;
79
+ let depth = typeof process.stdout.getColorDepth === 'function' ? process.stdout.getColorDepth() : 1;
21
80
  return depth && depth > 1;
22
81
  } catch {
23
82
  // Fallback heuristic
24
83
  return true;
25
84
  }
26
85
  }
27
- function styleFn(open, close, enabled) {
28
- return (input = '') => {
29
- const str = String(input);
30
- if (!enabled) return str;
31
- return open + str + close;
32
- };
33
- }
86
+
87
+ /**
88
+ * Create a colors API with optional color support detection
89
+ * @param {Object} options - Configuration options
90
+ * @param {boolean} [options.useColor] - Force color on/off (auto-detect if undefined)
91
+ * @returns {Object} Colors API with styling functions
92
+ */
34
93
  export function createColors(options = {}) {
35
- const enabled = options.useColor !== undefined ? !!options.useColor : supportsColorDefault();
36
- const codes = {
37
- reset: ['\x1b[0m', ''],
38
- bold: ['\x1b[1m', '\x1b[22m'],
39
- dim: ['\x1b[2m', '\x1b[22m'],
40
- italic: ['\x1b[3m', '\x1b[23m'],
41
- underline: ['\x1b[4m', '\x1b[24m'],
42
- strikethrough: ['\x1b[9m', '\x1b[29m'],
43
- red: ['\x1b[31m', '\x1b[39m'],
44
- green: ['\x1b[32m', '\x1b[39m'],
45
- yellow: ['\x1b[33m', '\x1b[39m'],
46
- blue: ['\x1b[34m', '\x1b[39m'],
47
- magenta: ['\x1b[35m', '\x1b[39m'],
48
- cyan: ['\x1b[36m', '\x1b[39m'],
49
- white: ['\x1b[37m', '\x1b[39m'],
50
- gray: ['\x1b[90m', '\x1b[39m']
51
- };
52
- const api = {};
53
- for (const [name, [open, close]] of Object.entries(codes)) {
54
- api[name] = styleFn(open, close || '\x1b[0m', enabled);
94
+ let enabled = options.useColor !== undefined ? !!options.useColor : supportsColorDefault();
95
+ if (!enabled) {
96
+ // Return no-op functions when color disabled
97
+ let noop = (input = '') => String(input);
98
+ return {
99
+ // Modifiers
100
+ reset: noop,
101
+ bold: noop,
102
+ dim: noop,
103
+ italic: noop,
104
+ underline: noop,
105
+ strikethrough: noop,
106
+ // Colors
107
+ red: noop,
108
+ green: noop,
109
+ yellow: noop,
110
+ blue: noop,
111
+ magenta: noop,
112
+ cyan: noop,
113
+ white: noop,
114
+ gray: noop,
115
+ black: noop,
116
+ // Semantic aliases
117
+ success: noop,
118
+ error: noop,
119
+ warning: noop,
120
+ info: noop,
121
+ // Extended colors (return noop factory for chaining)
122
+ rgb: () => noop,
123
+ hex: () => noop,
124
+ bgRgb: () => noop,
125
+ bgHex: () => noop,
126
+ // Observatory brand colors (noop versions)
127
+ brand: {
128
+ // Primary
129
+ amber: noop,
130
+ amberLight: noop,
131
+ // Semantic accents
132
+ success: noop,
133
+ warning: noop,
134
+ danger: noop,
135
+ info: noop,
136
+ // Text hierarchy
137
+ textPrimary: noop,
138
+ textSecondary: noop,
139
+ textTertiary: noop,
140
+ textMuted: noop,
141
+ // Background variants
142
+ bgAmber: noop,
143
+ bgSuccess: noop,
144
+ bgWarning: noop,
145
+ bgDanger: noop,
146
+ bgInfo: noop,
147
+ // Legacy aliases
148
+ green: noop,
149
+ red: noop,
150
+ cyan: noop,
151
+ slate: noop
152
+ }
153
+ };
55
154
  }
56
-
57
- // Semantic aliases
58
- api.success = api.green;
59
- api.error = api.red;
60
- api.warning = api.yellow;
61
- api.info = api.blue;
62
- return api;
155
+ return {
156
+ // Modifiers
157
+ reset: ansis.reset,
158
+ bold: ansis.bold,
159
+ dim: ansis.dim,
160
+ italic: ansis.italic,
161
+ underline: ansis.underline,
162
+ strikethrough: ansis.strikethrough,
163
+ // Basic ANSI colors (fallback)
164
+ red: ansis.red,
165
+ green: ansis.green,
166
+ yellow: ansis.yellow,
167
+ blue: ansis.blue,
168
+ magenta: ansis.magenta,
169
+ cyan: ansis.cyan,
170
+ white: ansis.white,
171
+ gray: ansis.gray,
172
+ black: ansis.black,
173
+ // Semantic aliases (basic)
174
+ success: ansis.green,
175
+ error: ansis.red,
176
+ warning: ansis.yellow,
177
+ info: ansis.blue,
178
+ // Extended colors for rich styling
179
+ rgb: ansis.rgb,
180
+ hex: ansis.hex,
181
+ bgRgb: ansis.bgRgb,
182
+ bgHex: ansis.bgHex,
183
+ // Observatory brand colors (Truecolor) - aligned with design system
184
+ brand: {
185
+ // Primary brand color
186
+ amber: ansis.hex(brand.amber),
187
+ amberLight: ansis.hex(brand.amberLight),
188
+ // Semantic accents
189
+ success: ansis.hex(brand.success),
190
+ warning: ansis.hex(brand.warning),
191
+ danger: ansis.hex(brand.danger),
192
+ info: ansis.hex(brand.info),
193
+ // Text hierarchy
194
+ textPrimary: ansis.hex(brand.textPrimary),
195
+ textSecondary: ansis.hex(brand.textSecondary),
196
+ textTertiary: ansis.hex(brand.textTertiary),
197
+ textMuted: ansis.hex(brand.textMuted),
198
+ // Background variants
199
+ bgAmber: ansis.bgHex(brand.amber),
200
+ bgSuccess: ansis.bgHex(brand.success),
201
+ bgWarning: ansis.bgHex(brand.warning),
202
+ bgDanger: ansis.bgHex(brand.danger),
203
+ bgInfo: ansis.bgHex(brand.info),
204
+ // Legacy aliases (backward compatibility)
205
+ green: ansis.hex(brand.green),
206
+ red: ansis.hex(brand.red),
207
+ cyan: ansis.hex(brand.cyan),
208
+ slate: ansis.hex(brand.slate)
209
+ }
210
+ };
63
211
  }
64
212
 
65
213
  // Default export with auto-detected color support
66
- export const colors = createColors();
214
+ export let colors = createColors();
@@ -91,10 +91,7 @@ export async function loadConfig(configPath = null, cliOverrides = {}) {
91
91
  config.apiKey = token;
92
92
  config.projectSlug = projectMapping.projectSlug;
93
93
  config.organizationSlug = projectMapping.organizationSlug;
94
- output.debug('Using project mapping', {
95
- project: projectMapping.projectSlug,
96
- org: projectMapping.organizationSlug
97
- });
94
+ output.debug('config', `linked to ${projectMapping.projectSlug} (${projectMapping.organizationSlug})`);
98
95
  }
99
96
  }
100
97
 
@@ -104,14 +101,14 @@ export async function loadConfig(configPath = null, cliOverrides = {}) {
104
101
  const envParallelId = getParallelId();
105
102
  if (envApiKey) {
106
103
  config.apiKey = envApiKey;
107
- output.debug('Using API token from environment');
104
+ output.debug('config', 'using token from environment');
108
105
  }
109
106
  if (envApiUrl !== 'https://app.vizzly.dev') config.apiUrl = envApiUrl;
110
107
  if (envParallelId) config.parallelId = envParallelId;
111
108
 
112
109
  // 5. Apply CLI overrides (highest priority)
113
110
  if (cliOverrides.token) {
114
- output.debug('Using API token from --token flag');
111
+ output.debug('config', 'using token from --token flag');
115
112
  }
116
113
  applyCLIOverrides(config, cliOverrides);
117
114
  return config;