@entro314labs/ai-changelog-generator 3.2.1 → 3.6.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 (36) hide show
  1. package/CHANGELOG.md +42 -2
  2. package/README.md +21 -1
  3. package/ai-changelog-mcp.sh +0 -0
  4. package/ai-changelog.sh +0 -0
  5. package/bin/ai-changelog-dxt.js +6 -3
  6. package/manifest.json +177 -0
  7. package/package.json +76 -81
  8. package/src/ai-changelog-generator.js +5 -4
  9. package/src/application/orchestrators/changelog.orchestrator.js +19 -203
  10. package/src/cli.js +16 -5
  11. package/src/domains/ai/ai-analysis.service.js +2 -0
  12. package/src/domains/analysis/analysis.engine.js +714 -37
  13. package/src/domains/changelog/changelog.service.js +623 -32
  14. package/src/domains/changelog/workspace-changelog.service.js +445 -622
  15. package/src/domains/git/commit-tagger.js +552 -0
  16. package/src/domains/git/git-manager.js +357 -0
  17. package/src/domains/git/git.service.js +865 -16
  18. package/src/infrastructure/cli/cli.controller.js +14 -9
  19. package/src/infrastructure/config/configuration.manager.js +25 -11
  20. package/src/infrastructure/interactive/interactive-workflow.service.js +8 -1
  21. package/src/infrastructure/mcp/mcp-server.service.js +105 -32
  22. package/src/infrastructure/providers/core/base-provider.js +1 -1
  23. package/src/infrastructure/providers/implementations/anthropic.js +16 -173
  24. package/src/infrastructure/providers/implementations/azure.js +16 -63
  25. package/src/infrastructure/providers/implementations/dummy.js +13 -16
  26. package/src/infrastructure/providers/implementations/mock.js +13 -26
  27. package/src/infrastructure/providers/implementations/ollama.js +12 -4
  28. package/src/infrastructure/providers/implementations/openai.js +13 -165
  29. package/src/infrastructure/providers/provider-management.service.js +126 -412
  30. package/src/infrastructure/providers/utils/base-provider-helpers.js +11 -0
  31. package/src/shared/utils/cli-ui.js +8 -10
  32. package/src/shared/utils/diff-processor.js +21 -19
  33. package/src/shared/utils/error-classes.js +33 -0
  34. package/src/shared/utils/utils.js +83 -63
  35. package/types/index.d.ts +61 -68
  36. package/src/domains/git/git-repository.analyzer.js +0 -678
@@ -1,453 +1,167 @@
1
- import process from 'node:process'
2
-
3
- import colors from '../../shared/constants/colors.js'
1
+ import { ProviderManagerService } from './provider-manager.service.js'
4
2
 
5
3
  /**
6
- * Provider Management Service
4
+ * ProviderManagementService - Alias for ProviderManagerService
7
5
  *
8
- * Extracts the missing provider management methods from main class:
9
- * - listProviders() (40 lines) - Provider listing functionality
10
- * - switchProvider() (40 lines) - Provider switching functionality
11
- * - configureProvider() (80 lines) - Provider configuration
12
- * - validateModels() (95 lines) - Model validation functionality
6
+ * This service manages AI provider lifecycle, configuration, and operations
7
+ * including registration, activation, validation, and testing of providers.
13
8
  */
