UNPKG

init-eliza

Version:
1,448 lines (1,238 loc) 52.2 kB
#!/usr/bin/env node import chalk from 'chalk'; import inquirer from 'inquirer'; import gradient from 'gradient-string'; import chalkAnimation from 'chalk-animation'; import figlet from 'figlet'; import { createSpinner } from 'nanospinner'; import { exec, spawn } from 'child_process'; import { promisify } from 'util'; import { existsSync, readdirSync, writeFileSync } from 'fs'; import path from 'path'; import OpenAI from "openai"; import Anthropic from '@anthropic-ai/sdk'; let openai = null let anthropicClient = null let platform = process.platform; let folderName; let agentName; let selectedProvider; let apiKey = ""; let agentType; let agentDescription; let twitterUsername = ""; let twitterEmail = ""; let twitterPassword = ""; let envTemplate = ` # Discord Configuration DISCORD_APPLICATION_ID= DISCORD_API_TOKEN= # Bot token DISCORD_VOICE_CHANNEL_ID= # The ID of the voice channel the bot should join (optional) # AI Model API Keys OPENAI_API_KEY= # OpenAI API key, starting with sk- SMALL_OPENAI_MODEL= # Default: gpt-4o-mini MEDIUM_OPENAI_MODEL= # Default: gpt-4o LARGE_OPENAI_MODEL= # Default: gpt-4o EMBEDDING_OPENAI_MODEL= # Default: text-embedding-3-small IMAGE_OPENAI_MODEL= # Default: dall-e-3 # Eternal AI's Decentralized Inference API ETERNALAI_URL= ETERNALAI_MODEL= # Default: "neuralmagic/Meta-Llama-3.1-405B-Instruct-quantized.w4a16" ETERNALAI_API_KEY= GROK_API_KEY= # GROK API Key GROQ_API_KEY= # Starts with gsk_ OPENROUTER_API_KEY= GOOGLE_GENERATIVE_AI_API_KEY= # Gemini API key ALI_BAILIAN_API_KEY= # Ali Bailian API Key NANOGPT_API_KEY= # NanoGPT API Key HYPERBOLIC_API_KEY= # Hyperbolic API Key HYPERBOLIC_MODEL= IMAGE_HYPERBOLIC_MODEL= # Default: FLUX.1-dev SMALL_HYPERBOLIC_MODEL= # Default: meta-llama/Llama-3.2-3B-Instruct MEDIUM_HYPERBOLIC_MODEL= # Default: meta-llama/Meta-Llama-3.1-70B-Instruct LARGE_HYPERBOLIC_MODEL= # Default: meta-llama/Meta-Llama-3.1-405-Instruct # Speech Synthesis ELEVENLABS_XI_API_KEY= # API key from elevenlabs # ElevenLabs Settings ELEVENLABS_MODEL_ID=eleven_multilingual_v2 ELEVENLABS_VOICE_ID=21m00Tcm4TlvDq8ikWAM ELEVENLABS_VOICE_STABILITY=0.5 ELEVENLABS_VOICE_SIMILARITY_BOOST=0.9 ELEVENLABS_VOICE_STYLE=0.66 ELEVENLABS_VOICE_USE_SPEAKER_BOOST=false ELEVENLABS_OPTIMIZE_STREAMING_LATENCY=4 ELEVENLABS_OUTPUT_FORMAT=pcm_16000 # Twitter/X Configuration TWITTER_DRY_RUN=false TWITTER_USERNAME= # Account username TWITTER_PASSWORD= # Account password TWITTER_EMAIL= # Account email TWITTER_2FA_SECRET= TWITTER_COOKIES= # Account cookies TWITTER_POLL_INTERVAL=900 # How often (in seconds) the bot should check for interactions TWITTER_SEARCH_ENABLE=FALSE # Enable timeline search, WARNING this greatly increases your chance of getting banned TWITTER_TARGET_USERS= # Comma separated list of Twitter user names to interact with X_SERVER_URL= XAI_API_KEY= XAI_MODEL= # Post Interval Settings (in minutes) POST_INTERVAL_MIN= # Default: 90 POST_INTERVAL_MAX= # Default: 180 POST_IMMEDIATELY= # Twitter action processing configuration ACTION_INTERVAL=1800000 # Interval in milliseconds between action processing runs (set: 15 minutes) ENABLE_ACTION_PROCESSING=false # Set to true to enable the action processing loop # Feature Flags IMAGE_GEN= # Set to TRUE to enable image generation USE_OPENAI_EMBEDDING= # Set to TRUE for OpenAI/1536, leave blank for local USE_OLLAMA_EMBEDDING= # Set to TRUE for OLLAMA/1024, leave blank for local # OpenRouter Models OPENROUTER_MODEL= # Default: uses hermes 70b/405b SMALL_OPENROUTER_MODEL= MEDIUM_OPENROUTER_MODEL= LARGE_OPENROUTER_MODEL= # REDPILL Configuration # https://docs.red-pill.ai/get-started/supported-models REDPILL_API_KEY= # REDPILL API Key REDPILL_MODEL= SMALL_REDPILL_MODEL= # Default: gpt-4o-mini MEDIUM_REDPILL_MODEL= # Default: gpt-4o LARGE_REDPILL_MODEL= # Default: gpt-4o # Grok Configuration SMALL_GROK_MODEL= # Default: grok-2-1212 MEDIUM_GROK_MODEL= # Default: grok-2-1212 LARGE_GROK_MODEL= # Default: grok-2-1212 EMBEDDING_GROK_MODEL= # Default: grok-2-1212 # Ollama Configuration OLLAMA_SERVER_URL= # Default: localhost:11434 OLLAMA_MODEL= OLLAMA_EMBEDDING_MODEL= # Default: mxbai-embed-large SMALL_OLLAMA_MODEL= # Default: llama3.2 MEDIUM_OLLAMA_MODEL= # Default: hermes3 LARGE_OLLAMA_MODEL= # Default: hermes3:70b # Google Configuration GOOGLE_MODEL= SMALL_GOOGLE_MODEL= # Default: gemini-1.5-flash-latest MEDIUM_GOOGLE_MODEL= # Default: gemini-1.5-flash-latest LARGE_GOOGLE_MODEL= # Default: gemini-1.5-pro-latest EMBEDDING_GOOGLE_MODEL= # Default: text-embedding-004 # Groq Configuration SMALL_GROQ_MODEL= # Default: llama-3.1-8b-instant MEDIUM_GROQ_MODEL= # Default: llama-3.3-70b-versatile LARGE_GROQ_MODEL= # Default: llama-3.2-90b-vision-preview EMBEDDING_GROQ_MODEL= # Default: llama-3.1-8b-instant # LlamaLocal Configuration LLAMALOCAL_PATH= # Default: "" which is the current directory in plugin-node/dist/ which gets destroyed and recreated on every build # NanoGPT Configuration SMALL_NANOGPT_MODEL= # Default: gpt-4o-mini MEDIUM_NANOGPT_MODEL= # Default: gpt-4o LARGE_NANOGPT_MODEL= # Default: gpt-4o # Anthropic Configuration ANTHROPIC_API_KEY= # For Claude SMALL_ANTHROPIC_MODEL= # Default: claude-3-haiku-20240307 MEDIUM_ANTHROPIC_MODEL= # Default: claude-3-5-sonnet-20241022 LARGE_ANTHROPIC_MODEL= # Default: claude-3-5-sonnet-20241022 # Heurist Configuration HEURIST_API_KEY= # Get from https://heurist.ai/dev-access SMALL_HEURIST_MODEL= # Default: meta-llama/llama-3-70b-instruct MEDIUM_HEURIST_MODEL= # Default: meta-llama/llama-3-70b-instruct LARGE_HEURIST_MODEL= # Default: meta-llama/llama-3.1-405b-instruct HEURIST_IMAGE_MODEL= # Default: PepeXL # Gaianet Configuration GAIANET_MODEL=qwen72b GAIANET_SERVER_URL=https://qwen72b.gaia.domains/v1 GAIANET_EMBEDDING_MODEL=nomic-embed USE_GAIANET_EMBEDDING=true SMALL_GAIANET_MODEL= # Default: llama3b SMALL_GAIANET_SERVER_URL= # Default: https://llama3b.gaia.domains/v1 MEDIUM_GAIANET_MODEL= # Default: llama MEDIUM_GAIANET_SERVER_URL= # Default: https://llama8b.gaia.domains/v1 LARGE_GAIANET_MODEL= # Default: qwen72b LARGE_GAIANET_SERVER_URL= # Default: https://qwen72b.gaia.domains/v1 # EVM EVM_PRIVATE_KEY= EVM_PROVIDER_URL= # Solana SOLANA_PRIVATE_KEY= SOLANA_PUBLIC_KEY= SOLANA_CLUSTER= # Default: devnet. Solana Cluster: 'devnet' | 'testnet' | 'mainnet-beta' SOLANA_ADMIN_PRIVATE_KEY= # This wallet is used to verify NFTs SOLANA_ADMIN_PUBLIC_KEY= # This wallet is used to verify NFTs SOLANA_VERIFY_TOKEN= # Authentication token for calling the verification API # Fallback Wallet Configuration (deprecated) WALLET_PRIVATE_KEY= WALLET_PUBLIC_KEY= BIRDEYE_API_KEY= # Solana Configuration SOL_ADDRESS=So11111111111111111111111111111111111111112 SLIPPAGE=1 BASE_MINT=So11111111111111111111111111111111111111112 RPC_URL=https://api.mainnet-beta.solana.com HELIUS_API_KEY= # Telegram Configuration TELEGRAM_BOT_TOKEN= # Together Configuration TOGETHER_API_KEY= # Server Configuration SERVER_PORT=3000 # Starknet Configuration STARKNET_ADDRESS= STARKNET_PRIVATE_KEY= STARKNET_RPC_URL= # Intiface Configuration INTIFACE_WEBSOCKET_URL=ws://localhost:12345 # Farcaster Neynar Configuration FARCASTER_FID= # The FID associated with the account your are sending casts from FARCASTER_NEYNAR_API_KEY= # Neynar API key: https://neynar.com/ FARCASTER_NEYNAR_SIGNER_UUID= # Signer for the account you are sending casts from. Create a signer here: https://dev.neynar.com/app FARCASTER_DRY_RUN=false # Set to true if you want to run the bot without actually publishing casts FARCASTER_POLL_INTERVAL=120 # How often (in seconds) the bot should check for farcaster interactions (replies and mentions) # Coinbase COINBASE_COMMERCE_KEY= # From Coinbase developer portal COINBASE_API_KEY= # From Coinbase developer portal COINBASE_PRIVATE_KEY= # From Coinbase developer portal COINBASE_GENERATED_WALLET_ID= # Not your address but the wallet ID from generating a wallet through the plugin COINBASE_GENERATED_WALLET_HEX_SEED= # Not your address but the wallet hex seed from generating a wallet through the plugin and calling export COINBASE_NOTIFICATION_URI= # For webhook plugin the uri you want to send the webhook to for dummy ones use https://webhook.site # Coinbase Charity Configuration IS_CHARITABLE=false # Set to true to enable charity donations CHARITY_ADDRESS_BASE=0x1234567890123456789012345678901234567890 CHARITY_ADDRESS_SOL=pWvDXKu6CpbKKvKQkZvDA66hgsTB6X2AgFxksYogHLV CHARITY_ADDRESS_ETH=0x750EF1D7a0b4Ab1c97B7A623D7917CcEb5ea779C CHARITY_ADDRESS_ARB=0x1234567890123456789012345678901234567890 CHARITY_ADDRESS_POL=0x1234567890123456789012345678901234567890 # Conflux Configuration CONFLUX_CORE_PRIVATE_KEY= CONFLUX_CORE_SPACE_RPC_URL= CONFLUX_ESPACE_PRIVATE_KEY= CONFLUX_ESPACE_RPC_URL= CONFLUX_MEME_CONTRACT_ADDRESS= # ZeroG ZEROG_INDEXER_RPC= ZEROG_EVM_RPC= ZEROG_PRIVATE_KEY= ZEROG_FLOW_ADDRESS= # TEE Configuration # TEE_MODE options: # - LOCAL: Uses simulator at localhost:8090 (for local development) # - DOCKER: Uses simulator at host.docker.internal:8090 (for docker development) # - PRODUCTION: No simulator, uses production endpoints # Defaults to OFF if not specified TEE_MODE=OFF # LOCAL | DOCKER | PRODUCTION WALLET_SECRET_SALT= # ONLY define if you want to use TEE Plugin, otherwise it will throw errors # Galadriel Configuration GALADRIEL_API_KEY=gal-* # Get from https://dashboard.galadriel.com/ # Venice Configuration VENICE_API_KEY= # generate from venice settings SMALL_VENICE_MODEL= # Default: llama-3.3-70b MEDIUM_VENICE_MODEL= # Default: llama-3.3-70b LARGE_VENICE_MODEL= # Default: llama-3.1-405b IMAGE_VENICE_MODEL= # Default: fluently-xl # fal.ai Configuration FAL_API_KEY= FAL_AI_LORA_PATH= # WhatsApp Cloud API Configuration WHATSAPP_ACCESS_TOKEN= # Permanent access token from Facebook Developer Console WHATSAPP_PHONE_NUMBER_ID= # Phone number ID from WhatsApp Business API WHATSAPP_BUSINESS_ACCOUNT_ID= # Business Account ID from Facebook Business Manager WHATSAPP_WEBHOOK_VERIFY_TOKEN= # Custom string for webhook verification WHATSAPP_API_VERSION=v17.0 # WhatsApp API version (default: v17.0) # Flow Blockchain Configuration FLOW_ADDRESS= FLOW_PRIVATE_KEY= # Private key for SHA3-256 + P256 ECDSA FLOW_NETWORK= # Default: mainnet FLOW_ENDPOINT_URL= # Default: https://mainnet.onflow.org # ICP INTERNET_COMPUTER_PRIVATE_KEY= INTERNET_COMPUTER_ADDRESS= # Aptos APTOS_PRIVATE_KEY= # Aptos private key APTOS_NETWORK= # Must be one of mainnet, testnet # EchoChambers Configuration ECHOCHAMBERS_API_URL=http://127.0.0.1:3333 ECHOCHAMBERS_API_KEY=testingkey0011 ECHOCHAMBERS_USERNAME=eliza ECHOCHAMBERS_DEFAULT_ROOM=general ECHOCHAMBERS_POLL_INTERVAL=60 ECHOCHAMBERS_MAX_MESSAGES=10 # MultiversX MVX_PRIVATE_KEY= # Multiversx private key MVX_NETWORK= # must be one of mainnet, devnet, testnet # NEAR NEAR_WALLET_SECRET_KEY= NEAR_WALLET_PUBLIC_KEY= NEAR_ADDRESS= SLIPPAGE=1 RPC_URL=https://rpc.testnet.near.org NEAR_NETWORK=testnet # or mainnet # ZKsync Era Configuration ZKSYNC_ADDRESS= ZKSYNC_PRIVATE_KEY= # Ton TON_PRIVATE_KEY= # Ton Mnemonic Seed Phrase Join With Empty String TON_RPC_URL= # ton rpc # AWS S3 Configuration Settings for File Upload AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= AWS_REGION= AWS_S3_BUCKET= AWS_S3_UPLOAD_PATH= # Deepgram DEEPGRAM_API_KEY= # Sui SUI_PRIVATE_KEY= SUI_NETWORK= # Story STORY_PRIVATE_KEY= # Story private key STORY_API_BASE_URL= # Story API base URL STORY_API_KEY= # Story API key PINATA_JWT= # Pinata JWT for uploading files to IPFS ` function getOperatingSystem() { switch (platform) { case 'win32': return 'WINDOWS'; case 'darwin': return 'MACOS'; case 'linux': return 'LINUX'; default: return 'UNKNOWN'; } } const execAsync = promisify(exec); async function retryWithNewName() { console.log(chalk.yellow('\nLet\'s try with a different agent name.')); await askName(); // This will get a new name from the user await cloneElizaRepo(); // Try cloning again with the new name } async function cloneElizaRepo() { folderName = `agents/`; // Add folder existence check /* if (existsSync(folderName)) { //console.log(chalk.red(`\nFolder '${folderName}' already exists.`)); await retryWithNewName(); return; }*/ // Assume the eliza project has already been cloned, installed, and built if (existsSync("agents")) { await generate_character_file(agentDescription, `${folderName}/characters/${agentName}.character.json`) let env_string = createTwitterEnvTemplate(twitterUsername, twitterPassword, twitterEmail) if (apiKey !== '') { env_string = createOpenEnvTemplate(apiKey) } await writeEnvFile(env_string, `${folderName}/agent/.env`) await startAgent(agentName); return } let spinner = createSpinner('Cloning Eliza repository...').start(); try { // Check if correct Node version is installed const hasCorrectVersion = await checkNodeVersion(); if (!hasCorrectVersion) { // Install the correct version if not present await execAsync('. ~/.nvm/nvm.sh && nvm install 23.3.0'); } // Use the correct version await execAsync('. ~/.nvm/nvm.sh && nvm use 23.3.0'); await execAsync(`git clone -b v0.1.7-alpha.1 https://github.com/ai16z/eliza.git ${folderName}`); //await execAsync(`cd ${folderName} && git checkout develop`); //await execAsync(`curl -o ${folderName}/packages/client-twitter/src/post.ts https://raw.githubusercontent.com/W3bbieLabs/init-eliza/refs/heads/main/src/post.ts`); // Generate the .env file let env_string = createTwitterEnvTemplate(twitterUsername, twitterPassword, twitterEmail) if (apiKey !== '') { env_string = createOpenEnvTemplate(apiKey) } await writeEnvFile(env_string, `${folderName}/agent/.env`) // Check and install pnpm if needed await installPnpm(); // Generate the character spinner.success({ text: `Successfully installed Eliza into ${folderName}` }); await generate_character_file(agentDescription, `${folderName}/characters/${agentName}.character.json`) spinner = createSpinner('Setting up Eliza...').start(); // Now run pnpm commands spinner.update({ text: 'Installing Eliza' }); let { stdout1, stderr1 } = await execAsync(`cd ${folderName} && . ~/.nvm/nvm.sh && nvm use 23.3.0 && pnpm i --no-frozen-lockfile`); if (stderr1) console.error('Errors:', stderr1); spinner.update({ text: 'Building Eliza. This may take a while... Grab a coffee ☕' }); let { stdout2, stderr2 } = await execAsync(`cd ${folderName} && . ~/.nvm/nvm.sh && nvm use 23.3.0 && pnpm build`); if (stderr2) console.error('Errors:', stderr2); spinner.success({ text: `Successfully configured Eliza.` }); console.log(chalk.green('Starting agent...')); await startAgent(agentName); } catch (error) { spinner.error({ text: 'Failed to clone repository' }); console.error(chalk.red('Error details:', error.message)); } } const sleep = (ms = 2000) => new Promise((r) => setTimeout(r, ms)); async function showMainMenu() { const answers = await inquirer.prompt({ name: 'action', type: 'list', message: 'What would you like to do?', choices: [ 'Create Agent', 'Start Agent', 'Remove Agent', 'List Agents', 'Chat with Agent', 'Reinstall SQLite', 'Exit' ] }); switch (answers.action) { case 'Create Agent': await askName(); await selectProvider(); if (selectedProvider !== 'cancel') { await configureAPI(); } await selectAgentType(); await getTwitterCredentials(); await getAgentDescription(); await cloneElizaRepo(); break; case 'Start Agent': await startExistingAgent(); break; case 'Remove Agent': await removeAgent(); break; case 'List Agents': await listAgents(); break; case 'Chat with Agent': await sendMessageToAgentMenu(); break; case 'Reinstall SQLite': await fixStupidSQLiteIssue(); await showMainMenu(); break; case 'Exit': console.log(chalk.green('Thanks for using Init-Eliza!')); process.exit(0); break; } } async function removeAgent() { const agentsDir = 'agents'; // Check if agents directory exists if (!existsSync(agentsDir)) { console.log(chalk.red('\nNo agents directory found.')); await showMainMenu(); return; } try { // Get list of agent folders let agents = readdirSync(path.join(agentsDir, 'characters'), { withFileTypes: true }) .filter(dirent => dirent.isFile()) .map(dirent => dirent.name) .filter(name => name.endsWith('.character.json')) .map(name => name.split('.')[0]); if (agents.length === 0) { console.log(chalk.yellow('\nNo agents found to remove.')); await showMainMenu(); return; } // Let user select an agent to remove const answer = await inquirer.prompt({ name: 'selectedAgent', type: 'list', message: 'Select an agent to remove:', choices: [...agents, 'Back to Main Menu'] }); if (answer.selectedAgent === 'Back to Main Menu') { await showMainMenu(); return; } // Confirm deletion const confirmation = await inquirer.prompt({ name: 'confirm', type: 'confirm', message: chalk.red(`Are you sure you want to delete agent '${answer.selectedAgent}'? This cannot be undone.`), default: false }); if (confirmation.confirm) { const spinner = createSpinner('Removing agent...').start(); try { await execAsync(`rm agents/characters/${answer.selectedAgent}.character.json`); spinner.success({ text: `Successfully removed agent: ${answer.selectedAgent}` }); await showMainMenu(); } catch (error) { spinner.error({ text: 'Failed to remove agent' }); console.error(chalk.red('Error details:', error.message)); await showMainMenu(); } } else { console.log(chalk.yellow('\nAgent deletion cancelled.')); } } catch (error) { console.error(chalk.red('Error reading agents directory:', error.message)); await showMainMenu(); } } async function listAgents() { const agentsDir = 'agents'; if (!existsSync(agentsDir)) { console.log(chalk.red('\nNo agents directory found.')); return; } try { // Get list of agent folders let agents = readdirSync(path.join(agentsDir, 'characters'), { withFileTypes: true }) .filter(dirent => dirent.isFile()) .map(dirent => dirent.name) .filter(name => name.endsWith('.character.json')) .map(name => name.split('.')[0]); if (agents.length === 0) { console.log(chalk.yellow('\nNo agents found.')); return; } console.log(chalk.blue('\nAvailable Agents:')); agents.forEach((agent, index) => { console.log(chalk.green(`${index + 1}. ${agent}`)); }); await sleep(10); // Prompt user for next action const { action } = await inquirer.prompt({ name: 'action', type: 'list', message: 'What would you like to do next?', choices: [ 'Return to Main Menu', 'Exit Program' ] }); if (action === 'Return to Main Menu') { await showMainMenu(); } else { console.log(chalk.green('Thanks for using Init-Eliza!')); process.exit(0); } } catch (error) { console.error(chalk.red('Error reading agents directory:', error.message)); } } async function askName() { const answers = await inquirer.prompt({ name: 'player_name', type: 'input', message: 'What is the name of your agent?', default() { return 'Agent1'; }, }); agentName = answers.player_name; } async function isOllamaInstalled() { try { await execAsync('which ollama'); return true; } catch (error) { return false; } } async function installOllama() { const os = getOperatingSystem(); const spinner = createSpinner('Checking Ollama installation...').start(); if (os !== 'LINUX') { spinner.error({ text: 'Ollama installation is only supported on Linux systems' }); return; } const ollamaInstalled = await isOllamaInstalled(); if (ollamaInstalled) { spinner.success({ text: 'Ollama is already installed' }); return; } spinner.update({ text: 'Installing Ollama...' }); try { await execAsync('curl -fsSL https://ollama.com/install.sh | sh'); spinner.success({ text: 'Successfully installed Ollama' }); } catch (error) { spinner.error({ text: 'Failed to install Ollama' }); console.error(chalk.red('Error details:', error.message)); } } async function selectProvider() { const answers = await inquirer.prompt({ name: 'provider', type: 'list', message: 'Select a Model:', choices: [ 'openai', 'anthropic', 'claude_vertex', 'cancel' ] }); selectedProvider = answers.provider; } async function configureAPI() { const shouldSetAPI = await inquirer.prompt({ name: 'setAPI', type: 'list', message: 'Would you like to configure the API key?', choices: ['Enter API Key', 'Skip'] }); if (shouldSetAPI.setAPI === 'Enter API Key') { const apiKeyInput = await inquirer.prompt({ name: 'key', type: 'input', message: 'Enter your API key:', }); apiKey = apiKeyInput.key; if (selectedProvider === 'openai') { openai = new OpenAI({ apiKey }); } else if (selectedProvider === 'anthropic') { anthropicClient = new Anthropic({ apiKey }); } } else { selectedProvider = 'cancel'; } } async function getTwitterCredentials() { if (agentType === 'Create Twitter Agent') { const twitterAnswers = await inquirer.prompt([ { name: 'twitter_username', type: 'input', message: 'Enter your Twitter username:', validate(input) { return input.length > 0 ? true : 'Username is required'; } }, { name: 'twitter_email', type: 'input', message: 'Enter your Twitter email:', validate(input) { return input.length > 0 && input.includes('@') ? true : 'Please enter a valid email'; } }, { name: 'twitter_password', type: 'password', message: 'Enter your Twitter password:', mask: '*', validate(input) { return input.length > 0 ? true : 'Password is required'; } } ]); twitterUsername = twitterAnswers.twitter_username; twitterEmail = twitterAnswers.twitter_email; twitterPassword = twitterAnswers.twitter_password; } else if (agentType === 'Create Discord Agent' || agentType === 'Create Agent Swarm') { console.log(chalk.yellow('\n⚠️ This feature is currently in development and will be available soon!\n')); await showMainMenu(); } } async function selectAgentType() { const answers = await inquirer.prompt({ name: 'agent', type: 'list', message: 'Select an agent type:', choices: [ 'Create Local Agent', 'Create Twitter Agent', ] }); agentType = answers.agent; } async function getAgentDescription() { const answers = await inquirer.prompt({ name: 'description', type: 'input', message: 'Describe your agent\'s purpose and functionality:', validate(input) { return input.length > 0 ? true : 'Please provide a description'; } }); agentDescription = answers.description; } function showSummary() { console.clear(); figlet(`Agent Summary`, (err, data) => { console.log(gradient.pastel.multiline(data) + '\n'); console.log(` ${chalk.blue('Agent Configuration Summary')} ${chalk.yellow('Agent Name:')} ${agentName} ${chalk.yellow('Provider:')} ${selectedProvider} ${chalk.yellow('API Key Status:')} ${apiKey ? 'Configured' : 'Skipped'} ${chalk.yellow('Agent Type:')} ${agentType} ${chalk.yellow('Description:')} ${agentDescription} ${agentType === 'Create Twitter Agent' ? ` ${chalk.blue('Twitter Configuration')} ${chalk.yellow('Username:')} ${twitterUsername} ${chalk.yellow('Email:')} ${twitterEmail} ${chalk.yellow('Password:')} ${'*'.repeat(twitterPassword?.length || 0)} ` : ''} `); console.log( chalk.green( `🤖 Agent configuration complete! Ready to build.` ) ); process.exit(0); }); } function show_title() { figlet(`Init-Eliza`, (err, data) => { console.log(gradient.pastel.multiline(data) + '\n'); console.log( chalk.green( `Built by W3bbie\n` ) ); console.log( chalk.green( `https://w3bbie.xyz/\n` ) ); }); } async function prepareNvmEnvironment() { const spinner = createSpinner('Checking NVM installation...').start(); try { // Check if nvm is installed await execAsync('command -v nvm'); spinner.success({ text: 'NVM is already installed' }); } catch (error) { // NVM is not installed spinner.update({ text: 'Installing NVM...' }); try { // Install NVM await execAsync('curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash'); // Source NVM await execAsync('source ~/.nvm/nvm.sh'); spinner.success({ text: 'Successfully installed NVM' }); } catch (installError) { spinner.error({ text: 'Failed to install NVM' }); console.error(chalk.red('Error details:', installError.message)); return false; } } // Install Node.js LTS version spinner.update({ text: 'Installing Node.js LTS version...' }); try { await execAsync('. ~/.nvm/nvm.sh install 23.3.0'); await execAsync('. ~/.nvm/nvm.sh use 23.3.0'); spinner.success({ text: 'Successfully installed Node.js LTS version' }); return true; } catch (nodeError) { spinner.error({ text: 'Failed to install Node.js LTS version' }); console.error(chalk.red('Error details:', nodeError.message)); return false; } } async function startApp() { console.clear(); await show_title(); await sleep(); // Prepare NVM environment await showMainMenu() //await sleep(); //await prepareNvmEnvironment(); //showSummary(); } async function checkNodeVersion() { const spinner = createSpinner('Checking Node.js version...').start(); try { // Source nvm and check if version exists const { stdout } = await execAsync('. ~/.nvm/nvm.sh && nvm ls | grep "v23.3.0"'); if (stdout.includes('v23.3.0')) { spinner.success({ text: 'Node.js v23.3.0 is already installed' }); return true; } else { spinner.info({ text: 'Node.js v23.3.0 is not installed' }); return false; } } catch (error) { spinner.error({ text: 'Error checking Node.js version' }); console.error(chalk.red('Error details:', error.message)); return false; } } async function fixStupidSQLiteIssue() { // This is to fix the better-sqlite3 issue. Temporary fix. Not ideal. let spinner = createSpinner('Waking up agent...').start(); await execAsync(`rm -rf agents/node_modules/better-sqlite3`); spinner.update({ text: 'Removed better-sqlite3' }); await execAsync(`pnpm store prune`); spinner.update({ text: 'Pruned store' }); await execAsync(`pnpm add better-sqlite3 --force -w`); spinner.update({ text: 'Added better-sqlite3' }); spinner.success({ text: 'Agent is awake!' }); } async function startAgent(agent_path) { // Not sure if this is needed. Avoids the stupid better-sqlite3 issue. //await fixStupidSQLiteIssue(); let p = `--character="characters/${agent_path}.character.json"` console.log(`p:${p}`) const child = spawn('pnpm', ['start', `--character="characters/${agent_path}.character.json"`], { cwd: `agents/`, shell: true, stdio: 'inherit' // This will pipe the process output directly to the parent process }); // Handle process exit return new Promise((resolve, reject) => { child.on('exit', (code) => { if (code === 0) { resolve(); } else { reject(new Error(`Process exited with code ${code}`)); } }); child.on('error', (err) => { reject(err); }); }); } async function isPnpmInstalled() { try { await execAsync('which pnpm'); return true; } catch (error) { return false; } } async function installPnpm() { const spinner = createSpinner('Checking pnpm installation...').start(); try { const pnpmInstalled = await isPnpmInstalled(); if (pnpmInstalled) { spinner.success({ text: 'pnpm is already installed' }); return true; } spinner.update({ text: 'Installing pnpm...' }); await execAsync('curl -fsSL https://get.pnpm.io/install.sh | sh -'); // Source the updated PATH to make pnpm available await execAsync('source /home/ubuntu/.bashrc'); spinner.success({ text: 'Successfully installed pnpm' }); return true; } catch (error) { spinner.error({ text: 'Failed to install pnpm' }); console.error(chalk.red('Error details:', error.message)); return false; } } async function startExistingAgent() { const agentsDir = 'agents'; let agents; // Check if agents directory exists if (!existsSync(agentsDir)) { console.log(chalk.red('\nNo agents directory found.')); return; } try { // Get list of agent folders agents = readdirSync(path.join(agentsDir, 'characters'), { withFileTypes: true }) .filter(dirent => dirent.isFile()) .map(dirent => dirent.name) .filter(name => name.endsWith('.character.json')) .map(name => name.split('.')[0]); agents.push("<Main Menu") if (agents.length === 0) { console.log(chalk.yellow('\nNo agents found. Create an agent first.')); return; } // Let user select an agent const answer = await inquirer.prompt({ name: 'selectedAgent', type: 'list', message: 'Select an agent to start:', choices: agents }); if (answer.selectedAgent === "<Main Menu") { await showMainMenu(); return; } //const selectedAgentPath = path.join(agentsDir, answer.selectedAgent); console.log(chalk.yellow(`Starting Agent: ${answer.selectedAgent}`)); try { await startAgent(answer.selectedAgent); } catch (error) { console.error(chalk.red('Error details:', error.message)); } //await startAgent(selectedAgentPath); } catch (error) { console.error(chalk.red('Error reading agents directory:', error.message)); } } async function makeGaiaNetRequest(messages, model = "llama") { const maxRetries = 3; const retryDelay = 1000; // 1 second for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await fetch('https://qwen72b.gaia.domains/v1/chat/completions', { method: 'POST', headers: { 'accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ messages, model }), timeout: 30000 // 30 second timeout }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } catch (error) { console.log(chalk.yellow(`Retrying (${attempt} / ${maxRetries})...`)); if (attempt === maxRetries) { // If all retries failed, return a fallback response return { choices: [{ message: { content: "I apologize, but I'm having trouble connecting to the server right now. Please try again later." } }] }; } // Wait before retrying await new Promise(resolve => setTimeout(resolve, retryDelay * attempt)); } } } async function makeOpenAIRequest(messages, model = "gpt-4o-mini") { const completion = await openai.chat.completions.create({ model, messages }); let output = completion.choices[0].message.content console.log(chalk.blueBright(`\n${output}`)); return output } async function makeClaudeRequest(system, messages, model = "claude-3-5-sonnet-latest") { const message = await anthropicClient.messages.create({ max_tokens: 1024, messages, model, system }); let output = message.content[0].text console.log(chalk.blueBright(`\n${output}`)); return output } // Change to support other models async function generatePrompt(instructions, description, system_prompt) { try { let content = `${instructions} for an AI agent based on the following description.\n\nDescription: '${description}'\n\nDo the best you can even if its hard. Simply respond with the best output you can. Simply return the string of the prompt.\n` if (selectedProvider === 'openai') { let resp = await makeOpenAIRequest([{ role: "system", content: system_prompt }, { role: "user", content }]) return resp } else if (selectedProvider === 'anthropic') { let resp = await makeClaudeRequest(system_prompt, [{ role: "user", content }]) return resp } else { let resp = await makeGaiaNetRequest([{ role: "system", content: system_prompt }, { role: "user", content }]) return resp.choices[0].message.content } } catch (error) { console.error(`Provider: ${selectedProvider} failed: ${error}`); process.exit(0) } } // Change to support other models async function generate_with_format_example(instructions, description, system_prompt, format_example) { let content = `${instructions} for an AI agent based on the following description.\n\nDescription: '${description}' \n\nUse the following format as an example: ${format_example}\n\nDo the best you can even if its hard. Simply respond with the best output you can. Simply return the string of the prompt.\n` //console.log(content) if (selectedProvider === 'openai') { let resp = await makeOpenAIRequest([{ role: "system", content: system_prompt }, { role: "user", content }]) return resp } else if (selectedProvider === 'anthropic') { let resp = await makeClaudeRequest(system_prompt, [{ role: "user", content }]) return resp } else { let resp = await makeGaiaNetRequest([{ role: "system", content: system_prompt }, { role: "user", content }]) return resp.choices[0].message.content } } async function generate_system_prompt(agent_description) { console.log(chalk.green('\nGenerating system prompt...')); let output = await generatePrompt("Generate a short system prompt. Do not include quotation marks.", agent_description, "You are an elite programmer.") output = output.replace(/^"|"$/g, '') return output } async function generate_bio(agent_description) { let example = `[ "hot takes specialist with no filter. lives for chaos, debates, and stirring the pot. if you're not mad, he's not doing his job.", "professional pot-stirrer who loves bold claims and wild predictions. thinks he's right 100% of the time, even when he's not.", "will defend his takes to the grave. loves nba drama almost as much as the games. unapologetically confident, always entertaining." ]` console.log(chalk.green('\nGenerating bio...')); let output = await generate_with_format_example("Generate an array of 5 bios", agent_description, "You are an elite programmer.", example) return output } async function generate_lore(agent_description) { let example = `[ "once claimed that russell westbrook would win mvp again and got ratioed by the entire in,ternet.", "famously predicted lebron would miss the playoffs in 2019 and never let anyone forget he was right.", "called kawhi leonard 'overrated' on live tv and nearly got kicked off the set.", ]` console.log(chalk.green('\nGenerating lore...')); let output = await generate_with_format_example("Generate an array of 5 lore descriptions as strings", agent_description, "You are an elite programmer.", example) return output } async function generate_message_examples(agent_description, agent_name) { let example = `[ [ { "user": "{{user1}}", "content": { "text": "what's your take on zion this season?" } }, { "user": "${agent_name}", "content": { "text": "zion's gonna average 28 and 10 this year, no debate. health isn't an issue anymore. book it." } } ], [ { "user": "{{user1}}", "content": { "text": "who's the real mvp right now?" } }, { "user": "${agent_name}", "content": { "text": "it's jokic and it's not even close. anyone saying otherwise is just salty." } } ], [ { "user": "{{user1}}", "content": { "text": "what's your take on the lakers this year?" } }, { "user": "${agent_name}", "content": { "text": "if the lakers don't make the western conference finals, it's a failure. no excuses." } } ] ]` console.log(chalk.green('\nGenerating message examples...')); let output = await generate_with_format_example("Generate an array of a short conversations with the same format as the description. Be sure to only return the array of conversations. Do not include any other text.", agent_description, "You are an elite programmer.", example) return output } async function generate_post_examples(agent_description) { let example = `[ "zion's winning mvp in the next two years. if you don't see it, you're blind.", "jokic is already a top 10 player of all time. the rings are coming, just wait.", "if curry gets one more ring, he's officially top 5 all time. no debate.", "kyrie irving is the most skilled point guard in nba history. respect the handles." ]` console.log(chalk.green('\nGenerating post examples...')); let output = await generate_with_format_example("Generate an array of 5 twitterpost examples", agent_description, "You are an elite programmer.", example) return output } async function generate_adjectives(agent_description) { let example = `[ "bold", "chaotic", "unapologetic", "confident", "spicy" ]` console.log(chalk.green('\nGenerating adjectives...')); let output = await generate_with_format_example("Generate an array of 5 adjectives", agent_description, "You are an elite programmer.", example) return output } async function generate_topics(agent_description) { let example = `[ "nba", "sports", "music", "movies", "tv" ]` console.log(chalk.green('\nGenerating topics...')); let output = await generate_with_format_example("Generate an array of 5 topics", agent_description, "You are an elite programmer.", example) return output } async function generate_style(agent_description) { let example = `{ "all": [ "use lowercase only", "never use hashtags or emojis", "make takes bold and debate-worthy", "keep responses short, punchy, and unapologetic", "don't hedge your bets—commit to your take", "be confident, even if it's ridiculous", "never sound apologetic or uncertain", "focus on starting conversations or arguments", "don't sugarcoat opinions—say what needs to be said", "embrace chaos and keep it fun", "act like a sports pundit who thrives on controversy" ], "chat": [ "answer with bold opinions, not safe guesses", "don't dodge questions—commit to the spiciest take", "be unfiltered but not mean", "never admit you're wrong unless it's funny", "respond like you're on a debate show" ], "post": [ "focus on bold predictions or spicy takes", "make statements that spark debate or reactions", "never explain yourself too much—let the take stand", "always sound confident, even if it's outlandish", "aim to entertain, not just inform" ] }` console.log(chalk.green('\nGenerating style...')); let output = await generate_with_format_example("Generate an javascript object with 3 keys. all, chat, and post. each key should have an array of strings as values. Be sure to only return the object. Do not include any other text.", agent_description, "You are an elite programmer.", example) return output } async function get_charcter_file_data(agent_description) { let generated_system_data = await generate_system_prompt(agent_description) await sleep(10) let generated_bio_data = await generate_bio(agent_description) await sleep(10) let generated_lore_data = await generate_lore(agent_description) await sleep(10) let generated_message_examples = await generate_message_examples(agent_description, "my_agent") await sleep(10) let generated_post_examples = await generate_post_examples(agent_description) await sleep(10) let generated_adjectives = await generate_adjectives(agent_description) await sleep(10) let generated_topics = await generate_topics(agent_description) await sleep(10) let generated_style = await generate_style(agent_description) /* console.log(chalk.blueBright(`\nSystem Prompt: ${generated_system_data} `)); console.log(chalk.blueBright(`\n\nBio: ${generated_bio_data} `)); console.log(chalk.blueBright(`\n\nLore: ${generated_lore_data} `)); console.log(chalk.blueBright(`\n\nMessage Examples: ${generated_message_examples} `)); console.log(chalk.blueBright(`\n\nPost Examples: ${generated_post_examples} `)); console.log(chalk.blueBright(`\n\nAdjectives: ${generated_adjectives} `)); console.log(chalk.blueBright(`\n\nTopics: ${generated_topics} `)); console.log(chalk.blueBright(`\n\nStyle: ${generated_style} `)); */ return { system: generated_system_data, bio: generated_bio_data, lore: generated_lore_data, messageExamples: generated_message_examples, postExamples: generated_post_examples, adjectives: generated_adjectives, topics: generated_topics, style: generated_style } } async function generate_character_file(agent_description, file_path) { let { system, bio, lore, messageExamples, postExamples, adjectives, topics, style } = await get_charcter_file_data(agent_description) let included_clients = agentType === 'Create Twitter Agent' ? `"twitter"` : "" let model_provider = "" if (selectedProvider === 'cancel') { model_provider = 'gaianet' } else { model_provider = selectedProvider } let character_file = `{ "name": "${agentName}", "clients": [${included_clients}], "modelProvider": "${model_provider}", "settings": { "voice": { "model": "en_GB-alan-medium" } }, "plugins": [], "system": "${system}", "bio": ${bio}, "lore": ${lore}, "knowledge": [], "messageExamples": ${messageExamples}, "postExamples": ${postExamples}, "topics": ${topics}, "style": ${style}, "adjectives": ${adjectives} }` try { writeFileSync(file_path, character_file, 'utf8'); console.log(chalk.green('Successfully wrote character file.')); } catch (error) { console.error(chalk.red(`Failed to write character file: ${error}`)); throw error; } } function createTwitterEnvTemplate(username, password, email) { let result = envTemplate.replace( /TWITTER_USERNAME=.*\nTWITTER_PASSWORD=.*\nTWITTER_EMAIL=.*/, `TWITTER_USERNAME=${username}\nTWITTER_PASSWORD=${password}\nTWITTER_EMAIL=${email}` ); envTemplate = result return result } function createOpenEnvTemplate(api_key) { if (selectedProvider === 'openai') { return envTemplate.replace( /OPENAI_API_KEY=.*\n/, `OPENAI_API_KEY=${api_key}\n` ); } else if (selectedProvider === 'anthropic') { return envTemplate.replace( /ANTHROPIC_API_KEY=.*\n/, `ANTHROPIC_API_KEY=${api_key}\n` ); } else if (selectedProvider === 'claude_vertex') { return envTemplate.replace( /CLAUDE_API_KEY=.*\n/, `CLAUDE_API_KEY=${api_key}\n` ); } } function writeEnvFile(content, filepath = 'test.env') { try { writeFileSync(filepath, content, 'utf8'); } catch (error) { throw new Error(`Failed to write env file: ${error} `); } } async function sendMessageToAgent(agentName, message = "Hello, how are you today?") { try { const response = await fetch(`http://localhost:3000/${agentName}/message`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: message }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); console.log("Agent: " + chalk.green(`${data[0].text}`)); //console.log(data); return data.text; } catch (error) { console.error(chalk.red('Error sending message:', error.message)); throw error; } } async function handleMessage(selectedAgent) { // Get the message from the user const messageAnswer = await inquirer.prompt({ name: 'message', type: 'input', message: 'Enter your message:', /* validate(input) { return input.length > 0 ? true : 'Message cannot be empty'; }*/ }); // Send the message if (messageAnswer.message.length > 0) { //let spinner = createSpinner('Agent is typing...').start(); await