recappi 0.1.46 → 0.1.48

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
@@ -161,7 +161,7 @@ function statusStyle(status) {
161
161
  case "failed":
162
162
  return { label: "Failed", color: "red" };
163
163
  default:
164
- return { label: status, color: "white" };
164
+ return { label: status, color: "gray" };
165
165
  }
166
166
  }
167
167
  function spinnerChar(frame) {
@@ -285,7 +285,7 @@ function recordingStatusStyle(status) {
285
285
  case "aborted":
286
286
  return { label: "Aborted", color: "gray", glyph: "\u2022" };
287
287
  default:
288
- return { label: status, color: "white", glyph: "\u2022" };
288
+ return { label: status, color: "gray", glyph: "\u2022" };
289
289
  }
290
290
  }
291
291
  function listWindow(selected, total, size) {
@@ -553,7 +553,7 @@ var init_LiveCaptionsView = __esm({
553
553
  init_liveCaptions();
554
554
  STATUS_COLOR = {
555
555
  connecting: "yellow",
556
- live: "red",
556
+ live: "cyan",
557
557
  reconnecting: "yellow",
558
558
  stopped: "gray",
559
559
  error: "red"
@@ -619,7 +619,7 @@ function MeterRow({
619
619
  const silent = level <= 0.03;
620
620
  return /* @__PURE__ */ jsxs2(Box2, { children: [
621
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) }) }),
622
+ /* @__PURE__ */ jsx3(Box2, { width, children: /* @__PURE__ */ jsx3(Text2, { color: paused ? "gray" : silent ? "yellow" : "cyan", children: waveform(samples, width) }) }),
623
623
  /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: ` ${paused ? "paused" : levelDb(level)}` })
624
624
  ] });
625
625
  }
@@ -728,7 +728,7 @@ function RecordingHeroScreen({
728
728
  ] }) }),
729
729
  /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: context }) }),
730
730
  captions ? /* @__PURE__ */ jsxs2(Box2, { marginTop: 1, flexDirection: "column", children: [
731
- /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: "LIVE CAPTIONS" }),
731
+ /* @__PURE__ */ jsx3(Text2, { bold: true, dimColor: true, children: "LIVE CAPTIONS" }),
732
732
  /* @__PURE__ */ jsx3(HeroCaptions, { state: captions })
733
733
  ] }) : null
734
734
  ] }),
@@ -791,18 +791,24 @@ var init_RecordingHeroScreen = __esm({
791
791
  // src/tui/AccountView.tsx
792
792
  import { Box as Box3, Text as Text3 } from "ink";
793
793
  import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
794
- function AccountView({ status }) {
794
+ function AccountView({
795
+ status,
796
+ nowMs = Date.now()
797
+ }) {
795
798
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, children: [
796
799
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "\u2039 Account" }),
797
800
  status === "loading" || status === void 0 ? /* @__PURE__ */ jsx5(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Loading account\u2026" }) }) : status === "error" ? /* @__PURE__ */ jsx5(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text3, { color: "red", children: "Couldn't load account status" }) }) : !status.loggedIn ? /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
798
801
  /* @__PURE__ */ jsx5(Text3, { color: "yellow", children: "Not signed in" }),
799
802
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: `origin ${status.origin}` }),
800
803
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Run `recappi auth login` to sign in." })
801
- ] }) : /* @__PURE__ */ jsx5(AccountBody, { status }),
804
+ ] }) : /* @__PURE__ */ jsx5(AccountBody, { status, nowMs }),
802
805
  /* @__PURE__ */ jsx5(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "r refresh \xB7 esc back \xB7 q quit" }) })
803
806
  ] });
804
807
  }
