@entro314labs/ai-changelog-generator 3.0.5 → 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 -785
  2. package/README.md +30 -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 +84 -52
  9. package/src/ai-changelog-generator.js +83 -81
  10. package/src/application/orchestrators/changelog.orchestrator.js +1040 -296
  11. package/src/application/services/application.service.js +145 -123
  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 +415 -247
  20. package/src/infrastructure/config/configuration.manager.js +220 -190
  21. package/src/infrastructure/interactive/interactive-staging.service.js +332 -0
  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 +556 -0
  43. package/src/shared/constants/colors.js +467 -172
  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 +1299 -775
  51. package/types/index.d.ts +353 -344
@@ -1,191 +1,297 @@
1
- import yargs from 'yargs/yargs';
2
- import { hideBin } from 'yargs/helpers';
3
- import { access } from 'fs/promises';
4
- import path from 'path';
5
- import { ApplicationService } from '../../application/services/application.service.js';
6
- import colors from '../../shared/constants/colors.js';
7
- import { formatDuration, promptForConfig } from '../../shared/utils/utils.js';
8
- import { setupProcessErrorHandlers, handleCLIError, getDefaultStartupTips } from '../../shared/utils/cli-entry-utils.js';
1
+ import { access } from 'node:fs/promises'
2
+ import path from 'node:path'
3
+ import process from 'node:process'
4
+
5
+ import { hideBin } from 'yargs/helpers'
6
+ import yargs from 'yargs/yargs'
7
+
8
+ import { ApplicationService } from '../../application/services/application.service.js'
9
+ import colors from '../../shared/constants/colors.js'
10
+ import { handleCLIError, setupProcessErrorHandlers } from '../../shared/utils/cli-entry-utils.js'
11
+ import { EnhancedConsole } from '../../shared/utils/cli-ui.js'
12
+ import { formatDuration, promptForConfig } from '../../shared/utils/utils.js'
9
13
 
10
14
  export class CLIController {
11
15
  constructor() {
12
- this.commands = new Map();
13
- this.appService = null;
14
- this.startTime = Date.now();
16
+ this.commands = new Map()
17
+ this.appService = null
18
+ this.startTime = Date.now()
15
19
 
16
20
  // Setup enhanced error handling
17
21
  setupProcessErrorHandlers('AI Changelog Generator', {
18
22
  gracefulShutdown: true,
19
23
  logErrors: true,
20
- showStack: process.env.DEBUG === 'true'
21
- });
24
+ showStack: process.env.DEBUG === 'true',
25
+ })
22
26
 
23
- this.registerCommands();
27
+ this.registerCommands()
24
28
  }
25
29
 
26
30
  registerCommands() {
27
31
  // Register all available commands
28
- this.commands.set('default', new DefaultCommand());
29
- this.commands.set('init', new InitCommand());
30
- this.commands.set('validate', new ValidateCommand());
31
- this.commands.set('analyze', new AnalyzeCommand());
32
- this.commands.set('analyze-commits', new AnalyzeCommitsCommand());
33
- this.commands.set('git-info', new GitInfoCommand());
34
- this.commands.set('health', new HealthCommand());
35
- this.commands.set('branches', new BranchesCommand());
36
- this.commands.set('comprehensive', new ComprehensiveCommand());
37
- this.commands.set('untracked', new UntrackedCommand());
38
- this.commands.set('working-dir', new WorkingDirCommand());
39
- this.commands.set('from-commits', new FromCommitsCommand());
40
- this.commands.set('commit-message', new CommitMessageCommand());
41
- this.commands.set('providers', new ProvidersCommand());
32
+ this.commands.set('default', new DefaultCommand())
33
+ this.commands.set('init', new InitCommand())
34
+ this.commands.set('validate', new ValidateCommand())
35
+ this.commands.set('analyze', new AnalyzeCommand())
36
+ this.commands.set('analyze-commits', new AnalyzeCommitsCommand())
37
+ this.commands.set('git-info', new GitInfoCommand())
38
+ this.commands.set('health', new HealthCommand())
39
+ this.commands.set('branches', new BranchesCommand())
40
+ this.commands.set('comprehensive', new ComprehensiveCommand())
41
+ this.commands.set('untracked', new UntrackedCommand())
42
+ this.commands.set('working-dir', new WorkingDirCommand())
43
+ this.commands.set('from-commits', new FromCommitsCommand())
44
+ this.commands.set('commit-message', new CommitMessageCommand())
45
+ this.commands.set('commit', new CommitCommand())
46
+ this.commands.set('providers', new ProvidersCommand())
42
47
  }
43
48
 
44
49
  async runCLI() {
45
50
  try {
46
51
  // Ensure config exists
47
- await this.ensureConfig();
52
+ await this.ensureConfig()
48
53
 
49
- const argv = await this.setupYargs();
54
+ const argv = await this.setupYargs()
50
55
 
51
56
  // Initialize application service with CLI options
52
57
  this.appService = new ApplicationService({
53
58
  dryRun: argv.dryRun,
54
59
  noColor: argv.noColor,
55
- silent: argv.silent
56
- });
60
+ silent: argv.silent,
61
+ })
57
62
 
58
63
  // Execute command
59
- const commandName = argv._[0] || 'default';
60
- const command = this.commands.get(commandName);
64
+ const commandName = argv._[0] || 'default'
65
+ const command = this.commands.get(commandName)
61
66
 
