codecrucible-synth
Version:
Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability
322 lines (279 loc) • 9.82 kB
text/typescript
/**
* Interactive REPL Mode for CodeCrucible Synth
* Provides a continuous interactive session like Claude
*/
import * as readline from 'readline';
import chalk from 'chalk';
import { REPLInterface } from './types.js';
import { CLIContext } from './cli/cli-types.js';
import { CLI } from './cli.js';
import { Logger } from './logger.js';
import { getErrorMessage } from '../utils/error-utils.js';
export class InteractiveREPL {
private rl: readline.Interface;
private cli: CLI;
private context: CLIContext;
private logger: Logger;
private isProcessing = false;
private history: string[] = [];
private currentModel: string = '';
constructor(cli: CLI, context: CLIContext) {
this.cli = cli;
this.context = context;
this.logger = new Logger('InteractiveREPL');
// Create readline interface for interactive input
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: chalk.cyan('\nCC> '),
historySize: 100,
removeHistoryDuplicates: true,
});
this.setupEventHandlers();
this.currentModel = context.config?.model?.name || 'qwen2.5-coder:7b';
}
/**
* Start the interactive REPL session
*/
async start(): Promise<void> {
// Show welcome banner
this.showBanner();
// Show initial status
await this.showQuickStatus();
// Start the REPL
this.rl.prompt();
}
/**
* Show welcome banner
*/
private showBanner(): void {
console.log(chalk.blue('\n╔══════════════════════════════════════════════════════════════╗'));
console.log(chalk.blue('║ CodeCrucible Synth ║'));
console.log(chalk.blue('║ Interactive AI Coding Assistant ║'));
console.log(chalk.blue('║ ║'));
console.log(chalk.blue('║ Type "help" for commands, "exit" to quit ║'));
console.log(chalk.blue('╚══════════════════════════════════════════════════════════════╝'));
}
/**
* Show quick status info
*/
private async showQuickStatus(): Promise<void> {
console.log(chalk.gray(`\n📊 Model: ${chalk.white(this.currentModel)}`));
console.log(chalk.gray(`🔧 Tools: ${chalk.green('Enabled')} (filesystem, git, terminal)`));
console.log(chalk.gray(`💾 Context: ${chalk.white(process.cwd())}\n`));
}
/**
* Setup event handlers for the REPL
*/
private setupEventHandlers(): void {
// Handle each line of input
this.rl.on('line', async (input: string) => {
const trimmedInput = input.trim();
// Skip empty input
if (!trimmedInput) {
this.rl.prompt();
return;
}
// Add to history
this.history.push(trimmedInput);
// Handle special commands
if (this.isSpecialCommand(trimmedInput)) {
await this.handleSpecialCommand(trimmedInput);
} else {
// Process as normal prompt
await this.processPrompt(trimmedInput);
}
});
// Handle Ctrl+C
this.rl.on('SIGINT', () => {
if (this.isProcessing) {
console.log(chalk.yellow('\n⚠️ Interrupting current task...'));
this.isProcessing = false;
this.rl.prompt();
} else {
this.handleExit();
}
});
// Handle close event
this.rl.on('close', () => {
this.handleExit();
});
}
/**
* Check if input is a special command
*/
private isSpecialCommand(input: string): boolean {
const commands = ['help', 'exit', 'quit', 'clear', 'status', 'models', 'history', 'reset'];
const firstWord = input.split(' ')[0].toLowerCase();
return commands.includes(firstWord);
}
/**
* Handle special REPL commands
*/
private async handleSpecialCommand(input: string): Promise<void> {
const [command, ...args] = input.split(' ');
const cmd = command.toLowerCase();
switch (cmd) {
case 'help':
this.showHelp();
break;
case 'exit':
case 'quit':
this.handleExit();
return;
case 'clear':
console.clear();
this.showBanner();
break;
case 'status':
await this.cli.showStatus();
break;
case 'models':
await this.cli.listModels();
break;
case 'history':
this.showHistory();
break;
case 'reset':
console.log(chalk.yellow('🔄 Resetting conversation context...'));
// Reset context if needed
console.log(chalk.green('✅ Context reset'));
break;
default:
console.log(chalk.red(`Unknown command: ${cmd}`));
}
this.rl.prompt();
}
/**
* Show REPL help
*/
private showHelp(): void {
console.log(chalk.cyan('\n📚 Interactive Commands:'));
console.log(' help Show this help message');
console.log(' exit/quit Exit the interactive session');
console.log(' clear Clear the screen');
console.log(' status Show system status');
console.log(' models List available models');
console.log(' history Show command history');
console.log(' reset Reset conversation context');
console.log();
console.log(chalk.cyan('💡 Usage Tips:'));
console.log(' - Just type your request naturally, like chatting');
console.log(' - The AI has access to filesystem tools when needed');
console.log(' - Use Ctrl+C to interrupt a running task');
console.log(' - Press Ctrl+C twice to exit');
}
/**
* Process a normal prompt
*/
private async processPrompt(prompt: string): Promise<void> {
this.isProcessing = true;
try {
console.log(chalk.gray('🤔 Processing...\n'));
// Check if this is a codebase analysis request
if (this.isCodebaseAnalysisRequest(prompt)) {
console.log(chalk.blue('🔍 Direct codebase analysis mode activated'));
await this.executeDirectCodebaseAnalysis(prompt);
} else {
// Pass other prompts to the CLI for processing
await this.cli.executePromptProcessing(prompt, {
stream: true,
autonomous: true,
contextAware: true,
});
}
} catch (error: unknown) {
const errorMessage = getErrorMessage(error);
console.error(chalk.red('❌ Error:'), errorMessage);
} finally {
this.isProcessing = false;
this.rl.prompt();
}
}
/**
* Check if this is a codebase analysis request
*/
private isCodebaseAnalysisRequest(prompt: string): boolean {
const lowerPrompt = prompt.toLowerCase();
return (
lowerPrompt.includes('analyze this codebase') ||
lowerPrompt.includes('analyze the codebase') ||
lowerPrompt.includes('audit this codebase') ||
lowerPrompt.includes('audit the codebase') ||
(lowerPrompt.includes('analyze') && lowerPrompt.includes('project')) ||
(lowerPrompt.includes('analyze') && lowerPrompt.includes('code')) ||
lowerPrompt.includes('comprehensive audit') ||
lowerPrompt.includes('thorough audit')
);
}
/**
* Execute direct codebase analysis in interactive mode
*/
private async executeDirectCodebaseAnalysis(prompt: string): Promise<void> {
try {
const { simpleCodebaseAnalyzer } = await import('./simple-codebase-analyzer.js');
console.log(chalk.gray('Using conflict-free direct analysis...'));
console.log(chalk.yellow('⏳ This may take 1-2 minutes for comprehensive analysis\n'));
const result = await simpleCodebaseAnalyzer.analyzeCurrentProject();
if (result.success) {
console.log(chalk.green('\n✅ Codebase Analysis Complete'));
console.log(chalk.blue('═'.repeat(80)));
console.log(result.content);
console.log(chalk.blue('═'.repeat(80)));
console.log(chalk.gray(`\n📊 Analysis Statistics:`));
console.log(chalk.gray(` Duration: ${(result.metadata.duration / 1000).toFixed(1)}s`));
console.log(chalk.gray(` Response length: ${result.metadata.responseLength} characters`));
console.log(
chalk.gray(
` Project items analyzed: ${result.metadata.projectStructure.split('\n').length}`
)
);
} else {
console.error(chalk.red('❌ Direct codebase analysis failed:'), result.error);
console.log(
chalk.yellow('🔄 You can try rephrasing your request or use a simpler prompt.')
);
}
} catch (error: unknown) {
const errorMessage = getErrorMessage(error);
console.error(chalk.red('Failed to load direct analyzer:'), errorMessage);
}
}
/**
* Show command history
*/
private showHistory(): void {
console.log(chalk.cyan('\n📜 Command History:'));
this.history.slice(-10).forEach((cmd, idx) => {
console.log(chalk.gray(` ${idx + 1}. ${cmd}`));
});
}
/**
* Handle exit
*/
private handleExit(): void {
console.log(chalk.cyan('\n👋 Goodbye!'));
this.rl.close();
// Don't call process.exit in test environments
if (process.env.NODE_ENV !== 'test') {
process.exit(0);
}
}
/**
* Clean up resources
*/
destroy(): void {
this.rl.close();
}
/**
* Cleanup method for graceful shutdown
*/
async cleanup(): Promise<void> {
return new Promise(resolve => {
if (this.rl) {
this.rl.close();
}
resolve();
});
}
}