recappi 0.1.14 → 0.1.16

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
@@ -9,6 +9,127 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/recordingCore.ts
13
+ function recordingCaptureMappingFromSelection(selection = DEFAULT_RECORDING_SELECTION, sources = DEFAULT_RECORDING_SOURCES) {
14
+ const source = sources.find((candidate) => candidate.id === selection.sourceId) ?? sources[0];
15
+ if (!source) {
16
+ throw new Error("No recording sources are available.");
17
+ }
18
+ if (source.kind === "microphone") {
19
+ return {
20
+ source,
21
+ includeSystemAudio: false,
22
+ includeMicrophone: true,
23
+ sourceLabel: source.label,
24
+ micEnabled: true
25
+ };
26
+ }
27
+ return {
28
+ source,
29
+ // Current helper support is system-wide capture. App-specific targeting is
30
+ // represented in the core model, then falls back to system capture until the
31
+ // Darwin adapter exposes a target app option.
32
+ includeSystemAudio: true,
33
+ includeMicrophone: selection.includeMicrophone,
34
+ sourceLabel: source.label,
35
+ micEnabled: selection.includeMicrophone
36
+ };
37
+ }
38
+ function recordingStatusFromSidecarState(state) {
39
+ switch (state) {
40
+ case "recording":
41
+ return "recording";
42
+ case "stopping":
43
+ case "finalizing":
44
+ case "uploading":
45
+ return "stopping";
46
+ case "completed":
47
+ case "cancelled":
48
+ return "stopped";
49
+ case "failed":
50
+ return "error";
51
+ case "idle":
52
+ case "starting":
53
+ default:
54
+ return "starting";
55
+ }
56
+ }
57
+ function levelFromRmsDb(rmsDb) {
58
+ if (typeof rmsDb !== "number" || Number.isNaN(rmsDb)) return 0;
59
+ return Math.max(0, Math.min(1, (rmsDb + 60) / 60));
60
+ }
61
+ function applyRecordingEventToTelemetry(telemetry, event) {
62
+ if (event.type === "recording.state") {
63
+ return {
64
+ ...telemetry,
65
+ status: recordingStatusFromSidecarState(event.state),
66
+ ...event.message && event.state === "failed" ? { error: event.message } : {}
67
+ };
68
+ }
69
+ if (event.type === "audio.level") {
70
+ const level = levelFromRmsDb(event.rmsDb);
71
+ if (event.input === "microphone") {
72
+ return { ...telemetry, level: { ...telemetry.level, mic: level } };
73
+ }
74
+ return { ...telemetry, level: { ...telemetry.level, system: level } };
75
+ }
76
+ if (event.type === "error") {
77
+ return { ...telemetry, status: "error", error: event.message };
78
+ }
79
+ return telemetry;
80
+ }
81
+ function recordingArtifactFromRecordData(data) {
82
+ const artifact = data.artifacts.find((item) => item.kind === "recording_session") ?? data.artifacts[0];
83
+ const metadata = isRecord5(artifact?.metadata) ? artifact.metadata : {};
84
+ const audioPath = typeof metadata.audioPath === "string" ? metadata.audioPath : typeof artifact?.localPath === "string" ? artifact.localPath : void 0;
85
+ const durationMs = typeof metadata.durationMs === "number" && Number.isFinite(metadata.durationMs) ? metadata.durationMs : void 0;
86
+ const sizeBytes = typeof metadata.sizeBytes === "number" && Number.isFinite(metadata.sizeBytes) ? metadata.sizeBytes : void 0;
87
+ return {
88
+ sessionId: data.sessionId,
89
+ ...audioPath ? { audioPath } : {},
90
+ ...durationMs != null ? { durationMs } : {},
91
+ ...sizeBytes != null ? { sizeBytes } : {},
92
+ uploadStatus: "local_only",
93
+ transcriptionStatus: "not_started"
94
+ };
95
+ }
96
+ function artifactTelemetryPatch(artifact) {
97
+ return {
98
+ savedPath: artifact.audioPath,
99
+ durationMs: artifact.durationMs,
100
+ sizeBytes: artifact.sizeBytes
101
+ };
102
+ }
103
+ function isRecord5(value) {
104
+ return typeof value === "object" && value !== null && !Array.isArray(value);
105
+ }
106
+ var DEFAULT_RECORDING_SOURCES, DEFAULT_RECORDING_SCENES, DEFAULT_RECORDING_SELECTION;
107
+ var init_recordingCore = __esm({
108
+ "src/recordingCore.ts"() {
109
+ "use strict";
110
+ DEFAULT_RECORDING_SOURCES = [
111
+ {
112
+ id: "system",
113
+ kind: "system",
114
+ label: "System audio",
115
+ canIncludeMicrophone: true
116
+ },
117
+ {
118
+ id: "microphone",
119
+ kind: "microphone",
120
+ label: "Microphone only",
121
+ canIncludeMicrophone: false
122
+ }
123
+ ];
124
+ DEFAULT_RECORDING_SCENES = [{ id: "default", label: "Default" }];
125
+ DEFAULT_RECORDING_SELECTION = {
126
+ sourceId: DEFAULT_RECORDING_SOURCES[0].id,
127
+ includeMicrophone: true,
128
+ sceneId: DEFAULT_RECORDING_SCENES[0].id
129
+ };
130
+ }
131
+ });
132
+
12
133
  // src/tui/format.ts
13
134
  function formatClockMs(ms) {
14
135
  if (ms == null || !Number.isFinite(ms) || ms < 0) return "--:--";
@@ -577,8 +698,7 @@ function Header({ active }) {
577
698
  ] }),
578
699
  /* @__PURE__ */ jsx5(Tab, { num: "1", label: "Overview", active: active === "overview" }),
