@chanl-ai/cli 2.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.
Files changed (77) hide show
  1. package/bin/chanl.js +10 -0
  2. package/dist/__tests__/cli.test.d.ts +2 -0
  3. package/dist/__tests__/cli.test.js +2313 -0
  4. package/dist/__tests__/cli.test.js.map +1 -0
  5. package/dist/cli.d.ts +12 -0
  6. package/dist/cli.js +72 -0
  7. package/dist/cli.js.map +1 -0
  8. package/dist/commands/agents.d.ts +8 -0
  9. package/dist/commands/agents.js +671 -0
  10. package/dist/commands/agents.js.map +1 -0
  11. package/dist/commands/auth.d.ts +16 -0
  12. package/dist/commands/auth.js +294 -0
  13. package/dist/commands/auth.js.map +1 -0
  14. package/dist/commands/call.d.ts +8 -0
  15. package/dist/commands/call.js +166 -0
  16. package/dist/commands/call.js.map +1 -0
  17. package/dist/commands/calls.d.ts +8 -0
  18. package/dist/commands/calls.js +719 -0
  19. package/dist/commands/calls.js.map +1 -0
  20. package/dist/commands/chat.d.ts +8 -0
  21. package/dist/commands/chat.js +203 -0
  22. package/dist/commands/chat.js.map +1 -0
  23. package/dist/commands/config.d.ts +8 -0
  24. package/dist/commands/config.js +231 -0
  25. package/dist/commands/config.js.map +1 -0
  26. package/dist/commands/health.d.ts +8 -0
  27. package/dist/commands/health.js +55 -0
  28. package/dist/commands/health.js.map +1 -0
  29. package/dist/commands/index.d.ts +18 -0
  30. package/dist/commands/index.js +39 -0
  31. package/dist/commands/index.js.map +1 -0
  32. package/dist/commands/knowledge.d.ts +8 -0
  33. package/dist/commands/knowledge.js +539 -0
  34. package/dist/commands/knowledge.js.map +1 -0
  35. package/dist/commands/mcp.d.ts +8 -0
  36. package/dist/commands/mcp.js +589 -0
  37. package/dist/commands/mcp.js.map +1 -0
  38. package/dist/commands/memory.d.ts +8 -0
  39. package/dist/commands/memory.js +408 -0
  40. package/dist/commands/memory.js.map +1 -0
  41. package/dist/commands/personas.d.ts +8 -0
  42. package/dist/commands/personas.js +356 -0
  43. package/dist/commands/personas.js.map +1 -0
  44. package/dist/commands/prompts.d.ts +8 -0
  45. package/dist/commands/prompts.js +295 -0
  46. package/dist/commands/prompts.js.map +1 -0
  47. package/dist/commands/scenarios.d.ts +8 -0
  48. package/dist/commands/scenarios.js +591 -0
  49. package/dist/commands/scenarios.js.map +1 -0
  50. package/dist/commands/scorecards.d.ts +8 -0
  51. package/dist/commands/scorecards.js +570 -0
  52. package/dist/commands/scorecards.js.map +1 -0
  53. package/dist/commands/tools.d.ts +8 -0
  54. package/dist/commands/tools.js +632 -0
  55. package/dist/commands/tools.js.map +1 -0
  56. package/dist/commands/toolsets.d.ts +8 -0
  57. package/dist/commands/toolsets.js +464 -0
  58. package/dist/commands/toolsets.js.map +1 -0
  59. package/dist/commands/workspaces.d.ts +8 -0
  60. package/dist/commands/workspaces.js +170 -0
  61. package/dist/commands/workspaces.js.map +1 -0
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.js +6 -0
  64. package/dist/index.js.map +1 -0
  65. package/dist/utils/config-store.d.ts +117 -0
  66. package/dist/utils/config-store.js +191 -0
  67. package/dist/utils/config-store.js.map +1 -0
  68. package/dist/utils/interactive.d.ts +41 -0
  69. package/dist/utils/interactive.js +83 -0
  70. package/dist/utils/interactive.js.map +1 -0
  71. package/dist/utils/output.d.ts +100 -0
  72. package/dist/utils/output.js +221 -0
  73. package/dist/utils/output.js.map +1 -0
  74. package/dist/utils/sdk-factory.d.ts +15 -0
  75. package/dist/utils/sdk-factory.js +34 -0
  76. package/dist/utils/sdk-factory.js.map +1 -0
  77. package/package.json +67 -0
