@entro314labs/ai-changelog-generator 3.1.1 → 3.2.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +383 -877
  2. package/README.md +8 -3
  3. package/ai-changelog-mcp.sh +0 -0
  4. package/ai-changelog.sh +0 -0
  5. package/bin/ai-changelog-dxt.js +9 -9
  6. package/bin/ai-changelog-mcp.js +19 -17
  7. package/bin/ai-changelog.js +6 -6
  8. package/package.json +80 -48
  9. package/src/ai-changelog-generator.js +83 -81
  10. package/src/application/orchestrators/changelog.orchestrator.js +791 -516
  11. package/src/application/services/application.service.js +137 -128
  12. package/src/cli.js +76 -57
  13. package/src/domains/ai/ai-analysis.service.js +289 -209
  14. package/src/domains/analysis/analysis.engine.js +253 -193
  15. package/src/domains/changelog/changelog.service.js +1062 -784
  16. package/src/domains/changelog/workspace-changelog.service.js +420 -249
  17. package/src/domains/git/git-repository.analyzer.js +348 -258
  18. package/src/domains/git/git.service.js +132 -112
  19. package/src/infrastructure/cli/cli.controller.js +390 -274
  20. package/src/infrastructure/config/configuration.manager.js +220 -190
  21. package/src/infrastructure/interactive/interactive-staging.service.js +154 -135
  22. package/src/infrastructure/interactive/interactive-workflow.service.js +200 -159
  23. package/src/infrastructure/mcp/mcp-server.service.js +208 -207
  24. package/src/infrastructure/metrics/metrics.collector.js +140 -123
  25. package/src/infrastructure/providers/core/base-provider.js +87 -40
  26. package/src/infrastructure/providers/implementations/anthropic.js +101 -99
  27. package/src/infrastructure/providers/implementations/azure.js +124 -101
  28. package/src/infrastructure/providers/implementations/bedrock.js +136 -126
  29. package/src/infrastructure/providers/implementations/dummy.js +23 -23
  30. package/src/infrastructure/providers/implementations/google.js +123 -114
  31. package/src/infrastructure/providers/implementations/huggingface.js +94 -87
  32. package/src/infrastructure/providers/implementations/lmstudio.js +75 -60
  33. package/src/infrastructure/providers/implementations/mock.js +69 -73
  34. package/src/infrastructure/providers/implementations/ollama.js +89 -66
  35. package/src/infrastructure/providers/implementations/openai.js +88 -89
  36. package/src/infrastructure/providers/implementations/vertex.js +227 -197
  37. package/src/infrastructure/providers/provider-management.service.js +245 -207
  38. package/src/infrastructure/providers/provider-manager.service.js +145 -125
  39. package/src/infrastructure/providers/utils/base-provider-helpers.js +308 -302
  40. package/src/infrastructure/providers/utils/model-config.js +220 -195
  41. package/src/infrastructure/providers/utils/provider-utils.js +105 -100
  42. package/src/infrastructure/validation/commit-message-validation.service.js +259 -161
  43. package/src/shared/constants/colors.js +453 -180
  44. package/src/shared/utils/cli-demo.js +285 -0
  45. package/src/shared/utils/cli-entry-utils.js +257 -249
  46. package/src/shared/utils/cli-ui.js +447 -0
  47. package/src/shared/utils/diff-processor.js +513 -0
  48. package/src/shared/utils/error-classes.js +125 -156
  49. package/src/shared/utils/json-utils.js +93 -89
  50. package/src/shared/utils/utils.js +1117 -945
  51. package/types/index.d.ts +353 -344
@@ -4,10 +4,12 @@
4
4
  */
5
5
 
6
6
  // ANSI escape codes for colors and styles
