UNPKG

trace.ai-cli

Version:

A powerful AI-powered CLI tool

486 lines (415 loc) 18.9 kB
const readline = require('readline'); const figlet = require('figlet'); const chalk = require('chalk'); const path = require('path'); const fs = require('fs').promises; const { analyzeFile } = require('../services/fileAnalyzer'); const { analyzeFolderStructure } = require('../services/folderAnalyzer'); const { extractTextFromImage } = require('../services/imageService'); const { processWithAI } = require('../services/aiService'); class TraceAI { constructor() { this.rl = readline.createInterface({ input: process.stdin, output: process.stdout, prompt: '' }); this.contexts = []; this.sessionStartTime = new Date(); this.queryCount = 0; } // Enhanced startup with animated loading async start() { // Clear screen for clean start console.clear(); // Animated startup sequence await this.showLoadingAnimation(); // Main header with enhanced ASCII art await this.displayHeader(); // Show welcome message and commands this.displayWelcomeMessage(); // Start the interactive session this.promptUser(); } async showLoadingAnimation() { const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; const messages = [ 'Initializing Trace.AI...', 'Loading AI modules...', 'Preparing analysis engines...', 'Ready to trace!' ]; for (let i = 0; i < messages.length; i++) { for (let j = 0; j < 10; j++) { process.stdout.write(`\r${chalk.blueBright(frames[j % frames.length])} ${chalk.white(messages[i])}`); await new Promise(resolve => setTimeout(resolve, 80)); } } console.log(`\r${chalk.green('✓')} ${chalk.white('Trace.Ai initialized successfully!')}\n`); await new Promise(resolve => setTimeout(resolve, 500)); } async displayHeader() { // Create enhanced ASCII art const asciiArt = figlet.textSync('TRACE.AI', { font: 'ANSI Shadow', horizontalLayout: 'fitted', verticalLayout: 'default' }); // Enhanced header with gradient effect console.log(chalk.bold.blueBright(asciiArt)); // Subtitle and version info console.log(chalk.gray.bold(' ') + chalk.white.bold('AI powered CLI Platform')); console.log(chalk.gray(' ') + chalk.gray('v1.1.0 | Powered by Mixkey')); // Dynamic separator const width = process.stdout.columns || 80; console.log(chalk.blueBright('═'.repeat(Math.min(width, 80)))); } displayWelcomeMessage() { // Welcome section with better formatting console.log(chalk.bold.green('\nWelcome to Trace.AI CLI')); console.log(); // Enhanced command documentation console.log(chalk.bold.cyan('📋 AVAILABLE COMMANDS')); console.log(chalk.gray('━'.repeat(50))); const commands = [ { cmd: '/file <path> [question]', desc: 'Analyze any code file with AI insights', example: '/file "src/app.js" explain the main function', icon: '📄' }, { cmd: '/folder <path> [question]', desc: 'Analyze entire project structure', example: '/folder "src/" what is the architecture?', icon: '📁' }, { cmd: '/image <path> [question]', desc: 'Analyze image content', example: '/image "diagram.png" explain this flowchart', icon: '🖼️' }, { cmd: '/context <text>', desc: 'Add contextual information', example: '/context This is a React project using TypeScript', icon: '💭' }, { cmd: '/context-file <path>', desc: 'Add file content as context', example: '/context-file "README.md"', icon: '📋' }, { cmd: '/view-context', desc: 'Display all active contexts', example: '/view-context', icon: '👁️' }, { cmd: '/clear [type]', desc: 'Clear contexts or screen', example: '/clear or /clear context', icon: '🧹' }, { cmd: '/help', desc: 'Show detailed help information', example: '/help', icon: '❓' }, { cmd: '/stats', desc: 'Show session statistics', example: '/stats', icon: '📊' }, { cmd: '/exit', desc: 'Exit the application', example: '/exit', icon: '👋' } ]; commands.forEach(({ cmd, desc, icon }) => { console.log(`${chalk.blueBright(icon)} ${chalk.bold.magenta(cmd.padEnd(25))} ${chalk.gray(desc)}`); }); console.log(chalk.gray('\n' + '━'.repeat(50))); // Quick start section console.log(chalk.bold.cyan('⚡ QUICK START')); console.log(chalk.gray('━'.repeat(20))); console.log(chalk.white('• Type ') + chalk.cyan('/help') + chalk.white(' for detailed guidance')); console.log(chalk.white('• Use ') + chalk.cyan('Tab') + chalk.white(' for auto-completion (when available)')); console.log(chalk.white('• Start with ') + chalk.cyan('/file "your-file.js"') + chalk.white(' to analyze code')); console.log(chalk.white('• Or just ask any question directly!')); // Status bar this.displayStatusBar(); console.log(chalk.gray('\n' + '─'.repeat(60))); } displayStatusBar() { const uptime = Math.floor((new Date() - this.sessionStartTime) / 1000); const contextCount = this.contexts.length; console.log(chalk.gray('\n┌─ SESSION INFO ') + chalk.gray('─'.repeat(34))); console.log(chalk.gray('│ ') + chalk.white('Uptime: ') + chalk.green(`${uptime}s`) + chalk.gray(' │ ') + chalk.white('Contexts: ') + chalk.cyan(contextCount) + chalk.gray(' │ ') + chalk.white('Queries: ') + chalk.blueBright(this.queryCount)); console.log(chalk.gray('└─') + chalk.gray('─'.repeat(48))); } promptUser() { // Enhanced prompt with status indicators const contextIndicator = this.contexts.length > 0 ? chalk.cyan(`[${this.contexts.length}]`) : ''; const prompt = `${chalk.bold.blueBright('Trace.Ai')} ${contextIndicator}${chalk.gray('>')} `; this.rl.question(prompt, async (input) => { try { if (input.trim()) { this.queryCount++; await this.handleInput(input); } } catch (error) { this.displayError(error.message); } this.promptUser(); }); } async handleInput(input) { const trimmedInput = input.trim(); if (!trimmedInput) return; // Command routing with enhanced feedback const commands = { '/file': () => this.handleFileCommand(trimmedInput), '/folder': () => this.handleFolderCommand(trimmedInput), '/image': () => this.handleImageCommand(trimmedInput), '/context ': () => this.handleContextCommand(trimmedInput), '/context-file': () => this.handleContextFileCommand(trimmedInput), '/view-context': () => this.displayContexts(), '/clear': () => this.handleClearCommand(trimmedInput), '/help': () => this.displayHelp(), '/stats': () => this.displayStats(), '/exit': () => this.close() }; // Find and execute command const commandKey = Object.keys(commands).find(cmd => trimmedInput.startsWith(cmd)); if (commandKey) { await commands[commandKey](); } else { await this.handleTextQuery(trimmedInput); } } async handleFileCommand(input) { const parts = this.parseCommand(input); if (!this.validateCommand(parts, 2, 'Usage: /file <file_path> [query]')) return; const filePath = this.cleanPath(parts[1]); const query = parts.slice(2).join(' ').replace(/^"|"$/g, '') || 'Analyze this file'; await this.executeWithSpinner( `Analyzing file: ${path.basename(filePath)}`, async () => { const result = await analyzeFile(path.resolve(filePath), query); this.displayResult('File Analysis', result.text, filePath); } ); } async handleFolderCommand(input) { const parts = this.parseCommand(input); if (!this.validateCommand(parts, 2, 'Usage: /folder <folder_path> [query]')) return; const folderPath = this.cleanPath(parts[1]); const query = parts.slice(2).join(' ').replace(/^"|"$/g, '') || 'Analyze this folder structure'; await this.executeWithSpinner( `Analyzing folder: ${path.basename(folderPath)}`, async () => { const result = await analyzeFolderStructure(path.resolve(folderPath), query); this.displayResult('Folder Analysis', result.text, folderPath); } ); } async handleImageCommand(input) { const parts = this.parseCommand(input); if (!this.validateCommand(parts, 2, 'Usage: /image <image_path> [query]')) return; const imagePath = this.cleanPath(parts[1]); const query = parts.slice(2).join(' ').replace(/^"|"$/g, '') || 'Describe this image'; await this.executeWithSpinner( `Analyzing image: ${path.basename(imagePath)}`, async () => { const result = await extractTextFromImage(path.resolve(imagePath), query); this.displayResult('Image Analysis', result, imagePath); } ); } handleContextCommand(input) { const context = input.substring('/context '.length).trim(); if (context) { this.contexts.push({ type: 'text', content: context, timestamp: new Date().toLocaleTimeString() }); console.log(chalk.green('✓ Context added successfully')); } else { console.log(chalk.cyan('⚠️ No context provided')); } } async handleContextFileCommand(input) { const filePath = input.substring('/context-file'.length).trim().replace(/^"|"$/g, ''); if (!filePath) { console.log(chalk.cyan('⚠️ No file path provided')); return; } try { const resolvedPath = path.resolve(filePath); const content = await fs.readFile(resolvedPath, 'utf8'); this.contexts.push({ type: 'file', content: `File ${path.basename(resolvedPath)}:\n${content}`, filename: path.basename(resolvedPath), timestamp: new Date().toLocaleTimeString() }); console.log(chalk.green(`✓ File ${path.basename(resolvedPath)} added to context`)); } catch (error) { this.displayError(`Error reading file: ${error.message}`); } } displayContexts() { if (this.contexts.length === 0) { console.log(chalk.cyan('📭 No active contexts')); return; } console.log(chalk.bold.green('\n📋 ACTIVE CONTEXTS')); console.log(chalk.gray('━'.repeat(50))); this.contexts.forEach((ctx, index) => { const icon = ctx.type === 'file' ? '📄' : '💭'; const preview = ctx.content.substring(0, 80).replace(/\n/g, ' '); const truncated = ctx.content.length > 80 ? '...' : ''; console.log(`${chalk.blueBright(icon)} ${chalk.bold(`[${index + 1}]`)} ${chalk.gray(ctx.timestamp)}`); console.log(` ${chalk.white(preview)}${chalk.gray(truncated)}`); if (index < this.contexts.length - 1) console.log(); }); console.log(chalk.gray('━'.repeat(50))); } handleClearCommand(input) { if (input === '/clear' || input === '/clear all') { this.contexts = []; console.log(chalk.green('✓ All contexts cleared')); } else if (input === '/clear screen') { console.clear(); this.displayHeader(); } else if (input === '/clear context') { this.contexts = []; console.log(chalk.green('✓ Context cleared')); } else { console.log(chalk.cyan('Usage: /clear [all|context|screen]')); } } displayHelp() { console.log(chalk.bold.green('\n📖 DETAILED HELP GUIDE')); console.log(chalk.gray('━'.repeat(60))); console.log(chalk.bold.cyan('\n🎯 GETTING STARTED')); console.log('Trace.AI is an intelligent CLI tool that helps you understand and analyze files,'); console.log('folder structures, and images using advanced AI capabilities.\n'); console.log(chalk.bold.cyan('💡 TIPS FOR BETTER RESULTS')); console.log('• Be specific in your questions'); console.log('• Use context to provide background information'); console.log('• Combine multiple commands for comprehensive analysis'); console.log('• File paths with spaces should be quoted\n'); console.log(chalk.bold.cyan('🔧 TROUBLESHOOTING')); console.log('• Ensure file paths are correct and accessible'); console.log('• Check file permissions for read access'); console.log('• Use /stats to monitor your session'); console.log('• Use /clear screen to refresh the interface\n'); console.log(chalk.gray('Press any key to continue...')); } displayStats() { const uptime = Math.floor((new Date() - this.sessionStartTime) / 1000); const minutes = Math.floor(uptime / 60); const seconds = uptime % 60; console.log(chalk.bold.green('\n📊 SESSION STATISTICS')); console.log(chalk.gray('━'.repeat(40))); console.log(`${chalk.blueBright('🕐')} Session Duration: ${chalk.white(`${minutes}m ${seconds}s`)}`); console.log(`${chalk.blueBright('💬')} Total Queries: ${chalk.white(this.queryCount)}`); console.log(`${chalk.blueBright('📋')} Active Contexts: ${chalk.white(this.contexts.length)}`); console.log(`${chalk.blueBright('🖥️')} Terminal Width: ${chalk.white(process.stdout.columns || 'Unknown')}`); console.log(`${chalk.blueBright('📅')} Started: ${chalk.white(this.sessionStartTime.toLocaleString())}`); console.log(chalk.gray('━'.repeat(40))); } async handleTextQuery(input) { await this.executeWithSpinner( 'Processing your query', async () => { const context = this.contexts.map(ctx => ctx.content).join('\n\n'); const result = await processWithAI(input, context); this.displayResult('Trace.Ai Response', result); } ); } // Utility methods parseCommand(input) { return input.match(/(?:[^\s"]+|"[^"]*")+/g) || []; } cleanPath(path) { // First remove quotes from beginning and end let cleanedPath = path.replace(/^"|"$/g, ''); // Then handle escaped spaces and other escaped characters cleanedPath = cleanedPath.replace(/\\([ \(\)\[\]\{\}\&\^\%\$\#\@\!\,\.\;\:\'\"])/g, '$1'); return cleanedPath; } validateCommand(parts, minLength, usage) { if (!parts || parts.length < minLength) { console.log(chalk.cyan(`⚠️ ${usage}`)); return false; } return true; } async executeWithSpinner(message, task) { const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; let frameIndex = 0; const spinner = setInterval(() => { process.stdout.write(`\r${chalk.blueBright(frames[frameIndex])} ${chalk.white(message)}`); frameIndex = (frameIndex + 1) % frames.length; }, 100); try { await task(); clearInterval(spinner); process.stdout.write(`\r${chalk.green('✓')} ${chalk.white(message)} ${chalk.gray('- Complete!')}\n`); } catch (error) { clearInterval(spinner); process.stdout.write(`\r${chalk.red('✗')} ${chalk.white(message)} ${chalk.gray('- Failed!')}\n`); this.displayError(error.message); } } displayResult(title, content, filePath = null) { console.log(chalk.bold.blueBright(`\n${title.toUpperCase()}`)); if (filePath) { console.log(chalk.gray(`📍 Source: ${filePath}`)); } console.log(chalk.blueBright('═'.repeat(60))); console.log(chalk.white(content)); console.log(chalk.blueBright('═'.repeat(60))); console.log(); } displayError(message) { console.log(chalk.red(`\n❌ Error: ${message}\n`)); } close() { // Farewell message const farewell = figlet.textSync('Goodbye!', { font: 'Small', horizontalLayout: 'fitted' }); console.log(chalk.blueBright(farewell)); console.log(chalk.gray('━'.repeat(50))); console.log(chalk.bold.green('Thank you for using Trace.AI!')); // Session summary const uptime = Math.floor((new Date() - this.sessionStartTime) / 1000); const minutes = Math.floor(uptime / 60); const seconds = uptime % 60; console.log(chalk.gray(`Session Duration: ${minutes}m ${seconds}s`)); console.log(chalk.gray(`Total Queries: ${this.queryCount}`)); console.log(chalk.gray(`Contexts Used: ${this.contexts.length}`)); console.log(chalk.gray('━'.repeat(50))); console.log(chalk.cyan('Visit us at: https://traceai.netlify.app')); console.log(chalk.gray('Have a great day! 👋\n')); this.rl.close(); process.exit(0); } } module.exports = TraceAI;