retell-sync-cli 2.1.0 → 3.0.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 +242 -117
  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,14 +135403,16 @@ 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) => {
135415
- const { _id, _version, ...updateData } = change.current;
135410
+ const {
135411
+ _id,
135412
+ _version,
135413
+ response_engine: _10,
135414
+ ...updateData
135415
+ } = change.current;
135416
135416
  await retell.agent.update(_id, updateData);
135417
135417
  return { type: "agent", id: _id, name: change.name };
135418
135418
  }),
@@ -135430,122 +135430,16 @@ async function deploy({
135430
135430
  spinner.stop(source_default.dim("Done"));
135431
135431
  for (const result of updateResults) {
135432
135432
  if (result.status === "fulfilled") {
135433
- const { type, id: id2, name } = result.value;
135433
+ const { type, name } = result.value;
135434
135434
  console.log(source_default.green(`Updated ${type} ${source_default.bold(name)}`));
135435
- if (type === "agent")
135436
- updatedAgentIds.add(id2);
135437
- else if (type === "llm")
135438
- updatedLlmIds.add(id2);
135439
- else if (type === "flow")
135440
- updatedFlowIds.add(id2);
135441
135435
  } else {
135442
135436
  console.log(source_default.red(`Failed to update: ${result.reason}`));
135443
135437
  }
135444
135438
  }
135445
- for (const agent of localState.voiceAgents) {
135446
- const engine = agent.response_engine;
135447
- if (engine.type === "retell-llm" && updatedLlmIds.has(engine.llm_id)) {
135448
- updatedAgentIds.add(agent._id);
135449
- } else if (engine.type === "conversation-flow" && updatedFlowIds.has(engine.conversation_flow_id)) {
135450
- updatedAgentIds.add(agent._id);
135451
- }
135452
- }
135453
135439
  console.log(source_default.green(`Deployed ${pluralize("change", totalChanges, true)}`));
135454
- if (publish && updatedAgentIds.size > 0) {
135455
- const agentNames = new Map(localState.voiceAgents.map((a7) => [a7._id, a7.agent_name ?? a7._id]));
135456
- spinner = createSpinner(`Publishing ${updatedAgentIds.size} agents...`);
135457
- const publishResults = await Promise.allSettled([...updatedAgentIds].map(async (id2) => {
135458
- await retell.agent.publish(id2);
135459
- return { id: id2, name: agentNames.get(id2) ?? id2 };
135460
- }));
135461
- spinner.stop(source_default.dim("Done"));
135462
- const publishedAgentIds = [];
135463
- for (const result of publishResults) {
135464
- if (result.status === "fulfilled") {
135465
- console.log(source_default.green(`Published ${source_default.bold(result.value.name)}`));
135466
- publishedAgentIds.push(result.value.id);
135467
- } else {
135468
- console.log(source_default.red(`Failed to publish: ${result.reason}`));
135469
- }
135470
- }
135471
- console.log(source_default.green(`Published ${pluralize("agent", publishedAgentIds.length, true)}`));
135472
- if (publishedAgentIds.length > 0) {
135473
- await updatePhoneNumberVersions(publishedAgentIds, agentNames);
135474
- }
135475
- }
135476
135440
  console.log(source_default.bold("Syncing latest state..."));
135477
135441
  await pull({ agentsDir, configFormat, agentIds });
135478
135442
  }
135479
- async function updatePhoneNumberVersions(publishedAgentIds, agentNames) {
135480
- const spinner = createSpinner("Updating phone numbers...");
135481
- const [phoneNumbers, ...agentVersionLists] = await Promise.all([
135482
- retell.phoneNumber.list(),
135483
- ...publishedAgentIds.map((id2) => retell.agent.getVersions(id2))
135484
- ]);
135485
- const publishedVersions = new Map;
135486
- for (const [i5, agentId] of publishedAgentIds.entries()) {
135487
- const versions2 = agentVersionLists[i5];
135488
- if (!versions2)
135489
- continue;
135490
- const latestPublished = versions2.filter((v10) => v10.is_published).sort((a7, b7) => (b7.version ?? 0) - (a7.version ?? 0))[0];
135491
- if (latestPublished?.version != null) {
135492
- publishedVersions.set(agentId, latestPublished.version);
135493
- }
135494
- }
135495
- const publishedAgentIdSet = new Set(publishedAgentIds);
135496
- const updates = [];
135497
- for (const phone of phoneNumbers) {
135498
- const inboundVersion = phone.inbound_agent_id && publishedAgentIdSet.has(phone.inbound_agent_id) ? publishedVersions.get(phone.inbound_agent_id) : undefined;
135499
- const outboundVersion = phone.outbound_agent_id && publishedAgentIdSet.has(phone.outbound_agent_id) ? publishedVersions.get(phone.outbound_agent_id) : undefined;
135500
- if (inboundVersion != null || outboundVersion != null) {
135501
- updates.push({
135502
- phoneNumber: phone.phone_number,
135503
- inboundVersion,
135504
- outboundVersion
135505
- });
135506
- }
135507
- }
135508
- if (updates.length === 0) {
135509
- spinner.stop(source_default.dim("No phone numbers to update"));
135510
- return;
135511
- }
135512
- const updateResults = await Promise.allSettled(updates.map(async ({ phoneNumber, inboundVersion, outboundVersion }) => {
135513
- await retell.phoneNumber.update(phoneNumber, {
135514
- ...inboundVersion != null && {
135515
- inbound_agent_version: inboundVersion
135516
- },
135517
- ...outboundVersion != null && {
135518
- outbound_agent_version: outboundVersion
135519
- }
135520
- });
135521
- return phoneNumber;
135522
- }));
135523
- spinner.stop(source_default.dim("Done"));
135524
- let updatedCount = 0;
135525
- for (const result of updateResults) {
135526
- if (result.status === "fulfilled") {
135527
- const phone = updates.find((u4) => u4.phoneNumber === result.value);
135528
- const agentInfo = [];
135529
- if (phone?.inboundVersion != null) {
135530
- const inboundPhone = phoneNumbers.find((p4) => p4.phone_number === result.value);
135531
- const agentId = inboundPhone?.inbound_agent_id;
135532
- const name = agentId ? agentNames.get(agentId) ?? agentId : "unknown";
135533
- agentInfo.push(`inbound: ${name} v${phone.inboundVersion}`);
135534
- }
135535
- if (phone?.outboundVersion != null) {
135536
- const outboundPhone = phoneNumbers.find((p4) => p4.phone_number === result.value);
135537
- const agentId = outboundPhone?.outbound_agent_id;
135538
- const name = agentId ? agentNames.get(agentId) ?? agentId : "unknown";
135539
- agentInfo.push(`outbound: ${name} v${phone.outboundVersion}`);
135540
- }
135541
- console.log(source_default.green(`Updated ${source_default.bold(result.value)} (${agentInfo.join(", ")})`));
135542
- updatedCount++;
135543
- } else {
135544
- console.log(source_default.red(`Failed to update phone number: ${result.reason}`));
135545
- }
135546
- }
135547
- console.log(source_default.green(`Updated ${pluralize("phone number", updatedCount, true)}`));
135548
- }
135549
135443
  function computeChanges(local, remote) {
135550
135444
  const changes = {
135551
135445
  agents: [],
@@ -135559,7 +135453,9 @@ function computeChanges(local, remote) {
135559
135453
  const remoteAgent = remoteAgents.get(agent._id);
135560
135454
  if (!remoteAgent)
135561
135455
  continue;
135562
- const differences = diff(remoteAgent, agent);
135456
+ const { response_engine: _localRe, ...localComparable } = agent;
135457
+ const { response_engine: _remoteRe, ...remoteComparable } = remoteAgent;
135458
+ const differences = diff(remoteComparable, localComparable);
135563
135459
  if (differences.length > 0) {
135564
135460
  changes.agents.push({
135565
135461
  id: agent._id,
@@ -135683,9 +135579,238 @@ Flows to update:`));
135683
135579
  }
135684
135580
  }
135685
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
+ });
135595
+ } catch (err) {
135596
+ if (err instanceof ExitPromptError) {
135597
+ console.log(source_default.dim("Aborted"));
135598
+ return;
135599
+ }
135600
+ throw err;
135601
+ }
135602
+ }
135603
+ async function publish({
135604
+ agentsDir = DEFAULT_AGENTS_DIR,
135605
+ configFormat = DEFAULT_CONFIG_FORMAT,
135606
+ agentIds = null
135607
+ } = {}) {
135608
+ const scopeLabel = agentIds ? `${agentIds.length} agent(s)` : "all agents";
135609
+ console.log(source_default.bold(`Checking ${scopeLabel} for unpublished changes...`));
135610
+ let spinner = createSpinner("Fetching draft and published states...");
135611
+ const [draftState, publishedState] = await Promise.all([
135612
+ getLocalState({ agentsDir, agentIds }),
135613
+ getRemoteState({ draft: false, agentIds })
135614
+ ]);
135615
+ spinner.stop(source_default.dim("Done"));
135616
+ const agentNames = new Map(draftState.voiceAgents.map((a7) => [a7._id, a7.agent_name ?? a7._id]));
135617
+ spinner = createSpinner("Comparing draft vs published...");
135618
+ const changes = computeChanges2(draftState, publishedState);
135619
+ 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`));
135620
+ const agentIdsToPublish = new Set;
135621
+ for (const change of changes.agents) {
135622
+ agentIdsToPublish.add(change.id);
135623
+ }
135624
+ const changedLlmIds = new Set(changes.llms.map((c7) => c7.id));
135625
+ for (const agent of draftState.voiceAgents) {
135626
+ if (agent.response_engine.type === "retell-llm" && changedLlmIds.has(agent.response_engine.llm_id)) {
135627
+ agentIdsToPublish.add(agent._id);
135628
+ }
135629
+ }
135630
+ const changedFlowIds = new Set(changes.flows.map((c7) => c7.id));
135631
+ for (const agent of draftState.voiceAgents) {
135632
+ if (agent.response_engine.type === "conversation-flow" && changedFlowIds.has(agent.response_engine.conversation_flow_id)) {
135633
+ agentIdsToPublish.add(agent._id);
135634
+ }
135635
+ }
135636
+ if (agentIdsToPublish.size === 0) {
135637
+ console.log(source_default.green("All agents are already up to date"));
135638
+ return;
135639
+ }
135640
+ console.log(source_default.bold(`Publishing ${pluralize("agent", agentIdsToPublish.size, true)}...`));
135641
+ spinner = createSpinner(`Publishing ${agentIdsToPublish.size} agents...`);
135642
+ const publishResults = await Promise.allSettled([...agentIdsToPublish].map(async (id2) => {
135643
+ await retell.agent.publish(id2);
135644
+ return { id: id2, name: agentNames.get(id2) ?? id2 };
135645
+ }));
135646
+ spinner.stop(source_default.dim("Done"));
135647
+ const publishedAgentIds = [];
135648
+ for (const result of publishResults) {
135649
+ if (result.status === "fulfilled") {
135650
+ console.log(source_default.green(`Published ${source_default.bold(result.value.name)}`));
135651
+ publishedAgentIds.push(result.value.id);
135652
+ } else {
135653
+ console.log(source_default.red(`Failed to publish: ${result.reason}`));
135654
+ }
135655
+ }
135656
+ console.log(source_default.green(`Published ${pluralize("agent", publishedAgentIds.length, true)}`));
135657
+ if (publishedAgentIds.length > 0) {
135658
+ await updatePhoneNumberVersions(publishedAgentIds, agentNames);
135659
+ }
135660
+ console.log(source_default.bold("Syncing latest state..."));
135661
+ await pull({ agentsDir, configFormat, agentIds });
135662
+ }
135663
+ function computeChanges2(draft, published) {
135664
+ const changes = {
135665
+ agents: [],
135666
+ llms: [],
135667
+ flows: []
135668
+ };
135669
+ const publishedAgents = new Map(published.voiceAgents.map((a7) => [a7._id, a7]));
135670
+ const publishedLLMs = new Map(published.llms.map((l5) => [l5._id, l5]));
135671
+ const publishedFlows = new Map(published.conversationFlows.map((f7) => [f7._id, f7]));
135672
+ for (const agent of draft.voiceAgents) {
135673
+ const publishedAgent = publishedAgents.get(agent._id);
135674
+ if (!publishedAgent) {
135675
+ changes.agents.push({
135676
+ id: agent._id,
135677
+ name: agent.agent_name ?? agent._id,
135678
+ current: agent,
135679
+ differences: [{ type: "CREATE", path: [], value: agent }]
135680
+ });
135681
+ continue;
135682
+ }
135683
+ const { response_engine: _draftRe, ...draftComparable } = agent;
135684
+ const { response_engine: _pubRe, ...publishedComparable } = publishedAgent;
135685
+ const differences = diff(publishedComparable, draftComparable);
135686
+ if (differences.length > 0) {
135687
+ changes.agents.push({
135688
+ id: agent._id,
135689
+ name: agent.agent_name ?? agent._id,
135690
+ current: agent,
135691
+ differences
135692
+ });
135693
+ }
135694
+ }
135695
+ for (const llm of draft.llms) {
135696
+ const publishedLLM = publishedLLMs.get(llm._id);
135697
+ if (!publishedLLM) {
135698
+ changes.llms.push({
135699
+ id: llm._id,
135700
+ name: llm._id,
135701
+ current: llm,
135702
+ differences: [{ type: "CREATE", path: [], value: llm }]
135703
+ });
135704
+ continue;
135705
+ }
135706
+ const differences = diff(publishedLLM, llm);
135707
+ if (differences.length > 0) {
135708
+ changes.llms.push({
135709
+ id: llm._id,
135710
+ name: llm._id,
135711
+ current: llm,
135712
+ differences
135713
+ });
135714
+ }
135715
+ }
135716
+ for (const flow of draft.conversationFlows) {
135717
+ const publishedFlow = publishedFlows.get(flow._id);
135718
+ if (!publishedFlow) {
135719
+ changes.flows.push({
135720
+ id: flow._id,
135721
+ name: flow._id,
135722
+ current: flow,
135723
+ differences: [{ type: "CREATE", path: [], value: flow }]
135724
+ });
135725
+ continue;
135726
+ }
135727
+ const differences = diff(publishedFlow, flow);
135728
+ if (differences.length > 0) {
135729
+ changes.flows.push({
135730
+ id: flow._id,
135731
+ name: flow._id,
135732
+ current: flow,
135733
+ differences
135734
+ });
135735
+ }
135736
+ }
135737
+ return changes;
135738
+ }
135739
+ async function updatePhoneNumberVersions(publishedAgentIds, agentNames) {
135740
+ const spinner = createSpinner("Updating phone numbers...");
135741
+ const [phoneNumbers, ...agentVersionLists] = await Promise.all([
135742
+ retell.phoneNumber.list(),
135743
+ ...publishedAgentIds.map((id2) => retell.agent.getVersions(id2))
135744
+ ]);
135745
+ const publishedVersions = new Map;
135746
+ for (const [i5, agentId] of publishedAgentIds.entries()) {
135747
+ const versions2 = agentVersionLists[i5];
135748
+ if (!versions2)
135749
+ continue;
135750
+ const latestPublished = versions2.filter((v10) => v10.is_published).sort((a7, b7) => (b7.version ?? 0) - (a7.version ?? 0))[0];
135751
+ if (latestPublished?.version != null) {
135752
+ publishedVersions.set(agentId, latestPublished.version);
135753
+ }
135754
+ }
135755
+ const publishedAgentIdSet = new Set(publishedAgentIds);
135756
+ const updates = [];
135757
+ for (const phone of phoneNumbers) {
135758
+ const inboundVersion = phone.inbound_agent_id && publishedAgentIdSet.has(phone.inbound_agent_id) ? publishedVersions.get(phone.inbound_agent_id) : undefined;
135759
+ const outboundVersion = phone.outbound_agent_id && publishedAgentIdSet.has(phone.outbound_agent_id) ? publishedVersions.get(phone.outbound_agent_id) : undefined;
135760
+ if (inboundVersion != null || outboundVersion != null) {
135761
+ updates.push({
135762
+ phoneNumber: phone.phone_number,
135763
+ inboundVersion,
135764
+ outboundVersion
135765
+ });
135766
+ }
135767
+ }
135768
+ if (updates.length === 0) {
135769
+ spinner.stop(source_default.dim("No phone numbers to update"));
135770
+ return;
135771
+ }
135772
+ const updateResults = await Promise.allSettled(updates.map(async ({ phoneNumber, inboundVersion, outboundVersion }) => {
135773
+ await retell.phoneNumber.update(phoneNumber, {
135774
+ ...inboundVersion != null && {
135775
+ inbound_agent_version: inboundVersion
135776
+ },
135777
+ ...outboundVersion != null && {
135778
+ outbound_agent_version: outboundVersion
135779
+ }
135780
+ });
135781
+ return phoneNumber;
135782
+ }));
135783
+ spinner.stop(source_default.dim("Done"));
135784
+ let updatedCount = 0;
135785
+ for (const result of updateResults) {
135786
+ if (result.status === "fulfilled") {
135787
+ const phone = updates.find((u4) => u4.phoneNumber === result.value);
135788
+ const agentInfo = [];
135789
+ if (phone?.inboundVersion != null) {
135790
+ const inboundPhone = phoneNumbers.find((p4) => p4.phone_number === result.value);
135791
+ const agentId = inboundPhone?.inbound_agent_id;
135792
+ const name = agentId ? agentNames.get(agentId) ?? agentId : "unknown";
135793
+ agentInfo.push(`inbound: ${name} v${phone.inboundVersion}`);
135794
+ }
135795
+ if (phone?.outboundVersion != null) {
135796
+ const outboundPhone = phoneNumbers.find((p4) => p4.phone_number === result.value);
135797
+ const agentId = outboundPhone?.outbound_agent_id;
135798
+ const name = agentId ? agentNames.get(agentId) ?? agentId : "unknown";
135799
+ agentInfo.push(`outbound: ${name} v${phone.outboundVersion}`);
135800
+ }
135801
+ console.log(source_default.green(`Updated ${source_default.bold(result.value)} (${agentInfo.join(", ")})`));
135802
+ updatedCount++;
135803
+ } else {
135804
+ console.log(source_default.red(`Failed to update phone number: ${result.reason}`));
135805
+ }
135806
+ }
135807
+ console.log(source_default.green(`Updated ${pluralize("phone number", updatedCount, true)}`));
135808
+ }
135809
+
135686
135810
  // src/cli.ts
135687
135811
  var program2 = new Command;
135688
135812
  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);
135689
135813
  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);
135690
- 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);
135814
+ 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);
135815
+ 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").action(publishCommand);
135691
135816
  program2.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "retell-sync-cli",
3
- "version": "2.1.0",
3
+ "version": "3.0.0",
4
4
  "description": "CLI tool for syncing Retell AI agents between local filesystem and API",
5
5
  "keywords": [
6
6
  "agents",