trace.ai-cli
Version:
A powerful AI-powered CLI tool
486 lines (415 loc) • 18.9 kB
JavaScript
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;