aigit-cli
Version:
AI-powered git commit message generator using OpenAI or DeepSeek
394 lines (331 loc) • 14.6 kB
JavaScript
import { program } from 'commander';
import chalk from 'chalk';
import { generateCommitMessage } from '../src/generator.js';
import { getGitDiff } from '../src/git.js';
import { config, isConfigValid, showConfigHelp, saveApiKey, saveProvider } from '../src/config.js';
import { ProjectConfigDetector } from '../src/project-config.js';
import { codeReviewer } from '../src/code-review.js';
import inquirer from 'inquirer';
// 显示当前配置(不包含敏感信息)
function showCurrentConfig() {
console.log(chalk.blue('⚙️ 当前配置信息'));
console.log(chalk.white('─'.repeat(50)));
console.log(chalk.cyan(`AI服务提供商: ${config.provider || '未设置'}`));
console.log(chalk.cyan(`OpenAI模型: ${config.openaiModel || '未设置'}`));
console.log(chalk.cyan(`DeepSeek模型: ${config.deepseekModel || '未设置'}`));
console.log(chalk.cyan(`生成温度: ${config.temperature || '未设置'}`));
console.log(chalk.cyan(`输出语言: ${config.language || '未设置'}`));
console.log(chalk.cyan(`Commit风格: ${config.style || '未设置'}`));
console.log(chalk.cyan(`最大Token数: ${config.maxTokens || '未设置'}`));
console.log(chalk.white('─'.repeat(50)));
console.log(chalk.blue('💡 使用 --show-keys 查看API密钥状态'));
console.log(chalk.blue('💡 使用 --config-help 查看配置帮助'));
}
// 显示API密钥状态(脱敏)
function showApiKeys() {
console.log(chalk.blue('🔑 API密钥状态'));
console.log(chalk.white('─'.repeat(50)));
const openaiStatus = config.openaiApiKey ? '已配置' : '未配置';
const deepseekStatus = config.deepseekApiKey ? '已配置' : '未配置';
console.log(chalk.cyan(`OpenAI API密钥: ${openaiStatus}`));
if (config.openaiApiKey) {
const maskedKey = maskApiKey(config.openaiApiKey);
console.log(chalk.gray(` 密钥: ${maskedKey}`));
}
console.log(chalk.cyan(`DeepSeek API密钥: ${deepseekStatus}`));
if (config.deepseekApiKey) {
const maskedKey = maskApiKey(config.deepseekApiKey);
console.log(chalk.gray(` 密钥: ${maskedKey}`));
}
console.log(chalk.white('─'.repeat(50)));
console.log(chalk.blue('💡 使用 --show-config 查看完整配置'));
console.log(chalk.blue('💡 使用 --config-help 查看配置帮助'));
}
// 脱敏API密钥
function maskApiKey(apiKey) {
if (!apiKey || apiKey.length < 8) return apiKey;
const prefix = apiKey.substring(0, 4);
const suffix = apiKey.substring(apiKey.length - 4);
const middle = '*'.repeat(Math.min(apiKey.length - 8, 8));
return `${prefix}${middle}${suffix}`;
}
// 自动合并master分支
async function autoMergeMaster() {
try {
const { execSync } = await import('child_process');
// 获取当前分支名
const currentBranch = execSync('git branch --show-current', { encoding: 'utf8' }).trim();
// 如果不是master分支,尝试合并
if (currentBranch !== 'master' && currentBranch !== 'main') {
console.log(chalk.gray(`🔄 正在尝试合并master分支到当前分支 ${currentBranch}...`));
try {
// 获取远程master分支的最新内容
execSync('git fetch origin master', { stdio: 'inherit' });
// 尝试合并
execSync('git merge origin/master', { stdio: 'inherit' });
console.log(chalk.green('✅ 成功合并master分支'));
} catch (mergeError) {
console.log(chalk.yellow('⚠️ 合并master分支失败,可能需要手动解决冲突'));
console.log(chalk.blue('💡 提示: 手动执行 git merge origin/master 解决冲突'));
}
} else {
console.log(chalk.blue('ℹ️ 当前在master分支,无需合并'));
}
} catch (error) {
console.log(chalk.yellow('⚠️ 自动合并功能出现问题,跳过合并'));
}
}
program
.name('aigit')
.description('AI-powered git commit message generator using OpenAI or DeepSeek')
.version('1.0.0')
.option('-p, --provider <provider>', 'AI service provider (openai or deepseek)', 'openai')
.option('-m, --model <model>', 'AI model to use', 'gpt-3.5-turbo')
.option('-t, --temperature <number>', 'Temperature for AI generation', '0.7')
.option('-l, --language <language>', 'Language for commit message', '中文')
.option('-s, --style <style>', 'Commit message style (conventional, simple, detailed)', 'conventional')
.option('-d, --dry-run', 'Show generated message without committing')
.option('--no-auto-add', 'Disable automatic git add .')
.option('--no-auto-merge', 'Disable automatic master branch merge')
.option('--config-help', 'Show configuration help')
.option('--show-config', 'Show current configuration (without sensitive data)')
.option('--show-keys', 'Show API keys (masked for security)')
.option('--detect-project', 'Detect and show project configuration')
.option('--code-review', 'Perform AI-powered code review')
.option('--review-type <type>', 'Code review type (comprehensive/security/performance/maintainability)', 'comprehensive')
.option('--review-summary', 'Generate code review summary only')
.parse();
const options = program.opts();
async function main() {
try {
// 显示配置帮助
if (options.configHelp) {
showConfigHelp();
return;
}
// 显示当前配置
if (options.showConfig) {
showCurrentConfig();
return;
}
// 显示API密钥(脱敏)
if (options.showKeys) {
showApiKeys();
return;
}
// 检测项目配置
if (options.detectProject) {
console.log(chalk.blue('🔍 项目配置检测'));
const projectDetector = new ProjectConfigDetector();
const projectConfig = await projectDetector.detectConfig();
projectDetector.showDetectionResult();
return;
}
// 代码审查
if (options.codeReview) {
console.log(chalk.blue('🔍 AI代码审查'));
// 获取git diff
const diff = await getGitDiff();
if (!diff) {
console.log(chalk.yellow('⚠️ 没有检测到代码变更'));
process.exit(1);
}
// 分析代码质量指标
const qualityMetrics = codeReviewer.analyzeCodeQuality(diff);
console.log(chalk.gray('\n📊 代码质量指标:'));
console.log(chalk.white('─'.repeat(40)));
console.log(chalk.cyan(`变更行数: ${qualityMetrics.linesChanged}`));
console.log(chalk.cyan(`变更文件数: ${qualityMetrics.filesChanged}`));
console.log(chalk.cyan(`新增行数: ${qualityMetrics.additions}`));
console.log(chalk.cyan(`删除行数: ${qualityMetrics.deletions}`));
console.log(chalk.cyan(`复杂度: ${qualityMetrics.complexity}`));
console.log(chalk.cyan(`风险等级: ${qualityMetrics.riskLevel}`));
console.log(chalk.white('─'.repeat(40)));
console.log(chalk.gray('\n🤖 正在执行AI代码审查...'));
try {
if (options.reviewSummary) {
// 生成审查摘要
const summary = await codeReviewer.generateReviewSummary(diff, {
language: options.language || config.language,
model: options.model
});
console.log(chalk.blue('\n📋 代码审查摘要:'));
console.log(chalk.white('─'.repeat(50)));
console.log(chalk.cyan(summary));
console.log(chalk.white('─'.repeat(50)));
} else {
// 执行完整代码审查
const reviewResult = await codeReviewer.reviewCode(diff, {
language: options.language || config.language,
reviewType: options.reviewType,
model: options.model,
temperature: options.temperature
});
console.log(chalk.blue('\n🔍 代码审查结果:'));
console.log(chalk.white('─'.repeat(50)));
console.log(chalk.cyan(reviewResult.review));
console.log(chalk.white('─'.repeat(50)));
console.log(chalk.gray('\n📅 审查时间:'), reviewResult.timestamp);
console.log(chalk.gray('🔧 审查类型:'), reviewResult.options.reviewType);
}
console.log(chalk.green('\n✅ 代码审查完成!'));
} catch (error) {
console.error(chalk.red('❌ 代码审查失败:'), error.message);
process.exit(1);
}
return;
}
// 检查配置
if (!isConfigValid()) {
console.log(chalk.yellow('🔑 需要配置AI服务提供商和API密钥'));
// 选择AI服务提供商
const providerAnswer = await inquirer.prompt([
{
type: 'list',
name: 'provider',
message: '请选择AI服务提供商:',
choices: [
{ name: 'OpenAI (GPT系列)', value: 'openai' },
{ name: 'DeepSeek', value: 'deepseek' }
],
default: 'openai'
}
]);
const provider = providerAnswer.provider;
const providerName = provider === 'openai' ? 'OpenAI' : 'DeepSeek';
const keyPrefix = provider === 'openai' ? 'sk-' : 'sk-';
// 输入API密钥
const apiKeyAnswer = await inquirer.prompt([
{
type: 'input',
name: 'apiKey',
message: `请输入你的${providerName} API密钥:`,
validate: (input) => {
if (!input.trim()) {
return 'API密钥不能为空';
}
if (!input.startsWith(keyPrefix)) {
return `${providerName} API密钥应该以${keyPrefix}开头`;
}
return true;
}
},
{
type: 'confirm',
name: 'saveKey',
message: '是否保存配置到配置文件?(推荐)',
default: true
}
]);
if (apiKeyAnswer.saveKey) {
await saveProvider(provider);
await saveApiKey(apiKeyAnswer.apiKey, provider);
console.log(chalk.green(`✅ ${providerName}配置已保存到配置文件`));
} else {
// 临时设置环境变量
if (provider === 'openai') {
process.env.OPENAI_API_KEY = apiKeyAnswer.apiKey;
} else {
process.env.DEEPSEEK_API_KEY = apiKeyAnswer.apiKey;
}
process.env.AI_PROVIDER = provider;
console.log(chalk.blue(`ℹ️ ${providerName}配置仅在此次会话中有效`));
}
}
console.log(chalk.blue('🚀 AI Git Commit Message Generator'));
// 检测项目配置
console.log(chalk.gray('🔍 正在检测项目配置...'));
const projectDetector = new ProjectConfigDetector();
const projectConfig = await projectDetector.detectConfig();
// 显示检测结果
projectDetector.showDetectionResult();
// 根据项目配置自动调整选项
if (!options.style && projectConfig.commitStyle !== config.style) {
console.log(chalk.blue(`💡 根据项目配置自动调整commit风格为: ${projectConfig.commitStyle}`));
options.style = projectConfig.commitStyle;
}
if (!options.language && projectConfig.language !== config.language) {
console.log(chalk.blue(`💡 根据项目配置自动调整语言为: ${projectConfig.language}`));
options.language = projectConfig.language;
}
// 自动执行 git add .
if (!options.noAutoAdd) {
console.log(chalk.gray('📁 正在添加所有文件到暂存区...'));
try {
const { execSync } = await import('child_process');
execSync('git add .', { stdio: 'inherit' });
console.log(chalk.green('✅ 文件已添加到暂存区'));
} catch (addError) {
console.log(chalk.yellow('⚠️ 自动添加文件失败,继续使用当前暂存区'));
}
}
console.log(chalk.gray('正在分析代码变更...\n'));
// 获取git diff
const diff = await getGitDiff();
if (!diff) {
console.log(chalk.yellow('⚠️ 没有检测到代码变更'));
process.exit(1);
}
console.log(chalk.green('✅ 检测到代码变更'));
console.log(chalk.gray('正在调用AI生成commit message...\n'));
// 生成commit message
const commitMessage = await generateCommitMessage(diff, options);
console.log(chalk.blue('📝 生成的Commit Message:'));
console.log(chalk.white('─'.repeat(50)));
console.log(chalk.cyan(commitMessage));
console.log(chalk.white('─'.repeat(50)));
if (options.dryRun) {
console.log(chalk.yellow('\n🔍 这是预览模式,不会执行commit'));
console.log(chalk.blue('\n💡 提示: 手动复制上面的commit message进行提交'));
return;
}
// 默认自动提交
console.log(chalk.green('\n✅ 正在自动提交...'));
try {
// 询问用户是否确认提交
const confirmAnswer = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: `确认使用以下commit message提交?\n"${commitMessage}"`,
default: true
}
]);
if (confirmAnswer.confirm) {
// 执行git commit
const { execSync } = await import('child_process');
// 清理commit message,移除换行符和特殊字符
const cleanMessage = commitMessage.replace(/\n/g, ' ').trim();
execSync(`git commit -m "${cleanMessage}"`, { stdio: 'inherit' });
console.log(chalk.green('🎉 提交成功!'));
// 自动合并master分支
if (!options.noAutoMerge) {
await autoMergeMaster();
}
// 显示git状态
try {
const status = execSync('git status --porcelain', { encoding: 'utf8' });
if (status.trim()) {
console.log(chalk.blue('\n📊 当前git状态:'));
console.log(chalk.gray(status));
} else {
console.log(chalk.green('\n✨ 工作区干净,所有更改已提交'));
}
} catch (statusError) {
console.log(chalk.yellow('\n⚠️ 无法获取git状态'));
}
} else {
console.log(chalk.yellow('❌ 用户取消提交'));
}
} catch (commitError) {
console.error(chalk.red('❌ 自动提交失败:'), commitError.message);
console.log(chalk.blue('\n💡 你可以手动执行:'));
console.log(chalk.cyan(`git commit -m "${commitMessage}"`));
}
} catch (error) {
console.error(chalk.red('❌ 错误:'), error.message);
process.exit(1);
}
}
main();