@chanl-ai/cli 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/chanl.js +10 -0
- package/dist/__tests__/cli.test.d.ts +2 -0
- package/dist/__tests__/cli.test.js +2313 -0
- package/dist/__tests__/cli.test.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +72 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/agents.d.ts +8 -0
- package/dist/commands/agents.js +671 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/auth.d.ts +16 -0
- package/dist/commands/auth.js +294 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/call.d.ts +8 -0
- package/dist/commands/call.js +166 -0
- package/dist/commands/call.js.map +1 -0
- package/dist/commands/calls.d.ts +8 -0
- package/dist/commands/calls.js +719 -0
- package/dist/commands/calls.js.map +1 -0
- package/dist/commands/chat.d.ts +8 -0
- package/dist/commands/chat.js +203 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +231 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/health.d.ts +8 -0
- package/dist/commands/health.js +55 -0
- package/dist/commands/health.js.map +1 -0
- package/dist/commands/index.d.ts +18 -0
- package/dist/commands/index.js +39 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/knowledge.d.ts +8 -0
- package/dist/commands/knowledge.js +539 -0
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/mcp.d.ts +8 -0
- package/dist/commands/mcp.js +589 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/memory.d.ts +8 -0
- package/dist/commands/memory.js +408 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/personas.d.ts +8 -0
- package/dist/commands/personas.js +356 -0
- package/dist/commands/personas.js.map +1 -0
- package/dist/commands/prompts.d.ts +8 -0
- package/dist/commands/prompts.js +295 -0
- package/dist/commands/prompts.js.map +1 -0
- package/dist/commands/scenarios.d.ts +8 -0
- package/dist/commands/scenarios.js +591 -0
- package/dist/commands/scenarios.js.map +1 -0
- package/dist/commands/scorecards.d.ts +8 -0
- package/dist/commands/scorecards.js +570 -0
- package/dist/commands/scorecards.js.map +1 -0
- package/dist/commands/tools.d.ts +8 -0
- package/dist/commands/tools.js +632 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commands/toolsets.d.ts +8 -0
- package/dist/commands/toolsets.js +464 -0
- package/dist/commands/toolsets.js.map +1 -0
- package/dist/commands/workspaces.d.ts +8 -0
- package/dist/commands/workspaces.js +170 -0
- package/dist/commands/workspaces.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/config-store.d.ts +117 -0
- package/dist/utils/config-store.js +191 -0
- package/dist/utils/config-store.js.map +1 -0
- package/dist/utils/interactive.d.ts +41 -0
- package/dist/utils/interactive.js +83 -0
- package/dist/utils/interactive.js.map +1 -0
- package/dist/utils/output.d.ts +100 -0
- package/dist/utils/output.js +221 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/sdk-factory.d.ts +15 -0
- package/dist/utils/sdk-factory.js +34 -0
- package/dist/utils/sdk-factory.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/agents.ts"],"sourcesContent":["import { Command } from 'commander';\nimport ora from 'ora';\nimport chalk from 'chalk';\nimport { readFileSync } from 'fs';\nimport type { Agent, CreateAgentData, SyncAgentsResult } from '@chanl-ai/sdk';\nimport { createSdk } from '../utils/sdk-factory.js';\nimport {\n printError,\n printSuccess,\n printInfo,\n printBlank,\n printSimpleTable,\n printLabel,\n isJsonOutput,\n printJson,\n formatDate,\n} from '../utils/output.js';\n\n/**\n * Create the agents command group\n */\nexport function createAgentsCommand(): Command {\n const agents = new Command('agents')\n .description('Manage AI agents (VAPI, ElevenLabs, custom)')\n .addHelpText(\n 'after',\n `\nWhat are Agents?\n Agents are AI-powered voice assistants from various platforms (VAPI, ElevenLabs)\n or custom implementations. They can be tested with scenarios and analyzed with\n scorecards.\n\nQuick Start:\n $ chanl agents list # List all agents\n $ chanl agents get <id> # View agent details\n $ chanl agents sync vapi # Sync from VAPI\n $ chanl agents create --template vapi # Generate VAPI template\n $ chanl agents stats # View statistics\n\nWorkflow:\n 1. Sync agents from your platform (vapi, elevenlabs)\n 2. Or create custom agents with 'create -f agent.json'\n 3. Test agents with 'chanl scenarios run <id> --agent <agentId>'\n 4. Analyze results with 'chanl calls analysis <id>'`\n );\n\n // agents list\n agents\n .command('list')\n .description('List all agents')\n .option('-p, --platform <platform>', 'Filter by platform (vapi, elevenlabs, custom)')\n .option('-s, --status <status>', 'Filter by status (active, inactive)')\n .option('-l, --limit <number>', 'Number of items per page', '20')\n .option('--page <number>', 'Page number', '1')\n .addHelpText(\n 'after',\n `\nExamples:\n $ chanl agents list # List all agents\n $ chanl agents list --platform vapi # Filter by platform\n $ chanl agents list --status active # Only active agents\n $ chanl agents list --json # Output as JSON`\n )\n .action(handleAgentsList);\n\n // agents get <id>\n agents\n .command('get <id>')\n .description('Get agent details')\n .addHelpText(\n 'after',\n `\nExamples:\n $ chanl agents get abc123 # Get agent details\n $ chanl agents get abc123 --json # Output as JSON`\n )\n .action(handleAgentsGet);\n\n // agents create\n agents\n .command('create')\n .description('Create a new agent')\n .option('-f, --file <path>', 'Path to JSON file with agent data')\n .option('--template [platform]', 'Output a template JSON (vapi, elevenlabs, custom)')\n .addHelpText(\n 'after',\n `\nExamples:\n $ chanl agents create --template vapi > agent.json # VAPI template\n $ chanl agents create --template custom > agent.json # Custom template\n $ chanl agents create -f agent.json # Create from file\n\nTemplates include platform-specific configurations and examples.`\n )\n .action(handleAgentsCreate);\n\n // agents update <id>\n agents\n .command('update <id>')\n .description('Update an existing agent')\n .option('-f, --file <path>', 'Path to JSON file with update data')\n .option('--sync-to-vapi', 'Sync changes back to VAPI platform')\n .addHelpText(\n 'after',\n `\nExamples:\n $ chanl agents update abc123 -f updates.json\n $ chanl agents update abc123 -f updates.json --sync-to-vapi`\n )\n .action(handleAgentsUpdate);\n\n // agents delete <id>\n agents\n .command('delete <id>')\n .description('Delete an agent')\n .option('-y, --yes', 'Skip confirmation prompt')\n .option('--delete-from-integration', 'Also delete from platform (VAPI, etc.)')\n .addHelpText(\n 'after',\n `\nExamples:\n $ chanl agents delete abc123\n $ chanl agents delete abc123 --delete-from-integration`\n )\n .action(handleAgentsDelete);\n\n // agents sync <platform>\n agents\n .command('sync <platform>')\n .description('Sync agents from a platform (vapi, bland, twilio)')\n .option('-f, --force', 'Force update existing agents')\n .option('--clean', 'Delete all existing agents for this platform before sync')\n .addHelpText(\n 'after',\n `\nExamples:\n $ chanl agents sync vapi # Sync from VAPI\n $ chanl agents sync vapi --force # Force update all\n $ chanl agents sync vapi --clean # Clean sync (delete first)`\n )\n .action(handleAgentsSync);\n\n // agents sync-all\n agents\n .command('sync-all')\n .description('Sync agents from all configured platforms')\n .option('-f, --force', 'Force update existing agents')\n .addHelpText(\n 'after',\n `\nExamples:\n $ chanl agents sync-all # Sync from all platforms\n $ chanl agents sync-all --force # Force update all`\n )\n .action(handleAgentsSyncAll);\n\n // agents import\n agents\n .command('import')\n .description('Import a specific agent from a third-party platform')\n .requiredOption('--from <platform>', 'Source platform (vapi, retell, bland)')\n .requiredOption('--agent-id <id>', 'External agent ID on the source platform')\n .option('-n, --name <name>', 'Override the agent name')\n .addHelpText(\n 'after',\n `\nExamples:\n $ chanl agents import --from vapi --agent-id asst_abc123\n $ chanl agents import --from retell --agent-id ret_xyz --name \"My Agent\"\n $ chanl agents import --from vapi --agent-id asst_abc --json`\n )\n .action(handleAgentsImport);\n\n // agents publish\n agents\n .command('publish <agent-id>')\n .description('Publish an agent to a third-party platform')\n .requiredOption('--to <platform>', 'Target platform (vapi, retell, bland)')\n .option('--target <ext-id>', 'External agent ID to update (omit to create new)')\n .option('--with-mcp', 'Include MCP URL for tool access')\n .addHelpText(\n 'after',\n `\nExamples:\n $ chanl agents publish agent_123 --to vapi\n $ chanl agents publish agent_123 --to vapi --with-mcp\n $ chanl agents publish agent_123 --to vapi --target ext_456\n $ chanl agents publish agent_123 --to vapi --json`\n )\n .action(handleAgentsPublish);\n\n // agents push-mcp\n agents\n .command('push-mcp <agent-id>')\n .description('Push MCP URL to a third-party agent for tool access')\n .requiredOption('--to <platform>', 'Target platform (vapi, retell, bland)')\n .option('--target <ext-id>', 'External agent ID (omit to use default)')\n .addHelpText(\n 'after',\n `\nExamples:\n $ chanl agents push-mcp agent_123 --to vapi\n $ chanl agents push-mcp agent_123 --to vapi --target ext_456\n $ chanl agents push-mcp agent_123 --to vapi --json`\n )\n .action(handleAgentsPushMcp);\n\n // agents stats\n agents\n .command('stats')\n .description('View agent statistics')\n .addHelpText(\n 'after',\n `\nExamples:\n $ chanl agents stats # View statistics\n $ chanl agents stats --json # Output as JSON`\n )\n .action(handleAgentsStats);\n\n return agents;\n}\n\n/**\n * Format agent status with color\n */\nfunction formatStatus(status?: string): string {\n switch (status) {\n case 'active':\n return chalk.green('active');\n case 'inactive':\n return chalk.gray('inactive');\n case 'error':\n return chalk.red('error');\n case 'training':\n return chalk.cyan('training');\n case 'maintenance':\n return chalk.yellow('maintenance');\n default:\n return chalk.gray(status || 'unknown');\n }\n}\n\n/**\n * Format platform with color\n */\nfunction formatPlatform(platform?: string): string {\n switch (platform) {\n case 'vapi':\n return chalk.blue('vapi');\n case 'elevenlabs':\n return chalk.magenta('elevenlabs');\n case 'custom':\n return chalk.cyan('custom');\n default:\n return chalk.gray(platform || 'unknown');\n }\n}\n\n/**\n * Get agent template for a platform\n */\nfunction getAgentTemplate(platform: string = 'custom'): CreateAgentData {\n const baseTemplate: CreateAgentData = {\n platform: 'custom',\n name: 'My Custom Agent',\n type: 'Voice Assistant',\n status: 'inactive',\n useCase: 'support',\n description: 'A custom AI agent for customer support',\n configuration: {\n prompt: 'You are a helpful customer support agent. Be friendly and professional.',\n model: 'gpt-4o',\n },\n autoAnalysisEnabled: true,\n simulationMode: 'text',\n };\n\n if (platform === 'vapi') {\n return {\n ...baseTemplate,\n platform: 'vapi',\n platformAgentId: 'YOUR_VAPI_ASSISTANT_ID',\n name: 'VAPI Support Agent',\n description: 'VAPI-powered customer support agent',\n phoneNumbers: ['+1234567890'],\n testPhoneNumber: '+1234567890',\n configuration: {\n prompt: 'You are a helpful customer support agent.',\n model: 'gpt-4o',\n voice: 'YOUR_VOICE_ID',\n transcriber: 'deepgram',\n vapi: {\n firstMessage: 'Hello! How can I help you today?',\n webhookUrl: 'https://your-domain.com/webhooks/vapi',\n },\n },\n defaultSimulationChannel: 'websocket',\n simulationMode: 'websocket',\n };\n }\n\n if (platform === 'elevenlabs') {\n return {\n ...baseTemplate,\n platform: 'elevenlabs',\n platformAgentId: 'YOUR_ELEVENLABS_AGENT_ID',\n name: 'ElevenLabs Voice Agent',\n type: 'Text-to-Speech',\n description: 'ElevenLabs-powered voice agent',\n configuration: {\n prompt: 'You are a helpful assistant.',\n voice: 'YOUR_ELEVENLABS_VOICE_ID',\n elevenlabs: {},\n },\n };\n }\n\n return baseTemplate;\n}\n\n/**\n * Handle agents list command\n */\nasync function handleAgentsList(options: {\n platform?: string;\n status?: string;\n limit: string;\n page: string;\n}): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Fetching agents...').start();\n\n try {\n const response = await sdk.agents.list({\n provider: options.platform,\n status: options.status,\n limit: parseInt(options.limit, 10),\n page: parseInt(options.page, 10),\n });\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to fetch agents', response.message);\n process.exitCode = 1;\n return;\n }\n\n const { agents, pagination } = response.data;\n\n if (isJsonOutput()) {\n printJson(response.data);\n return;\n }\n\n if (agents.length === 0) {\n printInfo('No agents found');\n printInfo(\"Use 'chanl agents sync vapi' to import from VAPI\");\n printInfo(\"Or 'chanl agents create --template' to create a custom agent\");\n return;\n }\n\n printBlank();\n printSimpleTable(\n ['ID', 'Name', 'Platform', 'Status', 'Created'],\n agents.map((a: Agent) => [\n a.id.slice(-12),\n a.name.slice(0, 25),\n formatPlatform(a.provider),\n formatStatus(a.status),\n formatDate(a.createdAt),\n ])\n );\n\n printBlank();\n if (pagination) {\n printInfo(`Total: ${pagination.total} agents (Page ${pagination.page} of ${pagination.pages})`);\n }\n } catch (error) {\n spinner.fail('Failed to fetch agents');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle agents get command\n */\nasync function handleAgentsGet(id: string): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Fetching agent...').start();\n\n try {\n const response = await sdk.agents.get(id);\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to fetch agent', response.message);\n process.exitCode = 1;\n return;\n }\n\n if (isJsonOutput()) {\n printJson(response.data);\n return;\n }\n\n const { agent } = response.data;\n printBlank();\n console.log(chalk.bold('Agent Details:'));\n console.log(` ID: ${agent.id}`);\n console.log(` Name: ${agent.name}`);\n console.log(` Platform: ${formatPlatform(agent.provider)}`);\n console.log(` Status: ${formatStatus(agent.status)}`);\n if (agent.providerId) console.log(` Platform ID: ${agent.providerId}`);\n if (agent.description) console.log(` Description: ${agent.description}`);\n console.log(` Created: ${formatDate(agent.createdAt)}`);\n console.log(` Updated: ${formatDate(agent.updatedAt)}`);\n printBlank();\n } catch (error) {\n spinner.fail('Failed to fetch agent');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle agents create command\n */\nasync function handleAgentsCreate(options: {\n file?: string;\n template?: string | boolean;\n}): Promise<void> {\n // Handle template output\n if (options.template !== undefined) {\n const platform = typeof options.template === 'string' ? options.template : 'custom';\n const template = getAgentTemplate(platform);\n console.log(JSON.stringify(template, null, 2));\n return;\n }\n\n // Require file for actual creation\n if (!options.file) {\n printError('Missing required option', \"Use '-f <file>' to specify agent data or '--template [platform]' to get a template\");\n process.exitCode = 1;\n return;\n }\n\n const sdk = createSdk();\n if (!sdk) return;\n\n // Read and parse the file\n let agentData: CreateAgentData;\n try {\n const content = readFileSync(options.file, 'utf-8');\n agentData = JSON.parse(content);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Failed to read file', message);\n process.exitCode = 1;\n return;\n }\n\n const spinner = ora('Creating agent...').start();\n\n try {\n const response = await sdk.agents.create(agentData);\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to create agent', response.message);\n process.exitCode = 1;\n return;\n }\n\n if (isJsonOutput()) {\n printJson(response.data);\n return;\n }\n\n const { agent } = response.data;\n printSuccess(`Agent created: ${agent.name}`);\n printLabel('ID', agent.id);\n printLabel('Platform', agent.provider || 'custom');\n } catch (error) {\n spinner.fail('Failed to create agent');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle agents update command\n */\nasync function handleAgentsUpdate(\n id: string,\n options: { file?: string; syncToVapi?: boolean }\n): Promise<void> {\n if (!options.file) {\n printError('Missing required option', \"Use '-f <file>' to specify update data\");\n process.exitCode = 1;\n return;\n }\n\n const sdk = createSdk();\n if (!sdk) return;\n\n // Read and parse the file\n let updateData: Partial<CreateAgentData> & { syncToVapi?: boolean };\n try {\n const content = readFileSync(options.file, 'utf-8');\n updateData = JSON.parse(content);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Failed to read file', message);\n process.exitCode = 1;\n return;\n }\n\n // Add syncToVapi flag if specified\n if (options.syncToVapi) {\n updateData.syncToVapi = true;\n }\n\n const spinner = ora('Updating agent...').start();\n\n try {\n const response = await sdk.agents.update(id, updateData);\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to update agent', response.message);\n process.exitCode = 1;\n return;\n }\n\n if (isJsonOutput()) {\n printJson(response.data);\n return;\n }\n\n const { agent } = response.data;\n printSuccess(`Agent updated: ${agent.name}`);\n if (options.syncToVapi) {\n printInfo('Changes synced to VAPI');\n }\n } catch (error) {\n spinner.fail('Failed to update agent');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle agents delete command\n */\nasync function handleAgentsDelete(\n id: string,\n options: { yes?: boolean; deleteFromIntegration?: boolean }\n): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n if (!options.yes) {\n printInfo(`About to delete agent: ${id}`);\n if (options.deleteFromIntegration) {\n printInfo('This will also delete the agent from the integration platform');\n }\n printInfo(\"Use '--yes' flag to skip this confirmation\");\n }\n\n const spinner = ora('Deleting agent...').start();\n\n try {\n const response = await sdk.agents.delete(id, {\n deleteFromIntegration: options.deleteFromIntegration,\n });\n\n spinner.stop();\n\n if (!response.success) {\n printError('Failed to delete agent', response.message);\n process.exitCode = 1;\n return;\n }\n\n if (isJsonOutput()) {\n printJson({ success: true, deleted: id });\n return;\n }\n\n printSuccess(`Agent deleted: ${id}`);\n } catch (error) {\n spinner.fail('Failed to delete agent');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle agents sync command\n */\nasync function handleAgentsSync(\n platform: string,\n options: { force?: boolean; clean?: boolean }\n): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const validPlatforms = ['vapi', 'bland', 'twilio'];\n if (!validPlatforms.includes(platform)) {\n printError('Invalid platform', `Valid platforms: ${validPlatforms.join(', ')}`);\n process.exitCode = 1;\n return;\n }\n\n const spinner = ora(`Syncing agents from ${platform}...`).start();\n\n try {\n const response = await sdk.agents.sync(platform as 'vapi' | 'bland' | 'twilio', {\n forceUpdate: options.force,\n cleanBeforeSync: options.clean,\n });\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to sync agents', response.message);\n process.exitCode = 1;\n return;\n }\n\n if (isJsonOutput()) {\n printJson(response.data);\n return;\n }\n\n const result = response.data.result as SyncAgentsResult;\n printBlank();\n console.log(chalk.bold('Sync Results:'));\n console.log(` Processed: ${result.processed}`);\n console.log(` Created: ${chalk.green(result.created.toString())}`);\n console.log(` Updated: ${chalk.cyan(result.updated.toString())}`);\n if (result.errors > 0) {\n console.log(` Errors: ${chalk.red(result.errors.toString())}`);\n }\n\n if (result.details && result.details.length > 0) {\n console.log(chalk.bold('\\nDetails:'));\n for (const detail of result.details.slice(0, 10)) {\n const statusColor =\n detail.status === 'created'\n ? chalk.green\n : detail.status === 'updated'\n ? chalk.cyan\n : detail.status === 'error'\n ? chalk.red\n : chalk.gray;\n console.log(` ${detail.name.slice(0, 30)}: ${statusColor(detail.status)}`);\n }\n if (result.details.length > 10) {\n printInfo(`... and ${result.details.length - 10} more`);\n }\n }\n\n printBlank();\n } catch (error) {\n spinner.fail('Failed to sync agents');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle agents sync-all command\n */\nasync function handleAgentsSyncAll(options: { force?: boolean }): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Syncing agents from all platforms...').start();\n\n try {\n const response = await sdk.agents.syncAll({\n forceUpdate: options.force,\n });\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to sync agents', response.message);\n process.exitCode = 1;\n return;\n }\n\n if (isJsonOutput()) {\n printJson(response.data);\n return;\n }\n\n const result = response.data.result as SyncAgentsResult;\n printSuccess(result.message || 'Sync completed');\n console.log(` Processed: ${result.processed}`);\n console.log(` Created: ${chalk.green(result.created.toString())}`);\n console.log(` Updated: ${chalk.cyan(result.updated.toString())}`);\n if (result.errors > 0) {\n console.log(` Errors: ${chalk.red(result.errors.toString())}`);\n }\n printBlank();\n } catch (error) {\n spinner.fail('Failed to sync agents');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle agents stats command\n */\nasync function handleAgentsImport(options: {\n from: string;\n agentId: string;\n name?: string;\n}): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora(`Importing agent from ${options.from}...`).start();\n\n try {\n const response = await sdk.agents.importFromPlatform({\n platform: options.from,\n externalAgentId: options.agentId,\n ...(options.name ? { name: options.name } : {}),\n });\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Import failed', response.message || 'Could not import agent');\n process.exitCode = 1;\n return;\n }\n\n const agent = response.data.agent || response.data;\n\n if (isJsonOutput()) {\n printJson(response.data);\n return;\n }\n\n printBlank();\n printSuccess(`Agent imported from ${options.from}`);\n printBlank();\n printLabel('Name', agent.name);\n printLabel('Chanl ID', agent.id);\n printLabel('Platform', (agent as any).platform || options.from);\n printLabel('External ID', (agent as any).platformAgentId || options.agentId);\n printLabel('Status', agent.status || 'active');\n if (agent.deployments?.length) {\n const dep = agent.deployments[0]!;\n printLabel('Deployment', `${dep.orchestrator} → ${dep.externalId} (${dep.status})`);\n }\n printBlank();\n } catch (error) {\n spinner.fail('Import failed');\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', errorMessage);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle agents publish command\n */\nasync function handleAgentsPublish(\n agentId: string,\n options: {\n to: string;\n target?: string;\n withMcp?: boolean;\n },\n): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora(`Publishing agent to ${options.to}...`).start();\n\n try {\n const response = await sdk.agents.publish(agentId, {\n platform: options.to,\n ...(options.target ? { targetExternalId: options.target } : {}),\n ...(options.withMcp ? { includeMcp: true } : {}),\n });\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Publish failed', response.message || 'Could not publish agent');\n process.exitCode = 1;\n return;\n }\n\n const result = response.data;\n\n if (isJsonOutput()) {\n printJson(response.data);\n return;\n }\n\n printBlank();\n printSuccess(`Agent ${result.action} on ${result.platform}`);\n printBlank();\n printLabel('Action', result.action);\n printLabel('Platform', result.platform);\n printLabel('External ID', result.externalId);\n printLabel('Status', result.status);\n if (result.mcpUrl) {\n printLabel('MCP URL', result.mcpUrl);\n }\n printBlank();\n } catch (error) {\n spinner.fail('Publish failed');\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', errorMessage);\n process.exitCode = 1;\n }\n}\n\n/**\n * Handle agents push-mcp command\n */\nasync function handleAgentsPushMcp(\n agentId: string,\n options: {\n to: string;\n target?: string;\n },\n): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora(`Pushing MCP URL to ${options.to}...`).start();\n\n try {\n const response = await sdk.agents.pushMcp(agentId, {\n platform: options.to,\n ...(options.target ? { targetExternalId: options.target } : {}),\n });\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Push MCP failed', response.message || 'Could not push MCP URL');\n process.exitCode = 1;\n return;\n }\n\n const result = response.data;\n\n if (isJsonOutput()) {\n printJson(response.data);\n return;\n }\n\n printBlank();\n printSuccess(`MCP URL pushed to ${result.platform}`);\n printBlank();\n printLabel('MCP URL', result.mcpUrl);\n printLabel('Platform', result.platform);\n printLabel('External ID', result.externalId);\n printLabel('Status', result.status);\n printBlank();\n } catch (error) {\n spinner.fail('Push MCP failed');\n const errorMessage = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', errorMessage);\n process.exitCode = 1;\n }\n}\n\nasync function handleAgentsStats(): Promise<void> {\n const sdk = createSdk();\n if (!sdk) return;\n\n const spinner = ora('Fetching statistics...').start();\n\n try {\n const response = await sdk.agents.getStats();\n\n spinner.stop();\n\n if (!response.success || !response.data) {\n printError('Failed to fetch statistics', response.message);\n process.exitCode = 1;\n return;\n }\n\n if (isJsonOutput()) {\n printJson(response.data);\n return;\n }\n\n const { stats } = response.data;\n printBlank();\n console.log(chalk.bold('Agent Statistics:'));\n console.log(chalk.dim('─'.repeat(40)));\n console.log(` Total Agents: ${stats.totalAgents}`);\n console.log(` Average Score: ${stats.avgScore !== null ? `${stats.avgScore.toFixed(1)}%` : '-'}`);\n console.log(` Tool Failures: ${stats.toolFailures}`);\n console.log(` Top Provider: ${stats.topProvider || '-'}`);\n\n if (stats.byPlatform && Object.keys(stats.byPlatform).length > 0) {\n console.log(chalk.bold('\\n By Platform:'));\n for (const [platform, count] of Object.entries(stats.byPlatform)) {\n console.log(` ${formatPlatform(platform)}: ${count}`);\n }\n }\n\n if (stats.byStatus && Object.keys(stats.byStatus).length > 0) {\n console.log(chalk.bold('\\n By Status:'));\n for (const [status, count] of Object.entries(stats.byStatus)) {\n console.log(` ${formatStatus(status)}: ${count}`);\n }\n }\n\n printBlank();\n } catch (error) {\n spinner.fail('Failed to fetch statistics');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Error', message);\n process.exitCode = 1;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create the auth command group
|
|
5
|
+
*/
|
|
6
|
+
declare function createAuthCommand(): Command;
|
|
7
|
+
/**
|
|
8
|
+
* Create the login command
|
|
9
|
+
*/
|
|
10
|
+
declare function createLoginCommand(): Command;
|
|
11
|
+
/**
|
|
12
|
+
* Create the logout command
|
|
13
|
+
*/
|
|
14
|
+
declare function createLogoutCommand(): Command;
|
|
15
|
+
|
|
16
|
+
export { createAuthCommand, createLoginCommand, createLogoutCommand };
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { select } from "@inquirer/prompts";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import http from "node:http";
|
|
6
|
+
import { ChanlSDK } from "@chanl-ai/sdk";
|
|
7
|
+
import { configStore } from "../utils/config-store.js";
|
|
8
|
+
import {
|
|
9
|
+
printSuccess,
|
|
10
|
+
printError,
|
|
11
|
+
printInfo,
|
|
12
|
+
printLabel,
|
|
13
|
+
printBlank,
|
|
14
|
+
isJsonOutput,
|
|
15
|
+
printJson,
|
|
16
|
+
maskString
|
|
17
|
+
} from "../utils/output.js";
|
|
18
|
+
function createAuthCommand() {
|
|
19
|
+
const auth = new Command("auth").description("Authentication commands");
|
|
20
|
+
auth.command("status").description("Show current authentication status").action(handleAuthStatus);
|
|
21
|
+
return auth;
|
|
22
|
+
}
|
|
23
|
+
function createLoginCommand() {
|
|
24
|
+
return new Command("login").description("Authenticate with Chanl API").option("-k, --api-key <key>", "API key to use for authentication").option("--browser", "Force browser-based login (email/password or Google/Microsoft)").option("--base-url <url>", "Override the API base URL").addHelpText(
|
|
25
|
+
"after",
|
|
26
|
+
`
|
|
27
|
+
Login Methods:
|
|
28
|
+
Default Opens browser for sign-in (Google, Microsoft, or email)
|
|
29
|
+
--api-key <key> Use an API key directly (skips browser)
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
$ chanl login # Browser login (recommended)
|
|
33
|
+
$ chanl login --api-key ak_xxx # API key login
|
|
34
|
+
$ chanl login --base-url http://localhost:3100 # Custom API URL`
|
|
35
|
+
).action(handleLogin);
|
|
36
|
+
}
|
|
37
|
+
function createLogoutCommand() {
|
|
38
|
+
return new Command("logout").description("Remove stored credentials").action(handleLogout);
|
|
39
|
+
}
|
|
40
|
+
async function handleLogin(options) {
|
|
41
|
+
if (options.baseUrl) {
|
|
42
|
+
configStore.setBaseUrl(options.baseUrl);
|
|
43
|
+
}
|
|
44
|
+
if (options.apiKey) {
|
|
45
|
+
await handleApiKeyLogin(options.apiKey);
|
|
46
|
+
} else {
|
|
47
|
+
await handleBrowserLogin();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function handleApiKeyLogin(apiKey) {
|
|
51
|
+
const baseUrl = configStore.getBaseUrl();
|
|
52
|
+
const spinner = ora("Validating API key...").start();
|
|
53
|
+
try {
|
|
54
|
+
const sdk = new ChanlSDK({ apiKey, baseUrl });
|
|
55
|
+
const healthResponse = await sdk.health.getHealth();
|
|
56
|
+
if (!healthResponse.success) {
|
|
57
|
+
spinner.fail("Cannot connect to API");
|
|
58
|
+
printError("API is not reachable", `Check that the server is running at ${baseUrl}`);
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const authTestResponse = await sdk.scenarios.list();
|
|
63
|
+
if (!authTestResponse.success) {
|
|
64
|
+
spinner.fail("Authentication failed");
|
|
65
|
+
printError("Invalid API key or unauthorized access");
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
configStore.setApiKey(apiKey);
|
|
70
|
+
spinner.succeed("Authentication successful");
|
|
71
|
+
if (isJsonOutput()) {
|
|
72
|
+
printJson({ success: true, method: "api-key", apiKey: maskString(apiKey), baseUrl });
|
|
73
|
+
} else {
|
|
74
|
+
printSuccess(`API key saved to ${configStore.getPath()}`);
|
|
75
|
+
printInfo("Workspace context is determined by your API key");
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
spinner.fail("Authentication failed");
|
|
79
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
80
|
+
printError("Failed to validate API key", message);
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function handleBrowserLogin() {
|
|
85
|
+
const baseUrl = configStore.getBaseUrl();
|
|
86
|
+
const appUrl = configStore.getAppUrl();
|
|
87
|
+
printBlank();
|
|
88
|
+
printInfo("Opening browser for authentication...");
|
|
89
|
+
try {
|
|
90
|
+
const { firebaseToken, port } = await startCallbackServer();
|
|
91
|
+
const authUrl = `${appUrl}/cli-auth?port=${port}`;
|
|
92
|
+
const open = (await import("open")).default;
|
|
93
|
+
await open(authUrl);
|
|
94
|
+
printInfo(`If the browser didn't open, visit:`);
|
|
95
|
+
console.log(chalk.cyan(` ${authUrl}`));
|
|
96
|
+
printBlank();
|
|
97
|
+
const spinner = ora("Waiting for authentication...").start();
|
|
98
|
+
const token = await firebaseToken;
|
|
99
|
+
spinner.text = "Exchanging token...";
|
|
100
|
+
const sdk = new ChanlSDK({ baseUrl });
|
|
101
|
+
const response = await sdk.auth.exchangeFirebaseToken(token);
|
|
102
|
+
if (!response.success || !response.data) {
|
|
103
|
+
spinner.fail("Token exchange failed");
|
|
104
|
+
printError("Failed to exchange token", response.message);
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const { accessToken, refreshToken, user, defaultWorkspaceId, workspaces } = response.data;
|
|
109
|
+
configStore.setJwtAuth(accessToken, refreshToken);
|
|
110
|
+
let selectedWorkspaceId = defaultWorkspaceId;
|
|
111
|
+
if (workspaces && workspaces.length > 1 && !isJsonOutput()) {
|
|
112
|
+
spinner.stop();
|
|
113
|
+
printBlank();
|
|
114
|
+
selectedWorkspaceId = await select({
|
|
115
|
+
message: "Select a workspace:",
|
|
116
|
+
choices: workspaces.map((ws) => ({
|
|
117
|
+
name: ws.name || ws.workspaceId,
|
|
118
|
+
value: ws.workspaceId,
|
|
119
|
+
description: ws.role
|
|
120
|
+
})),
|
|
121
|
+
default: defaultWorkspaceId
|
|
122
|
+
});
|
|
123
|
+
} else if (workspaces && workspaces.length === 1) {
|
|
124
|
+
selectedWorkspaceId = workspaces[0].workspaceId;
|
|
125
|
+
}
|
|
126
|
+
if (selectedWorkspaceId) {
|
|
127
|
+
configStore.setWorkspaceId(selectedWorkspaceId);
|
|
128
|
+
}
|
|
129
|
+
spinner.succeed("Authentication successful");
|
|
130
|
+
if (isJsonOutput()) {
|
|
131
|
+
printJson({
|
|
132
|
+
success: true,
|
|
133
|
+
method: "browser",
|
|
134
|
+
user: { email: user.email, displayName: user.displayName },
|
|
135
|
+
workspaceId: selectedWorkspaceId
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
printBlank();
|
|
139
|
+
printLabel("User", user.email);
|
|
140
|
+
if (user.displayName) printLabel("Name", user.displayName);
|
|
141
|
+
if (selectedWorkspaceId) printLabel("Workspace", selectedWorkspaceId);
|
|
142
|
+
printBlank();
|
|
143
|
+
printSuccess(`Credentials saved to ${configStore.getPath()}`);
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
147
|
+
if (message === "LOGIN_TIMEOUT") {
|
|
148
|
+
printError("Login timed out", "No response from browser within 2 minutes");
|
|
149
|
+
} else if (message === "LOGIN_CANCELLED") {
|
|
150
|
+
printInfo("Login cancelled");
|
|
151
|
+
} else {
|
|
152
|
+
printError("Browser login failed", message);
|
|
153
|
+
}
|
|
154
|
+
process.exitCode = 1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function startCallbackServer() {
|
|
158
|
+
return new Promise((resolveSetup) => {
|
|
159
|
+
let resolveToken;
|
|
160
|
+
let rejectToken;
|
|
161
|
+
const firebaseToken = new Promise((resolve, reject) => {
|
|
162
|
+
resolveToken = resolve;
|
|
163
|
+
rejectToken = reject;
|
|
164
|
+
});
|
|
165
|
+
const server = http.createServer((req, res) => {
|
|
166
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
167
|
+
res.setHeader("Access-Control-Allow-Methods", "POST, OPTIONS");
|
|
168
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type");
|
|
169
|
+
if (req.method === "OPTIONS") {
|
|
170
|
+
res.writeHead(204);
|
|
171
|
+
res.end();
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (req.method === "POST" && req.url === "/callback") {
|
|
175
|
+
let body = "";
|
|
176
|
+
req.on("data", (chunk) => {
|
|
177
|
+
body += chunk.toString();
|
|
178
|
+
});
|
|
179
|
+
req.on("end", () => {
|
|
180
|
+
try {
|
|
181
|
+
const data = JSON.parse(body);
|
|
182
|
+
if (data.error) {
|
|
183
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
184
|
+
res.end(JSON.stringify({ success: false }));
|
|
185
|
+
rejectToken(new Error(data.error));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!data.token) {
|
|
189
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
190
|
+
res.end(JSON.stringify({ error: "Missing token" }));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
194
|
+
res.end(JSON.stringify({ success: true }));
|
|
195
|
+
resolveToken(data.token);
|
|
196
|
+
} catch {
|
|
197
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
198
|
+
res.end(JSON.stringify({ error: "Invalid JSON" }));
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
res.writeHead(404);
|
|
204
|
+
res.end();
|
|
205
|
+
});
|
|
206
|
+
server.listen(0, "127.0.0.1", () => {
|
|
207
|
+
const addr = server.address();
|
|
208
|
+
const port = typeof addr === "object" && addr ? addr.port : 0;
|
|
209
|
+
const timeout = setTimeout(() => {
|
|
210
|
+
server.close();
|
|
211
|
+
rejectToken(new Error("LOGIN_TIMEOUT"));
|
|
212
|
+
}, 12e4);
|
|
213
|
+
firebaseToken.then(() => {
|
|
214
|
+
clearTimeout(timeout);
|
|
215
|
+
setTimeout(() => server.close(), 500);
|
|
216
|
+
}).catch(() => {
|
|
217
|
+
clearTimeout(timeout);
|
|
218
|
+
server.close();
|
|
219
|
+
});
|
|
220
|
+
resolveSetup({ firebaseToken, port, server });
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
async function handleLogout() {
|
|
225
|
+
if (!configStore.isAuthenticated()) {
|
|
226
|
+
if (isJsonOutput()) {
|
|
227
|
+
printJson({ success: true, message: "Already logged out" });
|
|
228
|
+
} else {
|
|
229
|
+
printInfo("Already logged out");
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
configStore.clearAuth();
|
|
234
|
+
configStore.delete("workspaceId");
|
|
235
|
+
if (isJsonOutput()) {
|
|
236
|
+
printJson({ success: true, message: "Logged out successfully" });
|
|
237
|
+
} else {
|
|
238
|
+
printSuccess("Logged out successfully");
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function handleAuthStatus() {
|
|
242
|
+
const authMethod = configStore.getAuthMethod();
|
|
243
|
+
const baseUrl = configStore.getBaseUrl();
|
|
244
|
+
const workspaceId = configStore.getWorkspaceId();
|
|
245
|
+
if (isJsonOutput()) {
|
|
246
|
+
printJson({
|
|
247
|
+
authenticated: authMethod !== null,
|
|
248
|
+
method: authMethod,
|
|
249
|
+
apiKey: authMethod === "api-key" ? maskString(configStore.getApiKey()) : null,
|
|
250
|
+
jwtToken: authMethod === "jwt" ? "(set)" : null,
|
|
251
|
+
workspaceId: workspaceId ?? null,
|
|
252
|
+
baseUrl
|
|
253
|
+
});
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
printBlank();
|
|
257
|
+
if (!authMethod) {
|
|
258
|
+
printLabel("Status", chalk.yellow("Not authenticated"));
|
|
259
|
+
printBlank();
|
|
260
|
+
printInfo("Run 'chanl login' to authenticate via browser");
|
|
261
|
+
printInfo("Run 'chanl login --api-key <key>' to use an API key");
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
printLabel("Status", chalk.green("Authenticated"));
|
|
265
|
+
printLabel("Method", authMethod === "jwt" ? "Browser (JWT)" : "API Key");
|
|
266
|
+
if (authMethod === "api-key") {
|
|
267
|
+
printLabel("API Key", maskString(configStore.getApiKey()));
|
|
268
|
+
}
|
|
269
|
+
printLabel("Base URL", baseUrl);
|
|
270
|
+
if (workspaceId) {
|
|
271
|
+
printLabel("Workspace ID", workspaceId);
|
|
272
|
+
try {
|
|
273
|
+
const sdkConfig = { baseUrl };
|
|
274
|
+
if (authMethod === "api-key") {
|
|
275
|
+
sdkConfig.apiKey = configStore.getApiKey();
|
|
276
|
+
} else {
|
|
277
|
+
sdkConfig.jwtToken = configStore.getJwtToken();
|
|
278
|
+
}
|
|
279
|
+
const sdk = new ChanlSDK(sdkConfig);
|
|
280
|
+
const response = await sdk.workspace.getById(workspaceId);
|
|
281
|
+
if (response.success && response.data?.name) {
|
|
282
|
+
printLabel("Workspace Name", response.data.name);
|
|
283
|
+
}
|
|
284
|
+
} catch {
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
printBlank();
|
|
288
|
+
}
|
|
289
|
+
export {
|
|
290
|
+
createAuthCommand,
|
|
291
|
+
createLoginCommand,
|
|
292
|
+
createLogoutCommand
|
|
293
|
+
};
|
|
294
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/auth.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { select } from '@inquirer/prompts';\nimport ora from 'ora';\nimport chalk from 'chalk';\nimport http from 'node:http';\nimport { ChanlSDK } from '@chanl-ai/sdk';\nimport { configStore } from '../utils/config-store.js';\nimport {\n printSuccess,\n printError,\n printInfo,\n printLabel,\n printBlank,\n isJsonOutput,\n printJson,\n maskString,\n} from '../utils/output.js';\n\n/**\n * Create the auth command group\n */\nexport function createAuthCommand(): Command {\n const auth = new Command('auth').description('Authentication commands');\n\n // `chanl auth status` subcommand\n auth\n .command('status')\n .description('Show current authentication status')\n .action(handleAuthStatus);\n\n return auth;\n}\n\n/**\n * Create the login command\n */\nexport function createLoginCommand(): Command {\n return new Command('login')\n .description('Authenticate with Chanl API')\n .option('-k, --api-key <key>', 'API key to use for authentication')\n .option('--browser', 'Force browser-based login (email/password or Google/Microsoft)')\n .option('--base-url <url>', 'Override the API base URL')\n .addHelpText(\n 'after',\n `\nLogin Methods:\n Default Opens browser for sign-in (Google, Microsoft, or email)\n --api-key <key> Use an API key directly (skips browser)\n\nExamples:\n $ chanl login # Browser login (recommended)\n $ chanl login --api-key ak_xxx # API key login\n $ chanl login --base-url http://localhost:3100 # Custom API URL`\n )\n .action(handleLogin);\n}\n\n/**\n * Create the logout command\n */\nexport function createLogoutCommand(): Command {\n return new Command('logout')\n .description('Remove stored credentials')\n .action(handleLogout);\n}\n\n// ── Login Handlers ─────────────────────────────────────────────\n\n/**\n * Handle login command — browser flow by default, API key if --api-key provided\n */\nasync function handleLogin(options: {\n apiKey?: string;\n browser?: boolean;\n baseUrl?: string;\n}): Promise<void> {\n // Save base URL if provided\n if (options.baseUrl) {\n configStore.setBaseUrl(options.baseUrl);\n }\n\n if (options.apiKey) {\n // Direct API key login\n await handleApiKeyLogin(options.apiKey);\n } else {\n // Browser-based login (default)\n await handleBrowserLogin();\n }\n}\n\n/**\n * API key login flow\n */\nasync function handleApiKeyLogin(apiKey: string): Promise<void> {\n const baseUrl = configStore.getBaseUrl();\n const spinner = ora('Validating API key...').start();\n\n try {\n const sdk = new ChanlSDK({ apiKey, baseUrl });\n\n const healthResponse = await sdk.health.getHealth();\n if (!healthResponse.success) {\n spinner.fail('Cannot connect to API');\n printError('API is not reachable', `Check that the server is running at ${baseUrl}`);\n process.exitCode = 1;\n return;\n }\n\n // Validate by making an authenticated request\n const authTestResponse = await sdk.scenarios.list();\n if (!authTestResponse.success) {\n spinner.fail('Authentication failed');\n printError('Invalid API key or unauthorized access');\n process.exitCode = 1;\n return;\n }\n\n configStore.setApiKey(apiKey);\n spinner.succeed('Authentication successful');\n\n if (isJsonOutput()) {\n printJson({ success: true, method: 'api-key', apiKey: maskString(apiKey), baseUrl });\n } else {\n printSuccess(`API key saved to ${configStore.getPath()}`);\n printInfo('Workspace context is determined by your API key');\n }\n } catch (error) {\n spinner.fail('Authentication failed');\n const message = error instanceof Error ? error.message : 'Unknown error';\n printError('Failed to validate API key', message);\n process.exitCode = 1;\n }\n}\n\n/**\n * Browser-based login flow:\n * 1. Start localhost HTTP server on random port\n * 2. Open browser to chanl-admin /cli-auth page\n * 3. User authenticates in browser\n * 4. Browser POSTs Firebase token to localhost callback\n * 5. CLI exchanges token for platform JWT\n * 6. Stores credentials and workspace\n */\nasync function handleBrowserLogin(): Promise<void> {\n const baseUrl = configStore.getBaseUrl();\n const appUrl = configStore.getAppUrl();\n\n printBlank();\n printInfo('Opening browser for authentication...');\n\n try {\n // Start local server and wait for callback\n const { firebaseToken, port } = await startCallbackServer();\n const authUrl = `${appUrl}/cli-auth?port=${port}`;\n\n // Open browser\n const open = (await import('open')).default;\n await open(authUrl);\n\n printInfo(`If the browser didn't open, visit:`);\n console.log(chalk.cyan(` ${authUrl}`));\n printBlank();\n\n const spinner = ora('Waiting for authentication...').start();\n\n // Wait for the token (promise resolves when browser POSTs back)\n const token = await firebaseToken;\n spinner.text = 'Exchanging token...';\n\n // Exchange Firebase token for platform JWT\n const sdk = new ChanlSDK({ baseUrl });\n const response = await sdk.auth.exchangeFirebaseToken(token);\n\n if (!response.success || !response.data) {\n spinner.fail('Token exchange failed');\n printError('Failed to exchange token', response.message);\n process.exitCode = 1;\n return;\n }\n\n const { accessToken, refreshToken, user, defaultWorkspaceId, workspaces } = response.data;\n\n // Store JWT tokens\n configStore.setJwtAuth(accessToken, refreshToken);\n\n // Handle workspace selection\n let selectedWorkspaceId = defaultWorkspaceId;\n\n if (workspaces && workspaces.length > 1 && !isJsonOutput()) {\n spinner.stop();\n printBlank();\n\n selectedWorkspaceId = await select({\n message: 'Select a workspace:',\n choices: workspaces.map((ws) => ({\n name: ws.name || ws.workspaceId,\n value: ws.workspaceId,\n description: ws.role,\n })),\n default: defaultWorkspaceId,\n });\n } else if (workspaces && workspaces.length === 1) {\n selectedWorkspaceId = workspaces[0]!.workspaceId;\n }\n\n if (selectedWorkspaceId) {\n configStore.setWorkspaceId(selectedWorkspaceId);\n }\n\n spinner.succeed('Authentication successful');\n\n if (isJsonOutput()) {\n printJson({\n success: true,\n method: 'browser',\n user: { email: user.email, displayName: user.displayName },\n workspaceId: selectedWorkspaceId,\n });\n } else {\n printBlank();\n printLabel('User', user.email);\n if (user.displayName) printLabel('Name', user.displayName);\n if (selectedWorkspaceId) printLabel('Workspace', selectedWorkspaceId);\n printBlank();\n printSuccess(`Credentials saved to ${configStore.getPath()}`);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n if (message === 'LOGIN_TIMEOUT') {\n printError('Login timed out', 'No response from browser within 2 minutes');\n } else if (message === 'LOGIN_CANCELLED') {\n printInfo('Login cancelled');\n } else {\n printError('Browser login failed', message);\n }\n process.exitCode = 1;\n }\n}\n\n/**\n * Start a localhost HTTP server that waits for the browser to POST back a Firebase token.\n * Returns a promise that resolves with the token when received.\n */\nfunction startCallbackServer(): Promise<{\n firebaseToken: Promise<string>;\n port: number;\n server: http.Server;\n}> {\n return new Promise((resolveSetup) => {\n let resolveToken: (token: string) => void;\n let rejectToken: (err: Error) => void;\n\n const firebaseToken = new Promise<string>((resolve, reject) => {\n resolveToken = resolve;\n rejectToken = reject;\n });\n\n const server = http.createServer((req, res) => {\n // CORS headers for browser POST\n res.setHeader('Access-Control-Allow-Origin', '*');\n res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');\n res.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n if (req.method === 'POST' && req.url === '/callback') {\n let body = '';\n req.on('data', (chunk: Buffer) => {\n body += chunk.toString();\n });\n req.on('end', () => {\n try {\n const data = JSON.parse(body) as { token?: string; error?: string };\n\n if (data.error) {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ success: false }));\n rejectToken(new Error(data.error));\n return;\n }\n\n if (!data.token) {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Missing token' }));\n return;\n }\n\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ success: true }));\n resolveToken(data.token);\n } catch {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Invalid JSON' }));\n }\n });\n return;\n }\n\n res.writeHead(404);\n res.end();\n });\n\n // Listen on random port\n server.listen(0, '127.0.0.1', () => {\n const addr = server.address();\n const port = typeof addr === 'object' && addr ? addr.port : 0;\n\n // Timeout after 2 minutes\n const timeout = setTimeout(() => {\n server.close();\n rejectToken(new Error('LOGIN_TIMEOUT'));\n }, 120000);\n\n // Clean up timeout when token received\n firebaseToken\n .then(() => {\n clearTimeout(timeout);\n // Give the response time to send before closing\n setTimeout(() => server.close(), 500);\n })\n .catch(() => {\n clearTimeout(timeout);\n server.close();\n });\n\n resolveSetup({ firebaseToken, port, server });\n });\n });\n}\n\n// ── Logout Handler ─────────────────────────────────────────────\n\nasync function handleLogout(): Promise<void> {\n if (!configStore.isAuthenticated()) {\n if (isJsonOutput()) {\n printJson({ success: true, message: 'Already logged out' });\n } else {\n printInfo('Already logged out');\n }\n return;\n }\n\n configStore.clearAuth();\n configStore.delete('workspaceId');\n\n if (isJsonOutput()) {\n printJson({ success: true, message: 'Logged out successfully' });\n } else {\n printSuccess('Logged out successfully');\n }\n}\n\n// ── Status Handler ─────────────────────────────────────────────\n\nasync function handleAuthStatus(): Promise<void> {\n const authMethod = configStore.getAuthMethod();\n const baseUrl = configStore.getBaseUrl();\n const workspaceId = configStore.getWorkspaceId();\n\n if (isJsonOutput()) {\n printJson({\n authenticated: authMethod !== null,\n method: authMethod,\n apiKey: authMethod === 'api-key' ? maskString(configStore.getApiKey()!) : null,\n jwtToken: authMethod === 'jwt' ? '(set)' : null,\n workspaceId: workspaceId ?? null,\n baseUrl,\n });\n return;\n }\n\n printBlank();\n\n if (!authMethod) {\n printLabel('Status', chalk.yellow('Not authenticated'));\n printBlank();\n printInfo(\"Run 'chanl login' to authenticate via browser\");\n printInfo(\"Run 'chanl login --api-key <key>' to use an API key\");\n return;\n }\n\n printLabel('Status', chalk.green('Authenticated'));\n printLabel('Method', authMethod === 'jwt' ? 'Browser (JWT)' : 'API Key');\n\n if (authMethod === 'api-key') {\n printLabel('API Key', maskString(configStore.getApiKey()!));\n }\n\n printLabel('Base URL', baseUrl);\n\n if (workspaceId) {\n printLabel('Workspace ID', workspaceId);\n\n // Try to fetch workspace details\n try {\n const sdkConfig: { baseUrl: string; apiKey?: string; jwtToken?: string } = { baseUrl };\n if (authMethod === 'api-key') {\n sdkConfig.apiKey = configStore.getApiKey();\n } else {\n sdkConfig.jwtToken = configStore.getJwtToken();\n }\n const sdk = new ChanlSDK(sdkConfig);\n const response = await sdk.workspace.getById(workspaceId);\n if (response.success && response.data?.name) {\n printLabel('Workspace Name', response.data.name);\n }\n } catch {\n // Ignore errors fetching workspace details\n }\n }\n\n printBlank();\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { createSdk } from "../utils/sdk-factory.js";
|
|
5
|
+
import {
|
|
6
|
+
printError,
|
|
7
|
+
printInfo,
|
|
8
|
+
printBlank,
|
|
9
|
+
printLabel,
|
|
10
|
+
isJsonOutput,
|
|
11
|
+
printJson
|
|
12
|
+
} from "../utils/output.js";
|
|
13
|
+
import {
|
|
14
|
+
formatElapsed,
|
|
15
|
+
printSegment,
|
|
16
|
+
displayScorecard
|
|
17
|
+
} from "../utils/interactive.js";
|
|
18
|
+
function createCallCommand() {
|
|
19
|
+
const call = new Command("call").description("Make an outbound phone call with an AI agent").argument("<phone>", "Phone number to call (E.164 format, e.g., +15551234567)").requiredOption("-a, --agent <agentId>", "Agent ID to handle the call").option("-p, --phone-number <id>", "Source phone number ID (from your workspace)").option("-n, --customer-name <name>", "Customer name").option("-s, --scorecard <id>", "Scorecard ID to evaluate the call").option("--no-watch", "Start the call and exit immediately (no live monitoring)").option("-w, --workspace <id>", "Workspace ID override").addHelpText(
|
|
20
|
+
"after",
|
|
21
|
+
`
|
|
22
|
+
What is Call?
|
|
23
|
+
Make an outbound phone call from your AI agent to a real phone number.
|
|
24
|
+
By default, enters interactive mode showing a live transcript as the
|
|
25
|
+
call progresses, with a scorecard summary when the call ends.
|
|
26
|
+
|
|
27
|
+
Modes:
|
|
28
|
+
Interactive (default): Live transcript + scorecard when call ends.
|
|
29
|
+
Press Ctrl+C to stop monitoring (call continues).
|
|
30
|
+
|
|
31
|
+
No-watch: Start the call and exit. Use 'chanl calls get <id>'
|
|
32
|
+
to check status later.
|
|
33
|
+
|
|
34
|
+
Examples:
|
|
35
|
+
$ chanl call +15551234567 --agent ag_abc123
|
|
36
|
+
$ chanl call +15551234567 --agent ag_abc --phone-number pn_xyz
|
|
37
|
+
$ chanl call +15551234567 --agent ag_abc --no-watch
|
|
38
|
+
$ chanl call +15551234567 --agent ag_abc --scorecard sc_123
|
|
39
|
+
$ chanl call +15551234567 --agent ag_abc --json`
|
|
40
|
+
).action(handleCall);
|
|
41
|
+
return call;
|
|
42
|
+
}
|
|
43
|
+
async function handleCall(phone, options) {
|
|
44
|
+
const sdk = createSdk();
|
|
45
|
+
if (!sdk) return;
|
|
46
|
+
if (!phone.match(/^\+[1-9]\d{1,14}$/)) {
|
|
47
|
+
printError("Invalid phone number", `"${phone}" is not in E.164 format (e.g., +15551234567)`);
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const spinner = ora(`Calling ${phone}...`).start();
|
|
52
|
+
try {
|
|
53
|
+
const response = await sdk.calls.initiate({
|
|
54
|
+
agentId: options.agent,
|
|
55
|
+
phone,
|
|
56
|
+
phoneNumberId: options.phoneNumber,
|
|
57
|
+
customerName: options.customerName,
|
|
58
|
+
scorecardId: options.scorecard
|
|
59
|
+
});
|
|
60
|
+
if (!response.success || !response.data) {
|
|
61
|
+
spinner.fail("Failed to initiate call");
|
|
62
|
+
printError("Error", response.message || "Could not start the call");
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const callData = response.data;
|
|
67
|
+
const callId = callData.id;
|
|
68
|
+
spinner.succeed(`Call started: ${callId}`);
|
|
69
|
+
if (isJsonOutput()) {
|
|
70
|
+
printJson(callData);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
printBlank();
|
|
74
|
+
printLabel("Call ID", callId);
|
|
75
|
+
printLabel("Status", callData.status || "queued");
|
|
76
|
+
printLabel("Phone", phone);
|
|
77
|
+
printLabel("Agent", options.agent);
|
|
78
|
+
if (options.watch === false) {
|
|
79
|
+
printBlank();
|
|
80
|
+
printInfo(`Monitor with: chanl calls get ${callId}`);
|
|
81
|
+
printInfo(`View transcript: chanl calls transcript ${callId}`);
|
|
82
|
+
printBlank();
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
printBlank();
|
|
86
|
+
await watchCall(sdk, callId);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
spinner.fail("Failed to start call");
|
|
89
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
90
|
+
printError("Error", message);
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
async function watchCall(sdk, callId) {
|
|
95
|
+
const startTime = /* @__PURE__ */ new Date();
|
|
96
|
+
let spinner = ora({
|
|
97
|
+
text: `${chalk.red("LIVE")} \u2014 ${formatElapsed(startTime)} | Waiting for call to connect...`,
|
|
98
|
+
spinner: "dots"
|
|
99
|
+
}).start();
|
|
100
|
+
const live = sdk.calls.watch(callId);
|
|
101
|
+
const handleSigint = () => {
|
|
102
|
+
live.stop();
|
|
103
|
+
spinner.stop();
|
|
104
|
+
printBlank();
|
|
105
|
+
printInfo(`Stopped monitoring. Call continues in background.`);
|
|
106
|
+
printInfo(`Check status: chanl calls get ${callId}`);
|
|
107
|
+
printInfo(`View transcript: chanl calls transcript ${callId}`);
|
|
108
|
+
printBlank();
|
|
109
|
+
process.exit(0);
|
|
110
|
+
};
|
|
111
|
+
process.on("SIGINT", handleSigint);
|
|
112
|
+
let headerPrinted = false;
|
|
113
|
+
let scorecardReceived = false;
|
|
114
|
+
let callEnded = false;
|
|
115
|
+
live.on("status", (status) => {
|
|
116
|
+
spinner.text = `${chalk.red("LIVE")} \u2014 ${formatElapsed(startTime)} | Status: ${status}`;
|
|
117
|
+
});
|
|
118
|
+
live.on("transcript", (seg) => {
|
|
119
|
+
if (!headerPrinted) {
|
|
120
|
+
spinner.stop();
|
|
121
|
+
headerPrinted = true;
|
|
122
|
+
} else {
|
|
123
|
+
spinner.stop();
|
|
124
|
+
}
|
|
125
|
+
printSegment(seg);
|
|
126
|
+
spinner.start();
|
|
127
|
+
});
|
|
128
|
+
live.on("ended", (summary) => {
|
|
129
|
+
callEnded = true;
|
|
130
|
+
spinner.stop();
|
|
131
|
+
const statusIcon = summary.status === "ended" || summary.status === "completed" ? chalk.green("\u2713") : chalk.red("\u2717");
|
|
132
|
+
console.log(`${statusIcon} Call ${summary.status} \u2014 ${formatElapsed(startTime)}`);
|
|
133
|
+
printBlank();
|
|
134
|
+
spinner = ora("Checking for scorecard...").start();
|
|
135
|
+
});
|
|
136
|
+
live.on("scorecard", (results) => {
|
|
137
|
+
scorecardReceived = true;
|
|
138
|
+
spinner.stop();
|
|
139
|
+
displayScorecard(results);
|
|
140
|
+
});
|
|
141
|
+
live.on("timeout", () => {
|
|
142
|
+
spinner.warn("Stopped monitoring (timeout after 5 minutes)");
|
|
143
|
+
printInfo(`Check status: chanl calls get ${callId}`);
|
|
144
|
+
printBlank();
|
|
145
|
+
});
|
|
146
|
+
try {
|
|
147
|
+
await live.completed;
|
|
148
|
+
} finally {
|
|
149
|
+
process.removeListener("SIGINT", handleSigint);
|
|
150
|
+
}
|
|
151
|
+
if (callEnded) {
|
|
152
|
+
if (!scorecardReceived) {
|
|
153
|
+
spinner.stop();
|
|
154
|
+
printInfo("No scorecard results (analysis may still be processing)");
|
|
155
|
+
printInfo(`Check later: chanl calls analysis ${callId}`);
|
|
156
|
+
}
|
|
157
|
+
printBlank();
|
|
158
|
+
printInfo(`Full transcript: chanl calls transcript ${callId}`);
|
|
159
|
+
printInfo(`Call details: chanl calls get ${callId}`);
|
|
160
|
+
printBlank();
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
export {
|
|
164
|
+
createCallCommand
|
|
165
|
+
};
|
|
166
|
+
//# sourceMappingURL=call.js.map
|