@mikado-ai/cli 0.1.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,597 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ MikadoClient,
4
+ pollJob
5
+ } from "./chunk-L27HHHAB.js";
6
+
7
+ // src/index.ts
8
+ import { Command } from "commander";
9
+
10
+ // src/commands/auth.ts
11
+ import { createInterface } from "readline";
12
+ import chalk2 from "chalk";
13
+
14
+ // src/core/auth.ts
15
+ import { homedir } from "os";
16
+ import { join } from "path";
17
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
18
+ function getConfigPath() {
19
+ return join(homedir(), ".mikado", "config.json");
20
+ }
21
+ function loadConfig() {
22
+ const envApiKey = process.env.MIKADO_API_KEY;
23
+ const envUrl = process.env.MIKADO_URL;
24
+ if (envApiKey && envUrl) {
25
+ return {
26
+ api_key: envApiKey,
27
+ base_url: envUrl
28
+ };
29
+ }
30
+ const configPath = getConfigPath();
31
+ if (!existsSync(configPath)) {
32
+ if (envApiKey && existsSync(configPath)) {
33
+ try {
34
+ const fileConfig = JSON.parse(readFileSync(configPath, "utf-8"));
35
+ return {
36
+ api_key: envApiKey,
37
+ base_url: fileConfig.base_url || envUrl || "https://mikadoai.app"
38
+ };
39
+ } catch {
40
+ }
41
+ }
42
+ return null;
43
+ }
44
+ try {
45
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
46
+ return {
47
+ api_key: envApiKey || config.api_key,
48
+ base_url: envUrl || config.base_url
49
+ };
50
+ } catch (error) {
51
+ throw new Error(`Failed to read config file at ${configPath}: ${error}`);
52
+ }
53
+ }
54
+ function saveConfig(config) {
55
+ const configPath = getConfigPath();
56
+ const configDir = join(homedir(), ".mikado");
57
+ if (!existsSync(configDir)) {
58
+ mkdirSync(configDir, { recursive: true, mode: 448 });
59
+ }
60
+ writeFileSync(configPath, JSON.stringify(config, null, 2), { mode: 384 });
61
+ }
62
+
63
+ // src/output/human.ts
64
+ import chalk from "chalk";
65
+ function formatDuration(ms) {
66
+ if (ms < 1e3) return `${ms}ms`;
67
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
68
+ const minutes = Math.floor(ms / 6e4);
69
+ const seconds = Math.floor(ms % 6e4 / 1e3);
70
+ return `${minutes}m ${seconds}s`;
71
+ }
72
+ function renderWhoami(response) {
73
+ console.log(chalk.green(`
74
+ \u2713 Authenticated as "${response.organization_name}" (${response.key_prefix}...)`));
75
+ console.log(` Key name: ${response.key_name}`);
76
+ console.log();
77
+ }
78
+ function renderJobStatus(job) {
79
+ const statusColors = {
80
+ PENDING: chalk.gray,
81
+ IN_PROGRESS: chalk.yellow,
82
+ SUCCESS: chalk.green,
83
+ FAILED: chalk.red,
84
+ RETRY: chalk.yellow,
85
+ CANCELLED: chalk.gray
86
+ };
87
+ const colorFn = statusColors[job.status] || chalk.white;
88
+ const statusIcon = job.status === "SUCCESS" ? "\u2713" : job.status === "FAILED" ? "\u2717" : "\u25D0";
89
+ console.log(`
90
+ Job: ${job.job_id}`);
91
+ console.log(` Status: ${statusIcon} ${colorFn(job.status)}`);
92
+ if (job.elapsed_ms) {
93
+ console.log(` Duration: ${formatDuration(job.elapsed_ms)}`);
94
+ }
95
+ if (job.entity_id) {
96
+ console.log(` Conversation: ${job.entity_id}`);
97
+ }
98
+ if (job.error_message) {
99
+ console.log(` Error: ${chalk.red(job.error_message)}`);
100
+ }
101
+ if (job.conversation_summary) {
102
+ const s = job.conversation_summary;
103
+ console.log(` Insights: ${s.insight_count} generated`);
104
+ if (s.sentiment) {
105
+ console.log(` Sentiment: ${s.sentiment.toLowerCase()} (${s.sentiment_score?.toFixed(2) ?? "N/A"})`);
106
+ }
107
+ if (s.outcome) {
108
+ console.log(` Outcome: ${s.outcome}`);
109
+ }
110
+ }
111
+ console.log();
112
+ }
113
+ function renderSubmitResult(conversation) {
114
+ console.log(chalk.bold.green("\n\u2713 Processing Complete\n"));
115
+ const meta = [];
116
+ if (conversation.provider) meta.push(`Provider: ${conversation.provider.toLowerCase()}`);
117
+ if (conversation.analytics.duration_ms) meta.push(`Duration: ${formatDuration(conversation.analytics.duration_ms)}`);
118
+ if (conversation.analytics.turn_count) meta.push(`Turns: ${conversation.analytics.turn_count}`);
119
+ if (meta.length > 0) {
120
+ console.log(` ${meta.join(" | ")}`);
121
+ }
122
+ if (conversation.analytics.sentiment) {
123
+ const score = conversation.analytics.sentiment_score?.toFixed(2) ?? "";
124
+ console.log(` Sentiment: ${conversation.analytics.sentiment.toLowerCase()} (${score})`);
125
+ }
126
+ if (conversation.analytics.outcome_result) {
127
+ console.log(` Outcome: ${conversation.analytics.outcome_result}`);
128
+ }
129
+ if (conversation.participants.length > 0) {
130
+ console.log(chalk.bold("\n Participants:"));
131
+ conversation.participants.forEach((p) => {
132
+ const name = p.name || p.id;
133
+ const role = p.role ? ` (${p.role})` : "";
134
+ console.log(` - ${name}${role} [${p.type}]`);
135
+ });
136
+ }
137
+ if (conversation.insights.length > 0) {
138
+ console.log(chalk.bold("\n Insights:"));
139
+ conversation.insights.forEach((insight) => {
140
+ const name = insight.template_name || "Unnamed";
141
+ const scoreStr = insight.total_score != null ? ` \u2014 Score: ${insight.total_score}` : "";
142
+ const labels = insight.labels?.map((l) => l.value).join(", ");
143
+ const labelStr = labels ? ` (${labels})` : "";
144
+ console.log(` ${chalk.cyan(name)}${scoreStr}${labelStr}`);
145
+ if (insight.summary) {
146
+ console.log(` ${insight.summary}`);
147
+ }
148
+ });
149
+ }
150
+ if (conversation.processing.processing_time_ms) {
151
+ console.log(`
152
+ Processed in ${formatDuration(conversation.processing.processing_time_ms)}`);
153
+ }
154
+ console.log(`
155
+ Conversation: ${conversation.id}`);
156
+ console.log();
157
+ }
158
+ function renderCampaigns(response) {
159
+ if (response.campaigns.length === 0) {
160
+ console.log(chalk.gray("\n No active campaigns found.\n"));
161
+ return;
162
+ }
163
+ console.log();
164
+ const idWidth = 10;
165
+ const nameWidth = Math.max(...response.campaigns.map((c) => c.name.length), 4) + 2;
166
+ const statusWidth = 10;
167
+ const templatesWidth = 10;
168
+ const convsWidth = 14;
169
+ console.log(
170
+ chalk.bold(" " + "ID".padEnd(idWidth)) + chalk.bold("Name".padEnd(nameWidth)) + chalk.bold("Status".padEnd(statusWidth)) + chalk.bold("Templates".padEnd(templatesWidth)) + chalk.bold("Conversations")
171
+ );
172
+ response.campaigns.forEach((c) => {
173
+ const statusColor = c.status === "ACTIVE" ? chalk.green : chalk.gray;
174
+ console.log(
175
+ ` ${c.short_id.padEnd(idWidth)}${chalk.cyan(c.name.padEnd(nameWidth))}${statusColor(c.status.padEnd(statusWidth))}${String(c.template_count).padEnd(templatesWidth)}${c.conversation_count}`
176
+ );
177
+ });
178
+ console.log();
179
+ }
180
+
181
+ // src/output/json.ts
182
+ function outputJson(data) {
183
+ console.log(JSON.stringify(data, null, 2));
184
+ }
185
+ function errorJson(message, code) {
186
+ const error = {
187
+ error: message,
188
+ ...code && { code }
189
+ };
190
+ console.error(JSON.stringify(error, null, 2));
191
+ }
192
+
193
+ // src/commands/auth.ts
194
+ async function prompt(question) {
195
+ const rl = createInterface({
196
+ input: process.stdin,
197
+ output: process.stdout
198
+ });
199
+ return new Promise((resolve) => {
200
+ rl.question(question, (answer) => {
201
+ rl.close();
202
+ resolve(answer.trim());
203
+ });
204
+ });
205
+ }
206
+ async function promptSecret(question) {
207
+ return new Promise((resolve) => {
208
+ process.stdout.write(question);
209
+ const stdin = process.stdin;
210
+ const wasRaw = stdin.isRaw;
211
+ if (stdin.isTTY) {
212
+ stdin.setRawMode(true);
213
+ }
214
+ stdin.resume();
215
+ stdin.setEncoding("utf-8");
216
+ let input = "";
217
+ const onData = (chunk) => {
218
+ for (const char of chunk) {
219
+ if (char === "\n" || char === "\r" || char === "") {
220
+ stdin.removeListener("data", onData);
221
+ if (stdin.isTTY) {
222
+ stdin.setRawMode(wasRaw ?? false);
223
+ }
224
+ stdin.pause();
225
+ process.stdout.write("\n");
226
+ resolve(input);
227
+ return;
228
+ } else if (char === "") {
229
+ process.exit(1);
230
+ } else if (char === "\x7F" || char === "\b") {
231
+ if (input.length > 0) {
232
+ input = input.slice(0, -1);
233
+ process.stdout.write("\b \b");
234
+ }
235
+ } else {
236
+ input += char;
237
+ process.stdout.write("*");
238
+ }
239
+ }
240
+ };
241
+ stdin.on("data", onData);
242
+ });
243
+ }
244
+ async function authCommand(options) {
245
+ let apiKey = options.key;
246
+ let baseUrl = options.url;
247
+ if (!apiKey || !baseUrl) {
248
+ console.log(chalk2.bold("\nMikado AI CLI Authentication Setup\n"));
249
+ if (!apiKey) {
250
+ apiKey = await promptSecret("API Key: ");
251
+ }
252
+ if (!baseUrl) {
253
+ const defaultUrl = "https://mikadoai.app";
254
+ const urlInput = await prompt(`Base URL [${defaultUrl}]: `);
255
+ baseUrl = urlInput || defaultUrl;
256
+ }
257
+ }
258
+ if (!apiKey) {
259
+ if (options.json) {
260
+ errorJson("API key is required", "MISSING_API_KEY");
261
+ } else {
262
+ console.error(chalk2.red("Error: API key is required"));
263
+ }
264
+ process.exit(2);
265
+ }
266
+ if (!baseUrl) {
267
+ baseUrl = "https://mikadoai.app";
268
+ }
269
+ try {
270
+ const client = new MikadoClient({ apiKey, baseUrl });
271
+ const whoami = await client.whoami();
272
+ saveConfig({
273
+ api_key: apiKey,
274
+ base_url: baseUrl
275
+ });
276
+ if (options.json) {
277
+ outputJson({
278
+ success: true,
279
+ ...whoami
280
+ });
281
+ } else {
282
+ console.log(chalk2.green("\n\u2713 Authentication successful!"));
283
+ renderWhoami(whoami);
284
+ console.log(chalk2.gray(`Config saved to ~/.mikado/config.json`));
285
+ }
286
+ } catch (error) {
287
+ if (options.json) {
288
+ errorJson(error instanceof Error ? error.message : "Authentication failed", "AUTH_FAILED");
289
+ } else {
290
+ console.error(chalk2.red("\n\u2717 Authentication failed:"));
291
+ console.error(chalk2.red(` ${error instanceof Error ? error.message : "Unknown error"}`));
292
+ }
293
+ process.exit(1);
294
+ }
295
+ }
296
+
297
+ // src/commands/submit.ts
298
+ import { readFileSync as readFileSync2 } from "fs";
299
+ import chalk3 from "chalk";
300
+ import ora from "ora";
301
+ async function readInput(filePath) {
302
+ if (filePath === "-") {
303
+ return new Promise((resolve, reject) => {
304
+ let data = "";
305
+ process.stdin.setEncoding("utf-8");
306
+ process.stdin.on("data", (chunk) => {
307
+ data += chunk;
308
+ });
309
+ process.stdin.on("end", () => resolve(data));
310
+ process.stdin.on("error", reject);
311
+ });
312
+ } else {
313
+ return readFileSync2(filePath, "utf-8");
314
+ }
315
+ }
316
+ async function submitCommand(filePath, options) {
317
+ const config = loadConfig();
318
+ if (!config) {
319
+ if (options.json) {
320
+ errorJson("Not authenticated. Run: mikado auth", "NOT_AUTHENTICATED");
321
+ } else {
322
+ console.error(chalk3.red("Error: Not authenticated"));
323
+ console.error(chalk3.yellow("Run: mikado auth"));
324
+ }
325
+ process.exit(1);
326
+ }
327
+ const client = new MikadoClient({
328
+ apiKey: config.api_key,
329
+ baseUrl: config.base_url
330
+ });
331
+ let content;
332
+ try {
333
+ content = await readInput(filePath);
334
+ } catch (error) {
335
+ if (options.json) {
336
+ errorJson(
337
+ `Failed to read file: ${error instanceof Error ? error.message : "Unknown error"}`,
338
+ "FILE_READ_ERROR"
339
+ );
340
+ } else {
341
+ console.error(chalk3.red("Error: Failed to read file"));
342
+ console.error(chalk3.red(` ${error instanceof Error ? error.message : "Unknown error"}`));
343
+ }
344
+ process.exit(2);
345
+ }
346
+ if (!content || content.trim().length === 0) {
347
+ if (options.json) {
348
+ errorJson("File is empty", "EMPTY_FILE");
349
+ } else {
350
+ console.error(chalk3.red("Error: File is empty"));
351
+ }
352
+ process.exit(2);
353
+ }
354
+ let submitResult;
355
+ try {
356
+ submitResult = await client.submit(content, {
357
+ campaign: options.campaign,
358
+ filename: filePath !== "-" ? filePath : void 0
359
+ });
360
+ } catch (error) {
361
+ if (options.json) {
362
+ errorJson(
363
+ `Failed to submit: ${error instanceof Error ? error.message : "Unknown error"}`,
364
+ "SUBMIT_FAILED"
365
+ );
366
+ } else {
367
+ console.error(chalk3.red("Error: Failed to submit transcript"));
368
+ console.error(chalk3.red(` ${error instanceof Error ? error.message : "Unknown error"}`));
369
+ }
370
+ process.exit(1);
371
+ }
372
+ if (!options.wait) {
373
+ if (options.json) {
374
+ outputJson(submitResult);
375
+ } else {
376
+ console.log(chalk3.green("\u2713 Submitted successfully"));
377
+ console.log(` Job ID: ${submitResult.job_id}`);
378
+ console.log(` Conversation: ${submitResult.conversation_id}`);
379
+ console.log(` Status: ${submitResult.status}`);
380
+ console.log();
381
+ console.log(chalk3.gray("Check status with: mikado status " + submitResult.job_id));
382
+ }
383
+ return;
384
+ }
385
+ const spinner = options.json ? null : ora("Processing transcript...").start();
386
+ const timeoutMs = options.timeout ? options.timeout * 1e3 : 3e5;
387
+ try {
388
+ const finalStatus = await pollJob(client, submitResult.job_id, {
389
+ timeout: timeoutMs,
390
+ onUpdate: (status) => {
391
+ if (spinner) {
392
+ const statusText = status.status.toLowerCase();
393
+ spinner.text = `Processing transcript... (${statusText})`;
394
+ }
395
+ }
396
+ });
397
+ if (spinner) {
398
+ spinner.stop();
399
+ }
400
+ if (finalStatus.status === "FAILED") {
401
+ if (options.json) {
402
+ errorJson(
403
+ finalStatus.error_message || "Processing failed",
404
+ "PROCESSING_FAILED"
405
+ );
406
+ } else {
407
+ console.error(chalk3.red("\n\u2717 Processing failed:"));
408
+ console.error(chalk3.red(` ${finalStatus.error_message || "Unknown error"}`));
409
+ }
410
+ process.exit(3);
411
+ }
412
+ const conversation = await client.getConversation(submitResult.conversation_id);
413
+ if (options.json) {
414
+ outputJson(conversation);
415
+ } else {
416
+ renderSubmitResult(conversation);
417
+ }
418
+ } catch (error) {
419
+ if (spinner) {
420
+ spinner.fail("Processing failed");
421
+ }
422
+ if (error instanceof Error && error.message.includes("timed out")) {
423
+ if (options.json) {
424
+ errorJson("Processing timed out", "TIMEOUT");
425
+ } else {
426
+ console.error(chalk3.red("\n\u2717 Processing timed out"));
427
+ console.error(chalk3.yellow(` Check status later with: mikado status ${submitResult.job_id}`));
428
+ }
429
+ process.exit(4);
430
+ } else {
431
+ if (options.json) {
432
+ errorJson(
433
+ error instanceof Error ? error.message : "Unknown error",
434
+ "POLLING_FAILED"
435
+ );
436
+ } else {
437
+ console.error(chalk3.red("\n\u2717 Error:"));
438
+ console.error(chalk3.red(` ${error instanceof Error ? error.message : "Unknown error"}`));
439
+ }
440
+ process.exit(1);
441
+ }
442
+ }
443
+ }
444
+
445
+ // src/commands/status.ts
446
+ import chalk4 from "chalk";
447
+ async function statusCommand(jobId, options) {
448
+ const config = loadConfig();
449
+ if (!config) {
450
+ if (options.json) {
451
+ errorJson("Not authenticated. Run: mikado auth", "NOT_AUTHENTICATED");
452
+ } else {
453
+ console.error(chalk4.red("Error: Not authenticated"));
454
+ console.error(chalk4.yellow("Run: mikado auth"));
455
+ }
456
+ process.exit(1);
457
+ }
458
+ const client = new MikadoClient({
459
+ apiKey: config.api_key,
460
+ baseUrl: config.base_url
461
+ });
462
+ try {
463
+ const status = await client.getJobStatus(jobId);
464
+ if (options.json) {
465
+ outputJson(status);
466
+ } else {
467
+ renderJobStatus(status);
468
+ }
469
+ } catch (error) {
470
+ if (options.json) {
471
+ errorJson(
472
+ error instanceof Error ? error.message : "Failed to fetch job status",
473
+ "STATUS_FAILED"
474
+ );
475
+ } else {
476
+ console.error(chalk4.red("Error: Failed to fetch job status"));
477
+ console.error(chalk4.red(` ${error instanceof Error ? error.message : "Unknown error"}`));
478
+ }
479
+ process.exit(1);
480
+ }
481
+ }
482
+
483
+ // src/commands/result.ts
484
+ import chalk5 from "chalk";
485
+ async function resultCommand(conversationId, options) {
486
+ const config = loadConfig();
487
+ if (!config) {
488
+ if (options.json) {
489
+ errorJson("Not authenticated. Run: mikado auth", "NOT_AUTHENTICATED");
490
+ } else {
491
+ console.error(chalk5.red("Error: Not authenticated"));
492
+ console.error(chalk5.yellow("Run: mikado auth"));
493
+ }
494
+ process.exit(1);
495
+ }
496
+ const client = new MikadoClient({
497
+ apiKey: config.api_key,
498
+ baseUrl: config.base_url
499
+ });
500
+ try {
501
+ const conversation = await client.getConversation(conversationId);
502
+ if (options.json) {
503
+ outputJson(conversation);
504
+ } else {
505
+ renderSubmitResult(conversation);
506
+ }
507
+ } catch (error) {
508
+ if (options.json) {
509
+ errorJson(
510
+ error instanceof Error ? error.message : "Failed to fetch conversation",
511
+ "RESULT_FAILED"
512
+ );
513
+ } else {
514
+ console.error(chalk5.red("Error: Failed to fetch conversation"));
515
+ console.error(chalk5.red(` ${error instanceof Error ? error.message : "Unknown error"}`));
516
+ }
517
+ process.exit(1);
518
+ }
519
+ }
520
+
521
+ // src/commands/campaigns.ts
522
+ import chalk6 from "chalk";
523
+ async function campaignsCommand(options) {
524
+ const config = loadConfig();
525
+ if (!config) {
526
+ if (options.json) {
527
+ errorJson("Not authenticated. Run: mikado auth", "NOT_AUTHENTICATED");
528
+ } else {
529
+ console.error(chalk6.red("Error: Not authenticated"));
530
+ console.error(chalk6.yellow("Run: mikado auth"));
531
+ }
532
+ process.exit(1);
533
+ }
534
+ const client = new MikadoClient({
535
+ apiKey: config.api_key,
536
+ baseUrl: config.base_url
537
+ });
538
+ try {
539
+ const campaigns = await client.listCampaigns();
540
+ if (options.json) {
541
+ outputJson(campaigns);
542
+ } else {
543
+ renderCampaigns(campaigns);
544
+ }
545
+ } catch (error) {
546
+ if (options.json) {
547
+ errorJson(
548
+ error instanceof Error ? error.message : "Failed to fetch campaigns",
549
+ "CAMPAIGNS_FAILED"
550
+ );
551
+ } else {
552
+ console.error(chalk6.red("Error: Failed to fetch campaigns"));
553
+ console.error(chalk6.red(` ${error instanceof Error ? error.message : "Unknown error"}`));
554
+ }
555
+ process.exit(1);
556
+ }
557
+ }
558
+
559
+ // src/index.ts
560
+ var program = new Command();
561
+ program.name("mikado").description("Mikado CLI - conversation data extraction").version("0.1.0");
562
+ program.option("--json", "Output results in JSON format");
563
+ program.command("auth").description("Authenticate with Mikado API").option("--key <api-key>", "API key (or use MIKADO_API_KEY env var)").option("--url <base-url>", "Base URL (default: https://mikadoai.app)").action(async (options) => {
564
+ await authCommand({
565
+ key: options.key,
566
+ url: options.url,
567
+ json: program.opts().json
568
+ });
569
+ });
570
+ program.command("submit <file>").description('Submit a transcript file for processing (use "-" to read from stdin)').option("-c, --campaign <id>", "Target campaign (short_id, UUID, or name)").option("--no-wait", "Do not wait for processing to complete").option("--timeout <seconds>", "Polling timeout in seconds (default: 300)", parseInt).action(async (file, options) => {
571
+ await submitCommand(file, {
572
+ campaign: options.campaign,
573
+ wait: options.wait,
574
+ timeout: options.timeout,
575
+ json: program.opts().json
576
+ });
577
+ });
578
+ program.command("status <job-id>").description("Check the status of a processing job").action(async (jobId) => {
579
+ await statusCommand(jobId, {
580
+ json: program.opts().json
581
+ });
582
+ });
583
+ program.command("result <conversation-id>").description("Get full results for a completed conversation").action(async (conversationId) => {
584
+ await resultCommand(conversationId, {
585
+ json: program.opts().json
586
+ });
587
+ });
588
+ program.command("campaigns").description("List available campaigns").action(async () => {
589
+ await campaignsCommand({
590
+ json: program.opts().json
591
+ });
592
+ });
593
+ program.command("mcp").description("Start MCP stdio server (requires MIKADO_API_KEY env var)").action(async () => {
594
+ const { startMcpServer } = await import("./mcp.js");
595
+ await startMcpServer();
596
+ });
597
+ program.parse();
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ declare function startMcpServer(): Promise<void>;
3
+
4
+ export { startMcpServer };