recappi 0.1.0 → 0.1.2

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 CHANGED
@@ -20,7 +20,7 @@ function Header({ active }) {
20
20
  ] }),
21
21
  /* @__PURE__ */ jsx(Tab, { num: "1", label: "Overview", active: active === "overview", enabled: true }),
22
22
  /* @__PURE__ */ jsx(Tab, { num: "2", label: "Jobs", active: active === "jobs", enabled: true }),
23
- /* @__PURE__ */ jsx(Tab, { num: "3", label: "Recordings", active: active === "recordings", enabled: false })
23
+ /* @__PURE__ */ jsx(Tab, { num: "3", label: "Recordings", active: active === "recordings", enabled: true })
24
24
  ] });
25
25
  }
26
26
  function Tab({
@@ -151,6 +151,36 @@ function resolveJobLinks(item, origin) {
151
151
  }
152
152
  return {};
153
153
  }
154
+ function resolveRecordingLinks(recordingId, origin) {
155
+ if (!recordingId) return {};
156
+ return { webUrl: `${origin}/recordings/${recordingId}` };
157
+ }
158
+ function recordingStatusStyle(status) {
159
+ switch (status) {
160
+ case "ready":
161
+ return { label: "Ready", color: "green", glyph: "\u2713" };
162
+ case "uploading":
163
+ return { label: "Uploading", color: "cyan", glyph: "\u2191" };
164
+ case "failed":
165
+ return { label: "Failed", color: "red", glyph: "\u2717" };
166
+ case "aborted":
167
+ return { label: "Aborted", color: "gray", glyph: "\u2022" };
168
+ default:
169
+ return { label: status, color: "white", glyph: "\u2022" };
170
+ }
171
+ }
172
+ function formatBytes2(bytes) {
173
+ if (bytes == null || !Number.isFinite(bytes) || bytes < 0) return "";
174
+ const units = ["B", "KB", "MB", "GB", "TB"];
175
+ let value = bytes;
176
+ let unit = 0;
177
+ while (value >= 1024 && unit < units.length - 1) {
178
+ value /= 1024;
179
+ unit += 1;
180
+ }
181
+ const rounded = value < 10 && unit > 0 ? value.toFixed(1) : String(Math.round(value));
182
+ return `${rounded}${units[unit]}`;
183
+ }
154
184
  var SPINNER_FRAMES;
155
185
  var init_format = __esm({
156
186
  "src/tui/format.ts"() {
@@ -212,83 +242,149 @@ var init_JobsView = __esm({
212
242
  }
213
243
  });
214
244
 
215
- // src/tui/OverviewView.tsx
245
+ // src/tui/RecordingRow.tsx
216
246
  import { Box as Box4, Text as Text4 } from "ink";
217
247
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
218
- function overviewActiveItems(items) {
219
- return items.filter((item) => item.status === "running" || item.status === "queued");
248
+ function recordingTitle2(item) {
249
+ return item.title || item.summaryTitle || item.recordingId;
250
+ }
251
+ function RecordingRow({
252
+ item,
253
+ selected,
254
+ nowMs
255
+ }) {
256
+ const style = recordingStatusStyle(item.status);
257
+ const detail = [
258
+ item.durationMs ? formatClockMs(item.durationMs) : void 0,
259
+ formatAge(item.createdAt, nowMs) || void 0,
260
+ item.activeTranscriptId ? "transcript ready" : void 0
261
+ ].filter(Boolean).join(" \xB7 ");
262
+ return /* @__PURE__ */ jsxs3(Box4, { children: [
263
+ /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: selected ? "\u25B8 " : " " }),
264
+ /* @__PURE__ */ jsx4(Text4, { color: style.color, children: `${style.glyph} ${padCell(style.label, 10)}` }),
265
+ /* @__PURE__ */ jsx4(Text4, { bold: selected, children: padCell(recordingTitle2(item), 26) }),
266
+ /* @__PURE__ */ jsx4(Text4, { dimColor: !selected, children: detail })
267
+ ] });
268
+ }
269
+ var init_RecordingRow = __esm({
270
+ "src/tui/RecordingRow.tsx"() {
271
+ "use strict";
272
+ init_format();
273
+ }
274
+ });
275
+
276
+ // src/tui/OverviewView.tsx
277
+ import { Box as Box5, Text as Text5 } from "ink";
278
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
279
+ function overviewRecentRecordings(recordings) {
280
+ return recordings.slice(0, RECENT_LIMIT);
220
281
  }
221
282
  function OverviewView({
222
- items,
283
+ recordings,
284
+ jobs,
285
+ stats,
223
286
  selectedIndex,
224
287
  spinnerFrame,
225
288
  nowMs
226
289
  }) {
227
- const counts = countJobs(items);
228
- const active = overviewActiveItems(items);
229
- const recent = items.filter((item) => item.status === "succeeded" || item.status === "failed").slice(0, 5);
230
- return /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "column", children: [
231
- /* @__PURE__ */ jsxs3(Text4, { children: [
232
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Jobs " }),
233
- /* @__PURE__ */ jsxs3(Text4, { color: "cyan", children: [
234
- counts.running,
235
- " running"
290
+ const recent = overviewRecentRecordings(recordings);
291
+ const activeJobs = jobs.filter((j) => j.status === "running" || j.status === "queued");
292
+ const jobCounts = countJobs(jobs);
293
+ const recTotal = stats?.recordings.total ?? recordings.length;
294
+ const recReady = stats?.recordings.ready;
295
+ const transcribed = stats?.recordings.totalDurationMs;
296
+ return /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
297
+ /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", children: [
298
+ /* @__PURE__ */ jsxs4(Text5, { children: [
299
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Recordings " }),
300
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: recTotal }),
301
+ recReady != null ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` \xB7 ${recReady} ready` }) : null,
302
+ transcribed != null ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` \xB7 ${formatClockMs(transcribed)} transcribed` }) : null
236
303
  ] }),
237
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " \xB7 " }),
238
- /* @__PURE__ */ jsxs3(Text4, { color: "yellow", children: [
239
- counts.queued,
240
- " queued"
241
- ] }),
242
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " \xB7 " }),
243
- /* @__PURE__ */ jsxs3(Text4, { color: "green", children: [
244
- counts.succeeded,
245
- " done"
246
- ] }),
247
- /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " \xB7 " }),
248
- /* @__PURE__ */ jsxs3(Text4, { color: "red", children: [
249
- counts.failed,
250
- " failed"
304
+ /* @__PURE__ */ jsxs4(Text5, { children: [
305
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Jobs " }),
306
+ /* @__PURE__ */ jsxs4(Text5, { color: "cyan", children: [
307
+ stats?.jobs.running ?? jobCounts.running,
308
+ " running"
309
+ ] }),
310
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " \xB7 " }),
311
+ /* @__PURE__ */ jsxs4(Text5, { color: "yellow", children: [
312
+ stats?.jobs.queued ?? jobCounts.queued,
313
+ " queued"
314
+ ] }),
315
+ /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " \xB7 " }),
316
+ /* @__PURE__ */ jsxs4(Text5, { color: "green", children: [
317
+ stats?.jobs.succeeded ?? jobCounts.succeeded,
318
+ " done"
319
+ ] })
251
320
  ] })
252
321
  ] }),
253
- /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "column", children: [
254
- /* @__PURE__ */ jsx4(Text4, { bold: true, children: "Active" }),
255
- active.length === 0 ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: " Nothing transcribing right now." }) : active.map((item, index) => /* @__PURE__ */ jsx4(
256
- JobRow,
322
+ /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
323
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Recent recordings" }),
324
+ recent.length === 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " No recordings yet \u2014 run: recappi upload <file>" }) : recent.map((item, index) => /* @__PURE__ */ jsx5(
325
+ RecordingRow,
257
326
  {
258
327
  item,
259
328
  selected: index === selectedIndex,
260
- spinnerFrame
329
+ nowMs
261
330
  },
262
- item.jobId
331
+ item.recordingId
263
332
  ))
264
333
  ] }),
265
- recent.length > 0 ? /* @__PURE__ */ jsxs3(Box4, { marginTop: 1, flexDirection: "column", children: [
266
- /* @__PURE__ */ jsx4(Text4, { bold: true, children: "Recent" }),
267
- recent.map((item) => {
268
- const style = statusStyle(item.status);
269
- const title = item.recording?.title ?? item.recordingId;
270
- const age = formatAge(item.finishedAt ?? item.enqueuedAt, nowMs);
271
- return /* @__PURE__ */ jsxs3(Box4, { children: [
272
- /* @__PURE__ */ jsx4(Text4, { children: " " }),
273
- /* @__PURE__ */ jsx4(Text4, { color: style.color, children: `${statusGlyph(item.status, 0)} ` }),
274
- /* @__PURE__ */ jsx4(Text4, { children: title }),
275
- age ? /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: ` ${age}` }) : null
276
- ] }, item.jobId);
334
+ activeJobs.length > 0 ? /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
335
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Transcribing now" }),
336
+ activeJobs.slice(0, 3).map((job) => {
337
+ const style = statusStyle(job.status);
338
+ return /* @__PURE__ */ jsxs4(Box5, { children: [
339
+ /* @__PURE__ */ jsx5(Text5, { children: " " }),
340
+ /* @__PURE__ */ jsx5(Text5, { color: style.color, children: `${statusGlyph(job.status, spinnerFrame)} ` }),
341
+ /* @__PURE__ */ jsx5(Text5, { children: job.recording?.title ?? job.recordingId })
342
+ ] }, job.jobId);
277
343
  })
278
344
  ] }) : null
279
345
  ] });
280
346
  }
347
+ var RECENT_LIMIT;
281
348
  var init_OverviewView = __esm({
282
349
  "src/tui/OverviewView.tsx"() {
283
350
  "use strict";
284
- init_JobRow();
351
+ init_RecordingRow();
285
352
  init_format();
353
+ RECENT_LIMIT = 6;
354
+ }
355
+ });
356
+
357
+ // src/tui/RecordingsView.tsx
358
+ import { Box as Box6, Text as Text6 } from "ink";
359
+ import { jsx as jsx6 } from "react/jsx-runtime";
360
+ function RecordingsView({
361
+ items,
362
+ selectedIndex,
363
+ nowMs
364
+ }) {
365
+ if (items.length === 0) {
366
+ return /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No recordings yet \u2014 run: recappi upload <file>" }) });
367
+ }
368
+ return /* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: items.map((item, index) => /* @__PURE__ */ jsx6(
369
+ RecordingRow,
370
+ {
371
+ item,
372
+ selected: index === selectedIndex,
373
+ nowMs
374
+ },
375
+ item.recordingId
376
+ )) });
377
+ }
378
+ var init_RecordingsView = __esm({
379
+ "src/tui/RecordingsView.tsx"() {
380
+ "use strict";
381
+ init_RecordingRow();
286
382
  }
287
383
  });
288
384
 
289
385
  // src/tui/JobDetailView.tsx
