recappi 0.1.44 → 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,
@@ -456,6 +444,11 @@ function sidecarToLiveCaptionEvent(event) {
456
444
  }
457
445
  return { kind: "partial", text: event.text };
458
446
  }
447
+ case "live_caption.status":
448
+ if (event.status === "error") {
449
+ return { kind: "error", message: event.message ?? "Live captions error" };
450
+ }
451
+ return { kind: "status", status: event.status };
459
452
  case "error":
460
453
  return event.code.startsWith("live_caption.") ? { kind: "error", message: event.message } : null;
461
454
  case "audio.level":
@@ -612,6 +605,43 @@ function waveform(samples, width) {
612
605
  });
613
606
  return "\u2581".repeat(Math.max(0, pad)) + cells.join("");
614
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
+ }
615
645
  function RecordingHeroScreen({
616
646
  telemetry,
617
647
  artifact,
@@ -622,11 +652,12 @@ function RecordingHeroScreen({
622
652
  }) {
623
653
  const size = useTerminalSize();
624
654
  const [tick, setTick] = useState3(() => now());
625
- const [wave, setWave] = useState3([]);
655
+ const [waveSys, setWaveSys] = useState3([]);
656
+ const [waveMic, setWaveMic] = useState3([]);
626
657
  useEffect2(() => {
627
658
  if (telemetry.level == null) return;
628
- const lvl = Math.max(telemetry.level.system ?? 0, telemetry.level.mic ?? 0);
629
- 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]);
630
661
  }, [telemetry.level]);
631
662
  useEffect2(() => {
632
663
  const id = setInterval(() => setTick(now()), 1e3);
@@ -636,17 +667,27 @@ function RecordingHeroScreen({
636
667
  const innerWidth = Math.max(10, size.columns - 4);
637
668
  if (telemetry.status === "stopped") {
638
669
  const handoff = stoppedHandoffCopy(artifact, canTranscribe);
670
+ const phase = stoppedPhase(artifact);
639
671
  const meta3 = [
640
672
  telemetry.durationMs != null ? formatClockMs(telemetry.durationMs) : null,
641
673
  formatBytes2(telemetry.sizeBytes) || null
642
674
  ].filter(Boolean).join(" \xB7 ");
675
+ const saved = artifact?.uploadStatus === "uploaded" ? "\u2713 Saved to Recappi Cloud" : "\u2713 Saved to your Mac";
643
676
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
644
677
  /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "recappi \xB7 Recording" }),
645
678
  /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
646
- /* @__PURE__ */ jsx3(Text2, { color: "green", children: "\u2713 Saved to your Mac" }),
679
+ /* @__PURE__ */ jsx3(Text2, { color: "green", children: saved }),
647
680
  meta3 ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: meta3 }) : null,
648
681
  telemetry.savedPath ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate-middle", children: telemetry.savedPath }) : null
649
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,
650
691
  /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
651
692
  /* @__PURE__ */ jsx3(Text2, { color: handoff.tone === "red" ? "red" : handoff.tone === "green" ? "green" : void 0, dimColor: handoff.tone === "dim", children: handoff.text }),
652
693
  artifact?.error ? /* @__PURE__ */ jsx3(Text2, { color: "red", wrap: "truncate-end", children: artifact.error }) : null
@@ -663,24 +704,33 @@ function RecordingHeroScreen({
663
704
  const paused = telemetry.status === "paused";
664
705
  const starting = telemetry.status === "starting" || telemetry.status === "stopping";
665
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 ");
666
710
  return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, height: size.rows, children: [
667
711
  /* @__PURE__ */ jsxs2(Text2, { children: [
668
712
  /* @__PURE__ */ jsx3(Text2, { bold: true, color: "green", children: "recappi" }),
669
713
  /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: " \xB7 Recording" })
670
714
  ] }),
671
- /* @__PURE__ */ jsxs2(Box2, { flexGrow: 1, flexDirection: "column", justifyContent: "center", alignItems: "center", children: [
672
- /* @__PURE__ */ jsx3(Text2, { bold: true, color: paused ? "yellow" : "red", children: badge }),
673
- /* @__PURE__ */ jsx3(Text2, { bold: true, children: elapsed }),
674
- /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: telemetry.level == null ? (
675
- // No level telemetry yet show honest activity, not a flat meter that
676
- // 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).
677
724
  /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: paused ? "Paused" : `Capturing audio${".".repeat(Math.floor(tick / 1e3) % 3 + 1)}` })
678
- ) : /* @__PURE__ */ jsx3(Text2, { color: paused ? "gray" : "red", children: waveform(wave, innerWidth) }) }),
679
- /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
680
- telemetry.sourceLabel,
681
- 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
682
728
  ] }) }),
683
- 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
684
734
  ] }),
