retell-sync-cli 2.2.0 → 3.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 (2) hide show
  1. package/dist/cli.js +245 -115
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -135364,7 +135364,6 @@ async function deployCommand(agentIdArgs, opts, cmd) {
135364
135364
  configFormat: globalOpts.configFormat,
135365
135365
  agentIds,
135366
135366
  dryRun: opts.dryRun,
135367
- publish: opts.publish,
135368
135367
  verbose: opts.verbose
135369
135368
  });
135370
135369
  } catch (err) {
@@ -135380,11 +135379,10 @@ async function deploy({
135380
135379
  configFormat = DEFAULT_CONFIG_FORMAT,
135381
135380
  agentIds = null,
135382
135381
  dryRun = false,
135383
- publish = false,
135384
135382
  verbose = false
135385
135383
  } = {}) {
135386
135384
  const scopeLabel = agentIds ? `${agentIds.length} agent(s)` : "all agents";
135387
- console.log(source_default.bold(publish ? `Deploying ${scopeLabel} to Retell (will publish)...` : `Deploying ${scopeLabel} to Retell...`));
135385
+ console.log(source_default.bold(`Deploying ${scopeLabel} to Retell draft...`));
135388
135386
  console.log(source_default.bold("Analyzing changes..."));
135389
135387
  let spinner = createSpinner("Reading local and remote state...");
135390
135388
  const [localState, remoteState] = await Promise.all([
@@ -135405,10 +135403,7 @@ async function deploy({
135405
135403
  printChangeSummary(changes, { verbose });
135406
135404
  return;
135407
135405
  }
135408
- console.log(source_default.bold("Deploying changes..."));
135409
- const updatedAgentIds = new Set;
135410
- const updatedLlmIds = new Set;
135411
- const updatedFlowIds = new Set;
135406
+ console.log(source_default.bold("Deploying changes to draft..."));
135412
135407
  spinner = createSpinner(`Deploying ${totalChanges} changes...`);
135413
135408
  const updateResults = await Promise.allSettled([
135414
135409
  ...changes.agents.map(async (change) => {
@@ -135435,122 +135430,16 @@ async function deploy({
135435
135430
  spinner.stop(source_default.dim("Done"));
135436
135431
  for (const result of updateResults) {
135437
135432
  if (result.status === "fulfilled") {
135438
- const { type, id: id2, name } = result.value;
135433
+ const { type, name } = result.value;
135439
135434
  console.log(source_default.green(`Updated ${type} ${source_default.bold(name)}`));
135440
- if (type === "agent")
135441
- updatedAgentIds.add(id2);
135442
- else if (type === "llm")
135443
- updatedLlmIds.add(id2);
135444
- else if (type === "flow")
135445
- updatedFlowIds.add(id2);
135446
135435
  } else {
135447
135436
  console.log(source_default.red(`Failed to update: ${result.reason}`));
135448
135437
  }
135449
135438
  }
135450
- for (const agent of localState.voiceAgents) {
135451
- const engine = agent.response_engine;
135452
- if (engine.type === "retell-llm" && updatedLlmIds.has(engine.llm_id)) {
135453
- updatedAgentIds.add(agent._id);
135454
- } else if (engine.type === "conversation-flow" && updatedFlowIds.has(engine.conversation_flow_id)) {
135455
- updatedAgentIds.add(agent._id);
135456
- }
135457
- }
135458
135439
  console.log(source_default.green(`Deployed ${pluralize("change", totalChanges, true)}`));
135459
- if (publish && updatedAgentIds.size > 0) {
135460
- const agentNames = new Map(localState.voiceAgents.map((a7) => [a7._id, a7.agent_name ?? a7._id]));
135461
- spinner = createSpinner(`Publishing ${updatedAgentIds.size} agents...`);
135462
- const publishResults = await Promise.allSettled([...updatedAgentIds].map(async (id2) => {
135463
- await retell.agent.publish(id2);
135464
- return { id: id2, name: agentNames.get(id2) ?? id2 };
135465
- }));
135466
- spinner.stop(source_default.dim("Done"));
135467
- const publishedAgentIds = [];
135468
- for (const result of publishResults) {
135469
- if (result.status === "fulfilled") {
135470
- console.log(source_default.green(`Published ${source_default.bold(result.value.name)}`));
135471
- publishedAgentIds.push(result.value.id);
135472
- } else {
135473
- console.log(source_default.red(`Failed to publish: ${result.reason}`));
135474
- }
135475
- }
135476
- console.log(source_default.green(`Published ${pluralize("agent", publishedAgentIds.length, true)}`));
135477
- if (publishedAgentIds.length > 0) {
135478
- await updatePhoneNumberVersions(publishedAgentIds, agentNames);
135479
- }
135480
- }
135481
135440
  console.log(source_default.bold("Syncing latest state..."));
135482
135441
  await pull({ agentsDir, configFormat, agentIds });
135483
135442
  }
135484
- async function updatePhoneNumberVersions(publishedAgentIds, agentNames) {
135485
- const spinner = createSpinner("Updating phone numbers...");
135486
- const [phoneNumbers, ...agentVersionLists] = await Promise.all([
135487
- retell.phoneNumber.list(),
135488
- ...publishedAgentIds.map((id2) => retell.agent.getVersions(id2))
135489
- ]);
135490
- const publishedVersions = new Map;
135491
- for (const [i5, agentId] of publishedAgentIds.entries()) {
135492
- const versions2 = agentVersionLists[i5];
135493
- if (!versions2)
135494
- continue;
135495
- const latestPublished = versions2.filter((v10) => v10.is_published).sort((a7, b7) => (b7.version ?? 0) - (a7.version ?? 0))[0];
135496
- if (latestPublished?.version != null) {
135497
- publishedVersions.set(agentId, latestPublished.version);
135498
- }
135499
- }
135500
- const publishedAgentIdSet = new Set(publishedAgentIds);
135501
- const updates = [];
135502
- for (const phone of phoneNumbers) {
135503
- const inboundVersion = phone.inbound_agent_id && publishedAgentIdSet.has(phone.inbound_agent_id) ? publishedVersions.get(phone.inbound_agent_id) : undefined;
135504
- const outboundVersion = phone.outbound_agent_id && publishedAgentIdSet.has(phone.outbound_agent_id) ? publishedVersions.get(phone.outbound_agent_id) : undefined;
135505
- if (inboundVersion != null || outboundVersion != null) {
135506
- updates.push({
135507
- phoneNumber: phone.phone_number,
135508
- inboundVersion,
135509
- outboundVersion
135510
- });
135511
- }
135512
- }
135513
- if (updates.length === 0) {
135514
- spinner.stop(source_default.dim("No phone numbers to update"));
135515
- return;
135516
- }
135517
- const updateResults = await Promise.allSettled(updates.map(async ({ phoneNumber, inboundVersion, outboundVersion }) => {
135518
- await retell.phoneNumber.update(phoneNumber, {
135519
- ...inboundVersion != null && {
135520
- inbound_agent_version: inboundVersion
135521
- },
135522
- ...outboundVersion != null && {
135523
- outbound_agent_version: outboundVersion
135524
- }
135525
- });
135526
- return phoneNumber;
135527
- }));
135528
- spinner.stop(source_default.dim("Done"));
135529
- let updatedCount = 0;
135530
- for (const result of updateResults) {
135531
- if (result.status === "fulfilled") {
135532
- const phone = updates.find((u4) => u4.phoneNumber === result.value);
135533
- const agentInfo = [];
135534
- if (phone?.inboundVersion != null) {
135535
- const inboundPhone = phoneNumbers.find((p4) => p4.phone_number === result.value);
135536
- const agentId = inboundPhone?.inbound_agent_id;
135537
- const name = agentId ? agentNames.get(agentId) ?? agentId : "unknown";
135538
- agentInfo.push(`inbound: ${name} v${phone.inboundVersion}`);
135539
- }
135540
- if (phone?.outboundVersion != null) {
135541
- const outboundPhone = phoneNumbers.find((p4) => p4.phone_number === result.value);
135542
- const agentId = outboundPhone?.outbound_agent_id;
135543
- const name = agentId ? agentNames.get(agentId) ?? agentId : "unknown";
135544
- agentInfo.push(`outbound: ${name} v${phone.outboundVersion}`);
135545
- }
135546
- console.log(source_default.green(`Updated ${source_default.bold(result.value)} (${agentInfo.join(", ")})`));
135547
- updatedCount++;
135548
- } else {
135549
- console.log(source_default.red(`Failed to update phone number: ${result.reason}`));
135550
- }
135551
- }
135552
- console.log(source_default.green(`Updated ${pluralize("phone number", updatedCount, true)}`));
135553
- }
135554
135443
  function computeChanges(local, remote) {
135555
135444
  const changes = {
135556
135445
  agents: [],
@@ -135690,9 +135579,250 @@ Flows to update:`));
135690
135579
  }
135691
135580
  }
135692
135581
 
135582
+ // src/commands/publish.ts
135583
+ async function publishCommand(agentIdArgs, opts, cmd) {
135584
+ const globalOpts = cmd.optsWithGlobals();
135585
+ try {
135586
+ const agentIds = await resolveAgentIds(agentIdArgs, {
135587
+ all: opts.all,
135588
+ select: opts.select
135589
+ });
135590
+ await publish({
135591
+ agentsDir: globalOpts.agentsDir,
135592
+ configFormat: globalOpts.configFormat,
135593
+ agentIds,
135594
+ dryRun: opts.dryRun
135595
+ });
135596
+ } catch (err) {
135597
+ if (err instanceof ExitPromptError) {
135598
+ console.log(source_default.dim("Aborted"));
135599
+ return;
135600
+ }
135601
+ throw err;
135602
+ }
135603
+ }
135604
+ async function publish({
135605
+ agentsDir = DEFAULT_AGENTS_DIR,
135606
+ configFormat = DEFAULT_CONFIG_FORMAT,
135607
+ agentIds = null,
135608
+ dryRun = false
135609
+ } = {}) {
135610
+ const scopeLabel = agentIds ? `${agentIds.length} agent(s)` : "all agents";
135611
+ console.log(source_default.bold(`Checking ${scopeLabel} for unpublished changes...`));
135612
+ let spinner = createSpinner("Fetching draft and published states...");
135613
+ const [draftState, publishedState] = await Promise.all([
135614
+ getLocalState({ agentsDir, agentIds }),
135615
+ getRemoteState({ draft: false, agentIds })
135616
+ ]);
135617
+ spinner.stop(source_default.dim("Done"));
135618
+ const agentNames = new Map(draftState.voiceAgents.map((a7) => [a7._id, a7.agent_name ?? a7._id]));
135619
+ spinner = createSpinner("Comparing draft vs published...");
135620
+ const changes = computeChanges2(draftState, publishedState);
135621
+ spinner.stop(source_default.dim(`Found ${source_default.white(changes.agents.length)} agent changes, ${source_default.white(changes.llms.length)} LLM changes, ${source_default.white(changes.flows.length)} flow changes`));
135622
+ const agentIdsToPublish = new Set;
135623
+ for (const change of changes.agents) {
135624
+ agentIdsToPublish.add(change.id);
135625
+ }
135626
+ const changedLlmIds = new Set(changes.llms.map((c7) => c7.id));
135627
+ for (const agent of draftState.voiceAgents) {
135628
+ if (agent.response_engine.type === "retell-llm" && changedLlmIds.has(agent.response_engine.llm_id)) {
135629
+ agentIdsToPublish.add(agent._id);
135630
+ }
135631
+ }
135632
+ const changedFlowIds = new Set(changes.flows.map((c7) => c7.id));
135633
+ for (const agent of draftState.voiceAgents) {
135634
+ if (agent.response_engine.type === "conversation-flow" && changedFlowIds.has(agent.response_engine.conversation_flow_id)) {
135635
+ agentIdsToPublish.add(agent._id);
135636
+ }
135637
+ }
135638
+ if (agentIdsToPublish.size === 0) {
135639
+ console.log(source_default.green("All agents are already up to date"));
135640
+ return;
135641
+ }
135642
+ if (dryRun) {
135643
+ console.log(source_default.yellow("Dry run mode - no changes will be made"));
135644
+ console.log(source_default.cyan(`
135645
+ Would publish ${pluralize("agent", agentIdsToPublish.size, true)}:`));
135646
+ for (const id2 of agentIdsToPublish) {
135647
+ const name = agentNames.get(id2) ?? id2;
135648
+ console.log(` ${source_default.bold(name)} ${source_default.dim(`(${id2})`)}`);
135649
+ }
135650
+ return;
135651
+ }
135652
+ console.log(source_default.bold(`Publishing ${pluralize("agent", agentIdsToPublish.size, true)}...`));
135653
+ spinner = createSpinner(`Publishing ${agentIdsToPublish.size} agents...`);
135654
+ const publishResults = await Promise.allSettled([...agentIdsToPublish].map(async (id2) => {
135655
+ await retell.agent.publish(id2);
135656
+ return { id: id2, name: agentNames.get(id2) ?? id2 };
135657
+ }));
135658
+ spinner.stop(source_default.dim("Done"));
135659
+ const publishedAgentIds = [];
135660
+ for (const result of publishResults) {
135661
+ if (result.status === "fulfilled") {
135662
+ console.log(source_default.green(`Published ${source_default.bold(result.value.name)}`));
135663
+ publishedAgentIds.push(result.value.id);
135664
+ } else {
135665
+ console.log(source_default.red(`Failed to publish: ${result.reason}`));
135666
+ }
135667
+ }
135668
+ console.log(source_default.green(`Published ${pluralize("agent", publishedAgentIds.length, true)}`));
135669
+ if (publishedAgentIds.length > 0) {
135670
+ await updatePhoneNumberVersions(publishedAgentIds, agentNames);
135671
+ }
135672
+ console.log(source_default.bold("Syncing latest state..."));
135673
+ await pull({ agentsDir, configFormat, agentIds });
135674
+ }
135675
+ function computeChanges2(draft, published) {
135676
+ const changes = {
135677
+ agents: [],
135678
+ llms: [],
135679
+ flows: []
135680
+ };
135681
+ const publishedAgents = new Map(published.voiceAgents.map((a7) => [a7._id, a7]));
135682
+ const publishedLLMs = new Map(published.llms.map((l5) => [l5._id, l5]));
135683
+ const publishedFlows = new Map(published.conversationFlows.map((f7) => [f7._id, f7]));
135684
+ for (const agent of draft.voiceAgents) {
135685
+ const publishedAgent = publishedAgents.get(agent._id);
135686
+ if (!publishedAgent) {
135687
+ changes.agents.push({
135688
+ id: agent._id,
135689
+ name: agent.agent_name ?? agent._id,
135690
+ current: agent,
135691
+ differences: [{ type: "CREATE", path: [], value: agent }]
135692
+ });
135693
+ continue;
135694
+ }
135695
+ const { response_engine: _draftRe, ...draftComparable } = agent;
135696
+ const { response_engine: _pubRe, ...publishedComparable } = publishedAgent;
135697
+ const differences = diff(publishedComparable, draftComparable);
135698
+ if (differences.length > 0) {
135699
+ changes.agents.push({
135700
+ id: agent._id,
135701
+ name: agent.agent_name ?? agent._id,
135702
+ current: agent,
135703
+ differences
135704
+ });
135705
+ }
135706
+ }
135707
+ for (const llm of draft.llms) {
135708
+ const publishedLLM = publishedLLMs.get(llm._id);
135709
+ if (!publishedLLM) {
135710
+ changes.llms.push({
135711
+ id: llm._id,
135712
+ name: llm._id,
135713
+ current: llm,
135714
+ differences: [{ type: "CREATE", path: [], value: llm }]
135715
+ });
135716
+ continue;
135717
+ }
135718
+ const differences = diff(publishedLLM, llm);
135719
+ if (differences.length > 0) {
135720
+ changes.llms.push({
135721
+ id: llm._id,
135722
+ name: llm._id,
135723
+ current: llm,
135724
+ differences
135725
+ });
135726
+ }
135727
+ }
135728
+ for (const flow of draft.conversationFlows) {
135729
+ const publishedFlow = publishedFlows.get(flow._id);
135730
+ if (!publishedFlow) {
135731
+ changes.flows.push({
135732
+ id: flow._id,
135733
+ name: flow._id,
135734
+ current: flow,
135735
+ differences: [{ type: "CREATE", path: [], value: flow }]
135736
+ });
135737
+ continue;
135738
+ }
135739
+ const differences = diff(publishedFlow, flow);
135740
+ if (differences.length > 0) {
135741
+ changes.flows.push({
135742
+ id: flow._id,
135743
+ name: flow._id,
135744
+ current: flow,
135745
+ differences
135746
+ });
135747
+ }
135748
+ }
135749
+ return changes;
135750
+ }
135751
+ async function updatePhoneNumberVersions(publishedAgentIds, agentNames) {
135752
+ const spinner = createSpinner("Updating phone numbers...");
135753
+ const [phoneNumbers, ...agentVersionLists] = await Promise.all([
135754
+ retell.phoneNumber.list(),
135755
+ ...publishedAgentIds.map((id2) => retell.agent.getVersions(id2))
135756
+ ]);
135757
+ const publishedVersions = new Map;
135758
+ for (const [i5, agentId] of publishedAgentIds.entries()) {
135759
+ const versions2 = agentVersionLists[i5];
135760
+ if (!versions2)
135761
+ continue;
135762
+ const latestPublished = versions2.filter((v10) => v10.is_published).sort((a7, b7) => (b7.version ?? 0) - (a7.version ?? 0))[0];
135763
+ if (latestPublished?.version != null) {
135764
+ publishedVersions.set(agentId, latestPublished.version);
135765
+ }
135766
+ }
135767
+ const publishedAgentIdSet = new Set(publishedAgentIds);
135768
+ const updates = [];
135769
+ for (const phone of phoneNumbers) {
135770
+ const inboundVersion = phone.inbound_agent_id && publishedAgentIdSet.has(phone.inbound_agent_id) ? publishedVersions.get(phone.inbound_agent_id) : undefined;
135771
+ const outboundVersion = phone.outbound_agent_id && publishedAgentIdSet.has(phone.outbound_agent_id) ? publishedVersions.get(phone.outbound_agent_id) : undefined;
135772
+ if (inboundVersion != null || outboundVersion != null) {
135773
+ updates.push({
135774
+ phoneNumber: phone.phone_number,
135775
+ inboundVersion,
135776
+ outboundVersion
135777
+ });
135778
+ }
135779
+ }
135780
+ if (updates.length === 0) {
135781
+ spinner.stop(source_default.dim("No phone numbers to update"));
135782
+ return;
135783
+ }
135784
+ const updateResults = await Promise.allSettled(updates.map(async ({ phoneNumber, inboundVersion, outboundVersion }) => {
135785
+ await retell.phoneNumber.update(phoneNumber, {
135786
+ ...inboundVersion != null && {
135787
+ inbound_agent_version: inboundVersion
135788
+ },
135789
+ ...outboundVersion != null && {
135790
+ outbound_agent_version: outboundVersion
135791
+ }
135792
+ });
135793
+ return phoneNumber;
135794
+ }));
135795
+ spinner.stop(source_default.dim("Done"));
135796
+ let updatedCount = 0;
135797
+ for (const result of updateResults) {
135798
+ if (result.status === "fulfilled") {
135799
+ const phone = updates.find((u4) => u4.phoneNumber === result.value);
135800
+ const agentInfo = [];
135801
+ if (phone?.inboundVersion != null) {
135802
+ const inboundPhone = phoneNumbers.find((p4) => p4.phone_number === result.value);
135803
+ const agentId = inboundPhone?.inbound_agent_id;
135804
+ const name = agentId ? agentNames.get(agentId) ?? agentId : "unknown";
135805
+ agentInfo.push(`inbound: ${name} v${phone.inboundVersion}`);
135806
+ }
135807
+ if (phone?.outboundVersion != null) {
135808
+ const outboundPhone = phoneNumbers.find((p4) => p4.phone_number === result.value);
135809
+ const agentId = outboundPhone?.outbound_agent_id;
135810
+ const name = agentId ? agentNames.get(agentId) ?? agentId : "unknown";
135811
+ agentInfo.push(`outbound: ${name} v${phone.outboundVersion}`);
135812
+ }
135813
+ console.log(source_default.green(`Updated ${source_default.bold(result.value)} (${agentInfo.join(", ")})`));
135814
+ updatedCount++;
135815
+ } else {
135816
+ console.log(source_default.red(`Failed to update phone number: ${result.reason}`));
135817
+ }
135818
+ }
135819
+ console.log(source_default.green(`Updated ${pluralize("phone number", updatedCount, true)}`));
135820
+ }
135821
+
135693
135822
  // src/cli.ts
135694
135823
  var program2 = new Command;
135695
135824
  program2.name("retell").description("Retell AI agent management CLI").option("-w, --agents-dir <dir>", "Directory for agent files", DEFAULT_AGENTS_DIR).option("-f, --config-format <format>", `Config file format (${CONFIG_FORMATS.join(", ")})`, DEFAULT_CONFIG_FORMAT);
135696
135825
  program2.command("pull [agentIds...]").description("Pull agents from Retell API (always pulls latest draft state)").option("-a, --all", "Pull all agents in the account").option("-s, --select", "Force interactive agent selection").option("-y, --yes", "Skip confirmation prompts").action(pullCommand);
135697
- program2.command("deploy [agentIds...]").description("Deploy local changes to Retell API").option("-a, --all", "Deploy all agents in the account").option("-s, --select", "Force interactive agent selection").option("-n, --dry-run", "Show changes without applying").option("-p, --publish", "Publish changed agents after deploying").option("-v, --verbose", "Show full diff details (use with --dry-run)").action(deployCommand);
135826
+ program2.command("deploy [agentIds...]").description("Deploy local changes to Retell draft").option("-a, --all", "Deploy all agents in the account").option("-s, --select", "Force interactive agent selection").option("-n, --dry-run", "Show changes without applying").option("-v, --verbose", "Show full diff details (use with --dry-run)").action(deployCommand);
135827
+ program2.command("publish [agentIds...]").description("Publish agents with unpublished draft changes").option("-a, --all", "Publish all agents in the account").option("-s, --select", "Force interactive agent selection").option("-n, --dry-run", "Show what would be published without publishing").action(publishCommand);
135698
135828
  program2.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retell-sync-cli",
3
- "version": "2.2.0",
3
+ "version": "3.1.0",
4
4
  "description": "CLI tool for syncing Retell AI agents between local filesystem and API",
5
5
  "keywords": [
6
6
  "agents",