7
+ import process from 'node:process'
8
+
7
9
  const ansiColors = {
8
10
  // Reset
9
11
  reset: '\x1b[0m',
10
-
12
+
11
13
  // Text colors
12
14
  black: '\x1b[30m',
13
15
  red: '\x1b[31m',
@@ -19,7 +21,7 @@ const ansiColors = {
19
21
  white: '\x1b[37m',
20
22
  gray: '\x1b[90m',
21
23
  grey: '\x1b[90m',
22
-
24
+
23
25
  // Bright colors
24
26
  redBright: '\x1b[91m',
25
27
  greenBright: '\x1b[92m',
@@ -28,175 +30,178 @@ const ansiColors = {
28
30
  magentaBright: '\x1b[95m',
29
31
  cyanBright: '\x1b[96m',
30
32
  whiteBright: '\x1b[97m',
31
-
33
+
32
34
  // Styles
33
35
  bold: '\x1b[1m',
34
36
  dim: '\x1b[2m',
35
37
  italic: '\x1b[3m',
36
38
  underline: '\x1b[4m',
37
39
  inverse: '\x1b[7m',
38
- strikethrough: '\x1b[9m'
39
- };
40
+ strikethrough: '\x1b[9m',
41
+ }
40
42
 
41
43
  /**
42
44
  * Color utility class
43
45
  */
44
46
  class Colors {
45
47
  constructor() {
46
- this.enabled = this.shouldEnableColors();
47
- this.setupColors();
48
+ this.enabled = this.shouldEnableColors()
49
+ this.setupColors()
48
50
  }
49
51
 
50
52
  shouldEnableColors() {
51
53
  // Don't use colors if explicitly disabled
52
54
  if (process.env.NO_COLOR || process.env.NODE_DISABLE_COLORS) {
53
- return false;
55
+ return false
54
56
  }
55
-
57
+
56
58
  // Don't use colors if not in a TTY (unless forced)
57
59
  if (process.stdout && process.stdout.isTTY === false) {
58
- return false;
60
+ return false
59
61
  }
60
-
62
+
61
63
  // Don't use colors in dumb terminals
62
64
  if (process.env.TERM === 'dumb') {
63
- return false;
65
+ return false
64
66
  }
65
-
67
+
66
68
  // Enable colors by default
67
- return true;
69
+ return true
68
70
  }
69
71
 
70
72
  colorize(color, text) {
71
- if (!this.enabled || !text) return text;
72
- return `${ansiColors[color] || ''}${text}${ansiColors.reset}`;
73
+ if (!(this.enabled && text)) {
74
+ return text
75
+ }
76
+ return `${ansiColors[color] || ''}${text}${ansiColors.reset}`
73
77
  }
74
78
 
75
79
  setupColors() {
76
80
  if (!this.enabled) {
77
- this.setupFallbackColors();
78
- return;
81
+ this.setupFallbackColors()
82
+ return
79
83
  }
80
84
 
81
85
  // Status colors
82
- this.success = (text) => this.colorize('green', text);
83
- this.error = (text) => this.colorize('red', text);
84
- this.warning = (text) => this.colorize('yellow', text);
85
- this.info = (text) => this.colorize('blue', text);
86
- this.secondary = (text) => this.colorize('gray', text);
87
- this.highlight = (text) => this.colorize('cyan', text);
88
- this.bold = (text) => this.colorize('bold', text);
89
- this.dim = (text) => this.colorize('dim', text);
86
+ this.success = (text) => this.colorize('green', text)
87
+ this.error = (text) => this.colorize('red', text)
88
+ this.warning = (text) => this.colorize('yellow', text)
89
+ this.info = (text) => this.colorize('blue', text)
90
+ this.secondary = (text) => this.colorize('gray', text)
91
+ this.highlight = (text) => this.colorize('cyan', text)
92
+ this.bold = (text) => this.colorize('bold', text)
93
+ this.dim = (text) => this.colorize('dim', text)
90
94
 
91
95
  // Semantic colors for changelog
92
- this.feature = (text) => this.colorize('greenBright', text);
93
- this.fix = (text) => this.colorize('redBright', text);
94
- this.security = (text) => this.colorize('magentaBright', text);
95
- this.breaking = (text) => this.colorize('bold', this.colorize('red', text));
96
- this.docs = (text) => this.colorize('blueBright', text);
97
- this.style = (text) => this.colorize('magenta', text);
98
- this.refactor = (text) => this.colorize('yellow', text);
99
- this.perf = (text) => this.colorize('cyan', text);
100
- this.test = (text) => this.colorize('blue', text);
101
- this.chore = (text) => this.colorize('gray', text);
96
+ this.feature = (text) => this.colorize('greenBright', text)
97
+ this.fix = (text) => this.colorize('redBright', text)
98
+ this.security = (text) => this.colorize('magentaBright', text)
99
+ this.breaking = (text) => this.colorize('bold', this.colorize('red', text))
100
+ this.docs = (text) => this.colorize('blueBright', text)
101
+ this.style = (text) => this.colorize('magenta', text)
102
+ this.refactor = (text) => this.colorize('yellow', text)
103
+ this.perf = (text) => this.colorize('cyan', text)
104
+ this.test = (text) => this.colorize('blue', text)
105
+ this.chore = (text) => this.colorize('gray', text)
102
106
 
103
107
  // UI elements
104
- this.header = (text) => this.colorize('underline', this.colorize('bold', text));
105
- this.subheader = (text) => this.colorize('bold', text);
106
- this.label = (text) => this.colorize('cyan', text);
107
- this.value = (text) => this.colorize('white', text);
108
- this.code = (text) => this.colorize('inverse', text);
109
- this.file = (text) => this.colorize('yellowBright', text);
110
- this.path = (text) => this.colorize('green', text);
111
- this.hash = (text) => this.colorize('magenta', text);
108
+ this.header = (text) => this.colorize('underline', this.colorize('bold', text))
109
+ this.subheader = (text) => this.colorize('bold', text)
110
+ this.label = (text) => this.colorize('cyan', text)
111
+ this.value = (text) => this.colorize('white', text)
112
+ this.code = (text) => this.colorize('inverse', text)
113
+ this.file = (text) => this.colorize('yellowBright', text)
114
+ this.path = (text) => this.colorize('green', text)
115
+ this.hash = (text) => this.colorize('magenta', text)
112
116
 
113
117
  // Metrics and stats
114
- this.metric = (text) => this.colorize('cyan', text);
115
- this.number = (text) => this.colorize('yellowBright', text);
116
- this.percentage = (text) => this.colorize('green', text);
118
+ this.metric = (text) => this.colorize('cyan', text)
119
+ this.number = (text) => this.colorize('yellowBright', text)
120
+ this.percentage = (text) => this.colorize('green', text)
117
121
 
118
122
  // Risk levels
119
- this.riskLow = (text) => this.colorize('green', text);
120
- this.riskMedium = (text) => this.colorize('yellow', text);
121
- this.riskHigh = (text) => this.colorize('red', text);
122
- this.riskCritical = (text) => this.colorize('inverse', this.colorize('bold', this.colorize('red', text)));
123
+ this.riskLow = (text) => this.colorize('green', text)
124
+ this.riskMedium = (text) => this.colorize('yellow', text)
125
+ this.riskHigh = (text) => this.colorize('red', text)
126
+ this.riskCritical = (text) =>
127
+ this.colorize('inverse', this.colorize('bold', this.colorize('red', text)))
123
128
 
124
129
  // Impact levels
125
- this.impactMinimal = (text) => this.colorize('gray', text);
126
- this.impactLow = (text) => this.colorize('blue', text);
127
- this.impactMedium = (text) => this.colorize('yellow', text);
128
- this.impactHigh = (text) => this.colorize('red', text);
129
- this.impactCritical = (text) => this.colorize('bold', this.colorize('red', text));
130
+ this.impactMinimal = (text) => this.colorize('gray', text)
131
+ this.impactLow = (text) => this.colorize('blue', text)
132
+ this.impactMedium = (text) => this.colorize('yellow', text)
133
+ this.impactHigh = (text) => this.colorize('red', text)
134
+ this.impactCritical = (text) => this.colorize('bold', this.colorize('red', text))
130
135
  }
131
136
 
132
137
  setupFallbackColors() {
133
138
  // Fallback to identity functions when colors are disabled
134
- const identity = (text) => text;
135
-
139
+ const identity = (text) => text
140
+
136
141
  // Status colors
137
- this.success = identity;
138
- this.error = identity;
139
- this.warning = identity;
140
- this.info = identity;
141
- this.secondary = identity;
142
- this.highlight = identity;
143
- this.bold = identity;
144
- this.dim = identity;
142
+ this.success = identity
143
+ this.error = identity
144
+ this.warning = identity
145
+ this.info = identity
146
+ this.secondary = identity
147
+ this.highlight = identity
148
+ this.bold = identity
149
+ this.dim = identity
145
150
 
146
151
  // Semantic colors for changelog
147
- this.feature = identity;
148
- this.fix = identity;
149
- this.security = identity;
150
- this.breaking = identity;
151
- this.docs = identity;
152
- this.style = identity;
153
- this.refactor = identity;
154
- this.perf = identity;
155
- this.test = identity;
156
- this.chore = identity;
152
+ this.feature = identity
153
+ this.fix = identity
154
+ this.security = identity
155
+ this.breaking = identity
156
+ this.docs = identity
157
+ this.style = identity
158
+ this.refactor = identity
159
+ this.perf = identity
160
+ this.test = identity
161
+ this.chore = identity
157
162
 
158
163
  // UI elements
159
- this.header = identity;
160
- this.subheader = identity;
161
- this.label = identity;
162
- this.value = identity;
163
- this.code = identity;
164
- this.file = identity;
165
- this.path = identity;
166
- this.hash = identity;
164
+ this.header = identity
165
+ this.subheader = identity
166
+ this.label = identity
167
+ this.value = identity
168
+ this.code = identity
169
+ this.file = identity
170
+ this.path = identity
171
+ this.hash = identity
167
172
 
168
173
  // Metrics and stats
169
- this.metric = identity;
170
- this.number = identity;
171
- this.percentage = identity;
174
+ this.metric = identity
175
+ this.number = identity
176
+ this.percentage = identity
172
177
 
173
178
  // Risk levels
174
- this.riskLow = identity;
175
- this.riskMedium = identity;
176
- this.riskHigh = identity;
177
- this.riskCritical = identity;
179
+ this.riskLow = identity
180
+ this.riskMedium = identity
181
+ this.riskHigh = identity
182
+ this.riskCritical = identity
178
183
 
179
184
  // Impact levels
180
- this.impactMinimal = identity;
181
- this.impactLow = identity;
182
- this.impactMedium = identity;
183
- this.impactHigh = identity;
184
- this.impactCritical = identity;
185
+ this.impactMinimal = identity
186
+ this.impactLow = identity
187
+ this.impactMedium = identity
188
+ this.impactHigh = identity
189
+ this.impactCritical = identity
185
190
  }
186
191
 
187
192
  disable() {
188
- this.enabled = false;
189
- this.setupFallbackColors();
193
+ this.enabled = false
194
+ this.setupFallbackColors()
190
195
  }
191
196
 
192
197
  enable() {
193
- this.enabled = true;
194
- this.setupColors();
198
+ this.enabled = true
199
+ this.setupColors()
195
200
  }
196
201
 
197
202
  // Utility methods for common patterns
198
203
  emoji(text) {
199
- return text; // Emojis work without colors
204
+ return text // Emojis work without colors
200
205
  }
201
206
 
202
207
  status(type, message) {
@@ -204,11 +209,11 @@ class Colors {
204
209
  success: this.success,
205
210
  error: this.error,
206
211
  warning: this.warning,
207
- info: this.info
208
- };
209
-
210
- const color = colorMap[type] || this.info;
211
- return color(message);
212
+ info: this.info,
213
+ }
214
+
215
+ const color = colorMap[type] || this.info
216
+ return color(message)
212
217
  }
213
218
 
214
219
  commitType(type) {
@@ -222,10 +227,10 @@ class Colors {
222
227
  refactor: this.refactor,
223
228
  perf: this.perf,
224
229
  test: this.test,
225
- chore: this.chore
226
- };
227
-
228
- return (colorMap[type] || this.secondary)(type);
230
+ chore: this.chore,
231
+ }
232
+
233
+ return (colorMap[type] || this.secondary)(type)
229
234
  }
