retell-cli 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -25,8 +25,8 @@ 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_fs4 = require("fs");
29
- var import_path4 = require("path");
28
+ var import_fs5 = require("fs");
29
+ var import_path6 = require("path");
30
30
 
31
31
  // src/commands/login.ts
32
32
  var readline = __toESM(require("readline/promises"));
@@ -133,6 +133,84 @@ function configFileExists() {
133
133
 
134
134
  // src/services/output-formatter.ts
135
135
  var import_retell_sdk = __toESM(require("retell-sdk"));
136
+ var DANGEROUS_KEYS = ["__proto__", "constructor", "prototype"];
137
+ var ARRAY_INDEX_PATTERN = /^\d+$/;
138
+ function validateSafeKey(key) {
139
+ if (DANGEROUS_KEYS.includes(key)) {
140
+ throw new Error(`Dangerous key detected in path: ${key}`);
141
+ }
142
+ }
143
+ function hasNestedPath(obj, path) {
144
+ if (obj === null || obj === void 0 || typeof obj !== "object") {
145
+ return false;
146
+ }
147
+ const keys = path.split(".");
148
+ for (const key of keys) {
149
+ validateSafeKey(key);
150
+ }
151
+ let current = obj;
152
+ for (const key of keys) {
153
+ if (current === null || current === void 0) {
154
+ return false;
155
+ }
156
+ if (Array.isArray(current)) {
157
+ const index = parseInt(key, 10);
158
+ if (isNaN(index) || index < 0 || index >= current.length) {
159
+ return false;
160
+ }
161
+ current = current[index];
162
+ } else if (typeof current === "object") {
163
+ if (!(key in current)) {
164
+ return false;
165
+ }
166
+ current = current[key];
167
+ } else {
168
+ return false;
169
+ }
170
+ }
171
+ return true;
172
+ }
173
+ function getNestedValue(obj, path) {
174
+ if (!obj || typeof obj !== "object") {
175
+ return void 0;
176
+ }
177
+ const keys = path.split(".");
178
+ let current = obj;
179
+ for (const key of keys) {
180
+ if (current === null || current === void 0) {
181
+ return void 0;
182
+ }
183
+ if (Array.isArray(current)) {
184
+ const index = parseInt(key, 10);
185
+ if (isNaN(index) || index < 0 || index >= current.length) {
186
+ return void 0;
187
+ }
188
+ current = current[index];
189
+ } else if (typeof current === "object") {
190
+ current = current[key];
191
+ } else {
192
+ return void 0;
193
+ }
194
+ }
195
+ return current;
196
+ }
197
+ function setNestedValue(obj, path, value) {
198
+ const keys = path.split(".");
199
+ const lastKey = keys.pop();
200
+ validateSafeKey(lastKey);
201
+ let current = obj;
202
+ for (let i = 0; i < keys.length; i++) {
203
+ const key = keys[i];
204
+ validateSafeKey(key);
205
+ if (!(key in current) || typeof current[key] !== "object" || current[key] === null) {
206
+ const nextKey = keys[i + 1];
207
+ const isNextKeyArrayIndex = nextKey && ARRAY_INDEX_PATTERN.test(nextKey);
208
+ current[key] = isNextKeyArrayIndex ? [] : {};
209
+ }
210
+ current = current[key];
211
+ }
212
+ current[lastKey] = value;
213
+ }
136
214
  function outputJson(data) {
137
215
  console.log(JSON.stringify(data, null, 2));
138
216
  }
@@ -178,10 +256,48 @@ function handleSdkError(error) {
178
256
  outputError(message, "API_ERROR");
179
257
  }
180
258
  if (error instanceof Error) {
259
+ if (error.name === "ValidationError") {
260
+ outputError(error.message, "VALIDATION_ERROR");
261
+ }
181
262
  outputError(error.message, "UNKNOWN_ERROR");
182
263
  }
183
264
  outputError("An unexpected error occurred", "UNKNOWN_ERROR");
184
265
  }
266
+ function filterFields(data, fields, options = {}) {
267
+ if (data === null || data === void 0) {
268
+ return data;
269
+ }
270
+ if (Array.isArray(data)) {
271
+ return data.map((item) => filterFields(item, fields, options));
272
+ }
273
+ if (typeof data !== "object") {
274
+ return data;
275
+ }
276
+ const result = {};
277
+ const warnings = [];
278
+ for (const field of fields) {
279
+ if (!hasNestedPath(data, field)) {
280
+ const availableFields = Object.keys(data);
281
+ const fieldList = availableFields.length > 0 ? `Available fields: ${availableFields.join(", ")}` : "No fields available";
282
+ const warning = `Field '${field}' not found in data. ${fieldList}`;
283
+ warnings.push(warning);
284
+ if (options.strict) {
285
+ throw new Error(warning);
286
+ }
287
+ continue;
288
+ }
289
+ const value = getNestedValue(data, field);
290
+ setNestedValue(result, field, value);
291
+ }
292
+ if (warnings.length > 0 && !options.strict) {
293
+ if (process.env.DEBUG) {
294
+ console.warn(JSON.stringify({ warnings }, null, 2));
295
+ } else {
296
+ warnings.forEach((warning) => console.warn(`Warning: ${warning}`));
297
+ }
298
+ }
299
+ return result;
300
+ }
185
301
 
186
302
  // src/commands/login.ts
187
303
  async function loginCommand() {
@@ -259,24 +375,33 @@ async function listTranscriptsCommand(options) {
259
375
  const calls = await client.call.list({
260
376
  limit: options.limit || 50
261
377
  });
262
- outputJson(calls);
378
+ const output = options.fields ? filterFields(calls, options.fields.split(",").map((f) => f.trim())) : calls;
379
+ outputJson(output);
263
380
  } catch (error) {
264
381
  handleSdkError(error);
265
382
  }
266
383
  }
