c9ai
Version:
C9 AI - Autonomous AI-Powered Productivity CLI with Semi-Learning System
1,194 lines (1,049 loc) • 158 kB
JavaScript
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