recappi 0.1.13 → 0.1.15

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
@@ -1339,10 +1339,71 @@ var init_PermissionPreflightView = __esm({
1339
1339
  }
1340
1340
  });
1341
1341
 
1342
- // src/tui/AppShell.tsx
1343
- import { useCallback, useEffect as useEffect2, useState as useState5 } from "react";
1344
- import { Box as Box14, Text as Text14, useApp, useInput as useInput5 } from "ink";
1342
+ // src/tui/RecordingScreen.tsx
1343
+ import { useEffect as useEffect2, useState as useState5 } from "react";
1344
+ import { Box as Box14, Text as Text14 } from "ink";
1345
1345
  import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1346
+ function RecordingScreen({
1347
+ source,
1348
+ savedPath,
1349
+ now = () => Date.now()
1350
+ }) {
1351
+ const [state, setState] = useState5(initialLiveCaptionsState);
1352
+ const [tick, setTick] = useState5(() => now());
1353
+ useEffect2(() => {
1354
+ const unsubscribe = source.onEvent((event) => {
1355
+ const mapped = sidecarToLiveCaptionEvent(event);
1356
+ if (mapped) setState((s) => liveCaptionReducer(s, mapped));
1357
+ });
1358
+ return unsubscribe;
1359
+ }, [source]);
1360
+ useEffect2(() => {
1361
+ const id = setInterval(() => setTick(now()), 1e3);
1362
+ return () => clearInterval(id);
1363
+ }, [now]);
1364
+ const elapsed = state.startedAtMs != null ? formatClockMs(Math.max(0, tick - state.startedAtMs)) : null;
1365
+ let body;
1366
+ switch (state.status) {
1367
+ case "live":
1368
+ body = /* @__PURE__ */ jsxs13(Fragment4, { children: [
1369
+ /* @__PURE__ */ jsxs13(Text14, { children: [
1370
+ /* @__PURE__ */ jsx16(Text14, { bold: true, color: "red", children: "\u25CF Recording" }),
1371
+ elapsed ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: ` ${elapsed}` }) : null
1372
+ ] }),
1373
+ /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Recording to your Mac. Press q to stop and save." })
1374
+ ] });
1375
+ break;
1376
+ case "stopped":
1377
+ body = /* @__PURE__ */ jsxs13(Fragment4, { children: [
1378
+ /* @__PURE__ */ jsx16(Text14, { color: "green", children: "\u2713 Recording saved to your Mac." }),
1379
+ savedPath ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, wrap: "truncate-middle", children: savedPath }) : null
1380
+ ] });
1381
+ break;
1382
+ case "error":
1383
+ body = /* @__PURE__ */ jsx16(Text14, { color: "red", children: state.error ? `Recording error: ${state.error}` : "Recording error" });
1384
+ break;
1385
+ default:
1386
+ body = /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Starting recording\u2026" });
1387
+ }
1388
+ const footer = state.status === "stopped" ? "esc / \u2190 back" : "q / esc / \u2190 stop & save";
1389
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, children: [
1390
+ /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "\u2039 Record" }),
1391
+ /* @__PURE__ */ jsx16(Box14, { marginTop: 1, flexDirection: "column", children: body }),
1392
+ /* @__PURE__ */ jsx16(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: footer }) })
1393
+ ] });
1394
+ }
1395
+ var init_RecordingScreen = __esm({
1396
+ "src/tui/RecordingScreen.tsx"() {
1397
+ "use strict";
1398
+ init_format();
1399
+ init_liveCaptions();
1400
+ }
1401
+ });
1402
+
1403
+ // src/tui/AppShell.tsx
1404
+ import { useCallback, useEffect as useEffect3, useState as useState6 } from "react";
1405
+ import { Box as Box15, Text as Text15, useApp, useInput as useInput5 } from "ink";
1406
+ import { Fragment as Fragment5, jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1346
1407
  function recordErrorCopy(code, message) {
1347
1408
  switch (code) {
1348
1409
  case "record.helper_unavailable":
@@ -1371,8 +1432,8 @@ function recordErrorCopy(code, message) {
1371
1432
  };
1372
1433
  case "record.capture_failed":
1373
1434
  return {
1374
- title: "Recording couldn't capture audio.",
1375
- detail: "Check permissions, make sure audio is playing, then try again.",
1435
+ title: "Couldn't start recording.",
1436
+ detail: "Recording failed to start \u2014 please try again.",
1376
1437
  tone: "red"
1377
1438
  };
1378
1439
  default:
@@ -1429,27 +1490,27 @@ function AppShell({
1429
1490
  }) {
1430
1491
  const { exit } = useApp();
1431
1492
  const size = useTerminalSize();
1432
- const [jobs, setJobs] = useState5([]);
1433
- const [recordings, setRecordings] = useState5([]);
1434
- const [recordingsNextCursor, setRecordingsNextCursor] = useState5(null);
1435
- const [recordingsTotalCount, setRecordingsTotalCount] = useState5(void 0);
1436
- const [stats, setStats] = useState5(void 0);
1437
- const [accountStatus, setAccountStatus] = useState5("loading");
1438
- const [origin, setOrigin] = useState5("");
1439
- const [stack, setStack] = useState5([{ kind: initialView }]);
1440
- const [selected, setSelected] = useState5(0);
1441
- const [spinnerFrame, setSpinnerFrame] = useState5(0);
1442
- const [loadingMoreRecordings, setLoadingMoreRecordings] = useState5(false);
1443
- const [loadError, setLoadError] = useState5(void 0);
1444
- const [notice, setNotice] = useState5(void 0);
1445
- const [summaryCache, setSummaryCache] = useState5(() => /* @__PURE__ */ new Map());
1446
- const [transcriptCache, setTranscriptCache] = useState5(
1493
+ const [jobs, setJobs] = useState6([]);
1494
+ const [recordings, setRecordings] = useState6([]);
1495
+ const [recordingsNextCursor, setRecordingsNextCursor] = useState6(null);
1496
+ const [recordingsTotalCount, setRecordingsTotalCount] = useState6(void 0);
1497
+ const [stats, setStats] = useState6(void 0);
1498
+ const [accountStatus, setAccountStatus] = useState6("loading");
1499
+ const [origin, setOrigin] = useState6("");
1500
+ const [stack, setStack] = useState6([{ kind: initialView }]);
1501
+ const [selected, setSelected] = useState6(0);
1502
+ const [spinnerFrame, setSpinnerFrame] = useState6(0);
1503
+ const [loadingMoreRecordings, setLoadingMoreRecordings] = useState6(false);
1504
+ const [loadError, setLoadError] = useState6(void 0);
1505
+ const [notice, setNotice] = useState6(void 0);
1506
+ const [summaryCache, setSummaryCache] = useState6(() => /* @__PURE__ */ new Map());
1507
+ const [transcriptCache, setTranscriptCache] = useState6(
1447
1508
  () => /* @__PURE__ */ new Map()
1448
1509
  );
1449
- const [audioCache, setAudioCache] = useState5(() => /* @__PURE__ */ new Map());
1450
- const [downloadedIds, setDownloadedIds] = useState5(() => /* @__PURE__ */ new Set());
1451
- const [liveRecord, setLiveRecord] = useState5(void 0);
1452
- const [recordRetryNonce, setRecordRetryNonce] = useState5(0);
1510
+ const [audioCache, setAudioCache] = useState6(() => /* @__PURE__ */ new Map());
1511
+ const [downloadedIds, setDownloadedIds] = useState6(() => /* @__PURE__ */ new Set());
1512
+ const [liveRecord, setLiveRecord] = useState6(void 0);
1513
+ const [recordRetryNonce, setRecordRetryNonce] = useState6(0);
1453
1514
  const refreshDownloadedIds = useCallback(async () => {
1454
1515
  if (!listDownloadedRecordingIds) return;
1455
1516
  try {
@@ -1457,7 +1518,7 @@ function AppShell({
1457
1518
  } catch {
1458
1519
  }
1459
1520
  }, [listDownloadedRecordingIds]);
1460
- useEffect2(() => {
1521
+ useEffect3(() => {
1461
1522
  void refreshDownloadedIds();
1462
1523
  }, [refreshDownloadedIds]);
1463
1524
  const screen = stack[stack.length - 1];
@@ -1474,7 +1535,7 @@ function AppShell({
1474
1535
  setLiveRecord(void 0);
1475
1536
  setStack([{ kind: "overview" }]);
1476
1537
  }, [liveRecord]);
1477
- useEffect2(() => {
1538
+ useEffect3(() => {
1478
1539
  if (screen.kind !== "record") return;
1479
1540
  if (!startLiveRecord) {
1480
1541
  setLiveRecord({
@@ -1503,7 +1564,7 @@ function AppShell({
1503
1564
  }, [screen.kind, startLiveRecord, recordRetryNonce]);
1504
1565
  const selectedRecording = screen.kind === "overview" ? recordings[selected] : void 0;
1505
1566
  const peekTranscriptId = selectedRecording?.activeTranscriptId ?? void 0;
1506
- useEffect2(() => {
1567
+ useEffect3(() => {
1507
1568
  if (!peekTranscriptId || summaryCache.has(peekTranscriptId)) return;
1508
1569
  let cancelled = false;
1509
1570
  const timer = setTimeout(() => {
@@ -1571,13 +1632,13 @@ function AppShell({
1571
1632
  setLoadingMoreRecordings(false);
1572
1633
  }
1573
1634
  }, [fetchRecordings, loadingMoreRecordings, recordingsNextCursor]);
1574
- useEffect2(() => {
1635
+ useEffect3(() => {
1575
1636
  void refresh({ resetRecordings: true });
1576
1637
  const id = setInterval(() => void refresh(), pollMs);
1577
1638
  return () => clearInterval(id);
1578
1639
  }, [refresh, pollMs]);
1579
1640
  const hasRunning = jobs.some((item) => item.status === "running");
1580
- useEffect2(() => {
1641
+ useEffect3(() => {
1581
1642
  if (!hasRunning) return;
1582
1643
  const id = setInterval(() => setSpinnerFrame((f) => f + 1), spinnerMs);
1583
1644
  return () => clearInterval(id);
@@ -1591,11 +1652,11 @@ function AppShell({
1591
1652
  }
1592
1653
  }
1593
1654
  const listLength = screen.kind === "jobs" ? jobs.length : screen.kind === "overview" ? recordings.length : 0;
1594
- useEffect2(() => {
1655
+ useEffect3(() => {
1595
1656
  setSelected((i) => Math.max(0, Math.min(i, Math.max(0, listLength - 1))));
1596
1657
  }, [listLength]);
1597
1658
  const visibleRecordingRows = Math.max(3, size.rows - 6);
1598
- useEffect2(() => {
1659
+ useEffect3(() => {
1599
1660
  if (screen.kind !== "overview" || !recordingsNextCursor) return;
1600
1661
  const nearLoadedEnd = recordings.length - selected <= RECORDINGS_PREFETCH_REMAINING;
1601
1662
  const underfilledViewport = recordings.length < visibleRecordingRows;
@@ -1628,7 +1689,7 @@ function AppShell({
1628
1689
  [fetchTranscript]
1629
1690
  );
1630
1691
  const detailTranscriptId = screen.kind === "recordingDetail" ? recordings.find((r) => r.recordingId === screen.recordingId)?.activeTranscriptId : void 0;
1631
- useEffect2(() => {
1692
+ useEffect3(() => {
1632
1693
  if (!detailTranscriptId || transcriptCache.has(detailTranscriptId)) return;
1633
1694
  let cancelled = false;
1634
1695
  setTranscriptCache((m) => new Map(m).set(detailTranscriptId, "loading"));
@@ -1741,18 +1802,18 @@ function AppShell({
1741
1802
  }
1742
1803
  });
1743
1804
  if (screen.kind === "transcript") {
1744
- return /* @__PURE__ */ jsx16(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
1805
+ return /* @__PURE__ */ jsx17(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
1745
1806
  }
1746
1807
  if (screen.kind === "jobDetail") {
1747
1808
  const job = jobs.find((j) => j.jobId === screen.jobId);
1748
- if (!job) return /* @__PURE__ */ jsx16(Missing, { label: "Job" });
1749
- return /* @__PURE__ */ jsx16(Detail, { notice, children: /* @__PURE__ */ jsx16(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
1809
+ if (!job) return /* @__PURE__ */ jsx17(Missing, { label: "Job" });
1810
+ return /* @__PURE__ */ jsx17(Detail, { notice, children: /* @__PURE__ */ jsx17(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
1750
1811
  }
1751
1812
  if (screen.kind === "recordingDetail") {
1752
1813
  const rec = recordings.find((r) => r.recordingId === screen.recordingId);
1753
- if (!rec) return /* @__PURE__ */ jsx16(Missing, { label: "Recording" });
1814
+ if (!rec) return /* @__PURE__ */ jsx17(Missing, { label: "Recording" });
1754
1815
  const detailTranscript = rec.activeTranscriptId ? transcriptCache.get(rec.activeTranscriptId) : void 0;
1755
- return /* @__PURE__ */ jsx16(Detail, { notice, children: /* @__PURE__ */ jsx16(
1816
+ return /* @__PURE__ */ jsx17(Detail, { notice, children: /* @__PURE__ */ jsx17(
1756
1817
  RecordingDetailView,
1757
1818
  {
1758
1819
  item: rec,
@@ -1763,22 +1824,22 @@ function AppShell({
1763
1824
  ) });
1764
1825
  }
1765
1826
  if (screen.kind === "record") {
1766
- if (liveRecord?.kind === "live") {
1767
- return /* @__PURE__ */ jsx16(LiveCaptionsScreen, { source: liveRecord.session.source, now });
1827
+ if (liveRecord?.kind === "live" && liveRecord.session.mode === "live_captions") {
1828
+ return /* @__PURE__ */ jsx17(LiveCaptionsScreen, { source: liveRecord.session.source, now });
1768
1829
  }
1769
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
1770
- /* @__PURE__ */ jsx16(Header, { active: "record" }),
1771
- /* @__PURE__ */ jsx16(Box14, { flexGrow: 1, flexDirection: "column", paddingX: 1, paddingTop: 1, children: liveRecord?.kind === "error" ? (() => {
1830
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
1831
+ /* @__PURE__ */ jsx17(Header, { active: "record" }),
1832
+ /* @__PURE__ */ jsx17(Box15, { flexGrow: 1, flexDirection: "column", paddingX: 1, paddingTop: 1, children: liveRecord?.kind === "live" ? /* @__PURE__ */ jsx17(RecordingScreen, { source: liveRecord.session.source, now }) : liveRecord?.kind === "error" ? (() => {
1772
1833
  if (liveRecord.code === "record.permission_required") {
1773
- return /* @__PURE__ */ jsx16(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
1834
+ return /* @__PURE__ */ jsx17(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
1774
1835
  }
1775
1836
  const copy = recordErrorCopy(liveRecord.code, liveRecord.message);
1776
- return /* @__PURE__ */ jsxs13(Fragment4, { children: [
1777
- /* @__PURE__ */ jsx16(Text14, { color: copy.tone, children: copy.title }),
1778
- copy.detail ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: copy.detail }) : null
1837
+ return /* @__PURE__ */ jsxs14(Fragment5, { children: [
1838
+ /* @__PURE__ */ jsx17(Text15, { color: copy.tone, children: copy.title }),
1839
+ copy.detail ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: copy.detail }) : null
1779
1840
  ] });
1780
- })() : /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Starting live recording\u2026" }) }),
1781
- /* @__PURE__ */ jsx16(Footer, { keys: "q / esc / \u2190 back" })
1841
+ })() : /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Starting live recording\u2026" }) }),
1842
+ /* @__PURE__ */ jsx17(Footer, { keys: "q / esc / \u2190 back" })
1782
1843
  ] });
1783
1844
  }
1784
1845
  const tab = screen.kind === "jobs" ? "jobs" : screen.kind === "account" ? "account" : "overview";
@@ -1796,7 +1857,7 @@ function AppShell({
1796
1857
  const showPeek = size.columns >= 100;
1797
1858
  const peekWidth = showPeek ? 34 : 0;
1798
1859
  const listColumns = showPeek ? Math.max(30, size.columns - peekWidth - 3) : size.columns;
1799
- body = /* @__PURE__ */ jsx16(
1860
+ body = /* @__PURE__ */ jsx17(
1800
1861
  OverviewView,
1801
1862
  {
1802
1863
  recordings: recordings.slice(win.start, win.end),
@@ -1816,11 +1877,11 @@ function AppShell({
1816
1877
  );
1817
1878
  } else if (screen.kind === "account") {
1818
1879
  position = "";
1819
- body = /* @__PURE__ */ jsx16(AccountView, { status: accountStatus });
1880
+ body = /* @__PURE__ */ jsx17(AccountView, { status: accountStatus });
1820
1881
  } else {
1821
1882
  const win = listWindow(selected, jobs.length, Math.max(3, size.rows - 4));
1822
1883
  position = jobs.length ? `${selected + 1} / ${jobs.length}` : "0";
1823
- body = /* @__PURE__ */ jsx16(
1884
+ body = /* @__PURE__ */ jsx17(
1824
1885
  JobsView,
1825
1886
  {
1826
1887
  items: jobs.slice(win.start, win.end),
@@ -1830,34 +1891,34 @@ function AppShell({
1830
1891
  );
1831
1892
  }
1832
1893
  const footerKeys = screen.kind === "jobs" ? `${position} \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 1 overview \xB7 3 account \xB7 4 record \xB7 r refresh \xB7 q quit` : screen.kind === "account" ? "3 account \xB7 1 overview \xB7 2 jobs \xB7 4 record \xB7 r refresh \xB7 q quit" : `${position} \xB7 \u2191\u2193 scroll \xB7 \u23CE open \xB7 t transcript \xB7 2 jobs \xB7 3 account \xB7 4 record \xB7 r refresh \xB7 q quit`;
1833
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
1834
- /* @__PURE__ */ jsx16(Header, { active: tab }),
1835
- /* @__PURE__ */ jsxs13(Box14, { flexGrow: 1, flexDirection: "column", children: [
1894
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
1895
+ /* @__PURE__ */ jsx17(Header, { active: tab }),
1896
+ /* @__PURE__ */ jsxs14(Box15, { flexGrow: 1, flexDirection: "column", children: [
1836
1897
  body,
1837
- loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx16(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
1898
+ loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { color: "red", children: [
1838
1899
  "! ",
1839
1900
  loadError
1840
1901
  ] }) }) : null
1841
1902
  ] }),
1842
- /* @__PURE__ */ jsx16(Footer, { keys: footerKeys })
1903
+ /* @__PURE__ */ jsx17(Footer, { keys: footerKeys })
1843
1904
  ] });
1844
1905
  }
1845
1906
  function Detail({
1846
1907
  notice,
1847
1908
  children
1848
1909
  }) {
1849
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
1910
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
1850
1911
  children,
1851
- notice ? /* @__PURE__ */ jsx16(Box14, { paddingX: 1, children: /* @__PURE__ */ jsx16(Text14, { color: "green", children: notice }) }) : null
1912
+ notice ? /* @__PURE__ */ jsx17(Box15, { paddingX: 1, children: /* @__PURE__ */ jsx17(Text15, { color: "green", children: notice }) }) : null
1852
1913
  ] });
1853
1914
  }
1854
1915
  function Missing({ label }) {
1855
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, children: [
1856
- /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
1916
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, children: [
1917
+ /* @__PURE__ */ jsxs14(Text15, { dimColor: true, children: [
1857
1918
  label,
1858
1919
  " no longer in the list."
1859
1920
  ] }),
1860
- /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "esc back \xB7 q quit" })
1921
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "esc back \xB7 q quit" })
1861
1922
  ] });
1862
1923
  }
1863
1924
  var RECORDINGS_PAGE_SIZE, RECORDINGS_PREFETCH_REMAINING;
@@ -1873,6 +1934,7 @@ var init_AppShell = __esm({
1873
1934
  init_TranscriptView();
1874
1935
  init_LiveCaptionsScreen();
1875
1936
  init_PermissionPreflightView();
1937
+ init_RecordingScreen();
1876
1938
  init_format();
1877
1939
  init_terminal();
1878
1940
  RECORDINGS_PAGE_SIZE = 50;
@@ -1891,7 +1953,7 @@ __export(tui_exports, {
1891
1953
  runDashboard: () => runDashboard,
1892
1954
  useTerminalSize: () => useTerminalSize
1893
1955
  });
1894
- import React7 from "react";
1956
+ import React8 from "react";
1895
1957
  import { render as render2 } from "ink";
1896
1958
  import { spawn as spawn3 } from "child_process";
1897
1959
  function openUrl(url2) {
@@ -1912,7 +1974,7 @@ function copyText(text) {
1912
1974
  async function runDashboard(deps) {
1913
1975
  const renderApp = deps.renderApp ?? render2;
1914
1976
  const app = renderApp(
1915
- React7.createElement(AppShell, {
1977
+ React8.createElement(AppShell, {
1916
1978
  fetchJobs: deps.fetchJobs,
1917
1979
  fetchTranscript: deps.fetchTranscript,
1918
1980
  fetchRecordings: deps.fetchRecordings,
@@ -19748,6 +19810,7 @@ async function recordViaSidecar(opts) {
19748
19810
  async function startLiveRecordSession(opts) {
19749
19811
  const session = await startRecordSession({ ...opts, live: false });
19750
19812
  return {
19813
+ mode: "local",
19751
19814
  source: session.source,
19752
19815
  stop: async () => {
19753
19816
  await session.stop();