579
700
  /* @__PURE__ */ jsx5(Tab, { num: "2", label: "Jobs", active: active === "jobs" }),
580
- /* @__PURE__ */ jsx5(Tab, { num: "3", label: "Account", active: active === "account" }),
581
- /* @__PURE__ */ jsx5(Tab, { num: "4", label: "Record", active: active === "record" })
701
+ /* @__PURE__ */ jsx5(Tab, { num: "3", label: "Account", active: active === "account" })
582
702
  ] });
583
703
  }
584
704
  function Tab({
@@ -1339,10 +1459,175 @@ var init_PermissionPreflightView = __esm({
1339
1459
  }
1340
1460
  });
1341
1461
 
1462
+ // src/tui/RecordSetupView.tsx
1463
+ import { useState as useState5 } from "react";
1464
+ import { Box as Box14, Text as Text14, useInput as useInput5 } from "ink";
1465
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1466
+ function levelBar(level, width = 8) {
1467
+ const v = Math.max(0, Math.min(1, level ?? 0));
1468
+ const n = Math.round(v * width);
1469
+ return "\u2587".repeat(n) + "\u2581".repeat(width - n);
1470
+ }
1471
+ function RecordSetupView({
1472
+ model,
1473
+ onStart,
1474
+ onCancel
1475
+ }) {
1476
+ const size = useTerminalSize();
1477
+ const [srcIdx, setSrcIdx] = useState5(0);
1478
+ const [includeMic, setIncludeMic] = useState5(true);
1479
+ const [sceneIdx, setSceneIdx] = useState5(0);
1480
+ const sources = model.sources;
1481
+ const selected = sources[Math.min(srcIdx, Math.max(0, sources.length - 1))];
1482
+ const micApplicable = selected?.canIncludeMicrophone ?? selected?.kind !== "microphone";
1483
+ const wide = size.columns >= 100;
1484
+ useInput5((input, key) => {
1485
+ if (key.upArrow || input === "k") setSrcIdx((i) => Math.max(0, i - 1));
1486
+ else if (key.downArrow || input === "j") setSrcIdx((i) => Math.min(sources.length - 1, i + 1));
1487
+ else if (input === " " && micApplicable) setIncludeMic((m) => !m);
1488
+ else if (input === "s" && model.scenes.length > 1) setSceneIdx((i) => (i + 1) % model.scenes.length);
1489
+ else if (key.return && selected) {
1490
+ onStart({
1491
+ sourceId: selected.id,
1492
+ includeMicrophone: micApplicable ? includeMic : false,
1493
+ sceneId: model.scenes[sceneIdx]?.id
1494
+ });
1495
+ } else if (key.escape) onCancel();
1496
+ });
1497
+ const sourceList = /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
1498
+ /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "SOURCE" }),
1499
+ sources.map((s, i) => {
1500
+ const on = i === srcIdx;
1501
+ return /* @__PURE__ */ jsxs13(Text14, { color: on ? "cyan" : void 0, wrap: "truncate-end", children: [
1502
+ on ? "\u25B8 \u25CF " : " \u25CB ",
1503
+ s.label
1504
+ ] }, s.id);
1505
+ })
1506
+ ] });
1507
+ const preview = /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
1508
+ /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "INPUT PREVIEW" }),
1509
+ /* @__PURE__ */ jsx16(Text14, { color: "green", children: levelBar(model.previewLevel, 10) }),
1510
+ /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "live level of selection" })
1511
+ ] });
1512
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, children: [
1513
+ /* @__PURE__ */ jsx16(Text14, { bold: true, color: "magenta", children: "New recording" }),
1514
+ /* @__PURE__ */ jsxs13(Box14, { marginTop: 1, flexDirection: wide ? "row" : "column", children: [
1515
+ /* @__PURE__ */ jsx16(Box14, { flexGrow: 1, flexDirection: "column", children: sourceList }),
1516
+ wide ? /* @__PURE__ */ jsx16(Box14, { marginLeft: 4, children: preview }) : null
1517
+ ] }),
1518
+ /* @__PURE__ */ jsxs13(Box14, { marginTop: 1, flexDirection: "column", children: [
1519
+ micApplicable ? /* @__PURE__ */ jsxs13(Text14, { children: [
1520
+ /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Microphone " }),
1521
+ /* @__PURE__ */ jsx16(Text14, { color: includeMic ? "green" : "gray", children: includeMic ? "[x] include mic" : "[ ] include mic" }),
1522
+ /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: " (space)" })
1523
+ ] }) : /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Microphone is the source" }),
1524
+ model.scenes.length > 0 ? /* @__PURE__ */ jsxs13(Text14, { children: [
1525
+ /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Scene " }),
1526
+ /* @__PURE__ */ jsx16(Text14, { children: model.scenes[sceneIdx]?.label ?? "Default" }),
1527
+ model.scenes.length > 1 ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: " (s to change)" }) : null
1528
+ ] }) : null
1529
+ ] }),
1530
+ /* @__PURE__ */ jsx16(Box14, { marginTop: 1, children: /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "\u2191\u2193 source \xB7 space mic \xB7 \u23CE start recording \xB7 esc cancel" }) })
1531
+ ] });
1532
+ }
1533
+ var init_RecordSetupView = __esm({
1534
+ "src/tui/RecordSetupView.tsx"() {
1535
+ "use strict";
1536
+ init_terminal();
1537
+ }
1538
+ });
1539
+
1540
+ // src/tui/RecordingHeroScreen.tsx
1541
+ import { useEffect as useEffect2, useState as useState6 } from "react";
1542
+ import { Box as Box15, Text as Text15 } from "ink";
1543
+ import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1544
+ function waveform(samples, width) {
1545
+ if (width <= 0) return "";
1546
+ const tail = samples.slice(-width);
1547
+ const pad = width - tail.length;
1548
+ const cells = tail.map((v) => {
1549
+ const i = Math.max(0, Math.min(BLOCKS.length - 1, Math.round(Math.max(0, Math.min(1, v)) * (BLOCKS.length - 1))));
1550
+ return BLOCKS[i];
1551
+ });
1552
+ return "\u2581".repeat(Math.max(0, pad)) + cells.join("");
1553
+ }
1554
+ function RecordingHeroScreen({
1555
+ telemetry,
1556
+ canPause = false,
1557
+ now = () => Date.now()
1558
+ }) {
1559
+ const size = useTerminalSize();
1560
+ const [tick, setTick] = useState6(() => now());
1561
+ const [wave, setWave] = useState6([]);
1562
+ useEffect2(() => {
1563
+ const lvl = Math.max(telemetry.level?.system ?? 0, telemetry.level?.mic ?? 0);
1564
+ setWave((w) => [...w.slice(-512), lvl]);
1565
+ }, [telemetry.level]);
1566
+ useEffect2(() => {
1567
+ const id = setInterval(() => setTick(now()), 1e3);
1568
+ return () => clearInterval(id);
1569
+ }, []);
1570
+ const elapsed = telemetry.startedAtMs != null ? formatClockMs(Math.max(0, tick - telemetry.startedAtMs)) : "00:00";
1571
+ const innerWidth = Math.max(10, size.columns - 4);
1572
+ if (telemetry.status === "stopped") {
1573
+ const meta3 = [
1574
+ telemetry.durationMs != null ? formatClockMs(telemetry.durationMs) : null,
1575
+ formatBytes2(telemetry.sizeBytes) || null
1576
+ ].filter(Boolean).join(" \xB7 ");
1577
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, children: [
1578
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "recappi \xB7 Recording" }),
1579
+ /* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection: "column", children: [
1580
+ /* @__PURE__ */ jsx17(Text15, { color: "green", children: "\u2713 Saved to your Mac" }),
1581
+ meta3 ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: meta3 }) : null,
1582
+ telemetry.savedPath ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, wrap: "truncate-middle", children: telemetry.savedPath }) : null
1583
+ ] }),
1584
+ /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Transcribe now? \u23CE yes \xB7 n not now \xB7 esc back" }) })
1585
+ ] });
1586
+ }
1587
+ if (telemetry.status === "error") {
1588
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, children: [
1589
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "recappi \xB7 Recording" }),
1590
+ /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text15, { color: "red", children: telemetry.error ? `Recording error: ${telemetry.error}` : "Recording error" }) }),
1591
+ /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "esc back" }) })
1592
+ ] });
1593
+ }
1594
+ const paused = telemetry.status === "paused";
1595
+ const starting = telemetry.status === "starting" || telemetry.status === "stopping";
1596
+ const badge = paused ? "\u23F8 PAUSED" : starting ? "\u2026" : "\u23FA REC";
1597
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, height: size.rows, children: [
1598
+ /* @__PURE__ */ jsxs14(Text15, { children: [
1599
+ /* @__PURE__ */ jsx17(Text15, { bold: true, color: "magenta", children: "recappi" }),
1600
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " \xB7 Recording" })
1601
+ ] }),
1602
+ /* @__PURE__ */ jsxs14(Box15, { flexGrow: 1, flexDirection: "column", justifyContent: "center", alignItems: "center", children: [
1603
+ /* @__PURE__ */ jsx17(Text15, { bold: true, color: paused ? "yellow" : "red", children: badge }),
1604
+ /* @__PURE__ */ jsx17(Text15, { bold: true, children: elapsed }),
1605
+ /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text15, { color: paused ? "gray" : "red", children: waveform(wave, innerWidth) }) }),
1606
+ /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { dimColor: true, children: [
1607
+ telemetry.sourceLabel,
1608
+ telemetry.micEnabled ? " + Microphone" : ""
1609
+ ] }) })
1610
+ ] }),
1611
+ /* @__PURE__ */ jsx17(Box15, { children: /* @__PURE__ */ jsxs14(Text15, { dimColor: true, children: [
1612
+ "q stop & save",
1613
+ canPause ? ` \xB7 p ${paused ? "resume" : "pause"}` : ""
1614
+ ] }) })
1615
+ ] });
1616
+ }
1617
+ var BLOCKS;
1618
+ var init_RecordingHeroScreen = __esm({
1619
+ "src/tui/RecordingHeroScreen.tsx"() {
1620
+ "use strict";
1621
+ init_format();
1622
+ init_terminal();
1623
+ BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
1624
+ }
1625
+ });
1626
+
1342
1627
  // 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";
