recappi 0.1.45 → 0.1.46

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
@@ -59,10 +59,19 @@ function applyRecordingEventToTelemetry(telemetry, event) {
59
59
  }
60
60
  if (event.type === "audio.level") {
61
61
  const level = levelFromRmsDb(event.rmsDb);
62
+ const levelDb3 = typeof event.rmsDb === "number" && Number.isFinite(event.rmsDb) ? event.rmsDb : void 0;
62
63
  if (event.input === "microphone") {
63
- return { ...telemetry, level: { ...telemetry.level, mic: level } };
64
+ return {
65
+ ...telemetry,
66
+ level: { ...telemetry.level, mic: level },
67
+ levelDb: { ...telemetry.levelDb, ...levelDb3 != null ? { mic: levelDb3 } : {} }
68
+ };
64
69
  }
65
- return { ...telemetry, level: { ...telemetry.level, system: level } };
70
+ return {
71
+ ...telemetry,
72
+ level: { ...telemetry.level, system: level },
73
+ levelDb: { ...telemetry.levelDb, ...levelDb3 != null ? { system: levelDb3 } : {} }
74
+ };
66
75
  }
67
76
  if (event.type === "error") {
68
77
  if (isLiveCaptionSidecarError(event.code)) return telemetry;
@@ -202,10 +211,6 @@ function jobDetail(item) {
202
211
  if (item.status === "failed") return "failed";
203
212
  return "";
204
213
  }
205
- function padCell(text, width) {
206
- if (text.length > width) return `${text.slice(0, Math.max(0, width - 1))}\u2026`;
207
- return text.padEnd(width);
208
- }
209
214
  function charWidth(code) {
210
215
  if (code >= 4352 && code <= 4447 || // Hangul Jamo
211
216
  code === 9001 || code === 9002 || code >= 11904 && code <= 12350 || // CJK radicals … Kangxi
@@ -228,23 +233,6 @@ function displayWidth(text) {
228
233
  for (const ch of text) width += charWidth(ch.codePointAt(0) ?? 0);
229
234
  return width;
230
235
  }
231
- function padDisplay(text, width) {
232
- const w = displayWidth(text);
233
- if (w === width) return text;
234
- if (w < width) return text + " ".repeat(width - w);
235
- let out = "";
236
- let acc = 0;
237
- for (const ch of text) {
238
- const cw = charWidth(ch.codePointAt(0) ?? 0);
239
- if (acc + cw > width - 1) break;
240
- out += ch;
241
- acc += cw;
242
- }
243
- out += "\u2026";
244
- acc += 1;
245
- if (acc < width) out += " ".repeat(width - acc);
246
- return out;
247
- }
248
236
  function countJobs(items) {
249
237
  const counts = {
250
238
  total: items.length,
@@ -617,6 +605,43 @@ function waveform(samples, width) {
617
605
  });
618
606
  return "\u2581".repeat(Math.max(0, pad)) + cells.join("");
619
607
  }
608
+ function levelDb(level) {
609
+ if (level <= 0.03) return "silent";
610
+ return `${Math.round(level * 60 - 60)} dB`;
611
+ }
612
+ function MeterRow({
613
+ label,
614
+ samples,
615
+ level,
616
+ paused,
617
+ width
618
+ }) {
619
+ const silent = level <= 0.03;
620
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
621
+ /* @__PURE__ */ jsx3(Box2, { width: 9, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: label }) }),
622
+ /* @__PURE__ */ jsx3(Box2, { width, children: /* @__PURE__ */ jsx3(Text2, { color: paused ? "gray" : silent ? "yellow" : "red", children: waveform(samples, width) }) }),
623
+ /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: ` ${paused ? "paused" : levelDb(level)}` })
624
+ ] });
625
+ }
626
+ function ProgressBar({ fraction, width = 12 }) {
627
+ const f = Math.max(0, Math.min(1, fraction));
628
+ const filled = Math.round(f * width);
629
+ return /* @__PURE__ */ jsxs2(Text2, { color: "cyan", children: [
630
+ "\u2593".repeat(filled),
631
+ /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "\u2591".repeat(Math.max(0, width - filled)) })
632
+ ] });
633
+ }
634
+ function stoppedPhase(artifact) {
635
+ if (!artifact) return null;
636
+ if (artifact.uploadStatus === "uploading") {
637
+ return { label: "Uploading to Recappi Cloud", fraction: artifact.uploadProgress };
638
+ }
639
+ if (artifact.uploadStatus === "queued") return { label: "Queued to upload" };
640
+ if (artifact.transcriptionStatus === "processing") {
641
+ return { label: "Transcribing", fraction: artifact.transcriptionProgress };
642
+ }
643
+ return null;
644
+ }
620
645
  function RecordingHeroScreen({
621
646
  telemetry,
622
647
  artifact,
@@ -627,11 +652,12 @@ function RecordingHeroScreen({
627
652
  }) {
628
653
  const size = useTerminalSize();
629
654
  const [tick, setTick] = useState3(() => now());
630
- const [wave, setWave] = useState3([]);
655
+ const [waveSys, setWaveSys] = useState3([]);
656
+ const [waveMic, setWaveMic] = useState3([]);
631
657
  useEffect2(() => {
632
658
  if (telemetry.level == null) return;
633
- const lvl = Math.max(telemetry.level.system ?? 0, telemetry.level.mic ?? 0);
634
- setWave((w) => [...w.slice(-512), lvl]);
659
+ setWaveSys((w) => [...w.slice(-256), telemetry.level.system ?? 0]);
660
+ setWaveMic((w) => [...w.slice(-256), telemetry.level.mic ?? 0]);
635
661
  }, [telemetry.level]);
636
662
  useEffect2(() => {
637
663
  const id = setInterval(() => setTick(now()), 1e3);
@@ -641,17 +667,27 @@ function RecordingHeroScreen({
641
667
  const innerWidth = Math.max(10, size.columns - 4);
642
668
  if (telemetry.status === "stopped") {
643
669
  const handoff = stoppedHandoffCopy(artifact, canTranscribe);
670
+ const phase = stoppedPhase(artifact);
644
671
  const meta3 = [
645
672
  telemetry.durationMs != null ? formatClockMs(telemetry.durationMs) : null,
646
673
  formatBytes2(telemetry.sizeBytes) || null
647
674
  ].filter(Boolean).join(" \xB7 ");
675
+ const saved = artifact?.uploadStatus === "uploaded" ? "\u2713 Saved to Recappi Cloud" : "\u2713 Saved to your Mac";
648
676
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
649
677
  /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "recappi \xB7 Recording" }),
650
678
  /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
651
- /* @__PURE__ */ jsx3(Text2, { color: "green", children: "\u2713 Saved to your Mac" }),
679
+ /* @__PURE__ */ jsx3(Text2, { color: "green", children: saved }),
652
680
  meta3 ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: meta3 }) : null,
653
681
  telemetry.savedPath ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate-middle", children: telemetry.savedPath }) : null
654
682
  ] }),
683
+ phase ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, children: [
684
+ /* @__PURE__ */ jsx3(Text2, { color: "cyan", children: `\u25D0 ${phase.label}` }),
685
+ phase.fraction != null ? /* @__PURE__ */ jsxs2(Fragment, { children: [
686
+ /* @__PURE__ */ jsx3(Text2, { children: " " }),
687
+ /* @__PURE__ */ jsx3(ProgressBar, { fraction: phase.fraction }),
688
+ /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: ` ${Math.round(phase.fraction * 100)}%` })
689
+ ] }) : /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "\u2026" })
690
+ ] }) : null,
655
691
  /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
656
692
  /* @__PURE__ */ jsx3(Text2, { color: handoff.tone === "red" ? "red" : handoff.tone === "green" ? "green" : void 0, dimColor: handoff.tone === "dim", children: handoff.text }),
657
693
  artifact?.error ? /* @__PURE__ */ jsx3(Text2, { color: "red", wrap: "truncate-end", children: artifact.error }) : null
