UNPKG

@symindx/cli

Version:

SYMindX - AI Agent Framework CLI with NyX agent

323 lines • 12.3 kB
#!/usr/bin/env node import { Command } from 'commander'; import { input, confirm, password, select } from '@inquirer/prompts'; import chalk from 'chalk'; import figlet from 'figlet'; import gradient from 'gradient-string'; import boxen from 'boxen'; import ora from 'ora'; import { Agent, NyxAgent } from './core/agent.js'; import { ConfigManager } from './utils/config.js'; import { ChatInterface } from './utils/chat-interface.js'; import { CharacterManager } from './core/character-manager.js'; const symindxGradient = gradient(['#FF006E', '#8338EC', '#3A86FF']); async function displayBanner() { console.clear(); const banner = figlet.textSync('SYMindX', { font: 'ANSI Shadow', horizontalLayout: 'fitted', verticalLayout: 'default' }); console.log(symindxGradient.multiline(banner)); console.log(boxen(chalk.cyan('AI Agent Framework'), { padding: 1, margin: 1, borderStyle: 'round', borderColor: 'cyan' })); } async function checkApiKey() { const config = await ConfigManager.load(); return config.openaiApiKey || null; } async function onboardUser() { console.log(chalk.yellow('\nšŸš€ Welcome to SYMindX! Let\'s get you set up.\n')); const apiKey = await password({ message: 'Enter your OpenAI API key:', validate: (input) => { if (!input || input.length < 10) { return 'Please enter a valid API key'; } return true; } }); const saveKey = await confirm({ message: 'Save API key for future sessions?', default: true }); if (saveKey) { await ConfigManager.save({ openaiApiKey: apiKey }); console.log(chalk.green('\nāœ… API key saved successfully!\n')); } // Validate the API key const spinner = ora('Validating API key...').start(); try { const agent = new NyxAgent(apiKey); await agent.validateApiKey(); spinner.succeed('API key validated successfully!'); } catch (error) { spinner.fail('Invalid API key. Please check and try again.'); process.exit(1); } } async function selectCharacter() { const characters = await CharacterManager.getEnabledCharacters(); if (characters.length === 0) { console.log(chalk.yellow('No enabled characters found. Using default NyX.')); return (await CharacterManager.loadCharacter('nyx')); } if (characters.length === 1) { return characters[0]; } const characterId = await select({ message: 'Select an agent:', choices: characters.map(c => ({ name: `${c.name} - ${c.personality.communication.style}`, value: c.id })) }); return characters.find(c => c.id === characterId); } async function startChat(apiKey, character) { if (!character) { character = await selectCharacter(); } const agent = new Agent(apiKey, character); await agent.initialize(); console.log(chalk.magenta(`\nšŸ¤– ${character.name} initialized and ready!\n`)); console.log(chalk.gray('Type "exit" or press Ctrl+C to quit\n')); const chatInterface = new ChatInterface(agent); await chatInterface.start(); } async function createCharacter() { console.log(chalk.cyan('\nšŸ“ Create a new character\n')); const template = await CharacterManager.getCharacterTemplate(); const id = await input({ message: 'Character ID (lowercase, no spaces):', default: 'custom-agent', validate: (input) => /^[a-z0-9-]+$/.test(input) || 'Use only lowercase letters, numbers, and hyphens' }); const name = await input({ message: 'Character name:', default: 'Custom Agent' }); const backstory = await input({ message: 'Backstory:', default: template.personality.backstory }); const traits = await input({ message: 'Personality traits (comma-separated):', default: template.personality.traits.join(', ') }); const style = await input({ message: 'Communication style:', default: template.personality.communication.style }); const tone = await input({ message: 'Communication tone:', default: template.personality.communication.tone }); const temperatureStr = await input({ message: 'Model temperature (0-2):', default: '0.7', validate: (input) => { const num = parseFloat(input); return (!isNaN(num) && num >= 0 && num <= 2) || 'Must be between 0 and 2'; } }); const character = { ...template, id, name, personality: { ...template.personality, backstory, traits: traits.split(',').map((t) => t.trim()), communication: { ...template.personality.communication, style, tone } }, model: { ...template.model, temperature: parseFloat(temperatureStr) } }; await CharacterManager.saveUserCharacter(character); console.log(chalk.green(`\nāœ… Character "${character.name}" created successfully!`)); console.log(chalk.gray(`\nCharacter saved to: ~/.symindx/characters/${character.id}.json`)); } async function main() { const program = new Command(); program .name('symindx') .description('SYMindX AI Agent Framework CLI') .version('1.0.0'); program .command('chat [character]') .description('Start a chat session') .action(async (characterId) => { await displayBanner(); let apiKey = await checkApiKey(); if (!apiKey) { await onboardUser(); apiKey = await checkApiKey(); } if (!apiKey) { console.error(chalk.red('No API key available. Exiting.')); process.exit(1); } let character; if (characterId) { character = (await CharacterManager.loadCharacter(characterId)) || undefined; if (!character) { console.error(chalk.red(`Character "${characterId}" not found.`)); process.exit(1); } } await startChat(apiKey, character); }); program .command('characters') .description('List available characters') .action(async () => { const characters = await CharacterManager.loadAllCharacters(); console.log(chalk.cyan('\nšŸ“š Available Characters:\n')); for (const char of characters) { const status = char.enabled ? chalk.green('āœ“') : chalk.gray('āœ—'); console.log(`${status} ${chalk.bold(char.name)} (${char.id})`); console.log(chalk.gray(` ${char.personality.communication.style}`)); console.log(); } console.log(chalk.gray('\nCustom characters directory: ~/.symindx/characters/')); }); program .command('create-character') .description('Create a new character') .action(createCharacter); program .command('config') .description('Manage configuration') .option('-s, --set-key <key>', 'Set OpenAI API key') .option('-r, --reset', 'Reset configuration') .action(async (options) => { if (options.setKey) { await ConfigManager.save({ openaiApiKey: options.setKey }); console.log(chalk.green('āœ… API key updated successfully!')); } else if (options.reset) { await ConfigManager.reset(); console.log(chalk.yellow('āš ļø Configuration reset')); } else { const config = await ConfigManager.load(); console.log(chalk.cyan('Current configuration:')); console.log(chalk.gray(`API Key: ${config.openaiApiKey ? '***' + config.openaiApiKey.slice(-4) : 'Not set'}`)); } }); program .command('memory [character]') .description('Manage agent memory') .option('-s, --stats', 'Show memory statistics') .option('-r, --recent [limit]', 'Show recent memories') .option('-c, --clear', 'Clear all memories') .action(async (characterId, options = {}) => { let apiKey = await checkApiKey(); if (!apiKey) { console.error(chalk.red('No API key available. Run "symindx config -s <key>" first.')); process.exit(1); } let character; if (characterId) { character = (await CharacterManager.loadCharacter(characterId)) || undefined; if (!character) { console.error(chalk.red(`Character "${characterId}" not found.`)); process.exit(1); } } else { // Use default NyX character character = await CharacterManager.loadCharacter('nyx'); } if (!character) { console.error(chalk.red('No character available.')); process.exit(1); } const agent = new Agent(apiKey, character); await agent.initialize(); if (options.stats) { const stats = await agent.getMemoryStats(); console.log(chalk.cyan(`\nšŸ“Š Memory Statistics for ${character.name}:\n`)); console.log(chalk.yellow(`Total memories: ${stats.total}`)); console.log(chalk.gray('By type:')); Object.entries(stats.byType).forEach(([type, count]) => { console.log(chalk.gray(` ${type}: ${count}`)); }); } else if (options.recent) { const limit = parseInt(options.recent) || 10; const memories = await agent.getRecentMemories(limit); console.log(chalk.cyan(`\nšŸ“ Recent memories for ${character.name} (${limit}):\n`)); memories.forEach((memory, index) => { console.log(chalk.yellow(`${index + 1}. [${memory.type}] ${memory.timestamp.toLocaleString()}`)); console.log(chalk.gray(` ${memory.content.substring(0, 100)}${memory.content.length > 100 ? '...' : ''}`)); console.log(chalk.gray(` Importance: ${memory.importance}, Tags: ${memory.tags.join(', ')}`)); console.log(); }); } else if (options.clear) { const confirm = await input({ message: `Are you sure you want to clear all memories for ${character.name}? (yes/no):`, }); if (confirm.toLowerCase() === 'yes') { await agent.clearMemories(); console.log(chalk.green(`āœ… Cleared all memories for ${character.name}`)); } else { console.log(chalk.yellow('Memory clear cancelled')); } } else { console.log(chalk.cyan(`\n🧠 Memory management for ${character.name}\n`)); console.log(chalk.gray('Available options:')); console.log(chalk.gray(' --stats Show memory statistics')); console.log(chalk.gray(' --recent Show recent memories')); console.log(chalk.gray(' --clear Clear all memories')); } }); // Default action (no command specified) if (process.argv.length === 2) { await displayBanner(); let apiKey = await checkApiKey(); if (!apiKey) { await onboardUser(); apiKey = await checkApiKey(); } if (!apiKey) { console.error(chalk.red('No API key available. Exiting.')); process.exit(1); } // Default to NyX const nyx = await CharacterManager.loadCharacter('nyx'); await startChat(apiKey, nyx ?? undefined); } else { program.parse(process.argv); } } // Handle errors gracefully process.on('unhandledRejection', (error) => { console.error(chalk.red('\nāŒ An error occurred:'), error); process.exit(1); }); process.on('SIGINT', () => { console.log(chalk.yellow('\n\nšŸ‘‹ Goodbye!')); process.exit(0); }); main().catch((error) => { console.error(chalk.red('Fatal error:'), error); process.exit(1); }); //# sourceMappingURL=cli.js.map