#!/usr/bin/env node import { Command } from 'commander'; import { ConfigLoader } from '../config/ConfigLoader'; import { ReviewCommand } from './commands/ReviewCommand'; import { ExplainCommand } from './commands/ExplainCommand'; import { CommitMessageCommand } from './commands/CommitMessageCommand'; import { RefactorCommand } from './commands/RefactorCommand'; import { TestSuggestCommand } from './commands/TestSuggestCommand'; import { SecurityScanCommand } from './commands/SecurityScanCommand'; import { DocsCommand } from './commands/DocsCommand'; import { PRSummaryCommand } from './commands/PRSummaryCommand'; import { MentorCommand } from './commands/MentorCommand'; import { ConfigCommand } from './commands/ConfigCommand'; import { VersionCommand } from './commands/VersionCommand'; const program = new Command(); program .name('ai-dev') .description('AI Developer Assistant - Automate, accelerate, and improve your software development lifecycle') .version('1.0.0'); // Global options program .option('-c, --config ', 'Path to configuration file') .option('-v, --verbose', 'Enable verbose output') .option('-d, --debug', 'Enable debug mode') .option('--no-color', 'Disable colored output') // LLM Provider options .option('--llm-provider ', 'LLM provider (openai, ollama, gemini)') .option('--llm-model ', 'LLM model name') .option('--llm-temperature ', 'LLM temperature (0-1)', parseFloat) .option('--llm-max-tokens ', 'LLM max tokens', parseInt) // OpenAI options .option('--openai-api-key ', 'OpenAI API key') .option('--openai-base-url ', 'OpenAI base URL') .option('--openai-organization ', 'OpenAI organization ID') // Gemini options .option('--gemini-api-key ', 'Google Gemini API key') .option('--gemini-model ', 'Gemini model name') // Ollama options .option('--ollama-enabled', 'Enable Ollama') .option('--ollama-base-url ', 'Ollama base URL') .option('--ollama-model ', 'Ollama model name') // GitHub options .option('--github-token ', 'GitHub personal access token') .option('--github-base-url ', 'GitHub API base URL') // Git options .option('--git-repo-path ', 'Git repository path') .option('--git-default-branch ', 'Default git branch') .option('--git-include-staged', 'Include staged changes') .option('--git-include-unstaged', 'Include unstaged changes') // Output options .option('--output-format ', 'Output format (console, markdown, json, html, file)') .option('--output-path ', 'Output file path') .option('--output-verbose', 'Enable verbose output') .option('--output-colorize', 'Enable colored output') // Security options .option('--security-enabled', 'Enable security scanning') .option('--security-severity ', 'Security severity levels (comma-separated)') .option('--security-categories ', 'Security categories (comma-separated)') // File pattern options .option('--file-patterns ', 'File patterns to include (comma-separated)') .option('--exclude-patterns ', 'File patterns to exclude (comma-separated)'); // Load configuration program.hook('preAction', async (thisCommand) => { const options = thisCommand.opts(); try { const config = await ConfigLoader.loadConfiguration(options.config); // Override config with CLI options const configOverride: any = {}; // Basic options if (options.verbose) { configOverride.output = { ...config.output, verbose: true }; } if (options.debug) { configOverride.debug = true; } if (options.noColor) { configOverride.output = { ...config.output, colorize: false }; } // LLM Provider options (with env var fallbacks) const llmProvider = options.llmProvider || process.env.LLM_PROVIDER; const llmModel = options.llmModel || process.env.LLM_MODEL; const llmTemperature = options.llmTemperature !== undefined ? options.llmTemperature : (process.env.LLM_TEMPERATURE ? parseFloat(process.env.LLM_TEMPERATURE) : undefined); const llmMaxTokens = options.llmMaxTokens !== undefined ? options.llmMaxTokens : (process.env.LLM_MAX_TOKENS ? parseInt(process.env.LLM_MAX_TOKENS) : undefined); if (llmProvider || llmModel || llmTemperature !== undefined || llmMaxTokens !== undefined) { configOverride.llm = { ...config.llm }; if (llmProvider) configOverride.llm.provider = llmProvider as any; if (llmModel) configOverride.llm.model = llmModel; if (llmTemperature !== undefined) configOverride.llm.temperature = llmTemperature; if (llmMaxTokens !== undefined) configOverride.llm.maxTokens = llmMaxTokens; } // OpenAI options (with env var fallbacks) const openaiApiKey = options.openaiApiKey || process.env.OPENAI_API_KEY; const openaiBaseUrl = options.openaiBaseUrl || process.env.OPENAI_BASE_URL; const openaiOrganization = options.openaiOrganization || process.env.OPENAI_ORGANIZATION; if (openaiApiKey || openaiBaseUrl || openaiOrganization) { configOverride.openai = { ...config.openai }; if (openaiApiKey) configOverride.openai.apiKey = openaiApiKey; if (openaiBaseUrl) configOverride.openai.baseUrl = openaiBaseUrl; if (openaiOrganization) configOverride.openai.organization = openaiOrganization; } // Gemini options (with env var fallbacks) const geminiApiKey = options.geminiApiKey || process.env.GEMINI_API_KEY; const geminiModel = options.geminiModel || process.env.GEMINI_MODEL; if (geminiApiKey || geminiModel) { configOverride.gemini = { ...config.gemini }; if (geminiApiKey) configOverride.gemini.apiKey = geminiApiKey; if (geminiModel) configOverride.gemini.model = geminiModel; } // Ollama options (with env var fallbacks) const ollamaEnabled = options.ollamaEnabled !== undefined ? options.ollamaEnabled : (process.env.OLLAMA_ENABLED === 'true'); const ollamaBaseUrl = options.ollamaBaseUrl || process.env.OLLAMA_BASE_URL; const ollamaModel = options.ollamaModel || process.env.OLLAMA_MODEL; if (ollamaEnabled !== undefined || ollamaBaseUrl || ollamaModel) { configOverride.ollama = { ...config.ollama }; if (ollamaEnabled !== undefined) configOverride.ollama.enabled = ollamaEnabled; if (ollamaBaseUrl) configOverride.ollama.baseUrl = ollamaBaseUrl; if (ollamaModel) configOverride.ollama.model = ollamaModel; } // GitHub options (with env var fallbacks) const githubToken = options.githubToken || process.env.GITHUB_TOKEN; const githubBaseUrl = options.githubBaseUrl || process.env.GITHUB_BASE_URL; if (githubToken || githubBaseUrl) { configOverride.github = { ...config.github }; if (githubToken) configOverride.github.token = githubToken; if (githubBaseUrl) configOverride.github.baseUrl = githubBaseUrl; } // Git options (with env var fallbacks) const gitRepoPath = options.gitRepoPath || process.env.GIT_REPO_PATH; const gitDefaultBranch = options.gitDefaultBranch || process.env.GIT_DEFAULT_BRANCH; const gitIncludeStaged = options.gitIncludeStaged !== undefined ? options.gitIncludeStaged : (process.env.GIT_INCLUDE_STAGED === 'true'); const gitIncludeUnstaged = options.gitIncludeUnstaged !== undefined ? options.gitIncludeUnstaged : (process.env.GIT_INCLUDE_UNSTAGED === 'true'); if (gitRepoPath || gitDefaultBranch || gitIncludeStaged !== undefined || gitIncludeUnstaged !== undefined) { configOverride.git = { ...config.git }; if (gitRepoPath) configOverride.git.repoPath = gitRepoPath; if (gitDefaultBranch) configOverride.git.defaultBranch = gitDefaultBranch; if (gitIncludeStaged !== undefined) configOverride.git.includeStaged = gitIncludeStaged; if (gitIncludeUnstaged !== undefined) configOverride.git.includeUnstaged = gitIncludeUnstaged; } // Output options (with env var fallbacks) const outputFormat = options.outputFormat || process.env.OUTPUT_FORMAT; const outputPath = options.outputPath || process.env.OUTPUT_PATH; const outputVerbose = options.outputVerbose !== undefined ? options.outputVerbose : (process.env.OUTPUT_VERBOSE === 'true'); const outputColorize = options.outputColorize !== undefined ? options.outputColorize : (process.env.OUTPUT_COLORIZE !== 'false'); if (outputFormat || outputPath || outputVerbose !== undefined || outputColorize !== undefined) { configOverride.output = { ...config.output }; if (outputFormat) configOverride.output.format = outputFormat as any; if (outputPath) configOverride.output.outputPath = outputPath; if (outputVerbose !== undefined) configOverride.output.verbose = outputVerbose; if (outputColorize !== undefined) configOverride.output.colorize = outputColorize; } // Security options (with env var fallbacks) const securityEnabled = options.securityEnabled !== undefined ? options.securityEnabled : (process.env.SECURITY_ENABLED === 'true'); const securitySeverity = options.securitySeverity || process.env.SECURITY_SEVERITY; const securityCategories = options.securityCategories || process.env.SECURITY_CATEGORIES; if (securityEnabled !== undefined || securitySeverity || securityCategories) { configOverride.security = { ...config.security }; if (securityEnabled !== undefined) configOverride.security.enabled = securityEnabled; if (securitySeverity) { configOverride.security.severity = securitySeverity.split(',').map((s: string) => s.trim()) as any; } if (securityCategories) { configOverride.security.categories = securityCategories.split(',').map((s: string) => s.trim()) as any; } } // File pattern options (with env var fallbacks) const filePatterns = options.filePatterns || process.env.FILE_PATTERNS; const excludePatterns = options.excludePatterns || process.env.EXCLUDE_PATTERNS; if (filePatterns) { configOverride.filePatterns = filePatterns.split(',').map((p: string) => p.trim()); } if (excludePatterns) { configOverride.excludePatterns = excludePatterns.split(',').map((p: string) => p.trim()); } const finalConfig = { ...config, ...configOverride }; // Store config in program for use by commands (program as any).config = finalConfig; } catch (error) { console.error(`Failed to load configuration: ${error instanceof Error ? error.message : 'Unknown error'}`); process.exit(1); } }); // Register commands program.addCommand(new ReviewCommand()); program.addCommand(new ExplainCommand()); program.addCommand(new CommitMessageCommand()); program.addCommand(new RefactorCommand()); program.addCommand(new TestSuggestCommand()); program.addCommand(new SecurityScanCommand()); program.addCommand(new DocsCommand()); program.addCommand(new PRSummaryCommand()); program.addCommand(new MentorCommand()); program.addCommand(new ConfigCommand()); program.addCommand(new VersionCommand()); // Handle unknown commands program.on('command:*', () => { console.error(`Unknown command: ${program.args.join(' ')}`); console.log('Use --help to see available commands'); process.exit(1); }); // Handle errors program.on('error', (error) => { console.error(`Error: ${error.message}`); process.exit(1); }); // Parse arguments program.parse(); // If no command is provided, show help if (!process.argv.slice(2).length) { program.outputHelp(); }