@@ -668,24 +704,33 @@ function RecordingHeroScreen({
668
704
  const paused = telemetry.status === "paused";
669
705
  const starting = telemetry.status === "starting" || telemetry.status === "stopping";
670
706
  const badge = paused ? "\u23F8 PAUSED" : starting ? "\u2026" : "\u23FA REC";
707
+ const meterW = Math.max(10, Math.min(48, innerWidth - 22));
708
+ const sizeStr = telemetry.sizeBytes ? formatBytes2(telemetry.sizeBytes) : "";
709
+ const context = [telemetry.sourceLabel, telemetry.micEnabled ? "Microphone" : null, sizeStr || null].filter(Boolean).join(" \xB7 ");
671
710
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, height: size.rows, children: [
672
711
  /* @__PURE__ */ jsxs2(Text2, { children: [
673
712
  /* @__PURE__ */ jsx3(Text2, { bold: true, color: "green", children: "recappi" }),
674
713
  /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: " \xB7 Recording" })
675
714
  ] }),
676
- /* @__PURE__ */ jsxs2(Box2, { flexGrow: 1, flexDirection: "column", justifyContent: "center", alignItems: "center", children: [
677
- /* @__PURE__ */ jsx3(Text2, { bold: true, color: paused ? "yellow" : "red", children: badge }),
678
- /* @__PURE__ */ jsx3(Text2, { bold: true, children: elapsed }),
679
- /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: telemetry.level == null ? (
680
- // No level telemetry yet show honest activity, not a flat meter that
681
- // looks like silence (the elapsed timer above proves it's live).
715
+ /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, paddingX: 1, flexGrow: 1, flexDirection: "column", children: [
716
+ /* @__PURE__ */ jsxs2(Text2, { children: [
717
+ /* @__PURE__ */ jsx3(Text2, { bold: true, color: paused ? "yellow" : "red", children: badge }),
718
+ /* @__PURE__ */ jsx3(Text2, { children: " " }),
719
+ /* @__PURE__ */ jsx3(Text2, { bold: true, children: elapsed })
720
+ ] }),
721
+ /* @__PURE__ */ jsx3(Box2, { marginTop: 1, flexDirection: "column", children: telemetry.level == null ? (
722
+ // No level telemetry yet — honest activity, not a flat meter that
723
+ // reads as silence (the elapsed timer above proves it's live).
682
724
  /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: paused ? "Paused" : `Capturing audio${".".repeat(Math.floor(tick / 1e3) % 3 + 1)}` })
683
- ) : /* @__PURE__ */ jsx3(Text2, { color: paused ? "gray" : "red", children: waveform(wave, innerWidth) }) }),
684
- /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
685
- telemetry.sourceLabel,
686
- telemetry.micEnabled ? " + Microphone" : ""
725
+ ) : /* @__PURE__ */ jsxs2(Fragment, { children: [
726
+ /* @__PURE__ */ jsx3(MeterRow, { label: "System", samples: waveSys, level: telemetry.level.system ?? 0, paused, width: meterW }),
727
+ telemetry.micEnabled ? /* @__PURE__ */ jsx3(MeterRow, { label: "Mic", samples: waveMic, level: telemetry.level.mic ?? 0, paused, width: meterW }) : null
687
728
  ] }) }),
688
- captions ? /* @__PURE__ */ jsx3(Box2, { marginTop: 1, flexDirection: "column", alignItems: "center", width: innerWidth, children: /* @__PURE__ */ jsx3(HeroCaptions, { state: captions }) }) : null
729
+ /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: context }) }),
730
+ captions ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
731
+ /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "LIVE CAPTIONS" }),
732
+ /* @__PURE__ */ jsx3(HeroCaptions, { state: captions })
733
+ ] }) : null
689
734
  ] }),
690
735
  /* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
691
736
  "q stop & save",
@@ -702,7 +747,7 @@ function HeroCaptions({ state }) {
702
747
  return /* @__PURE__ */ jsx3(Text2, { color: captionError ? "yellow" : void 0, dimColor: !captionError, wrap: "truncate-end", children: captionError ?? (state.status === "live" ? "Listening for speech\u2026" : liveCaptionStatusLabel(state.status)) });
703
748
  }
704
749
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
705
- recent.map((line) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", alignItems: "center", children: [
750
+ recent.map((line) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
706
751
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate-end", children: [
707
752
  line.speaker ? `${line.speaker}: ` : "",
708
753
  line.text
@@ -715,11 +760,8 @@ function HeroCaptions({ state }) {
715
760
  ] });
716
761
  }
717
762
  function stoppedHandoffCopy(artifact, canTranscribe) {
718
- if (artifact?.uploadStatus === "uploading") {
719
- return { text: "Uploading\u2026", tone: "normal" };
720
- }
721
- if (artifact?.transcriptionStatus === "processing") {
722
- return { text: "Transcribing\u2026", tone: "normal" };
763
+ if (artifact?.uploadStatus === "uploading" || artifact?.transcriptionStatus === "processing") {
764
+ return { text: "esc run in background", tone: "dim" };
723
765
  }
724
766
  if (artifact?.transcriptionStatus === "queued") {
725
767
  return { text: "Transcription queued \xB7 \u23CE open recording \xB7 n not now", tone: "green" };
@@ -763,13 +805,16 @@ function AccountView({ status }) {
763
805
  function AccountBody({ status }) {
764
806
  return /* @__PURE__ */ jsxs3(Fragment2, { children: [
765
807
  /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
766
- /* @__PURE__ */ jsx5(Text3, { bold: true, color: "green", children: status.email ?? status.userId ?? "Signed in" }),
808
+ /* @__PURE__ */ jsxs3(Text3, { children: [
809
+ /* @__PURE__ */ jsx5(Text3, { color: "green", children: "\u25CF " }),
810
+ /* @__PURE__ */ jsx5(Text3, { bold: true, children: status.email ?? status.userId ?? "Signed in" })
811
+ ] }),
767
812
  status.email && status.userId ? /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: status.userId }) : null,
768
813
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: `origin ${status.origin}` })
769
814
  ] }),
770
815
  status.billing ? /* @__PURE__ */ jsx5(Usage, { billing: status.billing }) : null,
771
816
  /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
772
- /* @__PURE__ */ jsx5(Text3, { bold: true, children: "Local store" }),
817
+ /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "LOCAL STORE" }),
773
818
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, wrap: "truncate-middle", children: status.localStore.path }),
774
819
  /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
775
820
  `${status.localStore.accountScopedArtifacts} artifact${status.localStore.accountScopedArtifacts === 1 ? "" : "s"} for this account`,