685
735
  /* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
686
736
  "q stop & save",
@@ -697,7 +747,7 @@ function HeroCaptions({ state }) {
697
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)) });
698
748
  }
699
749
  return /* @__PURE__ */ jsxs2(Fragment, { children: [
700
- recent.map((line) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", alignItems: "center", children: [
750
+ recent.map((line) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
701
751
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate-end", children: [
702
752
  line.speaker ? `${line.speaker}: ` : "",
703
753
  line.text
@@ -710,11 +760,8 @@ function HeroCaptions({ state }) {
710
760
  ] });
711
761
  }
712
762
  function stoppedHandoffCopy(artifact, canTranscribe) {
713
- if (artifact?.uploadStatus === "uploading") {
714
- return { text: "Uploading\u2026", tone: "normal" };
715
- }
716
- if (artifact?.transcriptionStatus === "processing") {
717
- return { text: "Transcribing\u2026", tone: "normal" };
763
+ if (artifact?.uploadStatus === "uploading" || artifact?.transcriptionStatus === "processing") {
764
+ return { text: "esc run in background", tone: "dim" };
718
765
  }
719
766
  if (artifact?.transcriptionStatus === "queued") {
720
767
  return { text: "Transcription queued \xB7 \u23CE open recording \xB7 n not now", tone: "green" };
@@ -758,13 +805,16 @@ function AccountView({ status }) {
758
805
  function AccountBody({ status }) {
759
806
  return /* @__PURE__ */ jsxs3(Fragment2, { children: [
760
807
  /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
761
- /* @__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
+ ] }),
762
812
  status.email && status.userId ? /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: status.userId }) : null,
763
813
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: `origin ${status.origin}` })
764
814
  ] }),
765
815
  status.billing ? /* @__PURE__ */ jsx5(Usage, { billing: status.billing }) : null,
766
816
  /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
767
- /* @__PURE__ */ jsx5(Text3, { bold: true, children: "Local store" }),
817
+ /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "LOCAL STORE" }),
768
818
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, wrap: "truncate-middle", children: status.localStore.path }),
769
819
  /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
770
820
  `${status.localStore.accountScopedArtifacts} artifact${status.localStore.accountScopedArtifacts === 1 ? "" : "s"} for this account`,
@@ -778,6 +828,7 @@ function Usage({ billing }) {
778
828
  const minutesUsed = billing.minutesUsed;
779
829
  const storageCap = billing.storageCapBytes;
780
830
  return /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
831
+ /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "USAGE" }),
781
832
  /* @__PURE__ */ jsxs3(Text3, { children: [
782
833
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Plan " }),
783
834
  /* @__PURE__ */ jsx5(Text3, { bold: true, children: billing.tier })
@@ -878,9 +929,10 @@ function JobRow({
878
929
  const glyph = statusGlyph(item.status, spinnerFrame);
879
930
  const title = item.recording?.title ?? item.recordingId;
880
931
  return /* @__PURE__ */ jsxs5(Box5, { children: [
881
- /* @__PURE__ */ jsx7(Text5, { color: "cyan", children: selected ? "\u25B8 " : " " }),
882
- /* @__PURE__ */ jsx7(Text5, { color: style.color, children: `${glyph} ${padCell(style.label, 13)}` }),
883
- /* @__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 }) }),
884
936
  /* @__PURE__ */ jsx7(Text5, { dimColor: !selected, children: jobDetail(item) })
885
937
  ] });
886
938
  }
@@ -941,7 +993,7 @@ function recordingLayout(columns) {
941
993
  const showWhen = usable >= 54;
942
994
  const title = Math.max(
943
995
  10,
944
- 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
945
997
  );
946
998
  return { title, showWhen };
947
999
  }
@@ -958,33 +1010,25 @@ function RecordingRow({
958
1010
  const { glyph, color } = recordingProcessingState(item, jobStatus, spinnerFrame);
959
1011
  const duration3 = item.durationMs ? formatClockMs(item.durationMs) : "\u2014";
960
1012
  return /* @__PURE__ */ jsxs6(Box7, { children: [
961
- /* @__PURE__ */ jsx9(Text7, { color: "cyan", children: selected ? "\u25B8 " : " " }),
962
- /* @__PURE__ */ jsx9(Text7, { color, children: `${glyph} ` }),
963
- /* @__PURE__ */ jsx9(Text7, { bold: selected, children: padDisplay(recordingTitle2(item), title) }),
964
- /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay(duration3, LENGTH_W) }),
965
- showWhen ? /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay(formatAge(item.createdAt, nowMs), WHEN_W) }) : null,
966
- /* @__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" : "" }) })
967
1019
  ] });
968
1020
  }
969
- function RecordingHeader({ columns }) {
970
- const { title, showWhen } = recordingLayout(columns);
971
- return /* @__PURE__ */ jsxs6(Box7, { children: [
972
- /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay("", MARKER_W + GLYPH_W) }),
973
- /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay("TITLE", title) }),
974
- /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: padDisplay("LENGTH", LENGTH_W) }),
975
- showWhen ? /* @__PURE__ */ jsx9(Text7, { dimColor: true, children: "WHEN" }) : null
976
- ] });
977
- }
978
- 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;
979
1022
  var init_RecordingRow = __esm({
980
1023
  "src/tui/RecordingRow.tsx"() {
981
1024
  "use strict";
982
1025
  init_format();
983
1026
  UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
984
- MARKER_W = 2;
1027
+ MARKER_W = 3;
985
1028
  GLYPH_W = 2;
986
- LENGTH_W = 9;
1029
+ LENGTH_W = 8;
987
1030
  WHEN_W = 9;
1031
+ DL_W = 3;
988
1032
  }
989
1033
  });
990
1034
 
@@ -1004,28 +1048,25 @@ function RecordingsView({
1004
1048
  if (items.length === 0) {
1005
1049
  return /* @__PURE__ */ jsx10(Box8, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text8, { dimColor: true, children: "No recordings yet \u2014 run: recappi upload <file>" }) });
1006
1050
  }
1007
- return /* @__PURE__ */ jsxs7(Box8, { marginTop: 1, flexDirection: "column", children: [
1008
- /* @__PURE__ */ jsx10(RecordingHeader, { columns }),
1009
- items.map((item, index) => {
1010
- const bucket = dateBucket(item.createdAt, nowMs);
1011
- const showHeader = index === 0 || bucket !== dateBucket(items[index - 1].createdAt, nowMs);
1012
- return /* @__PURE__ */ jsxs7(React5.Fragment, { children: [
1013
- showHeader ? /* @__PURE__ */ jsx10(Box8, { marginTop: index === 0 ? 0 : 1, children: /* @__PURE__ */ jsx10(Text8, { bold: true, color: "blue", children: bucket }) }) : null,
1014
- /* @__PURE__ */ jsx10(
1015
- RecordingRow,
1016
- {
1017
- item,
1018
- selected: index === selectedIndex,
1019
- nowMs,
1020
- columns,
1021
- jobStatus: jobStatusByRecording?.get(item.recordingId),
1022
- downloaded: downloadedRecordingIds?.has(item.recordingId) ?? false,
1023
- spinnerFrame
1024
- }
1025
- )
1026
- ] }, item.recordingId);
1027
- })
1028
- ] });
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
+ }) });
1029
1070
  }