14
- export class ProviderManagementService {
15
- constructor(providerManager) {
16
- this.providerManager = providerManager
9
+ export class ProviderManagementService extends ProviderManagerService {
10
+ constructor(config = {}, options = {}) {
11
+ super(config, options)
17
12
  }
18
13
 
19
- async listProviders(includeCapabilities = false) {
20
- console.log(colors.header('šŸ”Œ Available AI Providers:'))
21
- console.log('')
22
-
23
- try {
24
- if (!this.providerManager) {
25
- console.log(colors.errorMessage('Provider manager not available'))
26
- return
27
- }
28
-
29
- const providers = this.providerManager.getAllProviders()
30
- const activeProvider = this.providerManager.getActiveProvider()
31
-
32
- providers.forEach((provider) => {
33
- const isActive = activeProvider && provider.name === activeProvider.getName()
34
- const status = provider.available ? 'āœ… Available' : 'āŒ Not configured'
35
- const activeIndicator = isActive ? colors.success(' (ACTIVE)') : ''
36
-
37
- console.log(`${colors.highlight(provider.name.toUpperCase())}${activeIndicator}`)
38
- console.log(` ${colors.label('Status')}: ${status}`)
39
-
40
- if (includeCapabilities && provider.available && provider.capabilities) {
41
- console.log(
42
- ` ${colors.label('Capabilities')}: ${
43
- Object.entries(provider.capabilities)
44
- .filter(([_key, value]) => value === true)
45
- .map(([key]) => key)
46
- .join(', ') || 'Basic completion'
47
- }`
48
- )
49
- }
50
- console.log('')
51
- })
52
-
53
- console.log(colors.infoMessage(`Total providers: ${colors.number(providers.length)}`))
54
- console.log(
55
- colors.infoMessage(
56
- `Active provider: ${colors.highlight(activeProvider?.getName() || 'None')}`
57
- )
58
- )
59
-
60
- return {
61
- providers,
62
- activeProvider: activeProvider?.getName() || null,
63
- total: providers.length,
64
- }
65
- } catch (error) {
66
- console.error(colors.errorMessage(`Error listing providers: ${error.message}`))
67
- throw error
68
- }
14
+ /**
15
+ * Initialize the provider management service
16
+ * @returns {Promise<void>}
17
+ */
18
+ async initialize() {
19
+ await this.loadProviders()
20
+ await this.validateConfiguration()
69
21
  }
70
22
 
71
- async switchProvider(providerName, testConnection = false) {
72
- console.log(
73
- colors.processingMessage(
74
- `šŸ”„ Switching to provider: ${colors.highlight(providerName.toUpperCase())}`
75
- )
76
- )
77
-
78
- try {
79
- if (!this.providerManager) {
80
- console.log(colors.errorMessage('Provider manager not available'))
81
- return { success: false, error: 'Provider manager not available' }
82
- }
83
-
84
- // Switch provider
85
- const result = this.providerManager.switchProvider(providerName)
86
-
87
- if (result.success) {
88
- console.log(
89
- colors.successMessage(
90
- `āœ… Successfully switched to ${colors.highlight(providerName.toUpperCase())}`
91
- )
92
- )
93
-
94
- if (testConnection) {
95
- console.log(colors.processingMessage('🧪 Testing connection...'))
96
- const newProvider = this.providerManager.getActiveProvider()
97
- const testResult = await newProvider.testConnection()
98
-
99
- if (testResult.success) {
100
- console.log(colors.successMessage('āœ… Connection test passed'))
101
- if (testResult.model) {
102
- console.log(
103
- colors.infoMessage(
104
- ` ${colors.label('Model')}: ${colors.highlight(testResult.model)}`
105
- )
106
- )
107
- }
108
- return { success: true, provider: providerName, connectionTest: testResult }
109
- }
110
- console.log(colors.errorMessage(`āŒ Connection test failed: ${testResult.error}`))
111
- return { success: true, provider: providerName, connectionTest: testResult }
112
- }
113
-
114
- return { success: true, provider: providerName }
115
- }
116
- console.log(colors.errorMessage(`āŒ Failed to switch provider: ${result.error}`))
117
- return { success: false, error: result.error }
118
- } catch (error) {
119
- console.error(colors.errorMessage(`Error switching provider: ${error.message}`))
120
- return { success: false, error: error.message }
121
- }
23
+ /**
24
+ * Get all available providers
25
+ * @returns {Array} Array of provider instances
26
+ */
27
+ getAllProviders() {
28
+ return this.providers
122
29
  }
123
30
 
124
- async configureProvider(providerName = null, testConnection = false, showModels = false) {
125
- const activeProvider = this.providerManager?.getActiveProvider()
126
- const targetProvider = providerName || activeProvider?.getName()
127
-
128
- if (!targetProvider) {
129
- console.log(colors.errorMessage('No provider specified and no active provider found'))
130
- return { success: false, error: 'No provider specified' }
131
- }
132
-
133
- console.log(
134
- colors.header(`šŸ”§ Configuring ${colors.highlight(targetProvider.toUpperCase())} Provider:`)
135
- )
136
- console.log('')
31
+ /**
32
+ * Get provider by name
33
+ * @param {string} name - Provider name
34
+ * @returns {Object|null} Provider instance or null
35
+ */
36
+ getProviderByName(name) {
37
+ return this.findProviderByName(name)
38
+ }
137
39
 
40
+ /**
41
+ * Register a new provider
42
+ * @param {string} name - Provider name
43
+ * @param {Object} providerInstance - Provider instance
44
+ * @returns {boolean} Success status
45
+ */
46
+ registerProvider(name, providerInstance) {
138
47
  try {
139
- if (!this.providerManager) {
140
- console.log(colors.errorMessage('Provider manager not available'))
141
- return { success: false, error: 'Provider manager not available' }
142
- }
143
-
144
- const provider = this.providerManager.findProviderByName(targetProvider)
145
-
146
- if (!provider) {
147
- console.log(colors.errorMessage(`Provider '${targetProvider}' not found`))
148
- return { success: false, error: `Provider '${targetProvider}' not found` }
149
- }
150
-
151
- // Display current configuration status
152
- console.log(colors.subheader('šŸ“‹ Current Configuration:'))
153
- const config = provider.getConfiguration()
154
-
155
- Object.entries(config).forEach(([key, value]) => {
156
- const displayValue =
157
- key.toLowerCase().includes('key') || key.toLowerCase().includes('secret')
158
- ? value
159
- ? '***CONFIGURED***'
160
- : 'āŒ NOT SET'
161
- : value || 'āŒ NOT SET'
162
- console.log(` ${colors.label(key)}: ${displayValue}`)
48
+ this.providers.push({
49
+ name,
50
+ instance: providerInstance,
51
+ available: true,
163
52
  })
164
-
165
- // Show required environment variables
166
- console.log(colors.subheader('\nšŸ”‘ Required Environment Variables:'))
167
- const requiredVars = provider.getRequiredEnvVars()
168
- requiredVars.forEach((varName) => {
169
- const isSet = !!process.env[varName]
170
- const status = isSet ? 'āœ… SET' : 'āŒ NOT SET'
171
- console.log(` ${colors.label(varName)}: ${status}`)
172
- })
173
-
174
- // Configuration instructions
175
- console.log(colors.infoMessage('\nšŸ’” Configuration Instructions:'))
176
- console.log(' 1. Set the required environment variables in your .env.local file')
177
- console.log(' 2. Or export them in your shell session')
178
- console.log(' 3. Run the configuration test to verify setup')
179
-
180
- // Test connection if requested
181
- if (testConnection) {
182
- console.log(colors.processingMessage('\n🧪 Testing connection...'))
183
- const testResult = await provider.testConnection()
184
-
185
- if (testResult.success) {
186
- console.log(colors.successMessage('āœ… Configuration test passed'))
187
- if (testResult.model) {
188
- console.log(
189
- colors.infoMessage(
190
- ` ${colors.label('Default model')}: ${colors.highlight(testResult.model)}`
191
- )
192
- )
193
- }
194
- } else {
195
- console.log(colors.errorMessage(`āŒ Configuration test failed: ${testResult.error}`))
196
- console.log(
197
- colors.warningMessage(' Please check your environment variables and API keys')
198
- )
199
- }
200
- }
201
-
202
- // Show available models if requested
203
- if (showModels) {
204
- console.log(colors.processingMessage('\nšŸ¤– Fetching available models...'))
205
- try {
206
- const models = await provider.getAvailableModels()
207
- if (models && models.length > 0) {
208
- console.log(colors.subheader('Available Models:'))
209
- models.forEach((model) => {
210
- console.log(` - ${colors.highlight(model.name || model)}`)
211
- if (model.description) {
212
- console.log(` ${colors.dim(model.description)}`)
213
- }
214
- })
215
- } else {
216
- console.log(colors.warningMessage('No models found or API error'))
217
- }
218
- } catch (error) {
219
- console.log(colors.errorMessage(`Failed to fetch models: ${error.message}`))
220
- }
221
- }
222
-
223
- return {
224
- success: true,
225
- provider: targetProvider,
226
- configuration: config,
227
- testResult: testConnection ? await provider.testConnection() : null,
228
- }
53
+ return true
229
54
  } catch (error) {
230
- console.error(colors.errorMessage(`Error configuring provider: ${error.message}`))
231
- return { success: false, error: error.message }
55
+ console.error(`Failed to register provider ${name}:`, error.message)
56
+ return false
232
57
  }
233
58
  }
234
59
 
235
- async validateModels(providerName = null, testModels = false, checkCapabilities = false) {
236
- const activeProvider = this.providerManager?.getActiveProvider()
237
- const targetProvider = providerName || activeProvider?.getName()
238
-
239
- if (!targetProvider) {
240
- console.log(colors.errorMessage('No provider specified and no active provider found'))
241
- return { success: false, error: 'No provider specified' }
242
- }
243
-
244
- console.log(
245
- colors.header(`šŸ” Validating Models for ${colors.highlight(targetProvider.toUpperCase())}:`)
246
- )
247
- console.log('')
248
-
60
+ /**
61
+ * Activate a provider by name
62
+ * @param {string} name - Provider name to activate
63
+ * @returns {Promise<boolean>} Success status
64
+ */
65
+ async activateProvider(name) {
249
66
  try {
250
- if (!this.providerManager) {
251
- console.log(colors.errorMessage('Provider manager not available'))
252
- return { success: false, error: 'Provider manager not available' }
253
- }
254
-
255
- const provider = this.providerManager.findProviderByName(targetProvider)
256
-
67
+ const provider = this.findProviderByName(name)
257
68
  if (!provider) {
258
- console.log(colors.errorMessage(`Provider '${targetProvider}' not found`))
259
- return { success: false, error: `Provider '${targetProvider}' not found` }
260
- }
261
-
262
- // Check if provider is configured
263
- const isConfigured = provider.isAvailable()
264
- if (!isConfigured) {
265
- console.log(colors.errorMessage(`Provider '${targetProvider}' is not properly configured`))
266
- return { success: false, error: 'Provider not configured' }
267
- }
268
-
269
- // Get available models
270
- console.log(colors.processingMessage('šŸ“‹ Fetching available models...'))
271
- const models = await provider.getAvailableModels()
272
-
273
- if (!models || models.length === 0) {
274
- console.log(colors.warningMessage('No models found'))
275
- return { success: true, models: [] }
276
- }
277
-
278
- console.log(colors.successMessage(`āœ… Found ${colors.number(models.length)} models`))
279
- console.log('')
280
-
281
- const validationResults = []
282
-
283
- // Display and optionally test each model
284
- for (const model of models) {
285
- const modelName = model.name || model
286
- console.log(colors.subheader(`šŸ¤– ${colors.highlight(modelName)}`))
287
-
288
- if (model.description) {
289
- console.log(` ${colors.label('Description')}: ${colors.value(model.description)}`)
290
- }
291
-
292
- if (checkCapabilities && model.capabilities) {
293
- console.log(
294
- ` ${colors.label('Capabilities')}: ${Object.entries(model.capabilities)
295
- .filter(([_key, value]) => value === true)
296
- .map(([key]) => key)
297
- .join(', ')}`
298
- )
299
- }
300
-
301
- if (model.contextWindow) {
302
- console.log(
303
- ` ${colors.label('Context Window')}: ${colors.number(model.contextWindow)} tokens`
304
- )
305
- }
306
-
307
- if (model.maxTokens) {
308
- console.log(` ${colors.label('Max Output')}: ${colors.number(model.maxTokens)} tokens`)
309
- }
310
-
311
- // Test model if requested
312
- if (testModels) {
313
- console.log(colors.processingMessage(` 🧪 Testing ${modelName}...`))
314
- try {
315
- const testResult = await provider.testModel(modelName)
316
- if (testResult.success) {
317
- console.log(colors.successMessage(' āœ… Model test passed'))
318
- if (testResult.responseTime) {
319
- console.log(
320
- ` ${colors.label('Response Time')}: ${colors.number(testResult.responseTime)}ms`
321
- )
322
- }
323
- } else {
324
- console.log(colors.errorMessage(` āŒ Model test failed: ${testResult.error}`))
325
- }
326
- validationResults.push({ model: modelName, ...testResult })
327
- } catch (error) {
328
- console.log(colors.errorMessage(` āŒ Model test error: ${error.message}`))
329
- validationResults.push({ model: modelName, success: false, error: error.message })
330
- }
331
- } else {
332
- validationResults.push({ model: modelName, success: true })
333
- }
334
-
335
- console.log('')
336
- }
337
-
338
- // Summary
339
- if (testModels) {
340
- const successfulTests = validationResults.filter((r) => r.success).length
341
- const failedTests = validationResults.length - successfulTests
342
-
343
- console.log(colors.subheader('šŸ“Š Test Summary:'))
344
- console.log(` ${colors.label('Successful')}: ${colors.successMessage(successfulTests)}`)
345
- if (failedTests > 0) {
346
- console.log(` ${colors.label('Failed')}: ${colors.errorMessage(failedTests)}`)
347
- }
69
+ throw new Error(`Provider '${name}' not found`)
348
70
  }
349
71
 
350
- return {
351
- success: true,
352
- provider: targetProvider,
353
- models,
354
- validationResults: testModels ? validationResults : null,
355
- summary: {
356
- total: models.length,
357
- tested: testModels ? validationResults.length : 0,
358
- successful: testModels
359
- ? validationResults.filter((r) => r.success).length
360
- : models.length,
361
- },
362
- }
72
+ this.activeProvider = provider.instance
73
+ return true
363
74
  } catch (error) {
364
- console.error(colors.errorMessage(`Error validating models: ${error.message}`))
365
- return { success: false, error: error.message }
75
+ console.error(`Failed to activate provider ${name}:`, error.message)
76
+ return false
366
77
  }
367
78
  }
368
79
 
369
- // Utility methods
370
- getProviderStatus(providerName) {
80
+ /**
81
+ * Deactivate current provider
82
+ * @returns {boolean} Success status
83
+ */
84
+ deactivateProvider() {
371
85
  try {
372
- const provider = this.providerManager?.findProviderByName(providerName)
373
- if (!provider) {
374
- return { available: false, error: 'Provider not found' }
375
- }
376
-
377
- return {
378
- available: provider.isAvailable(),
379
- name: provider.getName(),
380
- configuration: provider.getConfiguration(),
381
- requiredVars: provider.getRequiredEnvVars(),
382
- }
86
+ this.activeProvider = null
87
+ return true
383
88
  } catch (error) {
384
- return { available: false, error: error.message }
89
+ console.error('Failed to deactivate provider:', error.message)
90
+ return false
385
91
  }
386
92
  }
387
93
 
388
- async getProviderCapabilities(providerName) {
389
- try {
390
- const provider = this.providerManager?.findProviderByName(providerName)
391
- if (!provider) {
392
- return { error: 'Provider not found' }
393
- }
394
-
395
- const capabilities = await provider.getCapabilities()
396
- return { success: true, capabilities }
397
- } catch (error) {
398
- return { success: false, error: error.message }
94
+ /**
95
+ * Get provider lifecycle status
96
+ * @param {string} name - Provider name
97
+ * @returns {Object} Lifecycle status information
98
+ */
99
+ getProviderLifecycle(name) {
100
+ const provider = this.findProviderByName(name)
101
+ return {
102
+ name,
103
+ exists: !!provider,
104
+ available: provider?.available,
105
+ active: this.activeProvider === provider?.instance,
106
+ initialized: provider?.instance?.isInitialized?.(),
399
107
  }
400
108
  }
401
109
 
402
- async testAllProviders() {
403
- console.log(colors.header('🧪 Testing All Configured Providers:'))
404
- console.log('')
405
-
406
- const results = []
407
-
408
- try {
409
- const providers = this.providerManager.getAllProviders()
410
- const configuredProviders = providers.filter((p) => p.available)
411
-
412
- if (configuredProviders.length === 0) {
413
- console.log(colors.warningMessage('No configured providers found'))
414
- return { success: true, results: [] }
415
- }
110
+ /**
111
+ * Get provider registry information
112
+ * @returns {Object} Registry information
113
+ */
114
+ getProviderRegistry() {
115
+ return {
116
+ total: this.providers.length,
117
+ active: this.activeProvider ? this.activeProvider.constructor.name : null,
118
+ available: this.providers.filter((p) => p.available).length,
119
+ providers: this.providers.map((p) => ({
120
+ name: p.name,
121
+ available: p.available,
122
+ type: p.instance.constructor.name,
123
+ })),
124
+ }
125
+ }
416
126
 
417
- for (const provider of configuredProviders) {
418
- console.log(
419
- colors.processingMessage(`Testing ${colors.highlight(provider.name.toUpperCase())}...`)
420
- )
127
+ /**
128
+ * Validate all provider configurations
129
+ * @returns {Promise<Object>} Validation results
130
+ */
131
+ async validateConfiguration() {
132
+ const results = {
133
+ valid: 0,
134
+ invalid: 0,
135
+ errors: [],
136
+ }
421
137
 
422
- try {
423
- const testResult = await provider.testConnection()
424
- if (testResult.success) {
425
- console.log(colors.successMessage(`āœ… ${provider.name} - Connection successful`))
426
- } else {
427
- console.log(colors.errorMessage(`āŒ ${provider.name} - ${testResult.error}`))
428
- }
429
- results.push({ provider: provider.name, ...testResult })
430
- } catch (error) {
431
- console.log(colors.errorMessage(`āŒ ${provider.name} - ${error.message}`))
432
- results.push({ provider: provider.name, success: false, error: error.message })
138
+ for (const provider of this.providers) {
139
+ try {
140
+ const isValid = await provider.instance.isAvailable?.()
141
+ if (isValid) {
142
+ results.valid++
143
+ } else {
144
+ results.invalid++
145
+ results.errors.push(`${provider.name}: Not available`)
433
146
  }
147
+ } catch (error) {
148
+ results.invalid++
149
+ results.errors.push(`${provider.name}: ${error.message}`)
434
150
  }
151
+ }
435
152
 
436
- // Summary
437
- const successful = results.filter((r) => r.success).length
438
- const failed = results.length - successful
439
-
440
- console.log(colors.subheader('\nšŸ“Š Test Results Summary:'))
441
- console.log(` ${colors.label('Total Providers')}: ${colors.number(results.length)}`)
442
- console.log(` ${colors.label('Successful')}: ${colors.successMessage(successful)}`)
443
- if (failed > 0) {
444
- console.log(` ${colors.label('Failed')}: ${colors.errorMessage(failed)}`)
445
- }
153
+ return results
154
+ }
446
155
 
447
- return { success: true, results, summary: { total: results.length, successful, failed } }
448
- } catch (error) {
449
- console.error(colors.errorMessage(`Error testing providers: ${error.message}`))
450
- return { success: false, error: error.message }
451
- }
156
+ /**
157
+ * Clean up provider resources
158
+ * @returns {Promise<void>}
159
+ */
160
+ async cleanup() {
161
+ this.activeProvider = null
162
+ this.providers = []
452
163
  }
453
164
  }
165
+
166
+ // Export as default for compatibility
167
+ export default ProviderManagementService
@@ -446,6 +446,17 @@ export function ConfigurationMixin(providerName, defaults = {}) {
446
446
  return buildClientOptions(providerConfig, { ...defaults, ...extraDefaults })
447
447
  },
448
448
 
449
+ getRequiredEnvVars() {
450
+ // Default implementation - providers should override if needed
451
+ const baseKey = `${providerName.toUpperCase()}_API_KEY`
452
+ return [baseKey]
453
+ },
454
+
455
+ getDefaultModel() {
456
+ const modelConfig = this.getProviderModelConfig()
457
+ return modelConfig.standardModel || 'unknown'
458
+ },
459
+
449
460
  getProviderInfo() {
450
461
  const providerConfig = this.getProviderConfig()
451
462
  const availableModels = this.getAvailableModels ? this.getAvailableModels() : []
@@ -223,8 +223,13 @@ export class EnhancedConsole {
223
223
  EnhancedConsole.log(message, 'ai')
224
224
  }
225
225
 
226
- static metrics(message) {
227
- EnhancedConsole.log(message, 'metrics')
226
+ static metrics(data, title = 'Metrics') {
227
+ if (typeof data === 'string') {
228
+ EnhancedConsole.log(data, 'metrics')
229
+ } else if (data && Object.keys(data).length > 0) {
230
+ EnhancedConsole.section(title)
231
+ console.log(colors.formatMetrics(data))
232
+ }
228
233
  }
229
234
 
230
235
  static section(title, content = '') {
@@ -252,13 +257,6 @@ export class EnhancedConsole {
252
257
  }
253
258
  }
254
259
 
255
- static metrics(metrics, title = 'Metrics') {
256
- if (metrics && Object.keys(metrics).length > 0) {
257
- EnhancedConsole.section(title)
258
- console.log(colors.formatMetrics(metrics))
259
- }
260
- }
261
-
262
260
  static divider(char = '─', length = 60) {
263
261
  console.log(colors.separator(char, length))
264
262
  }
@@ -438,7 +436,7 @@ export const cliUtils = {
438
436
  // Format bytes
439
437
  formatBytes(bytes) {
440
438
  const sizes = ['B', 'KB', 'MB', 'GB']
441
- if (bytes === 0) {
439
+ if (bytes <= 0 || !Number.isFinite(bytes)) {
442
440
  return '0B'
443
441
  }
444
442
  const i = Math.floor(Math.log(bytes) / Math.log(1024))