@@ -783,6 +828,7 @@ function Usage({ billing }) {
783
828
  const minutesUsed = billing.minutesUsed;
784
829
  const storageCap = billing.storageCapBytes;
785
830
  return /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
831
+ /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "USAGE" }),
786
832
  /* @__PURE__ */ jsxs3(Text3, { children: [
787
833
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Plan " }),
788
834
  /* @__PURE__ */ jsx5(Text3, { bold: true, children: billing.tier })
@@ -883,9 +929,10 @@ function JobRow({
883
929
  const glyph = statusGlyph(item.status, spinnerFrame);
884
930
  const title = item.recording?.title ?? item.recordingId;
885
931
  return /* @__PURE__ */ jsxs5(Box5, { children: [
886
- /* @__PURE__ */ jsx7(Text5, { color: "cyan", children: selected ? "\u25B8 " : " " }),
887
- /* @__PURE__ */ jsx7(Text5, { color: style.color, children: `${glyph} ${padCell(style.label, 13)}` }),
888
- /* @__PURE__ */ jsx7(Text5, { bold: selected, children: padCell(title, 24) }),
932
+ /* @__PURE__ */ jsx7(Box5, { width: 3, children: /* @__PURE__ */ jsx7(Text5, { color: "cyan", children: selected ? "\u25B8" : "" }) }),
933
+ /* @__PURE__ */ jsx7(Box5, { width: 2, children: /* @__PURE__ */ jsx7(Text5, { color: style.color, children: glyph }) }),
934
+ /* @__PURE__ */ jsx7(Box5, { width: 13, children: /* @__PURE__ */ jsx7(Text5, { color: style.color, children: style.label }) }),
935
+ /* @__PURE__ */ jsx7(Box5, { width: 26, children: /* @__PURE__ */ jsx7(Text5, { bold: selected, wrap: "truncate-end", children: title }) }),
889
936
  /* @__PURE__ */ jsx7(Text5, { dimColor: !selected, children: jobDetail(item) })
890
937
  ] });
891
938
  }
@@ -946,7 +993,7 @@ function recordingLayout(columns) {
946
993
  const showWhen = usable >= 54;
947
994
  const title = Math.max(
948
995
  10,
949
- usable - MARKER_W - GLYPH_W - LENGTH_W - (showWhen ? WHEN_W : 0)
996
+ usable - MARKER_W - GLYPH_W - LENGTH_W - (showWhen ? WHEN_W : 0) - DL_W
950
997
  );
951
998
  return { title, showWhen };
952
999
  }
@@ -963,33 +1010,25 @@ function RecordingRow({
963
1010
  const { glyph, color } = recordingProcessingState(item, jobStatus, spinnerFrame);
964
1011
  const duration3 = item.durationMs ? formatClockMs(item.durationMs) : "\u2014";
965
1012
  return /* @__PURE__ */ jsxs6(Box7, { children: [
966
- /* @__PURE__ */ jsx9(Text7, { color: "cyan", children: selected ? "\u25B8 " : " " }),
967
- /* @__PURE__ */ jsx9(Text7, { color, children: `${glyph} ` }),
968
- /* @__PURE__ */ jsx9(Text7, { bold: selected, children: padDisplay(recordingTitle2(item), title) }),
969
- /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay(duration3, LENGTH_W) }),
970
- showWhen ? /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay(formatAge(item.createdAt, nowMs), WHEN_W) }) : null,
971
- /* @__PURE__ */ jsx9(Text7, { color: "green", children: downloaded ? " \u2913" : " " })
1013
+ /* @__PURE__ */ jsx9(Box7, { width: MARKER_W, children: /* @__PURE__ */ jsx9(Text7, { color: "cyan", children: selected ? "\u25B8" : "" }) }),
1014
+ /* @__PURE__ */ jsx9(Box7, { width: GLYPH_W, children: /* @__PURE__ */ jsx9(Text7, { color, children: glyph }) }),
1015
+ /* @__PURE__ */ jsx9(Box7, { width: title, children: /* @__PURE__ */ jsx9(Text7, { bold: selected, wrap: "truncate-end", children: recordingTitle2(item) }) }),
1016
+ /* @__PURE__ */ jsx9(Box7, { width: LENGTH_W, justifyContent: "flex-end", children: /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: duration3 }) }),
1017
+ showWhen ? /* @__PURE__ */ jsx9(Box7, { width: WHEN_W, justifyContent: "flex-end", children: /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: formatAge(item.createdAt, nowMs) }) }) : null,
1018
+ /* @__PURE__ */ jsx9(Box7, { width: DL_W, justifyContent: "flex-end", children: /* @__PURE__ */ jsx9(Text7, { color: "green", children: downloaded ? "\u2913" : "" }) })
972
1019
  ] });
973
1020
  }
974
- function RecordingHeader({ columns }) {
975
- const { title, showWhen } = recordingLayout(columns);
976
- return /* @__PURE__ */ jsxs6(Box7, { children: [
977
- /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay("", MARKER_W + GLYPH_W) }),
978
- /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay("TITLE", title) }),
979
- /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay("LENGTH", LENGTH_W) }),
980
- showWhen ? /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: "WHEN" }) : null
981
- ] });
982
- }
983
- var UUID_RE, MARKER_W, GLYPH_W, LENGTH_W, WHEN_W;
1021
+ var UUID_RE, MARKER_W, GLYPH_W, LENGTH_W, WHEN_W, DL_W;
984
1022
  var init_RecordingRow = __esm({
985
1023
  "src/tui/RecordingRow.tsx"() {
986
1024
  "use strict";
987
1025
  init_format();
988
1026
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
989
- MARKER_W = 2;
1027
+ MARKER_W = 3;
990
1028
  GLYPH_W = 2;
991
- LENGTH_W = 9;
1029
+ LENGTH_W = 8;
992
1030
  WHEN_W = 9;
1031
+ DL_W = 3;
993
1032
  }
994
1033
  });
995
1034
 
@@ -1009,28 +1048,25 @@ function RecordingsView({
1009
1048
  if (items.length === 0) {
1010
1049
  return /* @__PURE__ */ jsx10(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "No recordings yet \u2014 run: recappi upload <file>" }) });
1011
1050
  }
1012
- return /* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "column", children: [
1013
- /* @__PURE__ */ jsx10(RecordingHeader, { columns }),
1014
- items.map((item, index) => {
1015
- const bucket = dateBucket(item.createdAt, nowMs);
1016
- const showHeader = index === 0 || bucket !== dateBucket(items[index - 1].createdAt, nowMs);
1017
- return /* @__PURE__ */ jsxs7(React5.Fragment, { children: [
1018
- showHeader ? /* @__PURE__ */ jsx10(Box8, { marginTop: index === 0 ? 0 : 1, children: /* @__PURE__ */ jsx10(Text8, { bold: true, color: "blue", children: bucket }) }) : null,
1019
- /* @__PURE__ */ jsx10(
1020
- RecordingRow,
1021
- {
1022
- item,
1023
- selected: index === selectedIndex,
1024
- nowMs,
1025
- columns,
1026
- jobStatus: jobStatusByRecording?.get(item.recordingId),
1027
- downloaded: downloadedRecordingIds?.has(item.recordingId) ?? false,
1028
- spinnerFrame
1029
- }
1030
- )
1031
- ] }, item.recordingId);
1032
- })
1033
- ] });
1051
+ return /* @__PURE__ */ jsx10(Box8, { marginTop: 1, flexDirection: "column", children: items.map((item, index) => {
1052
+ const bucket = dateBucket(item.createdAt, nowMs);
1053
+ const showHeader = index === 0 || bucket !== dateBucket(items[index - 1].createdAt, nowMs);
1054
+ return /* @__PURE__ */ jsxs7(React5.Fragment, { children: [
1055
+ showHeader ? /* @__PURE__ */ jsx10(Box8, { marginTop: index === 0 ? 0 : 1, children: /* @__PURE__ */ jsx10(Text8, { bold: true, dimColor: true, children: bucket }) }) : null,
1056
+ /* @__PURE__ */ jsx10(
1057
+ RecordingRow,
1058
+ {
1059
+ item,
1060
+ selected: index === selectedIndex,
1061
+ nowMs,
1062
+ columns,
1063
+ jobStatus: jobStatusByRecording?.get(item.recordingId),
1064
+ downloaded: downloadedRecordingIds?.has(item.recordingId) ?? false,
1065
+ spinnerFrame
1066
+ }
1067
+ )
1068
+ ] }, item.recordingId);
1069
+ }) });
1034
1070
  }
1035
1071
  var init_RecordingsView = __esm({
1036
1072
  "src/tui/RecordingsView.tsx"() {
@@ -1060,13 +1096,14 @@ function PeekBody({
1060
1096
  const meta3 = [
1061
1097
  item.durationMs ? formatClockMs(item.durationMs) : null,
1062
1098
  formatBytes2(item.sizeBytes) || null,
1063
- item.contentType || null
1064
- ].filter(Boolean).join(" \xB7 ");
1099
+ formatAge(item.createdAt, nowMs)
1100
+ ].filter(Boolean).join(" \xB7 ");
1065
1101
  return /* @__PURE__ */ jsxs8(Fragment3, { children: [
1066
- /* @__PURE__ */ jsx11(Text9, { bold: true, color: "green", wrap: "truncate-end", children: recordingTitle2(item) }),
1067
- /* @__PURE__ */ jsx11(Text9, { color: style.color, children: `${style.glyph} ${style.label}` }),
1068
- meta3 ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: meta3 }) : null,
1069
- /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: formatAge(item.createdAt, nowMs) }),
1102
+ /* @__PURE__ */ jsx11(Text9, { bold: true, wrap: "truncate-end", children: recordingTitle2(item) }),
1103
+ /* @__PURE__ */ jsxs8(Box9, { children: [
1104
+ /* @__PURE__ */ jsx11(Text9, { color: style.color, children: `${style.glyph} ${style.label}` }),
1105
+ meta3 ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: ` ${meta3}` }) : null
1106
+ ] }),
1070
1107
  /* @__PURE__ */ jsx11(Box9, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(SummarySection, { item, summary }) }),
