#!/usr/bin/env node /** * Interactive CLI wizard for agentic-flow configuration * Supports both interactive mode and direct CLI arguments */ import { readFileSync, writeFileSync, existsSync } from 'fs'; import { resolve } from 'path'; import { createInterface } from 'readline'; const CONFIG_DEFINITIONS = [ { key: 'ANTHROPIC_API_KEY', value: '', description: 'Anthropic API key for Claude models (sk-ant-...)', required: false, validation: (val) => val.startsWith('sk-ant-') || 'Must start with sk-ant-' }, { key: 'OPENROUTER_API_KEY', value: '', description: 'OpenRouter API key for alternative models (sk-or-v1-...)', required: false, validation: (val) => val.startsWith('sk-or-') || 'Must start with sk-or-' }, { key: 'GOOGLE_GEMINI_API_KEY', value: '', description: 'Google Gemini API key for Gemini models', required: false }, { key: 'COMPLETION_MODEL', value: 'claude-sonnet-4-5-20250929', description: 'Default model for completions', required: false }, { key: 'PROVIDER', value: 'anthropic', description: 'Default provider (anthropic, openrouter, gemini, onnx)', required: false, validation: (val) => ['anthropic', 'openrouter', 'gemini', 'onnx'].includes(val) || 'Must be anthropic, openrouter, gemini, or onnx' }, { key: 'AGENTS_DIR', value: '', description: 'Custom agents directory path (optional)', required: false }, { key: 'PROXY_PORT', value: '3000', description: 'Proxy server port for OpenRouter', required: false, validation: (val) => !isNaN(parseInt(val)) || 'Must be a number' }, { key: 'USE_OPENROUTER', value: 'false', description: 'Force OpenRouter usage (true/false)', required: false, validation: (val) => ['true', 'false'].includes(val) || 'Must be true or false' }, { key: 'USE_ONNX', value: 'false', description: 'Use ONNX local inference (true/false)', required: false, validation: (val) => ['true', 'false'].includes(val) || 'Must be true or false' } ]; export class ConfigWizard { envPath; currentConfig = new Map(); constructor(envPath) { this.envPath = envPath || resolve(process.cwd(), '.env'); this.loadExistingConfig(); } loadExistingConfig() { if (existsSync(this.envPath)) { const content = readFileSync(this.envPath, 'utf-8'); content.split('\n').forEach(line => { const trimmed = line.trim(); if (trimmed && !trimmed.startsWith('#')) { const [key, ...valueParts] = trimmed.split('='); const value = valueParts.join('='); if (key && value) { this.currentConfig.set(key, value); } } }); } } saveConfig() { const lines = [ '# Agentic Flow Configuration', '# Generated by agentic-flow config wizard', `# Created: ${new Date().toISOString()}`, '' ]; CONFIG_DEFINITIONS.forEach(def => { const value = this.currentConfig.get(def.key) || def.value; if (value) { lines.push(`# ${def.description}`); lines.push(`${def.key}=${value}`); lines.push(''); } }); // Preserve other env vars not in CONFIG_DEFINITIONS this.currentConfig.forEach((value, key) => { if (!CONFIG_DEFINITIONS.find(d => d.key === key)) { lines.push(`${key}=${value}`); } }); writeFileSync(this.envPath, lines.join('\n'), 'utf-8'); } // Interactive wizard mode async runInteractive() { console.log('\nšŸ¤– Agentic Flow Configuration Wizard\n'); console.log('Configure your environment variables for agentic-flow.'); console.log('Press Enter to keep current value, or type new value.\n'); const rl = createInterface({ input: process.stdin, output: process.stdout }); const question = (prompt) => { return new Promise((resolve) => { rl.question(prompt, resolve); }); }; try { for (const def of CONFIG_DEFINITIONS) { const currentValue = this.currentConfig.get(def.key) || def.value; const displayValue = currentValue ? (def.key.includes('KEY') ? `${currentValue.substring(0, 15)}...` : currentValue) : '(not set)'; console.log(`\nšŸ“ ${def.key}`); console.log(` ${def.description}`); let input; let isValid = false; while (!isValid) { input = await question(` Current: ${displayValue}\n New value: `); // Keep current if empty if (!input.trim()) { input = currentValue; } // Validate if validator exists if (input && def.validation) { const validationResult = def.validation(input); if (validationResult === true) { isValid = true; } else { console.log(` āŒ Invalid: ${validationResult}`); } } else { isValid = true; } if (isValid && input) { this.currentConfig.set(def.key, input); } else if (isValid && !input) { // Remove if empty this.currentConfig.delete(def.key); } } } console.log('\nšŸ’¾ Saving configuration...'); this.saveConfig(); console.log(`āœ… Configuration saved to: ${this.envPath}\n`); // Show summary this.showSummary(); } finally { rl.close(); } } // Direct CLI mode - set single value set(key, value) { const def = CONFIG_DEFINITIONS.find(d => d.key === key); if (!def) { throw new Error(`Unknown configuration key: ${key}\nAvailable keys: ${CONFIG_DEFINITIONS.map(d => d.key).join(', ')}`); } // Validate if (def.validation) { const result = def.validation(value); if (result !== true) { throw new Error(`Invalid value for ${key}: ${result}`); } } this.currentConfig.set(key, value); this.saveConfig(); console.log(`āœ… Set ${key}=${value.substring(0, 20)}${value.length > 20 ? '...' : ''}`); } // Get single value get(key) { const value = this.currentConfig.get(key); if (value) { // Mask API keys if (key.includes('KEY')) { console.log(`${key}=${value.substring(0, 15)}...`); } else { console.log(`${key}=${value}`); } } else { console.log(`${key} is not set`); } } // Delete value delete(key) { if (this.currentConfig.has(key)) { this.currentConfig.delete(key); this.saveConfig(); console.log(`āœ… Deleted ${key}`); } else { console.log(`āŒ ${key} is not set`); } } // List all values list() { console.log('\nšŸ“‹ Current Configuration:\n'); CONFIG_DEFINITIONS.forEach(def => { const value = this.currentConfig.get(def.key); const displayValue = value ? (def.key.includes('KEY') ? `${value.substring(0, 15)}...` : value) : '(not set)'; console.log(`${def.key.padEnd(25)} = ${displayValue}`); console.log(` ${def.description}`); console.log(''); }); } // Show configuration summary showSummary() { console.log('šŸ“Š Configuration Summary:\n'); const hasAnthropic = this.currentConfig.has('ANTHROPIC_API_KEY'); const hasOpenRouter = this.currentConfig.has('OPENROUTER_API_KEY'); const hasGemini = this.currentConfig.has('GOOGLE_GEMINI_API_KEY'); const provider = this.currentConfig.get('PROVIDER') || 'anthropic'; console.log('Providers configured:'); console.log(` ${hasAnthropic ? 'āœ…' : 'āŒ'} Anthropic (Claude)`); console.log(` ${hasOpenRouter ? 'āœ…' : 'āŒ'} OpenRouter (Alternative models)`); console.log(` ${hasGemini ? 'āœ…' : 'āŒ'} Gemini (Google AI)`); console.log(` āš™ļø ONNX (Local inference) - always available`); console.log(''); console.log(`Default provider: ${provider}`); console.log(''); if (!hasAnthropic && !hasOpenRouter && !hasGemini) { console.log('āš ļø Warning: No API keys configured!'); console.log(' You can use ONNX local inference, but quality may be limited.'); console.log(' Run with --provider onnx to use local inference.\n'); } console.log('Next steps:'); console.log(' npx agentic-flow --list # List available agents'); console.log(' npx agentic-flow --agent coder --task "Your task" # Run agent'); console.log(''); } // Reset to defaults reset() { console.log('āš ļø Resetting configuration to defaults...'); this.currentConfig.clear(); CONFIG_DEFINITIONS.forEach(def => { if (def.value) { this.currentConfig.set(def.key, def.value); } }); this.saveConfig(); console.log('āœ… Configuration reset to defaults'); } } // CLI handler export async function handleConfigCommand(args) { const command = args[0]; const wizard = new ConfigWizard(); switch (command) { case undefined: case 'wizard': case 'interactive': await wizard.runInteractive(); break; case 'set': if (args.length < 3) { console.error('Usage: config set '); process.exit(1); } wizard.set(args[1], args[2]); break; case 'get': if (args.length < 2) { console.error('Usage: config get '); process.exit(1); } wizard.get(args[1]); break; case 'delete': case 'remove': if (args.length < 2) { console.error('Usage: config delete '); process.exit(1); } wizard.delete(args[1]); break; case 'list': wizard.list(); break; case 'reset': wizard.reset(); break; case 'help': printConfigHelp(); break; default: console.error(`Unknown config command: ${command}`); printConfigHelp(); process.exit(1); } } function printConfigHelp() { console.log(` šŸ¤– Agentic Flow Configuration Manager USAGE: npx agentic-flow config [COMMAND] [OPTIONS] COMMANDS: (none), wizard, interactive Launch interactive configuration wizard set Set a configuration value get Get a configuration value delete Delete a configuration value list List all configuration values reset Reset to default configuration help Show this help message EXAMPLES: # Interactive wizard npx agentic-flow config # Direct commands npx agentic-flow config set ANTHROPIC_API_KEY sk-ant-xxxxx npx agentic-flow config set PROVIDER openrouter npx agentic-flow config get PROVIDER npx agentic-flow config list npx agentic-flow config delete OPENROUTER_API_KEY AVAILABLE CONFIGURATION KEYS: ANTHROPIC_API_KEY - Anthropic API key (sk-ant-...) OPENROUTER_API_KEY - OpenRouter API key (sk-or-v1-...) GOOGLE_GEMINI_API_KEY - Google Gemini API key COMPLETION_MODEL - Default model name PROVIDER - Default provider (anthropic, openrouter, gemini, onnx) AGENTS_DIR - Custom agents directory PROXY_PORT - Proxy server port (default: 3000) USE_OPENROUTER - Force OpenRouter (true/false) USE_ONNX - Use ONNX local inference (true/false) For more information: https://github.com/ruvnet/agentic-flow `); } // If run directly if (import.meta.url === `file://${process.argv[1]}`) { const args = process.argv.slice(2); handleConfigCommand(args).catch(err => { console.error(err.message); process.exit(1); }); } //# sourceMappingURL=config-wizard.js.map