@mintlify/cli 4.0.1083 → 4.0.1085

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.
@@ -0,0 +1,620 @@
1
+ import { addLog, ErrorLog, SpinnerLog, removeLastLog } from '@mintlify/previewing';
2
+ import chalk from 'chalk';
3
+ import { Text } from 'ink';
4
+ import type { Argv } from 'yargs';
5
+
6
+ import { getConfigValue } from '../config.js';
7
+ import { terminate } from '../helpers.js';
8
+ import {
9
+ getBucketThreads,
10
+ getBuckets,
11
+ getConversations,
12
+ getFeedback,
13
+ getFeedbackByPage,
14
+ getKpi,
15
+ getSearches,
16
+ } from './client.js';
17
+ import { num, truncate } from './format.js';
18
+ import { formatBarChart, formatOutput, resolveFormat, type OutputFormat } from './output.js';
19
+ import type { Conversation } from './types.js';
20
+
21
+ const withSubdomain = <T extends object>(yargs: Argv<T>) =>
22
+ yargs.option('subdomain', {
23
+ type: 'string' as const,
24
+ default: getConfigValue('subdomain'),
25
+ description: 'Documentation subdomain (default: mint config set subdomain)',
26
+ });
27
+
28
+ function defaultFrom(): string {
29
+ const configured = getConfigValue('dateFrom');
30
+ if (configured) return configured;
31
+ const d = new Date();
32
+ d.setDate(d.getDate() - 7);
33
+ return d.toISOString().slice(0, 10);
34
+ }
35
+
36
+ function defaultTo(): string {
37
+ return getConfigValue('dateTo') ?? new Date().toISOString().slice(0, 10);
38
+ }
39
+
40
+ const withDates = <T extends object>(yargs: Argv<T>) =>
41
+ yargs
42
+ .option('from', {
43
+ type: 'string' as const,
44
+ default: defaultFrom(),
45
+ description: 'Start date (YYYY-MM-DD)',
46
+ })
47
+ .option('to', {
48
+ type: 'string' as const,
49
+ default: defaultTo(),
50
+ description: 'End date (YYYY-MM-DD)',
51
+ });
52
+
53
+ const withFormat = <T extends object>(yargs: Argv<T>) =>
54
+ yargs
55
+ .option('format', {
56
+ type: 'string' as const,
57
+ choices: ['table', 'plain', 'json', 'graph'] as const,
58
+ description: 'Output format (table=pretty, plain=pipeable, json=raw)',
59
+ })
60
+ .option('agent', {
61
+ type: 'boolean' as const,
62
+ description: 'Agent-friendly output (equivalent to --format json)',
63
+ });
64
+
65
+ const withAll = <T extends object>(yargs: Argv<T>) => withFormat(withDates(withSubdomain(yargs)));
66
+
67
+ function output(format: OutputFormat, text: string) {
68
+ if (format === 'table') {
69
+ addLog(<Text>{text}</Text>);
70
+ } else {
71
+ process.stdout.write(text + '\n');
72
+ }
73
+ }
74
+
75
+ export const analyticsBuilder = (yargs: Argv) =>
76
+ yargs
77
+ .command(
78
+ 'stats',
79
+ 'display KPI numbers (views, visitors, searches)',
80
+ (yargs) =>
81
+ withAll(yargs)
82
+ .option('agents', { type: 'boolean', description: 'Show only agent traffic' })
83
+ .option('humans', { type: 'boolean', description: 'Show only human traffic' })
84
+ .option('page', { type: 'string', description: 'Filter to a specific page path' }),
85
+ async (argv) => {
86
+ const format = resolveFormat(argv);
87
+ try {
88
+ if (format === 'table') addLog(<SpinnerLog message="Fetching analytics..." />);
89
+ const kpi = await getKpi(
90
+ { dateFrom: argv.from, dateTo: argv.to, page: argv.page },
91
+ argv.subdomain
92
+ );
93
+ if (format === 'table') removeLastLog();
94
+
95
+ if (format === 'json') {
96
+ output(format, JSON.stringify(kpi, null, 2));
97
+ await terminate(0);
98
+ return;
99
+ }
100
+
101
+ if (format === 'plain') {
102
+ const lines = [
103
+ ['METRIC', 'HUMAN', 'AGENT', 'TOTAL'].join('\t'),
104
+ ['Views', kpi.humanViews, kpi.agentViews, kpi.humanViews + kpi.agentViews].join('\t'),
105
+ [
106
+ 'Visitors',
107
+ kpi.humanVisitors,
108
+ kpi.agentVisitors,
109
+ kpi.humanVisitors + kpi.agentVisitors,
110
+ ].join('\t'),
111
+ ['Searches', kpi.humanSearches, '', kpi.humanSearches].join('\t'),
112
+ ['Feedback', kpi.humanFeedback, '', kpi.humanFeedback].join('\t'),
113
+ [
114
+ 'Assistant',
115
+ kpi.humanAssistant,
116
+ kpi.agentMcpSearches,
117
+ kpi.humanAssistant + kpi.agentMcpSearches,
118
+ ].join('\t'),
119
+ ];
120
+ output(format, lines.join('\n'));
121
+ await terminate(0);
122
+ return;
123
+ }
124
+
125
+ if (format === 'graph') {
126
+ const label = argv.subdomain ?? 'default';
127
+ const lines: string[] = [];
128
+ lines.push(chalk.bold(`\nAnalytics \u2014 ${label} (${argv.from} to ${argv.to})\n`));
129
+ lines.push(chalk.bold(' Human vs Agent\n'));
130
+ lines.push(
131
+ formatBarChart([
132
+ { label: 'Human Views', value: kpi.humanViews, color: 'cyan' },
133
+ { label: 'Agent Views', value: kpi.agentViews, color: 'magenta' },
134
+ { label: 'Human Visitors', value: kpi.humanVisitors, color: 'cyan' },
135
+ { label: 'Agent Visitors', value: kpi.agentVisitors, color: 'magenta' },
136
+ ])
137
+ );
138
+ lines.push(chalk.bold('\n\n Engagement\n'));
139
+ lines.push(
140
+ formatBarChart([
141
+ { label: 'Searches', value: kpi.humanSearches, color: 'yellow' },
142
+ { label: 'Feedback', value: kpi.humanFeedback, color: 'green' },
143
+ { label: 'Assistant (web)', value: kpi.humanAssistant, color: 'blue' },
144
+ { label: 'Assistant (API)', value: kpi.agentMcpSearches, color: 'magenta' },
145
+ ])
146
+ );
147
+ output('table', lines.join('\n'));
148
+ await terminate(0);
149
+ return;
150
+ }
151
+
152
+ const agentOnly = argv.agents && !argv.humans;
153
+ const humanOnly = argv.humans && !argv.agents;
154
+ const showHuman = !agentOnly;
155
+ const showAgent = !humanOnly;
156
+ const showTotal = showHuman && showAgent;
157
+
158
+ const lines: string[] = [];
159
+ const label = argv.subdomain ?? 'default';
160
+ lines.push(chalk.bold(`\nAnalytics \u2014 ${label} (${argv.from} to ${argv.to})\n`));
161
+
162
+ if (argv.page) {
163
+ lines.push(` Page: ${argv.page}\n`);
164
+ }
165
+
166
+ lines.push(chalk.bold(' Views'));
167
+ if (showHuman) lines.push(` Human: ${chalk.cyan(num(kpi.humanViews).padStart(8))}`);
168
+ if (showAgent)
169
+ lines.push(` Agent: ${chalk.magenta(num(kpi.agentViews).padStart(8))}`);
170
+ if (showTotal)
171
+ lines.push(` Total: ${num(kpi.humanViews + kpi.agentViews).padStart(8)}`);
172
+
173
+ lines.push(chalk.bold('\n Visitors'));
174
+ if (showHuman)
175
+ lines.push(` Human: ${chalk.cyan(num(kpi.humanVisitors).padStart(8))}`);
176
+ if (showAgent)
177
+ lines.push(` Agent: ${chalk.magenta(num(kpi.agentVisitors).padStart(8))}`);
178
+ if (showTotal)
179
+ lines.push(` Total: ${num(kpi.humanVisitors + kpi.agentVisitors).padStart(8)}`);
180
+
181
+ lines.push(`\n Searches: ${chalk.bold(num(kpi.humanSearches))}`);
182
+ lines.push(` Feedback: ${chalk.bold(num(kpi.humanFeedback))}`);
183
+ lines.push(
184
+ ` Assistant: ${chalk.bold(num(kpi.humanAssistant))} web, ${chalk.bold(num(kpi.agentMcpSearches))} API`
185
+ );
186
+
187
+ output(format, lines.join('\n'));
188
+ await terminate(0);
189
+ } catch (err) {
190
+ if (format === 'table') removeLastLog();
191
+ addLog(<ErrorLog message={err instanceof Error ? err.message : 'unknown error'} />);
192
+ await terminate(1);
193
+ }
194
+ }
195
+ )
196
+ .command(
197
+ 'search',
198
+ 'display search analytics',
199
+ (yargs) =>
200
+ withAll(yargs)
201
+ .option('query', { type: 'string', description: 'Filter by search query substring' })
202
+ .option('page', { type: 'string', description: 'Filter by top clicked page' }),
203
+ async (argv) => {
204
+ const format = resolveFormat(argv);
205
+ try {
206
+ if (format === 'table') addLog(<SpinnerLog message="Fetching search analytics..." />);
207
+ const data = await getSearches({ dateFrom: argv.from, dateTo: argv.to }, argv.subdomain);
208
+ if (format === 'table') removeLastLog();
209
+
210
+ let rows = data.searches;
211
+ if (argv.query) {
212
+ const q = argv.query.toLowerCase();
213
+ rows = rows.filter((r) => r.searchQuery.toLowerCase().includes(q));
214
+ }
215
+ if (argv.page) {
216
+ rows = rows.filter((r) => r.topClickedPage?.includes(argv.page!));
217
+ }
218
+
219
+ const headers = ['Query', 'Hits', 'CTR', 'Top Clicked Page', 'Last Searched'];
220
+ const tableRows = rows.map((r) => [
221
+ truncate(r.searchQuery, 30),
222
+ num(r.hits),
223
+ r.ctr.toFixed(1) + '%',
224
+ truncate(r.topClickedPage || '\u2014', 30),
225
+ r.lastSearchedAt.slice(0, 10),
226
+ ]);
227
+
228
+ if (format === 'json') {
229
+ output(format, JSON.stringify(data, null, 2));
230
+ } else if (format === 'graph') {
231
+ const label = argv.subdomain ?? 'default';
232
+ const lines: string[] = [];
233
+ lines.push(
234
+ chalk.bold(`\nSearch Queries \u2014 ${label} (${argv.from} to ${argv.to})\n`)
235
+ );
236
+ lines.push(
237
+ formatBarChart(
238
+ rows.slice(0, 20).map((r) => ({
239
+ label: truncate(r.searchQuery, 25),
240
+ value: r.hits,
241
+ color: 'yellow',
242
+ }))
243
+ )
244
+ );
245
+ output('table', lines.join('\n'));
246
+ } else if (format === 'plain') {
247
+ output(format, formatOutput(format, headers, tableRows, data));
248
+ } else {
249
+ const label = argv.subdomain ?? 'default';
250
+ const lines: string[] = [];
251
+ lines.push(
252
+ chalk.bold(`\nSearch Analytics \u2014 ${label} (${argv.from} to ${argv.to})`)
253
+ );
254
+ lines.push(`Total Searches: ${chalk.bold(num(data.totalSearches))}\n`);
255
+ lines.push(formatOutput(format, headers, tableRows, data));
256
+ output(format, lines.join('\n'));
257
+ }
258
+ await terminate(0);
259
+ } catch (err) {
260
+ if (format === 'table') removeLastLog();
261
+ addLog(<ErrorLog message={err instanceof Error ? err.message : 'unknown error'} />);
262
+ await terminate(1);
263
+ }
264
+ }
265
+ )
266
+ .command(
267
+ 'feedback',
268
+ 'display feedback analytics',
269
+ (yargs) =>
270
+ withAll(yargs)
271
+ .option('type', {
272
+ type: 'string',
273
+ choices: ['code', 'page'] as const,
274
+ description: 'Feedback type: code snippets or page-level aggregation',
275
+ })
276
+ .option('page', { type: 'string', description: 'Filter to a specific page path' }),
277
+ async (argv) => {
278
+ const format = resolveFormat(argv);
279
+ try {
280
+ if (format === 'table') addLog(<SpinnerLog message="Fetching feedback..." />);
281
+
282
+ if (argv.type === 'page') {
283
+ const data = await getFeedbackByPage(
284
+ { dateFrom: argv.from, dateTo: argv.to },
285
+ argv.subdomain
286
+ );
287
+ if (format === 'table') removeLastLog();
288
+
289
+ let rows = data.feedback;
290
+ if (argv.page) rows = rows.filter((r) => r.path.includes(argv.page!));
291
+
292
+ const headers = ['Path', 'Thumbs Up', 'Thumbs Down', 'Code', 'Total'];
293
+ const tableRows = rows.map((r) => [
294
+ truncate(r.path, 40),
295
+ num(r.thumbsUp),
296
+ num(r.thumbsDown),
297
+ num(r.code),
298
+ num(r.total),
299
+ ]);
300
+
301
+ if (format === 'json') {
302
+ output(format, JSON.stringify(data, null, 2));
303
+ } else if (format === 'graph') {
304
+ const label = argv.subdomain ?? 'default';
305
+ const lines: string[] = [];
306
+ lines.push(
307
+ chalk.bold(`\nFeedback by Page \u2014 ${label} (${argv.from} to ${argv.to})\n`)
308
+ );
309
+ lines.push(
310
+ formatBarChart(
311
+ rows.slice(0, 20).map((r) => ({
312
+ label: truncate(r.path, 25),
313
+ value: r.total,
314
+ color: 'green',
315
+ }))
316
+ )
317
+ );
318
+ output('table', lines.join('\n'));
319
+ } else {
320
+ const label = argv.subdomain ?? 'default';
321
+ const lines: string[] = [];
322
+ if (format === 'table')
323
+ lines.push(chalk.bold(`\nFeedback \u2014 ${label} (${argv.from} to ${argv.to})\n`));
324
+ lines.push(formatOutput(format, headers, tableRows, data));
325
+ if (format === 'table' && data.hasMore)
326
+ lines.push(chalk.dim('\n (more results available)'));
327
+ output(format, lines.join('\n'));
328
+ }
329
+ } else {
330
+ const source = argv.type === 'code' ? 'code' : undefined;
331
+ const data = await getFeedback(
332
+ { dateFrom: argv.from, dateTo: argv.to, source },
333
+ argv.subdomain
334
+ );
335
+ if (format === 'table') removeLastLog();
336
+
337
+ let rows = data.feedback;
338
+ if (argv.page) rows = rows.filter((r) => r.path.includes(argv.page!));
339
+
340
+ const headers = ['ID', 'Path', 'Status', 'Source', 'Comment', 'Created'];
341
+ const tableRows = rows.map((r) => [
342
+ r.id.slice(0, 8),
343
+ truncate(r.path, 30),
344
+ r.status,
345
+ r.source,
346
+ truncate(r.comment ?? '\u2014', 30),
347
+ r.createdAt?.slice(0, 10) ?? '\u2014',
348
+ ]);
349
+
350
+ if (format === 'json') {
351
+ output(format, JSON.stringify(data, null, 2));
352
+ } else {
353
+ const label = argv.subdomain ?? 'default';
354
+ const lines: string[] = [];
355
+ if (format === 'table')
356
+ lines.push(chalk.bold(`\nFeedback \u2014 ${label} (${argv.from} to ${argv.to})\n`));
357
+ lines.push(formatOutput(format, headers, tableRows, data));
358
+ if (format === 'table' && data.hasMore)
359
+ lines.push(chalk.dim('\n (more results available)'));
360
+ output(format, lines.join('\n'));
361
+ }
362
+ }
363
+ await terminate(0);
364
+ } catch (err) {
365
+ if (format === 'table') removeLastLog();
366
+ addLog(<ErrorLog message={err instanceof Error ? err.message : 'unknown error'} />);
367
+ await terminate(1);
368
+ }
369
+ }
370
+ )
371
+ .command('conversation', 'view assistant conversation analytics', (yargs) =>
372
+ yargs
373
+ .command(
374
+ 'list',
375
+ 'list assistant conversations',
376
+ (yargs) =>
377
+ withAll(yargs).option('page', {
378
+ type: 'string',
379
+ description: 'Filter conversations mentioning this page in sources',
380
+ }),
381
+ async (argv) => {
382
+ const format = resolveFormat(argv);
383
+ try {
384
+ if (format === 'table') addLog(<SpinnerLog message="Fetching conversations..." />);
385
+ const data = await getConversations(
386
+ { dateFrom: argv.from, dateTo: argv.to },
387
+ argv.subdomain
388
+ );
389
+ if (format === 'table') removeLastLog();
390
+
391
+ let conversations = data.conversations;
392
+ if (argv.page) {
393
+ conversations = conversations.filter((c) =>
394
+ c.sources.some((s) => s.url.includes(argv.page!))
395
+ );
396
+ }
397
+
398
+ const headers = ['ID', 'Timestamp', 'Query', 'Category'];
399
+ const tableRows = conversations.map((c) => [
400
+ c.id,
401
+ c.timestamp.slice(0, 19).replace('T', ' '),
402
+ truncate(c.query, 40),
403
+ c.queryCategory || '\u2014',
404
+ ]);
405
+
406
+ if (format === 'json') {
407
+ output(format, JSON.stringify(data, null, 2));
408
+ } else {
409
+ const label = argv.subdomain ?? 'default';
410
+ const lines: string[] = [];
411
+ if (format === 'table')
412
+ lines.push(
413
+ chalk.bold(`\nConversations \u2014 ${label} (${argv.from} to ${argv.to})\n`)
414
+ );
415
+ lines.push(formatOutput(format, headers, tableRows, data));
416
+ if (format === 'table' && data.hasMore)
417
+ lines.push(chalk.dim('\n (more results available)'));
418
+ output(format, lines.join('\n'));
419
+ }
420
+ await terminate(0);
421
+ } catch (err) {
422
+ if (format === 'table') removeLastLog();
423
+ addLog(<ErrorLog message={err instanceof Error ? err.message : 'unknown error'} />);
424
+ await terminate(1);
425
+ }
426
+ }
427
+ )
428
+ .command(
429
+ 'view <id>',
430
+ 'view a single conversation',
431
+ (yargs) =>
432
+ withFormat(withSubdomain(yargs)).positional('id', {
433
+ type: 'string',
434
+ demandOption: true,
435
+ description: 'Conversation ID',
436
+ }),
437
+ async (argv) => {
438
+ const format = resolveFormat(argv);
439
+ try {
440
+ if (format === 'table') addLog(<SpinnerLog message="Fetching conversation..." />);
441
+
442
+ const today = new Date().toISOString().slice(0, 10);
443
+ let conversation: Conversation | undefined;
444
+ let cursor: string | undefined;
445
+
446
+ for (let i = 0; i < 10 && !conversation; i++) {
447
+ const data = await getConversations(
448
+ {
449
+ dateFrom: '2020-01-01',
450
+ dateTo: today,
451
+ limit: 100,
452
+ cursor,
453
+ },
454
+ argv.subdomain
455
+ );
456
+ conversation = data.conversations.find((c) => c.id === argv.id);
457
+ if (!data.nextCursor) break;
458
+ cursor = data.nextCursor;
459
+ }
460
+
461
+ if (format === 'table') removeLastLog();
462
+
463
+ if (!conversation) {
464
+ addLog(<ErrorLog message={`Conversation ${argv.id} not found`} />);
465
+ await terminate(1);
466
+ return;
467
+ }
468
+
469
+ if (format === 'json' || format === 'plain') {
470
+ output(format, JSON.stringify(conversation, null, 2));
471
+ } else {
472
+ const lines: string[] = [];
473
+ lines.push(chalk.bold(`\nConversation ${conversation.id}\n`));
474
+ lines.push(` Timestamp: ${conversation.timestamp}`);
475
+ lines.push(` Category: ${conversation.queryCategory || '\u2014'}`);
476
+ lines.push(chalk.bold('\n Query:'));
477
+ lines.push(` ${conversation.query}`);
478
+ lines.push(chalk.bold('\n Response:'));
479
+ for (const line of conversation.response.split('\n')) {
480
+ lines.push(` ${line}`);
481
+ }
482
+ if (conversation.sources.length > 0) {
483
+ lines.push(chalk.bold('\n Sources:'));
484
+ for (const src of conversation.sources) {
485
+ lines.push(` ${src.title} \u2014 ${chalk.dim(src.url)}`);
486
+ }
487
+ }
488
+ output(format, lines.join('\n'));
489
+ }
490
+ await terminate(0);
491
+ } catch (err) {
492
+ if (format === 'table') removeLastLog();
493
+ addLog(<ErrorLog message={err instanceof Error ? err.message : 'unknown error'} />);
494
+ await terminate(1);
495
+ }
496
+ }
497
+ )
498
+ .command('buckets', 'view conversation category buckets', (yargs) =>
499
+ yargs
500
+ .command(
501
+ 'list',
502
+ 'list conversation buckets',
503
+ (yargs) => withAll(yargs),
504
+ async (argv) => {
505
+ const format = resolveFormat(argv);
506
+ try {
507
+ if (format === 'table')
508
+ addLog(<SpinnerLog message="Fetching conversation buckets..." />);
509
+ const data = await getBuckets(
510
+ { dateFrom: argv.from, dateTo: argv.to },
511
+ argv.subdomain
512
+ );
513
+ if (format === 'table') removeLastLog();
514
+
515
+ const headers = ['ID', 'Label', 'Count', 'Last Asked'];
516
+ const tableRows = data.data.map((b) => [
517
+ b.id.slice(0, 12),
518
+ truncate(b.questionSummary, 50),
519
+ num(b.size),
520
+ b.lastAsked ? b.lastAsked.slice(0, 10) : '\u2014',
521
+ ]);
522
+
523
+ if (format === 'json') {
524
+ output(format, JSON.stringify(data, null, 2));
525
+ } else if (format === 'graph') {
526
+ const label = argv.subdomain ?? 'default';
527
+ const lines: string[] = [];
528
+ lines.push(
529
+ chalk.bold(
530
+ `\nConversation Buckets \u2014 ${label} (${argv.from} to ${argv.to})\n`
531
+ )
532
+ );
533
+ lines.push(
534
+ formatBarChart(
535
+ data.data.slice(0, 20).map((b) => ({
536
+ label: truncate(b.questionSummary, 30),
537
+ value: b.size,
538
+ color: 'blue',
539
+ }))
540
+ )
541
+ );
542
+ output('table', lines.join('\n'));
543
+ } else {
544
+ const label = argv.subdomain ?? 'default';
545
+ const lines: string[] = [];
546
+ if (format === 'table')
547
+ lines.push(
548
+ chalk.bold(
549
+ `\nConversation Buckets \u2014 ${label} (${argv.from} to ${argv.to})\n`
550
+ )
551
+ );
552
+ lines.push(formatOutput(format, headers, tableRows, data));
553
+ if (format === 'table')
554
+ lines.push(chalk.dim(`\n Total: ${data.pagination.total}`));
555
+ output(format, lines.join('\n'));
556
+ }
557
+ await terminate(0);
558
+ } catch (err) {
559
+ if (format === 'table') removeLastLog();
560
+ addLog(
561
+ <ErrorLog message={err instanceof Error ? err.message : 'unknown error'} />
562
+ );
563
+ await terminate(1);
564
+ }
565
+ }
566
+ )
567
+ .command(
568
+ 'view <id>',
569
+ 'view conversations in a bucket',
570
+ (yargs) =>
571
+ withAll(yargs).positional('id', {
572
+ type: 'string',
573
+ demandOption: true,
574
+ description: 'Bucket ID',
575
+ }),
576
+ async (argv) => {
577
+ const format = resolveFormat(argv);
578
+ try {
579
+ if (format === 'table')
580
+ addLog(<SpinnerLog message="Fetching bucket conversations..." />);
581
+ const data = await getBucketThreads(
582
+ argv.id,
583
+ { dateFrom: argv.from, dateTo: argv.to },
584
+ argv.subdomain
585
+ );
586
+ if (format === 'table') removeLastLog();
587
+
588
+ if (format === 'json') {
589
+ output(format, JSON.stringify(data, null, 2));
590
+ } else {
591
+ const headers = ['Thread ID', 'Query', 'Length', 'Feedback', 'Created'];
592
+ const tableRows = data.data.map((t) => [
593
+ t.id.slice(0, 12),
594
+ truncate(t.firstUserMessage || '\u2014', 40),
595
+ num(t.length),
596
+ `+${t.feedback.up} -${t.feedback.down}`,
597
+ t.createdAt.slice(0, 10),
598
+ ]);
599
+ const lines: string[] = [];
600
+ if (format === 'table') lines.push(chalk.bold(`\nBucket ${argv.id}\n`));
601
+ lines.push(formatOutput(format, headers, tableRows, data));
602
+ if (format === 'table' && data.pagination.hasMore)
603
+ lines.push(chalk.dim('\n (more results available)'));
604
+ output(format, lines.join('\n'));
605
+ }
606
+ await terminate(0);
607
+ } catch (err) {
608
+ if (format === 'table') removeLastLog();
609
+ addLog(
610
+ <ErrorLog message={err instanceof Error ? err.message : 'unknown error'} />
611
+ );
612
+ await terminate(1);
613
+ }
614
+ }
615
+ )
616
+ .demandCommand(1, 'specify a subcommand: list or view')
617
+ )
618
+ .demandCommand(1, 'specify a subcommand: list, view, or buckets')
619
+ )
620
+ .demandCommand(1, 'specify a subcommand: stats, search, feedback, or conversation');