62
67
  if (!command) {
63
- throw new Error(`Unknown command: ${commandName}`);
68
+ throw new Error(`Unknown command: ${commandName}`)
64
69
  }
65
70
 
66
- await command.execute(argv, this.appService);
71
+ await command.execute(argv, this.appService)
67
72
 
68
73
  // Show completion metrics
69
- await this.showMetrics();
70
-
74
+ await this.showMetrics()
71
75
  } catch (error) {
72
76
  handleCLIError(error, 'run CLI application', {
73
77
  exitOnError: false,
74
78
  showTips: true,
75
- showStack: process.env.DEBUG === 'true'
76
- });
77
- process.exitCode = 1;
79
+ showStack: process.env.DEBUG === 'true',
80
+ })
81
+ process.exitCode = 1
78
82
  }
79
83
  }
80
84
 
81
85
  async ensureConfig() {
82
- const configPath = path.join(process.cwd(), '.env.local');
86
+ const configPath = path.join(process.cwd(), '.env.local')
83
87
  try {
84
- await access(configPath);
88
+ await access(configPath)
85
89
  } catch {
86
- await promptForConfig();
90
+ await promptForConfig()
87
91
  }
88
92
  }
89
93
 
90
94
  setupYargs() {
91
- return yargs(hideBin(process.argv))
92
- .scriptName("ai-changelog")
93
- .usage(`${colors.header('AI Changelog Generator')} - ${colors.secondary('Automatically generate changelogs from your git commits using AI.')}\n\n${colors.header('Usage:')} $0 [command] [options]`)
94
-
95
- // Default command
96
- .command('$0', 'Generate a changelog from git commits (default command).', (yargs) => {
97
- yargs
98
- .option('interactive', { alias: 'i', type: 'boolean', description: 'Choose commits interactively.' })
99
- .option('release-version', { alias: 'v', type: 'string', description: 'Set the release version (e.g., 1.2.3).' })
100
- .option('since', { alias: 's', type: 'string', description: 'Generate changelog since a specific git ref (tag/commit).' })
101
- .option('model', { alias: 'm', type: 'string', description: 'Override the default model.' })
102
- .option('detailed', { type: 'boolean', description: 'Use detailed analysis mode.' })
103
- .option('enterprise', { type: 'boolean', description: 'Use enterprise analysis mode.' })
104
- .option('dry-run', { type: 'boolean', description: 'Preview changelog without writing to file.' })
105
- .option('no-attribution', { type: 'boolean', description: 'Disable the attribution footer.' });
106
- })
107
-
108
- // Analysis commands
109
- .command('init', 'Run interactive setup to configure the tool.')
110
- .command('validate', 'Validate your configuration and connectivity.')
111
- .command('analyze', 'Analyze current working directory changes.', this.createStandardOptions)
112
- .command('analyze-commits <limit>', 'Analyze recent commits with detailed information.', (yargs) => {
113
- this.createStandardOptions(yargs)
114
- .positional('limit', { type: 'number', default: 10, description: 'Number of commits to analyze' });
115
- })
116
- .command('git-info', 'Display comprehensive repository information and statistics.', this.createStandardOptions)
117
- .command('health', 'Assess repository health and commit quality.', this.createStandardOptions)
118
- .command('branches', 'Analyze all branches and unmerged commits.', this.createStandardOptions)
119
- .command('comprehensive', 'Comprehensive analysis including dangling commits.', this.createStandardOptions)
120
- .command('untracked', 'Include untracked files analysis.', this.createStandardOptions)
121
- .command('working-dir', 'Generate changelog from working directory changes.', this.createStandardOptions)
122
- .command('from-commits <commits...>', 'Generate changelog from specific commit hashes.', (yargs) => {
123
- yargs.positional('commits', { describe: 'Commit hashes to analyze', type: 'string' });
124
- })
125
-
126
- // Utility commands
127
- .command('commit-message', 'Generate a commit message for current changes.')
128
- .command('providers', 'Manage AI providers.', (yargs) => {
129
- yargs
130
- .command('list', 'List available providers.')
131
- .command('switch <provider>', 'Switch to a different provider.')
132
- .command('configure [provider]', 'Configure AI provider settings.')
133
- .command('validate [provider]', 'Validate provider models and capabilities.')
134
- .demandCommand(1, 'Please specify a provider subcommand.');
135
- })
136
-
137
- // Global options
138
- .option('no-color', { type: 'boolean', description: 'Disable colored output.' })
139
- .option('silent', { type: 'boolean', description: 'Suppress non-essential output.' })
140
- .help('h')
141
- .alias('h', 'help')
142
- .epilogue(`For more information, visit ${colors.highlight('https://github.com/entro314-labs/ai-changelog-generator')}`)
143
- .demandCommand(0)
144
- .strict()
145
- .parse();
95
+ return (
96
+ yargs(hideBin(process.argv))
97
+ .scriptName('ai-changelog')
98
+ .usage(
99
+ `${colors.header('AI Changelog Generator')} - ${colors.secondary('Automatically generate changelogs from your git commits using AI.')}\n\n${colors.header('Usage:')} $0 [command] [options]`
100
+ )
101
+
102
+ // Default command
103
+ .command('$0', 'Generate a changelog from git commits (default command).', (yargs) => {
104
+ yargs
105
+ .option('interactive', {
106
+ alias: 'i',
107
+ type: 'boolean',
108
+ description: 'Choose commits interactively.',
109
+ })
110
+ .option('release-version', {
111
+ alias: 'v',
112
+ type: 'string',
113
+ description: 'Set the release version (e.g., 1.2.3).',
114
+ })
115
+ .option('since', {
116
+ alias: 's',
117
+ type: 'string',
118
+ description: 'Generate changelog since a specific git ref (tag/commit).',
119
+ })
120
+ .option('model', {
121
+ alias: 'm',
122
+ type: 'string',
123
+ description: 'Override the default model.',
124
+ })
125
+ .option('detailed', { type: 'boolean', description: 'Use detailed analysis mode.' })
126
+ .option('enterprise', { type: 'boolean', description: 'Use enterprise analysis mode.' })
127
+ .option('dry-run', {
128
+ type: 'boolean',
129
+ description: 'Preview changelog without writing to file.',
130
+ })
131
+ .option('no-attribution', {
132
+ type: 'boolean',
133
+ description: 'Disable the attribution footer.',
134
+ })
135
+ })
136
+
137
+ // Analysis commands
138
+ .command('init', 'Run interactive setup to configure the tool.')
139
+ .command('validate', 'Validate your configuration and connectivity.')
140
+ .command(
141
+ 'analyze',
142
+ 'Analyze current working directory changes.',
143
+ this.createStandardOptions
144
+ )
145
+ .command(
146
+ 'analyze-commits <limit>',
147
+ 'Analyze recent commits with detailed information.',
148
+ (yargs) => {
149
+ this.createStandardOptions(yargs).positional('limit', {
150
+ type: 'number',
151
+ default: 10,
152
+ description: 'Number of commits to analyze',
153
+ })
154
+ }
155
+ )
156
+ .command(
157
+ 'git-info',
158
+ 'Display comprehensive repository information and statistics.',
159
+ this.createStandardOptions
160
+ )
161
+ .command(
162
+ 'health',
163
+ 'Assess repository health and commit quality.',
164
+ this.createStandardOptions
165
+ )
166
+ .command(
167
+ 'branches',
168
+ 'Analyze all branches and unmerged commits.',
169
+ this.createStandardOptions
170
+ )
171
+ .command(
172
+ 'comprehensive',
173
+ 'Comprehensive analysis including dangling commits.',
174
+ this.createStandardOptions
175
+ )
176
+ .command('untracked', 'Include untracked files analysis.', this.createStandardOptions)
177
+ .command(
178
+ 'working-dir',
179
+ 'Generate changelog from working directory changes.',
180
+ this.createStandardOptions
181
+ )
182
+ .command(
183
+ 'from-commits <commits...>',
184
+ 'Generate changelog from specific commit hashes.',
185
+ (yargs) => {
186
+ yargs.positional('commits', { describe: 'Commit hashes to analyze', type: 'string' })
187
+ }
188
+ )
189
+
190
+ // Utility commands
191
+ .command('commit-message', 'Generate a commit message for current changes.')
192
+ .command('commit', 'Interactive commit workflow with AI-generated messages.', (yargs) => {
193
+ yargs
194
+ .option('interactive', {
195
+ alias: 'i',
196
+ type: 'boolean',
197
+ default: true,
198
+ description: 'Use interactive staging (default).',
199
+ })
200
+ .option('all', {
201
+ alias: 'a',
202
+ type: 'boolean',
203
+ description: 'Automatically stage all changes.',
204
+ })
205
+ .option('message', {
206
+ alias: 'm',
207
+ type: 'string',
208
+ description: 'Use provided commit message (skip AI generation).',
209
+ })
210
+ .option('dry-run', {
211
+ type: 'boolean',
212
+ description: 'Preview commit message without committing.',
213
+ })
214
+ .option('editor', {
215
+ alias: 'e',
216
+ type: 'boolean',
217
+ description: 'Open editor to review/edit commit message.',
218
+ })
219
+ .option('model', { type: 'string', description: 'Override the default AI model.' })
220
+ })
221
+ .command('providers', 'Manage AI providers.', (yargs) => {
222
+ yargs
223
+ .command('list', 'List available providers.')
224
+ .command('switch <provider>', 'Switch to a different provider.')
225
+ .command('configure [provider]', 'Configure AI provider settings.')
226
+ .command('validate [provider]', 'Validate provider models and capabilities.')
227
+ .demandCommand(1, 'Please specify a provider subcommand.')
228
+ })
229
+
230
+ // Global options
231
+ .option('no-color', { type: 'boolean', description: 'Disable colored output.' })
232
+ .option('silent', { type: 'boolean', description: 'Suppress non-essential output.' })
233
+ .help('h')
234
+ .alias('h', 'help')
235
+ .epilogue(
236
+ `For more information, visit ${colors.highlight('https://github.com/entro314-labs/ai-changelog-generator')}`
237
+ )
238
+ .demandCommand(0)
239
+ .strict()
240
+ .parse()
241
+ )
146
242
  }