805
- function AccountBody({ status }) {
808
+ function AccountBody({
809
+ status,
810
+ nowMs
811
+ }) {
806
812
  return /* @__PURE__ */ jsxs3(Fragment2, { children: [
807
813
  /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
808
814
  /* @__PURE__ */ jsxs3(Text3, { children: [
@@ -812,9 +818,9 @@ function AccountBody({ status }) {
812
818
  status.email && status.userId ? /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: status.userId }) : null,
813
819
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: `origin ${status.origin}` })
814
820
  ] }),
815
- status.billing ? /* @__PURE__ */ jsx5(Usage, { billing: status.billing }) : null,
821
+ status.billing ? /* @__PURE__ */ jsx5(Usage, { billing: status.billing, nowMs }) : null,
816
822
  /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
817
- /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "LOCAL STORE" }),
823
+ /* @__PURE__ */ jsx5(Text3, { bold: true, dimColor: true, children: "LOCAL STORE" }),
818
824
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, wrap: "truncate-middle", children: status.localStore.path }),
819
825
  /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
820
826
  `${status.localStore.accountScopedArtifacts} artifact${status.localStore.accountScopedArtifacts === 1 ? "" : "s"} for this account`,
@@ -823,12 +829,15 @@ function AccountBody({ status }) {
823
829
  ] })
824
830
  ] });
825
831
  }
826
- function Usage({ billing }) {
832
+ function Usage({
833
+ billing,
834
+ nowMs
835
+ }) {
827
836
  const minutesCap = billing.minutesCap;
828
837
  const minutesUsed = billing.minutesUsed;
829
838
  const storageCap = billing.storageCapBytes;
830
839
  return /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
831
- /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "USAGE" }),
840
+ /* @__PURE__ */ jsx5(Text3, { bold: true, dimColor: true, children: "USAGE" }),
832
841
  /* @__PURE__ */ jsxs3(Text3, { children: [
833
842
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "Plan " }),
834
843
  /* @__PURE__ */ jsx5(Text3, { bold: true, children: billing.tier })
@@ -850,11 +859,11 @@ function Usage({ billing }) {
850
859
  billing.isOverMinutes ? "Over minutes limit. " : "",
851
860
  billing.isOverStorage ? "Over storage limit." : ""
852
861
  ] }) : null,
853
- /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: `Period ${periodText(billing)}` })
862
+ /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: `Period ${periodText(billing, nowMs)}` })
854
863
  ] });
855
864
  }
856
- function periodText(billing) {
857
- const remainingMs = epochToMs(billing.periodEnd) - Date.now();
865
+ function periodText(billing, nowMs) {
866
+ const remainingMs = epochToMs(billing.periodEnd) - nowMs;
858
867
  if (!Number.isFinite(remainingMs) || remainingMs <= 0) return "\u2014";
859
868
  const days = Math.floor(remainingMs / 864e5);
860
869
  if (days >= 1) return `${days}d left`;
@@ -1105,7 +1114,7 @@ function PeekBody({
1105
1114
  meta3 ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: ` ${meta3}` }) : null
1106
1115
  ] }),
1107
1116
  /* @__PURE__ */ jsx11(Box9, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx11(SummarySection, { item, summary }) }),
1108
- /* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "\u23CE open \xB7 t transcript \xB7 o web" }) })
1117
+ /* @__PURE__ */ jsx11(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "\u23CE open \xB7 t transcript" }) })
1109
1118
  ] });
1110
1119
  }