267
384
 
268
385
  // src/commands/transcripts/get.ts
269
- async function getTranscriptCommand(callId) {
386
+ async function getTranscriptCommand(callId, options = {}) {
270
387
  try {
271
388
  const client = getRetellClient();
272
389
  const call = await client.call.retrieve(callId);
273
- outputJson(call);
390
+ const output = options.fields ? filterFields(call, options.fields.split(",").map((f) => f.trim())) : call;
391
+ outputJson(output);
274
392
  } catch (error) {
275
393
  handleSdkError(error);
276
394
  }
277
395
  }
278
396
 
279
397
  // src/commands/transcripts/analyze.ts
398
+ var DEFAULT_LATENCY_THRESHOLD = 2e3;
399
+ var DEFAULT_SILENCE_THRESHOLD = 5e3;
400
+ function validateThreshold(threshold, name) {
401
+ if (!Number.isInteger(threshold) || threshold <= 0) {
402
+ throw new Error(`${name} must be a positive integer (got: ${threshold})`);
403
+ }
404
+ }
280
405
  function extractTranscriptTurns(transcriptObject) {
281
406
  if (!transcriptObject || !Array.isArray(transcriptObject)) {
282
407
  return [];
@@ -287,10 +412,122 @@ function extractTranscriptTurns(transcriptObject) {
287
412
  word_count: turn.content ? turn.content.split(/\s+/).length : 0
288
413
  }));
289
414
  }
