@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.
- package/bin/chanl.js +10 -0
- package/dist/__tests__/cli.test.d.ts +2 -0
- package/dist/__tests__/cli.test.js +2313 -0
- package/dist/__tests__/cli.test.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +72 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/agents.d.ts +8 -0
- package/dist/commands/agents.js +671 -0
- package/dist/commands/agents.js.map +1 -0
- package/dist/commands/auth.d.ts +16 -0
- package/dist/commands/auth.js +294 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/call.d.ts +8 -0
- package/dist/commands/call.js +166 -0
- package/dist/commands/call.js.map +1 -0
- package/dist/commands/calls.d.ts +8 -0
- package/dist/commands/calls.js +719 -0
- package/dist/commands/calls.js.map +1 -0
- package/dist/commands/chat.d.ts +8 -0
- package/dist/commands/chat.js +203 -0
- package/dist/commands/chat.js.map +1 -0
- package/dist/commands/config.d.ts +8 -0
- package/dist/commands/config.js +231 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/health.d.ts +8 -0
- package/dist/commands/health.js +55 -0
- package/dist/commands/health.js.map +1 -0
- package/dist/commands/index.d.ts +18 -0
- package/dist/commands/index.js +39 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/knowledge.d.ts +8 -0
- package/dist/commands/knowledge.js +539 -0
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/mcp.d.ts +8 -0
- package/dist/commands/mcp.js +589 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/memory.d.ts +8 -0
- package/dist/commands/memory.js +408 -0
- package/dist/commands/memory.js.map +1 -0
- package/dist/commands/personas.d.ts +8 -0
- package/dist/commands/personas.js +356 -0
- package/dist/commands/personas.js.map +1 -0
- package/dist/commands/prompts.d.ts +8 -0
- package/dist/commands/prompts.js +295 -0
- package/dist/commands/prompts.js.map +1 -0
- package/dist/commands/scenarios.d.ts +8 -0
- package/dist/commands/scenarios.js +591 -0
- package/dist/commands/scenarios.js.map +1 -0
- package/dist/commands/scorecards.d.ts +8 -0
- package/dist/commands/scorecards.js +570 -0
- package/dist/commands/scorecards.js.map +1 -0
- package/dist/commands/tools.d.ts +8 -0
- package/dist/commands/tools.js +632 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commands/toolsets.d.ts +8 -0
- package/dist/commands/toolsets.js +464 -0
- package/dist/commands/toolsets.js.map +1 -0
- package/dist/commands/workspaces.d.ts +8 -0
- package/dist/commands/workspaces.js +170 -0
- package/dist/commands/workspaces.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/config-store.d.ts +117 -0
- package/dist/utils/config-store.js +191 -0
- package/dist/utils/config-store.js.map +1 -0
- package/dist/utils/interactive.d.ts +41 -0
- package/dist/utils/interactive.js +83 -0
- package/dist/utils/interactive.js.map +1 -0
- package/dist/utils/output.d.ts +100 -0
- package/dist/utils/output.js +221 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/sdk-factory.d.ts +15 -0
- package/dist/utils/sdk-factory.js +34 -0
- package/dist/utils/sdk-factory.js.map +1 -0
- 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
|