1111
1120
  function SummarySection({
@@ -1120,7 +1129,7 @@ function SummarySection({
1120
1129
  }
1121
1130
  const points = (summary.keyPoints ?? []).slice(0, 3);
1122
1131
  return /* @__PURE__ */ jsxs8(Fragment3, { children: [
1123
- /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: "SUMMARY" }),
1132
+ /* @__PURE__ */ jsx11(Text9, { bold: true, dimColor: true, children: "SUMMARY" }),
1124
1133
  /* @__PURE__ */ jsx11(Text9, { children: summary.tldr }),
1125
1134
  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
1126
1135
  ] });
@@ -1630,7 +1639,7 @@ function PermissionPreflightView({
1630
1639
  return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, children: [
1631
1640
  /* @__PURE__ */ jsxs13(Text14, { children: [
1632
1641
  /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "\u2039 " }),
1633
- /* @__PURE__ */ jsx16(Text14, { bold: true, color: "cyan", children: "Recording permissions" })
1642
+ /* @__PURE__ */ jsx16(Text14, { bold: true, children: "Recording permissions" })
1634
1643
  ] }),
1635
1644
  /* @__PURE__ */ jsx16(Box14, { marginTop: 1, flexDirection: "column", children: items.length === 0 ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Checking permissions\u2026" }) : items.map((item) => {
1636
1645
  const status = statusGlyph2(item.status);
@@ -1677,7 +1686,7 @@ var init_PermissionPreflightView = __esm({
1677
1686
  });
1678
1687
 
1679
1688
  // src/tui/RecordSetupView.tsx
1680
- import { useEffect as useEffect3, useState as useState6 } from "react";
1689
+ import { useEffect as useEffect3, useRef, useState as useState6 } from "react";
1681
1690
  import { Box as Box15, Text as Text15, useInput as useInput5 } from "ink";
1682
1691
  import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1683
1692
  function levelDb2(level) {
@@ -1693,7 +1702,7 @@ function InputMeter({ level }) {
1693
1702
  if (level == null) return /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "\u2014" });
1694
1703
  const silent = level <= 0.03;
1695
1704
  return /* @__PURE__ */ jsxs14(Text15, { children: [
1696
- /* @__PURE__ */ jsx17(Text15, { color: silent ? "yellow" : "green", children: meterBar(level, METER_W) }),
1705
+ /* @__PURE__ */ jsx17(Text15, { color: silent ? "yellow" : "cyan", children: meterBar(level, METER_W) }),
1697
1706
  /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: ` ${levelDb2(level)}` })
1698
1707
  ] });
1699
1708
  }
@@ -1710,6 +1719,7 @@ function RecordSetupView({
1710
1719
  () => Math.max(0, model.microphones?.findIndex((device) => device.isDefault) ?? 0)
1711
1720
  );
1712
1721
  const [sceneIdx, setSceneIdx] = useState6(0);
1722
+ const userPickedMic = useRef(false);
1713
1723
  const sources = model.sources;
1714
1724
  const microphones = model.microphones ?? [];
1715
1725
  const selected = sources[Math.min(srcIdx, Math.max(0, sources.length - 1))];
@@ -1725,14 +1735,29 @@ function RecordSetupView({
1725
1735
  };
1726
1736
  useEffect3(() => {
1727
1737
  onSelectionChange?.(selection);
1728
- }, [srcIdx, includeMic, micIdx]);
1738
+ }, [
1739
+ includeMic,
1740
+ onSelectionChange,
1741
+ selection.includeMicrophone,
1742
+ selection.microphoneDeviceId,
1743
+ selection.sceneId,
1744
+ selection.sourceId,
1745
+ srcIdx,
1746
+ micIdx
1747
+ ]);
1748
+ useEffect3(() => {
1749
+ if (userPickedMic.current || microphones.length === 0) return;
1750
+ const di = microphones.findIndex((device) => device.isDefault);
1751
+ if (di > 0) setMicIdx(di);
1752
+ }, [microphones.length]);
1729
1753
  useInput5((input, key) => {
1730
1754
  if (key.upArrow || input === "k") setSrcIdx((i) => Math.max(0, i - 1));
1731
1755
  else if (key.downArrow || input === "j") setSrcIdx((i) => Math.min(sources.length - 1, i + 1));
1732
1756
  else if (input === " ") setIncludeMic((m) => !m);
1733
- else if (input === "m" && includeMic && hasMultipleMicrophones)
1757
+ else if (input === "m" && includeMic && hasMultipleMicrophones) {
1758
+ userPickedMic.current = true;
1734
1759
  setMicIdx((i) => (i + 1) % microphones.length);
1735
- else if (input === "s" && model.scenes.length > 1) setSceneIdx((i) => (i + 1) % model.scenes.length);
1760
+ } else if (input === "s" && model.scenes.length > 1) setSceneIdx((i) => (i + 1) % model.scenes.length);
1736
1761
  else if (key.return && selected) onStart(selection);
1737
1762
  else if (key.escape) onCancel();
1738
1763
  });
@@ -2036,7 +2061,8 @@ function AppShell({
2036
2061
  if (event.type !== "audio.level") return;
2037
2062
  const level = levelFromRmsDb(event.rmsDb);
2038
2063
  if (event.input === "microphone") {
2039
- const microphoneId = event.microphoneDeviceId ?? selection.microphoneDeviceId;
2064
+ const microphones = recordSetupModel.microphones ?? [];
2065
+ const microphoneId = event.microphoneDeviceId ?? selection.microphoneDeviceId ?? microphones.find((device) => device.isDefault)?.id ?? microphones[0]?.id;
2040
2066
  if (!microphoneId) return;
2041
2067
  setRecordSetupLevels((current) => ({
2042
2068
  ...current,
@@ -2061,6 +2087,7 @@ function AppShell({
2061
2087
  recordSetupSelection.includeMicrophone,
2062
2088
  recordSetupSelection.microphoneDeviceId,
2063
2089
  recordSetupSelection.sourceId,
2090
+ recordSetupModel.microphones,
2064
2091
  recordSetupModel.sources,
2065
2092
  screen.kind,
2066
2093
  startRecordSetupPreview
@@ -2100,7 +2127,9 @@ function AppShell({
2100
2127
  };
2101
2128
  });
2102
2129
  }).catch((error51) => {
2103
- setLiveRecord(recordErrorState(error51, selection));
2130
+ setLiveRecord(
2131
+ (current) => current?.kind === "starting" ? recordErrorState(error51, selection) : current
2132
+ );
2104
2133
  });
2105
2134
  },
2106
2135
  [now, recordSetupModel.sources, startLiveRecord]
@@ -2120,7 +2149,7 @@ function AppShell({
2120
2149
  const data = await current.session.stop();
2121
2150
  const artifact = recordingArtifactFromRecordData(data);
2122
2151
  const fallbackDuration = current.telemetry.startedAtMs != null ? Math.max(0, now() - current.telemetry.startedAtMs) : void 0;
2123
- setLiveRecord({
2152
+ const stoppedRecord = {
2124
2153
  kind: "stopped",
2125
2154
  selection: current.selection,
2126
2155
  artifact,
@@ -2130,17 +2159,25 @@ function AppShell({
2130
2159
  ...artifact.durationMs == null && fallbackDuration != null ? { durationMs: fallbackDuration } : {},
2131
2160
  status: "stopped"
2132
2161
  }
2162
+ };
2163
+ setLiveRecord((next) => {
2164
+ if (next?.kind !== "stopping" || next.session !== current.session) return next;
2165
+ return stoppedRecord;
2133
2166
  });
2134
2167
  void refreshDownloadedIds();
2135
2168
  } catch (error51) {
2136
- setLiveRecord({
2137
- kind: "error",
2138
- message: error51 instanceof Error ? error51.message : String(error51)
2169
+ setLiveRecord((next) => {
2170
+ if (next?.kind !== "stopping" || next.session !== current.session) return next;
2171
+ return {
2172
+ kind: "error",
2173
+ message: error51 instanceof Error ? error51.message : String(error51)
2174
+ };
2139
2175
  });
2140
2176
  }
2141
2177
  return;
2142
2178
  }
2143
- if (current?.kind === "stopped" || current?.kind === "error") setLiveRecord(void 0);
2179
+ if (current?.kind === "starting" || current?.kind === "stopping" || current?.kind === "stopped" || current?.kind === "error")
2180
+ setLiveRecord(void 0);
2144
2181
  setStack([{ kind: "overview" }]);
2145
2182
  }, [liveRecord, now, refreshDownloadedIds]);
2146
2183
  const liveSession = liveRecord?.kind === "live" ? liveRecord.session : void 0;
@@ -2455,7 +2492,11 @@ function AppShell({
2455
2492
  void transcribeStoppedRecording();
2456
2493
  return;
2457
2494
  }
2458
- if (input === "q" || key.escape || key.leftArrow || input === "n") void stopLiveRecord();
2495
+ if (liveRecord?.kind === "stopped" && input === "n") {
2496
+ void stopLiveRecord();
2497
+ return;
2498
+ }
2499
+ if (input === "q" || key.escape || key.leftArrow) void stopLiveRecord();
2459
2500
  return;
2460
2501
  }
2461
2502
  if (input === "q") return exit();
@@ -2479,6 +2520,8 @@ function AppShell({
2479
2520
  }
2480
2521
  if (input === "r") return void refresh({ resetRecordings: true });
2481
2522
  if (screen.kind === "overview") {
2523
+ if (input === "g") setSelected(0);
2524
+ if (input === "G") setSelected(Math.max(0, recordings.length - 1));
2482
2525
  if (key.upArrow || input === "k") setSelected((i) => Math.max(0, i - 1));
2483
2526
  if (key.downArrow || input === "j") setSelected((i) => Math.min(recordings.length - 1, i + 1));
2484
2527
  const rec = recordings[selected];
@@ -2488,6 +2531,8 @@ function AppShell({
2488
2531
  return;
2489
2532
  }
2490
2533
  if (screen.kind === "jobs") {
2534
+ if (input === "g") setSelected(0);
2535
+ if (input === "G") setSelected(Math.max(0, jobs.length - 1));
2491
2536
  if (key.upArrow || input === "k") setSelected((i) => Math.max(0, i - 1));
2492
2537
  if (key.downArrow || input === "j") setSelected((i) => Math.min(jobs.length - 1, i + 1));
2493
2538
  const job = jobs[selected];
@@ -2621,7 +2666,7 @@ function AppShell({
2621
2666
  );
2622
2667
  } else if (screen.kind === "account") {
2623
2668
  position = "";
2624
- body = /* @__PURE__ */ jsx18(AccountView, { status: accountStatus });
2669
+ body = /* @__PURE__ */ jsx18(AccountView, { status: accountStatus, nowMs: now() });
2625
2670
  } else {
2626
2671
  const win = listWindow(selected, jobs.length, Math.max(3, size.rows - 4));
2627
2672
  position = jobs.length ? `${selected + 1} / ${jobs.length}` : "0";
@@ -2634,7 +2679,7 @@ function AppShell({
2634
2679
  }
2635
2680
  );
2636
2681
  }
2637
- const footerKeys = screen.kind === "jobs" ? `${position} \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 n record \xB7 1 overview \xB7 3 account \xB7 r refresh \xB7 q quit` : screen.kind === "account" ? "3 account \xB7 n record \xB7 1 overview \xB7 2 jobs \xB7 r refresh \xB7 q quit" : `${position} \xB7 \u2191\u2193 scroll \xB7 \u23CE open \xB7 t transcript \xB7 n record \xB7 2 jobs \xB7 3 account \xB7 r refresh \xB7 q quit`;
2682
+ const footerKeys = screen.kind === "jobs" ? `${position} \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 n record \xB7 1 overview \xB7 3 account \xB7 r refresh \xB7 q quit` : screen.kind === "account" ? "Account \xB7 n record \xB7 1 overview \xB7 2 jobs \xB7 r refresh \xB7 q quit" : `${position} \xB7 \u2191\u2193 scroll \xB7 \u23CE open \xB7 t transcript \xB7 n record \xB7 2 jobs \xB7 3 account \xB7 r refresh \xB7 q quit`;
2638
2683
  return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
2639
2684
  /* @__PURE__ */ jsx18(Header, { active: tab }),
2640
2685
  /* @__PURE__ */ jsxs15(Box16, { flexGrow: 1, flexDirection: "column", children: [