369 lines
13 KiB
JavaScript
369 lines
13 KiB
JavaScript
#!/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 <KEY> <VALUE>');
|
|
process.exit(1);
|
|
}
|
|
wizard.set(args[1], args[2]);
|
|
break;
|
|
case 'get':
|
|
if (args.length < 2) {
|
|
console.error('Usage: config get <KEY>');
|
|
process.exit(1);
|
|
}
|
|
wizard.get(args[1]);
|
|
break;
|
|
case 'delete':
|
|
case 'remove':
|
|
if (args.length < 2) {
|
|
console.error('Usage: config delete <KEY>');
|
|
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 <KEY> <VALUE> Set a configuration value
|
|
get <KEY> Get a configuration value
|
|
delete <KEY> 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
|