1071
1108
  /* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "\u23CE open \xB7 t transcript \xB7 o web" }) })
1072
1109
  ] });
@@ -1083,7 +1120,7 @@ function SummarySection({
1083
1120
  }
1084
1121
  const points = (summary.keyPoints ?? []).slice(0, 3);
1085
1122
  return /* @__PURE__ */ jsxs8(Fragment3, { children: [
1086
- /* @__PURE__ */ jsx11(Text9, { bold: true, children: "Summary" }),
1123
+ /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "SUMMARY" }),
1087
1124
  /* @__PURE__ */ jsx11(Text9, { children: summary.tldr }),
1088
1125
  points.length > 0 ? /* @__PURE__ */ jsx11(Box9, { marginTop: 1, flexDirection: "column", children: points.map((point, i) => /* @__PURE__ */ jsx11(Text9, { dimColor: true, wrap: "truncate-end", children: `\u2022 ${point}` }, i)) }) : null
1089
1126
  ] });
@@ -1098,7 +1135,7 @@ var init_RecordingPeek = __esm({
1098
1135
 
1099
1136
  // src/tui/OverviewView.tsx
1100
1137
  import { Box as Box10, Text as Text10 } from "ink";
1101
- import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1138
+ import { Fragment as Fragment4, jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1102
1139
  function OverviewView({
1103
1140
  recordings,
1104
1141
  jobs,
@@ -1119,12 +1156,21 @@ function OverviewView({
1119
1156
  const queued = stats?.jobs.queued ?? jobCounts.queued;
1120
1157
  return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
1121
1158
  /* @__PURE__ */ jsxs9(Box10, { children: [
1122
- /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: "Recordings " }),
1123
1159
  /* @__PURE__ */ jsx12(Text10, { bold: true, children: stats?.recordings.total ?? recordings.length }),
1124
- stats?.recordings.ready != null ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` \xB7 ${stats.recordings.ready} ready` }) : null,
1125
- stats?.recordings.totalDurationMs != null ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` \xB7 ${formatClockMs(stats.recordings.totalDurationMs)} transcribed` }) : null,
1126
- running > 0 ? /* @__PURE__ */ jsx12(Text10, { color: "cyan", children: ` \xB7 ${running} transcribing` }) : null,
1127
- queued > 0 ? /* @__PURE__ */ jsx12(Text10, { color: "yellow", children: ` \xB7 ${queued} queued` }) : null
1160
+ /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " recordings" }),
1161
+ stats?.recordings.ready != null ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
1162
+ /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
1163
+ /* @__PURE__ */ jsx12(Text10, { color: "green", children: `${stats.recordings.ready} ready` })
1164
+ ] }) : null,
1165
+ running > 0 ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
1166
+ /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
1167
+ /* @__PURE__ */ jsx12(Text10, { color: "cyan", children: `${running} transcribing` })
1168
+ ] }) : null,
1169
+ queued > 0 ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
1170
+ /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: " \xB7 " }),
1171
+ /* @__PURE__ */ jsx12(Text10, { color: "yellow", children: `${queued} queued` })
1172
+ ] }) : null,
1173
+ stats?.recordings.totalDurationMs != null ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` \xB7 ${formatClockMs(stats.recordings.totalDurationMs)} transcribed` }) : null
1128
1174
  ] }),
1129
1175
  /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", alignItems: "flex-start", children: [