1030
1071
  var init_RecordingsView = __esm({
1031
1072
  "src/tui/RecordingsView.tsx"() {
@@ -1055,13 +1096,14 @@ function PeekBody({
1055
1096
  const meta3 = [
1056
1097
  item.durationMs ? formatClockMs(item.durationMs) : null,
1057
1098
  formatBytes2(item.sizeBytes) || null,
1058
- item.contentType || null
1059
- ].filter(Boolean).join(" \xB7 ");
1099
+ formatAge(item.createdAt, nowMs)
1100
+ ].filter(Boolean).join(" \xB7 ");
1060
1101
  return /* @__PURE__ */ jsxs8(Fragment3, { children: [
1061
- /* @__PURE__ */ jsx11(Text9, { bold: true, color: "green", wrap: "truncate-end", children: recordingTitle2(item) }),
1062
- /* @__PURE__ */ jsx11(Text9, { color: style.color, children: `${style.glyph} ${style.label}` }),
1063
- meta3 ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: meta3 }) : null,
1064
- /* @__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
+ ] }),
1065
1107
  /* @__PURE__ */ jsx11(Box9, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(SummarySection, { item, summary }) }),
1066
1108
  /* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "\u23CE open \xB7 t transcript \xB7 o web" }) })
1067
1109
  ] });
@@ -1078,7 +1120,7 @@ function SummarySection({
1078
1120
  }
1079
1121
  const points = (summary.keyPoints ?? []).slice(0, 3);
1080
1122
  return /* @__PURE__ */ jsxs8(Fragment3, { children: [
1081
- /* @__PURE__ */ jsx11(Text9, { bold: true, children: "Summary" }),
1123
+ /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "SUMMARY" }),
1082
1124
  /* @__PURE__ */ jsx11(Text9, { children: summary.tldr }),
1083
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
1084
1126
  ] });
@@ -1093,7 +1135,7 @@ var init_RecordingPeek = __esm({
1093
1135
 
1094
1136
  // src/tui/OverviewView.tsx
1095
1137
  import { Box as Box10, Text as Text10 } from "ink";
1096
- 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";
1097
1139
  function OverviewView({
1098
1140
  recordings,
1099
1141
  jobs,
@@ -1114,12 +1156,21 @@ function OverviewView({
1114
1156
  const queued = stats?.jobs.queued ?? jobCounts.queued;
1115
1157
  return /* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", children: [
1116
1158
  /* @__PURE__ */ jsxs9(Box10, { children: [
1117
- /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: "Recordings " }),
1118
1159
  /* @__PURE__ */ jsx12(Text10, { bold: true, children: stats?.recordings.total ?? recordings.length }),
1119
- stats?.recordings.ready != null ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` \xB7 ${stats.recordings.ready} ready` }) : null,
1120
- stats?.recordings.totalDurationMs != null ? /* @__PURE__ */ jsx12(Text10, { dimColor: true, children: ` \xB7 ${formatClockMs(stats.recordings.totalDurationMs)} transcribed` }) : null,
1121
- running > 0 ? /* @__PURE__ */ jsx12(Text10, { color: "cyan", children: ` \xB7 ${running} transcribing` }) : null,
1122
- 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
1123
1174
  ] }),
1124
1175
  /* @__PURE__ */ jsxs9(Box10, { flexDirection: "row", alignItems: "flex-start", children: [
1125
1176
  /* @__PURE__ */ jsx12(Box10, { flexGrow: 1, flexDirection: "column", children: /* @__PURE__ */ jsx12(
@@ -1128,7 +1179,7 @@ function OverviewView({
1128
1179
  items: recordings,
1129
1180
  selectedIndex,
1130
1181
  nowMs,
1131
- columns,
1182
+ columns: showPeek ? Math.max(20, columns - peekWidth - 1) : columns,
1132
1183
  jobStatusByRecording,
1133
1184
  downloadedRecordingIds,
1134
1185
  spinnerFrame
@@ -1290,7 +1341,7 @@ var init_JobDetailView = __esm({
1290
1341
  // src/tui/RecordingDetailView.tsx
1291
1342
  import React6, { useMemo as useMemo2, useState as useState4 } from "react";
1292
1343
  import { Box as Box12, Text as Text12, useInput as useInput3 } from "ink";
1293
- 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";
1294
1345
  function RecordingDetailView({
1295
1346
  item,
1296
1347
  nowMs,
@@ -1355,14 +1406,14 @@ function RecordingDetailView({
1355
1406
  });
1356
1407
  return /* @__PURE__ */ jsxs11(Box12, { flexDirection: "column", paddingX: 1, children: [
1357
1408
  /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "\u2039 Recordings" }),
1358
- /* @__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 }) }),
1359
1410
  /* @__PURE__ */ jsxs11(Text12, { children: [
1360
1411
  /* @__PURE__ */ jsx14(Text12, { color: style.color, children: `${style.glyph} ${style.label}` }),
1361
1412
  /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${formatAge(item.createdAt, nowMs) || "\u2014"}` })
1362
1413
  ] }),
1363
1414
  meta3 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: meta3 }) : null,
1364
1415
  /* @__PURE__ */ jsx14(AudioActionRow, { item, audio }),
1365
- !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: [
1366
1417
  /* @__PURE__ */ jsx14(TabBar, { active: tab }),
1367
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 }) })
1368
1419
  ] }),
@@ -1381,7 +1432,7 @@ function RecordingDetailView({
1381
1432
  function TabBar({ active }) {
1382
1433
  return /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: TAB_ORDER.map((tab, i) => /* @__PURE__ */ jsxs11(React6.Fragment, { children: [
1383
1434
  i > 0 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: " " }) : null,
1384
- 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]} ` })
1385
1436
  ] }, tab)) });
1386
1437
  }
1387
1438
  function AudioActionRow({
@@ -1427,7 +1478,7 @@ function SummaryPane({
1427
1478
  return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `Summary ${summary?.status ?? "unavailable"}` });
1428
1479
  }
1429
1480
  const points = (summary.keyPoints ?? []).slice(0, Math.max(1, budget - 4));
1430
- return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1481
+ return /* @__PURE__ */ jsxs11(Fragment5, { children: [
1431
1482
  /* @__PURE__ */ jsx14(Text12, { children: summary.tldr }),
1432
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
1433
1484
  ] });
@@ -1438,13 +1489,13 @@ function ChaptersPane({
1438
1489
  selectedIndex
1439
1490
  }) {
1440
1491
  if (chapters.length === 0) return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "No chapters" });
1441
- return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1492
+ return /* @__PURE__ */ jsxs11(Fragment5, { children: [
1442
1493
  chapters.slice(win.start, win.end).map((chapter, i) => {
1443
1494
  const index = win.start + i;
1444
1495
  const selected = index === selectedIndex;
1445
1496
  return /* @__PURE__ */ jsxs11(Text12, { wrap: "truncate-end", children: [
1446
1497
  /* @__PURE__ */ jsx14(Text12, { color: "cyan", children: selected ? "\u25B8 " : " " }),
1447
- /* @__PURE__ */ jsx14(Text12, { color: "blue", children: `[${formatClockMs(chapter.startMs)}] ` }),
1498
+ /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `[${formatClockMs(chapter.startMs)}] ` }),
1448
1499
  /* @__PURE__ */ jsx14(Text12, { bold: selected, children: chapter.title })
1449
1500
  ] }, index);
1450
1501
  }),
@@ -1456,10 +1507,10 @@ function TranscriptPane({
1456
1507
  win
1457
1508
  }) {
1458
1509
  if (segments.length === 0) return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "(no segments)" });
1459
- return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1510
+ return /* @__PURE__ */ jsxs11(Fragment5, { children: [
1460
1511
  segments.slice(win.start, win.end).map((seg, i) => /* @__PURE__ */ jsxs11(Text12, { children: [
1461
- /* @__PURE__ */ jsx14(Text12, { color: "blue", children: `[${formatClockMs(seg.startMs)}] ` }),
1462
- 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,
1463
1514
  /* @__PURE__ */ jsx14(Text12, { children: seg.text })
1464
1515
  ] }, win.start + i)),
1465
1516
  win.maxScroll > 0 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: ` ${win.start + 1}\u2013${win.end} / ${segments.length}` }) : null
@@ -1626,15 +1677,33 @@ var init_PermissionPreflightView = __esm({
1626
1677
  });
1627
1678
 
1628
1679
  // src/tui/RecordSetupView.tsx
1629
- import { useState as useState6 } from "react";
1680
+ import { useEffect as useEffect3, useState as useState6 } from "react";
1630
1681
  import { Box as Box15, Text as Text15, useInput as useInput5 } from "ink";
1631
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
+ }
1632
1700
  function RecordSetupView({
1633
1701
  model,
1702
+ levels,
1634
1703
  onStart,
1635
- onCancel
1704
+ onCancel,
1705
+ onSelectionChange
1636
1706
  }) {
1637
- const size = useTerminalSize();
1638
1707
  const [srcIdx, setSrcIdx] = useState6(0);
1639
1708
  const [includeMic, setIncludeMic] = useState6(true);
1640
1709
  const [micIdx, setMicIdx] = useState6(
@@ -1645,10 +1714,18 @@ function RecordSetupView({
1645
1714
  const microphones = model.microphones ?? [];
1646
1715
  const selected = sources[Math.min(srcIdx, Math.max(0, sources.length - 1))];
1647
1716
  const selectedMic = microphones[Math.min(micIdx, Math.max(0, microphones.length - 1))];
1648
- const wide = size.columns >= 100;
1649
1717
  const hasAppSource = sources.some((source) => source.kind === "app");
1650
1718
  const hasMultipleSources = sources.length > 1;
1651
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]);
1652
1729
  useInput5((input, key) => {
1653
1730
  if (key.upArrow || input === "k") setSrcIdx((i) => Math.max(0, i - 1));
1654
1731
  else if (key.downArrow || input === "j") setSrcIdx((i) => Math.min(sources.length - 1, i + 1));
@@ -1656,30 +1733,29 @@ function RecordSetupView({
1656
1733
  else if (input === "m" && includeMic && hasMultipleMicrophones)
1657
1734
  setMicIdx((i) => (i + 1) % microphones.length);
1658
1735
  else if (input === "s" && model.scenes.length > 1) setSceneIdx((i) => (i + 1) % model.scenes.length);
1659
- else if (key.return && selected) {
1660
- onStart({
1661
- sourceId: selected.id,
1662
- includeMicrophone: includeMic,
1663
- ...includeMic && selectedMic ? { microphoneDeviceId: selectedMic.id } : {},
1664
- sceneId: model.scenes[sceneIdx]?.id
1665
- });
1666
- } else if (key.escape) onCancel();
1736
+ else if (key.return && selected) onStart(selection);
1737
+ else if (key.escape) onCancel();
1667
1738
  });
1668
1739
  const sourceList = /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
1669
- /* @__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
+ ] }),
1670
1744
  sources.map((s, i) => {
1671
1745
  const on = i === srcIdx;
1672
- return /* @__PURE__ */ jsxs14(Text15, { color: on ? "cyan" : void 0, wrap: "truncate-end", children: [
1673
- on ? "\u25B8 \u25CF " : " \u25CB ",
1674
- 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] })
1675
1752
  ] }, s.id);
1676
1753
  }),
1677
1754
  !hasAppSource ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "No app-specific sources available right now" }) : null
1678
1755
  ] });
1679
- const capturePlan = /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
1680
- /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "CAPTURE PLAN" }),
1681
- /* @__PURE__ */ jsx17(Text15, { children: includeMic ? `${selected?.label ?? "System audio"} + microphone` : `${selected?.label ?? "System audio"} only` }),
1682
- /* @__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` })
1683
1759
  ] });
1684
1760
  const shortcuts = [
1685
1761
  hasMultipleSources ? "\u2191\u2193 source" : void 0,
@@ -1691,9 +1767,9 @@ function RecordSetupView({
1691
1767
  ].filter(Boolean).join(" \xB7 ");
1692
1768
  return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, children: [
1693
1769
  /* @__PURE__ */ jsx17(Text15, { bold: true, color: "green", children: "New recording" }),
1694
- /* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection: wide ? "row" : "column", children: [
1695
- /* @__PURE__ */ jsx17(Box15, { flexGrow: 1, flexDirection: "column", children: sourceList }),
1696
- /* @__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 })
1697
1773
  ] }),
1698
1774
  /* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection: "column", children: [
1699
1775
  /* @__PURE__ */ jsxs14(Text15, { children: [
@@ -1701,12 +1777,14 @@ function RecordSetupView({
1701
1777
  /* @__PURE__ */ jsx17(Text15, { color: includeMic ? "green" : "gray", children: includeMic ? "[x] include mic" : "[ ] include mic" }),
1702
1778
  /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " (space)" })
1703
1779
  ] }),
1704
- selectedMic ? /* @__PURE__ */ jsxs14(Text15, { dimColor: !includeMic, children: [
1705
- /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Mic device " }),
1706
- /* @__PURE__ */ jsx17(Text15, { children: selectedMic.label }),
1707
- selectedMic.isDefault ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " \xB7 default" }) : null,
1708
- 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
1709
1786
  ] }) : null,
1787
+ includeMic && hasMultipleMicrophones ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " (m to change device)" }) : null,
1710
1788
  model.scenes.length > 0 ? /* @__PURE__ */ jsxs14(Text15, { children: [
1711
1789
  /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Scene " }),
1712
1790
  /* @__PURE__ */ jsx17(Text15, { children: model.scenes[sceneIdx]?.label ?? "Default" }),
@@ -1716,17 +1794,18 @@ function RecordSetupView({
1716
1794
  /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: shortcuts }) })
1717
1795
  ] });