290
- import { Box as Box5, Text as Text5 } from "ink";
291
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
386
+ import { Box as Box7, Text as Text7 } from "ink";
387
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
292
388
  function JobDetailView({
293
389
  item,
294
390
  origin,
@@ -298,13 +394,13 @@ function JobDetailView({
298
394
  const style = statusStyle(item.status);
299
395
  const links = resolveJobLinks(item, origin);
300
396
  const title = item.recording?.title ?? item.recordingId;
301
- return /* @__PURE__ */ jsxs4(Box5, { flexDirection: "column", paddingX: 1, children: [
302
- /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
397
+ return /* @__PURE__ */ jsxs5(Box7, { flexDirection: "column", paddingX: 1, children: [
398
+ /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
303
399
  "\u2039 Jobs / ",
304
400
  title
305
401
  ] }),
306
- /* @__PURE__ */ jsxs4(
307
- Box5,
402
+ /* @__PURE__ */ jsxs5(
403
+ Box7,
308
404
  {
309
405
  marginTop: 1,
310
406
  borderStyle: "round",
@@ -312,17 +408,17 @@ function JobDetailView({
312
408
  paddingX: 1,
313
409
  flexDirection: "column",
314
410
  children: [
315
- /* @__PURE__ */ jsxs4(Text5, { color: style.color, bold: true, children: [
411
+ /* @__PURE__ */ jsxs5(Text7, { color: style.color, bold: true, children: [
316
412
  style.label,
317
- item.provider ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` ${item.provider}` }) : null
413
+ item.provider ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: ` ${item.provider}` }) : null
318
414
  ] }),
319
- /* @__PURE__ */ jsx5(StatusLine, { item, spinnerFrame, nowMs })
415
+ /* @__PURE__ */ jsx7(StatusLine, { item, spinnerFrame, nowMs })
320
416
  ]
321
417
  }
322
418
  ),
323
- /* @__PURE__ */ jsxs4(Box5, { marginTop: 1, flexDirection: "column", children: [
324
- /* @__PURE__ */ jsx5(Text5, { bold: true, children: "Timeline" }),
325
- /* @__PURE__ */ jsx5(
419
+ /* @__PURE__ */ jsxs5(Box7, { marginTop: 1, flexDirection: "column", children: [
420
+ /* @__PURE__ */ jsx7(Text7, { bold: true, children: "Timeline" }),
421
+ /* @__PURE__ */ jsx7(
326
422
  TimelineRow,
327
423
  {
328
424
  label: "Enqueued",
@@ -331,7 +427,7 @@ function JobDetailView({
331
427
  nowMs
332
428
  }
333
429
  ),
334
- /* @__PURE__ */ jsx5(
430
+ /* @__PURE__ */ jsx7(
335
431
  TimelineRow,
336
432
  {
337
433
  label: "Started",
@@ -340,7 +436,7 @@ function JobDetailView({
340
436
  nowMs
341
437
  }
342
438
  ),
343
- /* @__PURE__ */ jsx5(
439
+ /* @__PURE__ */ jsx7(
344
440
  TimelineRow,
345
441
  {
346
442
  label: item.status === "failed" ? "Failed" : item.status === "running" ? "Transcribing" : "Finished",
@@ -352,21 +448,21 @@ function JobDetailView({
352
448
  }
353
449
  )
354
450
  ] }),
355
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs4(Text5, { children: [
356
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "Recording " }),
451
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text7, { children: [
452
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Recording " }),
357
453
  title,
358
- item.recording?.durationMs ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` \xB7 ${formatClockMs(item.recording.durationMs)}` }) : null
454
+ item.recording?.durationMs ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: ` \xB7 ${formatClockMs(item.recording.durationMs)}` }) : null
359
455
  ] }) }),
360
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs4(Text5, { children: [
361
- /* @__PURE__ */ jsx5(Text5, { color: links.webUrl ? "cyan" : "gray", children: "o open" }),
362
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " \xB7 " }),
363
- /* @__PURE__ */ jsx5(Text5, { color: links.webUrl ? "cyan" : "gray", children: "w web" }),
364
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " \xB7 " }),
365
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "m mac app (soon)" }),
366
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " \xB7 " }),
367
- /* @__PURE__ */ jsx5(Text5, { color: links.webUrl ? "cyan" : "gray", children: "c copy" })
456
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs5(Text7, { children: [
457
+ /* @__PURE__ */ jsx7(Text7, { color: links.webUrl ? "cyan" : "gray", children: "o open" }),
458
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " \xB7 " }),
459
+ /* @__PURE__ */ jsx7(Text7, { color: links.webUrl ? "cyan" : "gray", children: "w web" }),
460
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " \xB7 " }),
461
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "m mac app (soon)" }),
462
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " \xB7 " }),
463
+ /* @__PURE__ */ jsx7(Text7, { color: links.webUrl ? "cyan" : "gray", children: "c copy" })
368
464
  ] }) }),
369
- /* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsxs4(Text5, { dimColor: true, children: [
465
+ /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
370
466
  "esc back \xB7 t transcript",
371
467
  item.transcriptId ? "" : " (when ready)",
372
468
  " \xB7 q quit"
@@ -383,24 +479,24 @@ function StatusLine({
383
479
  const elapsed = item.startedAt ? ` \xB7 ${formatClockMs(nowMs - item.startedAt)} elapsed` : "";
384
480
  if (fraction != null) {
385
481
  const pct = Math.round(fraction * 100);
386
- return /* @__PURE__ */ jsxs4(Text5, { children: [
482
+ return /* @__PURE__ */ jsxs5(Text7, { children: [
387
483
  `${progressBar(fraction)} ${pct}% ${formatClockMs(item.processedDurationMs)} / ${formatClockMs(
388
484
  item.recording?.durationMs
389
485
  )}`,
390
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: elapsed })
486
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: elapsed })
391
487
  ] });
392
488
  }
393
489
  const spinner = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"][spinnerFrame % 10];
394
- return /* @__PURE__ */ jsxs4(Text5, { children: [
490
+ return /* @__PURE__ */ jsxs5(Text7, { children: [
395
491
  `${spinner} transcribing\u2026`,
396
- /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: elapsed })
492
+ /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: elapsed })
397
493
  ] });
398
494
  }
399
495
  if (item.status === "succeeded")
400
- return /* @__PURE__ */ jsx5(Text5, { children: item.transcriptId ? "transcript ready" : "done" });
401
- if (item.status === "queued") return /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "waiting to start\u2026" });
402
- if (item.status === "failed") return /* @__PURE__ */ jsx5(Text5, { color: "red", children: "transcription failed" });
403
- return /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: item.status });
496
+ return /* @__PURE__ */ jsx7(Text7, { children: item.transcriptId ? "transcript ready" : "done" });
497
+ if (item.status === "queued") return /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "waiting to start\u2026" });
498
+ if (item.status === "failed") return /* @__PURE__ */ jsx7(Text7, { color: "red", children: "transcription failed" });
499
+ return /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: item.status });
404
500
  }
405
501
  function TimelineRow({
406
502
  label,
@@ -413,10 +509,10 @@ function TimelineRow({
413
509
  const glyph = failed ? "\u2717" : done ? "\u2713" : running ? "\u280B" : "\u25CB";
414
510
  const color = failed ? "red" : done ? "green" : running ? "cyan" : "gray";
415
511
  const age = at ? formatAge(at, nowMs) : running ? "now" : "";
416
- return /* @__PURE__ */ jsxs4(Box5, { children: [
417
- /* @__PURE__ */ jsx5(Text5, { color, children: ` ${glyph} ` }),
418
- /* @__PURE__ */ jsx5(Text5, { dimColor: !done && !running, children: label }),
419
- age ? /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: ` ${age}` }) : null
512
+ return /* @__PURE__ */ jsxs5(Box7, { children: [
513
+ /* @__PURE__ */ jsx7(Text7, { color, children: ` ${glyph} ` }),
514
+ /* @__PURE__ */ jsx7(Text7, { dimColor: !done && !running, children: label }),
515
+ age ? /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: ` ${age}` }) : null
420
516
  ] });
421
517
  }
422
518
  var init_JobDetailView = __esm({
@@ -426,46 +522,111 @@ var init_JobDetailView = __esm({
426
522
  }
427
523
  });
428
524
 
525
+ // src/tui/RecordingDetailView.tsx
526
+ import { Box as Box8, Text as Text8 } from "ink";
527
+ import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
528
+ function RecordingDetailView({
529
+ item,
530
+ nowMs
531
+ }) {
532
+ const style = recordingStatusStyle(item.status);
533
+ const links = resolveRecordingLinks(item.recordingId, item.origin);
534
+ const title = recordingTitle2(item);
535
+ const meta3 = [
536
+ item.durationMs ? formatClockMs(item.durationMs) : void 0,
537
+ formatBytes2(item.sizeBytes) || void 0,
538
+ item.contentType || void 0
539
+ ].filter(Boolean).join(" \xB7 ");
540
+ return /* @__PURE__ */ jsxs6(Box8, { flexDirection: "column", paddingX: 1, children: [
541
+ /* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
542
+ "\u2039 Recordings / ",
543
+ title
544
+ ] }),
545
+ /* @__PURE__ */ jsxs6(
546
+ Box8,
547
+ {
548
+ marginTop: 1,
549
+ borderStyle: "round",
550
+ borderColor: style.color,
551
+ paddingX: 1,
552
+ flexDirection: "column",
553
+ children: [
554
+ /* @__PURE__ */ jsxs6(Text8, { color: style.color, bold: true, children: [
555
+ style.label,
556
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: ` created ${formatAge(item.createdAt, nowMs) || "\u2014"}` })
557
+ ] }),
558
+ meta3 ? /* @__PURE__ */ jsx8(Text8, { children: meta3 }) : null
559
+ ]
560
+ }
561
+ ),
562
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text8, { children: [
563
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "Transcript " }),
564
+ item.activeTranscriptId ? /* @__PURE__ */ jsx8(Text8, { color: "green", children: "ready \u2014 press t to view" }) : /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "not available yet" })
565
+ ] }) }),
566
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: /* @__PURE__ */ jsxs6(Text8, { children: [
567
+ /* @__PURE__ */ jsx8(Text8, { color: links.webUrl ? "cyan" : "gray", children: "o open" }),
568
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " \xB7 " }),
569
+ /* @__PURE__ */ jsx8(Text8, { color: links.webUrl ? "cyan" : "gray", children: "w web" }),
570
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " \xB7 " }),
571
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: "m mac app (soon)" }),
572
+ /* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " \xB7 " }),
573
+ /* @__PURE__ */ jsx8(Text8, { color: links.webUrl ? "cyan" : "gray", children: "c copy" })
574
+ ] }) }),
575
+ /* @__PURE__ */ jsx8(Box8, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
576
+ "esc back \xB7 ",
577
+ item.activeTranscriptId ? "t transcript \xB7 " : "",
578
+ "q quit"
579
+ ] }) })
580
+ ] });
581
+ }
582
+ var init_RecordingDetailView = __esm({
583
+ "src/tui/RecordingDetailView.tsx"() {
584
+ "use strict";
585
+ init_format();
586
+ init_RecordingRow();
587
+ }
588
+ });
589
+
429
590
  // src/tui/TranscriptView.tsx
430
- import { Box as Box6, Text as Text6 } from "ink";
431
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
591
+ import { Box as Box9, Text as Text9 } from "ink";
592
+ import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
432
593
  function TranscriptView({ loading, data, error: error51 }) {
433
594
  if (loading) {
434
- return /* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "Loading transcript\u2026" }) });
595
+ return /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "Loading transcript\u2026" }) });
435
596
  }
436
597
  if (error51) {
437
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingX: 1, children: [
438
- /* @__PURE__ */ jsxs5(Text6, { color: "red", children: [
598
+ return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", paddingX: 1, children: [
599
+ /* @__PURE__ */ jsxs7(Text9, { color: "red", children: [
439
600
  "! ",
440
601
  error51
441
602
  ] }),
442
- /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "q / esc / \u2190 back" })
603
+ /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "q / esc / \u2190 back" })
443
604
  ] });
444
605
  }
445
606
  if (!data) {
446
- return /* @__PURE__ */ jsx6(Box6, { paddingX: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "No transcript." }) });
607
+ return /* @__PURE__ */ jsx9(Box9, { paddingX: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "No transcript." }) });
447
608
  }
