UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

454 lines (393 loc) • 16.4 kB
/** * CLI Command Handlers * Handles the execution of specific CLI commands */ import chalk from 'chalk'; import ora from 'ora'; import { readFile, stat } from 'fs/promises'; import { join, extname, isAbsolute } from 'path'; import { glob } from 'glob'; import { logger } from '../logger.js'; import { CLIOptions, CLIContext } from './cli-types.js'; // import { CLIDisplay } from './cli-display.js'; // import { ProjectContext } from '../client.js'; import { ServerModeInterface } from '../../refactor/server-mode-interface.js'; import { analysisWorkerPool, AnalysisTask } from '../workers/analysis-worker.js'; import { randomUUID } from 'crypto'; export class CLICommands { private context: CLIContext; private workingDirectory: string; constructor(context: CLIContext, workingDirectory: string = process.cwd()) { this.context = context; this.workingDirectory = workingDirectory; } /** * Show system status including model connections and health */ async showStatus(): Promise<void> { console.log(chalk.bold('\nšŸ“Š CodeCrucible Synth - System Status\n')); // Model Client Status console.log(chalk.cyan('šŸ¤– Model Client:')); try { if (this.context.modelClient) { logger.debug('About to call healthCheck'); const healthCheck = await this.context.modelClient.healthCheck(); logger.debug('HealthCheck completed', { healthCheck }); console.log(chalk.green(` āœ… Status: ${healthCheck ? 'Connected' : 'Disconnected'}`)); if (typeof (this.context.modelClient as any).getCurrentModel === 'function') { const currentModel = (this.context.modelClient as any).getCurrentModel(); console.log(chalk.cyan(` šŸŽÆ Current Model: ${currentModel || 'Auto-detect'}`)); } } else { console.log(chalk.red(' āŒ Model client not initialized')); } } catch (error) { console.log( chalk.red(` āŒ Error: ${error instanceof Error ? error.message : 'Unknown error'}`) ); } // Voice System Status console.log(chalk.cyan('\nšŸŽ­ Voice System:')); try { if (this.context.voiceSystem) { const voices = this.context.voiceSystem.getAvailableVoices(); console.log(chalk.green(` āœ… Available Voices: ${voices.length}`)); console.log(chalk.cyan(` šŸŽÆ Voice Names: ${voices.map(v => v.name).join(', ')}`)); } else { console.log(chalk.red(' āŒ Voice system not initialized')); } } catch (error) { console.log( chalk.red(` āŒ Error: ${error instanceof Error ? error.message : 'Unknown error'}`) ); } // MCP Server Status console.log(chalk.cyan('\nšŸ”§ MCP Servers:')); try { if (this.context.mcpManager) { const serverCount = (this.context.mcpManager as any).servers?.size || 0; console.log(chalk.green(` āœ… Active Servers: ${serverCount}`)); if (typeof (this.context.mcpManager as any).isReady === 'function') { const ready = (this.context.mcpManager as any).isReady(); console.log(chalk.cyan(` šŸš€ Ready: ${ready ? 'Yes' : 'No'}`)); } } else { console.log(chalk.red(' āŒ MCP manager not initialized')); } } catch (error) { console.log( chalk.red(` āŒ Error: ${error instanceof Error ? error.message : 'Unknown error'}`) ); } // Configuration Status console.log(chalk.cyan('\nāš™ļø Configuration:')); try { if (this.context.config) { console.log(chalk.green(' āœ… Configuration loaded')); if (this.context.config.model) { console.log( chalk.cyan(` šŸ”— Endpoint: ${this.context.config.model.endpoint || 'Default'}`) ); console.log( chalk.cyan(` ā±ļø Timeout: ${this.context.config.model.timeout || 'Default'}ms`) ); } } else { console.log(chalk.red(' āŒ Configuration not loaded')); } } catch (error) { console.log( chalk.red(` āŒ Error: ${error instanceof Error ? error.message : 'Unknown error'}`) ); } console.log(chalk.green('\n✨ System check complete!\n')); } /** * List available AI models */ async listModels(): Promise<void> { console.log(chalk.bold('\n🧠 Available AI Models\n')); const spinner = ora('Fetching available models...').start(); try { if (!this.context.modelClient) { spinner.fail('Model client not available'); console.log(chalk.red('āŒ Model client is not initialized')); return; } // Check if getAllAvailableModels method exists if (typeof this.context.modelClient.getAllAvailableModels === 'function') { const models = await this.context.modelClient.getAllAvailableModels(); spinner.succeed(`Found ${models.length} models`); if (models.length > 0) { console.log(chalk.cyan('šŸ“‹ Available Models:')); models.forEach((model, index) => { console.log(chalk.white(` ${index + 1}. ${model.name || model.id || model}`)); if (model.size) { console.log(chalk.gray(` Size: ${model.size}`)); } if (model.modified_at) { console.log( chalk.gray(` Modified: ${new Date(model.modified_at).toLocaleDateString()}`) ); } }); } else { console.log(chalk.yellow('No models found. Make sure your AI service is running.')); } } else { spinner.warn('Model listing not supported'); console.log(chalk.yellow('ā„¹ļø Model listing is not supported by the current client')); } } catch (error) { spinner.fail('Failed to fetch models'); console.error(chalk.red('āŒ Error fetching models:'), error); } console.log(''); } /** * Handle code generation requests */ async handleGeneration(prompt: string, options: CLIOptions = {}): Promise<void> { console.log(chalk.bold(`\nšŸŽØ Generating Code\n`)); console.log(chalk.cyan(`Prompt: ${prompt}`)); const spinner = ora('Generating code...').start(); try { if (!this.context.modelClient || !this.context.voiceSystem) { spinner.fail('Required services not available'); console.log(chalk.red('āŒ Model client or voice system not initialized')); return; } // Get voices for code generation const voices = options.voices || this.context.voiceSystem.getDefaultVoices() || ['developer']; // Generate multi-voice solutions const results = await this.context.voiceSystem.generateMultiVoiceSolutions( Array.isArray(voices) ? voices : [voices], prompt, { files: [] } ); spinner.succeed('Code generation complete'); if (results && results.length > 0) { console.log(chalk.green('\n✨ Generated Solutions:\n')); results.forEach((result: any, index: number) => { console.log(chalk.cyan(`\n${index + 1}. ${result.voice || 'Voice'} Solution:`)); console.log(chalk.white(result.content || 'No content generated')); if (result.confidence) { console.log(chalk.gray(` Confidence: ${Math.round(result.confidence * 100)}%`)); } }); } else { console.log(chalk.yellow('No solutions generated')); } } catch (error) { spinner.fail('Code generation failed'); console.error(chalk.red('āŒ Error during generation:'), error); } console.log(''); } /** * Handle file and directory analysis */ async handleAnalyze(files: string[] = [], options: CLIOptions = {}): Promise<void> { if (files.length === 0) { // Use our real codebase analysis for directory analysis console.log(chalk.cyan('šŸ” Performing comprehensive codebase analysis...')); try { logger.debug('About to import CodebaseAnalyzer'); const { CodebaseAnalyzer } = await import('../analysis/codebase-analyzer.js'); logger.debug('CodebaseAnalyzer imported successfully'); const analyzer = new CodebaseAnalyzer(this.workingDirectory); logger.debug('Analyzer created, starting analysis'); const analysis = await analyzer.performAnalysis(); logger.debug('Analysis completed'); console.log(analysis); } catch (error) { console.error(chalk.red('āŒ Analysis failed:'), error); logger.debug('Falling back to original analysis'); // For now, just show a message instead of hanging console.log( chalk.yellow('šŸ”§ Real-time analysis temporarily unavailable. Using fallback method.') ); console.log(chalk.cyan('ā„¹ļø The comprehensive analysis feature is being refined.')); } } else { // Analyze specified files using original logic for (const file of files) { const fullPath = isAbsolute(file) ? file : join(this.workingDirectory, file); try { const stats = await stat(fullPath); if (stats.isDirectory()) { await this.analyzeDirectory(fullPath, options); } else { await this.analyzeFile(fullPath, options); } } catch (error) { console.error(chalk.red(`āŒ Cannot access ${file}:`), error); } } } } /** * Start server mode */ async startServer(options: CLIOptions, serverMode: ServerModeInterface): Promise<void> { const port = parseInt(options.port || '3002', 10); console.log(chalk.cyan(`\nšŸš€ Starting CodeCrucible Server on port ${port}...`)); const serverOptions: any = { port, host: 'localhost', cors: true, }; try { await serverMode.startServerMode(this.context, serverOptions); console.log(chalk.green(`āœ… Server running at http://localhost:${port}`)); } catch (error) { console.error(chalk.red('āŒ Failed to start server:'), error); process.exit(1); } } /** * Analyze a directory structure with worker pool */ private async analyzeDirectory(dirPath: string, options: CLIOptions): Promise<void> { console.log(chalk.bold(`\nšŸ“ Analyzing Directory: ${dirPath}`)); const spinner = ora('Scanning directory...').start(); try { // Get file patterns for analysis const patterns = [ '**/*.{ts,js,tsx,jsx,py,java,cpp,hpp,c,h,rs,go,php,rb,swift,kt}', '**/package.json', '**/tsconfig.json', '**/README.md', ]; const allFiles: string[] = []; for (const pattern of patterns) { const files = await glob(pattern, { cwd: dirPath, ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'], }); allFiles.push(...files.map(f => join(dirPath, f))); } spinner.text = `Found ${allFiles.length} files, analyzing...`; // PERFORMANCE FIX: Use worker pool for non-blocking analysis if (this.context.voiceSystem && this.context.modelClient) { const voices = options.voices || ['analyzer', 'architect']; // Create analysis task for worker pool const analysisTask: AnalysisTask = { id: randomUUID(), files: allFiles, prompt: (options.prompt as string) || 'Analyze this codebase for architecture, quality, and potential improvements.', options: { voices, maxFiles: 50, // Limit files to prevent memory overload }, timeout: options.timeout || 300000, // 5 minutes timeout for complex analysis }; // Execute analysis in worker thread try { const result = await analysisWorkerPool.executeAnalysis(analysisTask, { endpoint: this.context.config.model?.endpoint || 'http://localhost:11434', providers: [{ type: 'ollama' as const }], executionMode: 'auto' as const, fallbackChain: ['ollama' as const], performanceThresholds: { fastModeMaxTokens: 2048, timeoutMs: 300000, // 5 minutes for complex operations maxConcurrentRequests: 2, }, security: { enableSandbox: true, maxInputLength: 100000, allowedCommands: ['node', 'npm', 'git'], }, }); spinner.succeed(`Analysis complete - processed ${result.result?.totalFiles || 0} files`); // Display results if (result.success && result.result) { console.log(chalk.green('\nāœ… Analysis Results:')); console.log(chalk.cyan(`šŸ“Š Files processed: ${result.result.totalFiles}`)); console.log(chalk.cyan(`ā±ļø Duration: ${result.duration}ms`)); console.log(chalk.cyan(`šŸ“ˆ Success rate: ${result.result.summary?.successRate || 0}%`)); // Display chunk results result.result.chunks.forEach((chunk: any, index: number) => { console.log(chalk.yellow(`\nšŸ“¦ Chunk ${index + 1}:`)); if (chunk.error) { console.log(chalk.red(`āŒ Error: ${chunk.error}`)); } else if (chunk.analysis) { console.log(chalk.green(`āœ… Successfully analyzed ${chunk.files.length} files`)); if (chunk.analysis.length > 0) { console.log(chalk.white(chunk.analysis[0].content.substring(0, 200) + '...')); } } }); } else { console.log(chalk.red(`āŒ Analysis failed: ${result.error || 'Unknown error'}`)); } } catch (analysisError) { spinner.fail('Analysis failed'); console.error(chalk.red('Error during analysis:'), analysisError); } } else { spinner.succeed('Basic directory scan complete'); console.log(chalk.cyan(`\nšŸ“Š Summary:`)); console.log(` Files found: ${allFiles.length}`); console.log(` Types: ${this.getFileTypes(allFiles)}`); } } catch (error) { spinner.fail('Directory analysis failed'); console.error(chalk.red('Error:'), error); } } /** * Analyze a specific file */ private async analyzeFile(filePath: string, options: CLIOptions): Promise<void> { console.log(chalk.bold(`\nšŸ“„ Analyzing File: ${filePath}`)); const spinner = ora('Reading file...').start(); try { const content = await readFile(filePath, 'utf-8'); const stats = await stat(filePath); spinner.text = 'Analyzing content...'; console.log(chalk.cyan('\nšŸ“Š File Analysis:')); console.log(` Size: ${(stats.size / 1024).toFixed(2)} KB`); console.log(` Lines: ${content.split('\n').length}`); console.log(` Type: ${extname(filePath)}`); if (this.context.voiceSystem && this.context.modelClient) { const voices = options.voices || ['analyzer']; const analysis = await this.context.voiceSystem.generateMultiVoiceSolutions( Array.isArray(voices) ? voices : [voices], `Analyze this file: ${filePath}\n\nContent:\n${content.substring(0, 2000)}${content.length > 2000 ? '...' : ''}`, { files: [filePath] } ); spinner.succeed('File analysis complete'); if (analysis && analysis.length > 0) { analysis.forEach((result: any, index: number) => { console.log(chalk.cyan(`\n${index + 1}. ${result.voice || 'Voice'} Analysis:`)); console.log(result.content || 'No analysis available'); }); } else { console.log(chalk.yellow('No analysis results available')); } } else { spinner.succeed('Basic file scan complete'); } } catch (error) { spinner.fail('File analysis failed'); console.error(chalk.red('Error:'), error); } } /** * Get file type summary */ private getFileTypes(files: string[]): string { const types = new Map<string, number>(); files.forEach(file => { const ext = extname(file) || 'no extension'; types.set(ext, (types.get(ext) || 0) + 1); }); return Array.from(types.entries()) .map(([ext, count]) => `${ext}(${count})`) .join(', '); } }