retell-cli 1.0.1 → 1.1.0

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.
Files changed (3) hide show
  1. package/README.md +160 -0
  2. package/dist/index.js +2339 -141
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -25,7 +25,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
25
 
26
26
  // src/index.ts
27
27
  var import_commander = require("commander");
28
- var import_fs5 = require("fs");
28
+ var import_fs15 = require("fs");
29
29
  var import_path6 = require("path");
30
30
 
31
31
  // src/commands/login.ts
@@ -1351,15 +1351,23 @@ async function diffPromptsCommand(agentId, options) {
1351
1351
 
1352
1352
  // src/commands/agent/publish.ts
1353
1353
  async function publishAgentCommand(agentId) {
1354
+ const client = getRetellClient();
1354
1355
  try {
1355
- const client = getRetellClient();
1356
- const result = await client.agent.publish(agentId);
1356
+ await client.agent.publish(agentId);
1357
+ } catch (error) {
1358
+ if (error instanceof Error && error.message.includes("invalid json response body") && error.message.includes("Unexpected end of JSON input")) {
1359
+ } else {
1360
+ handleSdkError(error);
1361
+ }
1362
+ }
1363
+ try {
1364
+ const agent = await client.agent.retrieve(agentId);
1357
1365
  outputJson({
1358
1366
  message: "Agent published successfully",
1359
- agent_id: result?.agent_id || agentId,
1360
- agent_name: result?.agent_name || "Unknown",
1361
- version: result?.version || "Unknown",
1362
- is_published: result?.is_published ?? true,
1367
+ agent_id: agent.agent_id,
1368
+ agent_name: agent.agent_name || "Unknown",
1369
+ version: agent.version || "Unknown",
1370
+ is_published: agent.is_published ?? true,
1363
1371
  note: "Draft version incremented and ready for new changes"
1364
1372
  });
1365
1373
  } catch (error) {
@@ -1367,142 +1375,2332 @@ async function publishAgentCommand(agentId) {
1367
1375
  }
1368
1376
  }
1369
1377
 
1370
- // src/index.ts
1371
- var packageJson = JSON.parse(
1372
- (0, import_fs5.readFileSync)((0, import_path6.join)(__dirname, "../package.json"), "utf-8")
1373
- );
1374
- var program = new import_commander.Command();
1375
- program.name("retell").description("Retell AI CLI - Manage transcripts and agent prompts").version(packageJson.version, "-v, --version", "Display version number").helpOption("-h, --help", "Display help for command").option("--json", "Output as JSON (default)", true);
1376
- program.command("login").description("Authenticate with Retell AI").addHelpText("after", `
1377
- Examples:
1378
- $ retell login
1379
- # Enter your API key when prompted
1380
- # Creates .retellrc.json in current directory
1381
- `).action(async () => {
1382
- await loginCommand();
1383
- });
1384
- var transcripts = program.command("transcripts").description("Manage call transcripts");
1385
- transcripts.command("list").description("List all call transcripts").option("-l, --limit <number>", "Maximum number of calls to return (default: 50)", "50").option("--fields <fields>", "Comma-separated list of fields to return (e.g., call_id,call_status,metadata.duration)").addHelpText("after", `
1386
- Examples:
1387
- $ retell transcripts list
1388
- $ retell transcripts list --limit 100
1389
- $ retell transcripts list --fields call_id,call_status
1390
- $ retell transcripts list | jq '.[] | select(.call_status == "error")'
1391
- `).action(async (options) => {
1392
- const limit = parseInt(options.limit, 10);
1393
- if (isNaN(limit) || limit < 1) {
1394
- console.error("Error: limit must be a positive number");
1395
- process.exit(1);
1378
+ // src/services/tool-resolver.ts
1379
+ async function resolveToolsSource(agentId) {
1380
+ const client = getRetellClient();
1381
+ const agent = await client.agent.retrieve(agentId);
1382
+ if (agent.response_engine.type === "retell-llm") {
1383
+ const llm = await client.llm.retrieve(agent.response_engine.llm_id);
1384
+ const generalTools = llm.general_tools ?? [];
1385
+ const stateTools = {};
1386
+ if (llm.states) {
1387
+ for (const state of llm.states) {
1388
+ const tools2 = state.tools ?? [];
1389
+ if (tools2.length > 0) {
1390
+ stateTools[state.name] = tools2;
1391
+ }
1392
+ }
1393
+ }
1394
+ const stateToolCount = Object.values(stateTools).reduce((sum, t) => sum + t.length, 0);
1395
+ const totalCount = generalTools.length + stateToolCount;
1396
+ return {
1397
+ type: "retell-llm",
1398
+ agentId,
1399
+ agentName: agent.agent_name ?? "Unknown",
1400
+ llmId: llm.llm_id,
1401
+ generalTools,
1402
+ stateTools,
1403
+ totalCount
1404
+ };
1396
1405
  }
1397
- await listTranscriptsCommand({
1398
- limit,
1399
- fields: options.fields
1400
- });
1401
- });
1402
- transcripts.command("get <call_id>").description("Get a specific call transcript").option("--fields <fields>", "Comma-separated list of fields to return (e.g., call_id,metadata.duration,analysis)").addHelpText("after", `
1403
- Examples:
1404
- $ retell transcripts get call_abc123
1405
- $ retell transcripts get call_abc123 --fields call_id,metadata.duration
1406
- $ retell transcripts get call_abc123 | jq '.transcript_object'
1407
- `).action(async (callId, options) => {
1408
- await getTranscriptCommand(callId, {
1409
- fields: options.fields
1410
- });
1411
- });
1412
- transcripts.command("analyze <call_id>").description("Analyze a call transcript with performance metrics and insights").option("--fields <fields>", "Comma-separated list of fields to return (e.g., call_id,performance,analysis.summary)").option("--raw", "Return unmodified API response instead of enriched analysis").option("--hotspots-only", "Return only conversation hotspots/issues for troubleshooting").option("--latency-threshold <ms>", `Latency threshold in ms for hotspot detection (default: ${DEFAULT_LATENCY_THRESHOLD})`, String(DEFAULT_LATENCY_THRESHOLD)).option("--silence-threshold <ms>", `Silence threshold in ms for hotspot detection (default: ${DEFAULT_SILENCE_THRESHOLD})`, String(DEFAULT_SILENCE_THRESHOLD)).addHelpText("after", `
1413
- Examples:
1414
- $ retell transcripts analyze call_abc123
1415
- $ retell transcripts analyze call_abc123 --fields call_id,performance
1416
- $ retell transcripts analyze call_abc123 --raw
1417
- $ retell transcripts analyze call_abc123 --raw --fields call_id,transcript_object
1418
- $ retell transcripts analyze call_abc123 --hotspots-only
1419
- $ retell transcripts analyze call_abc123 --hotspots-only --latency-threshold 1500
1420
- $ retell transcripts analyze call_abc123 --hotspots-only --fields hotspots
1421
- $ retell transcripts analyze call_abc123 | jq '.performance.latency_p50_ms'
1422
- `).action(async (callId, options) => {
1423
- await analyzeTranscriptCommand(callId, {
1424
- fields: options.fields,
1425
- raw: options.raw,
1426
- hotspotsOnly: options.hotspotsOnly,
1427
- latencyThreshold: options.latencyThreshold ? parseInt(options.latencyThreshold) : void 0,
1428
- silenceThreshold: options.silenceThreshold ? parseInt(options.silenceThreshold) : void 0
1429
- });
1430
- });
1431
- transcripts.command("search").description("Search transcripts with advanced filtering").option("--status <status>", "Filter by call status (error, ended, ongoing)").option("--agent-id <id>", "Filter by agent ID").option("--since <date>", "Filter calls after this date (YYYY-MM-DD or ISO format)").option("--until <date>", "Filter calls before this date (YYYY-MM-DD or ISO format)").option("--limit <number>", "Maximum number of results (default: 50)", "50").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
1432
- Examples:
1433
- $ retell transcripts search --status error
1434
- $ retell transcripts search --agent-id agent_123 --since 2025-11-01
1435
- $ retell transcripts search --status error --limit 10
1436
- $ retell transcripts search --status error --fields call_id,agent_id,call_status
1437
- $ retell transcripts search --since 2025-11-01 --until 2025-11-15
1438
- `).action(async (options) => {
1439
- await searchTranscriptsCommand({
1440
- status: options.status,
1441
- agentId: options.agentId,
1442
- since: options.since,
1443
- until: options.until,
1444
- limit: options.limit ? Number(options.limit) : void 0,
1445
- fields: options.fields
1446
- });
1447
- });
1448
- var agents = program.command("agents").description("Manage agents");
1449
- agents.command("list").description("List all agents").option("-l, --limit <number>", "Maximum number of agents to return (default: 100)", "100").option("--fields <fields>", "Comma-separated list of fields to return (e.g., agent_id,agent_name,response_engine_type)").addHelpText("after", `
1450
- Examples:
1451
- $ retell agents list
1452
- $ retell agents list --limit 10
1453
- $ retell agents list --fields agent_id,agent_name
1454
- $ retell agents list | jq '.[] | select(.response_engine.type == "retell-llm")'
1455
- `).action(async (options) => {
1456
- const limit = parseInt(options.limit, 10);
1457
- if (isNaN(limit) || limit < 1) {
1458
- console.error("Error: limit must be a positive number");
1459
- process.exit(1);
1406
+ if (agent.response_engine.type === "conversation-flow") {
1407
+ const flow = await client.conversationFlow.retrieve(
1408
+ agent.response_engine.conversation_flow_id
1409
+ );
1410
+ const flowTools = flow.tools ?? [];
1411
+ const componentTools = {};
1412
+ if (flow.components) {
1413
+ for (const component of flow.components) {
1414
+ const tools2 = component.tools ?? [];
1415
+ if (tools2.length > 0) {
1416
+ componentTools[component.name] = tools2;
1417
+ }
1418
+ }
1419
+ }
1420
+ const componentToolCount = Object.values(componentTools).reduce((sum, t) => sum + t.length, 0);
1421
+ const totalCount = flowTools.length + componentToolCount;
1422
+ return {
1423
+ type: "conversation-flow",
1424
+ agentId,
1425
+ agentName: agent.agent_name ?? "Unknown",
1426
+ flowId: flow.conversation_flow_id,
1427
+ flowTools,
1428
+ componentTools,
1429
+ totalCount
1430
+ };
1460
1431
  }
1461
- await listAgentsCommand({
1462
- limit,
1463
- fields: options.fields
1464
- });
1465
- });
1466
- agents.command("info <agent_id>").description("Get detailed agent information").option("--fields <fields>", "Comma-separated list of fields to return (e.g., agent_name,response_engine.type,voice_config)").addHelpText("after", `
1467
- Examples:
1468
- $ retell agents info agent_123abc
1469
- $ retell agents info agent_123abc --fields agent_name,response_engine.type
1470
- $ retell agents info agent_123abc | jq '.response_engine.type'
1471
- `).action(async (agentId, options) => {
1472
- await agentInfoCommand(agentId, {
1473
- fields: options.fields
1474
- });
1475
- });
1476
- var prompts = program.command("prompts").description("Manage agent prompts");
1477
- prompts.command("pull <agent_id>").description("Download agent prompts to a local file").option("-o, --output <path>", "Output file path (default: .retell-prompts/<agent_id>.json)", ".retell-prompts").addHelpText("after", `
1478
- Examples:
1479
- $ retell prompts pull agent_123abc
1480
- $ retell prompts pull agent_123abc --output my-prompts.json
1481
- `).action(async (agentId, options) => {
1482
- await pullPromptsCommand(agentId, options);
1483
- });
1484
- prompts.command("diff <agent_id>").description("Show differences between local and remote prompts").option("-s, --source <path>", "Source directory path (default: .retell-prompts)", ".retell-prompts").option("-f, --fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
1485
- Examples:
1486
- $ retell prompts diff agent_123abc
1487
- $ retell prompts diff agent_123abc --source ./custom-prompts
1488
- $ retell prompts diff agent_123abc --fields has_changes,changes.general_prompt
1489
- `).action(async (agentId, options) => {
1490
- await diffPromptsCommand(agentId, options);
1491
- });
1492
- prompts.command("update <agent_id>").description("Update agent prompts from a local file").option("-s, --source <path>", "Source file path (default: .retell-prompts/<agent_id>.json)", ".retell-prompts").option("--dry-run", "Preview changes without applying them", false).addHelpText("after", `
1493
- Examples:
1494
- $ retell prompts update agent_123abc --source my-prompts.json --dry-run
1495
- $ retell prompts update agent_123abc --source my-prompts.json
1496
- # Remember to publish: retell agent-publish agent_123abc
1497
- `).action(async (agentId, options) => {
1498
- await updatePromptsCommand(agentId, options);
1499
- });
1500
- program.command("agent-publish <agent_id>").description("Publish a draft agent to make changes live").addHelpText("after", `
1501
- Examples:
1502
- $ retell agent-publish agent_123abc
1503
- # Run this after updating prompts to make changes live
1504
- `).action(async (agentId) => {
1505
- await publishAgentCommand(agentId);
1432
+ return {
1433
+ type: "custom-llm",
1434
+ error: "Custom LLM agents cannot be managed via API. Use the Retell dashboard to manage tools for custom LLM agents."
1435
+ };
1436
+ }
1437
+ async function findToolInLlm(agentId, toolName, stateName) {
1438
+ const source = await resolveToolsSource(agentId);
1439
+ if (source.type !== "retell-llm") {
1440
+ return null;
1441
+ }
1442
+ if (stateName) {
1443
+ const stateTools = source.stateTools[stateName];
1444
+ if (stateTools) {
1445
+ const tool = stateTools.find((t) => t.name === toolName);
1446
+ if (tool) {
1447
+ return {
1448
+ tool,
1449
+ location: { location: "state", stateName }
1450
+ };
1451
+ }
1452
+ }
1453
+ return null;
1454
+ }
1455
+ const generalTool = source.generalTools.find((t) => t.name === toolName);
1456
+ if (generalTool) {
1457
+ return {
1458
+ tool: generalTool,
1459
+ location: { location: "general" }
1460
+ };
1461
+ }
1462
+ for (const [state, tools2] of Object.entries(source.stateTools)) {
1463
+ const tool = tools2.find((t) => t.name === toolName);
1464
+ if (tool) {
1465
+ return {
1466
+ tool,
1467
+ location: { location: "state", stateName: state }
1468
+ };
1469
+ }
1470
+ }
1471
+ return null;
1472
+ }
1473
+ async function findToolInFlow(agentId, toolName, componentId) {
1474
+ const source = await resolveToolsSource(agentId);
1475
+ if (source.type !== "conversation-flow") {
1476
+ return null;
1477
+ }
1478
+ if (componentId) {
1479
+ const componentTools = source.componentTools[componentId];
1480
+ if (componentTools) {
1481
+ const tool = componentTools.find((t) => t.name === toolName);
1482
+ if (tool) {
1483
+ return {
1484
+ tool,
1485
+ location: { location: "component", componentId }
1486
+ };
1487
+ }
1488
+ }
1489
+ return null;
1490
+ }
1491
+ const flowTool = source.flowTools.find((t) => t.name === toolName);
1492
+ if (flowTool) {
1493
+ return {
1494
+ tool: flowTool,
1495
+ location: { location: "flow" }
1496
+ };
1497
+ }
1498
+ for (const [compId, tools2] of Object.entries(source.componentTools)) {
1499
+ const tool = tools2.find((t) => t.name === toolName);
1500
+ if (tool) {
1501
+ return {
1502
+ tool,
1503
+ location: { location: "component", componentId: compId }
1504
+ };
1505
+ }
1506
+ }
1507
+ return null;
1508
+ }
1509
+ async function findTool(agentId, toolName, locationHint) {
1510
+ const source = await resolveToolsSource(agentId);
1511
+ if (source.type === "custom-llm") {
1512
+ return null;
1513
+ }
1514
+ if (source.type === "retell-llm") {
1515
+ const result = await findToolInLlm(agentId, toolName, locationHint);
1516
+ if (result) {
1517
+ return {
1518
+ tool: result,
1519
+ agentName: source.agentName,
1520
+ engineType: "retell-llm"
1521
+ };
1522
+ }
1523
+ }
1524
+ if (source.type === "conversation-flow") {
1525
+ const result = await findToolInFlow(agentId, toolName, locationHint);
1526
+ if (result) {
1527
+ return {
1528
+ tool: result,
1529
+ agentName: source.agentName,
1530
+ engineType: "conversation-flow"
1531
+ };
1532
+ }
1533
+ }
1534
+ return null;
1535
+ }
1536
+ function summarizeTool(tool) {
1537
+ return {
1538
+ name: tool.name,
1539
+ type: tool.type,
1540
+ description: tool.description
1541
+ };
1542
+ }
1543
+ function getAllToolNames(source) {
1544
+ if (source.type === "custom-llm") {
1545
+ return [];
1546
+ }
1547
+ if (source.type === "retell-llm") {
1548
+ const names = source.generalTools.map((t) => t.name);
1549
+ for (const tools2 of Object.values(source.stateTools)) {
1550
+ names.push(...tools2.map((t) => t.name));
1551
+ }
1552
+ return names;
1553
+ }
1554
+ if (source.type === "conversation-flow") {
1555
+ const names = source.flowTools.map((t) => t.name);
1556
+ for (const tools2 of Object.values(source.componentTools)) {
1557
+ names.push(...tools2.map((t) => t.name));
1558
+ }
1559
+ return names;
1560
+ }
1561
+ return [];
1562
+ }
1563
+
1564
+ // src/commands/tools/list.ts
1565
+ async function listToolsCommand(agentId, options) {
1566
+ try {
1567
+ const source = await resolveToolsSource(agentId);
1568
+ if (source.type === "custom-llm") {
1569
+ outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
1570
+ return;
1571
+ }
1572
+ let output;
1573
+ if (source.type === "retell-llm") {
1574
+ if (options.state) {
1575
+ const stateTools = source.stateTools[options.state];
1576
+ if (!stateTools) {
1577
+ outputError(
1578
+ `State '${options.state}' not found. Available states: ${Object.keys(source.stateTools).join(", ") || "none"}`,
1579
+ "STATE_NOT_FOUND"
1580
+ );
1581
+ return;
1582
+ }
1583
+ output = {
1584
+ agent_id: source.agentId,
1585
+ agent_name: source.agentName,
1586
+ engine_type: "retell-llm",
1587
+ llm_id: source.llmId,
1588
+ general_tools: [],
1589
+ state_tools: {
1590
+ [options.state]: stateTools.map(summarizeTool)
1591
+ },
1592
+ total_count: stateTools.length
1593
+ };
1594
+ } else {
1595
+ const stateToolsSummary = {};
1596
+ for (const [stateName, tools2] of Object.entries(source.stateTools)) {
1597
+ stateToolsSummary[stateName] = tools2.map(summarizeTool);
1598
+ }
1599
+ output = {
1600
+ agent_id: source.agentId,
1601
+ agent_name: source.agentName,
1602
+ engine_type: "retell-llm",
1603
+ llm_id: source.llmId,
1604
+ general_tools: source.generalTools.map(summarizeTool),
1605
+ state_tools: stateToolsSummary,
1606
+ total_count: source.totalCount
1607
+ };
1608
+ }
1609
+ } else {
1610
+ if (options.component) {
1611
+ const componentTools = source.componentTools[options.component];
1612
+ if (!componentTools) {
1613
+ outputError(
1614
+ `Component '${options.component}' not found. Available components: ${Object.keys(source.componentTools).join(", ") || "none"}`,
1615
+ "COMPONENT_NOT_FOUND"
1616
+ );
1617
+ return;
1618
+ }
1619
+ output = {
1620
+ agent_id: source.agentId,
1621
+ agent_name: source.agentName,
1622
+ engine_type: "conversation-flow",
1623
+ flow_id: source.flowId,
1624
+ flow_tools: [],
1625
+ component_tools: {
1626
+ [options.component]: componentTools.map(summarizeTool)
1627
+ },
1628
+ total_count: componentTools.length
1629
+ };
1630
+ } else {
1631
+ const componentToolsSummary = {};
1632
+ for (const [compId, tools2] of Object.entries(source.componentTools)) {
1633
+ componentToolsSummary[compId] = tools2.map(summarizeTool);
1634
+ }
1635
+ output = {
1636
+ agent_id: source.agentId,
1637
+ agent_name: source.agentName,
1638
+ engine_type: "conversation-flow",
1639
+ flow_id: source.flowId,
1640
+ flow_tools: source.flowTools.map(summarizeTool),
1641
+ component_tools: componentToolsSummary,
1642
+ total_count: source.totalCount
1643
+ };
1644
+ }
1645
+ }
1646
+ if (options.fields) {
1647
+ const filtered = filterFields(output, options.fields.split(",").map((f) => f.trim()));
1648
+ outputJson(filtered);
1649
+ } else {
1650
+ outputJson(output);
1651
+ }
1652
+ } catch (error) {
1653
+ handleSdkError(error);
1654
+ }
1655
+ }
1656
+
1657
+ // src/commands/tools/get.ts
1658
+ async function getToolCommand(agentId, toolName, options) {
1659
+ try {
1660
+ const source = await resolveToolsSource(agentId);
1661
+ if (source.type === "custom-llm") {
1662
+ outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
1663
+ return;
1664
+ }
1665
+ const locationHint = source.type === "retell-llm" ? options.state : options.component;
1666
+ const result = await findTool(agentId, toolName, locationHint);
1667
+ if (!result) {
1668
+ let errorMsg = `Tool '${toolName}' not found`;
1669
+ if (locationHint) {
1670
+ errorMsg += ` in ${source.type === "retell-llm" ? "state" : "component"} '${locationHint}'`;
1671
+ }
1672
+ outputError(errorMsg, "TOOL_NOT_FOUND");
1673
+ return;
1674
+ }
1675
+ const output = {
1676
+ agent_id: agentId,
1677
+ agent_name: result.agentName,
1678
+ tool_name: toolName,
1679
+ location: result.tool.location,
1680
+ tool: result.tool.tool
1681
+ };
1682
+ if (options.fields) {
1683
+ const filtered = filterFields(output, options.fields.split(",").map((f) => f.trim()));
1684
+ outputJson(filtered);
1685
+ } else {
1686
+ outputJson(output);
1687
+ }
1688
+ } catch (error) {
1689
+ handleSdkError(error);
1690
+ }
1691
+ }
1692
+
1693
+ // src/commands/tools/add.ts
1694
+ var import_fs5 = require("fs");
1695
+ async function addToolCommand(agentId, options) {
1696
+ try {
1697
+ if (!(0, import_fs5.existsSync)(options.file)) {
1698
+ outputError(`Tool file not found: ${options.file}`, "FILE_NOT_FOUND");
1699
+ return;
1700
+ }
1701
+ let tool;
1702
+ try {
1703
+ const content = (0, import_fs5.readFileSync)(options.file, "utf-8");
1704
+ tool = JSON.parse(content);
1705
+ } catch (error) {
1706
+ if (error instanceof SyntaxError) {
1707
+ outputError(`Invalid JSON in tool file: ${error.message}`, "INVALID_JSON");
1708
+ } else {
1709
+ outputError(`Error reading tool file: ${error.message}`, "FILE_READ_ERROR");
1710
+ }
1711
+ return;
1712
+ }
1713
+ if (!tool.name) {
1714
+ outputError('Tool must have a "name" field', "INVALID_TOOL");
1715
+ return;
1716
+ }
1717
+ if (!tool.type) {
1718
+ outputError('Tool must have a "type" field', "INVALID_TOOL");
1719
+ return;
1720
+ }
1721
+ const source = await resolveToolsSource(agentId);
1722
+ if (source.type === "custom-llm") {
1723
+ outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
1724
+ return;
1725
+ }
1726
+ const existingNames = getAllToolNames(source);
1727
+ if (existingNames.includes(tool.name)) {
1728
+ outputError(
1729
+ `Tool with name '${tool.name}' already exists. Use 'retell tools update' to modify it.`,
1730
+ "DUPLICATE_TOOL"
1731
+ );
1732
+ return;
1733
+ }
1734
+ const client = getRetellClient();
1735
+ if (source.type === "retell-llm") {
1736
+ if (options.dryRun) {
1737
+ const location = options.state ? `state '${options.state}'` : "general tools";
1738
+ outputJson({
1739
+ message: "Dry run - no changes applied",
1740
+ agent_id: agentId,
1741
+ agent_name: source.agentName,
1742
+ tool_name: tool.name,
1743
+ operation: "add",
1744
+ location: options.state ? { location: "state", stateName: options.state } : { location: "general" },
1745
+ tool_preview: tool,
1746
+ would_add_to: location
1747
+ });
1748
+ return;
1749
+ }
1750
+ const llm = await client.llm.retrieve(source.llmId);
1751
+ if (options.state) {
1752
+ const states = llm.states ?? [];
1753
+ const stateIndex = states.findIndex((s) => s.name === options.state);
1754
+ if (stateIndex === -1) {
1755
+ outputError(
1756
+ `State '${options.state}' not found. Available states: ${states.map((s) => s.name).join(", ") || "none"}`,
1757
+ "STATE_NOT_FOUND"
1758
+ );
1759
+ return;
1760
+ }
1761
+ const updatedStates = [...states];
1762
+ const stateTools = [...updatedStates[stateIndex].tools ?? []];
1763
+ stateTools.push(tool);
1764
+ updatedStates[stateIndex] = { ...updatedStates[stateIndex], tools: stateTools };
1765
+ await client.llm.update(source.llmId, { states: updatedStates });
1766
+ const output = {
1767
+ message: "Tool added successfully (draft version)",
1768
+ agent_id: agentId,
1769
+ agent_name: source.agentName,
1770
+ tool_name: tool.name,
1771
+ operation: "add",
1772
+ location: { location: "state", stateName: options.state },
1773
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
1774
+ };
1775
+ outputJson(output);
1776
+ } else {
1777
+ const generalTools = [...llm.general_tools ?? []];
1778
+ generalTools.push(tool);
1779
+ await client.llm.update(source.llmId, { general_tools: generalTools });
1780
+ const output = {
1781
+ message: "Tool added successfully (draft version)",
1782
+ agent_id: agentId,
1783
+ agent_name: source.agentName,
1784
+ tool_name: tool.name,
1785
+ operation: "add",
1786
+ location: { location: "general" },
1787
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
1788
+ };
1789
+ outputJson(output);
1790
+ }
1791
+ } else {
1792
+ if (options.dryRun) {
1793
+ const location = options.component ? `component '${options.component}'` : "flow tools";
1794
+ outputJson({
1795
+ message: "Dry run - no changes applied",
1796
+ agent_id: agentId,
1797
+ agent_name: source.agentName,
1798
+ tool_name: tool.name,
1799
+ operation: "add",
1800
+ location: options.component ? { location: "component", componentId: options.component } : { location: "flow" },
1801
+ tool_preview: tool,
1802
+ would_add_to: location
1803
+ });
1804
+ return;
1805
+ }
1806
+ const flow = await client.conversationFlow.retrieve(source.flowId);
1807
+ if (options.component) {
1808
+ const components = flow.components ?? [];
1809
+ const compIndex = components.findIndex((c) => c.name === options.component);
1810
+ if (compIndex === -1) {
1811
+ outputError(
1812
+ `Component '${options.component}' not found. Available components: ${components.map((c) => c.name).join(", ") || "none"}`,
1813
+ "COMPONENT_NOT_FOUND"
1814
+ );
1815
+ return;
1816
+ }
1817
+ const updatedComponents = [...components];
1818
+ const compTools = [...updatedComponents[compIndex].tools ?? []];
1819
+ compTools.push(tool);
1820
+ updatedComponents[compIndex] = { ...updatedComponents[compIndex], tools: compTools };
1821
+ await client.conversationFlow.update(source.flowId, { components: updatedComponents });
1822
+ const output = {
1823
+ message: "Tool added successfully (draft version)",
1824
+ agent_id: agentId,
1825
+ agent_name: source.agentName,
1826
+ tool_name: tool.name,
1827
+ operation: "add",
1828
+ location: { location: "component", componentId: options.component },
1829
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
1830
+ };
1831
+ outputJson(output);
1832
+ } else {
1833
+ const flowTools = [...flow.tools ?? []];
1834
+ flowTools.push(tool);
1835
+ await client.conversationFlow.update(source.flowId, { tools: flowTools });
1836
+ const output = {
1837
+ message: "Tool added successfully (draft version)",
1838
+ agent_id: agentId,
1839
+ agent_name: source.agentName,
1840
+ tool_name: tool.name,
1841
+ operation: "add",
1842
+ location: { location: "flow" },
1843
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
1844
+ };
1845
+ outputJson(output);
1846
+ }
1847
+ }
1848
+ } catch (error) {
1849
+ if (error instanceof SyntaxError) {
1850
+ outputError(`Invalid JSON: ${error.message}`, "INVALID_JSON");
1851
+ return;
1852
+ }
1853
+ handleSdkError(error);
1854
+ }
1855
+ }
1856
+
1857
+ // src/commands/tools/update.ts
1858
+ var import_fs6 = require("fs");
1859
+ async function updateToolCommand(agentId, toolName, options) {
1860
+ try {
1861
+ if (!(0, import_fs6.existsSync)(options.file)) {
1862
+ outputError(`Tool file not found: ${options.file}`, "FILE_NOT_FOUND");
1863
+ return;
1864
+ }
1865
+ let newTool;
1866
+ try {
1867
+ const content = (0, import_fs6.readFileSync)(options.file, "utf-8");
1868
+ newTool = JSON.parse(content);
1869
+ } catch (error) {
1870
+ if (error instanceof SyntaxError) {
1871
+ outputError(`Invalid JSON in tool file: ${error.message}`, "INVALID_JSON");
1872
+ } else {
1873
+ outputError(`Error reading tool file: ${error.message}`, "FILE_READ_ERROR");
1874
+ }
1875
+ return;
1876
+ }
1877
+ if (!newTool.name) {
1878
+ outputError('Tool must have a "name" field', "INVALID_TOOL");
1879
+ return;
1880
+ }
1881
+ if (!newTool.type) {
1882
+ outputError('Tool must have a "type" field', "INVALID_TOOL");
1883
+ return;
1884
+ }
1885
+ const source = await resolveToolsSource(agentId);
1886
+ if (source.type === "custom-llm") {
1887
+ outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
1888
+ return;
1889
+ }
1890
+ const locationHint = source.type === "retell-llm" ? options.state : options.component;
1891
+ const existingTool = await findTool(agentId, toolName, locationHint);
1892
+ if (!existingTool) {
1893
+ let errorMsg = `Tool '${toolName}' not found`;
1894
+ if (locationHint) {
1895
+ errorMsg += ` in ${source.type === "retell-llm" ? "state" : "component"} '${locationHint}'`;
1896
+ }
1897
+ outputError(errorMsg, "TOOL_NOT_FOUND");
1898
+ return;
1899
+ }
1900
+ const client = getRetellClient();
1901
+ if (source.type === "retell-llm") {
1902
+ if (options.dryRun) {
1903
+ outputJson({
1904
+ message: "Dry run - no changes applied",
1905
+ agent_id: agentId,
1906
+ agent_name: source.agentName,
1907
+ tool_name: toolName,
1908
+ operation: "update",
1909
+ location: existingTool.tool.location,
1910
+ old_tool: existingTool.tool.tool,
1911
+ new_tool: newTool
1912
+ });
1913
+ return;
1914
+ }
1915
+ const llm = await client.llm.retrieve(source.llmId);
1916
+ if (existingTool.tool.location.location === "state") {
1917
+ const stateName = existingTool.tool.location.stateName;
1918
+ const states = llm.states ?? [];
1919
+ const stateIndex = states.findIndex((s) => s.name === stateName);
1920
+ if (stateIndex === -1) {
1921
+ outputError(`State '${stateName}' not found`, "STATE_NOT_FOUND");
1922
+ return;
1923
+ }
1924
+ const updatedStates = [...states];
1925
+ const stateTools = [...updatedStates[stateIndex].tools ?? []];
1926
+ const toolIndex = stateTools.findIndex((t) => t.name === toolName);
1927
+ if (toolIndex === -1) {
1928
+ outputError(`Tool '${toolName}' not found in state '${stateName}'`, "TOOL_NOT_FOUND");
1929
+ return;
1930
+ }
1931
+ stateTools[toolIndex] = newTool;
1932
+ updatedStates[stateIndex] = { ...updatedStates[stateIndex], tools: stateTools };
1933
+ await client.llm.update(source.llmId, { states: updatedStates });
1934
+ const output = {
1935
+ message: "Tool updated successfully (draft version)",
1936
+ agent_id: agentId,
1937
+ agent_name: source.agentName,
1938
+ tool_name: newTool.name,
1939
+ operation: "update",
1940
+ location: { location: "state", stateName },
1941
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
1942
+ };
1943
+ outputJson(output);
1944
+ } else {
1945
+ const generalTools = [...llm.general_tools ?? []];
1946
+ const toolIndex = generalTools.findIndex((t) => t.name === toolName);
1947
+ if (toolIndex === -1) {
1948
+ outputError(`Tool '${toolName}' not found in general tools`, "TOOL_NOT_FOUND");
1949
+ return;
1950
+ }
1951
+ generalTools[toolIndex] = newTool;
1952
+ await client.llm.update(source.llmId, { general_tools: generalTools });
1953
+ const output = {
1954
+ message: "Tool updated successfully (draft version)",
1955
+ agent_id: agentId,
1956
+ agent_name: source.agentName,
1957
+ tool_name: newTool.name,
1958
+ operation: "update",
1959
+ location: { location: "general" },
1960
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
1961
+ };
1962
+ outputJson(output);
1963
+ }
1964
+ } else {
1965
+ if (options.dryRun) {
1966
+ outputJson({
1967
+ message: "Dry run - no changes applied",
1968
+ agent_id: agentId,
1969
+ agent_name: source.agentName,
1970
+ tool_name: toolName,
1971
+ operation: "update",
1972
+ location: existingTool.tool.location,
1973
+ old_tool: existingTool.tool.tool,
1974
+ new_tool: newTool
1975
+ });
1976
+ return;
1977
+ }
1978
+ const flow = await client.conversationFlow.retrieve(source.flowId);
1979
+ if (existingTool.tool.location.location === "component") {
1980
+ const componentId = existingTool.tool.location.componentId;
1981
+ const components = flow.components ?? [];
1982
+ const compIndex = components.findIndex((c) => c.name === componentId);
1983
+ if (compIndex === -1) {
1984
+ outputError(`Component '${componentId}' not found`, "COMPONENT_NOT_FOUND");
1985
+ return;
1986
+ }
1987
+ const updatedComponents = [...components];
1988
+ const compTools = [...updatedComponents[compIndex].tools ?? []];
1989
+ const toolIndex = compTools.findIndex((t) => t.name === toolName);
1990
+ if (toolIndex === -1) {
1991
+ outputError(`Tool '${toolName}' not found in component '${componentId}'`, "TOOL_NOT_FOUND");
1992
+ return;
1993
+ }
1994
+ compTools[toolIndex] = newTool;
1995
+ updatedComponents[compIndex] = { ...updatedComponents[compIndex], tools: compTools };
1996
+ await client.conversationFlow.update(source.flowId, { components: updatedComponents });
1997
+ const output = {
1998
+ message: "Tool updated successfully (draft version)",
1999
+ agent_id: agentId,
2000
+ agent_name: source.agentName,
2001
+ tool_name: newTool.name,
2002
+ operation: "update",
2003
+ location: { location: "component", componentId },
2004
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
2005
+ };
2006
+ outputJson(output);
2007
+ } else {
2008
+ const flowTools = [...flow.tools ?? []];
2009
+ const toolIndex = flowTools.findIndex((t) => t.name === toolName);
2010
+ if (toolIndex === -1) {
2011
+ outputError(`Tool '${toolName}' not found in flow tools`, "TOOL_NOT_FOUND");
2012
+ return;
2013
+ }
2014
+ flowTools[toolIndex] = newTool;
2015
+ await client.conversationFlow.update(source.flowId, { tools: flowTools });
2016
+ const output = {
2017
+ message: "Tool updated successfully (draft version)",
2018
+ agent_id: agentId,
2019
+ agent_name: source.agentName,
2020
+ tool_name: newTool.name,
2021
+ operation: "update",
2022
+ location: { location: "flow" },
2023
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
2024
+ };
2025
+ outputJson(output);
2026
+ }
2027
+ }
2028
+ } catch (error) {
2029
+ if (error instanceof SyntaxError) {
2030
+ outputError(`Invalid JSON: ${error.message}`, "INVALID_JSON");
2031
+ return;
2032
+ }
2033
+ handleSdkError(error);
2034
+ }
2035
+ }
2036
+
2037
+ // src/commands/tools/remove.ts
2038
+ async function removeToolCommand(agentId, toolName, options) {
2039
+ try {
2040
+ const source = await resolveToolsSource(agentId);
2041
+ if (source.type === "custom-llm") {
2042
+ outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
2043
+ return;
2044
+ }
2045
+ const locationHint = source.type === "retell-llm" ? options.state : options.component;
2046
+ const existingTool = await findTool(agentId, toolName, locationHint);
2047
+ if (!existingTool) {
2048
+ let errorMsg = `Tool '${toolName}' not found`;
2049
+ if (locationHint) {
2050
+ errorMsg += ` in ${source.type === "retell-llm" ? "state" : "component"} '${locationHint}'`;
2051
+ }
2052
+ outputError(errorMsg, "TOOL_NOT_FOUND");
2053
+ return;
2054
+ }
2055
+ const client = getRetellClient();
2056
+ if (source.type === "retell-llm") {
2057
+ if (options.dryRun) {
2058
+ outputJson({
2059
+ message: "Dry run - no changes applied",
2060
+ agent_id: agentId,
2061
+ agent_name: source.agentName,
2062
+ tool_name: toolName,
2063
+ operation: "remove",
2064
+ location: existingTool.tool.location,
2065
+ tool_to_remove: existingTool.tool.tool
2066
+ });
2067
+ return;
2068
+ }
2069
+ const llm = await client.llm.retrieve(source.llmId);
2070
+ if (existingTool.tool.location.location === "state") {
2071
+ const stateName = existingTool.tool.location.stateName;
2072
+ const states = llm.states ?? [];
2073
+ const stateIndex = states.findIndex((s) => s.name === stateName);
2074
+ if (stateIndex === -1) {
2075
+ outputError(`State '${stateName}' not found`, "STATE_NOT_FOUND");
2076
+ return;
2077
+ }
2078
+ const updatedStates = [...states];
2079
+ const stateTools = [...updatedStates[stateIndex].tools ?? []];
2080
+ const toolIndex = stateTools.findIndex((t) => t.name === toolName);
2081
+ if (toolIndex === -1) {
2082
+ outputError(`Tool '${toolName}' not found in state '${stateName}'`, "TOOL_NOT_FOUND");
2083
+ return;
2084
+ }
2085
+ stateTools.splice(toolIndex, 1);
2086
+ updatedStates[stateIndex] = { ...updatedStates[stateIndex], tools: stateTools };
2087
+ await client.llm.update(source.llmId, { states: updatedStates });
2088
+ const output = {
2089
+ message: "Tool removed successfully (draft version)",
2090
+ agent_id: agentId,
2091
+ agent_name: source.agentName,
2092
+ tool_name: toolName,
2093
+ operation: "remove",
2094
+ location: { location: "state", stateName },
2095
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
2096
+ };
2097
+ outputJson(output);
2098
+ } else {
2099
+ const generalTools = [...llm.general_tools ?? []];
2100
+ const toolIndex = generalTools.findIndex((t) => t.name === toolName);
2101
+ if (toolIndex === -1) {
2102
+ outputError(`Tool '${toolName}' not found in general tools`, "TOOL_NOT_FOUND");
2103
+ return;
2104
+ }
2105
+ generalTools.splice(toolIndex, 1);
2106
+ await client.llm.update(source.llmId, { general_tools: generalTools });
2107
+ const output = {
2108
+ message: "Tool removed successfully (draft version)",
2109
+ agent_id: agentId,
2110
+ agent_name: source.agentName,
2111
+ tool_name: toolName,
2112
+ operation: "remove",
2113
+ location: { location: "general" },
2114
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
2115
+ };
2116
+ outputJson(output);
2117
+ }
2118
+ } else {
2119
+ if (options.dryRun) {
2120
+ outputJson({
2121
+ message: "Dry run - no changes applied",
2122
+ agent_id: agentId,
2123
+ agent_name: source.agentName,
2124
+ tool_name: toolName,
2125
+ operation: "remove",
2126
+ location: existingTool.tool.location,
2127
+ tool_to_remove: existingTool.tool.tool
2128
+ });
2129
+ return;
2130
+ }
2131
+ const flow = await client.conversationFlow.retrieve(source.flowId);
2132
+ if (existingTool.tool.location.location === "component") {
2133
+ const componentId = existingTool.tool.location.componentId;
2134
+ const components = flow.components ?? [];
2135
+ const compIndex = components.findIndex((c) => c.name === componentId);
2136
+ if (compIndex === -1) {
2137
+ outputError(`Component '${componentId}' not found`, "COMPONENT_NOT_FOUND");
2138
+ return;
2139
+ }
2140
+ const updatedComponents = [...components];
2141
+ const compTools = [...updatedComponents[compIndex].tools ?? []];
2142
+ const toolIndex = compTools.findIndex((t) => t.name === toolName);
2143
+ if (toolIndex === -1) {
2144
+ outputError(`Tool '${toolName}' not found in component '${componentId}'`, "TOOL_NOT_FOUND");
2145
+ return;
2146
+ }
2147
+ compTools.splice(toolIndex, 1);
2148
+ updatedComponents[compIndex] = { ...updatedComponents[compIndex], tools: compTools };
2149
+ await client.conversationFlow.update(source.flowId, { components: updatedComponents });
2150
+ const output = {
2151
+ message: "Tool removed successfully (draft version)",
2152
+ agent_id: agentId,
2153
+ agent_name: source.agentName,
2154
+ tool_name: toolName,
2155
+ operation: "remove",
2156
+ location: { location: "component", componentId },
2157
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
2158
+ };
2159
+ outputJson(output);
2160
+ } else {
2161
+ const flowTools = [...flow.tools ?? []];
2162
+ const toolIndex = flowTools.findIndex((t) => t.name === toolName);
2163
+ if (toolIndex === -1) {
2164
+ outputError(`Tool '${toolName}' not found in flow tools`, "TOOL_NOT_FOUND");
2165
+ return;
2166
+ }
2167
+ flowTools.splice(toolIndex, 1);
2168
+ await client.conversationFlow.update(source.flowId, { tools: flowTools });
2169
+ const output = {
2170
+ message: "Tool removed successfully (draft version)",
2171
+ agent_id: agentId,
2172
+ agent_name: source.agentName,
2173
+ tool_name: toolName,
2174
+ operation: "remove",
2175
+ location: { location: "flow" },
2176
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
2177
+ };
2178
+ outputJson(output);
2179
+ }
2180
+ }
2181
+ } catch (error) {
2182
+ handleSdkError(error);
2183
+ }
2184
+ }
2185
+
2186
+ // src/commands/tools/export.ts
2187
+ var import_fs7 = require("fs");
2188
+ async function exportToolsCommand(agentId, options) {
2189
+ try {
2190
+ const source = await resolveToolsSource(agentId);
2191
+ if (source.type === "custom-llm") {
2192
+ outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
2193
+ return;
2194
+ }
2195
+ let exportData;
2196
+ if (source.type === "retell-llm") {
2197
+ const stateToolsExport = {};
2198
+ for (const [stateName, tools2] of Object.entries(source.stateTools)) {
2199
+ if (tools2.length > 0) {
2200
+ stateToolsExport[stateName] = tools2;
2201
+ }
2202
+ }
2203
+ exportData = {
2204
+ agent_id: source.agentId,
2205
+ agent_name: source.agentName,
2206
+ engine_type: "retell-llm",
2207
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
2208
+ tools: {
2209
+ general: source.generalTools.length > 0 ? source.generalTools : void 0,
2210
+ states: Object.keys(stateToolsExport).length > 0 ? stateToolsExport : void 0
2211
+ },
2212
+ total_count: source.totalCount
2213
+ };
2214
+ } else {
2215
+ const componentToolsExport = {};
2216
+ for (const [compId, tools2] of Object.entries(source.componentTools)) {
2217
+ if (tools2.length > 0) {
2218
+ componentToolsExport[compId] = tools2;
2219
+ }
2220
+ }
2221
+ exportData = {
2222
+ agent_id: source.agentId,
2223
+ agent_name: source.agentName,
2224
+ engine_type: "conversation-flow",
2225
+ exported_at: (/* @__PURE__ */ new Date()).toISOString(),
2226
+ tools: {
2227
+ flow: source.flowTools.length > 0 ? source.flowTools : void 0,
2228
+ components: Object.keys(componentToolsExport).length > 0 ? componentToolsExport : void 0
2229
+ },
2230
+ total_count: source.totalCount
2231
+ };
2232
+ }
2233
+ if (options.output) {
2234
+ try {
2235
+ (0, import_fs7.writeFileSync)(options.output, JSON.stringify(exportData, null, 2), "utf-8");
2236
+ outputJson({
2237
+ message: "Tools exported successfully",
2238
+ agent_id: agentId,
2239
+ agent_name: exportData.agent_name,
2240
+ engine_type: exportData.engine_type,
2241
+ output_file: options.output,
2242
+ total_count: exportData.total_count
2243
+ });
2244
+ } catch (error) {
2245
+ if (error.code === "EACCES") {
2246
+ outputError(`Permission denied writing to: ${options.output}`, "PERMISSION_DENIED");
2247
+ } else if (error.code === "ENOSPC") {
2248
+ outputError("No space left on device", "NO_SPACE");
2249
+ } else {
2250
+ outputError(`Error writing file: ${error.message}`, "WRITE_ERROR");
2251
+ }
2252
+ }
2253
+ } else {
2254
+ outputJson(exportData);
2255
+ }
2256
+ } catch (error) {
2257
+ handleSdkError(error);
2258
+ }
2259
+ }
2260
+
2261
+ // src/commands/tools/import.ts
2262
+ var import_fs8 = require("fs");
2263
+ async function importToolsCommand(agentId, options) {
2264
+ try {
2265
+ if (!(0, import_fs8.existsSync)(options.file)) {
2266
+ outputError(`Import file not found: ${options.file}`, "FILE_NOT_FOUND");
2267
+ return;
2268
+ }
2269
+ let importData;
2270
+ try {
2271
+ const content = (0, import_fs8.readFileSync)(options.file, "utf-8");
2272
+ importData = JSON.parse(content);
2273
+ } catch (error) {
2274
+ if (error instanceof SyntaxError) {
2275
+ outputError(`Invalid JSON in import file: ${error.message}`, "INVALID_JSON");
2276
+ } else {
2277
+ outputError(`Error reading import file: ${error.message}`, "FILE_READ_ERROR");
2278
+ }
2279
+ return;
2280
+ }
2281
+ if (!importData.tools) {
2282
+ outputError('Import file must contain a "tools" object', "INVALID_IMPORT");
2283
+ return;
2284
+ }
2285
+ const source = await resolveToolsSource(agentId);
2286
+ if (source.type === "custom-llm") {
2287
+ outputError(source.error, "CUSTOM_LLM_NOT_SUPPORTED");
2288
+ return;
2289
+ }
2290
+ const client = getRetellClient();
2291
+ const existingNames = new Set(getAllToolNames(source));
2292
+ const toolsAdded = [];
2293
+ const toolsSkipped = [];
2294
+ const toolsReplaced = [];
2295
+ if (source.type === "retell-llm") {
2296
+ if (importData.tools.flow || importData.tools.components) {
2297
+ outputError(
2298
+ "Import file contains Conversation Flow tools, but agent is Retell LLM",
2299
+ "TYPE_MISMATCH"
2300
+ );
2301
+ return;
2302
+ }
2303
+ const llm = await client.llm.retrieve(source.llmId);
2304
+ let generalTools = [...llm.general_tools ?? []];
2305
+ const states = [...llm.states ?? []];
2306
+ if (importData.tools.general) {
2307
+ for (const tool of importData.tools.general) {
2308
+ if (existingNames.has(tool.name)) {
2309
+ if (options.replace) {
2310
+ const idx = generalTools.findIndex((t) => t.name === tool.name);
2311
+ if (idx !== -1) {
2312
+ generalTools[idx] = tool;
2313
+ toolsReplaced.push(tool.name);
2314
+ }
2315
+ } else {
2316
+ toolsSkipped.push(tool.name);
2317
+ }
2318
+ } else {
2319
+ generalTools.push(tool);
2320
+ toolsAdded.push(tool.name);
2321
+ existingNames.add(tool.name);
2322
+ }
2323
+ }
2324
+ }
2325
+ if (importData.tools.states) {
2326
+ for (const [stateName, tools2] of Object.entries(importData.tools.states)) {
2327
+ const stateIndex = states.findIndex((s) => s.name === stateName);
2328
+ if (stateIndex === -1) {
2329
+ outputError(
2330
+ `State '${stateName}' not found in agent. Available states: ${states.map((s) => s.name).join(", ") || "none"}`,
2331
+ "STATE_NOT_FOUND"
2332
+ );
2333
+ return;
2334
+ }
2335
+ const stateTools = [...states[stateIndex].tools ?? []];
2336
+ for (const tool of tools2) {
2337
+ if (existingNames.has(tool.name)) {
2338
+ if (options.replace) {
2339
+ const idx = stateTools.findIndex((t) => t.name === tool.name);
2340
+ if (idx !== -1) {
2341
+ stateTools[idx] = tool;
2342
+ toolsReplaced.push(`${stateName}/${tool.name}`);
2343
+ }
2344
+ } else {
2345
+ toolsSkipped.push(`${stateName}/${tool.name}`);
2346
+ }
2347
+ } else {
2348
+ stateTools.push(tool);
2349
+ toolsAdded.push(`${stateName}/${tool.name}`);
2350
+ existingNames.add(tool.name);
2351
+ }
2352
+ }
2353
+ states[stateIndex] = { ...states[stateIndex], tools: stateTools };
2354
+ }
2355
+ }
2356
+ if (options.dryRun) {
2357
+ outputJson({
2358
+ message: "Dry run - no changes applied",
2359
+ agent_id: agentId,
2360
+ agent_name: source.agentName,
2361
+ engine_type: "retell-llm",
2362
+ tools_would_add: toolsAdded,
2363
+ tools_would_replace: toolsReplaced,
2364
+ tools_would_skip: toolsSkipped,
2365
+ total_changes: toolsAdded.length + toolsReplaced.length
2366
+ });
2367
+ return;
2368
+ }
2369
+ await client.llm.update(source.llmId, {
2370
+ general_tools: generalTools,
2371
+ states
2372
+ });
2373
+ const output = {
2374
+ message: "Tools imported successfully (draft version)",
2375
+ agent_id: agentId,
2376
+ agent_name: source.agentName,
2377
+ imported_count: toolsAdded.length + toolsReplaced.length,
2378
+ tools_added: [...toolsAdded, ...toolsReplaced],
2379
+ note: toolsSkipped.length > 0 ? `Skipped ${toolsSkipped.length} existing tools. Use --replace to overwrite. Run 'retell agent-publish ${agentId}' to publish changes.` : `Run 'retell agent-publish ${agentId}' to publish changes to production`
2380
+ };
2381
+ outputJson(output);
2382
+ } else {
2383
+ if (importData.tools.general || importData.tools.states) {
2384
+ outputError(
2385
+ "Import file contains Retell LLM tools, but agent is Conversation Flow",
2386
+ "TYPE_MISMATCH"
2387
+ );
2388
+ return;
2389
+ }
2390
+ const flow = await client.conversationFlow.retrieve(source.flowId);
2391
+ let flowTools = [...flow.tools ?? []];
2392
+ const components = [...flow.components ?? []];
2393
+ if (importData.tools.flow) {
2394
+ for (const tool of importData.tools.flow) {
2395
+ if (existingNames.has(tool.name)) {
2396
+ if (options.replace) {
2397
+ const idx = flowTools.findIndex((t) => t.name === tool.name);
2398
+ if (idx !== -1) {
2399
+ flowTools[idx] = tool;
2400
+ toolsReplaced.push(tool.name);
2401
+ }
2402
+ } else {
2403
+ toolsSkipped.push(tool.name);
2404
+ }
2405
+ } else {
2406
+ flowTools.push(tool);
2407
+ toolsAdded.push(tool.name);
2408
+ existingNames.add(tool.name);
2409
+ }
2410
+ }
2411
+ }
2412
+ if (importData.tools.components) {
2413
+ for (const [compId, tools2] of Object.entries(importData.tools.components)) {
2414
+ const compIndex = components.findIndex((c) => c.name === compId);
2415
+ if (compIndex === -1) {
2416
+ outputError(
2417
+ `Component '${compId}' not found in agent. Available components: ${components.map((c) => c.name).join(", ") || "none"}`,
2418
+ "COMPONENT_NOT_FOUND"
2419
+ );
2420
+ return;
2421
+ }
2422
+ const compTools = [...components[compIndex].tools ?? []];
2423
+ for (const tool of tools2) {
2424
+ if (existingNames.has(tool.name)) {
2425
+ if (options.replace) {
2426
+ const idx = compTools.findIndex((t) => t.name === tool.name);
2427
+ if (idx !== -1) {
2428
+ compTools[idx] = tool;
2429
+ toolsReplaced.push(`${compId}/${tool.name}`);
2430
+ }
2431
+ } else {
2432
+ toolsSkipped.push(`${compId}/${tool.name}`);
2433
+ }
2434
+ } else {
2435
+ compTools.push(tool);
2436
+ toolsAdded.push(`${compId}/${tool.name}`);
2437
+ existingNames.add(tool.name);
2438
+ }
2439
+ }
2440
+ components[compIndex] = { ...components[compIndex], tools: compTools };
2441
+ }
2442
+ }
2443
+ if (options.dryRun) {
2444
+ outputJson({
2445
+ message: "Dry run - no changes applied",
2446
+ agent_id: agentId,
2447
+ agent_name: source.agentName,
2448
+ engine_type: "conversation-flow",
2449
+ tools_would_add: toolsAdded,
2450
+ tools_would_replace: toolsReplaced,
2451
+ tools_would_skip: toolsSkipped,
2452
+ total_changes: toolsAdded.length + toolsReplaced.length
2453
+ });
2454
+ return;
2455
+ }
2456
+ await client.conversationFlow.update(source.flowId, {
2457
+ tools: flowTools,
2458
+ components
2459
+ });
2460
+ const output = {
2461
+ message: "Tools imported successfully (draft version)",
2462
+ agent_id: agentId,
2463
+ agent_name: source.agentName,
2464
+ imported_count: toolsAdded.length + toolsReplaced.length,
2465
+ tools_added: [...toolsAdded, ...toolsReplaced],
2466
+ note: toolsSkipped.length > 0 ? `Skipped ${toolsSkipped.length} existing tools. Use --replace to overwrite. Run 'retell agent-publish ${agentId}' to publish changes.` : `Run 'retell agent-publish ${agentId}' to publish changes to production`
2467
+ };
2468
+ outputJson(output);
2469
+ }
2470
+ } catch (error) {
2471
+ if (error instanceof SyntaxError) {
2472
+ outputError(`Invalid JSON: ${error.message}`, "INVALID_JSON");
2473
+ return;
2474
+ }
2475
+ handleSdkError(error);
2476
+ }
2477
+ }
2478
+
2479
+ // src/services/test-api.ts
2480
+ var BASE_URL = "https://api.retellai.com";
2481
+ async function apiRequest(method, path, body) {
2482
+ const config = getConfig();
2483
+ const response = await fetch(`${BASE_URL}${path}`, {
2484
+ method,
2485
+ headers: {
2486
+ "Authorization": `Bearer ${config.apiKey}`,
2487
+ "Content-Type": "application/json"
2488
+ },
2489
+ body: body ? JSON.stringify(body) : void 0
2490
+ });
2491
+ if (!response.ok) {
2492
+ const errorBody = await response.text();
2493
+ let errorMessage = `API error: ${response.status} ${response.statusText}`;
2494
+ try {
2495
+ const errorJson = JSON.parse(errorBody);
2496
+ if (errorJson.message) {
2497
+ errorMessage = errorJson.message;
2498
+ } else if (errorJson.error) {
2499
+ errorMessage = errorJson.error;
2500
+ }
2501
+ } catch {
2502
+ if (errorBody) {
2503
+ errorMessage = errorBody;
2504
+ }
2505
+ }
2506
+ throw new Error(errorMessage);
2507
+ }
2508
+ const text = await response.text();
2509
+ if (!text) {
2510
+ return {};
2511
+ }
2512
+ return JSON.parse(text);
2513
+ }
2514
+ async function listTestCaseDefinitions(responseEngine) {
2515
+ const params = new URLSearchParams();
2516
+ if (responseEngine.type === "retell-llm") {
2517
+ params.set("type", "retell-llm");
2518
+ params.set("llm_id", responseEngine.llm_id);
2519
+ } else {
2520
+ params.set("type", "conversation-flow");
2521
+ params.set("conversation_flow_id", responseEngine.conversation_flow_id);
2522
+ }
2523
+ return apiRequest(
2524
+ "GET",
2525
+ `/list-test-case-definitions?${params.toString()}`
2526
+ );
2527
+ }
2528
+ async function getTestCaseDefinition(testCaseDefinitionId) {
2529
+ return apiRequest(
2530
+ "GET",
2531
+ `/get-test-case-definition/${testCaseDefinitionId}`
2532
+ );
2533
+ }
2534
+ async function createTestCaseDefinition(params) {
2535
+ return apiRequest(
2536
+ "POST",
2537
+ "/create-test-case-definition",
2538
+ params
2539
+ );
2540
+ }
2541
+ async function updateTestCaseDefinition(testCaseDefinitionId, params) {
2542
+ return apiRequest(
2543
+ "PUT",
2544
+ `/update-test-case-definition/${testCaseDefinitionId}`,
2545
+ params
2546
+ );
2547
+ }
2548
+ async function deleteTestCaseDefinition(testCaseDefinitionId) {
2549
+ await apiRequest(
2550
+ "DELETE",
2551
+ `/delete-test-case-definition/${testCaseDefinitionId}`
2552
+ );
2553
+ }
2554
+ async function listBatchTests(responseEngine) {
2555
+ const params = new URLSearchParams();
2556
+ if (responseEngine.type === "retell-llm") {
2557
+ params.set("type", "retell-llm");
2558
+ params.set("llm_id", responseEngine.llm_id);
2559
+ } else {
2560
+ params.set("type", "conversation-flow");
2561
+ params.set("conversation_flow_id", responseEngine.conversation_flow_id);
2562
+ }
2563
+ return apiRequest(
2564
+ "GET",
2565
+ `/list-batch-tests?${params.toString()}`
2566
+ );
2567
+ }
2568
+ async function getBatchTest(batchJobId) {
2569
+ return apiRequest(
2570
+ "GET",
2571
+ `/get-batch-test/${batchJobId}`
2572
+ );
2573
+ }
2574
+ async function createBatchTest(params) {
2575
+ return apiRequest(
2576
+ "POST",
2577
+ "/create-batch-test",
2578
+ params
2579
+ );
2580
+ }
2581
+ async function listTestRuns(batchJobId) {
2582
+ return apiRequest(
2583
+ "GET",
2584
+ `/list-test-runs/${batchJobId}`
2585
+ );
2586
+ }
2587
+ async function getTestRun(testRunId) {
2588
+ return apiRequest(
2589
+ "GET",
2590
+ `/get-test-run/${testRunId}`
2591
+ );
2592
+ }
2593
+
2594
+ // src/commands/tests/cases/list.ts
2595
+ function buildResponseEngine(options) {
2596
+ if (options.type === "retell-llm") {
2597
+ if (!options.llmId) {
2598
+ outputError("--llm-id is required when type is retell-llm", "MISSING_PARAMETER");
2599
+ return null;
2600
+ }
2601
+ return { type: "retell-llm", llm_id: options.llmId };
2602
+ } else {
2603
+ if (!options.flowId) {
2604
+ outputError("--flow-id is required when type is conversation-flow", "MISSING_PARAMETER");
2605
+ return null;
2606
+ }
2607
+ return { type: "conversation-flow", conversation_flow_id: options.flowId };
2608
+ }
2609
+ }
2610
+ async function listTestCasesCommand(options) {
2611
+ try {
2612
+ const responseEngine = buildResponseEngine(options);
2613
+ if (!responseEngine)
2614
+ return;
2615
+ const testCases = await listTestCaseDefinitions(responseEngine);
2616
+ const output = {
2617
+ response_engine: responseEngine,
2618
+ test_case_definitions: testCases || [],
2619
+ total_count: (testCases || []).length
2620
+ };
2621
+ if (options.fields) {
2622
+ const filtered = filterFields(output, options.fields.split(",").map((f) => f.trim()));
2623
+ outputJson(filtered);
2624
+ } else {
2625
+ outputJson(output);
2626
+ }
2627
+ } catch (error) {
2628
+ handleSdkError(error);
2629
+ }
2630
+ }
2631
+
2632
+ // src/commands/tests/cases/get.ts
2633
+ async function getTestCaseCommand(testCaseDefinitionId, options) {
2634
+ try {
2635
+ const testCase = await getTestCaseDefinition(testCaseDefinitionId);
2636
+ if (options.fields) {
2637
+ const filtered = filterFields(testCase, options.fields.split(",").map((f) => f.trim()));
2638
+ outputJson(filtered);
2639
+ } else {
2640
+ outputJson(testCase);
2641
+ }
2642
+ } catch (error) {
2643
+ handleSdkError(error);
2644
+ }
2645
+ }
2646
+
2647
+ // src/commands/tests/cases/create.ts
2648
+ var import_fs9 = require("fs");
2649
+ function buildResponseEngine2(options) {
2650
+ if (options.llmId && options.flowId) {
2651
+ outputError("Cannot specify both --llm-id and --flow-id", "INVALID_PARAMETERS");
2652
+ return null;
2653
+ }
2654
+ if (!options.llmId && !options.flowId) {
2655
+ outputError("Either --llm-id or --flow-id is required", "MISSING_PARAMETER");
2656
+ return null;
2657
+ }
2658
+ if (options.llmId) {
2659
+ const engine = { type: "retell-llm", llm_id: options.llmId };
2660
+ if (options.version !== void 0) {
2661
+ engine.version = options.version;
2662
+ }
2663
+ return engine;
2664
+ } else {
2665
+ const engine = { type: "conversation-flow", conversation_flow_id: options.flowId };
2666
+ if (options.version !== void 0) {
2667
+ engine.version = options.version;
2668
+ }
2669
+ return engine;
2670
+ }
2671
+ }
2672
+ async function createTestCaseCommand(options) {
2673
+ try {
2674
+ if (!(0, import_fs9.existsSync)(options.file)) {
2675
+ outputError(`Test case file not found: ${options.file}`, "FILE_NOT_FOUND");
2676
+ return;
2677
+ }
2678
+ let input;
2679
+ try {
2680
+ const content = (0, import_fs9.readFileSync)(options.file, "utf-8");
2681
+ input = JSON.parse(content);
2682
+ } catch (error) {
2683
+ if (error instanceof SyntaxError) {
2684
+ outputError(`Invalid JSON in test case file: ${error.message}`, "INVALID_JSON");
2685
+ } else {
2686
+ outputError(`Error reading test case file: ${error.message}`, "FILE_READ_ERROR");
2687
+ }
2688
+ return;
2689
+ }
2690
+ if (!input.name) {
2691
+ outputError('Test case must have a "name" field', "INVALID_INPUT");
2692
+ return;
2693
+ }
2694
+ const responseEngine = buildResponseEngine2(options);
2695
+ if (!responseEngine)
2696
+ return;
2697
+ const testCase = await createTestCaseDefinition({
2698
+ name: input.name,
2699
+ user_prompt: input.user_prompt,
2700
+ scenario: input.scenario,
2701
+ metrics: input.metrics,
2702
+ response_engine: responseEngine
2703
+ });
2704
+ const output = {
2705
+ message: "Test case definition created successfully",
2706
+ test_case_definition_id: testCase.test_case_definition_id,
2707
+ name: testCase.name,
2708
+ operation: "create",
2709
+ response_engine: responseEngine
2710
+ };
2711
+ outputJson(output);
2712
+ } catch (error) {
2713
+ if (error instanceof SyntaxError) {
2714
+ outputError(`Invalid JSON: ${error.message}`, "INVALID_JSON");
2715
+ return;
2716
+ }
2717
+ handleSdkError(error);
2718
+ }
2719
+ }
2720
+
2721
+ // src/commands/tests/cases/update.ts
2722
+ var import_fs10 = require("fs");
2723
+ async function updateTestCaseCommand(testCaseDefinitionId, options) {
2724
+ try {
2725
+ if (!(0, import_fs10.existsSync)(options.file)) {
2726
+ outputError(`Test case file not found: ${options.file}`, "FILE_NOT_FOUND");
2727
+ return;
2728
+ }
2729
+ let input;
2730
+ try {
2731
+ const content = (0, import_fs10.readFileSync)(options.file, "utf-8");
2732
+ input = JSON.parse(content);
2733
+ } catch (error) {
2734
+ if (error instanceof SyntaxError) {
2735
+ outputError(`Invalid JSON in test case file: ${error.message}`, "INVALID_JSON");
2736
+ } else {
2737
+ outputError(`Error reading test case file: ${error.message}`, "FILE_READ_ERROR");
2738
+ }
2739
+ return;
2740
+ }
2741
+ const testCase = await updateTestCaseDefinition(testCaseDefinitionId, {
2742
+ name: input.name,
2743
+ user_prompt: input.user_prompt,
2744
+ scenario: input.scenario,
2745
+ metrics: input.metrics
2746
+ });
2747
+ const output = {
2748
+ message: "Test case definition updated successfully",
2749
+ test_case_definition_id: testCase.test_case_definition_id,
2750
+ name: testCase.name,
2751
+ operation: "update",
2752
+ response_engine: testCase.response_engine
2753
+ };
2754
+ outputJson(output);
2755
+ } catch (error) {
2756
+ if (error instanceof SyntaxError) {
2757
+ outputError(`Invalid JSON: ${error.message}`, "INVALID_JSON");
2758
+ return;
2759
+ }
2760
+ handleSdkError(error);
2761
+ }
2762
+ }
2763
+
2764
+ // src/commands/tests/cases/delete.ts
2765
+ async function deleteTestCaseCommand(testCaseDefinitionId) {
2766
+ try {
2767
+ await deleteTestCaseDefinition(testCaseDefinitionId);
2768
+ outputJson({
2769
+ message: "Test case definition deleted successfully",
2770
+ test_case_definition_id: testCaseDefinitionId,
2771
+ operation: "delete"
2772
+ });
2773
+ } catch (error) {
2774
+ handleSdkError(error);
2775
+ }
2776
+ }
2777
+
2778
+ // src/commands/tests/batch/list.ts
2779
+ function buildResponseEngine3(options) {
2780
+ if (options.type === "retell-llm") {
2781
+ if (!options.llmId) {
2782
+ outputError("--llm-id is required when type is retell-llm", "MISSING_PARAMETER");
2783
+ return null;
2784
+ }
2785
+ return { type: "retell-llm", llm_id: options.llmId };
2786
+ } else {
2787
+ if (!options.flowId) {
2788
+ outputError("--flow-id is required when type is conversation-flow", "MISSING_PARAMETER");
2789
+ return null;
2790
+ }
2791
+ return { type: "conversation-flow", conversation_flow_id: options.flowId };
2792
+ }
2793
+ }
2794
+ async function listBatchTestsCommand(options) {
2795
+ try {
2796
+ const responseEngine = buildResponseEngine3(options);
2797
+ if (!responseEngine)
2798
+ return;
2799
+ const batchTests = await listBatchTests(responseEngine);
2800
+ const output = {
2801
+ response_engine: responseEngine,
2802
+ batch_tests: batchTests || [],
2803
+ total_count: (batchTests || []).length
2804
+ };
2805
+ if (options.fields) {
2806
+ const filtered = filterFields(output, options.fields.split(",").map((f) => f.trim()));
2807
+ outputJson(filtered);
2808
+ } else {
2809
+ outputJson(output);
2810
+ }
2811
+ } catch (error) {
2812
+ handleSdkError(error);
2813
+ }
2814
+ }
2815
+
2816
+ // src/commands/tests/batch/get.ts
2817
+ async function getBatchTestCommand(batchJobId, options) {
2818
+ try {
2819
+ const batchTest = await getBatchTest(batchJobId);
2820
+ if (options.fields) {
2821
+ const filtered = filterFields(batchTest, options.fields.split(",").map((f) => f.trim()));
2822
+ outputJson(filtered);
2823
+ } else {
2824
+ outputJson(batchTest);
2825
+ }
2826
+ } catch (error) {
2827
+ handleSdkError(error);
2828
+ }
2829
+ }
2830
+
2831
+ // src/commands/tests/batch/create.ts
2832
+ function buildResponseEngine4(options) {
2833
+ if (options.llmId && options.flowId) {
2834
+ outputError("Cannot specify both --llm-id and --flow-id", "INVALID_PARAMETERS");
2835
+ return null;
2836
+ }
2837
+ if (!options.llmId && !options.flowId) {
2838
+ outputError("Either --llm-id or --flow-id is required", "MISSING_PARAMETER");
2839
+ return null;
2840
+ }
2841
+ if (options.llmId) {
2842
+ const engine = { type: "retell-llm", llm_id: options.llmId };
2843
+ if (options.version !== void 0) {
2844
+ engine.version = options.version;
2845
+ }
2846
+ return engine;
2847
+ } else {
2848
+ const engine = { type: "conversation-flow", conversation_flow_id: options.flowId };
2849
+ if (options.version !== void 0) {
2850
+ engine.version = options.version;
2851
+ }
2852
+ return engine;
2853
+ }
2854
+ }
2855
+ async function createBatchTestCommand(options) {
2856
+ try {
2857
+ const responseEngine = buildResponseEngine4(options);
2858
+ if (!responseEngine)
2859
+ return;
2860
+ const testCaseDefinitionIds = options.cases.split(",").map((id) => id.trim()).filter(Boolean);
2861
+ if (testCaseDefinitionIds.length === 0) {
2862
+ outputError("At least one test case definition ID is required", "MISSING_PARAMETER");
2863
+ return;
2864
+ }
2865
+ const batchTest = await createBatchTest({
2866
+ response_engine: responseEngine,
2867
+ test_case_definition_ids: testCaseDefinitionIds
2868
+ });
2869
+ const output = {
2870
+ message: "Batch test created successfully",
2871
+ test_case_batch_job_id: batchTest.test_case_batch_job_id,
2872
+ status: batchTest.status,
2873
+ response_engine: responseEngine,
2874
+ test_case_definition_ids: testCaseDefinitionIds
2875
+ };
2876
+ outputJson(output);
2877
+ } catch (error) {
2878
+ handleSdkError(error);
2879
+ }
2880
+ }
2881
+
2882
+ // src/commands/tests/runs/list.ts
2883
+ async function listTestRunsCommand(batchJobId, options) {
2884
+ try {
2885
+ const testRuns = await listTestRuns(batchJobId);
2886
+ const output = {
2887
+ batch_job_id: batchJobId,
2888
+ test_runs: testRuns || [],
2889
+ total_count: (testRuns || []).length
2890
+ };
2891
+ if (options.fields) {
2892
+ const filtered = filterFields(output, options.fields.split(",").map((f) => f.trim()));
2893
+ outputJson(filtered);
2894
+ } else {
2895
+ outputJson(output);
2896
+ }
2897
+ } catch (error) {
2898
+ handleSdkError(error);
2899
+ }
2900
+ }
2901
+
2902
+ // src/commands/tests/runs/get.ts
2903
+ async function getTestRunCommand(testRunId, options) {
2904
+ try {
2905
+ const testRun = await getTestRun(testRunId);
2906
+ if (options.fields) {
2907
+ const filtered = filterFields(testRun, options.fields.split(",").map((f) => f.trim()));
2908
+ outputJson(filtered);
2909
+ } else {
2910
+ outputJson(testRun);
2911
+ }
2912
+ } catch (error) {
2913
+ handleSdkError(error);
2914
+ }
2915
+ }
2916
+
2917
+ // src/commands/kb/list.ts
2918
+ async function listKnowledgeBasesCommand(options) {
2919
+ try {
2920
+ const client = getRetellClient();
2921
+ const knowledgeBases = await client.knowledgeBase.list();
2922
+ const output = options.fields ? filterFields(knowledgeBases, options.fields.split(",").map((f) => f.trim())) : knowledgeBases;
2923
+ outputJson(output);
2924
+ } catch (error) {
2925
+ handleSdkError(error);
2926
+ }
2927
+ }
2928
+
2929
+ // src/commands/kb/get.ts
2930
+ async function getKnowledgeBaseCommand(knowledgeBaseId, options) {
2931
+ try {
2932
+ const client = getRetellClient();
2933
+ const knowledgeBase = await client.knowledgeBase.retrieve(knowledgeBaseId);
2934
+ const output = options.fields ? filterFields(knowledgeBase, options.fields.split(",").map((f) => f.trim())) : knowledgeBase;
2935
+ outputJson(output);
2936
+ } catch (error) {
2937
+ handleSdkError(error);
2938
+ }
2939
+ }
2940
+
2941
+ // src/commands/kb/create.ts
2942
+ var import_fs11 = require("fs");
2943
+ async function createKnowledgeBaseCommand(options) {
2944
+ try {
2945
+ if (options.name.length > 40) {
2946
+ outputError("Knowledge base name must be 40 characters or less", "INVALID_NAME");
2947
+ return;
2948
+ }
2949
+ const createParams = {
2950
+ knowledge_base_name: options.name
2951
+ };
2952
+ if (options.autoRefresh) {
2953
+ createParams.enable_auto_refresh = true;
2954
+ }
2955
+ if (options.urls) {
2956
+ const urls = options.urls.split(",").map((u) => u.trim()).filter((u) => u.length > 0);
2957
+ if (urls.length > 0) {
2958
+ createParams.knowledge_base_urls = urls;
2959
+ }
2960
+ }
2961
+ if (options.texts) {
2962
+ if (!(0, import_fs11.existsSync)(options.texts)) {
2963
+ outputError(`Texts file not found: ${options.texts}`, "FILE_NOT_FOUND");
2964
+ return;
2965
+ }
2966
+ try {
2967
+ const content = (0, import_fs11.readFileSync)(options.texts, "utf-8");
2968
+ const textsData = JSON.parse(content);
2969
+ if (!Array.isArray(textsData)) {
2970
+ outputError("Texts file must contain an array of { title, text } objects", "INVALID_TEXTS");
2971
+ return;
2972
+ }
2973
+ const texts = textsData.map((entry, index) => {
2974
+ if (typeof entry !== "object" || entry === null) {
2975
+ throw new Error(`Entry at index ${index} must be an object`);
2976
+ }
2977
+ const e = entry;
2978
+ if (typeof e.title !== "string" || typeof e.text !== "string") {
2979
+ throw new Error(`Entry at index ${index} must have "title" and "text" string fields`);
2980
+ }
2981
+ return { title: e.title, text: e.text };
2982
+ });
2983
+ if (texts.length > 0) {
2984
+ createParams.knowledge_base_texts = texts;
2985
+ }
2986
+ } catch (error) {
2987
+ if (error instanceof SyntaxError) {
2988
+ outputError(`Invalid JSON in texts file: ${error.message}`, "INVALID_JSON");
2989
+ } else if (error instanceof Error) {
2990
+ outputError(`Error parsing texts file: ${error.message}`, "INVALID_TEXTS");
2991
+ }
2992
+ return;
2993
+ }
2994
+ }
2995
+ const client = getRetellClient();
2996
+ const knowledgeBase = await client.knowledgeBase.create(createParams);
2997
+ const output = {
2998
+ message: "Knowledge base created successfully",
2999
+ knowledge_base_id: knowledgeBase.knowledge_base_id,
3000
+ knowledge_base_name: knowledgeBase.knowledge_base_name,
3001
+ operation: "create"
3002
+ };
3003
+ outputJson({
3004
+ ...output,
3005
+ status: knowledgeBase.status,
3006
+ sources_count: knowledgeBase.knowledge_base_sources?.length ?? 0
3007
+ });
3008
+ } catch (error) {
3009
+ handleSdkError(error);
3010
+ }
3011
+ }
3012
+
3013
+ // src/commands/kb/delete.ts
3014
+ async function deleteKnowledgeBaseCommand(knowledgeBaseId) {
3015
+ try {
3016
+ const client = getRetellClient();
3017
+ await client.knowledgeBase.delete(knowledgeBaseId);
3018
+ const output = {
3019
+ message: "Knowledge base deleted successfully",
3020
+ knowledge_base_id: knowledgeBaseId,
3021
+ operation: "delete"
3022
+ };
3023
+ outputJson(output);
3024
+ } catch (error) {
3025
+ handleSdkError(error);
3026
+ }
3027
+ }
3028
+
3029
+ // src/commands/kb/sources/add.ts
3030
+ var import_fs12 = require("fs");
3031
+ async function addKnowledgeBaseSourcesCommand(knowledgeBaseId, options) {
3032
+ try {
3033
+ if (!options.urls && !options.texts) {
3034
+ outputError("At least one of --urls or --texts must be provided", "MISSING_SOURCES");
3035
+ return;
3036
+ }
3037
+ const addParams = {};
3038
+ if (options.urls) {
3039
+ const urls = options.urls.split(",").map((u) => u.trim()).filter((u) => u.length > 0);
3040
+ if (urls.length > 0) {
3041
+ addParams.knowledge_base_urls = urls;
3042
+ }
3043
+ }
3044
+ if (options.texts) {
3045
+ if (!(0, import_fs12.existsSync)(options.texts)) {
3046
+ outputError(`Texts file not found: ${options.texts}`, "FILE_NOT_FOUND");
3047
+ return;
3048
+ }
3049
+ try {
3050
+ const content = (0, import_fs12.readFileSync)(options.texts, "utf-8");
3051
+ const textsData = JSON.parse(content);
3052
+ if (!Array.isArray(textsData)) {
3053
+ outputError("Texts file must contain an array of { title, text } objects", "INVALID_TEXTS");
3054
+ return;
3055
+ }
3056
+ const texts = textsData.map((entry, index) => {
3057
+ if (typeof entry !== "object" || entry === null) {
3058
+ throw new Error(`Entry at index ${index} must be an object`);
3059
+ }
3060
+ const e = entry;
3061
+ if (typeof e.title !== "string" || typeof e.text !== "string") {
3062
+ throw new Error(`Entry at index ${index} must have "title" and "text" string fields`);
3063
+ }
3064
+ return { title: e.title, text: e.text };
3065
+ });
3066
+ if (texts.length > 0) {
3067
+ addParams.knowledge_base_texts = texts;
3068
+ }
3069
+ } catch (error) {
3070
+ if (error instanceof SyntaxError) {
3071
+ outputError(`Invalid JSON in texts file: ${error.message}`, "INVALID_JSON");
3072
+ } else if (error instanceof Error) {
3073
+ outputError(`Error parsing texts file: ${error.message}`, "INVALID_TEXTS");
3074
+ }
3075
+ return;
3076
+ }
3077
+ }
3078
+ const client = getRetellClient();
3079
+ const knowledgeBase = await client.knowledgeBase.addSources(knowledgeBaseId, addParams);
3080
+ const output = {
3081
+ message: "Sources added successfully",
3082
+ knowledge_base_id: knowledgeBase.knowledge_base_id,
3083
+ knowledge_base_name: knowledgeBase.knowledge_base_name,
3084
+ operation: "add_sources"
3085
+ };
3086
+ outputJson({
3087
+ ...output,
3088
+ status: knowledgeBase.status,
3089
+ sources_count: knowledgeBase.knowledge_base_sources?.length ?? 0
3090
+ });
3091
+ } catch (error) {
3092
+ handleSdkError(error);
3093
+ }
3094
+ }
3095
+
3096
+ // src/commands/kb/sources/delete.ts
3097
+ async function deleteKnowledgeBaseSourceCommand(knowledgeBaseId, sourceId) {
3098
+ try {
3099
+ const client = getRetellClient();
3100
+ const knowledgeBase = await client.knowledgeBase.deleteSource(knowledgeBaseId, sourceId);
3101
+ const output = {
3102
+ message: "Source deleted successfully",
3103
+ knowledge_base_id: knowledgeBase.knowledge_base_id,
3104
+ knowledge_base_name: knowledgeBase.knowledge_base_name,
3105
+ operation: "delete_source"
3106
+ };
3107
+ outputJson({
3108
+ ...output,
3109
+ status: knowledgeBase.status,
3110
+ sources_count: knowledgeBase.knowledge_base_sources?.length ?? 0
3111
+ });
3112
+ } catch (error) {
3113
+ handleSdkError(error);
3114
+ }
3115
+ }
3116
+
3117
+ // src/commands/flows/list.ts
3118
+ async function listFlowsCommand(options) {
3119
+ try {
3120
+ const client = getRetellClient();
3121
+ const flows2 = await client.conversationFlow.list({
3122
+ limit: options.limit || 100
3123
+ });
3124
+ const output = options.fields ? filterFields(flows2, options.fields.split(",").map((f) => f.trim())) : flows2;
3125
+ outputJson(output);
3126
+ } catch (error) {
3127
+ handleSdkError(error);
3128
+ }
3129
+ }
3130
+
3131
+ // src/commands/flows/get.ts
3132
+ async function getFlowCommand(conversationFlowId, options) {
3133
+ try {
3134
+ const client = getRetellClient();
3135
+ const retrieveOptions = {};
3136
+ if (options.version !== void 0) {
3137
+ retrieveOptions.version = options.version;
3138
+ }
3139
+ const flow = await client.conversationFlow.retrieve(conversationFlowId, retrieveOptions);
3140
+ const output = options.fields ? filterFields(flow, options.fields.split(",").map((f) => f.trim())) : flow;
3141
+ outputJson(output);
3142
+ } catch (error) {
3143
+ handleSdkError(error);
3144
+ }
3145
+ }
3146
+
3147
+ // src/commands/flows/create.ts
3148
+ var import_fs13 = require("fs");
3149
+ async function createFlowCommand(options) {
3150
+ try {
3151
+ if (!(0, import_fs13.existsSync)(options.file)) {
3152
+ outputError(`Flow file not found: ${options.file}`, "FILE_NOT_FOUND");
3153
+ return;
3154
+ }
3155
+ let flowConfig;
3156
+ try {
3157
+ const content = (0, import_fs13.readFileSync)(options.file, "utf-8");
3158
+ flowConfig = JSON.parse(content);
3159
+ } catch (error) {
3160
+ if (error instanceof SyntaxError) {
3161
+ outputError(`Invalid JSON in flow file: ${error.message}`, "INVALID_JSON");
3162
+ } else if (error instanceof Error) {
3163
+ outputError(`Error reading flow file: ${error.message}`, "FILE_READ_ERROR");
3164
+ }
3165
+ return;
3166
+ }
3167
+ if (!flowConfig.start_speaker) {
3168
+ outputError('Flow configuration must have a "start_speaker" field (user or agent)', "INVALID_FLOW");
3169
+ return;
3170
+ }
3171
+ const client = getRetellClient();
3172
+ const flow = await client.conversationFlow.create(flowConfig);
3173
+ const output = {
3174
+ message: "Conversation flow created successfully",
3175
+ conversation_flow_id: flow.conversation_flow_id,
3176
+ version: flow.version,
3177
+ operation: "create"
3178
+ };
3179
+ outputJson(output);
3180
+ } catch (error) {
3181
+ handleSdkError(error);
3182
+ }
3183
+ }
3184
+
3185
+ // src/commands/flows/update.ts
3186
+ var import_fs14 = require("fs");
3187
+ async function updateFlowCommand(conversationFlowId, options) {
3188
+ try {
3189
+ if (!(0, import_fs14.existsSync)(options.file)) {
3190
+ outputError(`Flow file not found: ${options.file}`, "FILE_NOT_FOUND");
3191
+ return;
3192
+ }
3193
+ let flowUpdates;
3194
+ try {
3195
+ const content = (0, import_fs14.readFileSync)(options.file, "utf-8");
3196
+ flowUpdates = JSON.parse(content);
3197
+ } catch (error) {
3198
+ if (error instanceof SyntaxError) {
3199
+ outputError(`Invalid JSON in flow file: ${error.message}`, "INVALID_JSON");
3200
+ } else if (error instanceof Error) {
3201
+ outputError(`Error reading flow file: ${error.message}`, "FILE_READ_ERROR");
3202
+ }
3203
+ return;
3204
+ }
3205
+ if (options.version !== void 0) {
3206
+ flowUpdates.version = options.version;
3207
+ }
3208
+ const client = getRetellClient();
3209
+ const flow = await client.conversationFlow.update(conversationFlowId, flowUpdates);
3210
+ const output = {
3211
+ message: "Conversation flow updated successfully",
3212
+ conversation_flow_id: flow.conversation_flow_id,
3213
+ version: flow.version,
3214
+ operation: "update"
3215
+ };
3216
+ outputJson(output);
3217
+ } catch (error) {
3218
+ handleSdkError(error);
3219
+ }
3220
+ }
3221
+
3222
+ // src/commands/flows/delete.ts
3223
+ async function deleteFlowCommand(conversationFlowId) {
3224
+ try {
3225
+ const client = getRetellClient();
3226
+ await client.conversationFlow.delete(conversationFlowId);
3227
+ const output = {
3228
+ message: "Conversation flow deleted successfully",
3229
+ conversation_flow_id: conversationFlowId,
3230
+ operation: "delete"
3231
+ };
3232
+ outputJson(output);
3233
+ } catch (error) {
3234
+ handleSdkError(error);
3235
+ }
3236
+ }
3237
+
3238
+ // src/index.ts
3239
+ var packageJson = JSON.parse(
3240
+ (0, import_fs15.readFileSync)((0, import_path6.join)(__dirname, "../package.json"), "utf-8")
3241
+ );
3242
+ var program = new import_commander.Command();
3243
+ program.name("retell").description("Retell AI CLI - Manage transcripts and agent prompts").version(packageJson.version, "-v, --version", "Display version number").helpOption("-h, --help", "Display help for command").option("--json", "Output as JSON (default)", true);
3244
+ program.command("login").description("Authenticate with Retell AI").addHelpText("after", `
3245
+ Examples:
3246
+ $ retell login
3247
+ # Enter your API key when prompted
3248
+ # Creates .retellrc.json in current directory
3249
+ `).action(async () => {
3250
+ await loginCommand();
3251
+ });
3252
+ var transcripts = program.command("transcripts").description("Manage call transcripts");
3253
+ transcripts.command("list").description("List all call transcripts").option("-l, --limit <number>", "Maximum number of calls to return (default: 50)", "50").option("--fields <fields>", "Comma-separated list of fields to return (e.g., call_id,call_status,metadata.duration)").addHelpText("after", `
3254
+ Examples:
3255
+ $ retell transcripts list
3256
+ $ retell transcripts list --limit 100
3257
+ $ retell transcripts list --fields call_id,call_status
3258
+ $ retell transcripts list | jq '.[] | select(.call_status == "error")'
3259
+ `).action(async (options) => {
3260
+ const limit = parseInt(options.limit, 10);
3261
+ if (isNaN(limit) || limit < 1) {
3262
+ console.error("Error: limit must be a positive number");
3263
+ process.exit(1);
3264
+ }
3265
+ await listTranscriptsCommand({
3266
+ limit,
3267
+ fields: options.fields
3268
+ });
3269
+ });
3270
+ transcripts.command("get <call_id>").description("Get a specific call transcript").option("--fields <fields>", "Comma-separated list of fields to return (e.g., call_id,metadata.duration,analysis)").addHelpText("after", `
3271
+ Examples:
3272
+ $ retell transcripts get call_abc123
3273
+ $ retell transcripts get call_abc123 --fields call_id,metadata.duration
3274
+ $ retell transcripts get call_abc123 | jq '.transcript_object'
3275
+ `).action(async (callId, options) => {
3276
+ await getTranscriptCommand(callId, {
3277
+ fields: options.fields
3278
+ });
3279
+ });
3280
+ transcripts.command("analyze <call_id>").description("Analyze a call transcript with performance metrics and insights").option("--fields <fields>", "Comma-separated list of fields to return (e.g., call_id,performance,analysis.summary)").option("--raw", "Return unmodified API response instead of enriched analysis").option("--hotspots-only", "Return only conversation hotspots/issues for troubleshooting").option("--latency-threshold <ms>", `Latency threshold in ms for hotspot detection (default: ${DEFAULT_LATENCY_THRESHOLD})`, String(DEFAULT_LATENCY_THRESHOLD)).option("--silence-threshold <ms>", `Silence threshold in ms for hotspot detection (default: ${DEFAULT_SILENCE_THRESHOLD})`, String(DEFAULT_SILENCE_THRESHOLD)).addHelpText("after", `
3281
+ Examples:
3282
+ $ retell transcripts analyze call_abc123
3283
+ $ retell transcripts analyze call_abc123 --fields call_id,performance
3284
+ $ retell transcripts analyze call_abc123 --raw
3285
+ $ retell transcripts analyze call_abc123 --raw --fields call_id,transcript_object
3286
+ $ retell transcripts analyze call_abc123 --hotspots-only
3287
+ $ retell transcripts analyze call_abc123 --hotspots-only --latency-threshold 1500
3288
+ $ retell transcripts analyze call_abc123 --hotspots-only --fields hotspots
3289
+ $ retell transcripts analyze call_abc123 | jq '.performance.latency_p50_ms'
3290
+ `).action(async (callId, options) => {
3291
+ await analyzeTranscriptCommand(callId, {
3292
+ fields: options.fields,
3293
+ raw: options.raw,
3294
+ hotspotsOnly: options.hotspotsOnly,
3295
+ latencyThreshold: options.latencyThreshold ? parseInt(options.latencyThreshold) : void 0,
3296
+ silenceThreshold: options.silenceThreshold ? parseInt(options.silenceThreshold) : void 0
3297
+ });
3298
+ });
3299
+ transcripts.command("search").description("Search transcripts with advanced filtering").option("--status <status>", "Filter by call status (error, ended, ongoing)").option("--agent-id <id>", "Filter by agent ID").option("--since <date>", "Filter calls after this date (YYYY-MM-DD or ISO format)").option("--until <date>", "Filter calls before this date (YYYY-MM-DD or ISO format)").option("--limit <number>", "Maximum number of results (default: 50)", "50").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3300
+ Examples:
3301
+ $ retell transcripts search --status error
3302
+ $ retell transcripts search --agent-id agent_123 --since 2025-11-01
3303
+ $ retell transcripts search --status error --limit 10
3304
+ $ retell transcripts search --status error --fields call_id,agent_id,call_status
3305
+ $ retell transcripts search --since 2025-11-01 --until 2025-11-15
3306
+ `).action(async (options) => {
3307
+ await searchTranscriptsCommand({
3308
+ status: options.status,
3309
+ agentId: options.agentId,
3310
+ since: options.since,
3311
+ until: options.until,
3312
+ limit: options.limit ? Number(options.limit) : void 0,
3313
+ fields: options.fields
3314
+ });
3315
+ });
3316
+ var agents = program.command("agents").description("Manage agents");
3317
+ agents.command("list").description("List all agents").option("-l, --limit <number>", "Maximum number of agents to return (default: 100)", "100").option("--fields <fields>", "Comma-separated list of fields to return (e.g., agent_id,agent_name,response_engine_type)").addHelpText("after", `
3318
+ Examples:
3319
+ $ retell agents list
3320
+ $ retell agents list --limit 10
3321
+ $ retell agents list --fields agent_id,agent_name
3322
+ $ retell agents list | jq '.[] | select(.response_engine.type == "retell-llm")'
3323
+ `).action(async (options) => {
3324
+ const limit = parseInt(options.limit, 10);
3325
+ if (isNaN(limit) || limit < 1) {
3326
+ console.error("Error: limit must be a positive number");
3327
+ process.exit(1);
3328
+ }
3329
+ await listAgentsCommand({
3330
+ limit,
3331
+ fields: options.fields
3332
+ });
3333
+ });
3334
+ agents.command("info <agent_id>").description("Get detailed agent information").option("--fields <fields>", "Comma-separated list of fields to return (e.g., agent_name,response_engine.type,voice_config)").addHelpText("after", `
3335
+ Examples:
3336
+ $ retell agents info agent_123abc
3337
+ $ retell agents info agent_123abc --fields agent_name,response_engine.type
3338
+ $ retell agents info agent_123abc | jq '.response_engine.type'
3339
+ `).action(async (agentId, options) => {
3340
+ await agentInfoCommand(agentId, {
3341
+ fields: options.fields
3342
+ });
3343
+ });
3344
+ var prompts = program.command("prompts").description("Manage agent prompts");
3345
+ prompts.command("pull <agent_id>").description("Download agent prompts to a local file").option("-o, --output <path>", "Output file path (default: .retell-prompts/<agent_id>.json)", ".retell-prompts").addHelpText("after", `
3346
+ Examples:
3347
+ $ retell prompts pull agent_123abc
3348
+ $ retell prompts pull agent_123abc --output my-prompts.json
3349
+ `).action(async (agentId, options) => {
3350
+ await pullPromptsCommand(agentId, options);
3351
+ });
3352
+ prompts.command("diff <agent_id>").description("Show differences between local and remote prompts").option("-s, --source <path>", "Source directory path (default: .retell-prompts)", ".retell-prompts").option("-f, --fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3353
+ Examples:
3354
+ $ retell prompts diff agent_123abc
3355
+ $ retell prompts diff agent_123abc --source ./custom-prompts
3356
+ $ retell prompts diff agent_123abc --fields has_changes,changes.general_prompt
3357
+ `).action(async (agentId, options) => {
3358
+ await diffPromptsCommand(agentId, options);
3359
+ });
3360
+ prompts.command("update <agent_id>").description("Update agent prompts from a local file").option("-s, --source <path>", "Source file path (default: .retell-prompts/<agent_id>.json)", ".retell-prompts").option("--dry-run", "Preview changes without applying them", false).addHelpText("after", `
3361
+ Examples:
3362
+ $ retell prompts update agent_123abc --source my-prompts.json --dry-run
3363
+ $ retell prompts update agent_123abc --source my-prompts.json
3364
+ # Remember to publish: retell agent-publish agent_123abc
3365
+ `).action(async (agentId, options) => {
3366
+ await updatePromptsCommand(agentId, options);
3367
+ });
3368
+ program.command("agent-publish <agent_id>").description("Publish a draft agent to make changes live").addHelpText("after", `
3369
+ Examples:
3370
+ $ retell agent-publish agent_123abc
3371
+ # Run this after updating prompts to make changes live
3372
+ `).action(async (agentId) => {
3373
+ await publishAgentCommand(agentId);
3374
+ });
3375
+ var tools = program.command("tools").description("Manage agent tools (custom functions, webhooks, etc.)");
3376
+ tools.command("list <agent_id>").description("List all tools configured for an agent").option("--state <name>", "Filter by state name (Retell LLM only)").option("--component <id>", "Filter by component ID (Conversation Flow only)").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3377
+ Examples:
3378
+ $ retell tools list agent_123abc
3379
+ $ retell tools list agent_123abc --state greeting
3380
+ $ retell tools list agent_123abc --fields total_count,general_tools
3381
+ `).action(async (agentId, options) => {
3382
+ await listToolsCommand(agentId, {
3383
+ state: options.state,
3384
+ component: options.component,
3385
+ fields: options.fields
3386
+ });
3387
+ });
3388
+ tools.command("get <agent_id> <tool_name>").description("Get detailed information about a specific tool").option("--state <name>", "State name to search within (Retell LLM only)").option("--component <id>", "Component ID to search within (Conversation Flow only)").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3389
+ Examples:
3390
+ $ retell tools get agent_123abc lookup_customer
3391
+ $ retell tools get agent_123abc book_cal --state booking
3392
+ $ retell tools get agent_123abc my_tool --fields tool.name,tool.type
3393
+ `).action(async (agentId, toolName, options) => {
3394
+ await getToolCommand(agentId, toolName, {
3395
+ state: options.state,
3396
+ component: options.component,
3397
+ fields: options.fields
3398
+ });
3399
+ });
3400
+ tools.command("add <agent_id>").description("Add a new tool to an agent").requiredOption("-f, --file <path>", "Path to JSON file containing tool definition").option("--state <name>", "Add to specific state (Retell LLM only)").option("--component <id>", "Add to specific component (Conversation Flow only)").option("--dry-run", "Preview changes without applying them").addHelpText("after", `
3401
+ Examples:
3402
+ $ retell tools add agent_123abc --file tool.json
3403
+ $ retell tools add agent_123abc --file tool.json --state booking
3404
+ $ retell tools add agent_123abc --file tool.json --dry-run
3405
+ `).action(async (agentId, options) => {
3406
+ await addToolCommand(agentId, {
3407
+ file: options.file,
3408
+ state: options.state,
3409
+ component: options.component,
3410
+ dryRun: options.dryRun
3411
+ });
3412
+ });
3413
+ tools.command("update <agent_id> <tool_name>").description("Update an existing tool").requiredOption("-f, --file <path>", "Path to JSON file containing updated tool definition").option("--state <name>", "State where tool exists (Retell LLM only)").option("--component <id>", "Component where tool exists (Conversation Flow only)").option("--dry-run", "Preview changes without applying them").addHelpText("after", `
3414
+ Examples:
3415
+ $ retell tools update agent_123abc lookup_customer --file tool.json
3416
+ $ retell tools update agent_123abc book_cal --file tool.json --state booking
3417
+ $ retell tools update agent_123abc my_tool --file tool.json --dry-run
3418
+ `).action(async (agentId, toolName, options) => {
3419
+ await updateToolCommand(agentId, toolName, {
3420
+ file: options.file,
3421
+ state: options.state,
3422
+ component: options.component,
3423
+ dryRun: options.dryRun
3424
+ });
3425
+ });
3426
+ tools.command("remove <agent_id> <tool_name>").description("Remove a tool from an agent").option("--state <name>", "State where tool exists (Retell LLM only)").option("--component <id>", "Component where tool exists (Conversation Flow only)").option("--dry-run", "Preview changes without applying them").addHelpText("after", `
3427
+ Examples:
3428
+ $ retell tools remove agent_123abc lookup_customer
3429
+ $ retell tools remove agent_123abc book_cal --state booking
3430
+ $ retell tools remove agent_123abc my_tool --dry-run
3431
+ `).action(async (agentId, toolName, options) => {
3432
+ await removeToolCommand(agentId, toolName, {
3433
+ state: options.state,
3434
+ component: options.component,
3435
+ dryRun: options.dryRun
3436
+ });
3437
+ });
3438
+ tools.command("export <agent_id>").description("Export all tools from an agent to a JSON file").option("-o, --output <path>", "Output file path (prints to stdout if not specified)").addHelpText("after", `
3439
+ Examples:
3440
+ $ retell tools export agent_123abc
3441
+ $ retell tools export agent_123abc --output tools.json
3442
+ $ retell tools export agent_123abc > tools.json
3443
+ `).action(async (agentId, options) => {
3444
+ await exportToolsCommand(agentId, {
3445
+ output: options.output
3446
+ });
3447
+ });
3448
+ tools.command("import <agent_id>").description("Import tools from a JSON file to an agent").requiredOption("-f, --file <path>", "Path to JSON file containing tools to import").option("--dry-run", "Preview changes without applying them").option("--replace", "Replace existing tools with same name instead of skipping").addHelpText("after", `
3449
+ Examples:
3450
+ $ retell tools import agent_123abc --file tools.json
3451
+ $ retell tools import agent_123abc --file tools.json --dry-run
3452
+ $ retell tools import agent_123abc --file tools.json --replace
3453
+ `).action(async (agentId, options) => {
3454
+ await importToolsCommand(agentId, {
3455
+ file: options.file,
3456
+ dryRun: options.dryRun,
3457
+ replace: options.replace
3458
+ });
3459
+ });
3460
+ var tests = program.command("tests").description("Manage test cases, batch tests, and test runs");
3461
+ var testsCases = tests.command("cases").description("Manage test case definitions");
3462
+ testsCases.command("list").description("List all test case definitions for an LLM or flow").requiredOption("-t, --type <type>", "Response engine type (retell-llm or conversation-flow)").option("--llm-id <id>", "LLM ID (required when type is retell-llm)").option("--flow-id <id>", "Flow ID (required when type is conversation-flow)").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3463
+ Examples:
3464
+ $ retell tests cases list --type retell-llm --llm-id llm_abc123
3465
+ $ retell tests cases list --type conversation-flow --flow-id cf_abc123
3466
+ $ retell tests cases list --type retell-llm --llm-id llm_abc123 --fields test_case_definitions
3467
+ `).action(async (options) => {
3468
+ if (options.type !== "retell-llm" && options.type !== "conversation-flow") {
3469
+ console.error('Error: type must be "retell-llm" or "conversation-flow"');
3470
+ process.exit(1);
3471
+ }
3472
+ await listTestCasesCommand({
3473
+ type: options.type,
3474
+ llmId: options.llmId,
3475
+ flowId: options.flowId,
3476
+ fields: options.fields
3477
+ });
3478
+ });
3479
+ testsCases.command("get <test_case_definition_id>").description("Get a specific test case definition").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3480
+ Examples:
3481
+ $ retell tests cases get tcd_abc123
3482
+ $ retell tests cases get tcd_abc123 --fields name,user_prompt
3483
+ `).action(async (testCaseDefinitionId, options) => {
3484
+ await getTestCaseCommand(testCaseDefinitionId, {
3485
+ fields: options.fields
3486
+ });
3487
+ });
3488
+ testsCases.command("create").description("Create a new test case definition from a JSON file").requiredOption("-f, --file <path>", "Path to JSON file containing test case definition").option("--llm-id <id>", "LLM ID (mutually exclusive with --flow-id)").option("--flow-id <id>", "Flow ID (mutually exclusive with --llm-id)").option("--version <number>", "Version of the LLM or flow (optional)").addHelpText("after", `
3489
+ Examples:
3490
+ $ retell tests cases create --file test-case.json --llm-id llm_abc123
3491
+ $ retell tests cases create --file test-case.json --flow-id cf_abc123
3492
+ $ retell tests cases create --file test-case.json --llm-id llm_abc123 --version 2
3493
+
3494
+ Test case JSON format:
3495
+ {
3496
+ "name": "Greeting Test",
3497
+ "user_prompt": "Hello, I need help with my order",
3498
+ "scenario": "User is calling about an order issue",
3499
+ "metrics": ["response_quality", "task_completion"]
3500
+ }
3501
+ `).action(async (options) => {
3502
+ await createTestCaseCommand({
3503
+ file: options.file,
3504
+ llmId: options.llmId,
3505
+ flowId: options.flowId,
3506
+ version: options.version ? parseInt(options.version, 10) : void 0
3507
+ });
3508
+ });
3509
+ testsCases.command("update <test_case_definition_id>").description("Update an existing test case definition from a JSON file").requiredOption("-f, --file <path>", "Path to JSON file containing test case updates").addHelpText("after", `
3510
+ Examples:
3511
+ $ retell tests cases update tcd_abc123 --file test-case.json
3512
+ `).action(async (testCaseDefinitionId, options) => {
3513
+ await updateTestCaseCommand(testCaseDefinitionId, {
3514
+ file: options.file
3515
+ });
3516
+ });
3517
+ testsCases.command("delete <test_case_definition_id>").description("Delete a test case definition").addHelpText("after", `
3518
+ Examples:
3519
+ $ retell tests cases delete tcd_abc123
3520
+ `).action(async (testCaseDefinitionId) => {
3521
+ await deleteTestCaseCommand(testCaseDefinitionId);
3522
+ });
3523
+ var testsBatch = tests.command("batch").description("Manage batch tests");
3524
+ testsBatch.command("list").description("List all batch tests for an LLM or flow").requiredOption("-t, --type <type>", "Response engine type (retell-llm or conversation-flow)").option("--llm-id <id>", "LLM ID (required when type is retell-llm)").option("--flow-id <id>", "Flow ID (required when type is conversation-flow)").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3525
+ Examples:
3526
+ $ retell tests batch list --type retell-llm --llm-id llm_abc123
3527
+ $ retell tests batch list --type conversation-flow --flow-id cf_abc123
3528
+ $ retell tests batch list --type retell-llm --llm-id llm_abc123 --fields batch_tests
3529
+ `).action(async (options) => {
3530
+ if (options.type !== "retell-llm" && options.type !== "conversation-flow") {
3531
+ console.error('Error: type must be "retell-llm" or "conversation-flow"');
3532
+ process.exit(1);
3533
+ }
3534
+ await listBatchTestsCommand({
3535
+ type: options.type,
3536
+ llmId: options.llmId,
3537
+ flowId: options.flowId,
3538
+ fields: options.fields
3539
+ });
3540
+ });
3541
+ testsBatch.command("get <batch_job_id>").description("Get a specific batch test with its status and stats").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3542
+ Examples:
3543
+ $ retell tests batch get bjj_abc123
3544
+ $ retell tests batch get bjj_abc123 --fields status,stats
3545
+ `).action(async (batchJobId, options) => {
3546
+ await getBatchTestCommand(batchJobId, {
3547
+ fields: options.fields
3548
+ });
3549
+ });
3550
+ testsBatch.command("create").description("Create a new batch test with specified test case definitions").option("--llm-id <id>", "LLM ID (mutually exclusive with --flow-id)").option("--flow-id <id>", "Flow ID (mutually exclusive with --llm-id)").requiredOption("--cases <ids>", "Comma-separated list of test case definition IDs").option("--version <number>", "Version of the LLM or flow (optional)").addHelpText("after", `
3551
+ Examples:
3552
+ $ retell tests batch create --llm-id llm_abc123 --cases tcd_xxx,tcd_yyy,tcd_zzz
3553
+ $ retell tests batch create --flow-id cf_abc123 --cases tcd_xxx,tcd_yyy
3554
+ $ retell tests batch create --llm-id llm_abc123 --cases tcd_xxx --version 2
3555
+ `).action(async (options) => {
3556
+ await createBatchTestCommand({
3557
+ llmId: options.llmId,
3558
+ flowId: options.flowId,
3559
+ cases: options.cases,
3560
+ version: options.version ? parseInt(options.version, 10) : void 0
3561
+ });
3562
+ });
3563
+ var testsRuns = tests.command("runs").description("View test run results");
3564
+ testsRuns.command("list <batch_job_id>").description("List all test runs for a batch test").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3565
+ Examples:
3566
+ $ retell tests runs list bjj_abc123
3567
+ $ retell tests runs list bjj_abc123 --fields test_runs
3568
+ `).action(async (batchJobId, options) => {
3569
+ await listTestRunsCommand(batchJobId, {
3570
+ fields: options.fields
3571
+ });
3572
+ });
3573
+ testsRuns.command("get <test_run_id>").description("Get a specific test run result").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3574
+ Examples:
3575
+ $ retell tests runs get tcj_abc123
3576
+ $ retell tests runs get tcj_abc123 --fields status,metric_results
3577
+ `).action(async (testRunId, options) => {
3578
+ await getTestRunCommand(testRunId, {
3579
+ fields: options.fields
3580
+ });
3581
+ });
3582
+ var kb = program.command("kb").description("Manage RAG knowledge bases");
3583
+ kb.command("list").description("List all knowledge bases").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3584
+ Examples:
3585
+ $ retell kb list
3586
+ $ retell kb list --fields knowledge_base_id,knowledge_base_name,status
3587
+ `).action(async (options) => {
3588
+ await listKnowledgeBasesCommand({
3589
+ fields: options.fields
3590
+ });
3591
+ });
3592
+ kb.command("get <knowledge_base_id>").description("Get a specific knowledge base").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3593
+ Examples:
3594
+ $ retell kb get kb_abc123
3595
+ $ retell kb get kb_abc123 --fields knowledge_base_name,status,knowledge_base_sources
3596
+ `).action(async (knowledgeBaseId, options) => {
3597
+ await getKnowledgeBaseCommand(knowledgeBaseId, {
3598
+ fields: options.fields
3599
+ });
3600
+ });
3601
+ kb.command("create").description("Create a new knowledge base").requiredOption("-n, --name <name>", "Knowledge base name (max 40 characters)").option("--urls <urls>", "Comma-separated list of URLs to scrape").option("--texts <file>", "Path to JSON file with text entries [{ title, text }, ...]").option("--auto-refresh", "Enable 12-hour automatic refresh for URL sources").addHelpText("after", `
3602
+ Examples:
3603
+ $ retell kb create --name "Product Docs"
3604
+ $ retell kb create --name "Support KB" --urls https://docs.example.com,https://help.example.com
3605
+ $ retell kb create --name "FAQ" --texts texts.json
3606
+ $ retell kb create --name "Docs" --urls https://docs.example.com --auto-refresh
3607
+
3608
+ Text file format (texts.json):
3609
+ [
3610
+ { "title": "Getting Started", "text": "Welcome to our product..." },
3611
+ { "title": "FAQ", "text": "Frequently asked questions..." }
3612
+ ]
3613
+ `).action(async (options) => {
3614
+ await createKnowledgeBaseCommand({
3615
+ name: options.name,
3616
+ urls: options.urls,
3617
+ texts: options.texts,
3618
+ autoRefresh: options.autoRefresh
3619
+ });
3620
+ });
3621
+ kb.command("delete <knowledge_base_id>").description("Delete a knowledge base").addHelpText("after", `
3622
+ Examples:
3623
+ $ retell kb delete kb_abc123
3624
+ `).action(async (knowledgeBaseId) => {
3625
+ await deleteKnowledgeBaseCommand(knowledgeBaseId);
3626
+ });
3627
+ var kbSources = kb.command("sources").description("Manage knowledge base sources");
3628
+ kbSources.command("add <knowledge_base_id>").description("Add sources to an existing knowledge base").option("--urls <urls>", "Comma-separated list of URLs to scrape").option("--texts <file>", "Path to JSON file with text entries [{ title, text }, ...]").addHelpText("after", `
3629
+ Examples:
3630
+ $ retell kb sources add kb_abc123 --urls https://docs.example.com/new
3631
+ $ retell kb sources add kb_abc123 --texts additional-texts.json
3632
+ $ retell kb sources add kb_abc123 --urls https://faq.example.com --texts more-texts.json
3633
+ `).action(async (knowledgeBaseId, options) => {
3634
+ await addKnowledgeBaseSourcesCommand(knowledgeBaseId, {
3635
+ urls: options.urls,
3636
+ texts: options.texts
3637
+ });
3638
+ });
3639
+ kbSources.command("delete <knowledge_base_id> <source_id>").description("Remove a source from a knowledge base").addHelpText("after", `
3640
+ Examples:
3641
+ $ retell kb sources delete kb_abc123 source_xyz789
3642
+ `).action(async (knowledgeBaseId, sourceId) => {
3643
+ await deleteKnowledgeBaseSourceCommand(knowledgeBaseId, sourceId);
3644
+ });
3645
+ var flows = program.command("flows").description("Manage conversation flow response engines");
3646
+ flows.command("list").description("List all conversation flows").option("-l, --limit <number>", "Maximum number of flows to return (default: 100, max: 1000)", "100").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3647
+ Examples:
3648
+ $ retell flows list
3649
+ $ retell flows list --limit 50
3650
+ $ retell flows list --fields conversation_flow_id,version,start_speaker
3651
+ `).action(async (options) => {
3652
+ const limit = parseInt(options.limit, 10);
3653
+ if (isNaN(limit) || limit < 1 || limit > 1e3) {
3654
+ console.error("Error: limit must be a positive number between 1 and 1000");
3655
+ process.exit(1);
3656
+ }
3657
+ await listFlowsCommand({
3658
+ limit,
3659
+ fields: options.fields
3660
+ });
3661
+ });
3662
+ flows.command("get <conversation_flow_id>").description("Get a specific conversation flow").option("--version <number>", "Specific version to retrieve (defaults to latest)").option("--fields <fields>", "Comma-separated list of fields to return").addHelpText("after", `
3663
+ Examples:
3664
+ $ retell flows get cf_abc123
3665
+ $ retell flows get cf_abc123 --version 2
3666
+ $ retell flows get cf_abc123 --fields conversation_flow_id,nodes,edges
3667
+ `).action(async (conversationFlowId, options) => {
3668
+ await getFlowCommand(conversationFlowId, {
3669
+ version: options.version ? parseInt(options.version, 10) : void 0,
3670
+ fields: options.fields
3671
+ });
3672
+ });
3673
+ flows.command("create").description("Create a new conversation flow from a JSON file").requiredOption("-f, --file <path>", "Path to JSON file containing flow configuration").addHelpText("after", `
3674
+ Examples:
3675
+ $ retell flows create --file flow.json
3676
+
3677
+ Flow JSON format (minimal):
3678
+ {
3679
+ "start_speaker": "agent",
3680
+ "start_node_id": "node_1",
3681
+ "nodes": [...],
3682
+ "edges": [...]
3683
+ }
3684
+ `).action(async (options) => {
3685
+ await createFlowCommand({
3686
+ file: options.file
3687
+ });
3688
+ });
3689
+ flows.command("update <conversation_flow_id>").description("Update an existing conversation flow from a JSON file").requiredOption("-f, --file <path>", "Path to JSON file containing flow updates").option("--version <number>", "Specific version to update (defaults to latest)").addHelpText("after", `
3690
+ Examples:
3691
+ $ retell flows update cf_abc123 --file updates.json
3692
+ $ retell flows update cf_abc123 --file updates.json --version 2
3693
+ `).action(async (conversationFlowId, options) => {
3694
+ await updateFlowCommand(conversationFlowId, {
3695
+ file: options.file,
3696
+ version: options.version ? parseInt(options.version, 10) : void 0
3697
+ });
3698
+ });
3699
+ flows.command("delete <conversation_flow_id>").description("Delete a conversation flow").addHelpText("after", `
3700
+ Examples:
3701
+ $ retell flows delete cf_abc123
3702
+ `).action(async (conversationFlowId) => {
3703
+ await deleteFlowCommand(conversationFlowId);
1506
3704
  });
1507
3705
  program.parse(process.argv);
1508
3706
  if (!process.argv.slice(2).length) {