230
235
 
231
236
  risk(level) {
@@ -233,10 +238,10 @@ class Colors {
233
238
  low: this.riskLow,
234
239
  medium: this.riskMedium,
235
240
  high: this.riskHigh,
236
- critical: this.riskCritical
237
- };
238
-
239
- return (colorMap[level] || this.secondary)(level.toUpperCase());
241
+ critical: this.riskCritical,
242
+ }
243
+
244
+ return (colorMap[level] || this.secondary)(level.toUpperCase())
240
245
  }
241
246
 
242
247
  impact(level) {
@@ -245,148 +250,416 @@ class Colors {
245
250
  low: this.impactLow,
246
251
  medium: this.impactMedium,
247
252
  high: this.impactHigh,
248
- critical: this.impactCritical
249
- };
250
-
251
- return (colorMap[level] || this.secondary)(level);
253
+ critical: this.impactCritical,
254
+ }
255
+
256
+ return (colorMap[level] || this.secondary)(level)
252
257
  }
253
258
 
254
259
  // Diff highlighting
255
260
  diffAdd(text) {
256
- return this.success(`+ ${text}`);
261
+ return this.success(`+ ${text}`)
257
262
  }
258
263
 
259
264
  diffRemove(text) {
260
- return this.error(`- ${text}`);
265
+ return this.error(`- ${text}`)
261
266
  }
