tlc-claude-code 1.4.9 → 1.5.2

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 (122) hide show
  1. package/CLAUDE.md +23 -0
  2. package/CODING-STANDARDS.md +408 -0
  3. package/bin/install.js +2 -0
  4. package/dashboard/dist/components/QualityGatePane.d.ts +38 -0
  5. package/dashboard/dist/components/QualityGatePane.js +31 -0
  6. package/dashboard/dist/components/QualityGatePane.test.d.ts +1 -0
  7. package/dashboard/dist/components/QualityGatePane.test.js +147 -0
  8. package/dashboard/dist/components/orchestration/AgentCard.d.ts +26 -0
  9. package/dashboard/dist/components/orchestration/AgentCard.js +60 -0
  10. package/dashboard/dist/components/orchestration/AgentCard.test.d.ts +1 -0
  11. package/dashboard/dist/components/orchestration/AgentCard.test.js +63 -0
  12. package/dashboard/dist/components/orchestration/AgentControls.d.ts +11 -0
  13. package/dashboard/dist/components/orchestration/AgentControls.js +20 -0
  14. package/dashboard/dist/components/orchestration/AgentControls.test.d.ts +1 -0
  15. package/dashboard/dist/components/orchestration/AgentControls.test.js +52 -0
  16. package/dashboard/dist/components/orchestration/AgentDetail.d.ts +35 -0
  17. package/dashboard/dist/components/orchestration/AgentDetail.js +37 -0
  18. package/dashboard/dist/components/orchestration/AgentDetail.test.d.ts +1 -0
  19. package/dashboard/dist/components/orchestration/AgentDetail.test.js +79 -0
  20. package/dashboard/dist/components/orchestration/AgentList.d.ts +31 -0
  21. package/dashboard/dist/components/orchestration/AgentList.js +47 -0
  22. package/dashboard/dist/components/orchestration/AgentList.test.d.ts +1 -0
  23. package/dashboard/dist/components/orchestration/AgentList.test.js +64 -0
  24. package/dashboard/dist/components/orchestration/CostMeter.d.ts +11 -0
  25. package/dashboard/dist/components/orchestration/CostMeter.js +28 -0
  26. package/dashboard/dist/components/orchestration/CostMeter.test.d.ts +1 -0
  27. package/dashboard/dist/components/orchestration/CostMeter.test.js +50 -0
  28. package/dashboard/dist/components/orchestration/ModelSelector.d.ts +20 -0
  29. package/dashboard/dist/components/orchestration/ModelSelector.js +12 -0
  30. package/dashboard/dist/components/orchestration/ModelSelector.test.d.ts +1 -0
  31. package/dashboard/dist/components/orchestration/ModelSelector.test.js +56 -0
  32. package/dashboard/dist/components/orchestration/OrchestrationDashboard.d.ts +28 -0
  33. package/dashboard/dist/components/orchestration/OrchestrationDashboard.js +28 -0
  34. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.d.ts +1 -0
  35. package/dashboard/dist/components/orchestration/OrchestrationDashboard.test.js +56 -0
  36. package/dashboard/dist/components/orchestration/QualityIndicator.d.ts +11 -0
  37. package/dashboard/dist/components/orchestration/QualityIndicator.js +37 -0
  38. package/dashboard/dist/components/orchestration/QualityIndicator.test.d.ts +1 -0
  39. package/dashboard/dist/components/orchestration/QualityIndicator.test.js +52 -0
  40. package/dashboard/dist/components/orchestration/index.d.ts +8 -0
  41. package/dashboard/dist/components/orchestration/index.js +8 -0
  42. package/package.json +1 -1
  43. package/server/lib/access-control.js +352 -0
  44. package/server/lib/access-control.test.js +322 -0
  45. package/server/lib/agents-cancel-command.js +139 -0
  46. package/server/lib/agents-cancel-command.test.js +180 -0
  47. package/server/lib/agents-get-command.js +159 -0
  48. package/server/lib/agents-get-command.test.js +167 -0
  49. package/server/lib/agents-list-command.js +150 -0
  50. package/server/lib/agents-list-command.test.js +149 -0
  51. package/server/lib/agents-logs-command.js +126 -0
  52. package/server/lib/agents-logs-command.test.js +198 -0
  53. package/server/lib/agents-retry-command.js +117 -0
  54. package/server/lib/agents-retry-command.test.js +192 -0
  55. package/server/lib/budget-limits.js +222 -0
  56. package/server/lib/budget-limits.test.js +214 -0
  57. package/server/lib/code-generator.js +291 -0
  58. package/server/lib/code-generator.test.js +307 -0
  59. package/server/lib/cost-command.js +290 -0
  60. package/server/lib/cost-command.test.js +202 -0
  61. package/server/lib/cost-optimizer.js +404 -0
  62. package/server/lib/cost-optimizer.test.js +232 -0
  63. package/server/lib/cost-projections.js +302 -0
  64. package/server/lib/cost-projections.test.js +217 -0
  65. package/server/lib/cost-reports.js +277 -0
  66. package/server/lib/cost-reports.test.js +254 -0
  67. package/server/lib/cost-tracker.js +216 -0
  68. package/server/lib/cost-tracker.test.js +302 -0
  69. package/server/lib/crypto-patterns.js +433 -0
  70. package/server/lib/crypto-patterns.test.js +346 -0
  71. package/server/lib/design-command.js +385 -0
  72. package/server/lib/design-command.test.js +249 -0
  73. package/server/lib/design-parser.js +237 -0
  74. package/server/lib/design-parser.test.js +290 -0
  75. package/server/lib/gemini-vision.js +377 -0
  76. package/server/lib/gemini-vision.test.js +282 -0
  77. package/server/lib/input-validator.js +360 -0
  78. package/server/lib/input-validator.test.js +295 -0
  79. package/server/lib/litellm-client.js +232 -0
  80. package/server/lib/litellm-client.test.js +267 -0
  81. package/server/lib/litellm-command.js +291 -0
  82. package/server/lib/litellm-command.test.js +260 -0
  83. package/server/lib/litellm-config.js +273 -0
  84. package/server/lib/litellm-config.test.js +212 -0
  85. package/server/lib/model-pricing.js +189 -0
  86. package/server/lib/model-pricing.test.js +178 -0
  87. package/server/lib/models-command.js +223 -0
  88. package/server/lib/models-command.test.js +193 -0
  89. package/server/lib/optimize-command.js +197 -0
  90. package/server/lib/optimize-command.test.js +193 -0
  91. package/server/lib/orchestration-integration.js +206 -0
  92. package/server/lib/orchestration-integration.test.js +235 -0
  93. package/server/lib/output-encoder.js +308 -0
  94. package/server/lib/output-encoder.test.js +312 -0
  95. package/server/lib/quality-evaluator.js +396 -0
  96. package/server/lib/quality-evaluator.test.js +337 -0
  97. package/server/lib/quality-gate-command.js +340 -0
  98. package/server/lib/quality-gate-command.test.js +321 -0
  99. package/server/lib/quality-gate-scorer.js +378 -0
  100. package/server/lib/quality-gate-scorer.test.js +376 -0
  101. package/server/lib/quality-history.js +265 -0
  102. package/server/lib/quality-history.test.js +359 -0
  103. package/server/lib/quality-presets.js +288 -0
  104. package/server/lib/quality-presets.test.js +269 -0
  105. package/server/lib/quality-retry.js +323 -0
  106. package/server/lib/quality-retry.test.js +325 -0
  107. package/server/lib/quality-thresholds.js +255 -0
  108. package/server/lib/quality-thresholds.test.js +237 -0
  109. package/server/lib/secure-auth.js +333 -0
  110. package/server/lib/secure-auth.test.js +288 -0
  111. package/server/lib/secure-code-command.js +540 -0
  112. package/server/lib/secure-code-command.test.js +309 -0
  113. package/server/lib/secure-errors.js +521 -0
  114. package/server/lib/secure-errors.test.js +298 -0
  115. package/server/lib/vision-command.js +372 -0
  116. package/server/lib/vision-command.test.js +255 -0
  117. package/server/lib/visual-command.js +350 -0
  118. package/server/lib/visual-command.test.js +256 -0
  119. package/server/lib/visual-testing.js +315 -0
  120. package/server/lib/visual-testing.test.js +357 -0
  121. package/server/package-lock.json +2 -2
  122. package/server/package.json +1 -1