448
609
  const summary = data.summary;
449
610
  const showSummary = summary?.status === "succeeded";
450
- return /* @__PURE__ */ jsxs5(Box6, { flexDirection: "column", paddingX: 1, children: [
451
- /* @__PURE__ */ jsx6(Text6, { bold: true, color: "magenta", children: summary?.title ?? "Transcript" }),
452
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, flexDirection: "column", children: data.segments.length === 0 ? /* @__PURE__ */ jsx6(Text6, { children: data.text }) : data.segments.slice(0, 200).map((segment, index) => /* @__PURE__ */ jsxs5(Text6, { children: [
453
- /* @__PURE__ */ jsxs5(Text6, { dimColor: true, children: [
611
+ return /* @__PURE__ */ jsxs7(Box9, { flexDirection: "column", paddingX: 1, children: [
612
+ /* @__PURE__ */ jsx9(Text9, { bold: true, color: "magenta", children: summary?.title ?? "Transcript" }),
613
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, flexDirection: "column", children: data.segments.length === 0 ? /* @__PURE__ */ jsx9(Text9, { children: data.text }) : data.segments.slice(0, 200).map((segment, index) => /* @__PURE__ */ jsxs7(Text9, { children: [
614
+ /* @__PURE__ */ jsxs7(Text9, { dimColor: true, children: [
454
615
  "[",
455
616
  formatClockMs(segment.startMs),
456
617
  "] "
457
618
  ] }),
458
- segment.speaker ? /* @__PURE__ */ jsxs5(Text6, { color: "cyan", children: [
619
+ segment.speaker ? /* @__PURE__ */ jsxs7(Text9, { color: "cyan", children: [
459
620
  segment.speaker,
460
621
  ": "
461
622
  ] }) : null,
462
623
  segment.text
463
624
  ] }, index)) }),
464
- showSummary && summary?.tldr ? /* @__PURE__ */ jsxs5(Box6, { marginTop: 1, flexDirection: "column", children: [
465
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Summary" }),
466
- /* @__PURE__ */ jsx6(Text6, { children: summary.tldr })
625
+ showSummary && summary?.tldr ? /* @__PURE__ */ jsxs7(Box9, { marginTop: 1, flexDirection: "column", children: [
626
+ /* @__PURE__ */ jsx9(Text9, { bold: true, children: "Summary" }),
627
+ /* @__PURE__ */ jsx9(Text9, { children: summary.tldr })
467
628
  ] }) : null,
468
- /* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "q / esc / \u2190 back" }) })
629
+ /* @__PURE__ */ jsx9(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "q / esc / \u2190 back" }) })
469
630
  ] });
470
631
  }
471
632
  var init_TranscriptView = __esm({
@@ -477,11 +638,13 @@ var init_TranscriptView = __esm({
477
638
 
478
639
  // src/tui/AppShell.tsx
479
640
  import { useCallback, useEffect, useState } from "react";
480
- import { Box as Box7, Text as Text7, useApp, useInput } from "ink";
481
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
641
+ import { Box as Box10, Text as Text10, useApp, useInput } from "ink";
642
+ import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
482
643
  function AppShell({
483
644
  fetchJobs,
484
645
  fetchTranscript,
646
+ fetchRecordings,
647
+ fetchDashboardStats,
485
648
  initialView = "overview",
486
649
  openUrl: openUrl2,
487
650
  copyText: copyText2,
@@ -490,41 +653,48 @@ function AppShell({
490
653
  spinnerMs = 80
491
654
  }) {
492
655
  const { exit } = useApp();
493
- const [items, setItems] = useState([]);
656
+ const [jobs, setJobs] = useState([]);
657
+ const [recordings, setRecordings] = useState([]);
658
+ const [stats, setStats] = useState(void 0);
494
659
  const [origin, setOrigin] = useState("");
495
- const [stack, setStack] = useState([
496
- { kind: initialView === "jobs" ? "jobs" : "overview" }
497
- ]);
660
+ const [stack, setStack] = useState([{ kind: initialView }]);
498
661
  const [selected, setSelected] = useState(0);
499
662
  const [spinnerFrame, setSpinnerFrame] = useState(0);
500
663
  const [loadError, setLoadError] = useState(void 0);
501
664
  const [notice, setNotice] = useState(void 0);
502
665
  const screen = stack[stack.length - 1];
503
666
  const refresh = useCallback(async () => {
504
- try {
505
- const data = await fetchJobs();
506
- setItems(data.items);
507
- setOrigin(data.origin);
667
+ const [jobsR, recR, statsR] = await Promise.allSettled([
668
+ fetchJobs(),
669
+ fetchRecordings ? fetchRecordings() : Promise.resolve(void 0),
670
+ fetchDashboardStats ? fetchDashboardStats() : Promise.resolve(void 0)
671
+ ]);
672
+ if (jobsR.status === "fulfilled") {
673
+ setJobs(jobsR.value.items);
674
+ setOrigin(jobsR.value.origin);
508
675
  setLoadError(void 0);
509
- } catch (error51) {
510
- setLoadError(error51 instanceof Error ? error51.message : String(error51));
676
+ } else {
677
+ setLoadError(jobsR.reason instanceof Error ? jobsR.reason.message : String(jobsR.reason));
511
678
  }
512
- }, [fetchJobs]);
679
+ if (recR.status === "fulfilled" && recR.value) setRecordings(recR.value.items);
680
+ if (statsR.status === "fulfilled" && statsR.value) setStats(statsR.value);
681
+ }, [fetchJobs, fetchRecordings, fetchDashboardStats]);
513
682
  useEffect(() => {
514
683
  void refresh();
515
684
  const id = setInterval(() => void refresh(), pollMs);
516
685
  return () => clearInterval(id);
517
686
  }, [refresh, pollMs]);
518
- const hasRunning = items.some((item) => item.status === "running");
687
+ const hasRunning = jobs.some((item) => item.status === "running");
519
688
  useEffect(() => {
520
689
  if (!hasRunning) return;
521
690
  const id = setInterval(() => setSpinnerFrame((f) => f + 1), spinnerMs);
522
691
  return () => clearInterval(id);
523
692
  }, [hasRunning, spinnerMs]);
524
- const currentList = screen.kind === "overview" ? overviewActiveItems(items) : items;
693
+ const recordingList = screen.kind === "overview" ? overviewRecentRecordings(recordings) : recordings;
694
+ const listLength = screen.kind === "jobs" ? jobs.length : recordingList.length;
525
695
  useEffect(() => {
526
- setSelected((i) => Math.max(0, Math.min(i, Math.max(0, currentList.length - 1))));
527
- }, [currentList.length]);
696
+ setSelected((i) => Math.max(0, Math.min(i, Math.max(0, listLength - 1))));
697
+ }, [listLength]);
528
698
  const openTranscript = useCallback(
529
699
  async (transcriptId) => {
530
700
  setStack((st) => [...st, { kind: "transcript", loading: true }]);
@@ -552,33 +722,46 @@ function AppShell({
552
722
  const back = () => setStack((st) => st.length > 1 ? st.slice(0, -1) : st);
553
723
  useInput((input, key) => {
554
724
  setNotice(void 0);
555
- if (input === "q") {
556
- exit();
557
- return;
558
- }
559
- if (key.escape || key.leftArrow) {
560
- back();
561
- return;
562
- }
725
+ if (input === "q") return exit();
726
+ if (key.escape || key.leftArrow) return back();
563
727
  if (input === "1") return goTab("overview");
564
728
  if (input === "2") return goTab("jobs");
565
- if (input === "r") {
566
- void refresh();
729
+ if (input === "3") return goTab("recordings");
730
+ if (input === "r") return void refresh();
731
+ if (screen.kind === "overview" || screen.kind === "recordings") {
732
+ if (key.upArrow || input === "k") setSelected((i) => Math.max(0, i - 1));
733
+ if (key.downArrow || input === "j")
734
+ setSelected((i) => Math.min(recordingList.length - 1, i + 1));
735
+ const rec = recordingList[selected];
736
+ if (key.return && rec)
737
+ setStack((st) => [...st, { kind: "recordingDetail", recordingId: rec.recordingId }]);
738
+ if (input === "t" && rec?.activeTranscriptId) void openTranscript(rec.activeTranscriptId);
567
739
  return;
568
740
  }
569
- if (screen.kind === "overview" || screen.kind === "jobs") {
741
+ if (screen.kind === "jobs") {
570
742
  if (key.upArrow || input === "k") setSelected((i) => Math.max(0, i - 1));
571
- if (key.downArrow || input === "j")
572
- setSelected((i) => Math.min(currentList.length - 1, i + 1));
573
- const item = currentList[selected];
574
- if (key.return && item) setStack((st) => [...st, { kind: "jobDetail", jobId: item.jobId }]);
575
- if (input === "t" && item?.transcriptId) void openTranscript(item.transcriptId);
743
+ if (key.downArrow || input === "j") setSelected((i) => Math.min(jobs.length - 1, i + 1));
744
+ const job = jobs[selected];
745
+ if (key.return && job) setStack((st) => [...st, { kind: "jobDetail", jobId: job.jobId }]);
746
+ if (input === "t" && job?.transcriptId) void openTranscript(job.transcriptId);
576
747
  return;
577
748
  }
578
749
  if (screen.kind === "jobDetail") {
579
- const item = items.find((i) => i.jobId === screen.jobId);
580
- const links = item ? resolveJobLinks(item, origin) : {};
581
- if (input === "t" && item?.transcriptId) void openTranscript(item.transcriptId);
750
+ const job = jobs.find((j) => j.jobId === screen.jobId);
751
+ const links = job ? resolveJobLinks(job, origin) : {};
752
+ if (input === "t" && job?.transcriptId) void openTranscript(job.transcriptId);
753
+ else if ((input === "o" || input === "w") && links.webUrl) openUrl2?.(links.webUrl);
754
+ else if (input === "m") setNotice("Mac app deeplink not available yet");
755
+ else if (input === "c" && links.webUrl) {
756
+ copyText2?.(links.webUrl);
757
+ setNotice("Link copied");
758
+ }
759
+ return;
760
+ }
761
+ if (screen.kind === "recordingDetail") {
762
+ const rec = recordings.find((r) => r.recordingId === screen.recordingId);
763
+ const links = rec ? resolveRecordingLinks(rec.recordingId, rec.origin) : {};
764
+ if (input === "t" && rec?.activeTranscriptId) void openTranscript(rec.activeTranscriptId);
582
765
  else if ((input === "o" || input === "w") && links.webUrl) openUrl2?.(links.webUrl);
583
766
  else if (input === "m") setNotice("Mac app deeplink not available yet");
584
767
  else if (input === "c" && links.webUrl) {
@@ -589,43 +772,56 @@ function AppShell({
589
772
  }
590
773
  });
591
774
  if (screen.kind === "transcript") {
592
- return /* @__PURE__ */ jsx7(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
775
+ return /* @__PURE__ */ jsx10(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
593
776
  }
594
777
  if (screen.kind === "jobDetail") {
595
- const item = items.find((i) => i.jobId === screen.jobId);
596
- if (!item) {
597
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", paddingX: 1, children: [
598
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Job no longer in the list." }),
599
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "esc back \xB7 q quit" })
600
- ] });
601
- }
602
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", children: [
603
- /* @__PURE__ */ jsx7(JobDetailView, { item, origin, spinnerFrame, nowMs: now() }),
604
- notice ? /* @__PURE__ */ jsx7(Box7, { paddingX: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "green", children: notice }) }) : null
605
- ] });
606
- }
607
- const tab = screen.kind === "jobs" ? "jobs" : "overview";
608
- return /* @__PURE__ */ jsxs6(Box7, { flexDirection: "column", paddingX: 1, children: [
609
- /* @__PURE__ */ jsx7(Header, { active: tab }),
610
- screen.kind === "overview" ? /* @__PURE__ */ jsx7(
778
+ const job = jobs.find((j) => j.jobId === screen.jobId);
779
+ if (!job) return /* @__PURE__ */ jsx10(Missing, { label: "Job" });
780
+ return /* @__PURE__ */ jsx10(Detail, { notice, children: /* @__PURE__ */ jsx10(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
781
+ }
782
+ if (screen.kind === "recordingDetail") {
783
+ const rec = recordings.find((r) => r.recordingId === screen.recordingId);
784
+ if (!rec) return /* @__PURE__ */ jsx10(Missing, { label: "Recording" });
785
+ return /* @__PURE__ */ jsx10(Detail, { notice, children: /* @__PURE__ */ jsx10(RecordingDetailView, { item: rec, nowMs: now() }) });
786
+ }
787
+ const tab = screen.kind === "jobs" ? "jobs" : screen.kind === "recordings" ? "recordings" : "overview";
788
+ const footerKeys = screen.kind === "jobs" ? "1/2/3 tabs \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 r refresh \xB7 q quit" : "1/2/3 tabs \xB7 \u2191\u2193 select \xB7 \u23CE recording \xB7 t transcript \xB7 r refresh \xB7 q quit";
789
+ return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", paddingX: 1, children: [
790
+ /* @__PURE__ */ jsx10(Header, { active: tab }),
791
+ screen.kind === "overview" ? /* @__PURE__ */ jsx10(
611
792
  OverviewView,
612
793
  {
613
- items,
794
+ recordings,
795
+ jobs,
796
+ stats,
614
797
  selectedIndex: selected,
615
798
  spinnerFrame,
616
799
  nowMs: now()
617
800
  }
618
- ) : /* @__PURE__ */ jsx7(JobsView, { items, selectedIndex: selected, spinnerFrame }),
619
- loadError && items.length === 0 ? /* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs6(Text7, { color: "red", children: [
801
+ ) : screen.kind === "recordings" ? /* @__PURE__ */ jsx10(RecordingsView, { items: recordings, selectedIndex: selected, nowMs: now() }) : /* @__PURE__ */ jsx10(JobsView, { items: jobs, selectedIndex: selected, spinnerFrame }),
802
+ loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx10(Box10, { marginTop: 1, children: /* @__PURE__ */ jsxs8(Text10, { color: "red", children: [
620
803
  "! ",
621
804
  loadError
622
805
  ] }) }) : null,
623
- /* @__PURE__ */ jsx7(
624
- Footer,
625
- {
626
- keys: screen.kind === "jobs" ? "1/2 tabs \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 r refresh \xB7 q quit" : "1/2 tabs \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 q quit"
627
- }
628
- )
806
+ /* @__PURE__ */ jsx10(Footer, { keys: footerKeys })
807
+ ] });
808
+ }
809
+ function Detail({
810
+ notice,
811
+ children
812
+ }) {
813
+ return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", children: [
814
+ children,
815
+ notice ? /* @__PURE__ */ jsx10(Box10, { paddingX: 1, children: /* @__PURE__ */ jsx10(Text10, { color: "green", children: notice }) }) : null
816
+ ] });
817
+ }
818
+ function Missing({ label }) {
819
+ return /* @__PURE__ */ jsxs8(Box10, { flexDirection: "column", paddingX: 1, children: [
820
+ /* @__PURE__ */ jsxs8(Text10, { dimColor: true, children: [
821
+ label,
822
+ " no longer in the list."
823
+ ] }),
824
+ /* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "esc back \xB7 q quit" })
629
825
  ] });
630
826
  }
631
827
  var init_AppShell = __esm({
@@ -634,7 +830,9 @@ var init_AppShell = __esm({
634
830
  init_chrome();
635
831
  init_JobsView();
636
832
  init_OverviewView();
833
+ init_RecordingsView();
637
834
  init_JobDetailView();
835
+ init_RecordingDetailView();
638
836
  init_TranscriptView();
639
837
  init_format();
640
838
  }
@@ -672,6 +870,8 @@ async function runDashboard(deps) {
672
870
  React2.createElement(AppShell, {
673
871
  fetchJobs: deps.fetchJobs,
674
872
  fetchTranscript: deps.fetchTranscript,
873
+ fetchRecordings: deps.fetchRecordings,
874
+ fetchDashboardStats: deps.fetchDashboardStats,
675
875
  initialView: deps.initialView ?? "overview",
676
876
  openUrl,
677
877
  copyText
@@ -692,6 +892,7 @@ var init_tui = __esm({
692
892
 
693
893
  // src/cli.ts
694
894
  import { Command, CommanderError, InvalidArgumentError } from "commander/esm.mjs";
895
+ import os3 from "os";
695
896
 
696
897
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
697
898
  var external_exports = {};
@@ -15208,7 +15409,7 @@ function date4(params) {
15208
15409
  config(en_default());
15209
15410
 
15210
15411
  // ../../packages/contracts/src/index.ts
15211
- var CLI_SCHEMA_VERSION = "2026-06-24";
15412
+ var CLI_SCHEMA_VERSION = "2026-06-25";
15212
15413
  function toJsonSchema(schema) {
15213
15414
  return external_exports.toJSONSchema(schema, { target: "draft-2020-12" });
15214
15415
  }
@@ -15262,6 +15463,22 @@ var authStatusDataSchema = external_exports.object({
15262
15463
  email: external_exports.string().optional(),
15263
15464
  userId: external_exports.string().optional()
15264
15465
  });
15466
+ var authLoginDataSchema = external_exports.object({
15467
+ loggedIn: external_exports.literal(true),
15468
+ origin: external_exports.string(),
15469
+ email: external_exports.string().optional(),
15470
+ userId: external_exports.string().optional()
15471
+ });
15472
+ var authLogoutDataSchema = external_exports.object({
15473
+ loggedIn: external_exports.literal(false),
15474
+ origin: external_exports.string(),
15475
+ cleared: external_exports.boolean()
15476
+ });
15477
+ var authImportDataSchema = external_exports.object({
15478
+ imported: external_exports.boolean(),
15479
+ origin: external_exports.string(),
15480
+ source: external_exports.literal("macos-keychain")
15481
+ });
15265
15482
  var uploadSuccessSchema = external_exports.object({
15266
15483
  filePath: external_exports.string(),
15267
15484
  recordingId: external_exports.string(),
@@ -15323,6 +15540,45 @@ var jobListDataSchema = external_exports.object({
15323
15540
  limit: external_exports.number().int().positive(),
15324
15541
  origin: external_exports.string()
15325
15542
  });
15543
+ var recordingDataSchema = external_exports.object({
15544
+ recordingId: external_exports.string(),
15545
+ title: external_exports.string().nullable().optional(),
15546
+ summaryTitle: external_exports.string().nullable().optional(),
15547
+ status: recordingStatusSchema,
15548
+ durationMs: external_exports.number().int().nonnegative().nullable().optional(),
15549
+ sizeBytes: external_exports.number().int().nonnegative().nullable().optional(),
15550
+ contentType: external_exports.string().optional(),
15551
+ activeTranscriptId: external_exports.string().nullable().optional(),
15552
+ createdAt: external_exports.number().int(),
15553
+ updatedAt: external_exports.number().int(),
15554
+ origin: external_exports.string()
15555
+ });
15556
+ var recordingListDataSchema = external_exports.object({
15557
+ items: external_exports.array(recordingDataSchema),
15558
+ limit: external_exports.number().int().positive(),
15559
+ nextCursor: external_exports.string().nullable().optional(),
15560
+ totalCount: external_exports.number().int().nonnegative().optional(),
15561
+ origin: external_exports.string()
15562
+ });
15563
+ var dashboardStatsDataSchema = external_exports.object({
15564
+ origin: external_exports.string(),
15565
+ recordings: external_exports.object({
15566
+ total: external_exports.number().int().nonnegative(),
15567
+ ready: external_exports.number().int().nonnegative(),
15568
+ uploading: external_exports.number().int().nonnegative(),
15569
+ failed: external_exports.number().int().nonnegative(),
15570
+ aborted: external_exports.number().int().nonnegative(),
15571
+ totalDurationMs: external_exports.number().int().nonnegative(),
15572
+ totalSizeBytes: external_exports.number().int().nonnegative()
15573
+ }),
15574
+ jobs: external_exports.object({
15575
+ active: external_exports.number().int().nonnegative(),
15576
+ queued: external_exports.number().int().nonnegative(),
15577
+ running: external_exports.number().int().nonnegative(),
15578
+ succeeded: external_exports.number().int().nonnegative(),
15579
+ failed: external_exports.number().int().nonnegative()
15580
+ })
15581
+ });
15326
15582
  var transcriptSegmentSchema = external_exports.object({
15327
15583
  startMs: external_exports.number().nonnegative(),
15328
15584
  endMs: external_exports.number().nonnegative(),
@@ -15543,17 +15799,19 @@ async function resolveAuthContext(opts = {}) {
15543
15799
  token: config2.token,
15544
15800
  source: "config"
15545
15801
  };
15546
- const platform = opts.platform ?? process.platform;
15547
- if (platform === "darwin" && env.RECAPPI_DISABLE_KEYCHAIN_AUTH !== "1") {
15548
- const token = await readMacOSAppToken();
15549
- if (token) return { origin, token, source: "macos-keychain" };
15802
+ if (opts.includeMacOSKeychain === true) {
15803
+ const keychain = await inspectMacOSAppKeychain({
15804
+ env,
15805
+ platform: opts.platform
15806
+ });
15807
+ if (keychain.token) return { origin, token: keychain.token, source: "macos-keychain" };
15550
15808
  }
15551
15809
  return { origin, token: null, source: "none" };
15552
15810
  }
15553
15811
  function requireToken(ctx) {
15554
15812
  if (!ctx.token) {
15555
15813
  throw cliError("auth.not_logged_in", "Not logged in to Recappi.", {
15556
- hint: "Set RECAPPI_AUTH_TOKEN, or open Recappi Mini and sign in before retrying."
15814
+ hint: "Run recappi auth login, or set RECAPPI_AUTH_TOKEN for automation."
15557
15815
  });
15558
15816
  }
15559
15817
  return ctx.token;
@@ -15579,14 +15837,61 @@ function validateOrigin(value) {
15579
15837
  }
15580
15838
  return url2.origin;
15581
15839
  }
15840
+ async function saveAuthConfig(homeDir, config2) {
15841
+ const target = primaryConfigPath(homeDir);
15842
+ await fs.mkdir(path.dirname(target), { recursive: true, mode: 448 });
15843
+ const existing = await readConfigObject(target);
15844
+ const next = {
15845
+ ...existing,
15846
+ origin: validateOrigin(config2.origin),
15847
+ authToken: config2.token
15848
+ };
15849
+ const tmp = `${target}.${process.pid}.${Date.now()}.tmp`;
15850
+ await fs.writeFile(tmp, `${JSON.stringify(next, null, 2)}
15851
+ `, { mode: 384 });
15852
+ await fs.rename(tmp, target);
15853
+ await fs.chmod(target, 384).catch(() => {
15854
+ });
15855
+ }
15856
+ async function clearAuthConfig(homeDir) {
15857
+ const target = primaryConfigPath(homeDir);
15858
+ const existing = await readConfigObject(target);
15859
+ if (!("authToken" in existing)) return false;
15860
+ delete existing.authToken;
15861
+ await fs.mkdir(path.dirname(target), { recursive: true, mode: 448 });
15862
+ await fs.writeFile(target, `${JSON.stringify(existing, null, 2)}
15863
+ `, { mode: 384 });
15864
+ await fs.chmod(target, 384).catch(() => {
15865
+ });
15866
+ return true;
15867
+ }
15868
+ async function inspectMacOSAppKeychain(opts = {}) {
15869
+ const env = opts.env ?? process.env;
15870
+ if (env.RECAPPI_DISABLE_KEYCHAIN_AUTH === "1") {
15871
+ return {
15872
+ status: "disabled",
15873
+ token: null,
15874
+ message: "macOS app keychain lookup is disabled by RECAPPI_DISABLE_KEYCHAIN_AUTH=1."
15875
+ };
15876
+ }
15877
+ const platform = opts.platform ?? process.platform;
15878
+ if (platform !== "darwin") {
15879
+ return {
15880
+ status: "unsupported",
15881
+ token: null,
15882
+ message: "macOS app keychain lookup is only available on macOS."
15883
+ };
15884
+ }
15885
+ return readMacOSAppToken();
15886
+ }
15887
+ function primaryConfigPath(homeDir) {
15888
+ return path.join(homeDir, ".config", "recappi", "config.json");
15889
+ }
15582
15890
  async function readConfig(homeDir) {
15583
- const candidates = [
15584
- path.join(homeDir, ".config", "recappi", "config.json"),
15585
- path.join(homeDir, ".recappi", "config.json")
15586
- ];
15891
+ const candidates = [primaryConfigPath(homeDir), path.join(homeDir, ".recappi", "config.json")];
15587
15892
  for (const candidate of candidates) {
15588
15893
  try {
15589
- const parsed = JSON.parse(await fs.readFile(candidate, "utf8"));
15894
+ const parsed = await readConfigObject(candidate);
15590
15895
  const token = typeof parsed.authToken === "string" ? parsed.authToken.trim() : void 0;
15591
15896
  const origin = typeof parsed.origin === "string" ? parsed.origin.trim() : void 0;
15592
15897
  if (token || origin) return { ...token ? { token } : {}, ...origin ? { origin } : {} };
@@ -15601,6 +15906,14 @@ async function readConfig(homeDir) {
15601
15906
  }
15602
15907
  return {};
15603
15908
  }
15909
+ async function readConfigObject(candidate) {
15910
+ try {
15911
+ return JSON.parse(await fs.readFile(candidate, "utf8"));
15912
+ } catch (error51) {
15913
+ if (error51.code === "ENOENT") return {};
15914
+ throw error51;
15915
+ }
15916
+ }
15604
15917
  async function readMacOSAppToken() {
15605
15918
  try {
15606
15919
  const { stdout } = await execFileAsync(
@@ -15609,12 +15922,167 @@ async function readMacOSAppToken() {
15609
15922
  { timeout: 2e3, maxBuffer: 1024 * 1024 }
15610
15923
  );
15611
15924
  const token = stdout.trim();
15612
- return token.length > 0 ? token : null;
15613
- } catch {
15614
- return null;
15925
+ if (token.length > 0) {
15926
+ return {
15927
+ status: "ok",
15928
+ token,
15929
+ message: "Found a Recappi Mini app token in the macOS keychain."
15930
+ };
15931
+ }
15932
+ return {
15933
+ status: "missing",
15934
+ token: null,
15935
+ message: "Recappi Mini app keychain item was present but empty."
15936
+ };
15937
+ } catch (error51) {
15938
+ const err = error51;
15939
+ const timedOut = err.killed || err.signal === "SIGTERM";
15940
+ const stderr = typeof err.message === "string" ? err.message : "";
15941
+ if (!timedOut && /could not be found|The specified item could not be found/i.test(stderr)) {
15942
+ return {
15943
+ status: "missing",
15944
+ token: null,
15945
+ message: "No Recappi Mini app token was found in the macOS keychain."
15946
+ };
15947
+ }
15948
+ return {
15949
+ status: "error",
15950
+ token: null,
15951
+ message: timedOut ? "Timed out while reading the Recappi Mini app keychain token." : "Could not read the Recappi Mini app keychain token.",
15952
+ hint: "Use recappi auth login for the CLI, or run recappi auth import-macos explicitly if you want to import the app session."
15953
+ };
15615
15954
  }
15616
15955
  }
15617
15956
 
15957
+ // src/auth-login.ts
15958
+ import { execFile as execFile2 } from "child_process";
15959
+ import os2 from "os";
15960
+ import { promisify as promisify2 } from "util";
15961
+ var execFileAsync2 = promisify2(execFile2);
15962
+ async function loginWithDeviceCode(opts) {
15963
+ const origin = validateOrigin(opts.origin);
15964
+ const fetchImpl = opts.deps?.fetchImpl ?? fetch;
15965
+ const sleep = opts.deps?.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
15966
+ const start = await startDeviceAuth(origin, fetchImpl);
15967
+ opts.onPrompt?.(
15968
+ [
15969
+ "To sign in to Recappi CLI:",
15970
+ ` 1. Open ${start.verificationUri}`,
15971
+ ` 2. Enter code: ${start.userCode}`,
15972
+ ""
15973
+ ].join("\n")
15974
+ );
15975
+ if (!opts.noOpen) {
15976
+ const openUrl2 = opts.deps?.openUrl ?? openUrlWithSystemBrowser;
15977
+ await openUrl2(start.verificationUriComplete).catch((error51) => {
15978
+ opts.onPrompt?.(
15979
+ `Could not open the browser automatically: ${error51 instanceof Error ? error51.message : String(error51)}
15980
+ `
15981
+ );
15982
+ });
15983
+ }
15984
+ let intervalMs = start.interval * 1e3;
15985
+ const expiresAt = Date.now() + start.expiresIn * 1e3;
15986
+ while (Date.now() < expiresAt) {
15987
+ await sleep(intervalMs);
15988
+ const poll = await pollDeviceAuth(origin, start.deviceCode, fetchImpl);
15989
+ if (poll.status === "pending") {
15990
+ if (typeof poll.interval === "number") intervalMs = poll.interval * 1e3;
15991
+ continue;
15992
+ }
15993
+ if (poll.status === "slow_down") {
15994
+ intervalMs = (typeof poll.interval === "number" ? poll.interval : intervalMs / 1e3 + 5) * 1e3;
15995
+ continue;
15996
+ }
15997
+ if (poll.status === "denied") {
15998
+ throw cliError("auth.unauthorized", "Recappi CLI sign-in was denied.");
15999
+ }
16000
+ if (poll.status === "expired") {
16001
+ throw cliError("auth.unauthorized", "Recappi CLI sign-in code expired.", {
16002
+ hint: "Run recappi auth login again."
16003
+ });
16004
+ }
16005
+ if (poll.status === "authorized") {
16006
+ const token = typeof poll.token === "string" ? poll.token.trim() : "";
16007
+ if (!token) {
16008
+ throw cliError("cloud.invalid_response", "Recappi device auth returned no token.");
16009
+ }
16010
+ const user = isRecord(poll.user) ? poll.user : {};
16011
+ await saveAuthConfig(opts.homeDir ?? os2.homedir(), { origin, token });
16012
+ return {
16013
+ loggedIn: true,
16014
+ origin,
16015
+ ...typeof user.email === "string" ? { email: user.email } : {},
16016
+ ...typeof user.id === "string" ? { userId: user.id } : {}
16017
+ };
16018
+ }
16019
+ throw cliError("cloud.invalid_response", "Recappi device auth returned an unknown status.");
16020
+ }
16021
+ throw cliError("auth.unauthorized", "Recappi CLI sign-in timed out.", {
16022
+ hint: "Run recappi auth login again."
16023
+ });
16024
+ }
16025
+ async function startDeviceAuth(origin, fetchImpl) {
16026
+ const response = await fetchImpl(new URL("/api/device-auth/start", origin), {
16027
+ method: "POST"
16028
+ });
16029
+ if (!response.ok) {
16030
+ throw cliError(
16031
+ "cloud.http_error",
16032
+ `Could not start Recappi device sign-in (${response.status}).`,
16033
+ {
16034
+ retryable: response.status >= 500
16035
+ }
16036
+ );
16037
+ }
16038
+ const body = await response.json();
16039
+ const deviceCode = stringField(body.device_code, "device_code");
16040
+ const userCode = stringField(body.user_code, "user_code");
16041
+ const verificationUri = stringField(body.verification_uri, "verification_uri");
16042
+ const verificationUriComplete = stringField(
16043
+ body.verification_uri_complete,
16044
+ "verification_uri_complete"
16045
+ );
16046
+ const expiresIn = numberField(body.expires_in, "expires_in");
16047
+ const interval = numberField(body.interval, "interval");
16048
+ return { deviceCode, userCode, verificationUri, verificationUriComplete, expiresIn, interval };
16049
+ }
16050
+ async function pollDeviceAuth(origin, deviceCode, fetchImpl) {
16051
+ const response = await fetchImpl(new URL("/api/device-auth/poll", origin), {
16052
+ method: "POST",
16053
+ headers: { "content-type": "application/json" },
16054
+ body: JSON.stringify({ device_code: deviceCode })
16055
+ });
16056
+ if (!response.ok) {
16057
+ throw cliError("cloud.http_error", `Recappi device sign-in poll failed (${response.status}).`, {
16058
+ retryable: response.status >= 500
16059
+ });
16060
+ }
16061
+ return await response.json();
16062
+ }
16063
+ async function openUrlWithSystemBrowser(url2) {
16064
+ if (process.platform === "darwin") {
16065
+ await execFileAsync2("/usr/bin/open", [url2]);
16066
+ return;
16067
+ }
16068
+ if (process.platform === "win32") {
16069
+ await execFileAsync2("cmd", ["/c", "start", "", url2]);
16070
+ return;
16071
+ }
16072
+ await execFileAsync2("xdg-open", [url2]);
16073
+ }
16074
+ function stringField(value, name) {
16075
+ if (typeof value === "string" && value.length > 0) return value;
16076
+ throw cliError("cloud.invalid_response", `Recappi device auth response is missing ${name}.`);
16077
+ }
16078
+ function numberField(value, name) {
16079
+ if (typeof value === "number" && Number.isFinite(value) && value > 0) return value;
16080
+ throw cliError("cloud.invalid_response", `Recappi device auth response is missing ${name}.`);
16081
+ }
16082
+ function isRecord(value) {
16083
+ return typeof value === "object" && value !== null;
16084
+ }
16085
+
15618
16086
  // src/api.ts
15619
16087
  import { promises as fs3 } from "fs";
15620
16088
 
@@ -15779,9 +16247,11 @@ var RecappiApiClient = class {
15779
16247
  this.auth = auth;
15780
16248
  this.fetchImpl = opts.fetchImpl ?? fetch;
15781
16249
  this.sleep = opts.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
16250
+ this.env = opts.env ?? process.env;
15782
16251
  }
15783
16252
  fetchImpl;
15784
16253
  sleep;
16254
+ env;
15785
16255
  async authStatus() {
15786
16256
  if (!this.auth.token) {
15787
16257
  return { loggedIn: false, origin: this.auth.origin };
@@ -15793,7 +16263,7 @@ var RecappiApiClient = class {
15793
16263
  return { loggedIn: false, origin: this.auth.origin };
15794
16264
  }
15795
16265
  const body = await parseJson(response);
15796
- const user = isRecord(body) && isRecord(body.user) ? body.user : void 0;
16266
+ const user = isRecord2(body) && isRecord2(body.user) ? body.user : void 0;
15797
16267
  return {
15798
16268
  loggedIn: Boolean(user),
15799
16269
  origin: this.auth.origin,
@@ -15820,7 +16290,7 @@ var RecappiApiClient = class {
15820
16290
  status: this.auth.token ? "ok" : "error",
15821
16291
  message: this.auth.token ? `Found auth token from ${this.auth.source}.` : "No Recappi auth token found.",
15822
16292
  ...this.auth.token ? {} : {
15823
- hint: "Set RECAPPI_AUTH_TOKEN, configure ~/.config/recappi/config.json, or sign in with Recappi Mini on macOS."
16293
+ hint: "Run recappi auth login, or set RECAPPI_AUTH_TOKEN for automation."
15824
16294
  }
15825
16295
  });
15826
16296
  if (this.auth.token) {
@@ -15830,7 +16300,9 @@ var RecappiApiClient = class {
15830
16300
  name: "auth.session",
15831
16301
  status: status2.loggedIn ? "ok" : "error",
15832
16302
  message: status2.loggedIn ? `Cloud session is valid${status2.email ? ` for ${status2.email}` : ""}.` : "Cloud did not accept the configured auth token.",
15833
- ...status2.loggedIn ? {} : { hint: "Refresh the token or sign in again before retrying cloud commands." }
16303
+ ...status2.loggedIn ? {} : {
16304
+ hint: `Run recappi auth login again. If you use --origin, make sure the token belongs to ${this.auth.origin}.`
16305
+ }
15834
16306
  });
15835
16307
  } catch (error51) {
15836
16308
  const cli = toCliError(error51);
@@ -15842,6 +16314,13 @@ var RecappiApiClient = class {
15842
16314
  });
15843
16315
  }
15844
16316
  }
16317
+ const keychain = await inspectMacOSAppKeychain({ env: this.env });
16318
+ checks.push({
16319
+ name: "auth.macos_keychain",
16320
+ status: keychain.status === "error" ? "warn" : "ok",
16321
+ message: keychain.message,
16322
+ ...keychain.status === "ok" ? { hint: "Run recappi auth import-macos to copy this app session into CLI config." } : keychain.hint ? { hint: keychain.hint } : {}
16323
+ });
15845
16324
  checks.push({
15846
16325
  name: "audio.wav",
15847
16326
  status: "ok",
@@ -15882,7 +16361,7 @@ var RecappiApiClient = class {
15882
16361
  limit: String(opts.limit)
15883
16362
  });
15884
16363
  const parsed = await this.getJson(`/api/jobs?${params}`);
15885
- const items = Array.isArray(parsed.items) ? parsed.items.filter(isRecord).map(mapJobListItem) : [];
16364
+ const items = Array.isArray(parsed.items) ? parsed.items.filter(isRecord2).map(mapJobListItem) : [];
15886
16365
  return jobListDataSchema.parse({
15887
16366
  items,
15888
16367
  status: typeof parsed.status === "string" ? parsed.status : opts.status,
@@ -15890,6 +16369,30 @@ var RecappiApiClient = class {
15890
16369
  origin: this.auth.origin
15891
16370
  });
15892
16371
  }
16372
+ async listRecordings(opts) {
16373
+ const params = new URLSearchParams({ limit: String(opts.limit) });
16374
+ if (opts.cursor) params.set("cursor", opts.cursor);
16375
+ if (opts.search) params.set("search", opts.search);
16376
+ const parsed = await this.getJson(`/api/recordings?${params}`);
16377
+ const items = Array.isArray(parsed.items) ? parsed.items.filter(isRecord2).map((row) => mapRecording(row, this.auth.origin)) : [];
16378
+ return recordingListDataSchema.parse({
16379
+ items,
16380
+ limit: opts.limit,
16381
+ ...typeof parsed.nextCursor === "string" || parsed.nextCursor === null ? { nextCursor: parsed.nextCursor } : {},
16382
+ ...typeof parsed.totalCount === "number" ? { totalCount: parsed.totalCount } : {},
16383
+ origin: this.auth.origin
16384
+ });
16385
+ }
16386
+ async getRecording(recordingId) {
16387
+ const parsed = await this.getJson(
16388
+ `/api/recordings/${encodeURIComponent(recordingId)}`
16389
+ );
16390
+ return mapRecording(parsed, this.auth.origin);
16391
+ }
16392
+ async dashboardStats() {
16393
+ const parsed = await this.getJson("/api/dashboard/stats");
16394
+ return mapDashboardStats(parsed, this.auth.origin);
16395
+ }
15893
16396
  async uploadPathBatch(opts) {
15894
16397
  const files = await collectAudioFiles(opts.inputPath);
15895
16398
  if (files.length === 0) {
@@ -16035,7 +16538,7 @@ var RecappiApiClient = class {
16035
16538
  }
16036
16539
  );
16037
16540
  const parsed = await parseJson(response);
16038
- if (!isRecord(parsed) || typeof parsed.etag !== "string") {
16541
+ if (!isRecord2(parsed) || typeof parsed.etag !== "string") {
16039
16542
  throw cliError("cloud.invalid_response", "Upload part response was missing etag.");
16040
16543
  }
16041
16544
  parts.push({ partNumber, etag: parsed.etag });
@@ -16107,8 +16610,8 @@ async function parseJson(response) {
16107
16610
  async function responseMessage(response) {
16108
16611
  try {
16109
16612
  const parsed = await response.clone().json();
16110
- if (isRecord(parsed) && typeof parsed.message === "string") return parsed.message;
16111
- if (isRecord(parsed) && typeof parsed.error === "string") return parsed.error;
16613
+ if (isRecord2(parsed) && typeof parsed.message === "string") return parsed.message;
16614
+ if (isRecord2(parsed) && typeof parsed.error === "string") return parsed.error;
16112
16615
  } catch {
16113
16616
  }
16114
16617
  try {
@@ -16118,7 +16621,7 @@ async function responseMessage(response) {
16118
16621
  return response.statusText;
16119
16622
  }
16120
16623
  }
16121
- function isRecord(value) {
16624
+ function isRecord2(value) {
16122
16625
  return typeof value === "object" && value !== null && !Array.isArray(value);
16123
16626
  }
16124
16627
  function mapTranscript(row) {
@@ -16162,7 +16665,7 @@ function requiredNumber(row, key) {
16162
16665
  }
16163
16666
  function parseSegments(value, text, durationMs) {
16164
16667
  const decoded = decodeJsonArray(value);
16165
- const rawSegments = decoded.filter(isRecord).map((segment) => {
16668
+ const rawSegments = decoded.filter(isRecord2).map((segment) => {
16166
16669
  const segmentText = typeof segment.text === "string" ? segment.text.trim() : "";
16167
16670
  if (!segmentText) return null;
16168
16671
  const start = typeof segment.start === "number" && segment.start >= 0 ? segment.start : 0;
@@ -16213,12 +16716,12 @@ function parseSummary(row) {
16213
16716
  if (strings.length > 0) summary[key] = strings;
16214
16717
  }
16215
16718
  }
16216
- const actionItems = Array.isArray(payload?.actionItems) ? payload.actionItems.filter(isRecord).flatMap((item) => {
16719
+ const actionItems = Array.isArray(payload?.actionItems) ? payload.actionItems.filter(isRecord2).flatMap((item) => {
16217
16720
  if (typeof item.what !== "string" || !item.what.trim()) return [];
16218
16721
  return [{ what: item.what, ...typeof item.who === "string" ? { who: item.who } : {} }];
16219
16722
  }) : [];
16220
16723
  if (actionItems.length > 0) summary.actionItems = actionItems;
16221
- const quotes = Array.isArray(payload?.quotes) ? payload.quotes.filter(isRecord).flatMap((item) => {
16724
+ const quotes = Array.isArray(payload?.quotes) ? payload.quotes.filter(isRecord2).flatMap((item) => {
16222
16725
  if (typeof item.text !== "string" || !item.text.trim()) return [];
16223
16726
  return [
16224
16727
  {
@@ -16228,7 +16731,7 @@ function parseSummary(row) {
16228
16731
  ];
16229
16732
  }) : [];
16230
16733
  if (quotes.length > 0) summary.quotes = quotes;
16231
- const timeline = Array.isArray(payload?.timeline) ? payload.timeline.filter(isRecord).flatMap((item) => {
16734
+ const timeline = Array.isArray(payload?.timeline) ? payload.timeline.filter(isRecord2).flatMap((item) => {
16232
16735
  if (typeof item.startMs !== "number" || typeof item.endMs !== "number" || typeof item.title !== "string" || typeof item.summary !== "string") {
16233
16736
  return [];
16234
16737
  }
@@ -16248,7 +16751,7 @@ function parseSummary(row) {
16248
16751
  return summary;
16249
16752
  }
16250
16753
  function mapJobListItem(row) {
16251
- const recording = isRecord(row.recording) ? row.recording : {};
16754
+ const recording = isRecord2(row.recording) ? row.recording : {};
16252
16755
  return {
16253
16756
  jobId: stringValue(row.jobId) ?? stringValue(row.id) ?? "",
16254
16757
  recordingId: stringValue(row.recordingId) ?? "",
@@ -16270,9 +16773,59 @@ function mapJobListItem(row) {
16270
16773
  }
16271
16774
  };
16272
16775
  }
16776
+ function mapRecording(row, origin) {
16777
+ const recordingId = stringValue(row.id) ?? stringValue(row.recordingId);
16778
+ const status = stringValue(row.status);
16779
+ const createdAt = numberValue(row.createdAt);
16780
+ const updatedAt = numberValue(row.updatedAt);
16781
+ if (!recordingId) {
16782
+ throw cliError("cloud.invalid_response", "Recording response was missing id.");
16783
+ }
16784
+ if (!status) {
16785
+ throw cliError("cloud.invalid_response", "Recording response was missing status.");
16786
+ }
16787
+ if (createdAt === void 0 || updatedAt === void 0) {
16788
+ throw cliError("cloud.invalid_response", "Recording response was missing timestamps.");
16789
+ }
16790
+ return recordingDataSchema.parse({
16791
+ recordingId,
16792
+ ...typeof row.title === "string" || row.title === null ? { title: row.title } : {},
16793
+ ...typeof row.summaryTitle === "string" || row.summaryTitle === null ? { summaryTitle: row.summaryTitle } : {},
16794
+ status,
16795
+ ...typeof row.durationMs === "number" || row.durationMs === null ? { durationMs: row.durationMs } : {},
16796
+ ...typeof row.sizeBytes === "number" || row.sizeBytes === null ? { sizeBytes: row.sizeBytes } : {},
16797
+ ...typeof row.contentType === "string" ? { contentType: row.contentType } : {},
16798
+ ...typeof row.activeTranscriptId === "string" || row.activeTranscriptId === null ? { activeTranscriptId: row.activeTranscriptId } : {},
16799
+ createdAt,
16800
+ updatedAt,
16801
+ origin
16802
+ });
16803
+ }
16804
+ function mapDashboardStats(row, origin) {
16805
+ return dashboardStatsDataSchema.parse({
16806
+ origin,
16807
+ recordings: mapCountObject(row.recordings, [
16808
+ "total",
16809
+ "ready",
16810
+ "uploading",
16811
+ "failed",
16812
+ "aborted",
16813
+ "totalDurationMs",
16814
+ "totalSizeBytes"
16815
+ ]),
16816
+ jobs: mapCountObject(row.jobs, ["active", "queued", "running", "succeeded", "failed"])
16817
+ });
16818
+ }
16819
+ function mapCountObject(value, keys) {
16820
+ const source = isRecord2(value) ? value : {};
16821
+ return Object.fromEntries(keys.map((key) => [key, numberValue(source[key]) ?? 0]));
16822
+ }
16273
16823
  function stringValue(value) {
16274
16824
  return typeof value === "string" ? value : void 0;
16275
16825
  }
16826
+ function numberValue(value) {
16827
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
16828
+ }
16276
16829
  function parseSummaryStatus(value) {
16277
16830
  const allowed = /* @__PURE__ */ new Set([
16278
16831
  "pending",
@@ -16295,11 +16848,11 @@ function decodeJsonArray(value) {
16295
16848
  }
16296
16849
  }
16297
16850
  function decodeJsonRecord(value) {
16298
- if (isRecord(value)) return value;
16851
+ if (isRecord2(value)) return value;
16299
16852
  if (typeof value !== "string" || !value.trim()) return null;
16300
16853
  try {
16301
16854
  const decoded = JSON.parse(value);
16302
- return isRecord(decoded) ? decoded : null;
16855
+ return isRecord2(decoded) ? decoded : null;
16303
16856
  } catch {
16304
16857
  return null;
16305
16858
  }
@@ -16385,7 +16938,20 @@ function renderEnvelope(envelope, opts) {
16385
16938
  `);
16386
16939
  }
16387
16940
  function renderHumanSuccess(command, data, opts) {
16388
- if (command === "auth status" && isRecord2(data)) {
16941
+ if (command === "auth login" && isRecord3(data)) {
16942
+ opts.stdout(`Signed in${typeof data.email === "string" ? ` as ${data.email}` : ""}
16943
+ `);
16944
+ return;
16945
+ }
16946
+ if (command === "auth logout" && isRecord3(data)) {
16947
+ opts.stdout(data.cleared ? "Signed out of Recappi CLI\n" : "No Recappi CLI session to clear\n");
16948
+ return;
16949
+ }
16950
+ if (command === "auth import-macos" && isRecord3(data)) {
16951
+ opts.stdout("Imported the Recappi Mini app session into Recappi CLI\n");
16952
+ return;
16953
+ }
16954
+ if (command === "auth status" && isRecord3(data)) {
16389
16955
  if (data.loggedIn) {
16390
16956
  opts.stdout(`Signed in${typeof data.email === "string" ? ` as ${data.email}` : ""}
16391
16957
  `);
@@ -16394,17 +16960,17 @@ function renderHumanSuccess(command, data, opts) {
16394
16960
  opts.stdout("Not logged in\n");
16395
16961
  return;
16396
16962
  }
16397
- if (command === "version" && isRecord2(data) && typeof data.version === "string") {
16963
+ if (command === "version" && isRecord3(data) && typeof data.version === "string") {
16398
16964
  opts.stdout(`${data.version}
16399
16965
  `);
16400
16966
  return;
16401
16967
  }
16402
- if (command === "doctor" && isRecord2(data) && Array.isArray(data.checks)) {
16968
+ if (command === "doctor" && isRecord3(data) && Array.isArray(data.checks)) {
16403
16969
  const status = typeof data.status === "string" ? data.status : "unknown";
16404
16970
  opts.stdout(`Doctor: ${status}
16405
16971
  `);
16406
16972
  for (const check2 of data.checks) {
16407
- if (!isRecord2(check2)) continue;
16973
+ if (!isRecord3(check2)) continue;
16408
16974
  const checkStatus = typeof check2.status === "string" ? check2.status : "unknown";
16409
16975
  const name = typeof check2.name === "string" ? check2.name : "check";
16410
16976
  const message = typeof check2.message === "string" ? ` \u2014 ${check2.message}` : "";
@@ -16415,10 +16981,60 @@ function renderHumanSuccess(command, data, opts) {
16415
16981
  }
16416
16982
  return;
16417
16983
  }
16418
- if (command === "transcript get" && isRecord2(data)) {
16984
+ if (command === "transcript get" && isRecord3(data)) {
16419
16985
  renderTranscriptHuman(data, opts);
16420
16986
  return;
16421
16987
  }
16988
+ if (command === "recordings list" && isRecord3(data) && Array.isArray(data.items)) {
16989
+ opts.stdout("Recordings:\n");
16990
+ for (const item of data.items) {
16991
+ if (!isRecord3(item)) continue;
16992
+ opts.stdout(` ${recordingLabel(item)}
16993
+ `);
16994
+ }
16995
+ if (data.items.length === 0) opts.stdout(" No recordings found.\n");
16996
+ if (typeof data.nextCursor === "string") {
16997
+ opts.stdout(`
16998
+ Next cursor: ${data.nextCursor}
16999
+ `);
17000
+ }
17001
+ return;
17002
+ }
17003
+ if (command === "recordings get" && isRecord3(data)) {
17004
+ opts.stdout(`${recordingTitle(data)}
17005
+ `);
17006
+ opts.stdout(` recordingId: ${String(data.recordingId)}
17007
+ `);
17008
+ if (typeof data.status === "string") opts.stdout(` status: ${data.status}
17009
+ `);
17010
+ if (typeof data.durationMs === "number")
17011
+ opts.stdout(` duration: ${formatDurationMs(data.durationMs)}
17012
+ `);
17013
+ if (typeof data.sizeBytes === "number") opts.stdout(` size: ${formatBytes(data.sizeBytes)}
17014
+ `);
17015
+ if (typeof data.activeTranscriptId === "string") {
17016
+ opts.stdout(` activeTranscriptId: ${data.activeTranscriptId}
17017
+ `);
17018
+ opts.stdout(`
17019
+ Next:
17020
+ recappi transcript get ${data.activeTranscriptId}
17021
+ `);
17022
+ }
17023
+ return;
17024
+ }
17025
+ if (command === "dashboard stats" && isRecord3(data)) {
17026
+ const recordings = isRecord3(data.recordings) ? data.recordings : {};
17027
+ const jobs = isRecord3(data.jobs) ? data.jobs : {};
17028
+ opts.stdout(
17029
+ `Recordings: ${numberText(recordings.total)} total, ${numberText(recordings.ready)} ready
17030
+ `
17031
+ );
17032
+ opts.stdout(
17033
+ `Jobs: ${numberText(jobs.active)} active (${numberText(jobs.queued)} queued, ${numberText(jobs.running)} running)
17034
+ `
17035
+ );
17036
+ return;
17037
+ }
16422
17038
  if (command === "upload" && isUploadBatch(data)) {
16423
17039
  if (data.successes.length > 0) {
16424
17040
  opts.stdout(data.successes.length === 1 ? "Upload complete\n" : "Uploads complete\n");
@@ -16448,7 +17064,7 @@ Next:
16448
17064
  }
16449
17065
  return;
16450
17066
  }
16451
- if ((command === "jobs wait" || command === "upload") && isRecord2(data)) {
17067
+ if ((command === "jobs wait" || command === "upload") && isRecord3(data)) {
16452
17068
  if (typeof data.transcriptId === "string") {
16453
17069
  opts.stdout("Transcription ready\n");
16454
17070
  opts.stdout(` transcriptId: ${data.transcriptId}
@@ -16469,10 +17085,10 @@ Next:
16469
17085
  }
16470
17086
  return;
16471
17087
  }
16472
- if (command === "schema" && isRecord2(data) && Array.isArray(data.commands)) {
17088
+ if (command === "schema" && isRecord3(data) && Array.isArray(data.commands)) {
16473
17089
  opts.stdout("Commands:\n");
16474
17090
  for (const entry of data.commands) {
16475
- if (!isRecord2(entry) || typeof entry.name !== "string") continue;
17091
+ if (!isRecord3(entry) || typeof entry.name !== "string") continue;
16476
17092
  const summary = typeof entry.summary === "string" ? ` \u2014 ${entry.summary}` : "";
16477
17093
  opts.stdout(` ${entry.name}${summary}
16478
17094
  `);
@@ -16562,7 +17178,7 @@ function renderTranscriptHuman(data, opts) {
16562
17178
  const segments = Array.isArray(data.segments) ? data.segments : [];
16563
17179
  let printedBody = false;
16564
17180
  for (const segment of segments) {
16565
- if (!isRecord2(segment) || typeof segment.text !== "string") continue;
17181
+ if (!isRecord3(segment) || typeof segment.text !== "string") continue;
16566
17182
  const clock = typeof segment.startMs === "number" ? `[${formatClock(segment.startMs / 1e3)}] ` : "";
16567
17183
  const speaker = typeof segment.speaker === "string" ? `${segment.speaker}: ` : "";
16568
17184
  opts.stdout(`${clock}${speaker}${segment.text}
@@ -16574,7 +17190,7 @@ function renderTranscriptHuman(data, opts) {
16574
17190
  `);
16575
17191
  printedBody = true;
16576
17192
  }
16577
- const summary = isRecord2(data.summary) ? data.summary : void 0;
17193
+ const summary = isRecord3(data.summary) ? data.summary : void 0;
16578
17194
  if (!summary || summary.status !== "succeeded") return;
16579
17195
  if (typeof summary.tldr === "string" && summary.tldr.length > 0) {
16580
17196
  opts.stdout(`
@@ -16592,13 +17208,44 @@ Summary:
16592
17208
  if (Array.isArray(summary.actionItems) && summary.actionItems.length > 0) {
16593
17209
  opts.stdout("\nAction items:\n");
16594
17210
  for (const item of summary.actionItems) {
16595
- if (!isRecord2(item) || typeof item.what !== "string") continue;
17211
+ if (!isRecord3(item) || typeof item.what !== "string") continue;
16596
17212
  const who = typeof item.who === "string" ? `${item.who}: ` : "";
16597
17213
  opts.stdout(` - ${who}${item.what}
16598
17214
  `);
16599
17215
  }
16600
17216
  }
16601
17217
  }
17218
+ function recordingLabel(item) {
17219
+ const id = typeof item.recordingId === "string" ? item.recordingId : "unknown";
17220
+ const status = typeof item.status === "string" ? item.status : "unknown";
17221
+ const duration3 = typeof item.durationMs === "number" ? ` \xB7 ${formatDurationMs(item.durationMs)}` : "";
17222
+ return `${recordingTitle(item)} (${status}, ${id}${duration3})`;
17223
+ }
17224
+ function recordingTitle(item) {
17225
+ for (const key of ["title", "summaryTitle"]) {
17226
+ const value = item[key];
17227
+ if (typeof value === "string" && value.trim()) return value.trim();
17228
+ }
17229
+ return "Untitled recording";
17230
+ }
17231
+ function numberText(value) {
17232
+ return typeof value === "number" && Number.isFinite(value) ? value.toLocaleString("en-US") : "0";
17233
+ }
17234
+ function formatDurationMs(ms) {
17235
+ return formatClock(ms / 1e3);
17236
+ }
17237
+ function formatBytes(bytes) {
17238
+ if (bytes < 1024) return `${bytes} B`;
17239
+ const units = ["KB", "MB", "GB", "TB"];
17240
+ let value = bytes / 1024;
17241
+ let unit = units[0];
17242
+ for (const next of units.slice(1)) {
17243
+ if (value < 1024) break;
17244
+ value /= 1024;
17245
+ unit = next;
17246
+ }
17247
+ return `${value >= 10 ? value.toFixed(0) : value.toFixed(1)} ${unit}`;
17248
+ }
16602
17249
  function formatClock(seconds) {
16603
17250
  const total = Math.max(0, Math.floor(seconds));
16604
17251
  const hours = Math.floor(total / 3600);
@@ -16627,7 +17274,7 @@ function applyFields(command, data, fields, compact) {
16627
17274
  };
16628
17275
  return compact ? compactData(filtered2) : filtered2;
16629
17276
  }
16630
- if (!isRecord2(data)) return data;
17277
+ if (!isRecord3(data)) return data;
16631
17278
  const allowed = new Set(Object.keys(data));
16632
17279
  assertKnownFields(fields, allowed);
16633
17280
  const filtered = pickFields(data, fields);
@@ -16652,7 +17299,7 @@ function compactData(value) {
16652
17299
  if (Array.isArray(value)) {
16653
17300
  return value.map(compactData).filter((item) => item !== void 0);
16654
17301
  }
16655
- if (isRecord2(value)) {
17302
+ if (isRecord3(value)) {
16656
17303
  const out = {};
16657
17304
  for (const [key, child] of Object.entries(value)) {
16658
17305
  const compacted = compactData(child);
@@ -16670,23 +17317,29 @@ function stableStringify(value, compact) {
16670
17317
  }
16671
17318
  function sortKeys(value) {
16672
17319
  if (Array.isArray(value)) return value.map(sortKeys);
16673
- if (!isRecord2(value)) return value;
17320
+ if (!isRecord3(value)) return value;
16674
17321
  return Object.fromEntries(
16675
17322
  Object.keys(value).sort().map((key) => [key, sortKeys(value[key])])
16676
17323
  );
16677
17324
  }
16678
- function isRecord2(value) {
17325
+ function isRecord3(value) {
16679
17326
  return typeof value === "object" && value !== null && !Array.isArray(value);
16680
17327
  }
16681
17328
  function isUploadBatch(value) {
16682
- return isRecord2(value) && Array.isArray(value.successes) && Array.isArray(value.failures);
17329
+ return isRecord3(value) && Array.isArray(value.successes) && Array.isArray(value.failures);
16683
17330
  }
16684
17331
 
16685
17332
  // src/schema.ts
16686
17333
  var COMMAND_DATA_SCHEMAS = {
17334
+ "auth login": authLoginDataSchema,
17335
+ "auth logout": authLogoutDataSchema,
17336
+ "auth import-macos": authImportDataSchema,
16687
17337
  "auth status": authStatusDataSchema,
16688
17338
  doctor: doctorDataSchema,
17339
+ "dashboard stats": dashboardStatsDataSchema,
16689
17340
  upload: uploadBatchDataSchema,
17341
+ "recordings get": recordingDataSchema,
17342
+ "recordings list": recordingListDataSchema,
16690
17343
  "jobs list": jobListDataSchema,
16691
17344
  "jobs wait": jobDataSchema,
16692
17345
  "transcript get": transcriptDataSchema
@@ -16814,12 +17467,22 @@ async function runCli(deps = {}) {
16814
17467
  renderSuccess("version", { version: CLI_VERSION }, render2);
16815
17468
  return 0;
16816
17469
  }
16817
- const auth = await resolveAuthContext({ origin: parsed.options.origin, env: deps.env });
16818
- const client = new RecappiApiClient(auth, { fetchImpl: deps.fetchImpl, sleep: deps.sleep });
17470
+ const auth = await resolveAuthContext({
17471
+ origin: parsed.options.origin,
17472
+ env: deps.env,
17473
+ homeDir: deps.homeDir
17474
+ });
17475
+ const client = new RecappiApiClient(auth, {
17476
+ fetchImpl: deps.fetchImpl,
17477
+ sleep: deps.sleep,
17478
+ env: deps.env
17479
+ });
16819
17480
  if (parsed.kind === "dashboard") {
16820
17481
  const runDashboard2 = deps.runDashboard ?? (await Promise.resolve().then(() => (init_tui(), tui_exports))).runDashboard;
16821
17482
  await runDashboard2({
16822
17483
  fetchJobs: () => client.listJobs({ status: "active", limit: 20 }),
17484
+ fetchRecordings: () => client.listRecordings({ limit: 20 }),
17485
+ fetchDashboardStats: () => client.dashboardStats(),
16823
17486
  fetchTranscript: (transcriptId) => client.getTranscript(transcriptId),
16824
17487
  initialView: parsed.initialView
16825
17488
  });
@@ -16830,6 +17493,44 @@ async function runCli(deps = {}) {
16830
17493
  renderSuccess("auth status", data, render2);
16831
17494
  return data.loggedIn ? 0 : 3;
16832
17495
  }
17496
+ if (parsed.kind === "auth-login") {
17497
+ const data = await loginWithDeviceCode({
17498
+ origin: auth.origin,
17499
+ homeDir: deps.homeDir,
17500
+ noOpen: parsed.noOpen,
17501
+ onPrompt: (message) => stderr(message),
17502
+ deps: {
17503
+ fetchImpl: deps.fetchImpl,
17504
+ openUrl: deps.openUrl,
17505
+ sleep: deps.sleep
17506
+ }
17507
+ });
17508
+ renderSuccess("auth login", data, render2);
17509
+ return 0;
17510
+ }
17511
+ if (parsed.kind === "auth-logout") {
17512
+ const cleared = await clearAuthConfig(deps.homeDir ?? os3.homedir());
17513
+ renderSuccess("auth logout", { loggedIn: false, origin: auth.origin, cleared }, render2);
17514
+ return 0;
17515
+ }
17516
+ if (parsed.kind === "auth-import-macos") {
17517
+ const keychain = await inspectMacOSAppKeychain({ env: deps.env });
17518
+ if (!keychain.token) {
17519
+ throw cliError("auth.not_logged_in", keychain.message, {
17520
+ hint: keychain.hint ?? "Run recappi auth login instead."
17521
+ });
17522
+ }
17523
+ await saveAuthConfig(deps.homeDir ?? os3.homedir(), {
17524
+ origin: auth.origin,
17525
+ token: keychain.token
17526
+ });
17527
+ renderSuccess(
17528
+ "auth import-macos",
17529
+ { imported: true, origin: auth.origin, source: "macos-keychain" },
17530
+ render2
17531
+ );
17532
+ return 0;
17533
+ }
16833
17534
  if (parsed.kind === "doctor") {
16834
17535
  const data = await client.doctor();
16835
17536
  renderSuccess("doctor", data, render2);
@@ -16879,6 +17580,25 @@ async function runCli(deps = {}) {
16879
17580
  renderSuccess("jobs list", data, render2);
16880
17581
  return 0;
16881
17582
  }
17583
+ if (parsed.kind === "recordings-list") {
17584
+ const data = await client.listRecordings({
17585
+ limit: parsed.limit,
17586
+ cursor: parsed.cursor,
17587
+ search: parsed.search
17588
+ });
17589
+ renderSuccess("recordings list", data, render2);
17590
+ return 0;
17591
+ }
17592
+ if (parsed.kind === "recordings-get") {
17593
+ const data = await client.getRecording(parsed.recordingId);
17594
+ renderSuccess("recordings get", data, render2);
17595
+ return 0;
17596
+ }
17597
+ if (parsed.kind === "dashboard-stats") {
17598
+ const data = await client.dashboardStats();
17599
+ renderSuccess("dashboard stats", data, render2);
17600
+ return 0;
17601
+ }
16882
17602
  if (parsed.kind === "transcript-get") {
16883
17603
  const data = await client.getTranscript(parsed.transcriptId);
16884
17604
  renderSuccess("transcript get", data, render2);
@@ -17027,6 +17747,34 @@ Agent mode:
17027
17747
  addCommonOptions(program);
17028
17748
  const auth = program.command("auth").description("Authentication commands");
17029
17749
  addCommonOptions(auth);
17750
+ const authLogin = auth.command("login").description("Sign in to Recappi Cloud with a device code").option("--no-open", "print the device URL without opening a browser");
17751
+ addCommonOptions(authLogin);
17752
+ authLogin.action((opts, command) => {
17753
+ onSelect({
17754
+ kind: "auth-login",
17755
+ options: collectGlobalOptions(command),
17756
+ commandName: "auth login",
17757
+ ...opts.noOpen === true ? { noOpen: true } : {}
17758
+ });
17759
+ });
17760
+ const authLogout = auth.command("logout").description("Remove the Recappi CLI sign-in token");
17761
+ addCommonOptions(authLogout);
17762
+ authLogout.action((_options, command) => {
17763
+ onSelect({
17764
+ kind: "auth-logout",
17765
+ options: collectGlobalOptions(command),
17766
+ commandName: "auth logout"
17767
+ });
17768
+ });
17769
+ const authImportMacOS = auth.command("import-macos").description("Copy the Recappi Mini macOS app session into CLI config");
17770
+ addCommonOptions(authImportMacOS);
17771
+ authImportMacOS.action((_options, command) => {
17772
+ onSelect({
17773
+ kind: "auth-import-macos",
17774
+ options: collectGlobalOptions(command),
17775
+ commandName: "auth import-macos"
17776
+ });
17777
+ });
17030
17778
  const authStatus = auth.command("status").description("Show Recappi Cloud sign-in status");
17031
17779
  addCommonOptions(authStatus);
17032
17780
  authStatus.action((_options, command) => {
@@ -17072,6 +17820,44 @@ Agent mode:
17072
17820
  document: buildSchemaDocument(program)
17073
17821
  });
17074
17822
  });
17823
+ const dashboard = program.command("dashboard").description("Dashboard data commands");
17824
+ addCommonOptions(dashboard);
17825
+ const dashboardStats = dashboard.command("stats").description("Fetch dashboard counters");
17826
+ addCommonOptions(dashboardStats);
17827
+ dashboardStats.action((_options, command) => {
17828
+ onSelect({
17829
+ kind: "dashboard-stats",
17830
+ options: collectGlobalOptions(command),
17831
+ commandName: "dashboard stats"
17832
+ });
17833
+ });
17834
+ const recordings = program.command("recordings").description("Recording commands");
17835
+ addCommonOptions(recordings);
17836
+ const recordingsList = recordings.command("list").description("List recent recordings").option("--limit <n>", "number of recordings to show", parseLimitOption("--limit", 1, 100), 20).option("--cursor <cursor>", "pagination cursor", parseStringOption("--cursor")).option("--search <query>", "search recordings and transcripts", parseStringOption("--search"));
17837
+ addCommonOptions(recordingsList);
17838
+ recordingsList.action((_options, command) => {
17839
+ const opts = command.opts();
17840
+ onSelect({
17841
+ kind: "recordings-list",
17842
+ options: collectGlobalOptions(command),
17843
+ commandName: "recordings list",
17844
+ limit: opts.limit ?? 20,
17845
+ ...typeof opts.cursor === "string" ? { cursor: opts.cursor } : {},
17846
+ ...typeof opts.search === "string" ? { search: opts.search } : {}
17847
+ });
17848
+ });
17849
+ const recordingsGet = recordings.command("get <recordingId>").description("Fetch a recording by recording id");
17850
+ addCommonOptions(recordingsGet);
17851
+ recordingsGet.action(
17852
+ (recordingId, _options, command) => {
17853
+ onSelect({
17854
+ kind: "recordings-get",
17855
+ options: collectGlobalOptions(command),
17856
+ commandName: "recordings get",
17857
+ recordingId
17858
+ });
17859
+ }
17860
+ );
17075
17861
  const transcript = program.command("transcript").description("Transcript commands");
17076
17862
  addCommonOptions(transcript);
17077
17863
  const transcriptGet = transcript.command("get <transcriptId>").description("Fetch a transcript by transcript id");
@@ -17186,7 +17972,9 @@ var VALUE_OPTIONS = /* @__PURE__ */ new Set([
17186
17972
  "--provider",
17187
17973
  "--prompt",
17188
17974
  "--status",
17189
- "--limit"
17975
+ "--limit",
17976
+ "--cursor",
17977
+ "--search"
17190
17978
  ]);
17191
17979
  function hasCommandToken(argv) {
17192
17980
  return commandTokens(argv).length > 0;