262
267
 
263
268
  diffContext(text) {
264
- return this.dim(` ${text}`);
269
+ return this.dim(` ${text}`)
265
270
  }
266
271
 
267
272
  // Progress indicators
268
273
  progress(current, total, label = '') {
269
- const percentage = Math.round((current / total) * 100);
270
- const bar = '█'.repeat(Math.round(percentage / 5)) + ''.repeat(20 - Math.round(percentage / 5));
271
- return `${this.info(`[${bar}]`)} ${this.percentage(`${percentage}%`)} ${label}`;
274
+ if (total <= 0) {
275
+ return `${this.info('[░░░░░░░░░░░░░░░░░░░░]')} ${this.percentage('0%')} ${label}`
276
+ }
277
+
278
+ const percentage = Math.max(0, Math.min(100, Math.round((current / total) * 100)))
279
+ const filledBars = Math.max(0, Math.round(percentage / 5))
280
+ const emptyBars = Math.max(0, 20 - filledBars)
281
+ const bar = '█'.repeat(filledBars) + '░'.repeat(emptyBars)
282
+ return `${this.info(`[${bar}]`)} ${this.percentage(`${percentage}%`)} ${label}`
272
283
  }
273
284
 
274
285
  // Helper to get actual visible length of string (without ANSI codes)
