#!/usr/bin/env node /** * @foxruv/iris - Iris CLI * * Unified CLI for MCP orchestration, swarm intelligence, and agent learning * Consolidates: MCP management, evaluation, council, federated learning */ // CRITICAL: Set MCP mode and suppress stdout BEFORE any imports if running as MCP server // MCP protocol requires clean stdout - any pollution breaks the JSON-RPC protocol const isMcpServer = process.argv.includes('mcp:server') || process.argv.includes('serve'); if (isMcpServer) { process.env.IRIS_MCP_MODE = 'true'; // Suppress ALL stdout during module loading - MCP needs clean stdout const originalLog = console.log; console.log = (...args: unknown[]) => { // Only allow through if it's JSON (MCP protocol) if (args.length === 1 && typeof args[0] === 'string' && args[0].startsWith('{')) { originalLog(...args); } // Otherwise suppress }; } import { Command } from 'commander'; import { runInit } from './commands/init.js'; import { runMcpImport } from './commands/mcp-import.js'; import { runMcpSync } from './commands/mcp-sync.js'; import { runEnhancedInit } from './commands/init-enhanced.js'; import { runMcpInstall, runMcpList } from './commands/mcp-install.js'; import { runConfigShow, runConfigWizard, runConfigToggle, runConfigReset, runConfigTopology } from './commands/execution-config.js'; import { loginCommand, logoutCommand, statusCommand } from './commands/auth-login.js'; import { runTelemetryMigrate, runTelemetrySync, runTelemetryStatus } from './commands/telemetry.js'; import { showSmartExecutionBanner } from './interceptor.js'; import chalk from 'chalk'; import * as fs from 'fs'; import * as path from 'path'; const program = new Command(); program .name('iris') .description('Iris - Self-improving MCP orchestration with agent vision and swarm intelligence') .version('1.0.0') .hook('preAction', async (thisCommand) => { // Skip banner for MCP server (needs clean stdout for protocol) const commandName = thisCommand.name(); if (commandName === 'mcp:server' || commandName === 'serve') { return; } // Show smart execution banner if enabled await showSmartExecutionBanner(); }) .hook('postAction', async (thisCommand) => { // Skip forced exit for MCP server (it needs to stay running) const commandName = thisCommand.name(); if (commandName === 'mcp:server' || commandName === 'serve') { return; } // Force exit after command completes to avoid lingering DB connections setTimeout(() => process.exit(0), 100); }); program .command('init') .description('Initialize FoxRuv agent infrastructure in current project') .option('--enhanced', 'Create .iris folder and context-aware CLAUDE.md files') .option('--force', 'Force overwrite existing files') .option('--no-claude-md', 'Skip CLAUDE.md generation') .option('--no-skills', 'Skip mcp-skills directory creation') .option('--no-contexts', 'Skip context detection and creation') .option('--no-claude', 'Skip Claude Code targeting (CLAUDE.md)') .option('--no-gemini', 'Skip Gemini targeting (GEMINI.md)') .option('--enable-agentdb', 'Enable AgentDB tracking (default: true)') .option('--enable-supabase', 'Enable Supabase integration (default: false)') .action(async (options) => { try { console.log(chalk.blue('\n🌈 Initializing Iris platform infrastructure...\n')); if (options.enhanced) { await runEnhancedInit(process.cwd(), { ...options, createClaudeContexts: options.claude, // Commander handles --no-claude -> options.claude = false createGeminiMd: options.gemini // Commander handles --no-gemini -> options.gemini = false }); } else { await runInit(process.cwd(), options); } console.log(chalk.green('\nāœ… Initialization complete!\n')); } catch (error) { console.error(chalk.red('\nāŒ Initialization failed:'), error); process.exit(1); } }); // MCP Management Commands const mcpCommand = program .command('mcp') .description('MCP skill management commands'); mcpCommand .command('list') .description('List available MCP servers from registry') .option('--category ', 'Filter by category') .option('--search ', 'Search MCPs by name or description') .action(async (options) => { try { await runMcpList(options); } catch (error) { console.error(chalk.red('\nāŒ List failed:'), error); process.exit(1); } }); mcpCommand .command('install ') .description('Install MCP server and generate skill files + wrappers') .option('--yes', 'Skip confirmation prompts') .option('--skip-wrappers', 'Skip TypeScript wrapper generation') .option('--skip-skills', 'Skip skill documentation generation') .action(async (mcpId, options) => { try { await runMcpInstall(mcpId, options); } catch (error) { console.error(chalk.red('\nāŒ Installation failed:'), error); process.exit(1); } }); mcpCommand .command('import') .description('Import MCPs from Claude global settings into project skills') .option('--backup', 'Backup Claude settings before modification', true) .option('--disable-global', 'Disable global MCPs after import', false) .option('--dry-run', 'Show what would be imported without making changes') .action(async (options) => { try { console.log(chalk.blue('\nšŸ”„ Importing MCPs from Claude settings...\n')); await runMcpImport(process.cwd(), options); console.log(chalk.green('\nāœ… MCP import complete!\n')); } catch (error) { console.error(chalk.red('\nāŒ Import failed:'), error); process.exit(1); } }); mcpCommand .command('sync-index') .alias('sync') .description('Synchronize mcp-skills/INDEX.md with actual skill files') .action(async () => { try { console.log(chalk.blue('\nšŸ”„ Synchronizing skill index...\n')); await runMcpSync(process.cwd()); console.log(chalk.green('\nāœ… Index synchronized!\n')); } catch (error) { console.error(chalk.red('\nāŒ Sync failed:'), error); process.exit(1); } }); // MCP Context management (Claude Code integration) const contextCommand = mcpCommand .command('context') .description('Manage MCP context usage in Claude Code'); contextCommand .command('list') .alias('ls') .description('List MCPs and their enabled/disabled status') .action(async () => { try { const { runMcpContextList } = await import('./commands/mcp-context.js'); await runMcpContextList(); } catch (error) { console.error(chalk.red('\nāŒ Failed:'), error); process.exit(1); } }); contextCommand .command('disable ') .description('Disable MCPs to reduce context token usage') .action(async (mcpIds: string[]) => { try { const { runMcpContextDisable } = await import('./commands/mcp-context.js'); await runMcpContextDisable(mcpIds); } catch (error) { console.error(chalk.red('\nāŒ Failed:'), error); process.exit(1); } }); contextCommand .command('enable ') .description('Re-enable disabled MCPs') .action(async (mcpIds: string[]) => { try { const { runMcpContextEnable } = await import('./commands/mcp-context.js'); await runMcpContextEnable(mcpIds); } catch (error) { console.error(chalk.red('\nāŒ Failed:'), error); process.exit(1); } }); contextCommand .command('optimize') .description('Interactively optimize MCP context usage') .option('--keep ', 'MCPs to keep enabled') .option('--non-interactive', 'Disable all except --keep without prompts') .action(async (options) => { try { const { runMcpContextOptimize } = await import('./commands/mcp-context.js'); await runMcpContextOptimize({ keepEnabled: options.keep, interactive: !options.nonInteractive }); } catch (error) { console.error(chalk.red('\nāŒ Failed:'), error); process.exit(1); } }); contextCommand .command('summary') .description('Show MCP context usage across all projects') .action(async () => { try { const { runMcpContextSummary } = await import('./commands/mcp-context.js'); await runMcpContextSummary(); } catch (error) { console.error(chalk.red('\nāŒ Failed:'), error); process.exit(1); } }); // MCP Scan - Extract tool schemas from MCPs mcpCommand .command('scan') .description('Scan MCPs and generate skill documentation from their tools') .option('--mcps ', 'Specific MCPs to scan') .option('--output ', 'Output directory for skill files') .option('--skip-disabled', 'Skip scanning disabled MCPs') .action(async (options) => { try { const { runMcpScan } = await import('./commands/mcp-scan.js'); await runMcpScan({ mcpIds: options.mcps, output: options.output, skipDisabled: options.skipDisabled }); } catch (error) { console.error(chalk.red('\nāŒ Scan failed:'), error); process.exit(1); } }); // ============================================================================ // CONFIG Commands - Smart Defaults // ============================================================================ const configCommand = program .command('config') .description('Manage execution configuration (agentic-flow + AgentDB smart defaults)'); configCommand .command('show') .description('Show current execution configuration') .action(async () => { try { await runConfigShow(); } catch (error) { console.error(chalk.red('\nāŒ Failed to show config:'), error); process.exit(1); } }); configCommand .command('wizard') .alias('setup') .description('Interactive configuration wizard') .action(async () => { try { await runConfigWizard(); } catch (error) { console.error(chalk.red('\nāŒ Configuration wizard failed:'), error); process.exit(1); } }); configCommand .command('toggle ') .description('Toggle setting on/off (agentic-flow|agentdb|learning|caching)') .option('--enable', 'Enable the setting') .option('--disable', 'Disable the setting') .action(async (setting, options) => { try { const enable = options.enable ? true : options.disable ? false : undefined; await runConfigToggle(setting as any, enable); } catch (error) { console.error(chalk.red('\nāŒ Toggle failed:'), error); process.exit(1); } }); configCommand .command('topology ') .description('Set swarm topology (mesh|hierarchical|ring|star)') .action(async (type) => { try { await runConfigTopology(type as any); } catch (error) { console.error(chalk.red('\nāŒ Failed to set topology:'), error); process.exit(1); } }); configCommand .command('reset') .description('Reset all settings to defaults') .action(async () => { try { await runConfigReset(); } catch (error) { console.error(chalk.red('\nāŒ Reset failed:'), error); process.exit(1); } }); // ============================================================================ // PROMPT BOOST Commands - Auto-inject agentic-flow + AgentDB // ============================================================================ const promptBoostCommand = program .command('prompt-boost') .alias('boost') .description('Manage prompt injection (auto-add "agentic-flow AND AgentDB")'); promptBoostCommand .command('status') .description('Show prompt boost status') .action(async () => { try { const { runPromptBoostStatus } = await import('./commands/prompt-boost.js'); await runPromptBoostStatus(process.cwd()); } catch (error) { console.error(chalk.red('\nāŒ Failed:'), error); process.exit(1); } }); promptBoostCommand .command('on') .description('Enable prompt boost (auto-inject agentic-flow AND AgentDB)') .action(async () => { try { const { runPromptBoostOn } = await import('./commands/prompt-boost.js'); await runPromptBoostOn(process.cwd()); } catch (error) { console.error(chalk.red('\nāŒ Failed:'), error); process.exit(1); } }); promptBoostCommand .command('off') .description('Disable prompt boost') .action(async () => { try { const { runPromptBoostOff } = await import('./commands/prompt-boost.js'); await runPromptBoostOff(process.cwd()); } catch (error) { console.error(chalk.red('\nāŒ Failed:'), error); process.exit(1); } }); promptBoostCommand .command('shadow ') .description('Enable/disable shadow mode (experimental prompt improvement)') .action(async (state) => { try { const { runPromptBoostShadow } = await import('./commands/prompt-boost.js'); await runPromptBoostShadow(process.cwd(), state === 'on'); } catch (error) { console.error(chalk.red('\nāŒ Failed:'), error); process.exit(1); } }); promptBoostCommand .command('model ') .description('Configure local model for shadow mode') .option('--endpoint ', 'Model endpoint (e.g., http://192.168.1.100:1234)') .option('--type ', 'Model type: lmstudio, ollama, vllm', 'lmstudio') .action(async (name, options) => { try { const { runPromptBoostSetModel } = await import('./commands/prompt-boost.js'); await runPromptBoostSetModel(process.cwd(), name, options); } catch (error) { console.error(chalk.red('\nāŒ Failed:'), error); process.exit(1); } }); promptBoostCommand .command('review') .description('Review shadow mode experiments (compare original vs improved)') .action(async () => { try { const { runPromptBoostReview } = await import('./commands/prompt-boost.js'); await runPromptBoostReview(process.cwd()); } catch (error) { console.error(chalk.red('\nāŒ Failed:'), error); process.exit(1); } }); // ============================================================================ // AUTO-OPTIMIZE Commands - Self-improving AI // ============================================================================ program .command('auto-check') .description('Check if any AI functions need optimization') .option('--target ', 'Check specific target only') .option('--quiet', 'Only output if trigger found') .option('--execute', 'Auto-execute optimization if triggered') .action(async (options) => { try { const { runAutoTriggerCheck } = await import('../auto-optimize/auto-trigger.js'); await runAutoTriggerCheck(process.cwd(), { autoExecute: options.execute, verbose: !options.quiet }); } catch (error) { if (!options.quiet) { console.error(chalk.red('\nāŒ Auto-check failed:'), error); } process.exit(1); } }); program .command('track ') .description('Track AI function performance (record, status, clear)') .option('--target ', 'Target function/file') .option('--success ', 'Record success/failure') .option('--tool ', 'Tool that was used') .action(async (action, options) => { try { if (action === 'record') { const { recordTelemetry } = await import('../auto-optimize/auto-trigger.js'); recordTelemetry(process.cwd(), { target: options.target || 'unknown', success: options.success !== 'false', latencyMs: 0 }); console.log(chalk.green('āœ“ Performance recorded')); } else if (action === 'status') { const { getAllTargets, getTelemetryForTarget, calculateMetrics } = await import('../auto-optimize/auto-trigger.js'); const targets = getAllTargets(process.cwd()); if (targets.length === 0) { console.log(chalk.yellow('\nšŸ“­ No performance data yet.')); console.log(chalk.gray(' Use AI functions with Iris instrumentation to collect data.')); return; } console.log(chalk.cyan(`\nšŸ“Š Performance Tracking (${targets.length} targets)\n`)); for (const target of targets) { const records = getTelemetryForTarget(process.cwd(), target); const metrics = calculateMetrics(records); const successColor = metrics.successRate >= 0.7 ? chalk.green : chalk.red; const trendIcon = metrics.recentTrend === 'improving' ? '↑' : metrics.recentTrend === 'degrading' ? '↓' : '→'; console.log(` ${chalk.bold(target)}`); console.log(` Calls: ${metrics.callCount} | Success: ${successColor((metrics.successRate * 100).toFixed(1) + '%')} | Latency: ${metrics.avgLatency.toFixed(0)}ms | Trend: ${trendIcon}`); } console.log(''); } else if (action === 'clear') { const fs = await import('fs'); const telemetryFile = '.iris/telemetry/calls.json'; if (fs.existsSync(telemetryFile)) { fs.unlinkSync(telemetryFile); console.log(chalk.green('āœ“ Performance data cleared')); } else { console.log(chalk.yellow('No data to clear')); } } else { console.log(chalk.yellow(`Unknown action: ${action}`)); console.log(chalk.gray('Available: record, status, clear')); } } catch (error) { console.error(chalk.red('\nāŒ Track command failed:'), error); process.exit(1); } }); // ============================================================================ // IRIS PRIME Commands - Core Intelligence Features // ============================================================================ program .command('discover [path]') .description('Discover and instrument expert agents in project') .option('--project ', 'Project path (alternative to positional arg)') .option('--dry-run', 'Show what would be discovered without changes') .option('--deep', 'Deep scan (check all subdirectories)') .action(async (pathArg, options) => { try { // Support both positional arg and --project option const projectPath = pathArg || options.project || '.'; console.log(chalk.blue(`\nšŸ” Iris Discovery - Scanning ${projectPath}...\n`)); // Dynamic import to avoid loading at startup const { default: discover } = await import('../scripts/iris/iris-discover.js'); await discover({ ...options, project: projectPath }); } catch (error) { console.error(chalk.red('\nāŒ Discovery failed:'), error); process.exit(1); } }); program .command('evaluate') .description('Evaluate project health and expert performance') .option('--project ', 'Project name', 'current') .option('--output-json ', 'Output report as JSON') .option('--auto-retrain', 'Auto-retrain if drift detected') .action(async (options) => { try { console.log(chalk.blue('\nšŸ“Š Iris Evaluation - Analyzing project health...\n')); const { default: evaluate } = await import('../scripts/iris/iris-evaluate.js'); await evaluate(options); } catch (error) { console.error(chalk.red('\nāŒ Evaluation failed:'), error); process.exit(1); } }); program .command('patterns') .description('Discover patterns across all projects') .option('--source ', 'Source project') .option('--target ', 'Target project') .action(async (options) => { try { const { default: patterns } = await import('../scripts/iris/iris-patterns.js'); await patterns(options); } catch (error) { console.error(chalk.red('\nāŒ Pattern discovery failed:'), error); process.exit(1); } }); program .command('health') .description('Quick health check for current project') .option('--detailed', 'Show detailed health information') .option('--project ', 'Project name (auto-detected from .iris/config.yaml or directory name)') .action(async (options) => { try { // Auto-detect project name let projectName = options.project; if (!projectName) { // Try to read from .iris/config.yaml const configPath = path.join(process.cwd(), '.iris', 'config.yaml'); if (fs.existsSync(configPath)) { const yaml = await import('js-yaml'); const config = yaml.load(fs.readFileSync(configPath, 'utf8')) as any; projectName = config?.project?.name || config?.projectId; } // Fallback to directory name if (!projectName) { projectName = path.basename(process.cwd()); } } console.log(chalk.blue(`\nšŸ„ Health check for project: ${projectName}\n`)); // Check basic health without full evaluate const checks = { irisFolder: fs.existsSync('.iris'), configExists: fs.existsSync('.iris/config.yaml'), agentDbExists: fs.existsSync('.iris/agentdb'), learningExists: fs.existsSync('.iris/learning'), }; console.log('šŸ“ Iris Infrastructure:'); console.log(` .iris/ folder: ${checks.irisFolder ? chalk.green('āœ“') : chalk.red('āœ—')} ${checks.irisFolder ? '' : '(run: npx iris init)'}`); console.log(` config.yaml: ${checks.configExists ? chalk.green('āœ“') : chalk.yellow('ā—‹')} ${checks.configExists ? '' : '(optional)'}`); console.log(` agentdb/: ${checks.agentDbExists ? chalk.green('āœ“') : chalk.yellow('ā—‹')} ${checks.agentDbExists ? '' : '(created on first use)'}`); console.log(` learning/: ${checks.learningExists ? chalk.green('āœ“') : chalk.yellow('ā—‹')} ${checks.learningExists ? '' : '(created on first use)'}`); // Check dependencies console.log('\nšŸ“¦ Dependencies:'); try { const { execSync } = await import('child_process'); let hasTsDspy = false; let hasPyDspy = false; let hasAx = false; // Check ts-dspy (TypeScript DSPy) try { execSync('npm ls @ts-dspy/core', { stdio: 'ignore' }); hasTsDspy = true; console.log(` @ts-dspy/core: ${chalk.green('āœ“ installed')} (TypeScript prompt optimization)`); } catch { console.log(` @ts-dspy/core: ${chalk.yellow('ā—‹ not installed')}`); } // Check Python DSPy try { execSync('python3 -c "import dspy"', { stdio: 'ignore' }); hasPyDspy = true; console.log(` dspy-ai (Python): ${chalk.green('āœ“ installed')} (Python prompt optimization)`); } catch { console.log(` dspy-ai (Python): ${chalk.yellow('ā—‹ not installed')}`); } // Check Ax try { execSync('python3 -c "import ax"', { stdio: 'ignore' }); hasAx = true; console.log(` ax-platform: ${chalk.green('āœ“ installed')} (Bayesian hyperparameter tuning)`); } catch { console.log(` ax-platform: ${chalk.yellow('ā—‹ not installed')}`); } // Show DSPy recommendation if (!hasTsDspy && !hasPyDspy) { console.log(chalk.yellow('\n šŸ’” For prompt optimization, install one of:')); console.log(chalk.gray(' npm install @ts-dspy/core (TypeScript - no Python needed)')); console.log(chalk.gray(' pip install dspy-ai (Python - more features)')); } else if (hasTsDspy && !hasPyDspy) { console.log(chalk.gray('\n ā„¹ļø Using TypeScript DSPy for prompt optimization')); } else if (!hasTsDspy && hasPyDspy) { console.log(chalk.gray('\n ā„¹ļø Using Python DSPy for prompt optimization')); } else { console.log(chalk.gray('\n ā„¹ļø Both TypeScript and Python DSPy available')); } if (!hasAx) { console.log(chalk.yellow(' šŸ’” For Bayesian optimization: pip install ax-platform')); } } catch (e) { console.log(` ${chalk.yellow('Could not check dependencies')}`); } console.log(chalk.green('\nāœ… Health check complete\n')); } catch (error) { console.error(chalk.red('\nāŒ Health check failed:'), error); process.exit(1); } }); program .command('instrument') .description('Guide on how to add AgentDB telemetry to AI functions') .option('--project ', 'Project path (defaults to current)', '.') .action(async (options) => { try { console.log(chalk.blue('\nšŸ“Š Iris Telemetry Instrumentation Guide...\n')); const { default: instrument } = await import('../scripts/iris/iris-instrument.js'); await instrument(options); } catch (error) { console.error(chalk.red('\nāŒ Instrumentation guide failed:'), error); process.exit(1); } }); // ============================================================================ // FEDERATED Commands // ============================================================================ const federatedCommand = program .command('federated') .description('Federated learning control plane'); federatedCommand .command('sync') .description('Sync local learning to Supabase (Federation)') .option('--project ', 'Project identifier') .action(async (options) => { try { const { default: sync } = await import('../scripts/federated/iris-federated-sync.js'); await sync(options); } catch (error) { console.error(chalk.red('\nāŒ Sync failed:'), error); process.exit(1); } }); federatedCommand .command('start') .description('Start federated control plane') .action(async () => { try { console.log(chalk.blue('\n🌐 Starting Federated Control Plane...\n')); await import('../federated/FederatedControlPlane.js'); console.log('Federated control plane ready. Implementation pending...'); } catch (error) { console.error(chalk.red('\nāŒ Failed to start:'), error); process.exit(1); } }); federatedCommand .command('status') .description('Check federated control plane status') .action(async () => { console.log('Federated status check...'); }); // ============================================================================ // COUNCIL Commands // ============================================================================ const councilCommand = program .command('council') .description('AI Council operations'); councilCommand .command('analyze') .description('Run AI Council analysis') .action(async (options) => { try { const { default: analyze } = await import('../scripts/iris/iris-council.js'); await analyze(options); } catch (error) { console.error(chalk.red('\nāŒ Analysis failed:'), error); process.exit(1); } }); // ============================================================================ // AUTHENTICATION Commands // ============================================================================ program .command('login') .description('Login to IRIS managed service') .option('--key ', 'Login with API key directly') .option('--email ', 'Email for login') .option('--register', 'Register new account') .action(async (options) => { try { await loginCommand(options); } catch (error) { console.error(chalk.red('\nāŒ Login failed:'), error); process.exit(1); } }); program .command('logout') .description('Logout and clear stored credentials') .action(async () => { try { await logoutCommand(); } catch (error) { console.error(chalk.red('\nāŒ Logout failed:'), error); process.exit(1); } }); program .command('status') .alias('auth') .description('Show authentication status') .action(async () => { try { await statusCommand(); } catch (error) { console.error(chalk.red('\nāŒ Status check failed:'), error); process.exit(1); } }); // ============================================================================ // OPTIMIZATION Commands // ============================================================================ program .command('optimize') .description('Run hyperparameter optimization') .option('--config ', 'Path to iris-config.yaml') .option('--target