UNPKG

@elevenlabs/convai-cli

Version:

CLI tool to manage ElevenLabs conversational AI agents

1,171 lines 48.7 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); const commander_1 = require("commander"); const path = __importStar(require("path")); const fs = __importStar(require("fs-extra")); const dotenv = __importStar(require("dotenv")); const utils_1 = require("./utils"); const templates_1 = require("./templates"); const elevenlabs_api_1 = require("./elevenlabs-api"); const config_1 = require("./config"); const tools_1 = require("./tools"); const package_json_1 = require("../package.json"); // Load environment variables dotenv.config(); const program = new commander_1.Command(); // Default file names const AGENTS_CONFIG_FILE = "agents.json"; const TOOLS_CONFIG_FILE = "tools.json"; const LOCK_FILE = "convai.lock"; program .name('convai') .description('ElevenLabs Conversational AI Agent Manager CLI') .version(package_json_1.version); program .command('init') .description('Initialize a new agent management project') .argument('[path]', 'Path to initialize the project in', '.') .action(async (projectPath) => { try { const fullPath = path.resolve(projectPath); console.log(`Initializing project in ${fullPath}`); // Create directory if it doesn't exist await fs.ensureDir(fullPath); // Create agents.json file const agentsConfigPath = path.join(fullPath, AGENTS_CONFIG_FILE); if (await fs.pathExists(agentsConfigPath)) { console.log(`${AGENTS_CONFIG_FILE} already exists, skipping creation`); } else { const initialConfig = { agents: [] }; await (0, utils_1.writeAgentConfig)(agentsConfigPath, initialConfig); console.log(`Created ${AGENTS_CONFIG_FILE}`); } // Create tools.json file const toolsConfigPath = path.join(fullPath, TOOLS_CONFIG_FILE); if (await fs.pathExists(toolsConfigPath)) { console.log(`${TOOLS_CONFIG_FILE} already exists, skipping creation`); } else { const initialToolsConfig = { tools: [] }; await (0, tools_1.writeToolsConfig)(toolsConfigPath, initialToolsConfig); console.log(`Created ${TOOLS_CONFIG_FILE}`); } // Create agent_configs directory structure const configDirs = ['agent_configs/dev', 'agent_configs/staging', 'agent_configs/prod', 'tool_configs']; for (const dir of configDirs) { const dirPath = path.join(fullPath, dir); await fs.ensureDir(dirPath); console.log(`Created directory: ${dir}`); } // Create initial lock file const lockFilePath = path.join(fullPath, LOCK_FILE); if (await fs.pathExists(lockFilePath)) { console.log(`${LOCK_FILE} already exists, skipping creation`); } else { const initialLockData = { agents: {}, tools: {} }; await (0, utils_1.saveLockFile)(lockFilePath, initialLockData); console.log(`Created ${LOCK_FILE}`); } // Create .env.example file const envExamplePath = path.join(fullPath, '.env.example'); if (!(await fs.pathExists(envExamplePath))) { const envExample = `# ElevenLabs API Key ELEVENLABS_API_KEY=your_api_key_here `; await fs.writeFile(envExamplePath, envExample); console.log('Created .env.example'); } console.log('\nProject initialized successfully!'); console.log('Next steps:'); console.log('1. Set your ElevenLabs API key: convai login'); console.log('2. Create an agent: convai add agent "My Agent" --template default'); console.log('3. Create tools: convai add webhook-tool "My Webhook" or convai add client-tool "My Client"'); console.log('4. Sync to ElevenLabs: convai sync'); } catch (error) { console.error(`Error initializing project: ${error}`); process.exit(1); } }); program .command('login') .description('Login with your ElevenLabs API key') .action(async () => { try { const { read } = await Promise.resolve().then(() => __importStar(require('read'))); const apiKey = await read({ prompt: 'Enter your ElevenLabs API key: ', silent: true, replace: '*' }); if (!apiKey || apiKey.trim() === '') { console.error('API key is required'); process.exit(1); } // Test the API key by making a simple request process.env.ELEVENLABS_API_KEY = apiKey.trim(); const client = await (0, elevenlabs_api_1.getElevenLabsClient)(); try { await (0, elevenlabs_api_1.listAgentsApi)(client, 1); console.log('API key verified successfully'); } catch (error) { console.error('Invalid API key or network error'); process.exit(1); } await (0, config_1.setApiKey)(apiKey.trim()); console.log('Login successful! API key saved securely.'); } catch (error) { console.error(`Error during login: ${error}`); process.exit(1); } }); program .command('logout') .description('Logout and remove stored API key') .action(async () => { try { const loggedIn = await (0, config_1.isLoggedIn)(); if (!loggedIn) { console.log('You are not logged in'); return; } await (0, config_1.removeApiKey)(); console.log('Logged out successfully. API key removed.'); } catch (error) { console.error(`Error during logout: ${error}`); process.exit(1); } }); program .command('whoami') .description('Show current login status') .action(async () => { try { const loggedIn = await (0, config_1.isLoggedIn)(); const apiKey = await (0, config_1.getApiKey)(); const residency = await (0, config_1.getResidency)(); if (loggedIn && apiKey) { const maskedKey = apiKey.slice(0, 8) + '...' + apiKey.slice(-4); console.log(`Logged in with API key: ${maskedKey}`); // Show source of API key if (process.env.ELEVENLABS_API_KEY) { console.log('Source: Environment variable'); } else { console.log('Source: Config file'); } console.log(`Residency: ${residency}`); } else { console.log('Not logged in'); console.log('Use "convai login" to authenticate'); } } catch (error) { console.error(`Error checking login status: ${error}`); process.exit(1); } }); program .command('residency') .description('Set the API residency location') .argument('<residency>', `Residency location (${config_1.LOCATIONS.join(', ')})`) .action(async (residency) => { try { function isValidLocation(value) { return config_1.LOCATIONS.includes(value); } if (!isValidLocation(residency)) { console.error(`Invalid residency: ${residency}`); console.error(`Valid options: ${config_1.LOCATIONS.join(', ')}`); process.exit(1); } await (0, config_1.setResidency)(residency); console.log(`Residency set to: ${residency}`); } catch (error) { console.error(`Error setting residency: ${error}`); process.exit(1); } }); const addCommand = program .command('add') .description('Add agents and tools'); addCommand .command('agent') .description('Add a new agent - creates config, uploads to ElevenLabs, and saves ID') .argument('<name>', 'Name of the agent to create') .option('--config-path <path>', 'Custom config path (optional)') .option('--template <template>', 'Template type to use', 'default') .option('--skip-upload', 'Create config file only, don\'t upload to ElevenLabs', false) .option('--env <environment>', 'Environment to create agent for', 'prod') .action(async (name, options) => { try { // Check if agents.json exists const agentsConfigPath = path.resolve(AGENTS_CONFIG_FILE); if (!(await fs.pathExists(agentsConfigPath))) { console.error('agents.json not found. Run \'convai init\' first.'); process.exit(1); } // Load existing config const agentsConfig = await (0, utils_1.readAgentConfig)(agentsConfigPath); // Load lock file to check environment-specific agents const lockFilePath = path.resolve(LOCK_FILE); const lockData = await (0, utils_1.loadLockFile)(lockFilePath); // Check if agent already exists for this specific environment const lockedAgent = (0, utils_1.getAgentFromLock)(lockData, name, options.env); if (lockedAgent?.id) { console.error(`Agent '${name}' already exists for environment '${options.env}'`); process.exit(1); } // Check if agent name exists in agents.json let existingAgent = agentsConfig.agents.find(agent => agent.name === name); // Generate environment-specific config path if not provided let configPath = options.configPath; if (!configPath) { const safeName = name.toLowerCase().replace(/\s+/g, '_').replace(/[[\]]/g, ''); configPath = `agent_configs/${options.env}/${safeName}.json`; } // Create config directory and file const configFilePath = path.resolve(configPath); await fs.ensureDir(path.dirname(configFilePath)); // Create agent config using template let agentConfig; try { agentConfig = (0, templates_1.getTemplateByName)(name, options.template); } catch (error) { console.error(`${error}`); process.exit(1); } await (0, utils_1.writeAgentConfig)(configFilePath, agentConfig); console.log(`Created config file: ${configPath} (template: ${options.template})`); if (existingAgent) { console.log(`Agent '${name}' exists, adding new environment '${options.env}'`); } else { console.log(`Creating new agent '${name}' for environment '${options.env}'`); } if (options.skipUpload) { if (!existingAgent) { const newAgent = { name, environments: { [options.env]: { config: configPath } } }; agentsConfig.agents.push(newAgent); console.log(`Added agent '${name}' to agents.json (local only)`); } else { if (!existingAgent.environments) { const oldConfig = existingAgent.config || ''; existingAgent.environments = { default: { config: oldConfig } }; delete existingAgent.config; } existingAgent.environments[options.env] = { config: configPath }; console.log(`Added environment '${options.env}' to existing agent '${name}' (local only)`); } await (0, utils_1.writeAgentConfig)(agentsConfigPath, agentsConfig); console.log(`Edit ${configPath} to customize your agent, then run 'convai sync --env ${options.env}' to upload`); return; } // Create agent in ElevenLabs console.log(`Creating agent '${name}' in ElevenLabs (environment: ${options.env})...`); const client = await (0, elevenlabs_api_1.getElevenLabsClient)(); // Extract config components const conversationConfig = agentConfig.conversation_config || {}; const platformSettings = agentConfig.platform_settings; let tags = agentConfig.tags || []; // Add environment tag if specified and not already present if (options.env && !tags.includes(options.env)) { tags = [...tags, options.env]; } // Create new agent const agentId = await (0, elevenlabs_api_1.createAgentApi)(client, name, conversationConfig, platformSettings, tags); console.log(`Created agent in ElevenLabs with ID: ${agentId}`); if (!existingAgent) { const newAgent = { name, environments: { [options.env]: { config: configPath } } }; agentsConfig.agents.push(newAgent); console.log(`Added agent '${name}' to agents.json`); } else { if (!existingAgent.environments) { const oldConfig = existingAgent.config || ''; existingAgent.environments = { default: { config: oldConfig } }; delete existingAgent.config; } existingAgent.environments[options.env] = { config: configPath }; console.log(`Added environment '${options.env}' to existing agent '${name}'`); } // Save updated agents.json await (0, utils_1.writeAgentConfig)(agentsConfigPath, agentsConfig); // Update lock file with environment-specific agent ID const configHash = (0, utils_1.calculateConfigHash)(agentConfig); (0, utils_1.updateAgentInLock)(lockData, name, options.env, agentId, configHash); await (0, utils_1.saveLockFile)(lockFilePath, lockData); console.log(`Edit ${configPath} to customize your agent, then run 'convai sync --env ${options.env}' to update`); } catch (error) { console.error(`Error creating agent: ${error}`); process.exit(1); } }); addCommand .command('webhook-tool') .description('Add a new webhook tool - creates config and uploads to ElevenLabs') .argument('<name>', 'Name of the webhook tool to create') .option('--config-path <path>', 'Custom config path (optional)') .option('--skip-upload', 'Create config file only, don\'t upload to ElevenLabs', false) .action(async (name, options) => { try { await addTool(name, 'webhook', options.configPath, options.skipUpload); } catch (error) { console.error(`Error creating webhook tool: ${error}`); process.exit(1); } }); addCommand .command('client-tool') .description('Add a new client tool - creates config and uploads to ElevenLabs') .argument('<name>', 'Name of the client tool to create') .option('--config-path <path>', 'Custom config path (optional)') .option('--skip-upload', 'Create config file only, don\'t upload to ElevenLabs', false) .action(async (name, options) => { try { await addTool(name, 'client', options.configPath, options.skipUpload); } catch (error) { console.error(`Error creating client tool: ${error}`); process.exit(1); } }); const templatesCommand = program .command('templates') .description('Manage agent templates'); templatesCommand .command('list') .description('List available agent templates') .action(() => { const templateOptions = (0, templates_1.getTemplateOptions)(); console.log('Available Agent Templates:'); console.log('='.repeat(40)); for (const [templateName, description] of Object.entries(templateOptions)) { console.log(`\n${templateName}`); console.log(` ${description}`); } console.log('\nUse \'convai add <name> --template <template_name>\' to create an agent with a specific template'); }); templatesCommand .command('show') .description('Show the configuration for a specific template') .argument('<template>', 'Template name to show') .option('--agent-name <name>', 'Agent name to use in template', 'example_agent') .action((templateName, options) => { try { const templateConfig = (0, templates_1.getTemplateByName)(options.agentName, templateName); console.log(`Template: ${templateName}`); console.log('='.repeat(40)); console.log(JSON.stringify(templateConfig, null, 2)); } catch (error) { console.error(`${error}`); process.exit(1); } }); program .command('sync') .description('Synchronize agents with ElevenLabs API when configs change') .option('--agent <name>', 'Specific agent name to sync (defaults to all agents)') .option('--dry-run', 'Show what would be done without making changes', false) .option('--env <environment>', 'Target specific environment (defaults to all environments)') .action(async (options) => { try { await syncAgents(options.agent, options.dryRun, options.env); } catch (error) { console.error(`Error during sync: ${error}`); process.exit(1); } }); program .command('status') .description('Show the status of agents') .option('--agent <name>', 'Specific agent name to check (defaults to all agents)') .option('--env <environment>', 'Environment to check status for (defaults to all environments)') .action(async (options) => { try { await showStatus(options.agent, options.env); } catch (error) { console.error(`Error showing status: ${error}`); process.exit(1); } }); program .command('watch') .description('Watch for config changes and auto-sync agents') .option('--agent <name>', 'Specific agent name to watch (defaults to all agents)') .option('--env <environment>', 'Environment to watch', 'prod') .option('--interval <seconds>', 'Check interval in seconds', '5') .action(async (options) => { try { await watchForChanges(options.agent, options.env, parseInt(options.interval)); } catch (error) { console.error(`Error in watch mode: ${error}`); process.exit(1); } }); program .command('list-agents') .description('List all configured agents') .action(async () => { try { await listConfiguredAgents(); } catch (error) { console.error(`Error listing agents: ${error}`); process.exit(1); } }); program .command('fetch') .description('Fetch all agents from ElevenLabs workspace and add them to local configuration') .option('--agent <name>', 'Specific agent name pattern to search for') .option('--output-dir <dir>', 'Directory to store fetched agent configs', 'agent_configs') .option('--search <term>', 'Search agents by name') .option('--dry-run', 'Show what would be fetched without making changes', false) .option('--env <environment>', 'Environment to associate fetched agents with', 'prod') .action(async (options) => { try { await fetchAgents(options); } catch (error) { console.error(`Error fetching agents: ${error}`); process.exit(1); } }); program .command('widget') .description('Generate HTML widget snippet for an agent') .argument('<name>', 'Name of the agent to generate widget for') .option('--env <environment>', 'Environment to get agent ID from', 'prod') .action(async (name, options) => { try { await generateWidget(name, options.env); } catch (error) { console.error(`Error generating widget: ${error}`); process.exit(1); } }); // Helper functions async function addTool(name, type, configPath, skipUpload = false) { // Check if tools.json exists, create if not const toolsConfigPath = path.resolve(TOOLS_CONFIG_FILE); let toolsConfig; try { toolsConfig = await (0, tools_1.readToolsConfig)(toolsConfigPath); } catch (error) { // Initialize tools.json if it doesn't exist toolsConfig = { tools: [] }; await (0, tools_1.writeToolsConfig)(toolsConfigPath, toolsConfig); console.log(`Created ${TOOLS_CONFIG_FILE}`); } // Load lock file const lockFilePath = path.resolve(LOCK_FILE); const lockData = await (0, utils_1.loadLockFile)(lockFilePath); // Check if tool already exists const existingTool = toolsConfig.tools.find(tool => tool.name === name); const lockedTool = (0, utils_1.getToolFromLock)(lockData, name); if (existingTool && lockedTool?.id) { console.error(`Tool '${name}' already exists`); process.exit(1); } // Generate config path if not provided if (!configPath) { const safeName = name.toLowerCase().replace(/\s+/g, '_').replace(/[[\]]/g, ''); configPath = `tool_configs/${safeName}.json`; } // Create config directory and file const configFilePath = path.resolve(configPath); await fs.ensureDir(path.dirname(configFilePath)); // Create tool config using appropriate template let toolConfig; if (type === 'webhook') { toolConfig = { name, description: `${name} webhook tool`, type: 'webhook', api_schema: { url: 'https://api.example.com/webhook', method: 'POST', path_params_schema: [], query_params_schema: [], request_body_schema: { id: 'body', type: 'object', value_type: 'llm_prompt', description: 'Request body for the webhook', dynamic_variable: '', constant_value: '', required: true, properties: [] }, request_headers: [ { type: 'value', name: 'Content-Type', value: 'application/json' } ], auth_connection: null }, response_timeout_secs: 30, dynamic_variables: { dynamic_variable_placeholders: {} } }; } else { toolConfig = { name, description: `${name} client tool`, type: 'client', expects_response: false, response_timeout_secs: 30, parameters: [ { id: 'input', type: 'string', value_type: 'llm_prompt', description: 'Input parameter for the client tool', dynamic_variable: '', constant_value: '', required: true } ], dynamic_variables: { dynamic_variable_placeholders: {} } }; } await (0, tools_1.writeToolConfig)(configFilePath, toolConfig); console.log(`Created config file: ${configPath}`); // Add to tools.json if not already present if (!existingTool) { const newTool = { name, type, config: configPath }; toolsConfig.tools.push(newTool); await (0, tools_1.writeToolsConfig)(toolsConfigPath, toolsConfig); console.log(`Added tool '${name}' to tools.json`); } if (skipUpload) { console.log(`Edit ${configPath} to customize your tool, then run 'convai sync-tools' to upload`); return; } // Create tool in ElevenLabs console.log(`Creating ${type} tool '${name}' in ElevenLabs...`); const client = await (0, elevenlabs_api_1.getElevenLabsClient)(); try { const response = await (0, elevenlabs_api_1.createToolApi)(client, toolConfig); const toolId = response.toolId || `tool_${Date.now()}`; console.log(`Created tool in ElevenLabs with ID: ${toolId}`); // Update lock file const configHash = (0, utils_1.calculateConfigHash)(toolConfig); (0, utils_1.updateToolInLock)(lockData, name, toolId, configHash); await (0, utils_1.saveLockFile)(lockFilePath, lockData); console.log(`Edit ${configPath} to customize your tool, then run 'convai sync-tools' to update`); } catch (error) { console.error(`Error creating tool in ElevenLabs: ${error}`); process.exit(1); } } async function syncAgents(agentName, dryRun = false, environment) { // Load agents configuration const agentsConfigPath = path.resolve(AGENTS_CONFIG_FILE); if (!(await fs.pathExists(agentsConfigPath))) { throw new Error('agents.json not found. Run \'init\' first.'); } const agentsConfig = await (0, utils_1.readAgentConfig)(agentsConfigPath); const lockFilePath = path.resolve(LOCK_FILE); const lockData = await (0, utils_1.loadLockFile)(lockFilePath); // Initialize ElevenLabs client let client; if (!dryRun) { client = await (0, elevenlabs_api_1.getElevenLabsClient)(); } // Filter agents if specific agent name provided let agentsToProcess = agentsConfig.agents; if (agentName) { agentsToProcess = agentsConfig.agents.filter(agent => agent.name === agentName); if (agentsToProcess.length === 0) { throw new Error(`Agent '${agentName}' not found in configuration`); } } // Determine environments to sync let environmentsToSync = []; if (environment) { environmentsToSync = [environment]; } else { const envSet = new Set(); for (const agentDef of agentsToProcess) { if (agentDef.environments) { Object.keys(agentDef.environments).forEach(env => envSet.add(env)); } else { envSet.add('prod'); // Old format compatibility } } environmentsToSync = Array.from(envSet); if (environmentsToSync.length === 0) { console.log('No environments found to sync'); return; } console.log(`Syncing all environments: ${environmentsToSync.join(', ')}`); } let changesMade = false; for (const currentEnv of environmentsToSync) { console.log(`\nProcessing environment: ${currentEnv}`); for (const agentDef of agentsToProcess) { const agentDefName = agentDef.name; // Handle both old and new config structure let configPath; if (agentDef.environments) { if (currentEnv in agentDef.environments) { configPath = agentDef.environments[currentEnv].config; } else { console.log(`Warning: Agent '${agentDefName}' not configured for environment '${currentEnv}'`); continue; } } else { configPath = agentDef.config; if (!configPath) { console.log(`Warning: No config path found for agent '${agentDefName}'`); continue; } } // Check if config file exists if (!(await fs.pathExists(configPath))) { console.log(`Warning: Config file not found for ${agentDefName}: ${configPath}`); continue; } // Load agent config let agentConfig; try { agentConfig = await (0, utils_1.readAgentConfig)(configPath); } catch (error) { console.log(`Error reading config for ${agentDefName}: ${error}`); continue; } // Calculate config hash const configHash = (0, utils_1.calculateConfigHash)(agentConfig); // Get environment-specific agent data from lock file const lockedAgent = (0, utils_1.getAgentFromLock)(lockData, agentDefName, currentEnv); let needsUpdate = true; if (lockedAgent) { if (lockedAgent.hash === configHash) { needsUpdate = false; console.log(`${agentDefName}: No changes (environment: ${currentEnv})`); } else { console.log(`${agentDefName}: Config changed, will update (environment: ${currentEnv})`); } } else { console.log(`${agentDefName}: New environment detected, will create/update (environment: ${currentEnv})`); } if (!needsUpdate) { continue; } if (dryRun) { console.log(`[DRY RUN] Would update agent: ${agentDefName} (environment: ${currentEnv})`); continue; } // Perform API operation try { const agentId = lockedAgent?.id; // Extract config components const conversationConfig = agentConfig.conversation_config || {}; const platformSettings = agentConfig.platform_settings; let tags = agentConfig.tags || []; // Add environment tag if specified and not already present if (currentEnv && !tags.includes(currentEnv)) { tags = [...tags, currentEnv]; } const agentDisplayName = agentConfig.name || agentDefName; if (!agentId) { // Create new agent for this environment const newAgentId = await (0, elevenlabs_api_1.createAgentApi)(client, agentDisplayName, conversationConfig, platformSettings, tags); console.log(`Created agent ${agentDefName} for environment '${currentEnv}' (ID: ${newAgentId})`); (0, utils_1.updateAgentInLock)(lockData, agentDefName, currentEnv, newAgentId, configHash); } else { // Update existing environment-specific agent await (0, elevenlabs_api_1.updateAgentApi)(client, agentId, agentDisplayName, conversationConfig, platformSettings, tags); console.log(`Updated agent ${agentDefName} for environment '${currentEnv}' (ID: ${agentId})`); (0, utils_1.updateAgentInLock)(lockData, agentDefName, currentEnv, agentId, configHash); } changesMade = true; } catch (error) { console.log(`Error processing ${agentDefName}: ${error}`); } } } // Save lock file if changes were made if (changesMade && !dryRun) { await (0, utils_1.saveLockFile)(lockFilePath, lockData); console.log('Updated lock file'); } } async function showStatus(agentName, environment) { const agentsConfigPath = path.resolve(AGENTS_CONFIG_FILE); if (!(await fs.pathExists(agentsConfigPath))) { throw new Error('agents.json not found. Run \'init\' first.'); } const agentsConfig = await (0, utils_1.readAgentConfig)(agentsConfigPath); const lockData = await (0, utils_1.loadLockFile)(path.resolve(LOCK_FILE)); if (agentsConfig.agents.length === 0) { console.log('No agents configured'); return; } // Filter agents if specific agent name provided let agentsToShow = agentsConfig.agents; if (agentName) { agentsToShow = agentsConfig.agents.filter(agent => agent.name === agentName); if (agentsToShow.length === 0) { throw new Error(`Agent '${agentName}' not found in configuration`); } } // Determine environments to show let environmentsToShow = []; if (environment) { environmentsToShow = [environment]; console.log(`Agent Status (Environment: ${environment}):`); } else { const envSet = new Set(); for (const agentDef of agentsToShow) { if (agentDef.environments) { Object.keys(agentDef.environments).forEach(env => envSet.add(env)); } else { envSet.add('prod'); // Old format compatibility } } environmentsToShow = Array.from(envSet); console.log('Agent Status (All Environments):'); } console.log('='.repeat(50)); for (const agentDef of agentsToShow) { const agentNameCurrent = agentDef.name; for (const currentEnv of environmentsToShow) { // Handle both old and new config structure let configPath; if (agentDef.environments) { if (currentEnv in agentDef.environments) { configPath = agentDef.environments[currentEnv].config; } else { continue; // Skip if agent not configured for this environment } } else { configPath = agentDef.config; if (!configPath) { continue; } } // Get environment-specific agent ID from lock file const lockedAgent = (0, utils_1.getAgentFromLock)(lockData, agentNameCurrent, currentEnv); const agentId = lockedAgent?.id || 'Not created for this environment'; console.log(`\n${agentNameCurrent}`); console.log(` Environment: ${currentEnv}`); console.log(` Agent ID: ${agentId}`); console.log(` Config: ${configPath}`); // Check config file status if (await fs.pathExists(configPath)) { try { const agentConfig = await (0, utils_1.readAgentConfig)(configPath); const configHash = (0, utils_1.calculateConfigHash)(agentConfig); console.log(` Config Hash: ${configHash.substring(0, 8)}...`); // Check lock status for specified environment if (lockedAgent) { if (lockedAgent.hash === configHash) { console.log(` Status: Synced (${currentEnv})`); } else { console.log(` Status: Config changed (needs sync for ${currentEnv})`); } } else { console.log(` Status: New (needs sync for ${currentEnv})`); } } catch (error) { console.log(` Status: Config error: ${error}`); } } else { console.log(' Status: Config file not found'); } } } } async function watchForChanges(agentName, environment = 'prod', interval = 5) { console.log(`Watching for config changes (checking every ${interval}s)...`); if (agentName) { console.log(`Agent: ${agentName}`); } else { console.log('Agent: All agents'); } console.log(`Environment: ${environment}`); console.log('Press Ctrl+C to stop'); // Track file modification times const fileTimestamps = new Map(); const getFileMtime = async (filePath) => { try { const exists = await fs.pathExists(filePath); if (!exists) return 0; const stats = await fs.stat(filePath); return stats.mtime.getTime(); } catch { return 0; } }; const checkForChanges = async () => { // Load agents configuration const agentsConfigPath = path.resolve(AGENTS_CONFIG_FILE); if (!(await fs.pathExists(agentsConfigPath))) { return false; } try { const agentsConfig = await (0, utils_1.readAgentConfig)(agentsConfigPath); // Filter agents if specific agent name provided let agentsToWatch = agentsConfig.agents; if (agentName) { agentsToWatch = agentsConfig.agents.filter(agent => agent.name === agentName); } // Check agents.json itself const agentsMtime = await getFileMtime(agentsConfigPath); if (fileTimestamps.get(agentsConfigPath) !== agentsMtime) { fileTimestamps.set(agentsConfigPath, agentsMtime); console.log(`Detected change in ${AGENTS_CONFIG_FILE}`); return true; } // Check individual agent config files for (const agentDef of agentsToWatch) { const configPaths = []; if (agentDef.environments) { if (environment in agentDef.environments) { configPaths.push(agentDef.environments[environment].config); } } else { if (agentDef.config) { configPaths.push(agentDef.config); } } for (const configPath of configPaths) { if (await fs.pathExists(configPath)) { const configMtime = await getFileMtime(configPath); if (fileTimestamps.get(configPath) !== configMtime) { fileTimestamps.set(configPath, configMtime); console.log(`Detected change in ${configPath}`); return true; } } } } return false; } catch { return false; } }; // Initialize file timestamps await checkForChanges(); try { while (true) { if (await checkForChanges()) { console.log('Running sync...'); try { await syncAgents(agentName, false, environment); } catch (error) { console.log(`Error during sync: ${error}`); } } await new Promise(resolve => setTimeout(resolve, interval * 1000)); } } catch (error) { if (error.code === 'SIGINT') { console.log('\nStopping watch mode'); } else { throw error; } } } async function listConfiguredAgents() { const agentsConfigPath = path.resolve(AGENTS_CONFIG_FILE); if (!(await fs.pathExists(agentsConfigPath))) { throw new Error('agents.json not found. Run \'init\' first.'); } const agentsConfig = await (0, utils_1.readAgentConfig)(agentsConfigPath); if (agentsConfig.agents.length === 0) { console.log('No agents configured'); return; } console.log('Configured Agents:'); console.log('='.repeat(30)); agentsConfig.agents.forEach((agentDef, i) => { console.log(`${i + 1}. ${agentDef.name}`); if (agentDef.environments) { console.log(' Environments:'); Object.entries(agentDef.environments).forEach(([envName, envConfig]) => { console.log(` ${envName}: ${envConfig.config}`); }); } else { const configPath = agentDef.config || 'No config path'; console.log(` Config: ${configPath}`); } console.log(); }); } async function fetchAgents(options) { // Check if agents.json exists const agentsConfigPath = path.resolve(AGENTS_CONFIG_FILE); if (!(await fs.pathExists(agentsConfigPath))) { throw new Error('agents.json not found. Run \'convai init\' first.'); } const client = await (0, elevenlabs_api_1.getElevenLabsClient)(); // Use agent option as search term if provided, otherwise use search parameter const searchTerm = options.agent || options.search; // Fetch all agents from ElevenLabs console.log('Fetching agents from ElevenLabs...'); const agentsList = await (0, elevenlabs_api_1.listAgentsApi)(client, 30, searchTerm); if (agentsList.length === 0) { console.log('No agents found in your ElevenLabs workspace.'); return; } console.log(`Found ${agentsList.length} agent(s)`); // Load existing config const agentsConfig = await (0, utils_1.readAgentConfig)(agentsConfigPath); const existingAgentNames = new Set(agentsConfig.agents.map(agent => agent.name)); // Load lock file to check for existing agent IDs per environment const lockFilePath = path.resolve(LOCK_FILE); const lockData = await (0, utils_1.loadLockFile)(lockFilePath); const existingAgentIds = new Set(); // Collect all existing agent IDs across all environments Object.values(lockData.agents).forEach(environments => { Object.values(environments).forEach(envData => { if (envData.id) { existingAgentIds.add(envData.id); } }); }); let newAgentsAdded = 0; for (const agentMeta of agentsList) { const agentMetaTyped = agentMeta; const agentId = agentMetaTyped.agentId || agentMetaTyped.agent_id; if (!agentId) { console.log(`Warning: Skipping agent '${agentMetaTyped.name}' - no agent ID found`); continue; } let agentNameRemote = agentMetaTyped.name; // Skip if agent already exists by ID (in any environment) if (existingAgentIds.has(agentId)) { console.log(`Skipping '${agentNameRemote}' - already exists (ID: ${agentId})`); continue; } // Check for name conflicts if (existingAgentNames.has(agentNameRemote)) { let counter = 1; const originalName = agentNameRemote; while (existingAgentNames.has(agentNameRemote)) { agentNameRemote = `${originalName}_${counter}`; counter++; } console.log(`Warning: Name conflict: renamed '${originalName}' to '${agentNameRemote}'`); } if (options.dryRun) { console.log(`[DRY RUN] Would fetch agent: ${agentNameRemote} (ID: ${agentId}) for environment: ${options.env}`); continue; } try { // Fetch detailed agent configuration console.log(`Fetching config for '${agentNameRemote}'...`); const agentDetails = await (0, elevenlabs_api_1.getAgentApi)(client, agentId); // Extract configuration components const agentDetailsTyped = agentDetails; const conversationConfig = agentDetailsTyped.conversationConfig || agentDetailsTyped.conversation_config || {}; const platformSettings = agentDetailsTyped.platformSettings || agentDetailsTyped.platform_settings || {}; const tags = agentDetailsTyped.tags || []; // Create agent config structure const agentConfig = { name: agentNameRemote, conversation_config: conversationConfig, platform_settings: platformSettings, tags }; // Generate config file path const safeName = agentNameRemote.toLowerCase().replace(/\s+/g, '_').replace(/[[\]]/g, ''); const configPath = `${options.outputDir}/${safeName}.json`; // Create config file const configFilePath = path.resolve(configPath); await fs.ensureDir(path.dirname(configFilePath)); await (0, utils_1.writeAgentConfig)(configFilePath, agentDetailsTyped); // Create new agent entry for agents.json const newAgent = { name: agentNameRemote, config: configPath }; // Add to agents config agentsConfig.agents.push(newAgent); existingAgentNames.add(agentNameRemote); existingAgentIds.add(agentId); // Update lock file with environment-specific agent ID const configHash = (0, utils_1.calculateConfigHash)(agentConfig); (0, utils_1.updateAgentInLock)(lockData, agentNameRemote, options.env, agentId, configHash); console.log(`Added '${agentNameRemote}' (config: ${configPath}) for environment: ${options.env}`); newAgentsAdded++; } catch (error) { console.log(`Error fetching agent '${agentNameRemote}': ${error}`); continue; } } if (!options.dryRun && newAgentsAdded > 0) { // Save updated agents.json await (0, utils_1.writeAgentConfig)(agentsConfigPath, agentsConfig); // Save updated lock file await (0, utils_1.saveLockFile)(lockFilePath, lockData); console.log(`Updated ${AGENTS_CONFIG_FILE} and ${LOCK_FILE}`); } if (options.dryRun) { const newAgentsCount = agentsList.filter((a) => { const agent = a; const id = agent.agentId || agent.agent_id; return id && !existingAgentIds.has(id); }).length; console.log(`[DRY RUN] Would add ${newAgentsCount} new agent(s) for environment: ${options.env}`); } else { console.log(`Successfully added ${newAgentsAdded} new agent(s) for environment: ${options.env}`); if (newAgentsAdded > 0) { console.log(`You can now edit the config files in '${options.outputDir}/' and run 'convai sync --env ${options.env}' to update`); } } } async function generateWidget(name, environment) { // Load agents configuration const agentsConfigPath = path.resolve(AGENTS_CONFIG_FILE); if (!(await fs.pathExists(agentsConfigPath))) { throw new Error('agents.json not found. Run \'convai init\' first.'); } // Load lock file to get agent ID const lockFilePath = path.resolve(LOCK_FILE); const lockData = await (0, utils_1.loadLockFile)(lockFilePath); // Check if agent exists in config const agentsConfig = await (0, utils_1.readAgentConfig)(agentsConfigPath); const agentExists = agentsConfig.agents.some(agent => agent.name === name); if (!agentExists) { throw new Error(`Agent '${name}' not found in configuration`); } // Get environment-specific agent data from lock file const lockedAgent = (0, utils_1.getAgentFromLock)(lockData, name, environment); if (!lockedAgent?.id) { throw new Error(`Agent '${name}' not found for environment '${environment}' or not yet synced. Run 'convai sync --agent ${name} --env ${environment}' to create the agent first`); } const agentId = lockedAgent.id; const residency = await (0, config_1.getResidency)(); // Generate HTML widget snippet with server-location attribute let htmlSnippet = `<elevenlabs-convai agent-id="${agentId}"`; // Add server-location attribute for isolated regions if (residency !== 'global' && residency !== 'us') { htmlSnippet += ` server-location="${residency}"`; } htmlSnippet += `></elevenlabs-convai> <script src="https://unpkg.com/@elevenlabs/convai-widget-embed" async type="text/javascript"></script>`; console.log(`HTML Widget for '${name}' (environment: ${environment}, residency: ${residency}):`); console.log('='.repeat(60)); console.log(htmlSnippet); console.log('='.repeat(60)); console.log(`Agent ID: ${agentId}`); } // Handle SIGINT (Ctrl+C) process.on('SIGINT', () => { console.log('\nExiting...'); process.exit(0); }); program.parse(); //# sourceMappingURL=cli.js.map