retell-cli 1.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.
package/dist/index.js ADDED
@@ -0,0 +1,808 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/index.ts
27
+ var import_commander = require("commander");
28
+ var import_fs4 = require("fs");
29
+ var import_path4 = require("path");
30
+
31
+ // src/commands/login.ts
32
+ var readline = __toESM(require("readline/promises"));
33
+ var import_process = require("process");
34
+ var import_retell_sdk2 = __toESM(require("retell-sdk"));
35
+
36
+ // src/services/config.ts
37
+ var import_fs = require("fs");
38
+ var import_path = require("path");
39
+ var import_zod = require("zod");
40
+ var ConfigSchema = import_zod.z.object({
41
+ apiKey: import_zod.z.string().min(1, "API key cannot be empty"),
42
+ defaultFormat: import_zod.z.enum(["json", "text"]).default("json")
43
+ });
44
+ var CONFIG_FILE_NAME = ".retellrc.json";
45
+ var CONFIG_FILE_PERMISSIONS = 384;
46
+ var ConfigError = class extends Error {
47
+ constructor(message, code) {
48
+ super(message);
49
+ this.code = code;
50
+ this.name = "ConfigError";
51
+ }
52
+ };
53
+ function getConfigPath() {
54
+ return (0, import_path.join)(process.cwd(), CONFIG_FILE_NAME);
55
+ }
56
+ function readConfigFile() {
57
+ const configPath = getConfigPath();
58
+ if (!(0, import_fs.existsSync)(configPath)) {
59
+ return null;
60
+ }
61
+ try {
62
+ const content = (0, import_fs.readFileSync)(configPath, "utf-8");
63
+ const parsed = JSON.parse(content);
64
+ return ConfigSchema.parse(parsed);
65
+ } catch (error) {
66
+ if (error instanceof import_zod.z.ZodError) {
67
+ throw new ConfigError(
68
+ `Invalid config file format: ${error.errors.map((e) => e.message).join(", ")}`,
69
+ "INVALID_CONFIG"
70
+ );
71
+ }
72
+ if (error instanceof SyntaxError) {
73
+ throw new ConfigError(
74
+ `Config file contains invalid JSON: ${error.message}`,
75
+ "INVALID_JSON"
76
+ );
77
+ }
78
+ throw error;
79
+ }
80
+ }
81
+ function getApiKeyFromEnv() {
82
+ return process.env.RETELL_API_KEY || null;
83
+ }
84
+ function getConfig() {
85
+ const envApiKey = getApiKeyFromEnv();
86
+ if (envApiKey) {
87
+ return {
88
+ apiKey: envApiKey,
89
+ defaultFormat: "json"
90
+ // Default when using env var
91
+ };
92
+ }
93
+ const fileConfig = readConfigFile();
94
+ if (fileConfig) {
95
+ return fileConfig;
96
+ }
97
+ throw new ConfigError(
98
+ "No configuration found. Please run `retell login` to authenticate.",
99
+ "NO_CONFIG"
100
+ );
101
+ }
102
+ function saveConfig(config) {
103
+ let validatedConfig;
104
+ try {
105
+ validatedConfig = ConfigSchema.parse(config);
106
+ } catch (error) {
107
+ if (error instanceof import_zod.z.ZodError) {
108
+ throw new ConfigError(
109
+ `Invalid configuration: ${error.errors.map((e) => e.message).join(", ")}`,
110
+ "INVALID_CONFIG"
111
+ );
112
+ }
113
+ throw error;
114
+ }
115
+ const configPath = getConfigPath();
116
+ try {
117
+ (0, import_fs.writeFileSync)(
118
+ configPath,
119
+ JSON.stringify(validatedConfig, null, 2),
120
+ { encoding: "utf-8" }
121
+ );
122
+ (0, import_fs.chmodSync)(configPath, CONFIG_FILE_PERMISSIONS);
123
+ } catch (error) {
124
+ throw new ConfigError(
125
+ `Failed to save config: ${error.message}`,
126
+ "WRITE_ERROR"
127
+ );
128
+ }
129
+ }
130
+ function configFileExists() {
131
+ return (0, import_fs.existsSync)(getConfigPath());
132
+ }
133
+
134
+ // src/services/output-formatter.ts
135
+ var import_retell_sdk = __toESM(require("retell-sdk"));
136
+ function outputJson(data) {
137
+ console.log(JSON.stringify(data, null, 2));
138
+ }
139
+ function outputError(error, code) {
140
+ const errorObj = {
141
+ error: typeof error === "string" ? error : error.message,
142
+ code: code || "UNKNOWN_ERROR"
143
+ };
144
+ console.error(JSON.stringify(errorObj, null, 2));
145
+ process.exit(1);
146
+ }
147
+ function handleSdkError(error) {
148
+ if (error instanceof import_retell_sdk.default.NotFoundError) {
149
+ outputError("Resource not found", "NOT_FOUND");
150
+ }
151
+ if (error instanceof import_retell_sdk.default.AuthenticationError) {
152
+ outputError(
153
+ "Authentication failed. Invalid API key. Please run `retell login` to authenticate.",
154
+ "AUTH_ERROR"
155
+ );
156
+ }
157
+ if (error instanceof import_retell_sdk.default.BadRequestError) {
158
+ const message = error.message || "Invalid request parameters";
159
+ outputError(message, "BAD_REQUEST");
160
+ }
161
+ if (error instanceof import_retell_sdk.default.RateLimitError) {
162
+ outputError("Rate limit exceeded. Please try again later.", "RATE_LIMIT");
163
+ }
164
+ if (error instanceof import_retell_sdk.default.PermissionDeniedError) {
165
+ outputError("Permission denied. Check your API key permissions.", "PERMISSION_DENIED");
166
+ }
167
+ if (error instanceof import_retell_sdk.default.InternalServerError) {
168
+ outputError("Retell API server error. Please try again later.", "SERVER_ERROR");
169
+ }
170
+ if (error instanceof import_retell_sdk.default.APIConnectionError) {
171
+ outputError("Failed to connect to Retell API. Check your network connection.", "CONNECTION_ERROR");
172
+ }
173
+ if (error instanceof import_retell_sdk.default.APIConnectionTimeoutError) {
174
+ outputError("Request to Retell API timed out. Please try again.", "TIMEOUT_ERROR");
175
+ }
176
+ if (error instanceof import_retell_sdk.default.APIError) {
177
+ const message = error.message || "An API error occurred";
178
+ outputError(message, "API_ERROR");
179
+ }
180
+ if (error instanceof Error) {
181
+ outputError(error.message, "UNKNOWN_ERROR");
182
+ }
183
+ outputError("An unexpected error occurred", "UNKNOWN_ERROR");
184
+ }
185
+
186
+ // src/commands/login.ts
187
+ async function loginCommand() {
188
+ const rl = readline.createInterface({ input: import_process.stdin, output: import_process.stdout });
189
+ try {
190
+ if (configFileExists()) {
191
+ const overwrite = await rl.question("Config already exists. Overwrite? (y/n): ");
192
+ if (overwrite.toLowerCase() !== "y") {
193
+ rl.close();
194
+ outputJson({ message: "Login cancelled" });
195
+ return;
196
+ }
197
+ }
198
+ const apiKey = await rl.question("Enter your Retell API key: ");
199
+ rl.close();
200
+ if (!apiKey || apiKey.trim() === "") {
201
+ outputJson({
202
+ error: "API key cannot be empty",
203
+ code: "INVALID_INPUT"
204
+ });
205
+ process.exit(1);
206
+ return;
207
+ }
208
+ const testClient = new import_retell_sdk2.default({ apiKey: apiKey.trim() });
209
+ await testClient.agent.list({ limit: 1 });
210
+ saveConfig({ apiKey: apiKey.trim(), defaultFormat: "json" });
211
+ outputJson({
212
+ message: "Successfully authenticated!",
213
+ configPath: "./.retellrc.json",
214
+ nextSteps: [
215
+ "Try: retell agents list",
216
+ "Try: retell transcripts list"
217
+ ]
218
+ });
219
+ } catch (error) {
220
+ rl.close();
221
+ handleSdkError(error);
222
+ }
223
+ }
224
+
225
+ // src/services/retell-client.ts
226
+ var import_retell_sdk3 = __toESM(require("retell-sdk"));
227
+ var clientInstance = null;
228
+ var CLIENT_CONFIG = {
229
+ maxRetries: 2,
230
+ // Retry failed requests 2 times (SDK default)
231
+ timeout: 6e4
232
+ // 60 second timeout (SDK default)
233
+ };
234
+ function getRetellClient() {
235
+ if (!clientInstance) {
236
+ let apiKey;
237
+ try {
238
+ const config = getConfig();
239
+ apiKey = config.apiKey;
240
+ } catch (error) {
241
+ if (error instanceof ConfigError) {
242
+ throw error;
243
+ }
244
+ throw new Error(`Failed to initialize Retell client: ${error.message}`);
245
+ }
246
+ clientInstance = new import_retell_sdk3.default({
247
+ apiKey,
248
+ maxRetries: CLIENT_CONFIG.maxRetries,
249
+ timeout: CLIENT_CONFIG.timeout
250
+ });
251
+ }
252
+ return clientInstance;
253
+ }
254
+
255
+ // src/commands/transcripts/list.ts
256
+ async function listTranscriptsCommand(options) {
257
+ try {
258
+ const client = getRetellClient();
259
+ const calls = await client.call.list({
260
+ limit: options.limit || 50
261
+ });
262
+ outputJson(calls);
263
+ } catch (error) {
264
+ handleSdkError(error);
265
+ }
266
+ }
267
+
268
+ // src/commands/transcripts/get.ts
269
+ async function getTranscriptCommand(callId) {
270
+ try {
271
+ const client = getRetellClient();
272
+ const call = await client.call.retrieve(callId);
273
+ outputJson(call);
274
+ } catch (error) {
275
+ handleSdkError(error);
276
+ }
277
+ }
278
+
279
+ // src/commands/transcripts/analyze.ts
280
+ function extractTranscriptTurns(transcriptObject) {
281
+ if (!transcriptObject || !Array.isArray(transcriptObject)) {
282
+ return [];
283
+ }
284
+ return transcriptObject.map((turn) => ({
285
+ role: turn.role,
286
+ content: turn.content,
287
+ word_count: turn.content ? turn.content.split(/\s+/).length : 0
288
+ }));
289
+ }
290
+ async function analyzeTranscriptCommand(callId) {
291
+ try {
292
+ const client = getRetellClient();
293
+ const call = await client.call.retrieve(callId);
294
+ const analysis = {
295
+ call_id: callId,
296
+ metadata: {
297
+ status: call.call_status || "unknown",
298
+ duration_ms: call.duration_ms || 0,
299
+ start_timestamp: call.start_timestamp || 0,
300
+ end_timestamp: call.end_timestamp || 0,
301
+ agent_name: call.agent_name || "Unknown"
302
+ },
303
+ transcript: extractTranscriptTurns(call.transcript_object),
304
+ analysis: {
305
+ summary: call.call_analysis?.call_summary || "No summary available",
306
+ sentiment: call.call_analysis?.user_sentiment || "Unknown",
307
+ successful: call.call_analysis?.call_successful ?? false,
308
+ in_voicemail: call.call_analysis?.in_voicemail ?? false
309
+ },
310
+ performance: {
311
+ latency_p50_ms: {
312
+ e2e: call.latency?.e2e?.p50 ?? null,
313
+ llm: call.latency?.llm?.p50 ?? null,
314
+ tts: call.latency?.tts?.p50 ?? null
315
+ },
316
+ latency_p90_ms: {
317
+ e2e: call.latency?.e2e?.p90 ?? null,
318
+ llm: call.latency?.llm?.p90 ?? null,
319
+ tts: call.latency?.tts?.p90 ?? null
320
+ }
321
+ },
322
+ cost: {
323
+ total: call.call_cost?.combined_cost || 0,
324
+ breakdown: call.call_cost?.product_costs || []
325
+ }
326
+ };
327
+ outputJson(analysis);
328
+ } catch (error) {
329
+ handleSdkError(error);
330
+ }
331
+ }
332
+
333
+ // src/commands/agents/list.ts
334
+ async function listAgentsCommand(options = {}) {
335
+ try {
336
+ const client = getRetellClient();
337
+ const agents2 = await client.agent.list({
338
+ limit: options.limit || 100
339
+ });
340
+ const formatted = agents2.map((agent) => {
341
+ let response_engine_id;
342
+ switch (agent.response_engine.type) {
343
+ case "retell-llm":
344
+ response_engine_id = agent.response_engine.llm_id || "unknown";
345
+ break;
346
+ case "conversation-flow":
347
+ response_engine_id = agent.response_engine.conversation_flow_id || "unknown";
348
+ break;
349
+ case "custom-llm":
350
+ response_engine_id = agent.response_engine.llm_websocket_url || "unknown";
351
+ break;
352
+ default:
353
+ response_engine_id = "unknown";
354
+ }
355
+ return {
356
+ agent_id: agent.agent_id,
357
+ agent_name: agent.agent_name,
358
+ version: agent.version,
359
+ is_published: agent.is_published,
360
+ response_engine_type: agent.response_engine.type,
361
+ response_engine_id
362
+ };
363
+ });
364
+ outputJson(formatted);
365
+ } catch (error) {
366
+ handleSdkError(error);
367
+ }
368
+ }
369
+
370
+ // src/commands/agents/info.ts
371
+ async function agentInfoCommand(agentId) {
372
+ try {
373
+ const client = getRetellClient();
374
+ const agent = await client.agent.retrieve(agentId);
375
+ outputJson(agent);
376
+ } catch (error) {
377
+ handleSdkError(error);
378
+ }
379
+ }
380
+
381
+ // src/commands/prompts/pull.ts
382
+ var import_fs2 = require("fs");
383
+ var import_path2 = require("path");
384
+
385
+ // src/services/prompt-resolver.ts
386
+ async function resolvePromptSource(agentId) {
387
+ const client = getRetellClient();
388
+ const agent = await client.agent.retrieve(agentId);
389
+ if (agent.response_engine.type === "retell-llm") {
390
+ const llm = await client.llm.retrieve(agent.response_engine.llm_id);
391
+ return {
392
+ type: "retell-llm",
393
+ llmId: llm.llm_id,
394
+ agentName: agent.agent_name,
395
+ prompts: {
396
+ llm_id: llm.llm_id,
397
+ version: llm.version,
398
+ general_prompt: llm.general_prompt,
399
+ begin_message: llm.begin_message,
400
+ states: llm.states
401
+ }
402
+ };
403
+ }
404
+ if (agent.response_engine.type === "conversation-flow") {
405
+ const flow = await client.conversationFlow.retrieve(
406
+ agent.response_engine.conversation_flow_id
407
+ );
408
+ return {
409
+ type: "conversation-flow",
410
+ flowId: flow.conversation_flow_id,
411
+ agentName: agent.agent_name,
412
+ prompts: {
413
+ conversation_flow_id: flow.conversation_flow_id,
414
+ version: flow.version,
415
+ global_prompt: flow.global_prompt,
416
+ nodes: flow.nodes
417
+ }
418
+ };
419
+ }
420
+ return {
421
+ type: "custom-llm",
422
+ error: "Custom LLM agents cannot be managed via API. Use the Retell dashboard to update prompts for custom LLM agents."
423
+ };
424
+ }
425
+
426
+ // src/commands/prompts/pull.ts
427
+ function validateAgentId(agentId) {
428
+ if (agentId.includes("..") || agentId.includes("/") || agentId.includes("\\")) {
429
+ throw new Error("Invalid agent ID: cannot contain path separators or traversal sequences");
430
+ }
431
+ }
432
+ async function pullPromptsCommand(agentId, options) {
433
+ try {
434
+ validateAgentId(agentId);
435
+ const promptSource = await resolvePromptSource(agentId);
436
+ if (promptSource.type === "custom-llm") {
437
+ outputError(promptSource.error, "CUSTOM_LLM_NOT_SUPPORTED");
438
+ return;
439
+ }
440
+ const baseDir = options.output || ".retell-prompts";
441
+ const agentDir = (0, import_path2.join)(baseDir, agentId);
442
+ try {
443
+ (0, import_fs2.mkdirSync)(agentDir, { recursive: true });
444
+ } catch (error) {
445
+ if (error.code === "EACCES") {
446
+ outputError(`Permission denied creating directory: ${agentDir}`, "PERMISSION_DENIED");
447
+ } else if (error.code === "ENOSPC") {
448
+ outputError(`No space left on device: ${agentDir}`, "NO_SPACE");
449
+ } else {
450
+ outputError(`Failed to create directory: ${error.message}`, "FS_ERROR");
451
+ }
452
+ return;
453
+ }
454
+ try {
455
+ if (promptSource.type === "retell-llm") {
456
+ saveRetellLlmPrompts(agentDir, promptSource);
457
+ } else if (promptSource.type === "conversation-flow") {
458
+ saveConversationFlowPrompts(agentDir, promptSource);
459
+ }
460
+ } catch (error) {
461
+ if (error.code === "EACCES") {
462
+ outputError(`Permission denied writing files to: ${agentDir}`, "PERMISSION_DENIED");
463
+ } else if (error.code === "ENOSPC") {
464
+ outputError(`No space left on device: ${agentDir}`, "NO_SPACE");
465
+ } else {
466
+ outputError(`Failed to write prompt files: ${error.message}`, "FS_ERROR");
467
+ }
468
+ return;
469
+ }
470
+ outputJson({
471
+ message: "Prompts pulled successfully",
472
+ agent_id: agentId,
473
+ agent_name: promptSource.agentName,
474
+ type: promptSource.type,
475
+ directory: agentDir,
476
+ files_created: getFilesCreated(promptSource.type, promptSource)
477
+ });
478
+ } catch (error) {
479
+ handleSdkError(error);
480
+ }
481
+ }
482
+ function saveRetellLlmPrompts(agentDir, promptSource) {
483
+ const { prompts: prompts2, llmId, agentName } = promptSource;
484
+ const metadata = {
485
+ type: "retell-llm",
486
+ agent_name: agentName,
487
+ llm_id: llmId,
488
+ version: prompts2.version,
489
+ pulled_at: (/* @__PURE__ */ new Date()).toISOString()
490
+ };
491
+ (0, import_fs2.writeFileSync)((0, import_path2.join)(agentDir, "metadata.json"), JSON.stringify(metadata, null, 2));
492
+ (0, import_fs2.writeFileSync)((0, import_path2.join)(agentDir, "general_prompt.md"), prompts2.general_prompt || "");
493
+ if (prompts2.begin_message) {
494
+ (0, import_fs2.writeFileSync)((0, import_path2.join)(agentDir, "begin_message.txt"), prompts2.begin_message);
495
+ }
496
+ if (prompts2.states && prompts2.states.length > 0) {
497
+ const statesDir = (0, import_path2.join)(agentDir, "states");
498
+ (0, import_fs2.mkdirSync)(statesDir, { recursive: true });
499
+ prompts2.states.forEach((state) => {
500
+ const filename = `${state.name}.md`;
501
+ const content = `# State: ${state.name}
502
+
503
+ ${state.state_prompt}`;
504
+ (0, import_fs2.writeFileSync)((0, import_path2.join)(statesDir, filename), content);
505
+ });
506
+ }
507
+ }
508
+ function saveConversationFlowPrompts(agentDir, promptSource) {
509
+ const { prompts: prompts2, flowId, agentName } = promptSource;
510
+ const metadata = {
511
+ type: "conversation-flow",
512
+ agent_name: agentName,
513
+ conversation_flow_id: flowId,
514
+ version: prompts2.version,
515
+ pulled_at: (/* @__PURE__ */ new Date()).toISOString()
516
+ };
517
+ (0, import_fs2.writeFileSync)((0, import_path2.join)(agentDir, "metadata.json"), JSON.stringify(metadata, null, 2));
518
+ (0, import_fs2.writeFileSync)((0, import_path2.join)(agentDir, "global_prompt.md"), prompts2.global_prompt || "");
519
+ (0, import_fs2.writeFileSync)((0, import_path2.join)(agentDir, "nodes.json"), JSON.stringify(prompts2.nodes, null, 2));
520
+ }
521
+ function getFilesCreated(type, promptSource) {
522
+ const files = ["metadata.json"];
523
+ if (type === "retell-llm" && promptSource.type === "retell-llm") {
524
+ files.push("general_prompt.md");
525
+ if (promptSource.prompts.begin_message) {
526
+ files.push("begin_message.txt");
527
+ }
528
+ if (promptSource.prompts.states && promptSource.prompts.states.length > 0) {
529
+ files.push(`states/ (${promptSource.prompts.states.length} state files)`);
530
+ }
531
+ } else if (type === "conversation-flow") {
532
+ files.push("global_prompt.md");
533
+ files.push("nodes.json");
534
+ }
535
+ return files;
536
+ }
537
+
538
+ // src/commands/prompts/update.ts
539
+ var import_fs3 = require("fs");
540
+ 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");
544
+ }
545
+ }
546
+ async function updatePromptsCommand(agentId, options) {
547
+ 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
+ }
603
+ } catch (error) {
604
+ if (error instanceof SyntaxError) {
605
+ outputError(`Invalid JSON in file: ${error.message}`, "INVALID_JSON");
606
+ return;
607
+ }
608
+ handleSdkError(error);
609
+ }
610
+ }
611
+ function loadRetellLlmPrompts(agentDir) {
612
+ const prompts2 = {};
613
+ const generalPromptPath = (0, import_path3.join)(agentDir, "general_prompt.md");
614
+ if (!(0, import_fs3.existsSync)(generalPromptPath)) {
615
+ throw new Error("general_prompt.md not found");
616
+ }
617
+ try {
618
+ prompts2.general_prompt = (0, import_fs3.readFileSync)(generalPromptPath, "utf-8");
619
+ } catch (error) {
620
+ throw new Error(`Failed to read general_prompt.md: ${error.message}`);
621
+ }
622
+ const beginMessagePath = (0, import_path3.join)(agentDir, "begin_message.txt");
623
+ if ((0, import_fs3.existsSync)(beginMessagePath)) {
624
+ try {
625
+ const beginMessage = (0, import_fs3.readFileSync)(beginMessagePath, "utf-8");
626
+ if (beginMessage) {
627
+ prompts2.begin_message = beginMessage;
628
+ }
629
+ } catch (error) {
630
+ throw new Error(`Failed to read begin_message.txt: ${error.message}`);
631
+ }
632
+ }
633
+ const statesDir = (0, import_path3.join)(agentDir, "states");
634
+ if ((0, import_fs3.existsSync)(statesDir)) {
635
+ try {
636
+ const stateFiles = (0, import_fs3.readdirSync)(statesDir).filter((f) => f.endsWith(".md"));
637
+ if (stateFiles.length > 0) {
638
+ prompts2.states = stateFiles.map((file) => {
639
+ const stateName = file.replace(".md", "");
640
+ let content;
641
+ try {
642
+ content = (0, import_fs3.readFileSync)((0, import_path3.join)(statesDir, file), "utf-8");
643
+ } catch (error) {
644
+ throw new Error(`Failed to read state file ${file}: ${error.message}`);
645
+ }
646
+ const STATE_HEADER_REGEX = /^#\s+State:\s+(.+)$/m;
647
+ const match = content.match(STATE_HEADER_REGEX);
648
+ if (!match) {
649
+ throw new Error(`Invalid state file format in ${file}: missing "# State:" header`);
650
+ }
651
+ const statePrompt = content.replace(STATE_HEADER_REGEX, "").trim();
652
+ return {
653
+ name: stateName,
654
+ state_prompt: statePrompt
655
+ };
656
+ });
657
+ }
658
+ } catch (error) {
659
+ if (error.message.includes("Invalid state file format") || error.message.includes("Failed to read state file")) {
660
+ throw error;
661
+ }
662
+ throw new Error(`Failed to read states directory: ${error.message}`);
663
+ }
664
+ }
665
+ return prompts2;
666
+ }
667
+ function loadConversationFlowPrompts(agentDir) {
668
+ const prompts2 = {};
669
+ const globalPromptPath = (0, import_path3.join)(agentDir, "global_prompt.md");
670
+ if (!(0, import_fs3.existsSync)(globalPromptPath)) {
671
+ throw new Error("global_prompt.md not found");
672
+ }
673
+ try {
674
+ prompts2.global_prompt = (0, import_fs3.readFileSync)(globalPromptPath, "utf-8");
675
+ } catch (error) {
676
+ throw new Error(`Failed to read global_prompt.md: ${error.message}`);
677
+ }
678
+ const nodesPath = (0, import_path3.join)(agentDir, "nodes.json");
679
+ if (!(0, import_fs3.existsSync)(nodesPath)) {
680
+ throw new Error("nodes.json not found");
681
+ }
682
+ try {
683
+ const nodesContent = JSON.parse((0, import_fs3.readFileSync)(nodesPath, "utf-8"));
684
+ if (!Array.isArray(nodesContent)) {
685
+ throw new Error("nodes.json must contain an array");
686
+ }
687
+ prompts2.nodes = nodesContent;
688
+ } catch (error) {
689
+ if (error instanceof SyntaxError) {
690
+ throw new Error(`Invalid JSON in nodes.json: ${error.message}`);
691
+ }
692
+ throw error;
693
+ }
694
+ return prompts2;
695
+ }
696
+
697
+ // src/commands/agent/publish.ts
698
+ async function publishAgentCommand(agentId) {
699
+ try {
700
+ const client = getRetellClient();
701
+ const result = await client.agent.publish(agentId);
702
+ outputJson({
703
+ 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,
708
+ note: "Draft version incremented and ready for new changes"
709
+ });
710
+ } catch (error) {
711
+ handleSdkError(error);
712
+ }
713
+ }
714
+
715
+ // src/index.ts
716
+ var packageJson = JSON.parse(
717
+ (0, import_fs4.readFileSync)((0, import_path4.join)(__dirname, "../package.json"), "utf-8")
718
+ );
719
+ var program = new import_commander.Command();
720
+ 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);
721
+ program.command("login").description("Authenticate with Retell AI").addHelpText("after", `
722
+ Examples:
723
+ $ retell login
724
+ # Enter your API key when prompted
725
+ # Creates .retellrc.json in current directory
726
+ `).action(async () => {
727
+ await loginCommand();
728
+ });
729
+ 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", `
731
+ Examples:
732
+ $ retell transcripts list
733
+ $ retell transcripts list --limit 100
734
+ $ retell transcripts list | jq '.[] | select(.call_status == "error")'
735
+ `).action(async (options) => {
736
+ const limit = parseInt(options.limit, 10);
737
+ if (isNaN(limit) || limit < 1) {
738
+ console.error("Error: limit must be a positive number");
739
+ process.exit(1);
740
+ }
741
+ await listTranscriptsCommand({
742
+ limit
743
+ });
744
+ });
745
+ transcripts.command("get <call_id>").description("Get a specific call transcript").addHelpText("after", `
746
+ Examples:
747
+ $ retell transcripts get call_abc123
748
+ $ retell transcripts get call_abc123 | jq '.transcript_object'
749
+ `).action(async (callId) => {
750
+ await getTranscriptCommand(callId);
751
+ });
752
+ transcripts.command("analyze <call_id>").description("Analyze a call transcript with performance metrics and insights").addHelpText("after", `
753
+ Examples:
754
+ $ retell transcripts analyze call_abc123
755
+ $ retell transcripts analyze call_abc123 | jq '.performance.latency_p50_ms'
756
+ `).action(async (callId) => {
757
+ await analyzeTranscriptCommand(callId);
758
+ });
759
+ 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", `
761
+ Examples:
762
+ $ retell agents list
763
+ $ retell agents list --limit 10
764
+ $ retell agents list | jq '.[] | select(.response_engine.type == "retell-llm")'
765
+ `).action(async (options) => {
766
+ const limit = parseInt(options.limit, 10);
767
+ if (isNaN(limit) || limit < 1) {
768
+ console.error("Error: limit must be a positive number");
769
+ process.exit(1);
770
+ }
771
+ await listAgentsCommand({
772
+ limit
773
+ });
774
+ });
775
+ agents.command("info <agent_id>").description("Get detailed agent information").addHelpText("after", `
776
+ Examples:
777
+ $ retell agents info agent_123abc
778
+ $ retell agents info agent_123abc | jq '.response_engine.type'
779
+ `).action(async (agentId) => {
780
+ await agentInfoCommand(agentId);
781
+ });
782
+ var prompts = program.command("prompts").description("Manage agent prompts");
783
+ 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", `
784
+ Examples:
785
+ $ retell prompts pull agent_123abc
786
+ $ retell prompts pull agent_123abc --output my-prompts.json
787
+ `).action(async (agentId, options) => {
788
+ await pullPromptsCommand(agentId, options);
789
+ });
790
+ 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
+ Examples:
792
+ $ retell prompts update agent_123abc --source my-prompts.json --dry-run
793
+ $ retell prompts update agent_123abc --source my-prompts.json
794
+ # Remember to publish: retell agent-publish agent_123abc
795
+ `).action(async (agentId, options) => {
796
+ await updatePromptsCommand(agentId, options);
797
+ });
798
+ program.command("agent-publish <agent_id>").description("Publish a draft agent to make changes live").addHelpText("after", `
799
+ Examples:
800
+ $ retell agent-publish agent_123abc
801
+ # Run this after updating prompts to make changes live
802
+ `).action(async (agentId) => {
803
+ await publishAgentCommand(agentId);
804
+ });
805
+ program.parse(process.argv);
806
+ if (!process.argv.slice(2).length) {
807
+ program.outputHelp();
808
+ }