275
286
  getVisibleLength(str) {
276
287
  // Remove ANSI escape sequences to get actual visible length
277
- return str.replace(/\x1b\[[0-9;]*m/g, '').length;
288
+ return str.replace(/\x1b\[[0-9;]*m/g, '').length
278
289
  }
279
290
 
280
291
  // Box drawing for sections with dynamic width calculation
281
292
  box(title, content, minWidth = 60) {
282
- const lines = content.split('\n');
283
- const titleVisibleLength = this.getVisibleLength(title);
284
-
293
+ const lines = content.split('\n')
294
+ const titleVisibleLength = this.getVisibleLength(title)
295
+
285
296
  // Calculate required width based on content
286
297
  const maxContentLength = Math.max(
287
298
  titleVisibleLength + 4, // title + padding
288
- ...lines.map(line => this.getVisibleLength(line) + 4), // content + padding
299
+ ...lines.map((line) => this.getVisibleLength(line) + 4), // content + padding
289
300
  minWidth
290
- );
291
-
292
- const width = Math.min(maxContentLength, 80); // Cap at 80 chars for readability
293
-
294
- const topBorder = '┌' + '─'.repeat(width - 2) + '┐';
295
- const bottomBorder = '└' + '─'.repeat(width - 2) + '┘';
296
-
301
+ )
302
+
303
+ const width = Math.min(maxContentLength, 80) // Cap at 80 chars for readability
304
+
305
+ const topBorder = `┌${'─'.repeat(width - 2)}┐`
306
+ const bottomBorder = `└${'─'.repeat(width - 2)}┘`
307
+
297
308
  // Title line with proper padding accounting for ANSI codes
298
- const titlePadding = width - titleVisibleLength - 3;
299
- const titleLine = `│ ${this.header(title)}${' '.repeat(Math.max(0, titlePadding))}│`;
300
-
301
- const contentLines = lines.map(line => {
302
- const visibleLength = this.getVisibleLength(line);
303
- const padding = width - visibleLength - 4;
304
- return `│ ${line}${' '.repeat(Math.max(0, padding))} │`;
305
- });
309
+ const titlePadding = width - titleVisibleLength - 3
310
+ const titleLine = `│ ${this.header(title)}${' '.repeat(Math.max(0, titlePadding))}│`
311
+
312
+ const contentLines = lines.map((line) => {
313
+ const visibleLength = this.getVisibleLength(line)
314
+ const padding = width - visibleLength - 4
315
+ return `│ ${line}${' '.repeat(Math.max(0, padding))} │`
316
+ })
306
317
 
307
318
  return [
308
319
  this.secondary(topBorder),
309
320
  titleLine,
310
- this.secondary('├' + '─'.repeat(width - 2) + '┤'),
321
+ this.secondary(`├${'─'.repeat(width - 2)}┤`),
311
322
  ...contentLines,
312
- this.secondary(bottomBorder)
313
- ].join('\n');
323
+ this.secondary(bottomBorder),
324
+ ].join('\n')
314
325
  }
315
326
 
316
327
  // Quick access to common formatted messages
317
328
  successMessage(message) {
318
- return `${this.success('✅')} ${message}`;
329
+ return `${this.success('✅')} ${message}`
319
330
  }