290
- async function analyzeTranscriptCommand(callId) {
415
+ function formatTimestamp(seconds) {
416
+ if (!seconds || seconds < 0)
417
+ return "N/A";
418
+ const totalSeconds = Math.floor(seconds);
419
+ const mins = Math.floor(totalSeconds / 60);
420
+ const secs = totalSeconds % 60;
421
+ const hrs = Math.floor(mins / 60);
422
+ const displayMins = mins % 60;
423
+ if (hrs > 0) {
424
+ return `${hrs.toString().padStart(2, "0")}:${displayMins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
425
+ }
426
+ return `${displayMins.toString().padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
427
+ }
428
+ function detectLatencySpikes(call, config) {
429
+ const hotspots = [];
430
+ if (call.latency?.e2e?.p90 && call.latency.e2e.p90 > config.latencyThreshold) {
431
+ hotspots.push({
432
+ turn_index: -1,
433
+ timestamp: "N/A",
434
+ issue_type: "latency_spike",
435
+ metrics: {
436
+ latency_p90_e2e: call.latency.e2e.p90,
437
+ latency_p90_llm: call.latency?.llm?.p90 || 0,
438
+ latency_p90_tts: call.latency?.tts?.p90 || 0
439
+ }
440
+ });
441
+ }
442
+ return hotspots;
443
+ }
444
+ function detectLongSilences(call, config) {
445
+ const hotspots = [];
446
+ const transcript = call.transcript_object || [];
447
+ for (let i = 1; i < transcript.length; i++) {
448
+ const prevTurn = transcript[i - 1];
449
+ const currTurn = transcript[i];
450
+ const prevWords = prevTurn.words || [];
451
+ const currWords = currTurn.words || [];
452
+ if (prevWords.length === 0 || currWords.length === 0)
453
+ continue;
454
+ const prevEnd = prevWords[prevWords.length - 1]?.end;
455
+ const currStart = currWords[0]?.start;
456
+ if (prevEnd === void 0 || currStart === void 0)
457
+ continue;
458
+ const gapMs = (currStart - prevEnd) * 1e3;
459
+ if (gapMs > config.silenceThreshold) {
460
+ hotspots.push({
461
+ turn_index: i,
462
+ timestamp: formatTimestamp(currStart),
463
+ issue_type: "long_silence",
464
+ user_utterance: prevTurn.role === "user" ? prevTurn.content : currTurn.content,
465
+ agent_utterance: currTurn.role === "agent" ? currTurn.content : prevTurn.content,
466
+ metrics: {
467
+ silence_duration_ms: Math.round(gapMs)
468
+ }
469
+ });
470
+ }
471
+ }
472
+ return hotspots;
473
+ }
474
+ function detectSentimentIssues(call) {
475
+ const hotspots = [];
476
+ if (call.call_analysis?.user_sentiment === "Negative") {
477
+ hotspots.push({
478
+ turn_index: -1,
479
+ timestamp: "N/A",
480
+ issue_type: "sentiment",
481
+ metrics: {
482
+ sentiment: call.call_analysis.user_sentiment
483
+ }
484
+ });
485
+ }
486
+ return hotspots;
487
+ }
488
+ function detectAllHotspots(call, config) {
489
+ const hotspots = [
490
+ ...detectLatencySpikes(call, config),
491
+ ...detectLongSilences(call, config),
492
+ ...detectSentimentIssues(call)
493
+ ];
494
+ return hotspots.sort((a, b) => {
495
+ if (a.turn_index === -1)
496
+ return -1;
497
+ if (b.turn_index === -1)
498
+ return 1;
499
+ return a.turn_index - b.turn_index;
500
+ });
501
+ }
502
+ async function analyzeTranscriptCommand(callId, options = {}) {
291
503
  try {
504
+ if (options.hotspotsOnly) {
505
+ const latencyThreshold = options.latencyThreshold ?? DEFAULT_LATENCY_THRESHOLD;
506
+ const silenceThreshold = options.silenceThreshold ?? DEFAULT_SILENCE_THRESHOLD;
507
+ validateThreshold(latencyThreshold, "Latency threshold");
508
+ validateThreshold(silenceThreshold, "Silence threshold");
509
+ }
292
510
  const client = getRetellClient();
293
511
  const call = await client.call.retrieve(callId);
512
+ if (options.raw) {
513
+ const output2 = options.fields ? filterFields(call, options.fields.split(",").map((f) => f.trim())) : call;
514
+ outputJson(output2);
515
+ return;
516
+ }
517
+ if (options.hotspotsOnly) {
518
+ const config = {
519
+ latencyThreshold: options.latencyThreshold ?? DEFAULT_LATENCY_THRESHOLD,
520
+ silenceThreshold: options.silenceThreshold ?? DEFAULT_SILENCE_THRESHOLD
521
+ };
522
+ const hotspots = detectAllHotspots(call, config);
523
+ const result = {
524
+ call_id: callId,
525
+ hotspots
526
+ };
527
+ const output2 = options.fields ? filterFields(result, options.fields.split(",").map((f) => f.trim())) : result;
528
+ outputJson(output2);
529
+ return;
530
+ }
294
531
  const analysis = {
295
532
  call_id: callId,
296
533
  metadata: {
@@ -324,12 +561,154 @@ async function analyzeTranscriptCommand(callId) {
324
561
  breakdown: call.call_cost?.product_costs || []
325
562
  }
326
563
  };
327
- outputJson(analysis);
564
+ const output = options.fields ? filterFields(analysis, options.fields.split(",").map((f) => f.trim())) : analysis;
565
+ outputJson(output);
328
566
  } catch (error) {
329
567
  handleSdkError(error);
330
568
  }
331
569
  }
332
570
 
571
+ // src/commands/transcripts/search.ts
572
+ var import_zod2 = require("zod");
573
+ var MAX_LIMIT = 1e3;
574
+ var DEFAULT_LIMIT = 50;
575
+ var CallSchema = import_zod2.z.object({
576
+ call_id: import_zod2.z.string(),
577
+ call_status: import_zod2.z.string().optional(),
578
+ agent_id: import_zod2.z.string().optional(),
579
+ start_timestamp: import_zod2.z.number().optional(),
580
+ end_timestamp: import_zod2.z.number().optional()
581
+ // Allow additional fields (the API may return more fields than we validate)
582
+ }).passthrough();
583
+ var CallListSchema = import_zod2.z.array(CallSchema);
584
+ var ValidationError = class extends Error {
585
+ constructor(message) {
586
+ super(message);
587
+ this.name = "ValidationError";
588
+ }
589
+ };
590
+ function parseDate(dateStr) {
591
+ if (!dateStr) {
592
+ throw new ValidationError("Date string cannot be empty");
593
+ }
594
+ const dateOnlyPattern = /^\d{4}-\d{2}-\d{2}$/;
595
+ const iso8601Pattern = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
596
+ if (!iso8601Pattern.test(dateStr)) {
597
+ throw new ValidationError(`Invalid date format: "${dateStr}". Use YYYY-MM-DD or ISO 8601 format.`);
598
+ }
599
+ const date = new Date(dateStr);
600
+ if (isNaN(date.getTime())) {
601
+ throw new ValidationError(`Invalid date format: "${dateStr}". Use YYYY-MM-DD or ISO 8601 format.`);
602
+ }
603
+ return { date, isDateOnly: dateOnlyPattern.test(dateStr) };
604
+ }
605
+ function validateSearchOptions(options) {
606
+ const validStatuses = ["error", "ended", "ongoing"];
607
+ if (options.status && !validStatuses.includes(options.status)) {
608
+ throw new ValidationError(
609
+ `Invalid status: "${options.status}". Valid options: ${validStatuses.join(", ")}`
610
+ );
611
+ }
612
+ const parsedDates = {};
613
+ if (options.since !== void 0) {
614
+ parsedDates.sinceDate = parseDate(options.since);
615
+ }
616
+ if (options.until !== void 0) {
617
+ parsedDates.untilDate = parseDate(options.until);
618
+ if (parsedDates.untilDate.isDateOnly) {
619
+ const date = new Date(parsedDates.untilDate.date.getTime());
620
+ date.setUTCHours(23, 59, 59, 999);
621
+ parsedDates.untilDate.date = date;
622
+ }
623
+ }
624
+ if (parsedDates.sinceDate && parsedDates.untilDate) {
625
+ if (parsedDates.sinceDate.date > parsedDates.untilDate.date) {
626
+ throw new ValidationError(
627
+ `Invalid date range: --since ("${options.since}") is after --until ("${options.until}")`
628
+ );
629
+ }
630
+ }
631
+ if (options.limit !== void 0 && (options.limit < 1 || !Number.isInteger(options.limit))) {
632
+ throw new ValidationError(`Limit must be a positive integer (got: "${options.limit}")`);
633
+ }
634
+ if (options.limit !== void 0 && options.limit > MAX_LIMIT) {
635
+ throw new ValidationError(`Limit cannot exceed ${MAX_LIMIT} (got: "${options.limit}")`);
636
+ }
637
+ return parsedDates;
638
+ }
639
+ async function searchTranscripts(options, parsedDates) {
640
+ const client = getRetellClient();
641
+ const apiParams = {
642
+ limit: options.limit || DEFAULT_LIMIT,
643
+ sort_order: "descending"
644
+ // Most recent first
645
+ };
646
+ const filterCriteria = {};
647
+ if (options.status) {
648
+ filterCriteria.call_status = [options.status];
649
+ }
650
+ if (options.agentId) {
651
+ filterCriteria.agent_id = [options.agentId];
652
+ }
653
+ if (parsedDates.sinceDate || parsedDates.untilDate) {
654
+ filterCriteria.start_timestamp = {};
655
+ if (parsedDates.sinceDate) {
656
+ filterCriteria.start_timestamp.lower_threshold = parsedDates.sinceDate.date.getTime();
657
+ }
658
+ if (parsedDates.untilDate) {
659
+ filterCriteria.start_timestamp.upper_threshold = parsedDates.untilDate.date.getTime();
660
+ }
661
+ }
662
+ if (Object.keys(filterCriteria).length > 0) {
663
+ apiParams.filter_criteria = filterCriteria;
664
+ }
665
+ const response = await client.call.list(apiParams);
666
+ const validation = CallListSchema.safeParse(response);
667
+ if (!validation.success) {
668
+ console.warn("Warning: API response validation failed:", validation.error.message);
669
+ throw new Error(`Invalid API response format: ${validation.error.message}`);
670
+ }
671
+ const results = validation.data;
672
+ const requestedLimit = options.limit || DEFAULT_LIMIT;
673
+ const result = {
674
+ results,
675
+ total_count: results.length,
676
+ filters_applied: {
677
+ ...options.status && { status: options.status },
678
+ ...options.agentId && { agent_id: options.agentId },
679
+ ...options.since && { since: options.since },
680
+ ...options.until && { until: options.until },
681
+ limit: requestedLimit
682
+ }
683
+ };
684
+ if (results.length === requestedLimit && results.length > 0) {
685
+ console.warn(
686
+ `Warning: Results limited to ${results.length}. There may be additional results available. Use --limit to increase (max: ${MAX_LIMIT}).`
687
+ );
688
+ }
689
+ return result;
690
+ }
691
+ async function searchTranscriptsCommand(options = {}) {
692
+ try {
693
+ const parsedDates = validateSearchOptions(options);
694
+ const searchResult = await searchTranscripts(options, parsedDates);
695
+ const output = options.fields ? {
696
+ results: searchResult.results.map(
697
+ (r) => filterFields(r, options.fields.split(",").map((f) => f.trim()))
698
+ ),
699
+ total_count: searchResult.total_count,
700
+ filters_applied: searchResult.filters_applied
701
+ } : searchResult;
702
+ outputJson(output);
703
+ } catch (error) {
704
+ if (error instanceof ValidationError) {
705
+ handleSdkError(error);
706
+ } else {
707
+ handleSdkError(error);
708
+ }
709
+ }
710
+ }
711
+
333
712
  // src/commands/agents/list.ts
334
713
  async function listAgentsCommand(options = {}) {
335
714
  try {
@@ -361,18 +740,20 @@ async function listAgentsCommand(options = {}) {
361
740
  response_engine_id
362
741
  };
363
742
  });
364
- outputJson(formatted);
743
+ const output = options.fields ? filterFields(formatted, options.fields.split(",").map((f) => f.trim())) : formatted;
744
+ outputJson(output);
365
745
  } catch (error) {
366
746
  handleSdkError(error);
367
747
  }
368
748
  }
369
749
 
370
750
  // src/commands/agents/info.ts
371
- async function agentInfoCommand(agentId) {
751
+ async function agentInfoCommand(agentId, options = {}) {
372
752
  try {
373
753
  const client = getRetellClient();
374
754
  const agent = await client.agent.retrieve(agentId);
375
- outputJson(agent);
755
+ const output = options.fields ? filterFields(agent, options.fields.split(",").map((f) => f.trim())) : agent;
756
+ outputJson(output);
376
757
  } catch (error) {
377
758
  handleSdkError(error);
378
759
  }
@@ -396,8 +777,8 @@ async function resolvePromptSource(agentId) {
396
777
  llm_id: llm.llm_id,
397
778
  version: llm.version,
398
779
  general_prompt: llm.general_prompt,
399
- begin_message: llm.begin_message,
400
- states: llm.states
780
+ begin_message: llm.begin_message ?? void 0,
781
+ states: llm.states ?? void 0
401
782
  }
402
783
  };
403
784
  }
@@ -413,7 +794,7 @@ async function resolvePromptSource(agentId) {
413
794
  conversation_flow_id: flow.conversation_flow_id,
414
795
  version: flow.version,
415
796
  global_prompt: flow.global_prompt,
416
- nodes: flow.nodes
797
+ nodes: flow.nodes ?? []
417
798
  }
418
799
  };
419
800
  }