147
243
 
148
244
  createStandardOptions(yargs) {
149
245
  return yargs
150
- .option('format', { alias: 'f', type: 'string', choices: ['markdown', 'json'], default: 'markdown', description: 'Output format' })
246
+ .option('format', {
247
+ alias: 'f',
248
+ type: 'string',
249
+ choices: ['markdown', 'json'],
250
+ default: 'markdown',
251
+ description: 'Output format',
252
+ })
151
253
  .option('output', { alias: 'o', type: 'string', description: 'Output file path' })
152
254
  .option('since', { type: 'string', description: 'Analyze changes since this git ref' })
153
255
  .option('silent', { type: 'boolean', description: 'Suppress non-essential output' })
154
256
  .option('dry-run', { type: 'boolean', description: 'Preview without writing files' })
155
257
  .option('detailed', { type: 'boolean', description: 'Use detailed analysis mode' })
156
258
  .option('enterprise', { type: 'boolean', description: 'Use enterprise analysis mode' })
157
- .option('model', { alias: 'm', type: 'string', description: 'Override the default model' });
259
+ .option('model', { alias: 'm', type: 'string', description: 'Override the default model' })
158
260
  }
159
261
 
160
262
  async showMetrics() {
161
- if (!this.appService || this.appService.options.silent) return;
263
+ if (!this.appService || this.appService.options.silent) {
264
+ return
265
+ }
162
266
 
163
- const endTime = Date.now();
164
- const metrics = this.appService.getMetrics();
267
+ const endTime = Date.now()
268
+ const metrics = this.appService.getMetrics()
165
269
 
166
- const summaryLines = [
167
- `${colors.label('Total time')}: ${colors.value(formatDuration(endTime - this.startTime))}`,
168
- `${colors.label('Commits processed')}: ${colors.number(metrics.commitsProcessed || 0)}`
169
- ];
270
+ const summaryData = {
271
+ 'Total time': formatDuration(endTime - this.startTime),
272
+ 'Commits processed': metrics.commitsProcessed || 0,
273
+ }
170
274
 
171
275
  if (metrics.apiCalls > 0) {
172
- summaryLines.push(`${colors.label('AI calls')}: ${colors.number(metrics.apiCalls)}`);
173
- summaryLines.push(`${colors.label('Total tokens')}: ${colors.number((metrics.totalTokens || 0).toLocaleString())}`);
276
+ summaryData['AI calls'] = metrics.apiCalls
277
+ summaryData['Total tokens'] = (metrics.totalTokens || 0).toLocaleString()
174
278
  }
175
279
 
176
280
  if (metrics.errors > 0) {
177
- summaryLines.push('');
178
- summaryLines.push(colors.error(`❌ Errors: ${metrics.errors}`));
281
+ summaryData.Errors = colors.error(`${metrics.errors}`)
179
282
  }
180
283
 
181
- console.log(colors.box('📊 Session Summary', summaryLines.join('\n')));
284
+ EnhancedConsole.box('📊 Session Summary', colors.formatMetrics(summaryData), {
285
+ borderStyle: 'rounded',
286
+ borderColor: 'info',
287
+ })
182
288
  }