1718
1796
  }
1797
+ var METER_W;
1719
1798
  var init_RecordSetupView = __esm({
1720
1799
  "src/tui/RecordSetupView.tsx"() {
1721
1800
  "use strict";
1722
- init_terminal();
1801
+ METER_W = 12;
1723
1802
  }
1724
1803
  });
1725
1804
 
1726
1805
  // src/tui/AppShell.tsx
1727
- import { useCallback, useEffect as useEffect3, useState as useState7 } from "react";
1806
+ import { useCallback, useEffect as useEffect4, useState as useState7 } from "react";
1728
1807
  import { Box as Box16, Text as Text16, useApp, useInput as useInput6 } from "ink";
1729
- 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";
1730
1809
  function recordErrorCopy(code, message) {
1731
1810
  switch (code) {
1732
1811
  case "record.helper_unavailable":
@@ -1807,6 +1886,43 @@ function transcribeHandoffErrorCopy(error51) {
1807
1886
  return "Could not start transcription. Please try again.";
1808
1887
  }
1809
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
+ }
1810
1926
  function permissionItemsFromRecordError(data) {
1811
1927
  const sidecarError = isRecord8(data) ? data : void 0;
1812
1928
  const sidecarData = isRecord8(sidecarError?.data) ? sidecarError.data : void 0;
@@ -1845,6 +1961,7 @@ function AppShell({
1845
1961
  listDownloadedRecordingIds,
1846
1962
  fetchRecordSetup,
1847
1963
  startLiveRecord,
1964
+ startRecordSetupPreview,
1848
1965
  transcribeRecordingArtifact,
1849
1966
  initialView = "overview",
1850
1967
  openUrl: openUrl2,
@@ -1879,6 +1996,13 @@ function AppShell({
1879
1996
  sources: DEFAULT_RECORDING_SOURCES,
1880
1997
  microphones: []
1881
1998
  });
1999
+ const [recordSetupSelection, setRecordSetupSelection] = useState7(
2000
+ DEFAULT_RECORDING_SELECTION
2001
+ );
2002
+ const [recordSetupLevels, setRecordSetupLevels] = useState7({
2003
+ bySourceId: {},
2004
+ byMicrophoneId: {}
2005
+ });
1882
2006
  const recordSetupModel = {
1883
2007
  sources: recordSetupInputs.sources.length > 0 ? recordSetupInputs.sources : DEFAULT_RECORDING_SOURCES,
1884
2008
  microphones: recordSetupInputs.microphones ?? [],
@@ -1891,10 +2015,56 @@ function AppShell({
1891
2015
  } catch {
1892
2016
  }
1893
2017
  }, [listDownloadedRecordingIds]);
1894
- useEffect3(() => {
2018
+ useEffect4(() => {
1895
2019
  void refreshDownloadedIds();
1896
2020
  }, [refreshDownloadedIds]);
1897
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
+ ]);
1898
2068
  const beginLiveRecord = useCallback(
1899
2069
  (selection = DEFAULT_RECORDING_SELECTION) => {
1900
2070
  const capture = recordingCaptureMappingFromSelection(selection, recordSetupModel.sources);
@@ -1974,7 +2144,7 @@ function AppShell({
1974
2144
  setStack([{ kind: "overview" }]);
1975
2145
  }, [liveRecord, now, refreshDownloadedIds]);
1976
2146
  const liveSession = liveRecord?.kind === "live" ? liveRecord.session : void 0;
1977
- useEffect3(() => {
2147
+ useEffect4(() => {
1978
2148
  if (!liveSession) return;
1979
2149
  const session = liveSession;
1980
2150
  const unsubscribe = session.source.onEvent((event) => {
@@ -1997,7 +2167,7 @@ function AppShell({
1997
2167
  }, [liveSession]);
1998
2168
  const selectedRecording = screen.kind === "overview" ? recordings[selected] : void 0;
1999
2169
  const peekTranscriptId = selectedRecording?.activeTranscriptId ?? void 0;
2000
- useEffect3(() => {
2170
+ useEffect4(() => {
2001
2171
  if (!peekTranscriptId || summaryCache.has(peekTranscriptId)) return;
2002
2172
  let cancelled = false;
2003
2173
  const timer = setTimeout(() => {
@@ -2062,23 +2232,46 @@ function AppShell({
2062
2232
  artifact: {
2063
2233
  ...artifact,
2064
2234
  uploadStatus: "uploading",
2235
+ uploadProgress: 0,
2065
2236
  transcriptionStatus: "not_started",
2237
+ transcriptionProgress: void 0,
2066
2238
  error: void 0
2067
2239
  }
2068
2240
  });
2069
2241
  try {
2070
- 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
+ });
2071
2258
  const transcriptionStatus = uploaded.transcriptId != null || uploaded.status === "succeeded" || uploaded.status === "ready" ? "ready" : uploaded.status === "running" ? "processing" : uploaded.jobId ? "queued" : "not_started";
2072
- setLiveRecord({
2073
- ...current,
2074
- artifact: {
2075
- ...artifact,
2076
- recordingId: uploaded.recordingId,
2077
- ...uploaded.jobId ? { jobId: uploaded.jobId } : {},
2078
- ...uploaded.transcriptId ? { transcriptId: uploaded.transcriptId } : {},
2079
- uploadStatus: "uploaded",
2080
- transcriptionStatus
2081
- }
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
+ };
2082
2275
  });
2083
2276
  setNotice(
2084
2277
  transcriptionStatus === "ready" ? "Transcription ready." : uploaded.jobId ? "Transcription queued." : "Uploaded to Recappi Cloud."
@@ -2122,13 +2315,13 @@ function AppShell({
2122
2315
  setLoadingMoreRecordings(false);
2123
2316
  }
2124
2317
  }, [fetchRecordings, loadingMoreRecordings, recordingsNextCursor]);
2125
- useEffect3(() => {
2318
+ useEffect4(() => {
2126
2319
  void refresh({ resetRecordings: true });
2127
2320
  const id = setInterval(() => void refresh(), pollMs);
2128
2321
  return () => clearInterval(id);
2129
2322
  }, [refresh, pollMs]);
2130
2323
  const hasRunning = jobs.some((item) => item.status === "running");
2131
- useEffect3(() => {
2324
+ useEffect4(() => {
2132
2325
  if (!hasRunning) return;
2133
2326
  const id = setInterval(() => setSpinnerFrame((f) => f + 1), spinnerMs);
2134
2327
  return () => clearInterval(id);
@@ -2141,12 +2334,29 @@ function AppShell({
2141
2334
  jobStatusByRecording.set(job.recordingId, job.status);
2142
2335
  }
2143
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]);
2144
2354
  const listLength = screen.kind === "jobs" ? jobs.length : screen.kind === "overview" ? recordings.length : 0;
2145
- useEffect3(() => {
2355
+ useEffect4(() => {
2146
2356
  setSelected((i) => Math.max(0, Math.min(i, Math.max(0, listLength - 1))));
2147
2357
  }, [listLength]);
2148
2358
  const visibleRecordingRows = Math.max(3, size.rows - 6);
2149
- useEffect3(() => {
2359
+ useEffect4(() => {
2150
2360
  if (screen.kind !== "overview" || !recordingsNextCursor) return;
2151
2361
  const nearLoadedEnd = recordings.length - selected <= RECORDINGS_PREFETCH_REMAINING;
2152
2362
  const underfilledViewport = recordings.length < visibleRecordingRows;
@@ -2179,7 +2389,7 @@ function AppShell({
2179
2389
  [fetchTranscript]
2180
2390
  );
2181
2391
  const detailTranscriptId = screen.kind === "recordingDetail" ? recordings.find((r) => r.recordingId === screen.recordingId)?.activeTranscriptId : void 0;
2182
- useEffect3(() => {
2392
+ useEffect4(() => {
2183
2393
  if (!detailTranscriptId || transcriptCache.has(detailTranscriptId)) return;
2184
2394
  let cancelled = false;
2185
2395
  setTranscriptCache((m) => new Map(m).set(detailTranscriptId, "loading"));
@@ -2339,8 +2549,10 @@ function AppShell({
2339
2549
  RecordSetupView,
2340
2550
  {
2341
2551
  model: recordSetupModel,
2552
+ levels: recordSetupLevels,
2342
2553
  onStart: beginLiveRecord,
2343
- 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
2344
2556
  }
2345
2557
  ) });
2346
2558
  }
@@ -2366,7 +2578,7 @@ function AppShell({
2366
2578
  return /* @__PURE__ */ jsx18(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
2367
2579
  }
2368
2580
  const copy = recordErrorCopy(liveRecord.code, liveRecord.message);
2369
- return /* @__PURE__ */ jsxs15(Fragment5, { children: [
2581
+ return /* @__PURE__ */ jsxs15(Fragment6, { children: [
2370
2582
  /* @__PURE__ */ jsx18(Text16, { color: copy.tone, children: copy.title }),
2371
2583
  copy.detail ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: copy.detail }) : null
2372
2584
  ] });
@@ -2519,6 +2731,7 @@ async function runDashboard(deps) {
2519
2731
  listDownloadedRecordingIds: deps.listDownloadedRecordingIds,
2520
2732
  fetchRecordSetup: deps.fetchRecordSetup,
2521
2733
  startLiveRecord: deps.startLiveRecord,
2734
+ startRecordSetupPreview: deps.startRecordSetupPreview,
2522
2735
  transcribeRecordingArtifact: deps.transcribeRecordingArtifact,
2523
2736
  initialView: deps.initialView ?? "overview",
2524
2737
  openUrl,
@@ -17279,6 +17492,19 @@ var sidecarRecordingStartResultSchema = external_exports.object({
17279
17492
  state: sidecarRecordingStateSchema,
17280
17493
  localSessionRef: external_exports.string().optional()
17281
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
+ });
17282
17508
  var sidecarSessionParamsSchema = external_exports.object({
17283
17509
  sessionId: external_exports.string()
17284
17510
  });
@@ -17320,6 +17546,18 @@ var sidecarRequestSchema = external_exports.discriminatedUnion("method", [
17320
17546
  method: external_exports.literal("recappi.recording.start"),
17321
17547
  params: sidecarRecordingStartParamsSchema
17322
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
+ }),
17323
17561
  external_exports.object({
17324
17562
  jsonrpc: external_exports.literal("2.0"),
17325
17563
  id: sidecarJsonRpcIdSchema,
@@ -17378,8 +17616,11 @@ var sidecarEventSchema = external_exports.discriminatedUnion("type", [
17378
17616
  }),
17379
17617
  external_exports.object({
17380
17618
  type: external_exports.literal("audio.level"),
17381
- sessionId: external_exports.string(),
17619
+ sessionId: external_exports.string().optional(),
17620
+ previewId: external_exports.string().optional(),
17382
17621
  input: external_exports.enum(["system", "microphone"]),
17622
+ sourceId: external_exports.string().optional(),
17623
+ microphoneDeviceId: external_exports.string().optional(),
17383
17624
  rmsDb: external_exports.number().optional(),
17384
17625
  peakDb: external_exports.number().optional(),
17385
17626
  at: external_exports.number().int().optional(),
@@ -20219,6 +20460,20 @@ var MiniSidecarClient = class {
20219
20460
  sidecarRecordingStartResultSchema
20220
20461
  );
20221
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
+ }
20222
20477
  getPermissionStatus(params) {
20223
20478
  return this.request(
20224
20479
  "recappi.permissions.status",
@@ -20568,6 +20823,55 @@ async function listRecordInputs(opts) {
20568
20823
  sidecar.kill();
20569
20824
  }
20570
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
+ }
20571
20875
  async function startRecordSession(opts) {
20572
20876
  let retriedAfterMicrophoneGrant = false;
20573
20877
  while (true) {
@@ -21115,14 +21419,24 @@ async function runCli(deps = {}) {
21115
21419
  sources
21116
21420
  );
21117
21421
  },
21118
- 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) => {
21119
21432
  if (!artifact.audioPath) {
21120
21433
  throw cliError("input.not_found", "No local audio file is available to transcribe.");
21121
21434
  }
21122
21435
  const data = await client.uploadPathBatch({
21123
21436
  inputPath: artifact.audioPath,
21124
21437
  transcribe: true,
21125
- wait: false
21438
+ wait: false,
21439
+ onEvent
21126
21440
  });
21127
21441
  if (data.failures.length > 0) {
21128
21442
  const failure = data.failures[0];