320
331
 
321
332
  errorMessage(message) {
322
- return `${this.error('❌')} ${message}`;
333
+ return `${this.error('❌')} ${message}`
323
334
  }
324
335
 
325
336
  warningMessage(message) {
326
- return `${this.warning('⚠️')} ${message}`;
337
+ return `${this.warning('⚠️')} ${message}`
327
338
  }
328
339
 
329
340
  infoMessage(message) {
330
- return `${this.info('ℹ️')} ${message}`;
341
+ return `${this.info('ℹ️')} ${message}`
331
342
  }
332
343
 
333
344
  processingMessage(message) {
334
- return `${this.highlight('🔍')} ${message}`;
345
+ return `${this.highlight('🔍')} ${message}`
335
346
  }
336
347
 
337
348
  aiMessage(message) {
338
- return `${this.highlight('🤖')} ${message}`;
349
+ return `${this.highlight('🤖')} ${message}`
339
350
  }
340
351
 
341
352
  metricsMessage(message) {
342
- return `${this.metric('📊')} ${message}`;
353
+ return `${this.metric('📊')} ${message}`
343
354
  }
344
355
 
345
356
  separator(char = '─', length = 50) {
346
- return this.dim(char.repeat(length));
357
+ return this.dim(char.repeat(length))
347
358
  }
348
359
 
349
360
  sectionHeader(text) {
350
- return this.header(text);
361
+ return this.header(text)
351
362
  }
352
363
 
353
364
  // Format file lists with syntax highlighting
354
365
  formatFileList(files, maxDisplay = 10) {
355
- const displayed = files.slice(0, maxDisplay);
356
- const result = displayed.map(file => {
357
- const ext = file.split('.').pop()?.toLowerCase();
358
- let color = this.file;
359
-
366
+ const displayed = files.slice(0, maxDisplay)
367
+ const result = displayed.map((file) => {
368
+ const ext = file.split('.').pop()?.toLowerCase()
369
+ let color = this.file
370
+
360
371
  // Color by file type
361
- if (['ts', 'tsx', 'js', 'jsx'].includes(ext)) color = this.feature;
362
- else if (['css', 'scss', 'sass'].includes(ext)) color = this.style;
363
- else if (['md', 'txt'].includes(ext)) color = this.docs;
364
- else if (['json', 'yaml', 'yml'].includes(ext)) color = this.warning;
365
- else if (['sql'].includes(ext)) color = this.fix;
366
-
367
- return ` - ${color(file)}`;
368
- });
372
+ if (['ts', 'tsx', 'js', 'jsx'].includes(ext)) {
373
+ color = this.feature
374
+ } else if (['css', 'scss', 'sass'].includes(ext)) {
375
+ color = this.style
376
+ } else if (['md', 'txt'].includes(ext)) {
377
+ color = this.docs
378
+ } else if (['json', 'yaml', 'yml'].includes(ext)) {
379
+ color = this.warning
380
+ } else if (['sql'].includes(ext)) {
381
+ color = this.fix
382
+ }
383
+
384
+ return ` - ${color(file)}`
385
+ })
369
386
 
370
387
  if (files.length > maxDisplay) {
371
- result.push(` ${this.dim(`... and ${files.length - maxDisplay} more`)}`);
388
+ result.push(` ${this.dim(`... and ${files.length - maxDisplay} more`)}`)
372
389
  }
373
390
 
374
- return result.join('\n');
391
+ return result.join('\n')
375
392
  }
376
393
 
377
394
  // Format metrics table