1345
- import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1628
+ import { useCallback, useEffect as useEffect3, useState as useState7 } from "react";
1629
+ import { Box as Box16, Text as Text16, useApp, useInput as useInput6 } from "ink";
1630
+ import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1346
1631
  function recordErrorCopy(code, message) {
1347
1632
  switch (code) {
1348
1633
  case "record.helper_unavailable":
@@ -1379,36 +1664,37 @@ function recordErrorCopy(code, message) {
1379
1664
  return { title: "Couldn't start recording.", detail: message, tone: "red" };
1380
1665
  }
1381
1666
  }
1382
- function recordErrorState(error51) {
1667
+ function recordErrorState(error51, selection) {
1383
1668
  if (error51 instanceof Error) {
1384
- const descriptor = isRecord6(error51) && isRecord6(error51.descriptor) ? error51.descriptor : void 0;
1669
+ const descriptor = isRecord7(error51) && isRecord7(error51.descriptor) ? error51.descriptor : void 0;
1385
1670
  return {
1386
1671
  kind: "error",
1387
1672
  message: error51.message,
1388
1673
  code: typeof descriptor?.code === "string" ? descriptor.code : "code" in error51 && typeof error51.code === "string" ? error51.code : void 0,
1389
- data: isRecord6(error51) ? error51.data : void 0
1674
+ data: isRecord7(error51) ? error51.data : void 0,
1675
+ ...selection ? { selection } : {}
1390
1676
  };
1391
1677
  }
1392
- return { kind: "error", message: String(error51) };
1678
+ return { kind: "error", message: String(error51), ...selection ? { selection } : {} };
1393
1679
  }
1394
1680
  function permissionItemsFromRecordError(data) {
1395
- const sidecarError = isRecord6(data) ? data : void 0;
1396
- const sidecarData = isRecord6(sidecarError?.data) ? sidecarError.data : void 0;
1681
+ const sidecarError = isRecord7(data) ? data : void 0;
1682
+ const sidecarData = isRecord7(sidecarError?.data) ? sidecarError.data : void 0;
1397
1683
  const permission = typeof sidecarData?.permission === "string" ? sidecarData.permission : "";
1398
1684
  const hint = typeof sidecarData?.recovery === "string" ? sidecarData.recovery : void 0;
1399
1685
  const item = permission === "microphone" ? "Microphone" : permission === "screen_recording" ? "Screen Recording" : "Recording";
1400
1686
  return [{ name: item, status: "denied", ...hint ? { hint } : {} }];
1401
1687
  }
1402
1688
  function settingsUrlFromRecordError(data) {
1403
- const sidecarError = isRecord6(data) ? data : void 0;
1404
- const sidecarData = isRecord6(sidecarError?.data) ? sidecarError.data : void 0;
1689
+ const sidecarError = isRecord7(data) ? data : void 0;
1690
+ const sidecarData = isRecord7(sidecarError?.data) ? sidecarError.data : void 0;
1405
1691
  const permission = typeof sidecarData?.permission === "string" ? sidecarData.permission : "";
1406
1692
  if (permission === "microphone") {
1407
1693
  return "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone";
1408
1694
  }
1409
1695
  return "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
1410
1696
  }
1411
- function isRecord6(value) {
1697
+ function isRecord7(value) {
1412
1698
  return typeof value === "object" && value !== null && !Array.isArray(value);
1413
1699
  }
1414
1700
  function AppShell({
@@ -1429,27 +1715,30 @@ function AppShell({
1429
1715
  }) {
1430
1716
  const { exit } = useApp();
1431
1717
  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(
1718
+ const [jobs, setJobs] = useState7([]);
1719
+ const [recordings, setRecordings] = useState7([]);
1720
+ const [recordingsNextCursor, setRecordingsNextCursor] = useState7(null);
1721
+ const [recordingsTotalCount, setRecordingsTotalCount] = useState7(void 0);
1722
+ const [stats, setStats] = useState7(void 0);
1723
+ const [accountStatus, setAccountStatus] = useState7("loading");
1724
+ const [origin, setOrigin] = useState7("");
1725
+ const [stack, setStack] = useState7([{ kind: initialView }]);
1726
+ const [selected, setSelected] = useState7(0);
1727
+ const [spinnerFrame, setSpinnerFrame] = useState7(0);
1728
+ const [loadingMoreRecordings, setLoadingMoreRecordings] = useState7(false);
1729
+ const [loadError, setLoadError] = useState7(void 0);
1730
+ const [notice, setNotice] = useState7(void 0);
1731
+ const [summaryCache, setSummaryCache] = useState7(() => /* @__PURE__ */ new Map());
1732
+ const [transcriptCache, setTranscriptCache] = useState7(
1447
1733
  () => /* @__PURE__ */ new Map()
1448
1734
  );
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);
1735
+ const [audioCache, setAudioCache] = useState7(() => /* @__PURE__ */ new Map());
1736
+ const [downloadedIds, setDownloadedIds] = useState7(() => /* @__PURE__ */ new Set());
1737
+ const [liveRecord, setLiveRecord] = useState7(void 0);
1738
+ const recordSetupModel = {
1739
+ sources: DEFAULT_RECORDING_SOURCES,
1740
+ scenes: DEFAULT_RECORDING_SCENES
1741
+ };
1453
1742
  const refreshDownloadedIds = useCallback(async () => {
1454
1743
  if (!listDownloadedRecordingIds) return;
1455
1744
  try {
@@ -1457,53 +1746,104 @@ function AppShell({
1457
1746
  } catch {
1458
1747
  }
1459
1748
  }, [listDownloadedRecordingIds]);
1460
- useEffect2(() => {
1749
+ useEffect3(() => {
1461
1750
  void refreshDownloadedIds();
1462
1751
  }, [refreshDownloadedIds]);
1463
1752
  const screen = stack[stack.length - 1];
1753
+ const beginLiveRecord = useCallback(
1754
+ (selection = DEFAULT_RECORDING_SELECTION) => {
1755
+ const capture = recordingCaptureMappingFromSelection(selection, DEFAULT_RECORDING_SOURCES);
1756
+ const telemetry = {
1757
+ status: "starting",
1758
+ startedAtMs: now(),
1759
+ sourceLabel: capture.sourceLabel,
1760
+ micEnabled: capture.micEnabled
1761
+ };
1762
+ setStack((current) => {
1763
+ const withoutSetup = current[current.length - 1]?.kind === "recordSetup" ? current.slice(0, -1) : current;
1764
+ return [...withoutSetup, { kind: "record" }];
1765
+ });
1766
+ if (!startLiveRecord) {
1767
+ setLiveRecord({
1768
+ kind: "error",
1769
+ code: "record.helper_unavailable",
1770
+ message: "Live recording is not available",
1771
+ selection
1772
+ });
1773
+ return;
1774
+ }
1775
+ setLiveRecord({ kind: "starting", selection, telemetry });
1776
+ startLiveRecord(selection).then((session) => {
1777
+ setLiveRecord((current) => {
1778
+ if (current?.kind !== "starting") return current;
1779
+ return {
1780
+ kind: "live",
1781
+ session,
1782
+ selection,
1783
+ telemetry: { ...current.telemetry, status: "recording" }
1784
+ };
1785
+ });
1786
+ }).catch((error51) => {
1787
+ setLiveRecord(recordErrorState(error51, selection));
1788
+ });
1789
+ },
1790
+ [now, startLiveRecord]
1791
+ );
1464
1792
  const stopLiveRecord = useCallback(async () => {
1465
1793
  const current = liveRecord;
1466
1794
  if (current?.kind === "live") {
1467
- setLiveRecord({ kind: "starting" });
1795
+ const stoppingTelemetry = { ...current.telemetry, status: "stopping" };
1796
+ setLiveRecord({
1797
+ kind: "stopping",
1798
+ session: current.session,
1799
+ selection: current.selection,
1800
+ telemetry: stoppingTelemetry
1801
+ });
1468
1802
  try {
1469
- await current.session.stop();
1803
+ const data = await current.session.stop();
1804
+ const artifact = recordingArtifactFromRecordData(data);
1805
+ const fallbackDuration = current.telemetry.startedAtMs != null ? Math.max(0, now() - current.telemetry.startedAtMs) : void 0;
1806
+ setLiveRecord({
1807
+ kind: "stopped",
1808
+ selection: current.selection,
1809
+ artifact,
1810
+ telemetry: {
1811
+ ...stoppingTelemetry,
1812
+ ...artifactTelemetryPatch(artifact),
1813
+ ...artifact.durationMs == null && fallbackDuration != null ? { durationMs: fallbackDuration } : {},
1814
+ status: "stopped"
1815
+ }
1816
+ });
1817
+ void refreshDownloadedIds();
1470
1818
  } catch (error51) {
1471
- setNotice(error51 instanceof Error ? error51.message : String(error51));
1819
+ setLiveRecord({
1820
+ kind: "error",
1821
+ message: error51 instanceof Error ? error51.message : String(error51)
1822
+ });
1472
1823
  }
1824
+ return;
1473
1825
  }
1474
- setLiveRecord(void 0);
1826
+ if (current?.kind === "stopped" || current?.kind === "error") setLiveRecord(void 0);
1475
1827
  setStack([{ kind: "overview" }]);
1476
- }, [liveRecord]);
1477
- useEffect2(() => {
1478
- if (screen.kind !== "record") return;
1479
- if (!startLiveRecord) {
1480
- setLiveRecord({
1481
- kind: "error",
1482
- code: "record.helper_unavailable",
1483
- message: "Live recording is not available"
1828
+ }, [liveRecord, now, refreshDownloadedIds]);
1829
+ const liveSession = liveRecord?.kind === "live" ? liveRecord.session : void 0;
1830
+ useEffect3(() => {
1831
+ if (!liveSession) return;
1832
+ const session = liveSession;
1833
+ const unsubscribe = session.source.onEvent((event) => {
1834
+ setLiveRecord((current) => {
1835
+ if (current?.kind !== "live" || current.session !== session) return current;
1836
+ return {
1837
+ ...current,
1838
+ telemetry: applyRecordingEventToTelemetry(current.telemetry, event)
1839
+ };
1484
1840
  });
1485
- return;
1486
- }
1487
- let cancelled = false;
1488
- setLiveRecord({ kind: "starting" });
1489
- startLiveRecord().then((session) => {
1490
- if (cancelled) {
1491
- void session.stop();
1492
- return;
1493
- }
1494
- setLiveRecord({ kind: "live", session });
1495
- }).catch((error51) => {
1496
- if (!cancelled) {
1497
- setLiveRecord(recordErrorState(error51));
1498
- }
1499
1841
  });
1500
- return () => {
1501
- cancelled = true;
1502
- };
1503
- }, [screen.kind, startLiveRecord, recordRetryNonce]);
1842
+ return unsubscribe;
1843
+ }, [liveSession]);
1504
1844
  const selectedRecording = screen.kind === "overview" ? recordings[selected] : void 0;
1505
1845
  const peekTranscriptId = selectedRecording?.activeTranscriptId ?? void 0;
1506
- useEffect2(() => {
1846
+ useEffect3(() => {
1507
1847
  if (!peekTranscriptId || summaryCache.has(peekTranscriptId)) return;
1508
1848
  let cancelled = false;
1509
1849
  const timer = setTimeout(() => {
@@ -1571,13 +1911,13 @@ function AppShell({
1571
1911
  setLoadingMoreRecordings(false);
1572
1912
  }
1573
1913
  }, [fetchRecordings, loadingMoreRecordings, recordingsNextCursor]);
1574
- useEffect2(() => {
1914
+ useEffect3(() => {
1575
1915
  void refresh({ resetRecordings: true });
1576
1916
  const id = setInterval(() => void refresh(), pollMs);
1577
1917
  return () => clearInterval(id);
1578
1918
  }, [refresh, pollMs]);
1579
1919
  const hasRunning = jobs.some((item) => item.status === "running");
1580
- useEffect2(() => {
1920
+ useEffect3(() => {
1581
1921
  if (!hasRunning) return;
1582
1922
  const id = setInterval(() => setSpinnerFrame((f) => f + 1), spinnerMs);
1583
1923
  return () => clearInterval(id);
@@ -1591,11 +1931,11 @@ function AppShell({
1591
1931
  }
1592
1932
  }
1593
1933
  const listLength = screen.kind === "jobs" ? jobs.length : screen.kind === "overview" ? recordings.length : 0;
1594
- useEffect2(() => {
1934
+ useEffect3(() => {
1595
1935
  setSelected((i) => Math.max(0, Math.min(i, Math.max(0, listLength - 1))));
1596
1936
  }, [listLength]);
1597
1937
  const visibleRecordingRows = Math.max(3, size.rows - 6);
1598
- useEffect2(() => {
1938
+ useEffect3(() => {
1599
1939
  if (screen.kind !== "overview" || !recordingsNextCursor) return;
1600
1940
  const nearLoadedEnd = recordings.length - selected <= RECORDINGS_PREFETCH_REMAINING;
1601
1941
  const underfilledViewport = recordings.length < visibleRecordingRows;
@@ -1628,7 +1968,7 @@ function AppShell({
1628
1968
  [fetchTranscript]
1629
1969
  );
1630
1970
  const detailTranscriptId = screen.kind === "recordingDetail" ? recordings.find((r) => r.recordingId === screen.recordingId)?.activeTranscriptId : void 0;
1631
- useEffect2(() => {
1971
+ useEffect3(() => {
1632
1972
  if (!detailTranscriptId || transcriptCache.has(detailTranscriptId)) return;
1633
1973
  let cancelled = false;
1634
1974
  setTranscriptCache((m) => new Map(m).set(detailTranscriptId, "loading"));
@@ -1675,18 +2015,26 @@ function AppShell({
1675
2015
  setNotice(void 0);
1676
2016
  };
1677
2017
  const back = () => setStack((st) => st.length > 1 ? st.slice(0, -1) : st);
1678
- useInput5((input, key) => {
2018
+ useInput6((input, key) => {
1679
2019
  setNotice(void 0);
2020
+ if (screen.kind === "recordSetup") {
2021
+ if (input === "q" || key.leftArrow) back();
2022
+ return;
2023
+ }
1680
2024
  if (screen.kind === "record") {
1681
2025
  if (liveRecord?.kind === "error" && input === "r") {
1682
- setRecordRetryNonce((value) => value + 1);
2026
+ beginLiveRecord(liveRecord.selection ?? DEFAULT_RECORDING_SELECTION);
1683
2027
  return;
1684
2028
  }
1685
2029
  if (liveRecord?.kind === "error" && input === "o") {
1686
2030
  openUrl2?.(settingsUrlFromRecordError(liveRecord.data));
1687
2031
  return;
1688
2032
  }
1689
- if (input === "q" || key.escape || key.leftArrow) void stopLiveRecord();
2033
+ if (liveRecord?.kind === "stopped" && key.return) {
2034
+ setNotice("Transcribe handoff is coming next.");
2035
+ return;
2036
+ }
2037
+ if (input === "q" || key.escape || key.leftArrow || input === "n") void stopLiveRecord();
1690
2038
  return;
1691
2039
  }
1692
2040
  if (input === "q") return exit();
@@ -1694,7 +2042,10 @@ function AppShell({
1694
2042
  if (input === "1") return goTab("overview");
1695
2043
  if (input === "2") return goTab("jobs");
1696
2044
  if (input === "3") return goTab("account");
1697
- if (input === "4") return goTab("record");
2045
+ if (input === "n") {
2046
+ setStack((st) => [...st, { kind: "recordSetup" }]);
2047
+ return;
2048
+ }
1698
2049
  if (input === "r") return void refresh({ resetRecordings: true });
1699
2050
  if (screen.kind === "overview") {
1700
2051
  if (key.upArrow || input === "k") setSelected((i) => Math.max(0, i - 1));
@@ -1741,18 +2092,18 @@ function AppShell({
1741
2092
  }
1742
2093
  });
1743
2094
  if (screen.kind === "transcript") {
1744
- return /* @__PURE__ */ jsx16(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
2095
+ return /* @__PURE__ */ jsx18(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
1745
2096
  }
1746
2097
  if (screen.kind === "jobDetail") {
1747
2098
  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() }) });
2099
+ if (!job) return /* @__PURE__ */ jsx18(Missing, { label: "Job" });
2100
+ return /* @__PURE__ */ jsx18(Detail, { notice, children: /* @__PURE__ */ jsx18(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
1750
2101
  }
1751
2102
  if (screen.kind === "recordingDetail") {
1752
2103
  const rec = recordings.find((r) => r.recordingId === screen.recordingId);
1753
- if (!rec) return /* @__PURE__ */ jsx16(Missing, { label: "Recording" });
2104
+ if (!rec) return /* @__PURE__ */ jsx18(Missing, { label: "Recording" });
1754
2105
  const detailTranscript = rec.activeTranscriptId ? transcriptCache.get(rec.activeTranscriptId) : void 0;
1755
- return /* @__PURE__ */ jsx16(Detail, { notice, children: /* @__PURE__ */ jsx16(
2106
+ return /* @__PURE__ */ jsx18(Detail, { notice, children: /* @__PURE__ */ jsx18(
1756
2107
  RecordingDetailView,
1757
2108
  {
1758
2109
  item: rec,
@@ -1762,23 +2113,35 @@ function AppShell({
1762
2113
  }
1763
2114
  ) });
1764
2115
  }
2116
+ if (screen.kind === "recordSetup") {
2117
+ return /* @__PURE__ */ jsx18(Box16, { flexDirection: "column", height: size.rows, paddingX: 1, children: /* @__PURE__ */ jsx18(
2118
+ RecordSetupView,
2119
+ {
2120
+ model: recordSetupModel,
2121
+ onStart: beginLiveRecord,
2122
+ onCancel: () => setStack((st) => st.length > 1 ? st.slice(0, -1) : [{ kind: "overview" }])
2123
+ }
2124
+ ) });
2125
+ }
1765
2126
  if (screen.kind === "record") {
1766
- if (liveRecord?.kind === "live") {
1767
- return /* @__PURE__ */ jsx16(LiveCaptionsScreen, { source: liveRecord.session.source, now });
2127
+ if (liveRecord?.kind === "live" && liveRecord.session.mode === "live_captions") {
2128
+ return /* @__PURE__ */ jsx18(LiveCaptionsScreen, { source: liveRecord.session.source, now });
2129
+ }
2130
+ if (liveRecord?.kind === "live" || liveRecord?.kind === "starting" || liveRecord?.kind === "stopping" || liveRecord?.kind === "stopped") {
2131
+ return /* @__PURE__ */ jsx18(Detail, { notice, children: /* @__PURE__ */ jsx18(RecordingHeroScreen, { telemetry: liveRecord.telemetry, now }) });
1768
2132
  }
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" ? (() => {
2133
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
2134
+ /* @__PURE__ */ jsx18(Box16, { flexGrow: 1, flexDirection: "column", paddingX: 1, paddingTop: 1, children: liveRecord?.kind === "error" ? (() => {
1772
2135
  if (liveRecord.code === "record.permission_required") {
1773
- return /* @__PURE__ */ jsx16(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
2136
+ return /* @__PURE__ */ jsx18(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
1774
2137
  }
1775
2138
  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
2139
+ return /* @__PURE__ */ jsxs15(Fragment4, { children: [
2140
+ /* @__PURE__ */ jsx18(Text16, { color: copy.tone, children: copy.title }),
2141
+ copy.detail ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: copy.detail }) : null
1779
2142
  ] });
1780
- })() : /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Starting live recording\u2026" }) }),
1781
- /* @__PURE__ */ jsx16(Footer, { keys: "q / esc / \u2190 back" })
2143
+ })() : /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Starting recording\u2026" }) }),
2144
+ /* @__PURE__ */ jsx18(Footer, { keys: "r retry \xB7 o settings \xB7 q / esc / \u2190 back" })
1782
2145
  ] });
1783
2146
  }
1784
2147
  const tab = screen.kind === "jobs" ? "jobs" : screen.kind === "account" ? "account" : "overview";
@@ -1796,7 +2159,7 @@ function AppShell({
1796
2159
  const showPeek = size.columns >= 100;
1797
2160
  const peekWidth = showPeek ? 34 : 0;
1798
2161
  const listColumns = showPeek ? Math.max(30, size.columns - peekWidth - 3) : size.columns;
1799
- body = /* @__PURE__ */ jsx16(
2162
+ body = /* @__PURE__ */ jsx18(
1800
2163
  OverviewView,
1801
2164
  {
1802
2165
  recordings: recordings.slice(win.start, win.end),
@@ -1816,11 +2179,11 @@ function AppShell({
1816
2179
  );
1817
2180
  } else if (screen.kind === "account") {
1818
2181
  position = "";
1819
- body = /* @__PURE__ */ jsx16(AccountView, { status: accountStatus });
2182
+ body = /* @__PURE__ */ jsx18(AccountView, { status: accountStatus });
1820
2183
  } else {
1821
2184
  const win = listWindow(selected, jobs.length, Math.max(3, size.rows - 4));
1822
2185
  position = jobs.length ? `${selected + 1} / ${jobs.length}` : "0";
1823
- body = /* @__PURE__ */ jsx16(
2186
+ body = /* @__PURE__ */ jsx18(
1824
2187
  JobsView,
1825
2188
  {
1826
2189
  items: jobs.slice(win.start, win.end),
@@ -1829,35 +2192,35 @@ function AppShell({
1829
2192
  }
1830
2193
  );
1831
2194
  }
1832
- 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: [
2195
+ 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`;
2196
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
2197
+ /* @__PURE__ */ jsx18(Header, { active: tab }),
2198
+ /* @__PURE__ */ jsxs15(Box16, { flexGrow: 1, flexDirection: "column", children: [
1836
2199
  body,
1837
- loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx16(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
2200
+ loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx18(Box16, { marginTop: 1, children: /* @__PURE__ */ jsxs15(Text16, { color: "red", children: [
1838
2201
  "! ",
1839
2202
  loadError
1840
2203
  ] }) }) : null
1841
2204
  ] }),
1842
- /* @__PURE__ */ jsx16(Footer, { keys: footerKeys })
2205
+ /* @__PURE__ */ jsx18(Footer, { keys: footerKeys })
1843
2206
  ] });
1844
2207
  }
1845
2208
  function Detail({
1846
2209
  notice,
1847
2210
  children
1848
2211
  }) {
1849
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
2212
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
1850
2213
  children,
1851
- notice ? /* @__PURE__ */ jsx16(Box14, { paddingX: 1, children: /* @__PURE__ */ jsx16(Text14, { color: "green", children: notice }) }) : null
2214
+ notice ? /* @__PURE__ */ jsx18(Box16, { paddingX: 1, children: /* @__PURE__ */ jsx18(Text16, { color: "green", children: notice }) }) : null
1852
2215
  ] });
1853
2216
  }
1854
2217
  function Missing({ label }) {
1855
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, children: [
1856
- /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
2218
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 1, children: [
2219
+ /* @__PURE__ */ jsxs15(Text16, { dimColor: true, children: [
1857
2220
  label,
1858
2221
  " no longer in the list."
1859
2222
  ] }),
1860
- /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "esc back \xB7 q quit" })
2223
+ /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "esc back \xB7 q quit" })
1861
2224
  ] });
1862
2225
  }
1863
2226
  var RECORDINGS_PAGE_SIZE, RECORDINGS_PREFETCH_REMAINING;
@@ -1873,6 +2236,9 @@ var init_AppShell = __esm({
1873
2236
  init_TranscriptView();
1874
2237
  init_LiveCaptionsScreen();
1875
2238
  init_PermissionPreflightView();
2239
+ init_RecordSetupView();
2240
+ init_RecordingHeroScreen();
2241
+ init_recordingCore();
1876
2242
  init_format();
1877
2243
  init_terminal();
1878
2244
  RECORDINGS_PAGE_SIZE = 50;
@@ -1891,7 +2257,7 @@ __export(tui_exports, {
1891
2257
  runDashboard: () => runDashboard,
1892
2258
  useTerminalSize: () => useTerminalSize
1893
2259
  });
1894
- import React7 from "react";
2260
+ import React9 from "react";
1895
2261
  import { render as render2 } from "ink";
1896
2262
  import { spawn as spawn3 } from "child_process";
1897
2263
  function openUrl(url2) {
@@ -1912,7 +2278,7 @@ function copyText(text) {
1912
2278
  async function runDashboard(deps) {
1913
2279
  const renderApp = deps.renderApp ?? render2;
1914
2280
  const app = renderApp(
1915
- React7.createElement(AppShell, {
2281
+ React9.createElement(AppShell, {
1916
2282
  fetchJobs: deps.fetchJobs,
1917
2283
  fetchTranscript: deps.fetchTranscript,
1918
2284
  fetchRecordings: deps.fetchRecordings,
@@ -19502,6 +19868,7 @@ import { createRequire as createRequire2 } from "module";
19502
19868
  import { dirname, join } from "path";
19503
19869
  import { fileURLToPath } from "url";
19504
19870
  import { render, useInput as useInput2 } from "ink";
19871
+ init_recordingCore();
19505
19872
 
19506
19873
  // src/sidecar.ts
19507
19874
  import { spawn as spawn2 } from "child_process";
@@ -19662,7 +20029,7 @@ var MiniSidecarClient = class {
19662
20029
  }
19663
20030
  };
19664
20031
  function sidecarErrorToCliError(error51) {
19665
- const data = isRecord5(error51.data) ? error51.data : void 0;
20032
+ const data = isRecord6(error51.data) ? error51.data : void 0;
19666
20033
  const maybeCode = typeof data?.cliCode === "string" ? cliErrorCodeSchema.safeParse(data.cliCode) : void 0;
19667
20034
  const hint = typeof data?.recovery === "string" ? data.recovery : void 0;
19668
20035
  const retryable = typeof data?.retryable === "boolean" ? data.retryable : error51.code >= -32099 && error51.code <= -32e3;
@@ -19679,7 +20046,7 @@ function sidecarErrorToCliError(error51) {
19679
20046
  retryable
19680
20047
  });
19681
20048
  }
19682
- function isRecord5(value) {
20049
+ function isRecord6(value) {
19683
20050
  return typeof value === "object" && value !== null && !Array.isArray(value);
19684
20051
  }
19685
20052
  function spawnMiniSidecar(opts) {
@@ -19745,13 +20112,21 @@ async function recordViaSidecar(opts) {
19745
20112
  session?.close();
19746
20113
  }
19747
20114
  }
19748
- async function startLiveRecordSession(opts) {
19749
- const session = await startRecordSession({ ...opts, live: false });
20115
+ async function startLiveRecordSession(opts, selection = {
20116
+ sourceId: DEFAULT_RECORDING_SOURCES[0].id,
20117
+ includeMicrophone: true
20118
+ }, sources = DEFAULT_RECORDING_SOURCES) {
20119
+ const capture = recordingCaptureMappingFromSelection(selection, sources);
20120
+ const session = await startRecordSession({
20121
+ ...opts,
20122
+ includeSystemAudio: capture.includeSystemAudio,
20123
+ includeMicrophone: capture.includeMicrophone,
20124
+ live: false
20125
+ });
19750
20126
  return {
20127
+ mode: "local",
19751
20128
  source: session.source,
19752
- stop: async () => {
19753
- await session.stop();
19754
- }
20129
+ stop: session.stop
19755
20130
  };
19756
20131
  }
19757
20132
  async function startRecordSession(opts) {
@@ -20076,24 +20451,27 @@ async function runCli(deps = {}) {
20076
20451
  recordingAudio,
20077
20452
  listDownloadedRecordingIds: () => recordingAudio.listDownloadedRecordingIds(),
20078
20453
  listDownloads: () => recordingAudio.listDownloads(),
20079
- startLiveRecord: async () => {
20454
+ startLiveRecord: async (selection) => {
20080
20455
  const liveStatus = await client.authStatus();
20081
20456
  if (!liveStatus.loggedIn || !liveStatus.userId) {
20082
20457
  throw cliError("auth.not_logged_in", "Sign in before starting a sidecar recording.", {
20083
20458
  hint: "Run recappi auth login, or import the Recappi Mini session with recappi auth import-macos."
20084
20459
  });
20085
20460
  }
20086
- return startLiveRecordSession({
20087
- account: {
20088
- backendOrigin: auth.origin,
20089
- userId: liveStatus.userId,
20090
- ...liveStatus.email ? { email: liveStatus.email } : {}
20461
+ return startLiveRecordSession(
20462
+ {
20463
+ account: {
20464
+ backendOrigin: auth.origin,
20465
+ userId: liveStatus.userId,
20466
+ ...liveStatus.email ? { email: liveStatus.email } : {}
20467
+ },
20468
+ cliVersion: CLI_VERSION,
20469
+ env: deps.env,
20470
+ homeDir: deps.homeDir,
20471
+ runtime: deps.recordRuntime
20091
20472
  },
20092
- cliVersion: CLI_VERSION,
20093
- env: deps.env,
20094
- homeDir: deps.homeDir,
20095
- runtime: deps.recordRuntime
20096
- });
20473
+ selection
20474
+ );
20097
20475
  },
20098
20476
  initialView: parsed.initialView
20099
20477
  });