1130
1176
  /* @__PURE__ */ jsx12(Box10, { flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ jsx12(
@@ -1133,7 +1179,7 @@ function OverviewView({
1133
1179
  items: recordings,
1134
1180
  selectedIndex,
1135
1181
  nowMs,
1136
- columns,
1182
+ columns: showPeek ? Math.max(20, columns - peekWidth - 1) : columns,
1137
1183
  jobStatusByRecording,
1138
1184
  downloadedRecordingIds,
1139
1185
  spinnerFrame
@@ -1295,7 +1341,7 @@ var init_JobDetailView = __esm({
1295
1341
  // src/tui/RecordingDetailView.tsx
1296
1342
  import React6, { useMemo as useMemo2, useState as useState4 } from "react";
1297
1343
  import { Box as Box12, Text as Text12, useInput as useInput3 } from "ink";
1298
- import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1344
+ import { Fragment as Fragment5, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1299
1345
  function RecordingDetailView({
1300
1346
  item,
1301
1347
  nowMs,
@@ -1360,14 +1406,14 @@ function RecordingDetailView({
1360
1406
  });
1361
1407
  return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 1, children: [
1362
1408
  /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "\u2039 Recordings" }),
1363
- /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { bold: true, color: "green", children: title }) }),
1409
+ /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { bold: true, children: title }) }),
1364
1410
  /* @__PURE__ */ jsxs11(Text12, { children: [
1365
1411
  /* @__PURE__ */ jsx14(Text12, { color: style.color, children: `${style.glyph} ${style.label}` }),
1366
1412
  /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${formatAge(item.createdAt, nowMs) || "\u2014"}` })
1367
1413
  ] }),
1368
1414
  meta3 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: meta3 }) : null,
1369
1415
  /* @__PURE__ */ jsx14(AudioActionRow, { item, audio }),
1370
- !item.activeTranscriptId ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Transcript not available yet" }) }) : transcript === "loading" || transcript === void 0 ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Loading\u2026" }) }) : transcript === "error" ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "(transcript unavailable)" }) }) : /* @__PURE__ */ jsxs11(Fragment4, { children: [
1416
+ !item.activeTranscriptId ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Transcript not available yet" }) }) : transcript === "loading" || transcript === void 0 ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Loading\u2026" }) }) : transcript === "error" ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "(transcript unavailable)" }) }) : /* @__PURE__ */ jsxs11(Fragment5, { children: [
1371
1417
  /* @__PURE__ */ jsx14(TabBar, { active: tab }),
1372
1418
  /* @__PURE__ */ jsx14(Box12, { marginTop: 1, flexDirection: "column", children: tab === "summary" ? /* @__PURE__ */ jsx14(SummaryPane, { summary, budget: paneBudget }) : tab === "chapters" ? /* @__PURE__ */ jsx14(ChaptersPane, { chapters, win: chapWin, selectedIndex: chapterSel }) : /* @__PURE__ */ jsx14(TranscriptPane, { segments, win: segWin }) })
1373
1419
  ] }),
@@ -1386,7 +1432,7 @@ function RecordingDetailView({
1386
1432
  function TabBar({ active }) {
1387
1433
  return /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: TAB_ORDER.map((tab, i) => /* @__PURE__ */ jsxs11(React6.Fragment, { children: [
1388
1434
  i > 0 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: " " }) : null,
1389
- tab === active ? /* @__PURE__ */ jsx14(Text12, { inverse: true, bold: true, children: ` ${TAB_LABEL[tab]} ` }) : /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${TAB_LABEL[tab]} ` })
1435
+ tab === active ? /* @__PURE__ */ jsx14(Text12, { inverse: true, bold: true, color: "cyan", children: ` ${TAB_LABEL[tab]} ` }) : /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${TAB_LABEL[tab]} ` })
1390
1436
  ] }, tab)) });
1391
1437
  }
1392
1438
  function AudioActionRow({
@@ -1432,7 +1478,7 @@ function SummaryPane({
1432
1478
  return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `Summary ${summary?.status ?? "unavailable"}` });
1433
1479
  }
1434
1480
  const points = (summary.keyPoints ?? []).slice(0, Math.max(1, budget - 4));
1435
- return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1481
+ return /* @__PURE__ */ jsxs11(Fragment5, { children: [
1436
1482
  /* @__PURE__ */ jsx14(Text12, { children: summary.tldr }),
1437
1483
  points.length > 0 ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, flexDirection: "column", children: points.map((point, i) => /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `\u2022 ${point}` }, i)) }) : null
1438
1484
  ] });
@@ -1443,13 +1489,13 @@ function ChaptersPane({
1443
1489
  selectedIndex
1444
1490
  }) {
1445
1491
  if (chapters.length === 0) return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "No chapters" });
1446
- return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1492
+ return /* @__PURE__ */ jsxs11(Fragment5, { children: [
1447
1493
  chapters.slice(win.start, win.end).map((chapter, i) => {
1448
1494
  const index = win.start + i;
1449
1495
  const selected = index === selectedIndex;
1450
1496
  return /* @__PURE__ */ jsxs11(Text12, { wrap: "truncate-end", children: [
1451
1497
  /* @__PURE__ */ jsx14(Text12, { color: "cyan", children: selected ? "\u25B8 " : " " }),
1452
- /* @__PURE__ */ jsx14(Text12, { color: "blue", children: `[${formatClockMs(chapter.startMs)}] ` }),
1498
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `[${formatClockMs(chapter.startMs)}] ` }),
1453
1499
  /* @__PURE__ */ jsx14(Text12, { bold: selected, children: chapter.title })
1454
1500
  ] }, index);
1455
1501
  }),
@@ -1461,10 +1507,10 @@ function TranscriptPane({
1461
1507
  win
1462
1508
  }) {
1463
1509
  if (segments.length === 0) return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "(no segments)" });
1464
- return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1510
+ return /* @__PURE__ */ jsxs11(Fragment5, { children: [
1465
1511
  segments.slice(win.start, win.end).map((seg, i) => /* @__PURE__ */ jsxs11(Text12, { children: [
1466
- /* @__PURE__ */ jsx14(Text12, { color: "blue", children: `[${formatClockMs(seg.startMs)}] ` }),
1467
- seg.speaker ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `${seg.speaker} ` }) : null,
1512
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `[${formatClockMs(seg.startMs)}] ` }),
1513
+ seg.speaker ? /* @__PURE__ */ jsx14(Text12, { color: "cyan", children: `${seg.speaker} ` }) : null,
1468
1514
  /* @__PURE__ */ jsx14(Text12, { children: seg.text })
1469
1515
  ] }, win.start + i)),
1470
1516
  win.maxScroll > 0 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${win.start + 1}\u2013${win.end} / ${segments.length}` }) : null
@@ -1631,15 +1677,33 @@ var init_PermissionPreflightView = __esm({
1631
1677
  });
1632
1678
 
1633
1679
  // src/tui/RecordSetupView.tsx
1634
- import { useState as useState6 } from "react";
1680
+ import { useEffect as useEffect3, useState as useState6 } from "react";
1635
1681
  import { Box as Box15, Text as Text15, useInput as useInput5 } from "ink";
1636
1682
  import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1683
+ function levelDb2(level) {
1684
+ if (level <= 0.03) return "silent";
1685
+ return `${Math.round(level * 60 - 60)} dB`;
1686
+ }
1687
+ function meterBar(level, width) {
1688
+ const f = Math.max(0, Math.min(1, level));
1689
+ const filled = Math.round(f * width);
1690
+ return "\u2587".repeat(filled) + "\u2591".repeat(Math.max(0, width - filled));
1691
+ }
1692
+ function InputMeter({ level }) {
1693
+ if (level == null) return /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "\u2014" });
1694
+ const silent = level <= 0.03;
1695
+ return /* @__PURE__ */ jsxs14(Text15, { children: [
1696
+ /* @__PURE__ */ jsx17(Text15, { color: silent ? "yellow" : "green", children: meterBar(level, METER_W) }),
1697
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: ` ${levelDb2(level)}` })
1698
+ ] });
1699
+ }
1637
1700
  function RecordSetupView({
1638
1701
  model,
1702
+ levels,
1639
1703
  onStart,
1640
- onCancel
1704
+ onCancel,
1705
+ onSelectionChange
1641
1706
  }) {
1642
- const size = useTerminalSize();
1643
1707
  const [srcIdx, setSrcIdx] = useState6(0);
1644
1708
  const [includeMic, setIncludeMic] = useState6(true);
1645
1709
  const [micIdx, setMicIdx] = useState6(
@@ -1650,10 +1714,18 @@ function RecordSetupView({
1650
1714
  const microphones = model.microphones ?? [];
1651
1715
  const selected = sources[Math.min(srcIdx, Math.max(0, sources.length - 1))];
1652
1716
  const selectedMic = microphones[Math.min(micIdx, Math.max(0, microphones.length - 1))];
1653
- const wide = size.columns >= 100;
1654
1717
  const hasAppSource = sources.some((source) => source.kind === "app");
1655
1718
  const hasMultipleSources = sources.length > 1;
1656
1719
  const hasMultipleMicrophones = microphones.length > 1;
1720
+ const selection = {
1721
+ sourceId: selected?.id ?? "system",
1722
+ includeMicrophone: includeMic,
1723
+ ...includeMic && selectedMic ? { microphoneDeviceId: selectedMic.id } : {},
1724
+ sceneId: model.scenes[sceneIdx]?.id
1725
+ };
1726
+ useEffect3(() => {
1727
+ onSelectionChange?.(selection);
1728
+ }, [srcIdx, includeMic, micIdx]);
1657
1729
  useInput5((input, key) => {
1658
1730
  if (key.upArrow || input === "k") setSrcIdx((i) => Math.max(0, i - 1));
1659
1731
  else if (key.downArrow || input === "j") setSrcIdx((i) => Math.min(sources.length - 1, i + 1));
@@ -1661,30 +1733,29 @@ function RecordSetupView({
1661
1733
  else if (input === "m" && includeMic && hasMultipleMicrophones)
1662
1734
  setMicIdx((i) => (i + 1) % microphones.length);
1663
1735
  else if (input === "s" && model.scenes.length > 1) setSceneIdx((i) => (i + 1) % model.scenes.length);
1664
- else if (key.return && selected) {
1665
- onStart({
1666
- sourceId: selected.id,
1667
- includeMicrophone: includeMic,
1668
- ...includeMic && selectedMic ? { microphoneDeviceId: selectedMic.id } : {},
1669
- sceneId: model.scenes[sceneIdx]?.id
1670
- });
1671
- } else if (key.escape) onCancel();
1736
+ else if (key.return && selected) onStart(selection);
1737
+ else if (key.escape) onCancel();
1672
1738
  });
1673
1739
  const sourceList = /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
1674
- /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "SOURCE" }),
1740
+ /* @__PURE__ */ jsxs14(Box15, { children: [
1741
+ /* @__PURE__ */ jsx17(Box15, { width: 36, children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "SOURCE" }) }),
1742
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "INPUT" })
1743
+ ] }),
1675
1744
  sources.map((s, i) => {
1676
1745
  const on = i === srcIdx;
1677
- return /* @__PURE__ */ jsxs14(Text15, { color: on ? "cyan" : void 0, wrap: "truncate-end", children: [
1678
- on ? "\u25B8 \u25CF " : " \u25CB ",
1679
- s.label
1746
+ return /* @__PURE__ */ jsxs14(Box15, { children: [
1747
+ /* @__PURE__ */ jsx17(Box15, { width: 36, children: /* @__PURE__ */ jsxs14(Text15, { color: on ? "cyan" : void 0, wrap: "truncate-end", children: [
1748
+ on ? "\u25B8 \u25CF " : " \u25CB ",
1749
+ s.label
1750
+ ] }) }),
1751
+ /* @__PURE__ */ jsx17(InputMeter, { level: levels?.bySourceId?.[s.id] })
1680
1752
  ] }, s.id);
1681
1753
  }),
1682
1754
  !hasAppSource ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "No app-specific sources available right now" }) : null
1683
1755
  ] });
1684
- const capturePlan = /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
1685
- /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "CAPTURE PLAN" }),
1686
- /* @__PURE__ */ jsx17(Text15, { children: includeMic ? `${selected?.label ?? "System audio"} + microphone` : `${selected?.label ?? "System audio"} only` }),
1687
- /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: includeMic ? "Mic is mixed into the recording" : "Mic stays muted" })
1756
+ const capturePlan = /* @__PURE__ */ jsxs14(Text15, { children: [
1757
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Capture " }),
1758
+ /* @__PURE__ */ jsx17(Text15, { children: includeMic ? `${selected?.label ?? "System audio"} + microphone` : `${selected?.label ?? "System audio"} only` })
1688
1759
  ] });