@@ -0,0 +1,719 @@
1
+ import { Command } from "commander";
2
+ import ora from "ora";
3
+ import chalk from "chalk";
4
+ import * as fs from "fs";
5
+ import { createSdk } from "../utils/sdk-factory.js";
6
+ import {
7
+ printError,
8
+ printInfo,
9
+ printBlank,
10
+ printSimpleTable,
11
+ printLabel,
12
+ isJsonOutput,
13
+ printJson,
14
+ formatDate,
15
+ printSuccess,
16
+ printWarning
17
+ } from "../utils/output.js";
18
+ function createCallsCommand() {
19
+ const calls = new Command("calls").description("Import, view, and analyze call recordings").addHelpText(
20
+ "after",
21
+ `
22
+ What are Calls?
23
+ Calls are voice recordings from your AI agents (via VAPI, Twilio, etc.)
24
+ or imported transcripts/recordings. Each call can be analyzed with
25
+ scorecards to evaluate agent performance.
26
+
27
+ Quick Start:
28
+ $ chanl calls import --transcript "..." # Import a call
29
+ $ chanl calls list # List recent calls
30
+ $ chanl calls list --external-ref orderId=123 # Filter by external ref
31
+ $ chanl calls get <id> # View call details
32
+ $ chanl calls analyze <id> # Trigger scorecard analysis
33
+ $ chanl calls delete <id> # Delete a call
34
+
35
+ Import Workflow:
36
+ 1. Import a call with transcript, audio URL, or S3
37
+ 2. Optionally specify analysis fields and scorecard
38
+ 3. Webhook notifies when analysis is complete
39
+ 4. View results with 'analysis <id>'
40
+
41
+ Analysis Workflow:
42
+ 1. List calls to find one to analyze
43
+ 2. Trigger analysis with 'analyze <id>'
44
+ 3. View results with 'analysis <id>'
45
+ 4. Check transcript with 'transcript <id>'`
46
+ );
47
+ calls.command("list").description("List calls").option("-s, --status <status>", "Filter by status (pending, in_progress, completed, failed)").option("-a, --agent <agentId>", "Filter by agent ID").option("-d, --direction <direction>", "Filter by direction (inbound, outbound)").option("--customer <name>", "Filter by customer name").option("-e, --external-ref <key=value>", "Filter by external reference (repeatable)", collectExternalRefs, {}).option("-l, --limit <number>", "Number of items per page", "20").option("-p, --page <number>", "Page number", "1").addHelpText(
48
+ "after",
49
+ `
50
+ Examples:
51
+ $ chanl calls list # List recent calls
52
+ $ chanl calls list --status completed # Only completed calls
53
+ $ chanl calls list --agent abc123 # Filter by agent
54
+ $ chanl calls list --external-ref orderId=ORDER-123 # Filter by external ref
55
+ $ chanl calls list -e orderId=123 -e customerId=456 # Multiple external refs
56
+ $ chanl calls list --json # Output as JSON`
57
+ ).action(handleCallsList);
58
+ calls.command("get <id>").description("Get call details").addHelpText(
59
+ "after",
60
+ `
61
+ Examples:
62
+ $ chanl calls get abc123 # Get call details
63
+ $ chanl calls get abc123 --json # Output as JSON`
64
+ ).action(handleCallsGet);
65
+ calls.command("analyze <id>").description("Trigger scorecard analysis for a call").option("-s, --scorecard <scorecardId>", "Specific scorecard to use").option("-f, --force", "Force re-analysis even if already done").addHelpText(
66
+ "after",
67
+ `
68
+ Examples:
69
+ $ chanl calls analyze abc123 # Analyze with default scorecard
70
+ $ chanl calls analyze abc123 --scorecard xyz # Use specific scorecard
71
+ $ chanl calls analyze abc123 --force # Force re-analysis`
72
+ ).action(handleCallsAnalyze);
73
+ calls.command("analysis <id>").description("View scorecard analysis results for a call").addHelpText(
74
+ "after",
75
+ `
76
+ Examples:
77
+ $ chanl calls analysis abc123 # View analysis results
78
+ $ chanl calls analysis abc123 --json # Output as JSON`
79
+ ).action(handleCallsAnalysis);
80
+ calls.command("transcript <id>").description("View call transcript").option("--full", "Show full transcript text instead of segments").addHelpText(
81
+ "after",
82
+ `
83
+ Examples:
84
+ $ chanl calls transcript abc123 # View transcript segments
85
+ $ chanl calls transcript abc123 --full # Show full text
86
+ $ chanl calls transcript abc123 --json # Output as JSON`
87
+ ).action(handleCallsTranscript);
88
+ calls.command("metrics <id>").description("View call metrics").addHelpText(
89
+ "after",
90
+ `
91
+ Examples:
92
+ $ chanl calls metrics abc123 # View call metrics
93
+ $ chanl calls metrics abc123 --json # Output as JSON`
94
+ ).action(handleCallsMetrics);
95
+ calls.command("import").description("Import a call from transcript, audio URL, or S3").option("-t, --transcript <text>", "Transcript text (plain text or JSON segments)").option("-f, --file <path>", "JSON file with import options").option("--audio-url <url>", "Audio file URL to transcribe").option("--audio-id <id>", "Pre-uploaded audio ID").option("--bucket <name>", "S3 bucket name").option("--key <key>", "S3 object key").option("--region <region>", "AWS region").option("--customer-name <name>", "Customer name").option("--agent-name <name>", "Agent name").option("--direction <dir>", "Call direction (inbound, outbound)").option("-e, --external-ref <key=value>", "External reference (repeatable)", collectExternalRefs, {}).option("--analysis-fields <fields>", "Comma-separated analysis fields").option("--scorecard <id>", "Scorecard ID to evaluate against").option("--webhook-url <url>", "Webhook URL for completion notification").addHelpText(
96
+ "after",
97
+ `
98
+ Import Modes (at least one required):
99
+ --transcript Import with existing transcript text
100
+ --audio-url Import from audio URL (will be transcribed)
101
+ --audio-id Import using pre-uploaded audio
102
+ --bucket/key Import from S3 bucket
103
+ --file Import from JSON file containing all options
104
+
105
+ Analysis Fields:
106
+ sentiment, topics, keywords, quality, coaching, predictions,
107
+ speakers, upsell, metrics, extraction, followups
108
+
109
+ Examples:
110
+ # Import with transcript
111
+ $ chanl calls import --transcript "Agent: Hello! Customer: Hi..."
112
+
113
+ # Import with customer metadata
114
+ $ chanl calls import --transcript "..." --customer-name "John" -e orderId=ORD-123
115
+
116
+ # Import from audio URL
117
+ $ chanl calls import --audio-url "https://example.com/call.mp3"
118
+
119
+ # Import from JSON file
120
+ $ chanl calls import -f import-data.json
121
+
122
+ # Import with analysis options
123
+ $ chanl calls import -t "..." --scorecard abc123 --analysis-fields sentiment,quality`
124
+ ).action(handleCallsImport);
125
+ calls.command("delete <id>").description("Delete a call").option("-y, --yes", "Skip confirmation prompt").addHelpText(
126
+ "after",
127
+ `
128
+ Examples:
129
+ $ chanl calls delete abc123 # Delete with confirmation
130
+ $ chanl calls delete abc123 --yes # Delete without confirmation`
131
+ ).action(handleCallsDelete);
132
+ calls.command("bulk-delete").description("Delete multiple calls").option("--ids <ids>", "Comma-separated call IDs to delete").option("-f, --file <path>", "File with call IDs (one per line)").option("-y, --yes", "Skip confirmation prompt").option("--keep-files", "Do not delete associated storage files").addHelpText(
133
+ "after",
134
+ `
135
+ Examples:
136
+ $ chanl calls bulk-delete --ids id1,id2,id3 # Delete by IDs
137
+ $ chanl calls bulk-delete -f call-ids.txt # Delete from file
138
+ $ chanl calls bulk-delete --ids id1,id2 --yes # Skip confirmation`
139
+ ).action(handleCallsBulkDelete);
140
+ return calls;
141
+ }
142
+ function formatStatus(status) {
143
+ switch (status) {
144
+ case "completed":
145
+ return chalk.green("completed");
146
+ case "in_progress":
147
+ return chalk.cyan("in_progress");
148
+ case "pending":
149
+ return chalk.blue("pending");
150
+ case "failed":
151
+ return chalk.red("failed");
152
+ default:
153
+ return chalk.gray(status || "unknown");
154
+ }
155
+ }
156
+ function formatScore(score) {
157
+ if (score === void 0 || score === null) return "-";
158
+ if (score >= 80) return chalk.green(`${score}%`);
159
+ if (score >= 60) return chalk.yellow(`${score}%`);
160
+ return chalk.red(`${score}%`);
161
+ }
162
+ function formatDuration(seconds) {
163
+ if (!seconds) return "-";
164
+ if (seconds < 60) return `${Math.round(seconds)}s`;
165
+ const mins = Math.floor(seconds / 60);
166
+ const secs = Math.round(seconds % 60);
167
+ return `${mins}m ${secs}s`;
168
+ }
169
+ function collectExternalRefs(value, previous) {
170
+ const [key, ...rest] = value.split("=");
171
+ if (!key || rest.length === 0) {
172
+ printWarning(`Invalid external-ref format: '${value}'. Expected key=value`);
173
+ return previous;
174
+ }
175
+ return { ...previous, [key]: rest.join("=") };
176
+ }
177
+ async function handleCallsList(options) {
178
+ const sdk = createSdk();
179
+ if (!sdk) return;
180
+ const spinner = ora("Fetching calls...").start();
181
+ try {
182
+ const response = await sdk.calls.list({
183
+ status: options.status,
184
+ agentId: options.agent,
185
+ direction: options.direction,
186
+ customerName: options.customer,
187
+ externalRefs: options.externalRef && Object.keys(options.externalRef).length > 0 ? options.externalRef : void 0,
188
+ limit: parseInt(options.limit, 10),
189
+ page: parseInt(options.page, 10)
190
+ });
191
+ spinner.stop();
192
+ if (!response.success || !response.data) {
193
+ printError("Failed to fetch calls", response.message);
194
+ process.exitCode = 1;
195
+ return;
196
+ }
197
+ const rawData = response.data;
198
+ const calls = Array.isArray(rawData) ? rawData : rawData.calls || rawData.interactions || [];
199
+ if (isJsonOutput()) {
200
+ printJson(response.data);
201
+ return;
202
+ }
203
+ if (calls.length === 0) {
204
+ printInfo("No calls found");
205
+ return;
206
+ }
207
+ printBlank();
208
+ printSimpleTable(
209
+ ["ID", "Status", "Duration", "Score", "Agent", "Created"],
210
+ calls.map((c) => [
211
+ c.id.slice(-12),
212
+ formatStatus(c.status),
213
+ formatDuration(c.duration),
214
+ formatScore(c.score),
215
+ c.agentId?.slice(-8) || "-",
216
+ formatDate(c.createdAt)
217
+ ])
218
+ );
219
+ printBlank();
220
+ printInfo(`${calls.length} calls shown`);
221
+ } catch (error) {
222
+ spinner.fail("Failed to fetch calls");
223
+ const message = error instanceof Error ? error.message : "Unknown error";
224
+ printError("Error", message);
225
+ process.exitCode = 1;
226
+ }
227
+ }
228
+ async function handleCallsGet(id) {
229
+ const sdk = createSdk();
230
+ if (!sdk) return;
231
+ const spinner = ora("Fetching call...").start();
232
+ try {
233
+ const response = await sdk.calls.get(id);
234
+ spinner.stop();
235
+ if (!response.success || !response.data) {
236
+ printError("Failed to fetch call", response.message);
237
+ process.exitCode = 1;
238
+ return;
239
+ }
240
+ if (isJsonOutput()) {
241
+ printJson(response.data);
242
+ return;
243
+ }
244
+ const call = response.data.call || response.data;
245
+ printBlank();
246
+ console.log(chalk.bold("Call Details:"));
247
+ console.log(` ID: ${call.id}`);
248
+ console.log(` Status: ${formatStatus(call.status)}`);
249
+ console.log(` Duration: ${formatDuration(call.duration)}`);
250
+ console.log(` Score: ${formatScore(call.score)}`);
251
+ if (call.agentId) console.log(` Agent: ${call.agentId}`);
252
+ if (call.scenarioId) console.log(` Scenario: ${call.scenarioId}`);
253
+ console.log(` Created: ${formatDate(call.createdAt)}`);
254
+ printBlank();
255
+ } catch (error) {
256
+ spinner.fail("Failed to fetch call");
257
+ const message = error instanceof Error ? error.message : "Unknown error";
258
+ printError("Error", message);
259
+ process.exitCode = 1;
260
+ }
261
+ }
262
+ async function handleCallsAnalyze(id, options) {
263
+ const sdk = createSdk();
264
+ if (!sdk) return;
265
+ const spinner = ora("Triggering analysis...").start();
266
+ try {
267
+ const response = await sdk.calls.analyze(id, {
268
+ scorecardId: options.scorecard,
269
+ forceReanalysis: options.force
270
+ });
271
+ spinner.stop();
272
+ if (!response.success || !response.data) {
273
+ printError("Failed to trigger analysis", response.message);
274
+ process.exitCode = 1;
275
+ return;
276
+ }
277
+ if (isJsonOutput()) {
278
+ printJson(response.data);
279
+ return;
280
+ }
281
+ const { status, resultId, message } = response.data;
282
+ printBlank();
283
+ if (status === "queued") {
284
+ console.log(chalk.green("\u2713 Analysis queued successfully"));
285
+ if (resultId) {
286
+ printLabel("Result ID", resultId);
287
+ }
288
+ printInfo(`Run 'chanl calls analysis ${id}' to view results when complete`);
289
+ } else if (status === "no_scorecard") {
290
+ console.log(chalk.yellow("\u26A0 No scorecard available"));
291
+ printInfo(message || "Configure a default scorecard for your workspace");
292
+ } else if (status === "no_transcript") {
293
+ console.log(chalk.yellow("\u26A0 No transcript available"));
294
+ printInfo(message || "Call needs a transcript before analysis");
295
+ }
296
+ printBlank();
297
+ } catch (error) {
298
+ spinner.fail("Failed to trigger analysis");
299
+ const message = error instanceof Error ? error.message : "Unknown error";
300
+ printError("Error", message);
301
+ process.exitCode = 1;
302
+ }
303
+ }
304
+ async function handleCallsAnalysis(id) {
305
+ const sdk = createSdk();
306
+ if (!sdk) return;
307
+ const spinner = ora("Fetching analysis...").start();
308
+ try {
309
+ const response = await sdk.calls.getAnalysis(id);
310
+ spinner.stop();
311
+ if (!response.success || !response.data) {
312
+ printError("Failed to fetch analysis", response.message);
313
+ process.exitCode = 1;
314
+ return;
315
+ }
316
+ if (isJsonOutput()) {
317
+ printJson(response.data);
318
+ return;
319
+ }
320
+ const results = Array.isArray(response.data) ? response.data : response.data.results;
321
+ if (!results || results.length === 0) {
322
+ printInfo("No analysis results found for this call");
323
+ printInfo(`Run 'chanl calls analyze ${id}' to trigger analysis`);
324
+ return;
325
+ }
326
+ printBlank();
327
+ console.log(chalk.bold(`Analysis Results for Call ${id.slice(-12)}`));
328
+ console.log(chalk.dim("\u2500".repeat(60)));
329
+ for (const result of results) {
330
+ console.log(chalk.bold(`
331
+ Scorecard: ${result.scorecardId?.slice(-8) || "Unknown"}`));
332
+ console.log(` Overall Score: ${formatScore(result.overallScore)}`);
333
+ console.log(` Status: ${result.status || "completed"}`);
334
+ if (result.categoryScores && result.categoryScores.length > 0) {
335
+ console.log(chalk.bold("\n Category Scores:"));
336
+ for (const cat of result.categoryScores) {
337
+ console.log(` ${cat.categoryName}: ${formatScore(cat.score)}`);
338
+ }
339
+ }
340
+ if (result.feedback) {
341
+ console.log(chalk.bold("\n Feedback:"));
342
+ console.log(` ${result.feedback}`);
343
+ }
344
+ }
345
+ printBlank();
346
+ } catch (error) {
347
+ spinner.fail("Failed to fetch analysis");
348
+ const message = error instanceof Error ? error.message : "Unknown error";
349
+ printError("Error", message);
350
+ process.exitCode = 1;
351
+ }
352
+ }
353
+ async function handleCallsTranscript(id, options) {
354
+ const sdk = createSdk();
355
+ if (!sdk) return;
356
+ const spinner = ora("Fetching transcript...").start();
357
+ try {
358
+ const response = await sdk.calls.getTranscript(id);
359
+ spinner.stop();
360
+ if (!response.success || !response.data) {
361
+ printError("Failed to fetch transcript", response.message);
362
+ process.exitCode = 1;
363
+ return;
364
+ }
365
+ if (isJsonOutput()) {
366
+ printJson(response.data);
367
+ return;
368
+ }
369
+ const transcript = response.data.transcript || response.data;
370
+ if (!transcript) {
371
+ printInfo("No transcript available for this call");
372
+ return;
373
+ }
374
+ printBlank();
375
+ console.log(chalk.bold(`Transcript for Call ${id.slice(-12)}`));
376
+ console.log(chalk.dim("\u2500".repeat(60)));
377
+ if (transcript.wordCount) {
378
+ printLabel("Word Count", transcript.wordCount.toString());
379
+ }
380
+ if (transcript.speakerCount) {
381
+ printLabel("Speakers", transcript.speakerCount.toString());
382
+ }
383
+ if (transcript.language) {
384
+ printLabel("Language", transcript.language);
385
+ }
386
+ if (transcript.confidence) {
387
+ printLabel("Confidence", `${(transcript.confidence * 100).toFixed(1)}%`);
388
+ }
389
+ console.log(chalk.dim("\u2500".repeat(60)));
390
+ if (options.full || !transcript.segments || transcript.segments.length === 0) {
391
+ console.log("\n" + (transcript.text || "No text available"));
392
+ } else {
393
+ console.log("");
394
+ for (const segment of transcript.segments) {
395
+ const speaker = segment.speaker === "speaker_0" ? chalk.cyan("Agent") : chalk.yellow("Customer");
396
+ const time = `[${formatDuration(segment.start)} - ${formatDuration(segment.end)}]`;
397
+ console.log(`${speaker} ${chalk.dim(time)}`);
398
+ console.log(` ${segment.text}`);
399
+ console.log("");
400
+ }
401
+ }
402
+ if (transcript.toolCalls && transcript.toolCalls.length > 0) {
403
+ console.log(chalk.dim("\u2500".repeat(60)));
404
+ console.log(chalk.bold(`Tool Calls (${transcript.toolCalls.length}):`));
405
+ console.log("");
406
+ printTranscriptToolCalls(transcript.toolCalls);
407
+ }
408
+ printBlank();
409
+ } catch (error) {
410
+ spinner.fail("Failed to fetch transcript");
411
+ const message = error instanceof Error ? error.message : "Unknown error";
412
+ printError("Error", message);
413
+ process.exitCode = 1;
414
+ }
415
+ }
416
+ async function handleCallsMetrics(id) {
417
+ const sdk = createSdk();
418
+ if (!sdk) return;
419
+ const spinner = ora("Fetching metrics...").start();
420
+ try {
421
+ const response = await sdk.calls.getMetrics(id);
422
+ spinner.stop();
423
+ if (!response.success || !response.data) {
424
+ printError("Failed to fetch metrics", response.message);
425
+ process.exitCode = 1;
426
+ return;
427
+ }
428
+ if (isJsonOutput()) {
429
+ printJson(response.data);
430
+ return;
431
+ }
432
+ const metrics = response.data.metrics || response.data;
433
+ printBlank();
434
+ console.log(chalk.bold(`Metrics for Call ${id.slice(-12)}`));
435
+ console.log(chalk.dim("\u2500".repeat(50)));
436
+ if (metrics.duration) {
437
+ printLabel("Duration", formatDuration(metrics.duration));
438
+ }
439
+ if (metrics.talkTime) {
440
+ console.log(chalk.bold("\nTalk Time:"));
441
+ if (metrics.talkTime.total) console.log(` Total: ${formatDuration(metrics.talkTime.total)}`);
442
+ if (metrics.talkTime.agent) console.log(` Agent: ${formatDuration(metrics.talkTime.agent)}`);
443
+ if (metrics.talkTime.customer) console.log(` Customer: ${formatDuration(metrics.talkTime.customer)}`);
444
+ }
445
+ if (metrics.responseTime) {
446
+ console.log(chalk.bold("\nResponse Time:"));
447
+ if (metrics.responseTime.average) console.log(` Average: ${metrics.responseTime.average.toFixed(0)}ms`);
448
+ if (metrics.responseTime.max) console.log(` Max: ${metrics.responseTime.max.toFixed(0)}ms`);
449
+ }
450
+ if (metrics.silence) {
451
+ console.log(chalk.bold("\nSilence:"));
452
+ if (metrics.silence.total) console.log(` Total: ${formatDuration(metrics.silence.total)}`);
453
+ if (metrics.silence.count) console.log(` Count: ${metrics.silence.count} pauses`);
454
+ }
455
+ if (metrics.interruptions) {
456
+ console.log(chalk.bold("\nInterruptions:"));
457
+ if (metrics.interruptions.total !== void 0) console.log(` Total: ${metrics.interruptions.total}`);
458
+ if (metrics.interruptions.byAgent !== void 0) console.log(` By Agent: ${metrics.interruptions.byAgent}`);
459
+ if (metrics.interruptions.byCustomer !== void 0) console.log(` By Customer: ${metrics.interruptions.byCustomer}`);
460
+ }
461
+ if (metrics.turnCount) {
462
+ printLabel("\nTurn Count", metrics.turnCount.toString());
463
+ }
464
+ printBlank();
465
+ } catch (error) {
466
+ spinner.fail("Failed to fetch metrics");
467
+ const message = error instanceof Error ? error.message : "Unknown error";
468
+ printError("Error", message);
469
+ process.exitCode = 1;
470
+ }
471
+ }
472
+ async function handleCallsImport(options) {
473
+ const sdk = createSdk();
474
+ if (!sdk) return;
475
+ const hasTranscript = !!options.transcript;
476
+ const hasFile = !!options.file;
477
+ const hasAudioUrl = !!options.audioUrl;
478
+ const hasAudioId = !!options.audioId;
479
+ const hasS3 = !!(options.bucket && options.key);
480
+ if (!hasTranscript && !hasFile && !hasAudioUrl && !hasAudioId && !hasS3) {
481
+ printError(
482
+ "Missing import source",
483
+ "Provide one of: --transcript, --file, --audio-url, --audio-id, or --bucket/--key"
484
+ );
485
+ process.exitCode = 1;
486
+ return;
487
+ }
488
+ let importOptions;
489
+ if (options.file) {
490
+ try {
491
+ const fileContent = fs.readFileSync(options.file, "utf-8");
492
+ importOptions = JSON.parse(fileContent);
493
+ } catch (err) {
494
+ const message = err instanceof Error ? err.message : "Unknown error";
495
+ printError("Failed to read import file", message);
496
+ process.exitCode = 1;
497
+ return;
498
+ }
499
+ } else {
500
+ importOptions = {};
501
+ if (options.transcript) {
502
+ try {
503
+ const parsed = JSON.parse(options.transcript);
504
+ if (Array.isArray(parsed)) {
505
+ importOptions.transcript = parsed;
506
+ } else {
507
+ importOptions.transcript = options.transcript;
508
+ }
509
+ } catch {
510
+ importOptions.transcript = options.transcript;
511
+ }
512
+ }
513
+ if (options.audioUrl) {
514
+ importOptions.audioUrl = options.audioUrl;
515
+ }
516
+ if (options.audioId) {
517
+ importOptions.audioId = options.audioId;
518
+ }
519
+ if (options.bucket) {
520
+ importOptions.bucket = options.bucket;
521
+ }
522
+ if (options.key) {
523
+ importOptions.key = options.key;
524
+ }
525
+ if (options.region) {
526
+ importOptions.region = options.region;
527
+ }
528
+ if (options.customerName) {
529
+ importOptions.customerName = options.customerName;
530
+ }
531
+ if (options.agentName) {
532
+ importOptions.agentName = options.agentName;
533
+ }
534
+ if (options.direction) {
535
+ importOptions.callDirection = options.direction;
536
+ }
537
+ if (options.externalRef && Object.keys(options.externalRef).length > 0) {
538
+ importOptions.externalReferenceIds = options.externalRef;
539
+ }
540
+ if (options.analysisFields) {
541
+ importOptions.analysisFields = options.analysisFields.split(",").map((f) => f.trim());
542
+ }
543
+ if (options.scorecard) {
544
+ importOptions.scorecardId = options.scorecard;
545
+ }
546
+ if (options.webhookUrl) {
547
+ importOptions.webhookUrl = options.webhookUrl;
548
+ }
549
+ }
550
+ const spinner = ora("Importing call...").start();
551
+ try {
552
+ const response = await sdk.calls.import(importOptions);
553
+ spinner.stop();
554
+ if (!response.success || !response.data) {
555
+ printError("Failed to import call", response.message);
556
+ process.exitCode = 1;
557
+ return;
558
+ }
559
+ if (isJsonOutput()) {
560
+ printJson(response.data);
561
+ return;
562
+ }
563
+ const { callId, processingStatus, importMode, externalReferenceIds } = response.data;
564
+ printBlank();
565
+ printSuccess("Call imported successfully");
566
+ printLabel("Call ID", callId);
567
+ printLabel("Status", processingStatus || "pending");
568
+ if (importMode) {
569
+ printLabel("Import Mode", importMode);
570
+ }
571
+ if (externalReferenceIds && Object.keys(externalReferenceIds).length > 0) {
572
+ printLabel("External Refs", Object.entries(externalReferenceIds).map(([k, v]) => `${k}=${v}`).join(", "));
573
+ }
574
+ printBlank();
575
+ printInfo(`Run 'chanl calls get ${callId}' to view call details`);
576
+ printBlank();
577
+ } catch (error) {
578
+ spinner.fail("Failed to import call");
579
+ const message = error instanceof Error ? error.message : "Unknown error";
580
+ printError("Error", message);
581
+ process.exitCode = 1;
582
+ }
583
+ }
584
+ async function handleCallsDelete(id, options) {
585
+ const sdk = createSdk();
586
+ if (!sdk) return;
587
+ if (!options.yes) {
588
+ printWarning(`This will permanently delete call ${id.slice(-12)}`);
589
+ printInfo("Use --yes to skip this confirmation");
590
+ const readline = await import("readline");
591
+ const rl = readline.createInterface({
592
+ input: process.stdin,
593
+ output: process.stdout
594
+ });
595
+ const answer = await new Promise((resolve) => {
596
+ rl.question(chalk.yellow("Are you sure? (y/N): "), (ans) => {
597
+ rl.close();
598
+ resolve(ans);
599
+ });
600
+ });
601
+ if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
602
+ printInfo("Cancelled");
603
+ return;
604
+ }
605
+ }
606
+ const spinner = ora("Deleting call...").start();
607
+ try {
608
+ const response = await sdk.calls.delete(id);
609
+ spinner.stop();
610
+ if (!response.success) {
611
+ printError("Failed to delete call", response.message);
612
+ process.exitCode = 1;
613
+ return;
614
+ }
615
+ if (isJsonOutput()) {
616
+ printJson({ deleted: true, callId: id });
617
+ return;
618
+ }
619
+ printSuccess(`Call ${id.slice(-12)} deleted successfully`);
620
+ } catch (error) {
621
+ spinner.fail("Failed to delete call");
622
+ const message = error instanceof Error ? error.message : "Unknown error";
623
+ printError("Error", message);
624
+ process.exitCode = 1;
625
+ }
626
+ }
627
+ async function handleCallsBulkDelete(options) {
628
+ const sdk = createSdk();
629
+ if (!sdk) return;
630
+ let callIds = [];
631
+ if (options.ids) {
632
+ callIds = options.ids.split(",").map((id) => id.trim()).filter(Boolean);
633
+ }
634
+ if (options.file) {
635
+ try {
636
+ const fileContent = fs.readFileSync(options.file, "utf-8");
637
+ const fileIds = fileContent.split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
638
+ callIds = [...callIds, ...fileIds];
639
+ } catch (err) {
640
+ const message = err instanceof Error ? err.message : "Unknown error";
641
+ printError("Failed to read IDs file", message);
642
+ process.exitCode = 1;
643
+ return;
644
+ }
645
+ }
646
+ callIds = [...new Set(callIds)];
647
+ if (callIds.length === 0) {
648
+ printError("No call IDs provided", "Use --ids or --file to specify calls to delete");
649
+ process.exitCode = 1;
650
+ return;
651
+ }
652
+ if (!options.yes) {
653
+ printWarning(`This will permanently delete ${callIds.length} call(s)`);
654
+ printInfo("Use --yes to skip this confirmation");
655
+ const readline = await import("readline");
656
+ const rl = readline.createInterface({
657
+ input: process.stdin,
658
+ output: process.stdout
659
+ });
660
+ const answer = await new Promise((resolve) => {
661
+ rl.question(chalk.yellow("Are you sure? (y/N): "), (ans) => {
662
+ rl.close();
663
+ resolve(ans);
664
+ });
665
+ });
666
+ if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
667
+ printInfo("Cancelled");
668
+ return;
669
+ }
670
+ }
671
+ const spinner = ora(`Deleting ${callIds.length} calls...`).start();
672
+ try {
673
+ const response = await sdk.calls.bulkDelete({
674
+ callIds,
675
+ deleteStorageFiles: !options.keepFiles
676
+ });
677
+ spinner.stop();
678
+ if (!response.success || !response.data) {
679
+ printError("Failed to bulk delete calls", response.message);
680
+ process.exitCode = 1;
681
+ return;
682
+ }
683
+ if (isJsonOutput()) {
684
+ printJson(response.data);
685
+ return;
686
+ }
687
+ const { deletedCount, failedIds, storageFilesDeleted } = response.data;
688
+ printBlank();
689
+ printSuccess(`Successfully deleted ${deletedCount} call(s)`);
690
+ if (storageFilesDeleted > 0) {
691
+ printInfo(`Deleted ${storageFilesDeleted} storage files`);
692
+ }
693
+ if (failedIds && failedIds.length > 0) {
694
+ printWarning(`Failed to delete ${failedIds.length} call(s): ${failedIds.slice(0, 5).join(", ")}${failedIds.length > 5 ? "..." : ""}`);
695
+ }
696
+ printBlank();
697
+ } catch (error) {
698
+ spinner.fail("Failed to bulk delete calls");
699
+ const message = error instanceof Error ? error.message : "Unknown error";
700
+ printError("Error", message);
701
+ process.exitCode = 1;
702
+ }
703
+ }
704
+ function printTranscriptToolCalls(toolCalls) {
705
+ for (const tc of toolCalls) {
706
+ const statusIcon = tc.status === "success" ? chalk.green("\u2713") : tc.status === "error" ? chalk.red("\u2717") : tc.status === "timeout" ? chalk.yellow("\u23F1") : chalk.dim("\u2026");
707
+ const duration = tc.durationMs ? chalk.dim(` (${tc.durationMs}ms)`) : "";
708
+ const time = tc.secondsFromStart !== void 0 ? chalk.dim(` @ ${tc.secondsFromStart.toFixed(1)}s`) : "";
709
+ const params = Object.keys(tc.parameters).length ? chalk.dim(` ${JSON.stringify(tc.parameters)}`) : "";
710
+ console.log(` ${statusIcon} ${chalk.yellow(tc.name)}${params}${duration}${time}`);
711
+ if (tc.status === "error" && tc.error) {
712
+ console.log(` ${chalk.red(tc.error.message)}`);
713
+ }
714
+ }
715
+ }
716
+ export {
717
+ createCallsCommand
718
+ };
719
+ //# sourceMappingURL=calls.js.map