@@ -0,0 +1,255 @@
1
+ /**
2
+ * Vision Command Tests
3
+ *
4
+ * CLI commands for vision operations
5
+ */
6
+
7
+ const { describe, it, beforeEach } = require('node:test');
8
+ const assert = require('node:assert');
9
+
10
+ const {
11
+ VisionCommand,
12
+ parseArgs,
13
+ formatAnalysis,
14
+ formatComparison,
15
+ formatAccessibilityReport,
16
+ } = require('./vision-command.js');
17
+
18
+ describe('Vision Command', () => {
19
+ let command;
20
+ let mockClient;
21
+
22
+ beforeEach(() => {
23
+ mockClient = {
24
+ _call: async () => ({ text: 'Mock response' }),
25
+ };
26
+
27
+ command = new VisionCommand({
28
+ client: mockClient,
29
+ });
30
+ });
31
+
32
+ describe('execute analyze', () => {
33
+ it('analyzes screenshot', async () => {
34
+ mockClient._call = async () => ({
35
+ description: 'A login form with email and password inputs.',
36
+ elements: ['input', 'button'],
37
+ });
38
+
39
+ const result = await command.execute('analyze /path/to/screenshot.png');
40
+
41
+ assert.ok(result.success);
42
+ assert.ok(result.output);
43
+ assert.ok(result.analysis);
44
+ });
45
+
46
+ it('accepts custom prompt', async () => {
47
+ let receivedPrompt;
48
+ mockClient._call = async (prompt) => {
49
+ receivedPrompt = prompt;
50
+ return { description: 'Custom analysis' };
51
+ };
52
+
53
+ await command.execute('analyze /path/to/image.png --prompt "Find all buttons"');
54
+
55
+ assert.ok(receivedPrompt.includes('buttons'));
56
+ });
57
+ });
58
+
59
+ describe('execute compare', () => {
60
+ it('compares before/after images', async () => {
61
+ mockClient._call = async () => ({
62
+ differences: [
63
+ { type: 'changed', description: 'Header color changed' },
64
+ ],
65
+ similarity: 0.92,
66
+ });
67
+
68
+ const result = await command.execute('compare /path/to/before.png /path/to/after.png');
69
+
70
+ assert.ok(result.success);
71
+ assert.ok(result.comparison);
72
+ assert.ok(result.comparison.differences);
73
+ });
74
+
75
+ it('reports when identical', async () => {
76
+ mockClient._call = async () => ({
77
+ differences: [],
78
+ similarity: 1.0,
79
+ });
80
+
81
+ const result = await command.execute('compare /path/to/a.png /path/to/b.png');
82
+
83
+ assert.ok(result.output.includes('identical') || result.output.includes('no diff'));
84
+ });
85
+ });
86
+
87
+ describe('execute a11y', () => {
88
+ it('runs accessibility audit', async () => {
89
+ mockClient._call = async () => ({
90
+ issues: [
91
+ { type: 'contrast', severity: 'high', description: 'Low contrast text' },
92
+ ],
93
+ score: 75,
94
+ });
95
+
96
+ const result = await command.execute('a11y /path/to/ui.png');
97
+
98
+ assert.ok(result.success);
99
+ assert.ok(result.audit);
100
+ assert.ok(result.audit.issues);
101
+ assert.ok(result.audit.score !== undefined);
102
+ });
103
+
104
+ it('formats issues by severity', async () => {
105
+ mockClient._call = async () => ({
106
+ issues: [
107
+ { type: 'contrast', severity: 'high', description: 'Issue 1' },
108
+ { type: 'touch', severity: 'low', description: 'Issue 2' },
109
+ ],
110
+ score: 80,
111
+ });
112
+
113
+ const result = await command.execute('a11y /path/to/ui.png');
114
+
115
+ assert.ok(result.output.includes('high') || result.output.includes('HIGH'));
116
+ });
117
+ });
118
+
119
+ describe('execute extract', () => {
120
+ it('extracts components from mockup', async () => {
121
+ mockClient._call = async () => ({
122
+ components: [
123
+ { type: 'button', label: 'Submit' },
124
+ { type: 'input', placeholder: 'Email' },
125
+ ],
126
+ });
127
+
128
+ const result = await command.execute('extract /path/to/mockup.png');
129
+
130
+ assert.ok(result.success);
131
+ assert.ok(result.components);
132
+ assert.ok(result.components.length > 0);
133
+ });
134
+
135
+ it('filters by component type', async () => {
136
+ mockClient._call = async () => ({
137
+ components: [
138
+ { type: 'button', label: 'Submit' },
139
+ ],
140
+ });
141
+
142
+ const result = await command.execute('extract /path/to/mockup.png --type button');
143
+
144
+ result.components.forEach(c => {
145
+ assert.strictEqual(c.type, 'button');
146
+ });
147
+ });
148
+ });
149
+
150
+ describe('parseArgs', () => {
151
+ it('parses analyze command', () => {
152
+ const parsed = parseArgs('analyze /path/to/image.png');
153
+
154
+ assert.strictEqual(parsed.command, 'analyze');
155
+ assert.strictEqual(parsed.imagePath, '/path/to/image.png');
156
+ });
157
+
158
+ it('parses compare command', () => {
159
+ const parsed = parseArgs('compare /path/to/before.png /path/to/after.png');
160
+
161
+ assert.strictEqual(parsed.command, 'compare');
162
+ assert.strictEqual(parsed.beforeImage, '/path/to/before.png');
163
+ assert.strictEqual(parsed.afterImage, '/path/to/after.png');
164
+ });
165
+
166
+ it('parses a11y command', () => {
167
+ const parsed = parseArgs('a11y /path/to/ui.png');
168
+
169
+ assert.strictEqual(parsed.command, 'a11y');
170
+ assert.strictEqual(parsed.imagePath, '/path/to/ui.png');
171
+ });
172
+
173
+ it('parses prompt flag', () => {
174
+ const parsed = parseArgs('analyze /image.png --prompt "Find buttons"');
175
+
176
+ assert.strictEqual(parsed.prompt, 'Find buttons');
177
+ });
178
+
179
+ it('parses type filter', () => {
180
+ const parsed = parseArgs('extract /mockup.png --type button');
181
+
182
+ assert.strictEqual(parsed.type, 'button');
183
+ });
184
+ });
185
+
186
+ describe('formatAnalysis', () => {
187
+ it('creates readable output', () => {
188
+ const analysis = {
189
+ description: 'A login form with two input fields and a button.',
190
+ elements: ['input:email', 'input:password', 'button:submit'],
191
+ };
192
+
193
+ const formatted = formatAnalysis(analysis);
194
+
195
+ assert.ok(typeof formatted === 'string');
196
+ assert.ok(formatted.includes('login') || formatted.includes('Login'));
197
+ });
198
+ });
199
+
200
+ describe('formatComparison', () => {
201
+ it('formats differences', () => {
202
+ const comparison = {
203
+ differences: [
204
+ { type: 'changed', description: 'Button color changed' },
205
+ { type: 'added', description: 'New icon added' },
206
+ ],
207
+ similarity: 0.85,
208
+ };
209
+
210
+ const formatted = formatComparison(comparison);
211
+
212
+ assert.ok(formatted.includes('changed') || formatted.includes('Changed'));
213
+ assert.ok(formatted.includes('85%') || formatted.includes('0.85'));
214
+ });
215
+
216
+ it('handles no differences', () => {
217
+ const comparison = {
218
+ differences: [],
219
+ similarity: 1.0,
220
+ };
221
+
222
+ const formatted = formatComparison(comparison);
223
+
224
+ assert.ok(formatted.includes('identical') || formatted.includes('100%'));
225
+ });
226
+ });
227
+
228
+ describe('formatAccessibilityReport', () => {
229
+ it('formats issues with severity', () => {
230
+ const audit = {
231
+ issues: [
232
+ { type: 'contrast', severity: 'high', description: 'Low contrast' },
233
+ { type: 'touch', severity: 'medium', description: 'Small button' },
234
+ ],
235
+ score: 70,
236
+ };
237
+
238
+ const formatted = formatAccessibilityReport(audit);
239
+
240
+ assert.ok(formatted.includes('70') || formatted.includes('Score'));
241
+ assert.ok(formatted.includes('contrast') || formatted.includes('Contrast'));
242
+ });
243
+
244
+ it('shows passing score', () => {
245
+ const audit = {
246
+ issues: [],
247
+ score: 100,
248
+ };
249
+
250
+ const formatted = formatAccessibilityReport(audit);
251
+
252
+ assert.ok(formatted.includes('100') || formatted.includes('pass'));
253
+ });
254
+ });
255
+ });
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Visual Command Module
3
+ *
4
+ * CLI for visual regression testing
5
+ */
6
+
7
+ const path = require('path');
8
+ const {
9
+ createBaseline,
10
+ updateBaseline,
11
+ runVisualTest,
12
+ formatVisualReport,
13
+ } = require('./visual-testing.js');
14
+
15
+ /**
16
+ * Parse command line arguments
17
+ * @param {string} input - Command input
18
+ * @returns {Object} Parsed arguments
19
+ */
20
+ function parseArgs(input) {
21
+ const parts = input.split(/\s+/);
22
+ const result = {
23
+ command: parts[0] || 'test',
24
+ };
25
+
26
+ let argIndex = 1;
27
+
28
+ // Handle positional name argument
29
+ if (parts[1] && !parts[1].startsWith('--')) {
30
+ result.name = parts[1];
31
+ argIndex = 2;
32
+ }
33
+
34
+ // Parse flags
35
+ for (let i = argIndex; i < parts.length; i++) {
36
+ const part = parts[i];
37
+
38
+ if (part === '--url' && parts[i + 1]) {
39
+ result.url = parts[i + 1];
40
+ i++;
41
+ } else if (part === '--viewport' && parts[i + 1]) {
42
+ result.viewport = parts[i + 1];
43
+ i++;
44
+ } else if (part === '--selector' && parts[i + 1]) {
45
+ result.selector = parts[i + 1];
46
+ i++;
47
+ } else if (part === '--threshold' && parts[i + 1]) {
48
+ result.threshold = parts[i + 1];
49
+ i++;
50
+ } else if (part === '--pattern' && parts[i + 1]) {
51
+ result.pattern = parts[i + 1];
52
+ i++;
53
+ }
54
+ }
55
+
56
+ return result;
57
+ }
58
+
59
+ /**
60
+ * Parse viewport string (e.g., "375x812")
61
+ * @param {string} viewport - Viewport string
62
+ * @returns {Object} Viewport object
63
+ */
64
+ function parseViewport(viewport) {
65
+ if (!viewport) return null;
66
+
67
+ const [width, height] = viewport.split('x').map(Number);
68
+ return { width, height };
69
+ }
70
+
71
+ /**
72
+ * Format test summary
73
+ * @param {Array} results - Test results
74
+ * @param {Object} [options] - Format options
75
+ * @returns {string} Formatted summary
76
+ */
77
+ function formatTestSummary(results, options = {}) {
78
+ const { showTiming = false } = options;
79
+
80
+ const passed = results.filter(r => r.pass).length;
81
+ const failed = results.filter(r => !r.pass).length;
82
+ const total = results.length;
83
+
84
+ const lines = [
85
+ 'Visual Test Summary',
86
+ '═'.repeat(40),
87
+ '',
88
+ `Total: ${total} | Pass: ${passed} | Fail: ${failed}`,
89
+ '',
90
+ ];
91
+
92
+ for (const result of results) {
93
+ const status = result.pass ? '✓ PASS' : '✗ FAIL';
94
+ let line = `${status} ${result.name}`;
95
+
96
+ if (result.similarity !== undefined && !result.pass) {
97
+ line += ` (${Math.round(result.similarity * 100)}% similar)`;
98
+ }
99
+
100
+ if (showTiming && result.duration) {
101
+ line += ` [${result.duration}ms]`;
102
+ }
103
+
104
+ lines.push(line);
105
+ }
106
+
107
+ return lines.join('\n');
108
+ }
109
+
110
+ /**
111
+ * Visual Command class
112
+ */
113
+ class VisualCommand {
114
+ /**
115
+ * Create a visual command handler
116
+ * @param {Object} options - Dependencies
117
+ * @param {Object} options.tester - Visual tester instance
118
+ */
119
+ constructor(options) {
120
+ this.tester = options.tester;
121
+ this.pendingApprovals = new Map();
122
+ }
123
+
124
+ /**
125
+ * Execute a command
126
+ * @param {string} input - Command input
127
+ * @returns {Promise<Object>} Execution result
128
+ */
129
+ async execute(input) {
130
+ const args = parseArgs(input);
131
+
132
+ switch (args.command) {
133
+ case 'baseline':
134
+ return this.executeBaseline(args);
135
+
136
+ case 'test':
137
+ return this.executeTest(args);
138
+
139
+ case 'approve':
140
+ return this.executeApprove(args);
141
+
142
+ case 'list':
143
+ return this.executeList(args);
144
+
145
+ case 'run':
146
+ return this.executeRun(args);
147
+
148
+ default:
149
+ return {
150
+ success: false,
151
+ output: `Unknown command: ${args.command}`,
152
+ };
153
+ }
154
+ }
155
+
156
+ /**
157
+ * Execute baseline command
158
+ * @param {Object} args - Parsed arguments
159
+ * @returns {Promise<Object>} Baseline result
160
+ */
161
+ async executeBaseline(args) {
162
+ try {
163
+ const viewport = parseViewport(args.viewport);
164
+
165
+ const baseline = await createBaseline(this.tester, {
166
+ name: args.name,
167
+ url: args.url,
168
+ viewport,
169
+ selector: args.selector,
170
+ });
171
+
172
+ return {
173
+ success: true,
174
+ output: `Baseline created: ${args.name}`,
175
+ baseline,
176
+ };
177
+ } catch (error) {
178
+ return {
179
+ success: false,
180
+ output: `Failed to create baseline: ${error.message}`,
181
+ error: error.message,
182
+ };
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Execute test command
188
+ * @param {Object} args - Parsed arguments
189
+ * @returns {Promise<Object>} Test result
190
+ */
191
+ async executeTest(args) {
192
+ try {
193
+ if (args.threshold) {
194
+ this.tester.threshold = parseFloat(args.threshold);
195
+ }
196
+
197
+ const result = await runVisualTest(this.tester, {
198
+ name: args.name,
199
+ url: args.url,
200
+ createIfMissing: false,
201
+ });
202
+
203
+ if (!result.pass) {
204
+ // Store for potential approval
205
+ this.pendingApprovals.set(args.name, {
206
+ url: args.url,
207
+ timestamp: new Date(),
208
+ });
209
+ }
210
+
211
+ return {
212
+ success: true,
213
+ pass: result.pass,
214
+ output: result.pass
215
+ ? `✓ Visual test passed: ${args.name}`
216
+ : `✗ Visual test failed: ${args.name} (${Math.round(result.similarity * 100)}% similar)`,
217
+ similarity: result.similarity,
218
+ differences: result.differences,
219
+ duration: result.duration,
220
+ };
221
+ } catch (error) {
222
+ return {
223
+ success: false,
224
+ pass: false,
225
+ output: `Test failed: ${error.message}`,
226
+ error: error.message,
227
+ };
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Execute approve command
233
+ * @param {Object} args - Parsed arguments
234
+ * @returns {Promise<Object>} Approval result
235
+ */
236
+ async executeApprove(args) {
237
+ try {
238
+ await updateBaseline(this.tester, {
239
+ name: args.name,
240
+ });
241
+
242
+ this.pendingApprovals.delete(args.name);
243
+
244
+ return {
245
+ success: true,
246
+ output: `Baseline updated: ${args.name}`,
247
+ };
248
+ } catch (error) {
249
+ return {
250
+ success: false,
251
+ output: `Failed to approve: ${error.message}`,
252
+ error: error.message,
253
+ };
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Execute list command
259
+ * @param {Object} args - Parsed arguments
260
+ * @returns {Promise<Object>} List result
261
+ */
262
+ async executeList(args) {
263
+ try {
264
+ const files = await this.tester._listFiles(this.tester.baselineDir);
265
+ const baselines = files.filter(f => f.endsWith('.png')).map(f => path.basename(f, '.png'));
266
+
267
+ const lines = [
268
+ 'Visual Baselines',
269
+ '═'.repeat(40),
270
+ '',
271
+ ...baselines.map(b => ` - ${b}`),
272
+ ];
273
+
274
+ return {
275
+ success: true,
276
+ output: lines.join('\n'),
277
+ baselines,
278
+ };
279
+ } catch (error) {
280
+ return {
281
+ success: false,
282
+ output: `Failed to list baselines: ${error.message}`,
283
+ baselines: [],
284
+ };
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Execute run command (all tests)
290
+ * @param {Object} args - Parsed arguments
291
+ * @returns {Promise<Object>} Run result
292
+ */
293
+ async executeRun(args) {
294
+ try {
295
+ const files = await this.tester._listFiles(this.tester.baselineDir);
296
+ let baselines = files.filter(f => f.endsWith('.png')).map(f => path.basename(f, '.png'));
297
+
298
+ // Filter by pattern if specified
299
+ if (args.pattern) {
300
+ baselines = baselines.filter(b => b.includes(args.pattern));
301
+ }
302
+
303
+ const results = [];
304
+
305
+ for (const name of baselines) {
306
+ // Load metadata for URL
307
+ const metadataPath = path.join(this.tester.baselineDir, `${name}.json`);
308
+ let url;
309
+
310
+ try {
311
+ const metadata = JSON.parse(await this.tester._readFile(metadataPath));
312
+ url = metadata.url;
313
+ } catch {
314
+ url = 'http://localhost:3000'; // Default
315
+ }
316
+
317
+ const result = await runVisualTest(this.tester, {
318
+ name,
319
+ url,
320
+ });
321
+
322
+ results.push(result);
323
+ }
324
+
325
+ return {
326
+ success: true,
327
+ output: formatVisualReport(results),
328
+ results,
329
+ summary: {
330
+ total: results.length,
331
+ passed: results.filter(r => r.pass).length,
332
+ failed: results.filter(r => !r.pass).length,
333
+ },
334
+ };
335
+ } catch (error) {
336
+ return {
337
+ success: false,
338
+ output: `Failed to run tests: ${error.message}`,
339
+ results: [],
340
+ };
341
+ }
342
+ }
343
+ }
344
+
345
+ module.exports = {
346
+ VisualCommand,
347
+ parseArgs,
348
+ parseViewport,
349
+ formatTestSummary,
350
+ };