1689
1760
  const shortcuts = [
1690
1761
  hasMultipleSources ? "\u2191\u2193 source" : void 0,
@@ -1696,9 +1767,9 @@ function RecordSetupView({
1696
1767
  ].filter(Boolean).join(" \xB7 ");
1697
1768
  return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, children: [
1698
1769
  /* @__PURE__ */ jsx17(Text15, { bold: true, color: "green", children: "New recording" }),
1699
- /* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection: wide ? "row" : "column", children: [
1700
- /* @__PURE__ */ jsx17(Box15, { flexGrow: 1, flexDirection: "column", children: sourceList }),
1701
- /* @__PURE__ */ jsx17(Box15, { marginLeft: wide ? 4 : 0, marginTop: wide ? 0 : 1, children: capturePlan })
1770
+ /* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection: "column", children: [
1771
+ sourceList,
1772
+ /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: capturePlan })
1702
1773
  ] }),
1703
1774
  /* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection: "column", children: [
1704
1775
  /* @__PURE__ */ jsxs14(Text15, { children: [
@@ -1706,12 +1777,14 @@ function RecordSetupView({
1706
1777
  /* @__PURE__ */ jsx17(Text15, { color: includeMic ? "green" : "gray", children: includeMic ? "[x] include mic" : "[ ] include mic" }),
1707
1778
  /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " (space)" })
1708
1779
  ] }),
1709
- selectedMic ? /* @__PURE__ */ jsxs14(Text15, { dimColor: !includeMic, children: [
1710
- /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Mic device " }),
1711
- /* @__PURE__ */ jsx17(Text15, { children: selectedMic.label }),
1712
- selectedMic.isDefault ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " \xB7 default" }) : null,
1713
- includeMic && hasMultipleMicrophones ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " (m to change)" }) : null
1780
+ selectedMic ? /* @__PURE__ */ jsxs14(Box15, { children: [
1781
+ /* @__PURE__ */ jsx17(Box15, { width: 36, children: /* @__PURE__ */ jsxs14(Text15, { dimColor: !includeMic, wrap: "truncate-end", children: [
1782
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Mic device " }),
1783
+ selectedMic.label
1784
+ ] }) }),
1785
+ includeMic ? /* @__PURE__ */ jsx17(InputMeter, { level: levels?.byMicrophoneId?.[selectedMic.id] }) : null
1714
1786
  ] }) : null,
1787
+ includeMic && hasMultipleMicrophones ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " (m to change device)" }) : null,
1715
1788
  model.scenes.length > 0 ? /* @__PURE__ */ jsxs14(Text15, { children: [
1716
1789
  /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Scene " }),
1717
1790
  /* @__PURE__ */ jsx17(Text15, { children: model.scenes[sceneIdx]?.label ?? "Default" }),
@@ -1721,17 +1794,18 @@ function RecordSetupView({
1721
1794
  /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: shortcuts }) })
1722
1795
  ] });
1723
1796
  }
1797
+ var METER_W;
1724
1798
  var init_RecordSetupView = __esm({
1725
1799
  "src/tui/RecordSetupView.tsx"() {
1726
1800
  "use strict";
1727
- init_terminal();
1801
+ METER_W = 12;
1728
1802
  }
1729
1803
  });
1730
1804
 
1731
1805
  // src/tui/AppShell.tsx
1732
- import { useCallback, useEffect as useEffect3, useState as useState7 } from "react";
1806
+ import { useCallback, useEffect as useEffect4, useState as useState7 } from "react";
1733
1807
  import { Box as Box16, Text as Text16, useApp, useInput as useInput6 } from "ink";