@@ -536,76 +917,45 @@ function getFilesCreated(type, promptSource) {
536
917
  }
537
918
 
538
919
  // src/commands/prompts/update.ts
920
+ var import_fs4 = require("fs");
921
+ var import_path4 = require("path");
922
+
923
+ // src/services/prompt-loader.ts
539
924
  var import_fs3 = require("fs");
540
925
  var import_path3 = require("path");
541
- function validateAgentId2(agentId) {
542
- if (agentId.includes("..") || agentId.includes("/") || agentId.includes("\\")) {
543
- throw new Error("Invalid agent ID: cannot contain path separators or traversal sequences");
926
+ function loadLocalPrompts(agentId, agentDir) {
927
+ if (!(0, import_fs3.existsSync)(agentDir)) {
928
+ throw new Error(`Prompts directory not found: ${agentDir}. Run 'retell prompts pull ${agentId}' first.`);
544
929
  }
545
- }
546
- async function updatePromptsCommand(agentId, options) {
930
+ const metadataPath = (0, import_path3.join)(agentDir, "metadata.json");
931
+ if (!(0, import_fs3.existsSync)(metadataPath)) {
932
+ throw new Error(`metadata.json not found in ${agentDir}. Directory may be corrupted.`);
933
+ }
934
+ let metadata;
547
935
  try {
548
- validateAgentId2(agentId);
549
- const baseDir = options.source || ".retell-prompts";
550
- const agentDir = (0, import_path3.join)(baseDir, agentId);
551
- if (!(0, import_fs3.existsSync)(agentDir)) {
552
- outputError(
553
- `Prompts directory not found: ${agentDir}. Run 'retell prompts pull ${agentId}' first.`,
554
- "DIRECTORY_NOT_FOUND"
555
- );
556
- return;
557
- }
558
- const metadataPath = (0, import_path3.join)(agentDir, "metadata.json");
559
- if (!(0, import_fs3.existsSync)(metadataPath)) {
560
- outputError(
561
- `metadata.json not found in ${agentDir}. Directory may be corrupted.`,
562
- "METADATA_NOT_FOUND"
563
- );
564
- return;
565
- }
566
- const metadata = JSON.parse((0, import_fs3.readFileSync)(metadataPath, "utf-8"));
567
- const promptSource = await resolvePromptSource(agentId);
568
- if (promptSource.type === "custom-llm") {
569
- outputError(promptSource.error, "CUSTOM_LLM_NOT_SUPPORTED");
570
- return;
571
- }
572
- if (metadata.type !== promptSource.type) {
573
- outputError(
574
- `Type mismatch: local files are ${metadata.type}, but agent uses ${promptSource.type}. Pull prompts again to sync.`,
575
- "TYPE_MISMATCH"
576
- );
577
- return;
578
- }
579
- const client = getRetellClient();
580
- if (promptSource.type === "retell-llm") {
581
- const prompts2 = loadRetellLlmPrompts(agentDir);
582
- await client.llm.update(promptSource.llmId, prompts2);
583
- outputJson({
584
- message: "Prompts updated successfully (draft version)",
585
- agent_id: agentId,
586
- agent_name: promptSource.agentName,
587
- type: "retell-llm",
588
- llm_id: promptSource.llmId,
589
- note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
590
- });
591
- } else if (promptSource.type === "conversation-flow") {
592
- const prompts2 = loadConversationFlowPrompts(agentDir);
593
- await client.conversationFlow.update(promptSource.flowId, prompts2);
594
- outputJson({
595
- message: "Prompts updated successfully (draft version)",
596
- agent_id: agentId,
597
- agent_name: promptSource.agentName,
598
- type: "conversation-flow",
599
- conversation_flow_id: promptSource.flowId,
600
- note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
601
- });
602
- }
936
+ metadata = JSON.parse((0, import_fs3.readFileSync)(metadataPath, "utf-8"));
603
937
  } catch (error) {
604
938
  if (error instanceof SyntaxError) {
605
- outputError(`Invalid JSON in file: ${error.message}`, "INVALID_JSON");
606
- return;
939
+ throw new Error(`Invalid JSON in metadata.json: ${error.message}`);
607
940
  }
608
- handleSdkError(error);
941
+ throw new Error(`Failed to read metadata.json: ${error.message}`);
942
+ }
943
+ if (metadata.type === "retell-llm") {
944
+ const prompts2 = loadRetellLlmPrompts(agentDir);
945
+ return {
946
+ type: "retell-llm",
947
+ metadata,
948
+ prompts: prompts2
949
+ };
950
+ } else if (metadata.type === "conversation-flow") {
951
+ const prompts2 = loadConversationFlowPrompts(agentDir);
952
+ return {
953
+ type: "conversation-flow",
954
+ metadata,
955
+ prompts: prompts2
956
+ };
957
+ } else {
958
+ throw new Error(`Unknown agent type in metadata: ${metadata.type}`);
609
959
  }
610
960
  }
611
961
  function loadRetellLlmPrompts(agentDir) {
@@ -694,6 +1044,311 @@ function loadConversationFlowPrompts(agentDir) {
694
1044
  return prompts2;
695
1045
  }
696
1046
 
1047
+ // src/services/prompt-diff.ts
1048
+ function deepEqual(obj1, obj2, maxDepth = 100) {
1049
+ if (obj1 === obj2)
1050
+ return true;
1051
+ if (obj1 === null || obj2 === null)
1052
+ return false;
1053
+ if (typeof obj1 !== "object" || typeof obj2 !== "object")
1054
+ return false;
1055
+ let currentDepth = 0;
1056
+ const sortedStringify = (obj) => {
1057
+ if (++currentDepth > maxDepth) {
1058
+ throw new Error(
1059
+ `Maximum depth (${maxDepth}) exceeded during comparison. This may indicate a circular reference or extremely deep nesting.`
1060
+ );
1061
+ }
1062
+ if (obj === null)
1063
+ return "null";
1064
+ if (typeof obj !== "object")
1065
+ return JSON.stringify(obj);
1066
+ if (Array.isArray(obj)) {
1067
+ return "[" + obj.map(sortedStringify).join(",") + "]";
1068
+ }
1069
+ const keys = Object.keys(obj).sort();
1070
+ const pairs = keys.map((k) => JSON.stringify(k) + ":" + sortedStringify(obj[k]));
1071
+ return "{" + pairs.join(",") + "}";
1072
+ };
1073
+ try {
1074
+ return sortedStringify(obj1) === sortedStringify(obj2);
1075
+ } catch (error) {
1076
+ if (error instanceof Error && error.message.includes("Maximum depth")) {
1077
+ console.warn("Warning:", error.message, "Falling back to reference equality.");
1078
+ return obj1 === obj2;
1079
+ }
1080
+ throw error;
1081
+ }
1082
+ }
1083
+ function generateDiff(agentId, localPrompts, remotePrompts) {
1084
+ if (remotePrompts.type === "custom-llm") {
1085
+ throw new Error("Cannot diff custom LLM agents");
1086
+ }
1087
+ if (localPrompts.type !== remotePrompts.type) {
1088
+ throw new Error(
1089
+ `Type mismatch: local files are ${localPrompts.type}, but agent uses ${remotePrompts.type}`
1090
+ );
1091
+ }
1092
+ if (localPrompts.type === "retell-llm" && remotePrompts.type === "retell-llm") {
1093
+ return generateRetellLlmDiff(agentId, localPrompts, remotePrompts);
1094
+ } else if (localPrompts.type === "conversation-flow" && remotePrompts.type === "conversation-flow") {
1095
+ return generateConversationFlowDiff(agentId, localPrompts, remotePrompts);
1096
+ }
1097
+ throw new Error(`Unsupported agent type: ${localPrompts.type}`);
1098
+ }
1099
+ function generateRetellLlmDiff(agentId, localPrompts, remotePrompts) {
1100
+ const changes = {};
1101
+ if (localPrompts.prompts.general_prompt !== remotePrompts.prompts.general_prompt) {
1102
+ changes.general_prompt = {
1103
+ old: remotePrompts.prompts.general_prompt,
1104
+ new: localPrompts.prompts.general_prompt,
1105
+ change_type: "modified"
1106
+ };
1107
+ }
1108
+ const localBeginMessage = localPrompts.prompts.begin_message || null;
1109
+ const remoteBeginMessage = remotePrompts.prompts.begin_message || null;
1110
+ if (localBeginMessage !== remoteBeginMessage) {
1111
+ if (localBeginMessage && !remoteBeginMessage) {
1112
+ changes.begin_message = {
1113
+ old: null,
1114
+ new: localBeginMessage,
1115
+ change_type: "added"
1116
+ };
1117
+ } else if (!localBeginMessage && remoteBeginMessage) {
1118
+ changes.begin_message = {
1119
+ old: remoteBeginMessage,
1120
+ new: null,
1121
+ change_type: "removed"
1122
+ };
1123
+ } else {
1124
+ changes.begin_message = {
1125
+ old: remoteBeginMessage,
1126
+ new: localBeginMessage,
1127
+ change_type: "modified"
1128
+ };
1129
+ }
1130
+ }
1131
+ const localStates = localPrompts.prompts.states || [];
1132
+ const remoteStates = remotePrompts.prompts.states || [];
1133
+ const localStatesMap = new Map(localStates.map((s) => [s.name, s.state_prompt]));
1134
+ const remoteStatesMap = new Map(remoteStates.map((s) => [s.name, s.state_prompt]));
1135
+ for (const [stateName, localPrompt] of localStatesMap) {
1136
+ const remotePrompt = remoteStatesMap.get(stateName);
1137
+ const fieldKey = `states.${stateName}`;
1138
+ if (remotePrompt === void 0) {
1139
+ changes[fieldKey] = {
1140
+ old: null,
1141
+ new: localPrompt,
1142
+ change_type: "added"
1143
+ };
1144
+ } else if (localPrompt !== remotePrompt) {
1145
+ changes[fieldKey] = {
1146
+ old: remotePrompt,
1147
+ new: localPrompt,
1148
+ change_type: "modified"
1149
+ };
1150
+ }
1151
+ }
1152
+ for (const [stateName, remotePrompt] of remoteStatesMap) {
1153
+ if (!localStatesMap.has(stateName)) {
1154
+ const fieldKey = `states.${stateName}`;
1155
+ changes[fieldKey] = {
1156
+ old: remotePrompt,
1157
+ new: null,
1158
+ change_type: "removed"
1159
+ };
1160
+ }
1161
+ }
1162
+ return {
1163
+ agent_id: agentId,
1164
+ agent_type: "retell-llm",
1165
+ has_changes: Object.keys(changes).length > 0,
1166
+ changes
1167
+ };
1168
+ }
1169
+ function generateConversationFlowDiff(agentId, localPrompts, remotePrompts) {
1170
+ const changes = {};
1171
+ if (localPrompts.prompts.global_prompt !== remotePrompts.prompts.global_prompt) {
1172
+ changes.global_prompt = {
1173
+ old: remotePrompts.prompts.global_prompt,
1174
+ new: localPrompts.prompts.global_prompt,
1175
+ change_type: "modified"
1176
+ };
1177
+ }
1178
+ const localNodes = localPrompts.prompts.nodes || [];
1179
+ const remoteNodes = remotePrompts.prompts.nodes || [];
1180
+ const localNodesMap = new Map(localNodes.map((n) => [n.id, n]));
1181
+ const remoteNodesMap = new Map(remoteNodes.map((n) => [n.id, n]));
1182
+ for (const [nodeId, localNode] of localNodesMap) {
1183
+ const remoteNode = remoteNodesMap.get(nodeId);
1184
+ const fieldKey = `nodes.${nodeId}`;
1185
+ if (remoteNode === void 0) {
1186
+ changes[fieldKey] = {
1187
+ old: null,
1188
+ new: localNode,
1189
+ change_type: "added"
1190
+ };
1191
+ } else {
1192
+ if (!deepEqual(localNode, remoteNode)) {
1193
+ changes[fieldKey] = {
1194
+ old: remoteNode,
1195
+ new: localNode,
1196
+ change_type: "modified"
1197
+ };
1198
+ }
1199
+ }
1200
+ }
1201
+ for (const [nodeId, remoteNode] of remoteNodesMap) {
1202
+ if (!localNodesMap.has(nodeId)) {
1203
+ const fieldKey = `nodes.${nodeId}`;
1204
+ changes[fieldKey] = {
1205
+ old: remoteNode,
1206
+ new: null,
1207
+ change_type: "removed"
1208
+ };
1209
+ }
1210
+ }
1211
+ return {
1212
+ agent_id: agentId,
1213
+ agent_type: "conversation-flow",
1214
+ has_changes: Object.keys(changes).length > 0,
1215
+ changes
1216
+ };
1217
+ }
1218
+
1219
+ // src/commands/prompts/update.ts
1220
+ function validateAgentId2(agentId) {
1221
+ if (agentId.includes("..") || agentId.includes("/") || agentId.includes("\\")) {
1222
+ throw new Error("Invalid agent ID: cannot contain path separators or traversal sequences");
1223
+ }
1224
+ }
1225
+ async function updatePromptsCommand(agentId, options) {
1226
+ try {
1227
+ validateAgentId2(agentId);
1228
+ const baseDir = options.source || ".retell-prompts";
1229
+ const agentDir = (0, import_path4.join)(baseDir, agentId);
1230
+ if (!(0, import_fs4.existsSync)(agentDir)) {
1231
+ outputError(
1232
+ `Prompts directory not found: ${agentDir}. Run 'retell prompts pull ${agentId}' first.`,
1233
+ "DIRECTORY_NOT_FOUND"
1234
+ );
1235
+ return;
1236
+ }
1237
+ const metadataPath = (0, import_path4.join)(agentDir, "metadata.json");
1238
+ if (!(0, import_fs4.existsSync)(metadataPath)) {
1239
+ outputError(
1240
+ `metadata.json not found in ${agentDir}. Directory may be corrupted.`,
1241
+ "METADATA_NOT_FOUND"
1242
+ );
1243
+ return;
1244
+ }
1245
+ const metadata = JSON.parse((0, import_fs4.readFileSync)(metadataPath, "utf-8"));
1246
+ const promptSource = await resolvePromptSource(agentId);
1247
+ if (promptSource.type === "custom-llm") {
1248
+ outputError(promptSource.error, "CUSTOM_LLM_NOT_SUPPORTED");
1249
+ return;
1250
+ }
1251
+ if (metadata.type !== promptSource.type) {
1252
+ outputError(
1253
+ `Type mismatch: local files are ${metadata.type}, but agent uses ${promptSource.type}. Pull prompts again to sync.`,
1254
+ "TYPE_MISMATCH"
1255
+ );
1256
+ return;
1257
+ }
1258
+ if (options.dryRun) {
1259
+ let localPrompts2;
1260
+ try {
1261
+ localPrompts2 = loadLocalPrompts(agentId, agentDir);
1262
+ } catch (error) {
1263
+ outputError(error.message, "LOCAL_PROMPTS_ERROR");
1264
+ return;
1265
+ }
1266
+ let diff;
1267
+ try {
1268
+ diff = generateDiff(agentId, localPrompts2, promptSource);
1269
+ } catch (error) {
1270
+ outputError(error.message, "DIFF_GENERATION_ERROR");
1271
+ return;
1272
+ }
1273
+ outputJson({
1274
+ message: "Dry run - no changes applied",
1275
+ ...diff
1276
+ });
1277
+ return;
1278
+ }
1279
+ let localPrompts;
1280
+ try {
1281
+ localPrompts = loadLocalPrompts(agentId, agentDir);
1282
+ } catch (error) {
1283
+ outputError(error.message, "LOCAL_PROMPTS_ERROR");
1284
+ return;
1285
+ }
1286
+ const client = getRetellClient();
1287
+ if (promptSource.type === "retell-llm" && localPrompts.type === "retell-llm") {
1288
+ await client.llm.update(promptSource.llmId, localPrompts.prompts);
1289
+ outputJson({
1290
+ message: "Prompts updated successfully (draft version)",
1291
+ agent_id: agentId,
1292
+ agent_name: promptSource.agentName,
1293
+ type: "retell-llm",
1294
+ llm_id: promptSource.llmId,
1295
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
1296
+ });
1297
+ } else if (promptSource.type === "conversation-flow" && localPrompts.type === "conversation-flow") {
1298
+ await client.conversationFlow.update(promptSource.flowId, localPrompts.prompts);
1299
+ outputJson({
1300
+ message: "Prompts updated successfully (draft version)",
1301
+ agent_id: agentId,
1302
+ agent_name: promptSource.agentName,
1303
+ type: "conversation-flow",
1304
+ conversation_flow_id: promptSource.flowId,
1305
+ note: `Run 'retell agent-publish ${agentId}' to publish changes to production`
1306
+ });
1307
+ }
1308
+ } catch (error) {
1309
+ if (error instanceof SyntaxError) {
1310
+ outputError(`Invalid JSON in file: ${error.message}`, "INVALID_JSON");
1311
+ return;
1312
+ }
1313
+ handleSdkError(error);
1314
+ }
1315
+ }
1316
+
1317
+ // src/commands/prompts/diff.ts
1318
+ var import_path5 = require("path");
1319
+ function validateAgentId3(agentId) {
1320
+ if (agentId.includes("..") || agentId.includes("/") || agentId.includes("\\")) {
1321
+ throw new Error("Invalid agent ID: cannot contain path separators or traversal sequences");
1322
+ }
1323
+ }
1324
+ async function diffPromptsCommand(agentId, options) {
1325
+ try {
1326
+ validateAgentId3(agentId);
1327
+ const baseDir = options.source || ".retell-prompts";
1328
+ const agentDir = (0, import_path5.join)(baseDir, agentId);
1329
+ const localPrompts = loadLocalPrompts(agentId, agentDir);
1330
+ const remotePrompts = await resolvePromptSource(agentId);
1331
+ if (remotePrompts.type === "custom-llm") {
1332
+ outputError(remotePrompts.error, "CUSTOM_LLM_NOT_SUPPORTED");
1333
+ }
1334
+ if (localPrompts.type !== remotePrompts.type) {
1335
+ outputError(
1336
+ `Type mismatch: local files are ${localPrompts.type}, but agent uses ${remotePrompts.type}. Pull prompts again to sync.`,
1337
+ "TYPE_MISMATCH"
1338
+ );
1339
+ }
1340
+ const diff = generateDiff(agentId, localPrompts, remotePrompts);
1341
+ let output = diff;
1342
+ if (options.fields) {
1343
+ const fieldList = options.fields.split(",").map((f) => f.trim());
1344
+ output = filterFields(diff, fieldList);
1345
+ }
1346
+ outputJson(output);
1347
+ } catch (error) {
1348
+ handleSdkError(error);
1349
+ }
1350
+ }
1351
+
697
1352
  // src/commands/agent/publish.ts
698
1353
  async function publishAgentCommand(agentId) {
699
1354
  try {
@@ -701,10 +1356,10 @@ async function publishAgentCommand(agentId) {
701
1356
  const result = await client.agent.publish(agentId);
702
1357
  outputJson({
703
1358
  message: "Agent published successfully",
704
- agent_id: result.agent_id,
705
- agent_name: result.agent_name,
706
- version: result.version,
707
- is_published: result.is_published,
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,
708
1363
  note: "Draft version incremented and ready for new changes"
709
1364
  });
710
1365
  } catch (error) {
@@ -714,7 +1369,7 @@ async function publishAgentCommand(agentId) {
714
1369
 
715
1370
  // src/index.ts
716
1371
  var packageJson = JSON.parse(
717
- (0, import_fs4.readFileSync)((0, import_path4.join)(__dirname, "../package.json"), "utf-8")
1372
+ (0, import_fs5.readFileSync)((0, import_path6.join)(__dirname, "../package.json"), "utf-8")
718
1373
  );
719
1374
  var program = new import_commander.Command();
720
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);
@@ -727,10 +1382,11 @@ Examples:
727
1382
  await loginCommand();
728
1383
  });
729
1384
  var transcripts = program.command("transcripts").description("Manage call transcripts");
730
- transcripts.command("list").description("List all call transcripts").option("-l, --limit <number>", "Maximum number of calls to return (default: 50)", "50").addHelpText("after", `
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", `
731
1386
  Examples:
732
1387
  $ retell transcripts list
733
1388
  $ retell transcripts list --limit 100
1389
+ $ retell transcripts list --fields call_id,call_status
734
1390
  $ retell transcripts list | jq '.[] | select(.call_status == "error")'
735
1391
  `).action(async (options) => {
736
1392
  const limit = parseInt(options.limit, 10);
@@ -739,28 +1395,62 @@ Examples:
739
1395
  process.exit(1);
740
1396
  }
741
1397
  await listTranscriptsCommand({
742
- limit
1398
+ limit,
1399
+ fields: options.fields
743
1400
  });
744
1401
  });
745
- transcripts.command("get <call_id>").description("Get a specific call transcript").addHelpText("after", `
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", `
746
1403
  Examples:
747
1404
  $ retell transcripts get call_abc123
1405
+ $ retell transcripts get call_abc123 --fields call_id,metadata.duration
748
1406
  $ retell transcripts get call_abc123 | jq '.transcript_object'
749
- `).action(async (callId) => {
750
- await getTranscriptCommand(callId);
1407
+ `).action(async (callId, options) => {
1408
+ await getTranscriptCommand(callId, {
1409
+ fields: options.fields
1410
+ });
751
1411
  });
752
- transcripts.command("analyze <call_id>").description("Analyze a call transcript with performance metrics and insights").addHelpText("after", `
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", `
753
1413
  Examples:
754
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
755
1421
  $ retell transcripts analyze call_abc123 | jq '.performance.latency_p50_ms'
756
- `).action(async (callId) => {
757
- await analyzeTranscriptCommand(callId);
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
+ });
758
1447
  });
759
1448
  var agents = program.command("agents").description("Manage agents");
760
- agents.command("list").description("List all agents").option("-l, --limit <number>", "Maximum number of agents to return (default: 100)", "100").addHelpText("after", `
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", `
761
1450
  Examples:
762
1451
  $ retell agents list
763
1452
  $ retell agents list --limit 10
1453
+ $ retell agents list --fields agent_id,agent_name
764
1454
  $ retell agents list | jq '.[] | select(.response_engine.type == "retell-llm")'
765
1455
  `).action(async (options) => {
766
1456
  const limit = parseInt(options.limit, 10);
@@ -769,15 +1459,19 @@ Examples:
769
1459
  process.exit(1);
770
1460
  }
771
1461
  await listAgentsCommand({
772
- limit
1462
+ limit,
1463
+ fields: options.fields
773
1464
  });
774
1465
  });
775
- agents.command("info <agent_id>").description("Get detailed agent information").addHelpText("after", `
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", `
776
1467
  Examples:
777
1468
  $ retell agents info agent_123abc
1469
+ $ retell agents info agent_123abc --fields agent_name,response_engine.type
778
1470
  $ retell agents info agent_123abc | jq '.response_engine.type'
779
- `).action(async (agentId) => {
780
- await agentInfoCommand(agentId);
1471
+ `).action(async (agentId, options) => {
1472
+ await agentInfoCommand(agentId, {
1473
+ fields: options.fields
1474
+ });
781
1475
  });
782
1476
  var prompts = program.command("prompts").description("Manage agent prompts");
783
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", `
@@ -787,6 +1481,14 @@ Examples:
787
1481
  `).action(async (agentId, options) => {
788
1482
  await pullPromptsCommand(agentId, options);
789
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
+ });
790
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", `
791
1493
  Examples:
792
1494
  $ retell prompts update agent_123abc --source my-prompts.json --dry-run