@symindx/cli
Version:
SYMindX - AI Agent Framework CLI with NyX agent
323 lines ⢠12.3 kB
JavaScript
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