1734
- import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1808
+ import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1735
1809
  function recordErrorCopy(code, message) {
1736
1810
  switch (code) {
1737
1811
  case "record.helper_unavailable":
@@ -1812,6 +1886,43 @@ function transcribeHandoffErrorCopy(error51) {
1812
1886
  return "Could not start transcription. Please try again.";
1813
1887
  }
1814
1888
  }
1889
+ function artifactProgressPatchFromOperationEvent(event) {
1890
+ if (event.command !== "upload") return void 0;
1891
+ if (event.status === "uploading" && typeof event.percent === "number") {
1892
+ return {
1893
+ uploadStatus: "uploading",
1894
+ uploadProgress: Math.max(0, Math.min(1, event.percent / 100))
1895
+ };
1896
+ }
1897
+ if (event.status === "finishing_upload") {
1898
+ return {
1899
+ uploadStatus: "uploading",
1900
+ uploadProgress: 1
1901
+ };
1902
+ }
1903
+ if (event.status === "starting_transcription") {
1904
+ return {
1905
+ uploadStatus: "uploaded",
1906
+ uploadProgress: 1,
1907
+ transcriptionStatus: "queued"
1908
+ };
1909
+ }
1910
+ return void 0;
1911
+ }
1912
+ function transcriptionStatusFromJob(status) {
1913
+ switch (status) {
1914
+ case "queued":
1915
+ return "queued";
1916
+ case "running":
1917
+ return "processing";
1918
+ case "succeeded":
1919
+ return "ready";
1920
+ case "failed":
1921
+ return "failed";
1922
+ default:
1923
+ return "not_started";
1924
+ }
1925
+ }
1815
1926
  function permissionItemsFromRecordError(data) {
1816
1927
  const sidecarError = isRecord8(data) ? data : void 0;
1817
1928
  const sidecarData = isRecord8(sidecarError?.data) ? sidecarError.data : void 0;
@@ -1850,6 +1961,7 @@ function AppShell({
1850
1961
  listDownloadedRecordingIds,
1851
1962
  fetchRecordSetup,
1852
1963
  startLiveRecord,
1964
+ startRecordSetupPreview,
1853
1965
  transcribeRecordingArtifact,
1854
1966
  initialView = "overview",
1855
1967
  openUrl: openUrl2,
@@ -1884,6 +1996,13 @@ function AppShell({
1884
1996
  sources: DEFAULT_RECORDING_SOURCES,
1885
1997
  microphones: []
1886
1998
  });
1999
+ const [recordSetupSelection, setRecordSetupSelection] = useState7(
2000
+ DEFAULT_RECORDING_SELECTION
2001
+ );
2002
+ const [recordSetupLevels, setRecordSetupLevels] = useState7({
2003
+ bySourceId: {},
2004
+ byMicrophoneId: {}
2005
+ });
1887
2006
  const recordSetupModel = {
1888
2007
  sources: recordSetupInputs.sources.length > 0 ? recordSetupInputs.sources : DEFAULT_RECORDING_SOURCES,
1889
2008
  microphones: recordSetupInputs.microphones ?? [],
@@ -1896,10 +2015,56 @@ function AppShell({
1896
2015
  } catch {
1897
2016
  }
1898
2017
  }, [listDownloadedRecordingIds]);
1899
- useEffect3(() => {
2018
+ useEffect4(() => {
1900
2019
  void refreshDownloadedIds();
1901
2020
  }, [refreshDownloadedIds]);
1902
2021
  const screen = stack[stack.length - 1];
2022
+ useEffect4(() => {
2023
+ if (screen.kind !== "recordSetup" || !startRecordSetupPreview) return;
2024
+ let cancelled = false;
2025
+ let preview;
2026
+ let unsubscribe;
2027
+ const selection = recordSetupSelection;
2028
+ setRecordSetupLevels({ bySourceId: {}, byMicrophoneId: {} });
2029
+ startRecordSetupPreview(selection, recordSetupModel.sources).then((session) => {
2030
+ if (cancelled) {
2031
+ void session.stop();
2032
+ return;
2033
+ }
2034
+ preview = session;
2035
+ unsubscribe = session.source.onEvent((event) => {
2036
+ if (event.type !== "audio.level") return;
2037
+ const level = levelFromRmsDb(event.rmsDb);
2038
+ if (event.input === "microphone") {
2039
+ const microphoneId = event.microphoneDeviceId ?? selection.microphoneDeviceId;
2040
+ if (!microphoneId) return;
2041
+ setRecordSetupLevels((current) => ({
2042
+ ...current,
2043
+ byMicrophoneId: { ...current.byMicrophoneId ?? {}, [microphoneId]: level }
2044
+ }));
2045
+ return;
2046
+ }
2047
+ const sourceId = event.sourceId ?? selection.sourceId;
2048
+ setRecordSetupLevels((current) => ({
2049
+ ...current,
2050
+ bySourceId: { ...current.bySourceId, [sourceId]: level }
2051
+ }));
2052
+ });
2053
+ }).catch(() => {
2054
+ });
2055
+ return () => {
2056
+ cancelled = true;
2057
+ unsubscribe?.();
2058
+ if (preview) void preview.stop();
2059
+ };
2060
+ }, [
2061
+ recordSetupSelection.includeMicrophone,
2062
+ recordSetupSelection.microphoneDeviceId,
2063
+ recordSetupSelection.sourceId,
2064
+ recordSetupModel.sources,
2065
+ screen.kind,
2066
+ startRecordSetupPreview
2067
+ ]);
1903
2068
  const beginLiveRecord = useCallback(
1904
2069
  (selection = DEFAULT_RECORDING_SELECTION) => {
1905
2070
  const capture = recordingCaptureMappingFromSelection(selection, recordSetupModel.sources);
@@ -1979,7 +2144,7 @@ function AppShell({
1979
2144
  setStack([{ kind: "overview" }]);
1980
2145
  }, [liveRecord, now, refreshDownloadedIds]);
1981
2146
  const liveSession = liveRecord?.kind === "live" ? liveRecord.session : void 0;
1982
- useEffect3(() => {
2147
+ useEffect4(() => {
1983
2148
  if (!liveSession) return;
1984
2149
  const session = liveSession;
1985
2150
  const unsubscribe = session.source.onEvent((event) => {
@@ -2002,7 +2167,7 @@ function AppShell({
2002
2167
  }, [liveSession]);
2003
2168
  const selectedRecording = screen.kind === "overview" ? recordings[selected] : void 0;
2004
2169
  const peekTranscriptId = selectedRecording?.activeTranscriptId ?? void 0;
2005
- useEffect3(() => {
2170
+ useEffect4(() => {
2006
2171
  if (!peekTranscriptId || summaryCache.has(peekTranscriptId)) return;
2007
2172
  let cancelled = false;
2008
2173
  const timer = setTimeout(() => {
@@ -2067,23 +2232,46 @@ function AppShell({
2067
2232
  artifact: {
2068
2233
  ...artifact,
2069
2234
  uploadStatus: "uploading",
2235
+ uploadProgress: 0,
2070
2236
  transcriptionStatus: "not_started",
2237
+ transcriptionProgress: void 0,
2071
2238
  error: void 0
2072
2239
  }
2073
2240
  });
2074
2241
  try {
2075
- const uploaded = await transcribeRecordingArtifact(artifact);
2242
+ const uploaded = await transcribeRecordingArtifact(artifact, (event) => {
2243
+ const patch = artifactProgressPatchFromOperationEvent(event);
2244
+ if (!patch) return;
2245
+ setLiveRecord((latest) => {
2246
+ if (latest?.kind !== "stopped" || latest.artifact?.sessionId !== artifact.sessionId) {
2247
+ return latest;
2248
+ }
2249
+ return {
2250
+ ...latest,
2251
+ artifact: {
2252
+ ...latest.artifact,
2253
+ ...patch
2254
+ }
2255
+ };
2256
+ });
2257
+ });
2076
2258
  const transcriptionStatus = uploaded.transcriptId != null || uploaded.status === "succeeded" || uploaded.status === "ready" ? "ready" : uploaded.status === "running" ? "processing" : uploaded.jobId ? "queued" : "not_started";
2077
- setLiveRecord({
2078
- ...current,
2079
- artifact: {
2080
- ...artifact,
2081
- recordingId: uploaded.recordingId,
2082
- ...uploaded.jobId ? { jobId: uploaded.jobId } : {},
2083
- ...uploaded.transcriptId ? { transcriptId: uploaded.transcriptId } : {},
2084
- uploadStatus: "uploaded",
2085
- transcriptionStatus
2086
- }
2259
+ setLiveRecord((latest) => {
2260
+ const base = latest?.kind === "stopped" && latest.artifact?.sessionId === artifact.sessionId ? latest : current;
2261
+ return {
2262
+ ...base,
2263
+ artifact: {
2264
+ ...artifact,
2265
+ ...base.artifact ?? {},
2266
+ recordingId: uploaded.recordingId,
2267
+ ...uploaded.jobId ? { jobId: uploaded.jobId } : {},
2268
+ ...uploaded.transcriptId ? { transcriptId: uploaded.transcriptId } : {},
2269
+ uploadStatus: "uploaded",
2270
+ uploadProgress: 1,
2271
+ transcriptionStatus,
2272
+ ...transcriptionStatus === "ready" ? { transcriptionProgress: 1 } : {}
2273
+ }
2274
+ };
2087
2275
  });
2088
2276
  setNotice(
2089
2277
  transcriptionStatus === "ready" ? "Transcription ready." : uploaded.jobId ? "Transcription queued." : "Uploaded to Recappi Cloud."
@@ -2127,13 +2315,13 @@ function AppShell({
2127
2315
  setLoadingMoreRecordings(false);
2128
2316
  }
2129
2317
  }, [fetchRecordings, loadingMoreRecordings, recordingsNextCursor]);
2130
- useEffect3(() => {
2318
+ useEffect4(() => {
2131
2319
  void refresh({ resetRecordings: true });
2132
2320
  const id = setInterval(() => void refresh(), pollMs);
2133
2321
  return () => clearInterval(id);
2134
2322
  }, [refresh, pollMs]);
2135
2323
  const hasRunning = jobs.some((item) => item.status === "running");
2136
- useEffect3(() => {
2324
+ useEffect4(() => {
2137
2325
  if (!hasRunning) return;
2138
2326
  const id = setInterval(() => setSpinnerFrame((f) => f + 1), spinnerMs);
2139
2327
  return () => clearInterval(id);
@@ -2146,12 +2334,29 @@ function AppShell({
2146
2334
  jobStatusByRecording.set(job.recordingId, job.status);
2147
2335
  }
2148
2336
  }
2337
+ useEffect4(() => {
2338
+ setLiveRecord((current) => {
2339
+ if (current?.kind !== "stopped" || !current.artifact?.jobId) return current;
2340
+ const job = jobs.find((item) => item.jobId === current.artifact?.jobId);
2341
+ if (!job) return current;
2342
+ const fraction = transcribeFraction(job);
2343
+ return {
2344
+ ...current,
2345
+ artifact: {
2346
+ ...current.artifact,
2347
+ transcriptionStatus: transcriptionStatusFromJob(job.status),
2348
+ ...job.transcriptId ? { transcriptId: job.transcriptId } : {},
2349
+ ...job.status === "succeeded" ? { transcriptionProgress: 1 } : fraction != null ? { transcriptionProgress: fraction } : {}
2350
+ }
2351
+ };
2352
+ });
2353
+ }, [jobs]);
2149
2354
  const listLength = screen.kind === "jobs" ? jobs.length : screen.kind === "overview" ? recordings.length : 0;
2150
- useEffect3(() => {
2355
+ useEffect4(() => {
2151
2356
  setSelected((i) => Math.max(0, Math.min(i, Math.max(0, listLength - 1))));
2152
2357
  }, [listLength]);
2153
2358
  const visibleRecordingRows = Math.max(3, size.rows - 6);
2154
- useEffect3(() => {
2359
+ useEffect4(() => {
2155
2360
  if (screen.kind !== "overview" || !recordingsNextCursor) return;
2156
2361
  const nearLoadedEnd = recordings.length - selected <= RECORDINGS_PREFETCH_REMAINING;
2157
2362
  const underfilledViewport = recordings.length < visibleRecordingRows;
@@ -2184,7 +2389,7 @@ function AppShell({
2184
2389
  [fetchTranscript]
2185
2390
  );
2186
2391
  const detailTranscriptId = screen.kind === "recordingDetail" ? recordings.find((r) => r.recordingId === screen.recordingId)?.activeTranscriptId : void 0;
2187
- useEffect3(() => {
2392
+ useEffect4(() => {
2188
2393
  if (!detailTranscriptId || transcriptCache.has(detailTranscriptId)) return;
2189
2394
  let cancelled = false;
2190
2395
  setTranscriptCache((m) => new Map(m).set(detailTranscriptId, "loading"));
@@ -2344,8 +2549,10 @@ function AppShell({
2344
2549
  RecordSetupView,
2345
2550
  {
2346
2551
  model: recordSetupModel,
2552
+ levels: recordSetupLevels,
2347
2553
  onStart: beginLiveRecord,
2348
- onCancel: () => setStack((st) => st.length > 1 ? st.slice(0, -1) : [{ kind: "overview" }])
2554
+ onCancel: () => setStack((st) => st.length > 1 ? st.slice(0, -1) : [{ kind: "overview" }]),
2555
+ onSelectionChange: setRecordSetupSelection
2349
2556
  }
2350
2557
  ) });
2351
2558
  }
@@ -2371,7 +2578,7 @@ function AppShell({
2371
2578
  return /* @__PURE__ */ jsx18(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
2372
2579
  }
2373
2580
  const copy = recordErrorCopy(liveRecord.code, liveRecord.message);
2374
- return /* @__PURE__ */ jsxs15(Fragment5, { children: [
2581
+ return /* @__PURE__ */ jsxs15(Fragment6, { children: [
2375
2582
  /* @__PURE__ */ jsx18(Text16, { color: copy.tone, children: copy.title }),
2376
2583
  copy.detail ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: copy.detail }) : null
2377
2584
  ] });
@@ -2524,6 +2731,7 @@ async function runDashboard(deps) {
2524
2731
  listDownloadedRecordingIds: deps.listDownloadedRecordingIds,
2525
2732
  fetchRecordSetup: deps.fetchRecordSetup,
2526
2733
  startLiveRecord: deps.startLiveRecord,
2734
+ startRecordSetupPreview: deps.startRecordSetupPreview,
2527
2735
  transcribeRecordingArtifact: deps.transcribeRecordingArtifact,
2528
2736
  initialView: deps.initialView ?? "overview",
2529
2737
  openUrl,
@@ -17284,6 +17492,19 @@ var sidecarRecordingStartResultSchema = external_exports.object({
17284
17492
  state: sidecarRecordingStateSchema,
17285
17493
  localSessionRef: external_exports.string().optional()
17286
17494
  });
17495
+ var sidecarLevelPreviewStartParamsSchema = external_exports.object({
17496
+ options: sidecarRecordingOptionsSchema
17497
+ });
17498
+ var sidecarLevelPreviewStartResultSchema = external_exports.object({
17499
+ previewId: external_exports.string()
17500
+ });
17501
+ var sidecarLevelPreviewStopParamsSchema = external_exports.object({
17502
+ previewId: external_exports.string()
17503
+ });
17504
+ var sidecarLevelPreviewStopResultSchema = external_exports.object({
17505
+ previewId: external_exports.string(),
17506
+ state: external_exports.literal("stopped")
17507
+ });
17287
17508
  var sidecarSessionParamsSchema = external_exports.object({
17288
17509
  sessionId: external_exports.string()
17289
17510
  });
@@ -17325,6 +17546,18 @@ var sidecarRequestSchema = external_exports.discriminatedUnion("method", [
17325
17546
  method: external_exports.literal("recappi.recording.start"),
17326
17547
  params: sidecarRecordingStartParamsSchema
17327
17548
  }),
17549
+ external_exports.object({
17550
+ jsonrpc: external_exports.literal("2.0"),
17551
+ id: sidecarJsonRpcIdSchema,
17552
+ method: external_exports.literal("recappi.recording.level_preview.start"),
17553
+ params: sidecarLevelPreviewStartParamsSchema
17554
+ }),
17555
+ external_exports.object({
17556
+ jsonrpc: external_exports.literal("2.0"),
17557
+ id: sidecarJsonRpcIdSchema,
17558
+ method: external_exports.literal("recappi.recording.level_preview.stop"),
17559
+ params: sidecarLevelPreviewStopParamsSchema
17560
+ }),
17328
17561
  external_exports.object({
17329
17562
  jsonrpc: external_exports.literal("2.0"),
17330
17563
  id: sidecarJsonRpcIdSchema,
@@ -17383,8 +17616,11 @@ var sidecarEventSchema = external_exports.discriminatedUnion("type", [
17383
17616
  }),
17384
17617
  external_exports.object({
17385
17618
  type: external_exports.literal("audio.level"),
17386
- sessionId: external_exports.string(),
17619
+ sessionId: external_exports.string().optional(),
17620
+ previewId: external_exports.string().optional(),
17387
17621
  input: external_exports.enum(["system", "microphone"]),
17622
+ sourceId: external_exports.string().optional(),
17623
+ microphoneDeviceId: external_exports.string().optional(),
17388
17624
  rmsDb: external_exports.number().optional(),
17389
17625
  peakDb: external_exports.number().optional(),
17390
17626
  at: external_exports.number().int().optional(),
@@ -20224,6 +20460,20 @@ var MiniSidecarClient = class {
20224
20460
  sidecarRecordingStartResultSchema
20225
20461
  );
20226
20462
  }
20463
+ startLevelPreview(params) {
20464
+ return this.request(
20465
+ "recappi.recording.level_preview.start",
20466
+ sidecarLevelPreviewStartParamsSchema.parse(params),
20467
+ sidecarLevelPreviewStartResultSchema
20468
+ );
20469
+ }
20470
+ stopLevelPreview(params) {
20471
+ return this.request(
20472
+ "recappi.recording.level_preview.stop",
20473
+ sidecarLevelPreviewStopParamsSchema.parse(params),
20474
+ sidecarLevelPreviewStopResultSchema
20475
+ );
20476
+ }
20227
20477
  getPermissionStatus(params) {
20228
20478
  return this.request(
20229
20479
  "recappi.permissions.status",
@@ -20573,6 +20823,55 @@ async function listRecordInputs(opts) {
20573
20823
  sidecar.kill();
20574
20824
  }
20575
20825
  }
20826
+ async function startRecordSetupLevelPreview(opts, selection = {
20827
+ sourceId: DEFAULT_RECORDING_SOURCES[0].id,
20828
+ includeMicrophone: true
20829
+ }, sources = DEFAULT_RECORDING_SOURCES) {
20830
+ const capture = recordingCaptureMappingFromSelection(selection, sources);
20831
+ const command = resolveSidecarCommand(opts);
20832
+ const sidecarArgs = opts.sidecarArgs ?? [];
20833
+ const spawnSidecar = opts.runtime?.spawnSidecar ?? spawnMiniSidecar;
20834
+ const sidecar = spawnSidecar({ command, args: sidecarArgs, env: opts.env });
20835
+ let previewId;
20836
+ let closed = false;
20837
+ const close = () => {
20838
+ if (closed) return;
20839
+ closed = true;
20840
+ sidecar.kill();
20841
+ };
20842
+ try {
20843
+ await sidecar.client.handshake(
20844
+ defaultSidecarHandshakeParams({
20845
+ client: { name: "recappi-cli", version: opts.cliVersion },
20846
+ capabilities: ["recording.capture"]
20847
+ })
20848
+ );
20849
+ const started = await sidecar.client.startLevelPreview({
20850
+ options: {
20851
+ includeSystemAudio: capture.includeSystemAudio,
20852
+ includeMicrophone: capture.includeMicrophone,
20853
+ liveCaptions: false,
20854
+ ...capture.targetBundleId ? { targetBundleId: capture.targetBundleId } : {},
20855
+ ...capture.microphoneDeviceId ? { microphoneDeviceId: capture.microphoneDeviceId } : {}
20856
+ }
20857
+ });
20858
+ previewId = started.previewId;
20859
+ return {
20860
+ source: sidecar.client,
20861
+ stop: async () => {
20862
+ try {
20863
+ if (previewId) await sidecar.client.stopLevelPreview({ previewId });
20864
+ } catch {
20865
+ } finally {
20866
+ close();
20867
+ }
20868
+ }
20869
+ };
20870
+ } catch (error51) {
20871
+ close();
20872
+ throw error51;
20873
+ }
20874
+ }
20576
20875
  async function startRecordSession(opts) {
20577
20876
  let retriedAfterMicrophoneGrant = false;
20578
20877
  while (true) {
@@ -21120,14 +21419,24 @@ async function runCli(deps = {}) {
21120
21419
  sources
21121
21420
  );
21122
21421
  },
21123
- transcribeRecordingArtifact: async (artifact) => {
21422
+ startRecordSetupPreview: async (selection, sources) => startRecordSetupLevelPreview(
21423
+ {
21424
+ cliVersion: CLI_VERSION,
21425
+ env: deps.env,
21426
+ runtime: deps.recordRuntime
21427
+ },
21428
+ selection,
21429
+ sources
21430
+ ),
21431
+ transcribeRecordingArtifact: async (artifact, onEvent) => {
21124
21432
  if (!artifact.audioPath) {
21125
21433
  throw cliError("input.not_found", "No local audio file is available to transcribe.");
21126
21434
  }
21127
21435
  const data = await client.uploadPathBatch({
21128
21436
  inputPath: artifact.audioPath,
21129
21437
  transcribe: true,
21130
- wait: false
21438
+ wait: false,
21439
+ onEvent
21131
21440
  });
21132
21441
  if (data.failures.length > 0) {
21133
21442
  const failure = data.failures[0];