183
289
  }
184
290
 
185
291
  // Base command class
186
292
  class BaseCommand {
187
- async execute(argv, appService) {
188
- throw new Error('Command execute method not implemented');
293
+ async execute(_argv, _appService) {
294
+ throw new Error('Command execute method not implemented')
189
295
  }
190
296
 
191
297
  processStandardFlags(argv, appService) {
@@ -193,58 +299,64 @@ class BaseCommand {
193
299
  format: argv.format || 'markdown',
194
300
  output: argv.output,
195
301
  since: argv.since,
196
- silent: argv.silent || false,
197
- dryRun: argv.dryRun || false
198
- };
302
+ silent: argv.silent,
303
+ dryRun: argv.dryRun,
304
+ }
199
305
 
200
306
  // Apply analysis mode
201
- if (argv.detailed) appService.setAnalysisMode('detailed');
202
- if (argv.enterprise) appService.setAnalysisMode('enterprise');
203
- if (argv.model) appService.setModelOverride(argv.model);
307
+ if (argv.detailed) {
308
+ appService.setAnalysisMode('detailed')
309
+ }
310
+ if (argv.enterprise) {
311
+ appService.setAnalysisMode('enterprise')
312
+ }
313
+ if (argv.model) {
314
+ appService.setModelOverride(argv.model)
315
+ }
204
316
 
205
- return config;
317
+ return config
206
318
  }
207
319
  }
208
320
 
209
321
  // Command implementations
210
322
  class DefaultCommand extends BaseCommand {
211
323
  async execute(argv, appService) {
212
- const config = this.processStandardFlags(argv, appService);
324
+ const _config = this.processStandardFlags(argv, appService)
213
325
 
214
326
  if (argv.interactive) {
215
- await appService.runInteractive();
327
+ await appService.runInteractive()
216
328
  } else {
217
329
  await appService.generateChangelog({
218
330
  version: argv.releaseVersion,
219
- since: argv.since
220
- });
331
+ since: argv.since,
332
+ })
221
333
  }
222
334
  }
223
335
  }
224
336
 
