UNPKG

c9ai

Version:

C9 AI - Autonomous AI-Powered Productivity CLI with Semi-Learning System

1,194 lines (1,049 loc) 158 kB
const { spawn } = require('child_process'); const fs = require('fs-extra'); const path = require('path'); const chalk = require('chalk'); const ora = require('ora'); const os = require('os'); const inquirer = require('inquirer'); const https = require('https'); // Local LLM support let LlamaModel, LlamaContext, LlamaChatSession; try { const llamaCpp = require('node-llama-cpp'); LlamaModel = llamaCpp.LlamaModel; LlamaContext = llamaCpp.LlamaContext; LlamaChatSession = llamaCpp.LlamaChatSession; } catch (error) { // node-llama-cpp not available, will use fallback } class C9AI { constructor() { this.currentModel = 'claude'; this.configDir = path.join(os.homedir(), '.c9ai'); this.scriptsDir = path.join(this.configDir, 'scripts'); // This will now be the general tools directory this.modelsDir = path.join(this.configDir, 'models'); // Directory for local AI models // Timeout and retry configuration this.localModelTimeout = 30000; // 30 seconds this.maxRetries = 3; this.toolsRegistry = {}; // This will be for internal tools, not external scripts this.agenticTools = {}; // Registry for agentic tool use this.appMappings = {}; // Application name mappings this.learningData = {}; // Learning system data this.knowledgeBase = { topics: {}, fallbacks: {} }; // Knowledge base for content generation this.running = false; this.maxIterations = 20; this.localModel = null; // Will store the loaded local model instance this.initialized = false; this.init(); } async init() { if (this.initialized) return; // Ensure config and tools directories exist await fs.ensureDir(this.configDir); await fs.ensureDir(this.scriptsDir); // scriptsDir is now the tools directory await fs.ensureDir(this.modelsDir); // Ensure models directory exists await fs.ensureDir(path.join(this.configDir, 'logs')); // Copy scripts to the tools directory await this.copyScripts(); // Load configuration await this.loadConfig(); // Load agentic tools registry await this.loadAgenticTools(); // Load application mappings and learning data await this.loadAppMappings(); // Load knowledge base await this.loadKnowledgeBase(); // No longer loading tools from a registry, they are discovered dynamically this.initialized = true; } async copyScripts() { try { const sourceScriptsDir = path.join(__dirname, '../../mac_linux'); const scriptsToCopy = ['check-todos.sh', 'cleanup-weekly.sh', 'run-analytics.sh']; // Add all relevant scripts for (const scriptName of scriptsToCopy) { const sourcePath = path.join(sourceScriptsDir, scriptName); const destPath = path.join(this.scriptsDir, scriptName); if (await fs.exists(sourcePath)) { await fs.copy(sourcePath, destPath, { overwrite: true }); // Make the script executable await fs.chmod(destPath, '755'); } } } catch (error) { console.log(chalk.yellow('⚠️ Could not copy internal scripts. Some features might not work.')); } } async loadConfig() { const configPath = path.join(this.configDir, 'config.json'); try { if (await fs.exists(configPath)) { const config = await fs.readJson(configPath); this.currentModel = config.defaultModel || 'claude'; } } catch (error) { console.log(chalk.yellow('⚠️ Using default configuration')); } } async saveConfig() { const configPath = path.join(this.configDir, 'config.json'); await fs.writeJson(configPath, { defaultModel: this.currentModel, lastUpdated: new Date().toISOString() }, { spaces: 2 }); } // Removed loadTools as tools are now dynamically discovered async loadAgenticTools() { try { const toolsPath = path.join(__dirname, 'tools-registry.json'); if (await fs.exists(toolsPath)) { const toolsData = await fs.readJson(toolsPath); this.agenticTools = toolsData.tools; this.toolSelectionPrompt = toolsData.tool_selection_prompt; console.log(chalk.green(`✅ Loaded ${Object.keys(this.agenticTools).length} agentic tools`)); } } catch (error) { console.log(chalk.yellow('⚠️ Could not load agentic tools registry')); } } async loadAppMappings() { try { const mappingsPath = path.join(__dirname, 'app-mappings.json'); if (await fs.exists(mappingsPath)) { const mappingsData = await fs.readJson(mappingsPath); this.appMappings = mappingsData.applications; this.learningData = mappingsData.learning; console.log(chalk.green(`✅ Loaded ${Object.keys(this.appMappings).length} app mappings`)); } } catch (error) { console.log(chalk.yellow('⚠️ Could not load app mappings')); } } async loadKnowledgeBase() { try { const knowledgePath = path.join(__dirname, 'knowledge-base.json'); if (await fs.exists(knowledgePath)) { this.knowledgeBase = await fs.readJson(knowledgePath); console.log(chalk.green(`✅ Loaded ${Object.keys(this.knowledgeBase.topics).length} knowledge topics`)); } else { console.log(chalk.yellow('⚠️ No knowledge base file found, using built-in knowledge')); } } catch (error) { console.log(chalk.yellow('⚠️ Could not load knowledge base, using built-in knowledge')); } } async saveAppMappings() { try { const mappingsPath = path.join(__dirname, 'app-mappings.json'); const mappingsData = { applications: this.appMappings, learning: this.learningData }; await fs.writeJson(mappingsPath, mappingsData, { spaces: 2 }); } catch (error) { console.log(chalk.yellow('⚠️ Could not save app mappings')); } } async handleCommand(input) { const [command, ...args] = input.split(' '); try { // Handle shell commands with '!' sigil if (input.startsWith('!')) { const shellCommand = input.substring(1).trim(); if (shellCommand) { // Special handling for 'cd' if (shellCommand.startsWith('cd')) { let targetDir = shellCommand.substring(2).trim(); if (!targetDir || targetDir === '~') { targetDir = os.homedir(); } try { process.chdir(targetDir); console.log(chalk.green(`Changed directory to: ${process.cwd()}`)); } catch (error) { console.error(chalk.red(`Error changing directory: ${error.message}`)); } } else { await this.runShellCommand(shellCommand); } } return; // Command handled } // Handle sigil-based modes if (input.startsWith('@')) { const parts = input.substring(1).split(' '); const mode = parts[0]; const content = parts.slice(1).join(' '); switch (mode) { case 'claude': case 'gemini': case 'local': if (content) { // Direct prompt to AI model await this.runAI(mode, content); } else { // Interactive session await this.startInteractiveSession(mode); } return; case 'conv': case 'chat': // Explicit conversation mode await this.handleConversation(content || 'Hello!'); return; case 'cmd': case 'command': // Explicit command mode await this.processNaturalLanguageCommand(content); return; case 'tool': // Tool execution await this.executeToolDirective(content); return; } } switch (command.toLowerCase()) { case 'claude': await this.runAI('claude', args.join(' ')); break; case 'gemini': await this.runAI('gemini', args.join(' ')); break; case 'switch': await this.switchModel(args[0]); break; case 'todos': await this.handleTodos(args[0], args.slice(1)); break; case 'add': await this.handleTodos('add', args); break; case 'analytics': await this.showAnalytics(); break; case 'tools': await this.handleTools(args[0], args.slice(1)); break; case 'models': await this.handleModels(args[0], args[1]); break; case 'scan': await this.handleKnowledgeScan(args); break; case 'issues': await this.handleIssues(args[0], args.slice(1)); break; case 'achieve': case 'goal': await this.achieveGoal(args.join(' ')); break; case 'config': await this.showConfig(); break; case 'help': this.showHelp(); break; case 'logo': case 'banner': this.showBanner(); break; default: // Smart detection: conversation vs command if (command && input.trim().length > 0) { if (this.isConversationalInput(input.trim())) { await this.handleConversation(input.trim()); } else { await this.processNaturalLanguageCommand(input.trim()); } } } } catch (error) { console.error(chalk.red('❌ Error executing command:'), error.message); } } async runAI(model, prompt, options = {}) { if (!prompt.trim()) { console.log(chalk.yellow('⚠️ Please provide a prompt')); return; } const spinner = ora(`🤖 ${model.charAt(0).toUpperCase() + model.slice(1)} is thinking...`).start(); try { // Log the interaction await this.logInteraction(model, prompt); if (options.autonomous) { spinner.stop(); await this.runAutonomous(model, prompt); } else { spinner.stop(); // Stop spinner before launching interactive AI console.log(chalk.cyan(` 💡 An interactive ${model.toUpperCase()} session has started to help analyze the error.`)); console.log(chalk.yellow(` Please interact with ${model.toUpperCase()} directly. Type 'exit' or 'quit' to return to c9ai.`)); await this.startInteractiveSession(model, prompt); } } catch (error) { spinner.stop(); console.error(chalk.red(`❌ Error running ${model}:`), error.message); console.log(chalk.yellow('💡 Make sure the CLI is installed and configured:')); console.log(chalk.white(` ${model === 'claude' ? 'claude' : 'gemini-cli'} --version`)); } } async runAutonomous(model, goal) { console.log(chalk.cyan(` 🚀 Starting autonomous execution with ${model.toUpperCase()}`)); console.log(chalk.white(`📋 Goal: ${goal}`)); console.log(chalk.gray('='.repeat(60))); this.running = true; let iteration = 0; while (this.running && iteration < this.maxIterations) { iteration++; console.log(chalk.cyan(` 🔄 Step ${iteration}:`)); // For now, we'll simulate autonomous execution // In a real implementation, this would: // 1. Ask AI to plan next step // 2. Execute tools based on AI response // 3. Evaluate results and continue try { await this.simulateAutonomousStep(model, goal, iteration); // Check if goal is achieved (simplified logic for now) if (iteration >= 3) { console.log(chalk.green(` ✅ GOAL ACHIEVED: Task completed successfully`)); break; } // Brief pause between steps await this.sleep(1000); } catch (error) { console.log(chalk.red(`❌ Step ${iteration} failed: ${error.message}`)); console.log(chalk.yellow('🔄 Attempting to recover...')); } } this.running = false; console.log(chalk.cyan(` 🏁 Autonomous execution completed`)); } async simulateAutonomousStep(model, goal, step) { const actions = [ '📖 Analyzing current state...', '🔍 Identifying required actions...', '⚙️ Executing tools and commands...', '✅ Validating results...' ]; const action = actions[Math.min(step - 1, actions.length - 1)]; const spinner = ora(action).start(); await this.sleep(1500); spinner.succeed(action.replace('...', ' ✅')); // Simulate tool execution if (step === 2) { console.log(chalk.gray(' 🔧 Running: git status')); console.log(chalk.gray(' 📊 Analyzing: GitHub issues')); } } async switchModel(model) { const validModels = ['claude', 'gemini', 'local']; if (!validModels.includes(model)) { console.log(chalk.red(`❌ Invalid model. Choose from: ${validModels.join(', ')}`)); return; } this.currentModel = model; await this.saveConfig(); console.log(chalk.green(`🔄 Switched to ${model.toUpperCase()}`)); // Test the AI availability const testSpinner = ora(`Testing ${model} availability...`).start(); try { if (model === 'local') { if (await this.hasLocalModel()) { await this.initLocalModel(); testSpinner.succeed('LOCAL model is ready'); } else { testSpinner.fail('No local models installed'); console.log(chalk.yellow('💡 Install a model: models install phi-3')); } } else { const command = model === 'claude' ? 'claude' : 'gemini-cli'; await this.runCommand(`${command} --version`); testSpinner.succeed(`${model.toUpperCase()} is ready`); } } catch (error) { testSpinner.fail(`${model.toUpperCase()} not available`); if (model === 'local') { console.log(chalk.yellow('💡 Install a model: models install phi-3')); } else { console.log(chalk.yellow(`💡 Install ${model} CLI to use this model`)); } } } async handleTodos(action = 'list', task) { console.log(chalk.cyan('📋 Todo Management')); switch (action) { case 'list': await this.listTodos(); break; case 'execute': await this.executeTodos(); break; case 'add': if (!task || task.length === 0) { console.log(chalk.yellow('💡 Please provide a task description. Usage: todos add <your task here>')); } else { await this.addTodo(task.join(' ')); } break; case 'actions': await this.listActions(); break; case 'sync': await this.syncTodos(); break; default: // If the action doesn't match, assume it's part of a task description for 'add' const fullTask = [action, ...task].join(' '); await this.addTodo(fullTask); } } async listTodos() { console.log(chalk.cyan('--- GitHub Issues ---')); try { const scriptPath = path.join(this.scriptsDir, 'check-todos.sh'); if (await fs.exists(scriptPath)) { const githubIssues = await this.runCommand(`bash "${scriptPath}"`, true); console.log(githubIssues || chalk.gray('No open issues on GitHub.')); } else { const githubIssues = await this.runCommand('gh issue list --repo hebbarp/todo-management --state open', true); console.log(githubIssues || chalk.gray('No open issues on GitHub.')); } } catch (error) { console.log(chalk.red('❌ Error fetching GitHub issues:'), error.message); console.log(chalk.yellow('💡 Make sure GitHub CLI is installed and authenticated.')); } console.log(chalk.cyan('--- Local Tasks (todo.md) ---')); const localTodos = await this.parseLocalTodos(); if (localTodos.length > 0) { localTodos.forEach(todo => console.log(todo)); } else { console.log(chalk.gray('No tasks found in todo.md.')); } } async parseLocalTodos() { const todoFilePath = path.join(process.cwd(), 'todo.md'); if (!await fs.exists(todoFilePath)) { return []; } const content = await fs.readFile(todoFilePath, 'utf-8'); return content.split('\n').filter(line => line.startsWith('- [ ]')); } async listActions() { const actionableTodos = await this.parseActionableTodos(); if (actionableTodos.length === 0) { console.log(chalk.yellow('No actionable todos found in todo.md.')); return; } console.log(chalk.cyan('\nActionable Todos:')); for (const todo of actionableTodos) { console.log(`- ${todo.task}`); console.log(` └─ ${chalk.gray(`@${todo.verb} ${todo.target}`)}`); } } async addTodo(task) { await this.init(); // Ensure initialization is complete const todoFilePath = path.join(process.cwd(), 'todo.md'); // Check if it's already structured with @action if (task.includes('@action:')) { const taskLine = `\n- [ ] ${task}`; try { await fs.appendFile(todoFilePath, taskLine); console.log(chalk.green(`✅ Added structured task: "${task}"`)); } catch (error) { console.error(chalk.red(`❌ Error adding task:`), error.message); } return; } // Check if it has manual @action format const actionIndex = task.indexOf('@'); if (actionIndex !== -1) { const description = task.substring(0, actionIndex).trim(); const rawActionString = task.substring(actionIndex + 1).trim(); const taskLine = `\n- [ ] ${description} @action: ${rawActionString}`; try { await fs.appendFile(todoFilePath, taskLine); console.log(chalk.green(`✅ Added task: "${description}"`)); console.log(chalk.gray(` └─ With intent: @${rawActionString}`)); } catch (error) { console.error(chalk.red(`❌ Error adding task:`), error.message); } return; } // Try intelligent processing for natural language todos await this.addIntelligentTodo(task, todoFilePath); } async addIntelligentTodo(task, todoFilePath) { console.log(chalk.cyan(`🤖 Analyzing: "${task}"`)); // Try local AI first (if available) if (this.currentModel === 'local' && await this.hasLocalModel()) { try { const spinner = ora('Processing with local AI...').start(); const parsed = await this.parseNaturalLanguageTodo(task); spinner.succeed('Local AI processed successfully'); const taskLine = `\n- [ ] ${task} @action: ${parsed.verb} ${parsed.target}`; await fs.appendFile(todoFilePath, taskLine); console.log(chalk.green(`✅ Added intelligent task: "${task}"`)); console.log(chalk.cyan(` 🧠 AI suggested: @action: ${parsed.verb} ${parsed.target}`)); return; } catch (error) { console.log(chalk.yellow('🔄 Local AI failed, trying cloud...')); } } // Try cloud AI fallback if (this.currentModel === 'claude' || this.currentModel === 'gemini') { try { console.log(chalk.cyan(`🌐 Processing with ${this.currentModel.toUpperCase()}...`)); // For now, we'll add a placeholder for cloud processing // In the full implementation, this would call the cloud API const taskLine = `\n- [ ] ${task} @action: search ${task.toLowerCase().replace(/\s+/g, '_')}`; await fs.appendFile(todoFilePath, taskLine); console.log(chalk.green(`✅ Added task: "${task}"`)); console.log(chalk.gray(` 🌐 Processed with ${this.currentModel.toUpperCase()}`)); return; } catch (error) { console.log(chalk.yellow('🔄 Cloud AI failed, adding as manual task...')); } } // Final fallback - add as manual todo const taskLine = `\n- [ ] ${task}`; try { await fs.appendFile(todoFilePath, taskLine); console.log(chalk.green(`✅ Added task: "${task}"`)); console.log(chalk.yellow('💡 Add @action: for automatic execution')); } catch (error) { console.error(chalk.red(`❌ Error adding task:`), error.message); } } async executeTodos() { const actionableTodos = await this.parseActionableTodos(); if (actionableTodos.length === 0) { console.log(chalk.yellow('No actionable todos found in todo.md.')); return; } const { selectedTodos } = await inquirer.prompt([ { type: 'checkbox', name: 'selectedTodos', message: 'Select todos to execute', choices: actionableTodos.map(todo => ({ name: todo.task, value: todo.task })) // Simplify value to todo.task } ]); console.log(chalk.blue(`[DEBUG] Selected Todos: ${JSON.stringify(selectedTodos)}`)); for (const selected of selectedTodos) { // Re-parse verb and target from the selected task string const parsedTodo = actionableTodos.find(todo => todo.task === selected); if (!parsedTodo) { console.log(chalk.red(`❌ Error: Could not find parsed todo for selected task: ${selected}`)); continue; } const { verb, target } = parsedTodo; try { console.log(chalk.cyan(` ▶️ Executing intent: @${verb} ${target}`)); await this.runIntent(verb, target); console.log(chalk.green('✅ Execution successful')); } catch (error) { console.log(chalk.red(`❌ Error executing intent: @${verb} ${target}`), error.message); // AI Fallback Logic console.log(chalk.cyan(` 🤖 AI is analyzing the error...`)); const analysisPrompt = `My goal was to execute the intent "@${verb} ${target}". It failed with the following error: ${error.message}. Please analyze this error and provide a step-by-step solution.`; await this.runAI(this.currentModel, analysisPrompt); } } } async parseActionableTodos() { const todoFilePath = path.join(process.cwd(), 'todo.md'); if (!await fs.exists(todoFilePath)) { return []; } const content = await fs.readFile(todoFilePath, 'utf-8'); const lines = content.split('\n'); const actionableTodos = []; for (const line of lines) { const actionMatch = line.match(/@action:\s*(\w+)\s*(.*)/); if (actionMatch) { const task = line.split('@action:')[0].replace('- [ ]', '').trim(); const verb = actionMatch[1]; const target = actionMatch[2].trim(); actionableTodos.push({ task, verb, target }); } } return actionableTodos; } async runIntent(verb, target) { console.log(chalk.blue(`[DEBUG] runIntent: Verb - ${verb}, Target - ${target}`)); let commandToExecute = ''; const osType = os.platform(); switch (verb.toLowerCase()) { case 'open': if (osType === 'darwin') { // macOS commandToExecute = `open "${target}"`; } else if (osType === 'win32') { // Windows commandToExecute = `start "" "${target}"`; } else { // Linux and others commandToExecute = `xdg-open "${target}"`; } break; case 'compile': // Assuming .tex files for now, can be expanded if (target.endsWith('.tex')) { commandToExecute = `pdflatex "${target}"`; } else { throw new Error(`Unsupported compile target: ${target}`); } break; case 'run': // Assuming shell scripts for now, can be expanded for python, node etc. // Need to handle relative paths for scripts in ~/.c9ai/scripts const scriptPath = path.join(this.scriptsDir, target); if (await fs.exists(scriptPath)) { // Determine interpreter based on extension if (target.endsWith('.sh')) { // Use bash on Unix, or suggest .bat files on Windows if (osType === 'win32') { throw new Error('Shell scripts (.sh) not supported on Windows. Use .bat files instead.'); } commandToExecute = `bash "${scriptPath}"`; } else if (target.endsWith('.bat') && osType === 'win32') { commandToExecute = `"${scriptPath}"`; } else if (target.endsWith('.py')) { // Use 'python' on Windows, 'python3' on Unix systems const pythonCmd = osType === 'win32' ? 'python' : 'python3'; commandToExecute = `${pythonCmd} "${scriptPath}"`; } else if (target.endsWith('.js')) { commandToExecute = `node "${scriptPath}"`; } else { // Default to direct execution if no known extension commandToExecute = `"${scriptPath}"`; } } else { throw new Error(`Script not found: ${target}`); } break; case 'search': // Basic Google search const encodedTarget = encodeURIComponent(target); commandToExecute = `open "https://www.google.com/search?q=${encodedTarget}"`; if (osType === 'win32') { commandToExecute = `start "" "https://www.google.com/search?q=${encodedTarget}"`; } else if (osType === 'linux') { commandToExecute = `xdg-open "https://www.google.com/search?q=${encodedTarget}"`; } break; default: throw new Error(`Unknown intent verb: ${verb}`); } if (commandToExecute) { console.log(chalk.blue(`[DEBUG] runIntent: Executing command - ${commandToExecute}`)); await this.runCommand(commandToExecute); } else { throw new Error(`Could not determine command for verb: ${verb} and target: ${target}`); } } async syncTodos() { const spinner = ora('🔄 Syncing todos from all sources...').start(); try { // This would sync from GitHub, local files, etc. await this.sleep(2000); spinner.succeed('✅ Todos synced successfully'); } catch (error) { spinner.fail('❌ Sync failed'); console.log(chalk.red('Error:'), error.message); } } async showAnalytics() { console.log(chalk.cyan('📊 C9 AI Analytics Dashboard')); console.log(chalk.gray('='.repeat(40))); try { const logPath = path.join(this.configDir, 'logs'); const files = await fs.readdir(logPath); console.log(chalk.white(`📈 Total sessions: ${files.length}`)); console.log(chalk.white(`🤖 Current model: ${this.currentModel.toUpperCase()}`)); console.log(chalk.white(`📅 Last updated: ${new Date().toLocaleDateString()}`)); console.log(chalk.yellow('\n💡 Full analytics dashboard coming soon!')); } catch (error) { console.log(chalk.yellow('📊 No analytics data yet - start using c9ai to build insights!')); } } async handleTools(action = 'list', args = []) { switch (action) { case 'list': case 'ls': await this.listAgenticTools(); break; case 'add': await this.addAgenticTool(args); break; case 'edit': case 'modify': await this.editAgenticTool(args[0]); break; case 'remove': case 'delete': case 'rm': await this.removeAgenticTool(args[0]); break; case 'scripts': await this.listScriptTools(); break; case 'reload': await this.loadAgenticTools(); console.log(chalk.green('✅ Tools registry reloaded')); break; default: console.log(chalk.yellow('💡 Available tool commands:')); console.log(chalk.white(' tools list - List all agentic tools')); console.log(chalk.white(' tools add - Add a new tool (interactive)')); console.log(chalk.white(' tools edit <name> - Edit an existing tool')); console.log(chalk.white(' tools remove <name> - Remove a tool')); console.log(chalk.white(' tools scripts - List executable scripts')); console.log(chalk.white(' tools reload - Reload tools registry')); } } async listAgenticTools() { console.log(chalk.cyan('🤖 Agentic Tools Registry')); console.log(chalk.gray('='.repeat(50))); if (Object.keys(this.agenticTools).length === 0) { console.log(chalk.yellow('No agentic tools loaded. Check tools-registry.json')); return; } for (const [toolName, tool] of Object.entries(this.agenticTools)) { console.log(chalk.green(`\n📦 ${toolName}`)); console.log(chalk.white(` Description: ${tool.description}`)); console.log(chalk.gray(` Command: ${tool.command}`)); if (tool.windows_command && tool.windows_command !== tool.command) { console.log(chalk.gray(` Windows: ${tool.windows_command}`)); } if (tool.parameters && Object.keys(tool.parameters).length > 0) { console.log(chalk.cyan(' Parameters:')); for (const [paramName, paramInfo] of Object.entries(tool.parameters)) { const required = paramInfo.required ? '(required)' : '(optional)'; console.log(chalk.white(` ${paramName}: ${paramInfo.description} ${required}`)); } } } console.log(chalk.yellow(`\n💡 Total: ${Object.keys(this.agenticTools).length} tools available`)); console.log(chalk.cyan('📝 Use "tools edit <name>" to modify or "tools add" to create new tools')); } async listScriptTools() { console.log(chalk.cyan('📜 Executable Scripts:')); console.log(chalk.gray('='.repeat(40))); try { const files = await fs.readdir(this.scriptsDir); const executableFiles = []; for (const file of files) { const filePath = path.join(this.scriptsDir, file); const stats = await fs.stat(filePath); if (stats.isFile() && (stats.mode & fs.constants.S_IXUSR)) { executableFiles.push(file); } } if (executableFiles.length === 0) { console.log(chalk.yellow('No executable scripts found in ~/.c9ai/scripts/')); return; } for (const toolName of executableFiles) { console.log(chalk.white(`- ${toolName}`)); } console.log(chalk.yellow('\n💡 Use @run <script_name> in your todos to execute these scripts.')); } catch (error) { console.error(chalk.red('❌ Error listing scripts:'), error.message); } } async addAgenticTool(args) { console.log(chalk.cyan('➕ Add New Agentic Tool')); console.log(chalk.gray('='.repeat(30))); try { const questions = [ { type: 'input', name: 'name', message: 'Tool name (e.g., "my_custom_tool"):', validate: (input) => { if (!input.trim()) return 'Tool name is required'; if (this.agenticTools[input]) return 'Tool already exists. Use "tools edit" to modify.'; if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(input)) return 'Invalid name. Use letters, numbers, underscores only.'; return true; } }, { type: 'input', name: 'description', message: 'Tool description:', validate: (input) => input.trim() ? true : 'Description is required' }, { type: 'input', name: 'command', message: 'Command to execute (Unix/Mac):', validate: (input) => input.trim() ? true : 'Command is required' }, { type: 'input', name: 'windows_command', message: 'Windows command (leave empty to use same as Unix):' }, { type: 'confirm', name: 'hasParameters', message: 'Does this tool need parameters?', default: false } ]; const answers = await inquirer.prompt(questions); const newTool = { name: answers.name, description: answers.description, command: answers.command, parameters: {} }; if (answers.windows_command) { newTool.windows_command = answers.windows_command; } if (answers.hasParameters) { console.log(chalk.yellow('\nAdding parameters (press Enter with empty name to finish):')); while (true) { const paramQuestions = [ { type: 'input', name: 'paramName', message: 'Parameter name:' } ]; const paramAnswer = await inquirer.prompt(paramQuestions); if (!paramAnswer.paramName.trim()) break; const paramDetailsQuestions = [ { type: 'input', name: 'description', message: `Description for ${paramAnswer.paramName}:`, validate: (input) => input.trim() ? true : 'Parameter description is required' }, { type: 'list', name: 'type', message: 'Parameter type:', choices: ['string', 'number', 'boolean'], default: 'string' }, { type: 'confirm', name: 'required', message: 'Is this parameter required?', default: false } ]; const paramDetails = await inquirer.prompt(paramDetailsQuestions); newTool.parameters[paramAnswer.paramName] = { type: paramDetails.type, description: paramDetails.description, required: paramDetails.required }; } } // Add to registry this.agenticTools[answers.name] = newTool; // Save to file await this.saveAgenticTools(); console.log(chalk.green(`✅ Tool '${answers.name}' added successfully!`)); console.log(chalk.cyan('💡 Test it by saying something like:')); console.log(chalk.white(` "${answers.description.toLowerCase()}"`)); } catch (error) { if (error.message !== 'User interrupted') { console.error(chalk.red('❌ Error adding tool:'), error.message); } } } async editAgenticTool(toolName) { if (!toolName) { console.log(chalk.yellow('💡 Usage: tools edit <tool_name>')); return; } if (!this.agenticTools[toolName]) { console.log(chalk.red(`❌ Tool '${toolName}' not found`)); return; } console.log(chalk.cyan(`✏️ Editing Tool: ${toolName}`)); console.log(chalk.gray('='.repeat(30))); const toolsPath = path.join(__dirname, 'tools-registry.json'); console.log(chalk.cyan(`📝 Opening tools registry for editing: ${toolsPath}`)); const editor = process.env.EDITOR || 'nano'; try { await this.runCommand(`${editor} "${toolsPath}"`); await this.loadAgenticTools(); console.log(chalk.green('✅ Tools registry reloaded')); } catch (error) { console.error(chalk.red('❌ Error editing tools:'), error.message); } } async removeAgenticTool(toolName) { if (!toolName) { console.log(chalk.yellow('💡 Usage: tools remove <tool_name>')); return; } if (!this.agenticTools[toolName]) { console.log(chalk.red(`❌ Tool '${toolName}' not found`)); return; } const { confirm } = await inquirer.prompt([ { type: 'confirm', name: 'confirm', message: `Remove tool '${toolName}'?`, default: false } ]); if (confirm) { delete this.agenticTools[toolName]; await this.saveAgenticTools(); console.log(chalk.green(`✅ Tool '${toolName}' removed successfully`)); } else { console.log(chalk.gray('Cancelled')); } } async saveAgenticTools() { try { const toolsPath = path.join(__dirname, 'tools-registry.json'); const toolsData = { tools: this.agenticTools, tool_selection_prompt: this.toolSelectionPrompt }; await fs.writeJson(toolsPath, toolsData, { spaces: 2 }); } catch (error) { throw new Error(`Failed to save tools registry: ${error.message}`); } } async showConfig() { console.log(chalk.cyan('⚙️ C9 AI Configuration')); console.log(chalk.gray('='.repeat(30))); console.log(chalk.white(`📍 Config directory: ${this.configDir}`)); console.log(chalk.white(`🤖 Default AI model: ${this.currentModel.toUpperCase()}`)); console.log(chalk.white(`🔧 Max iterations: ${this.maxIterations}`)); } showHelp() { console.log(chalk.cyan('📖 C9 AI Help')); console.log(chalk.gray('='.repeat(20))); console.log(chalk.yellow('\n🤖 AI Modes & Conversation:')); console.log(chalk.white(' @claude [prompt] - Claude session or direct prompt')); console.log(chalk.white(' @gemini [prompt] - Gemini session or direct prompt')); console.log(chalk.white(' @local [prompt] - Local AI session or direct prompt')); console.log(chalk.white(' @conv <message> - Explicit conversation mode')); console.log(chalk.white(' @cmd <command> - Explicit command mode')); console.log(chalk.white(' Natural questions - Auto-detected as conversation')); console.log(chalk.yellow('\n⚡ Quick Prompts:')); console.log(chalk.white(' (Removed - use interactive sessions for AI prompts)')); console.log(chalk.yellow('\n📋 Productivity & Issues:')); console.log(chalk.white(' todos [action] - Manage todos (list, add, sync)')); console.log(chalk.white(' issues list - List GitHub issues')); console.log(chalk.white(' issues execute [#] - Execute specific issue')); console.log(chalk.white(' issues auto - Auto-execute matching issues')); console.log(chalk.white(' achieve "<goal>" - Autonomous goal achievement')); console.log(chalk.white(' analytics - View productivity insights')); console.log(chalk.yellow('\\n🔧 System & Tools:')); console.log(chalk.white(' ! <command> - Execute any shell command (e.g., !ls -l)')); console.log(chalk.white(' switch <model> - Switch default AI model (claude|gemini|local)')); console.log(chalk.white(' tools list - List all agentic tools')); console.log(chalk.white(' tools add - Add new tool (interactive)')); console.log(chalk.white(' tools edit <name> - Edit existing tool')); console.log(chalk.white(' tools remove <name> - Remove tool')); console.log(chalk.white(' tools scripts - List executable scripts')); console.log(chalk.white(' scan <dirs...> - Scan directories to build knowledge base')); console.log(chalk.white(' scan --help - Show scanning options')); console.log(chalk.white(' config - Show configuration')); console.log(chalk.white(' help - Show this help')); } async handleKnowledgeScan(args) { const KnowledgeScanner = require('./knowledge-scanner.js'); if (args.includes('--help') || args.includes('-h')) { console.log(chalk.cyan('🔍 Knowledge Base Scanner')); console.log(chalk.gray('='.repeat(30))); console.log(chalk.yellow('\nUsage:')); console.log(chalk.white(' scan [directories...] - Scan specified directories')); console.log(chalk.white(' scan - Scan current directory')); console.log(chalk.white(' scan ~/Documents ~/Code - Scan multiple directories')); console.log(chalk.yellow('\nOptions:')); console.log(chalk.white(' --help, -h - Show this help')); console.log(chalk.yellow('\nExamples:')); console.log(chalk.gray(' scan ~/Documents ~/Projects')); console.log(chalk.gray(' scan .')); console.log(chalk.gray(' scan /Users/me/code')); return; } // Default to current directory if no args provided const directories = args.length > 0 ? args : [process.cwd()]; // Expand home directory const expandedDirs = directories.map(dir => { if (dir.startsWith('~')) { return path.join(require('os').homedir(), dir.substring(1)); } return path.resolve(dir); }); console.log(chalk.cyan(`🔍 Scanning ${expandedDirs.length} directories for knowledge...`)); console.log(chalk.gray('This may take a few minutes depending on directory size.')); try { const scanner = new KnowledgeScanner(); const knowledgeBase = await scanner.scanDirectories(expandedDirs, { includeCode: true, includeDocs: true, includeReadmes: true, maxDepth: 3, ignorePatterns: ['node_modules', '.git', 'dist', 'build', '.DS_Store', 'coverage'] }); // Save the knowledge base const knowledgePath = path.join(__dirname, 'knowledge-base.json'); await scanner.saveKnowledgeBase(knowledgePath); // Reload the knowledge base in current instance await this.loadKnowledgeBase(); console.log(chalk.green('\\n🎉 Knowledge base successfully created!')); console.log(chalk.white(`📊 Summary:`)); console.log(chalk.white(` 🧠 Topics: ${Object.keys(knowledgeBase.topics).length}`)); console.log(chalk.white(` 📝 Sources: Various files from scanned directories`)); console.log(chalk.cyan('\\n💡 Try creating content with your discovered topics:')); // Show some discovered topics const topics = Object.keys(knowledgeBase.topics).slice(0, 3); for (const topic of topics) { console.log(chalk.gray(` c9ai> write a post about ${topic}`)); } } catch (error) { console.error(chalk.red('❌ Knowledge scanning failed:'), error.message); } } showBanner() { const banner = ` ${chalk.cyan('🌟 ============================================ 🌟')} ${chalk.cyan(' ____ ___ _ ___ ')} ${chalk.cyan(' / ___|/ _ \ / \ |_ _| ')} ${chalk.cyan(' | | | (_) |/ _ \ | | ')} ${chalk.cyan(' | |___|\__, / ___ \ | | ')} ${chalk.cyan(' \____| /_/_/ \_\___| ')} ${chalk.cyan(' ')} ${chalk.yellow(' Autonomous AI-Powered Productivity System ')} ${chalk.green(' 🤖 Claude CLI ✨ Gemini CLI 🚀 Tool Use ')} ${chalk.cyan('🌟 ============================================ 🌟')} `; console.log(banner); } async runShellCommand(command) { return new Promise((resolve) => { const child = spawn(command, { stdio: 'inherit', shell: true }); child.on('close', (code) => { if (code !== 0) { console.log(chalk.yellow(`\n[c9ai: Command exited with code ${code}]`)); } resolve(); }); child.on('error', (err) => { console.error(chalk.red(`\n[c9ai: Failed to start command: ${err.message}]`)); resolve(); }); }); } async startInteractiveSession(model, initialPrompt = '') { console.log(chalk.cyan(`\nEntering interactive session with ${model.toUpperCase()}. Type