378
395
  formatMetrics(metrics) {
379
- const entries = Object.entries(metrics);
380
- const maxKeyLength = Math.max(...entries.map(([k]) => k.length));
381
-
382
- return entries.map(([key, value]) => {
383
- const paddedKey = key.padEnd(maxKeyLength);
384
- return `${this.label(paddedKey)}: ${this.value(value)}`;
385
- }).join('\n');
396
+ const entries = Object.entries(metrics)
397
+ const maxKeyLength = Math.max(...entries.map(([k]) => k.length))
398
+
399
+ return entries
400
+ .map(([key, value]) => {
401
+ const paddedKey = key.padEnd(maxKeyLength)
402
+ return `${this.label(paddedKey)}: ${this.value(value)}`
403
+ })
404
+ .join('\n')
405
+ }
406
+
407
+ // Unicode symbols for cross-platform compatibility
408
+ get symbols() {
409
+ return {
410
+ success: '✓',
411
+ error: '✗',
412
+ warning: '⚠',
413
+ info: 'ℹ',
414
+ arrow: '→',
415
+ bullet: '•',
416
+ check: '✓',
417
+ cross: '✗',
418
+ star: '★',
419
+ heart: '♥',
420
+ diamond: '♦',
421
+ circle: '●',
422
+ square: '■',
423
+ triangle: '▲',
424
+ play: '▶',
425
+ pause: '⏸',
426
+ stop: '⏹',
427
+ refresh: '↻',
428
+ sync: '⟲',
429
+ upload: '↑',
430
+ download: '↓',
431
+ plus: '+',
432
+ minus: '-',
433
+ multiply: '×',
434
+ divide: '÷',
435
+ equals: '=',
436
+ pipe: '|',
437
+ hash: '#',
438
+ at: '@',
439
+ dollar: '$',
440
+ percent: '%',
441
+ ampersand: '&',
442
+ question: '?',
443
+ exclamation: '!',
444
+ ellipsis: '…',
445
+ middot: '·',
446
+ section: '§',
447
+ paragraph: '¶',
448
+ copyright: '©',
449
+ registered: '®',
450
+ trademark: '™',
451
+ degree: '°',
452
+ plusminus: '±',
453
+ micro: 'µ',
454
+ alpha: 'α',
455
+ beta: 'β',
456
+ gamma: 'γ',
457
+ delta: 'δ',
458
+ lambda: 'λ',
459
+ mu: 'μ',
460
+ pi: 'π',
461
+ sigma: 'σ',
462
+ tau: 'τ',
463
+ phi: 'φ',
464
+ omega: 'ω',
465
+ }
466
+ }
467
+
468
+ // Enhanced status messages with symbols
469
+ statusSymbol(type, message) {
470
+ const symbolMap = {
471
+ success: this.success(this.symbols.success),
472
+ error: this.error(this.symbols.error),
473
+ warning: this.warning(this.symbols.warning),
474
+ info: this.info(this.symbols.info),
475
+ processing: this.highlight(this.symbols.refresh),
476
+ ai: this.highlight('🤖'),
477
+ metrics: this.metric('📊'),
478
+ }
479
+
480
+ const symbol = symbolMap[type] || symbolMap.info
481
+ return `${symbol} ${message}`
482
+ }
483
+
484
+ // Gradient text support (will work with gradient-string if available)
485
+ async gradient(text, colors = ['#FF6B6B', '#4ECDC4']) {
486
+ try {
487
+ // Try to use gradient-string if available
488
+ const { default: gradient } = await import('gradient-string')
489
+ return gradient(colors)(text)
490
+ } catch {
491
+ // Fallback to regular coloring
492
+ return this.highlight(text)
493
+ }
494
+ }
495
+
496
+ // Spinner-like text animation frames
497
+ get spinnerFrames() {
498
+ return ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
499
+ }
500
+
501
+ // Loading dots animation frames
502
+ get dotsFrames() {
503
+ return [' ', '. ', '.. ', '...']
504
+ }
505
+
506
+ // Enhanced box with options
507
+ boxed(content, options = {}) {
508
+ const {
509
+ title = '',
510
+ padding = 1,
511
+ margin = 0,
512
+ borderStyle = 'single',
513
+ borderColor = 'secondary',
514
+ titleColor = 'header',
515
+ } = options
516
+
517
+ const borders = {
518
+ single: {
519
+ top: '─',
520
+ bottom: '─',
521
+ left: '│',
522
+ right: '│',
523
+ topLeft: '┌',
524
+ topRight: '┐',
525
+ bottomLeft: '└',
526
+ bottomRight: '┘',
527
+ },
528
+ double: {
529
+ top: '═',
530
+ bottom: '═',
531
+ left: '║',
532
+ right: '║',
533
+ topLeft: '╔',
534
+ topRight: '╗',
535
+ bottomLeft: '╚',
536
+ bottomRight: '╝',
537
+ },
538
+ rounded: {
539
+ top: '─',
540
+ bottom: '─',
541
+ left: '│',
542
+ right: '│',
543
+ topLeft: '╭',
544
+ topRight: '╮',
545
+ bottomLeft: '╰',
546
+ bottomRight: '╯',
547
+ },
548
+ }
549
+
550
+ const border = borders[borderStyle] || borders.single
551
+ const lines = content.split('\n')
552
+ const maxContentLength = Math.max(...lines.map((line) => this.getVisibleLength(line)))
553
+ const titleLength = title ? this.getVisibleLength(title) + 2 : 0
554
+ const width = Math.max(maxContentLength, titleLength) + padding * 2
555
+
556
+ const colorBorder = this[borderColor] || this.secondary
557
+ const colorTitle = this[titleColor] || this.header
558
+
559
+ const result = []
560
+
561
+ // Top margin
562
+ if (margin > 0) {
563
+ result.push(
564
+ ''
565
+ .repeat(margin)
566
+ .split('')
567
+ .map(() => '')
568
+ .join('\n')
569
+ )
570
+ }
571
+
572
+ // Top border
573
+ if (title) {
574
+ const titlePadding = Math.max(0, width - titleLength)
575
+ result.push(
576
+ colorBorder(border.topLeft + border.top.repeat(2)) +
577
+ ` ${colorTitle(title)} ` +
578
+ colorBorder(border.top.repeat(titlePadding - 2) + border.topRight)
579
+ )
580
+ } else {
581
+ result.push(colorBorder(border.topLeft + border.top.repeat(width) + border.topRight))
582
+ }
583
+
584
+ // Content
585
+ lines.forEach((line) => {
586
+ const contentPadding = Math.max(0, width - this.getVisibleLength(line))
587
+ result.push(
588
+ colorBorder(border.left) +
589
+ ' '.repeat(padding) +
590
+ line +
591
+ ' '.repeat(contentPadding - padding) +
592
+ colorBorder(border.right)
593
+ )
594
+ })
595
+
596
+ // Bottom border
597
+ result.push(colorBorder(border.bottomLeft + border.bottom.repeat(width) + border.bottomRight))
598
+
599
+ // Bottom margin
600
+ if (margin > 0) {
601
+ result.push(
602
+ ''
603
+ .repeat(margin)
604
+ .split('')
605
+ .map(() => '')
606
+ .join('\n')
607
+ )
608
+ }
609
+
610
+ return result.join('\n')
611
+ }
612
+
613
+ // Table formatting helper
614
+ table(data, options = {}) {
615
+ if (!Array.isArray(data) || data.length === 0) {
616
+ return ''
617
+ }
618
+
619
+ const { headers = Object.keys(data[0]), align = 'left', padding = 1 } = options
620
+ const rows = data.map((row) => headers.map((header) => String(row[header] || '')))
621
+
622
+ // Calculate column widths
623
+ const colWidths = headers.map((header, i) =>
624
+ Math.max(header.length, ...rows.map((row) => this.getVisibleLength(row[i])))
625
+ )
626
+
627
+ const formatRow = (row, isHeader = false) => {
628
+ const cells = row.map((cell, i) => {
629
+ const width = colWidths[i]
630
+ const visibleLength = this.getVisibleLength(String(cell))
631
+ const paddingNeeded = width - visibleLength
632
+
633
+ if (align === 'right') {
634
+ return ' '.repeat(paddingNeeded) + cell
635
+ }
636
+ if (align === 'center') {
637
+ const leftPad = Math.floor(paddingNeeded / 2)
638
+ const rightPad = paddingNeeded - leftPad
639
+ return ' '.repeat(leftPad) + cell + ' '.repeat(rightPad)
640
+ }
641
+ return cell + ' '.repeat(paddingNeeded)
642
+ })
643
+
644
+ const colorFunc = isHeader ? this.header : this.secondary
645
+ return colorFunc('│') + cells.map((cell) => ` ${cell} `).join(colorFunc('│')) + colorFunc('│')
646
+ }
647
+
648
+ const separator = this.secondary(`├${colWidths.map((w) => '─'.repeat(w + 2)).join('┼')}┤`)
649
+ const topBorder = this.secondary(`┌${colWidths.map((w) => '─'.repeat(w + 2)).join('┬')}┐`)
650
+ const bottomBorder = this.secondary(`└${colWidths.map((w) => '─'.repeat(w + 2)).join('┴')}┘`)
651
+
652
+ return [
653
+ topBorder,
654
+ formatRow(headers, true),
655
+ separator,
656
+ ...rows.map((row) => formatRow(row)),
657
+ bottomBorder,
658
+ ].join('\n')
386
659
  }
387
660
  }
388
661
 
389
662
  // Export singleton instance
390
- const colors = new Colors();
663
+ const colors = new Colors()
391
664
 
392
- export default colors;
665
+ export default colors