225
337
  class InitCommand extends BaseCommand {
226
- async execute(argv, appService) {
227
- await promptForConfig();
338
+ async execute(_argv, _appService) {
339
+ await promptForConfig()
228
340
  }
229
341
  }
230
342
 
231
343
  class ValidateCommand extends BaseCommand {
232
- async execute(argv, appService) {
233
- const validation = await appService.validateConfiguration();
344
+ async execute(_argv, appService) {
345
+ const validation = await appService.validateConfiguration()
234
346
 
235
347
  if (validation.valid) {
236
- console.log(colors.successMessage('✅ Configuration is valid'));
348
+ console.log(colors.successMessage('✅ Configuration is valid'))
237
349
  } else {
238
- console.log(colors.errorMessage('❌ Configuration has issues:'));
239
- validation.issues.forEach(issue => {
240
- console.log(` - ${issue}`);
241
- });
350
+ console.log(colors.errorMessage('❌ Configuration has issues:'))
351
+ validation.issues.forEach((issue) => {
352
+ console.log(` - ${issue}`)
353
+ })
242
354
 
243
355
  if (validation.recommendations.length > 0) {
244
- console.log(colors.infoMessage('\n💡 Recommendations:'));
245
- validation.recommendations.forEach(rec => {
246
- console.log(` - ${rec}`);
247
- });
356
+ console.log(colors.infoMessage('\n💡 Recommendations:'))
357
+ validation.recommendations.forEach((rec) => {
358
+ console.log(` - ${rec}`)
359
+ })
248
360
  }
249
361
  }
250
362
  }
@@ -252,266 +364,322 @@ class ValidateCommand extends BaseCommand {
252
364
 
253
365
  class AnalyzeCommand extends BaseCommand {
254
366
  async execute(argv, appService) {
255
- const config = this.processStandardFlags(argv, appService);
256
- await appService.analyzeCurrentChanges();
367
+ const _config = this.processStandardFlags(argv, appService)
368
+ await appService.analyzeCurrentChanges()
257
369
  }
258
370
  }
259
371
 
260
372
  class AnalyzeCommitsCommand extends BaseCommand {
261
373
  async execute(argv, appService) {
262
- const config = this.processStandardFlags(argv, appService);
263
- await appService.analyzeRecentCommits(argv.limit || 10);
374
+ const _config = this.processStandardFlags(argv, appService)
375
+ await appService.analyzeRecentCommits(argv.limit || 10)
264
376
  }
265
377
  }
266
378
 
267
379
  class GitInfoCommand extends BaseCommand {
268
380
  async execute(argv, appService) {
269
- const config = this.processStandardFlags(argv, appService);
270
- await appService.analyzeRepository({ type: 'git-info', ...config });
381
+ const config = this.processStandardFlags(argv, appService)
382
+ await appService.analyzeRepository({ type: 'git-info', ...config })
271
383
  }
272
384
  }
273
385
 
274
386
  class HealthCommand extends BaseCommand {
275
387
  async execute(argv, appService) {
276
- const config = this.processStandardFlags(argv, appService);
277
- await appService.assessHealth(config);
388
+ const config = this.processStandardFlags(argv, appService)
389
+ await appService.assessHealth(config)
278
390
  }
279
391
  }
280
392
 
281
393
  class BranchesCommand extends BaseCommand {
282
394
  async execute(argv, appService) {
283
- const config = this.processStandardFlags(argv, appService);
284
- await appService.analyzeRepository({ type: 'branches', ...config });
395
+ const config = this.processStandardFlags(argv, appService)
396
+ await appService.analyzeRepository({ type: 'branches', ...config })
285
397
  }
286
398
  }
287
399
 
288
400
  class ComprehensiveCommand extends BaseCommand {
289
401
  async execute(argv, appService) {
290
- const config = this.processStandardFlags(argv, appService);
291
- await appService.analyzeRepository({ type: 'comprehensive', ...config });
402
+ const config = this.processStandardFlags(argv, appService)
403
+ await appService.analyzeRepository({ type: 'comprehensive', ...config })
292
404
  }
293
405
  }
294
406
 
295
407
  class UntrackedCommand extends BaseCommand {
296
408
  async execute(argv, appService) {
297
- const config = this.processStandardFlags(argv, appService);
298
- await appService.analyzeRepository({ type: 'untracked', ...config });
409
+ const config = this.processStandardFlags(argv, appService)
410
+ await appService.analyzeRepository({ type: 'untracked', ...config })
299
411
  }
300
412
  }
301
413
 
302
414
  class WorkingDirCommand extends BaseCommand {
303
415
  async execute(argv, appService) {
304
- const config = this.processStandardFlags(argv, appService);
305
- await appService.generateChangelogFromChanges(argv.releaseVersion);
416
+ const _config = this.processStandardFlags(argv, appService)
417
+ await appService.generateChangelogFromChanges(argv.releaseVersion)
306
418
  }
307
419
  }
308
420
 
309
421
  class FromCommitsCommand extends BaseCommand {
310
422
  async execute(argv, appService) {
311
- const config = this.processStandardFlags(argv, appService);
423
+ const _config = this.processStandardFlags(argv, appService)
312
424
  // Implementation would generate changelog from specific commits
313
- console.log(colors.infoMessage(`Generating changelog from commits: ${argv.commits.join(', ')}`));
425
+ console.log(colors.infoMessage(`Generating changelog from commits: ${argv.commits.join(', ')}`))
314
426
  }
315
427
  }
316
428
 
317
429
  class CommitMessageCommand extends BaseCommand {
318
- async execute(argv, appService) {
319
- console.log(colors.processingMessage('🤖 Analyzing current changes for commit message suggestions...'));
430
+ async execute(_argv, appService) {
431
+ console.log(
432
+ colors.processingMessage('🤖 Analyzing current changes for commit message suggestions...')
433
+ )
320
434
 
321
435
  try {
322
- const result = await appService.generateCommitMessage();
436
+ const result = await appService.generateCommitMessage()
323
437
 
324
- if (result && result.suggestions && result.suggestions.length > 0) {
325
- console.log(colors.successMessage('\n✅ Generated commit message suggestions:'));
438
+ if (result?.suggestions && result.suggestions.length > 0) {
439
+ console.log(colors.successMessage('\n✅ Generated commit message suggestions:'))
326
440
  result.suggestions.forEach((suggestion, index) => {
327
- console.log(`${colors.number(index + 1)}. ${colors.highlight(suggestion)}`);
328
- });
441
+ console.log(`${colors.number(index + 1)}. ${colors.highlight(suggestion)}`)
442
+ })
329
443
 
330
444
  if (result.context) {
331
- console.log(colors.dim(`\nContext: ${result.context}`));
445
+ console.log(colors.dim(`\nContext: ${result.context}`))
332
446
  }
333
447
  } else {
334
- console.log(colors.warningMessage('No commit message suggestions could be generated.'));
335
- console.log(colors.infoMessage('Make sure you have uncommitted changes.'));
448
+ console.log(colors.warningMessage('No commit message suggestions could be generated.'))
449
+ console.log(colors.infoMessage('Make sure you have uncommitted changes.'))
336
450
  }
337
451
  } catch (error) {
338
- console.error(colors.errorMessage(`Error generating commit message: ${error.message}`));
452
+ console.error(colors.errorMessage(`Error generating commit message: ${error.message}`))
453
+ }
454
+ }
455
+ }
456
+
457
+ class CommitCommand extends BaseCommand {
458
+ async execute(argv, appService) {
459
+ console.log(colors.processingMessage('🚀 Starting interactive commit workflow...'))
460
+
461
+ try {
462
+ // Process flags and model override
463
+ if (argv.model) {
464
+ appService.setModelOverride(argv.model)
465
+ }
466
+
467
+ // Execute the commit workflow
468
+ const result = await appService.executeCommitWorkflow({
469
+ interactive: argv.interactive !== false, // Default to true unless explicitly false
470
+ stageAll: argv.all,
471
+ customMessage: argv.message,
472
+ dryRun: argv.dryRun,
473
+ useEditor: argv.editor,
474
+ })
475
+
476
+ if (result?.success) {
477
+ if (argv.dryRun) {
478
+ console.log(colors.successMessage('✅ Commit workflow completed (dry-run mode)'))
479
+ console.log(colors.highlight(`Proposed commit message:\n${result.commitMessage}`))
480
+ } else {
481
+ console.log(colors.successMessage('✅ Changes committed successfully!'))
482
+ console.log(colors.highlight(`Commit: ${result.commitHash}`))
483
+ console.log(colors.dim(`Message: ${result.commitMessage}`))
484
+ }
485
+ } else {
486
+ console.log(colors.warningMessage('Commit workflow cancelled or no changes to commit.'))
487
+ }
488
+ } catch (error) {
489
+ console.error(colors.errorMessage(`Commit workflow failed: ${error.message}`))
490
+
491
+ // Provide helpful suggestions based on error type
492
+ if (error.message.includes('No changes')) {
493
+ console.log(
494
+ colors.infoMessage('💡 Try making some changes first, then run the commit command.')
495
+ )
496
+ } else if (error.message.includes('git')) {
497
+ console.log(
498
+ colors.infoMessage(
499
+ '💡 Make sure you are in a git repository and git is properly configured.'
500
+ )
501
+ )
502
+ }
339
503
  }
340
504
  }
341
505
  }
342
506
 
343
507
  class ProvidersCommand extends BaseCommand {
344
508
  async execute(argv, appService) {
345
- const subcommand = argv._[1];
509
+ const subcommand = argv._[1]
346
510
 
347
511
  switch (subcommand) {
348
512
  case 'list':
349
- await this.listProviders(appService);
350
- break;
513
+ await this.listProviders(appService)
514
+ break
351
515
  case 'switch':
352
- await this.switchProvider(appService, argv.provider);
353
- break;
516
+ await this.switchProvider(appService, argv.provider)
517
+ break
354
518
  case 'configure':
355
- await this.configureProvider(appService, argv.provider);
356
- break;
519
+ await this.configureProvider(appService, argv.provider)
520
+ break
357
521
  case 'validate':
358
- await this.validateProvider(appService, argv.provider);
359
- break;
522
+ await this.validateProvider(appService, argv.provider)
523
+ break
360
524
  default:
361
- console.log(colors.errorMessage('Unknown provider subcommand'));
362
- console.log(colors.infoMessage('Available subcommands: list, switch, configure, validate'));
525
+ console.log(colors.errorMessage('Unknown provider subcommand'))
526
+ console.log(colors.infoMessage('Available subcommands: list, switch, configure, validate'))
363
527
  }
364
528
  }
365
529
 
366
530
  async listProviders(appService) {
367
531
  try {
368
- const providers = await appService.listProviders();
532
+ const providers = await appService.listProviders()
369
533
 
370
- console.log(colors.header('\n🤖 Available AI Providers:'));
534
+ console.log(colors.header('\n🤖 Available AI Providers:'))
371
535
 
372
- providers.forEach(provider => {
373
- const status = provider.available ? '✅ Available' : '⚠️ Needs configuration';
374
- const activeIndicator = provider.active ? ' 🎯 (Active)' : '';
536
+ providers.forEach((provider) => {
537
+ const status = provider.available ? '✅ Available' : '⚠️ Needs configuration'
538
+ const activeIndicator = provider.active ? ' 🎯 (Active)' : ''
375
539
 
376
- console.log(` ${colors.highlight(provider.name)} - ${status}${activeIndicator}`);
540
+ console.log(` ${colors.highlight(provider.name)} - ${status}${activeIndicator}`)
377
541
 
378
542
  if (provider.capabilities && Object.keys(provider.capabilities).length > 0) {
379
543
  const caps = Object.entries(provider.capabilities)
380
- .filter(([key, value]) => value === true)
544
+ .filter(([_key, value]) => value === true)
381
545
  .map(([key]) => key)
382
- .join(', ');
546
+ .join(', ')
383
547
  if (caps) {
384
- console.log(` ${colors.dim(`Capabilities: ${caps}`)}`);
548
+ console.log(` ${colors.dim(`Capabilities: ${caps}`)}`)
385
549
  }
386
550
  }
387
- });
388
-
389
- console.log(colors.dim('\nUse "ai-changelog providers configure <provider>" to set up a provider'));
551
+ })
390
552
 
553
+ console.log(
554
+ colors.dim('\nUse "ai-changelog providers configure <provider>" to set up a provider')
555
+ )
391
556
  } catch (error) {
392
- console.error(colors.errorMessage(`Error listing providers: ${error.message}`));
557
+ console.error(colors.errorMessage(`Error listing providers: ${error.message}`))
393
558
  }
394
559
  }
395
560
 
396
561
  async switchProvider(appService, providerName) {
397
562
  if (!providerName) {
398
- console.log(colors.errorMessage('Please specify a provider name'));
399
- console.log(colors.infoMessage('Usage: ai-changelog providers switch <provider>'));
400
- return;
563
+ console.log(colors.errorMessage('Please specify a provider name'))
564
+ console.log(colors.infoMessage('Usage: ai-changelog providers switch <provider>'))
565
+ return
401
566
  }
402
567
 
403
568
  try {
404
- const result = await appService.switchProvider(providerName);
569
+ const result = await appService.switchProvider(providerName)
405
570
 
406
571
  if (result.success) {
407
- console.log(colors.successMessage(`✅ Switched to ${providerName} provider`));
572
+ console.log(colors.successMessage(`✅ Switched to ${providerName} provider`))
408
573
  } else {
409
- console.log(colors.errorMessage(`❌ Failed to switch provider: ${result.error}`));
410
- console.log(colors.infoMessage('Use "ai-changelog providers list" to see available providers'));
574
+ console.log(colors.errorMessage(`❌ Failed to switch provider: ${result.error}`))
575
+ console.log(
576
+ colors.infoMessage('Use "ai-changelog providers list" to see available providers')
577
+ )
411
578
  }
412
579
  } catch (error) {
413
- console.error(colors.errorMessage(`Error switching provider: ${error.message}`));
580
+ console.error(colors.errorMessage(`Error switching provider: ${error.message}`))
414
581
  }
415
582
  }
416
583
 
417
584
  async configureProvider(appService, providerName) {
418
- const { select } = await import('@clack/prompts');
585
+ const { select } = await import('@clack/prompts')
419
586
 
420
587
  try {
421
588
  // If no provider specified, let user choose
422
589
  if (!providerName) {
423
- const providers = await appService.listProviders();
590
+ const providers = await appService.listProviders()
424
591
 
425
- const choices = providers.map(p => ({
592
+ const choices = providers.map((p) => ({
426
593
  value: p.name,
427
- label: `${p.name} ${p.available ? '✅' : '⚠️ (needs configuration)'}`
428
- }));
594
+ label: `${p.name} ${p.available ? '✅' : '⚠️ (needs configuration)'}`,
595
+ }))
429
596
 
430
597
  providerName = await select({
431
598
  message: 'Select provider to configure:',
432
- options: choices
433
- });
599
+ options: choices,
600
+ })
434
601
  }
435
602
 
436
- console.log(colors.header(`\n🔧 Configuring ${providerName.toUpperCase()} Provider`));
437
- console.log(colors.infoMessage('Please add the following to your .env.local file:\n'));
603
+ console.log(colors.header(`\n🔧 Configuring ${providerName.toUpperCase()} Provider`))
604
+ console.log(colors.infoMessage('Please add the following to your .env.local file:\n'))
438
605
 
439
606
  switch (providerName.toLowerCase()) {
440
607
  case 'openai':
441
- console.log(colors.code('OPENAI_API_KEY=your_openai_api_key_here'));
442
- console.log(colors.dim('Get your API key from: https://platform.openai.com/api-keys'));
443
- break;
608
+ console.log(colors.code('OPENAI_API_KEY=your_openai_api_key_here'))
609
+ console.log(colors.dim('Get your API key from: https://platform.openai.com/api-keys'))
610
+ break
444
611
 
445
612
  case 'anthropic':
446
- console.log(colors.code('ANTHROPIC_API_KEY=your_anthropic_api_key_here'));
447
- console.log(colors.dim('Get your API key from: https://console.anthropic.com/'));
448
- break;
613
+ console.log(colors.code('ANTHROPIC_API_KEY=your_anthropic_api_key_here'))
614
+ console.log(colors.dim('Get your API key from: https://console.anthropic.com/'))
615
+ break
449
616
 
450
617
  case 'azure':
451
- console.log(colors.code('AZURE_OPENAI_API_KEY=your_azure_api_key_here'));
452
- console.log(colors.code('AZURE_OPENAI_ENDPOINT=your_azure_endpoint_here'));
453
- console.log(colors.dim('Get from your Azure OpenAI resource in Azure portal'));
454
- break;
618
+ console.log(colors.code('AZURE_OPENAI_API_KEY=your_azure_api_key_here'))
619
+ console.log(colors.code('AZURE_OPENAI_ENDPOINT=your_azure_endpoint_here'))
620
+ console.log(colors.dim('Get from your Azure OpenAI resource in Azure portal'))
621
+ break
455
622
 
456
623
  case 'google':
457
- console.log(colors.code('GOOGLE_API_KEY=your_google_api_key_here'));
458
- console.log(colors.dim('Get your API key from: https://aistudio.google.com/app/apikey'));
459
- break;
624
+ console.log(colors.code('GOOGLE_API_KEY=your_google_api_key_here'))
625
+ console.log(colors.dim('Get your API key from: https://aistudio.google.com/app/apikey'))
626
+ break
460
627
 
461
628
  case 'ollama':
462
- console.log(colors.code('OLLAMA_HOST=http://localhost:11434'));
463
- console.log(colors.dim('Make sure Ollama is running: ollama serve'));
464
- break;
629
+ console.log(colors.code('OLLAMA_HOST=http://localhost:11434'))
630
+ console.log(colors.dim('Make sure Ollama is running: ollama serve'))
631
+ break
465
632
 
466
633
  default:
467
- console.log(colors.code(`${providerName.toUpperCase()}_API_KEY=your_api_key_here`));
634
+ console.log(colors.code(`${providerName.toUpperCase()}_API_KEY=your_api_key_here`))
468
635
  }
469
636
 
470
- console.log(colors.infoMessage('\nAfter adding the configuration, run:'));
471
- console.log(colors.highlight(`ai-changelog providers validate ${providerName}`));
472
-
637
+ console.log(colors.infoMessage('\nAfter adding the configuration, run:'))
638
+ console.log(colors.highlight(`ai-changelog providers validate ${providerName}`))
473
639
  } catch (error) {
474
- console.error(colors.errorMessage(`Error configuring provider: ${error.message}`));
640
+ console.error(colors.errorMessage(`Error configuring provider: ${error.message}`))
475
641
  }
476
642
  }
477
643
 
478
644
  async validateProvider(appService, providerName) {
479
645
  try {
480
646
  if (!providerName) {
481
- console.log(colors.processingMessage('🔍 Validating all configured providers...'));
482
- const result = await appService.validateAllProviders();
647
+ console.log(colors.processingMessage('🔍 Validating all configured providers...'))
648
+ const result = await appService.validateAllProviders()
483
649
 
484
- console.log(colors.header('\n📊 Provider Validation Results:'));
650
+ console.log(colors.header('\n📊 Provider Validation Results:'))
485
651
 
486
652
  Object.entries(result).forEach(([name, validation]) => {
487
- const status = validation.success ? '✅ Valid' : '❌ Invalid';
488
- console.log(` ${colors.highlight(name)}: ${status}`);
653
+ const status = validation.success ? '✅ Valid' : '❌ Invalid'
654
+ console.log(` ${colors.highlight(name)}: ${status}`)
489
655
 
490
656
  if (!validation.success) {
491
- console.log(` ${colors.errorMessage(validation.error)}`);
657
+ console.log(` ${colors.errorMessage(validation.error)}`)
492
658
  }
493
- });
494
-
659
+ })
495
660
  } else {
496
- console.log(colors.processingMessage(`🔍 Validating ${providerName} provider...`));
497
- const result = await appService.validateProvider(providerName);
661
+ console.log(colors.processingMessage(`🔍 Validating ${providerName} provider...`))
662
+ const result = await appService.validateProvider(providerName)
498
663
 
499
664
  if (result.success) {
500
- console.log(colors.successMessage(`✅ ${providerName} provider is configured correctly`));
665
+ console.log(colors.successMessage(`✅ ${providerName} provider is configured correctly`))
501
666
  if (result.model) {
502
- console.log(colors.dim(` Default model: ${result.model}`));
667
+ console.log(colors.dim(` Default model: ${result.model}`))
503
668
  }
504
669
  } else {
505
- console.log(colors.errorMessage(`❌ ${providerName} validation failed: ${result.error}`));
506
- console.log(colors.infoMessage(`Use "ai-changelog providers configure ${providerName}" for setup instructions`));
670
+ console.log(colors.errorMessage(`❌ ${providerName} validation failed: ${result.error}`))
671
+ console.log(
672
+ colors.infoMessage(
673
+ `Use "ai-changelog providers configure ${providerName}" for setup instructions`
674
+ )
675
+ )
507
676
  }
508
677
  }
509
-
510
678
  } catch (error) {
511
- console.error(colors.errorMessage(`Error validating provider: ${error.message}`));
679
+ console.error(colors.errorMessage(`Error validating provider: ${error.message}`))
512
680
  }
513
681
  }
514
682
  }
515
683
 
516
684
  // Export the controller
517
- export default CLIController;
685
+ export default CLIController