recappi 0.1.78 → 0.1.79

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,117 @@ var __export = (target, all) => {
9
9
  __defProp(target, name, { get: all[name], enumerable: true });
10
10
  };
11
11
 
12
+ // src/tui/askInline.ts
13
+ function formatAskTimecode(ms) {
14
+ const total = Math.max(0, Math.floor(ms / 1e3));
15
+ const h = Math.floor(total / 3600);
16
+ const m = Math.floor(total % 3600 / 60);
17
+ const s = total % 60;
18
+ const pad = (n) => String(n).padStart(2, "0");
19
+ return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
20
+ }
21
+ function askCitationInlineLabel(citation) {
22
+ if (typeof citation.startMs === "number") return formatAskTimecode(citation.startMs);
23
+ const label = citation.label?.trim();
24
+ if (label) {
25
+ const firstRangePart = label.split(/[-–—]/)[0]?.trim();
26
+ if (firstRangePart) return firstRangePart;
27
+ }
28
+ if (typeof citation.index === "number") return `#${citation.index + 1}`;
29
+ return "Source";
30
+ }
31
+ function hasCitationMarkers(content) {
32
+ MARKER.lastIndex = 0;
33
+ return MARKER.test(content);
34
+ }
35
+ function strippedAskAnswer(content) {
36
+ return content.replace(LOOSE_MARKER, "");
37
+ }
38
+ function renderAskInline(content, citations) {
39
+ const bySegmentId = /* @__PURE__ */ new Map();
40
+ for (const citation of citations) {
41
+ if (citation.segmentId && !bySegmentId.has(citation.segmentId)) {
42
+ bySegmentId.set(citation.segmentId, citation);
43
+ }
44
+ }
45
+ if (!hasCitationMarkers(content) || bySegmentId.size === 0) {
46
+ const text = strippedAskAnswer(content);
47
+ return text ? [{ kind: "text", text }] : [];
48
+ }
49
+ const segments = [];
50
+ let lastVisibleChar;
51
+ const pushText = (text) => {
52
+ if (!text) return;
53
+ const prev = segments[segments.length - 1];
54
+ if (prev && prev.kind === "text") prev.text += text;
55
+ else segments.push({ kind: "text", text });
56
+ lastVisibleChar = text[text.length - 1];
57
+ };
58
+ const pushCitation = (citation) => {
59
+ if (lastVisibleChar !== void 0 && !/\s/.test(lastVisibleChar)) pushText(" ");
60
+ const label = askCitationInlineLabel(citation);
61
+ segments.push({ kind: "citation", label, citation });
62
+ lastVisibleChar = label[label.length - 1];
63
+ };
64
+ MARKER.lastIndex = 0;
65
+ let lastIndex = 0;
66
+ let match;
67
+ while ((match = MARKER.exec(content)) !== null) {
68
+ const textBefore = content.slice(lastIndex, match.index);
69
+ const citation = bySegmentId.get(match[1]);
70
+ if (citation) {
71
+ pushText(textBefore);
72
+ pushCitation(citation);
73
+ } else {
74
+ const nextChar = content[match.index + match[0].length];
75
+ pushText(cleanupBeforeRemovedMarker(textBefore, nextChar));
76
+ }
77
+ lastIndex = match.index + match[0].length;
78
+ }
79
+ pushText(content.slice(lastIndex));
80
+ return segments;
81
+ }
82
+ function cleanupBeforeRemovedMarker(text, nextChar) {
83
+ if (!nextChar || !TRAILING_PUNCTUATION.includes(nextChar)) return text;
84
+ return text.replace(/[ \t]+$/, "");
85
+ }
86
+ function truncateSnippet(snippet, max = 100) {
87
+ const clean = snippet.replace(/\s+/g, " ").trim();
88
+ return clean.length > max ? `${clean.slice(0, max - 1)}\u2026` : clean;
89
+ }
90
+ function formatAskAnswerPlain(content, citations) {
91
+ const segments = renderAskInline(content, citations);
92
+ const body = segments.map((segment) => segment.kind === "text" ? segment.text : `\u27E8${segment.label}\u27E9`).join("").trim();
93
+ const seen = /* @__PURE__ */ new Set();
94
+ const cited = [];
95
+ for (const segment of segments) {
96
+ if (segment.kind !== "citation") continue;
97
+ const key = segment.citation.segmentId ?? segment.label;
98
+ if (seen.has(key)) continue;
99
+ seen.add(key);
100
+ cited.push(segment.citation);
101
+ }
102
+ if (cited.length === 0) return body;
103
+ const sources = cited.map((citation) => {
104
+ const label = askCitationInlineLabel(citation);
105
+ const meta3 = [citation.speaker?.trim(), citation.snippet ? truncateSnippet(citation.snippet) : ""].filter(Boolean).join(" \xB7 ");
106
+ return ` \u27E8${label}\u27E9${meta3 ? ` ${meta3}` : ""}`;
107
+ });
108
+ return `${body}
109
+
110
+ Sources:
111
+ ${sources.join("\n")}`;
112
+ }
113
+ var MARKER, LOOSE_MARKER, TRAILING_PUNCTUATION;
114
+ var init_askInline = __esm({
115
+ "src/tui/askInline.ts"() {
116
+ "use strict";
117
+ MARKER = /\[\[(seg-[^\]]+)\]\]/g;
118
+ LOOSE_MARKER = /\s*\[?\[seg-[^\]]+\]\]?/g;
119
+ TRAILING_PUNCTUATION = ".\u3002!\uFF01?\uFF1F,\uFF0C;\uFF1B:\uFF1A";
120
+ }
121
+ });
122
+
12
123
  // src/recordingCore.ts
13
124
  function recordingCaptureMappingFromSelection(selection = DEFAULT_RECORDING_SELECTION, sources = DEFAULT_RECORDING_SOURCES) {
14
125
  const source = sources.find((candidate) => candidate.id === selection.sourceId) ?? sources[0];
@@ -1254,7 +1365,7 @@ function RecordingDetailView({
1254
1365
  scrollable ? " \xB7 \u2191\u2193 scroll" : "",
1255
1366
  ready ? " \xB7 " : "",
1256
1367
  `o open \xB7 d download \xB7 f finder`,
1257
- " \xB7 T re-transcribe \xB7 s re-summarize \xB7 e export",
1368
+ " \xB7 T re-transcribe \xB7 s re-summarize \xB7 a ask \xB7 e export",
1258
1369
  item.activeTranscriptId ? " \xB7 t full" : "",
1259
1370
  links.webUrl ? " \xB7 w web" : "",
1260
1371
  " \xB7 r refresh \xB7 esc back"
@@ -1364,13 +1475,177 @@ var init_RecordingDetailView = __esm({
1364
1475
  }
1365
1476
  });
1366
1477
 
1367
- // src/tui/TranscriptView.tsx
1368
- import { useMemo as useMemo3, useState as useState5 } from "react";
1478
+ // src/tui/AskScreen.tsx
1479
+ import { useCallback, useRef as useRef2, useState as useState5 } from "react";
1369
1480
  import { Box as Box13, Text as Text13, useInput as useInput5 } from "ink";
1370
1481
  import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
1482
+ function AskScreen({
1483
+ recordingId,
1484
+ title,
1485
+ askRecording,
1486
+ onBack,
1487
+ onOpenTranscript,
1488
+ spinnerFrame
1489
+ }) {
1490
+ const [phase, setPhase] = useState5("input");
1491
+ const [question, setQuestion] = useState5("");
1492
+ const [asked, setAsked] = useState5("");
1493
+ const [content, setContent] = useState5("");
1494
+ const [citations, setCitations] = useState5([]);
1495
+ const [error51, setError] = useState5(void 0);
1496
+ const runIdRef = useRef2(0);
1497
+ const submit = useCallback(
1498
+ (raw) => {
1499
+ const query = raw.trim();
1500
+ if (!query) return;
1501
+ const runId = ++runIdRef.current;
1502
+ setAsked(query);
1503
+ setPhase("asking");
1504
+ setContent("");
1505
+ setCitations([]);
1506
+ setError(void 0);
1507
+ void (async () => {
1508
+ try {
1509
+ let text = "";
1510
+ let cites = [];
1511
+ for await (const event of askRecording({ recordingId, question: query })) {
1512
+ if (runIdRef.current !== runId) return;
1513
+ if (event.type === "answer_delta") {
1514
+ text += event.delta;
1515
+ setContent(text);
1516
+ } else if (event.type === "citation") {
1517
+ cites = [...cites, event.citation];
1518
+ setCitations(cites);
1519
+ } else if (event.type === "done") {
1520
+ if (typeof event.content === "string" && event.content) {
1521
+ text = event.content;
1522
+ setContent(text);
1523
+ }
1524
+ if (event.citations.length) {
1525
+ cites = event.citations;
1526
+ setCitations(cites);
1527
+ }
1528
+ }
1529
+ }
1530
+ if (runIdRef.current === runId) setPhase("done");
1531
+ } catch (err) {
1532
+ if (runIdRef.current === runId) {
1533
+ setError(err instanceof Error ? err.message : String(err));
1534
+ setPhase("error");
1535
+ }
1536
+ }
1537
+ })();
1538
+ },
1539
+ [askRecording, recordingId]
1540
+ );
1541
+ const reset = () => {
1542
+ runIdRef.current++;
1543
+ setPhase("input");
1544
+ setQuestion("");
1545
+ setAsked("");
1546
+ setContent("");
1547
+ setCitations([]);
1548
+ setError(void 0);
1549
+ };
1550
+ useInput5((input, key) => {
1551
+ if (key.escape) {
1552
+ runIdRef.current++;
1553
+ onBack();
1554
+ return;
1555
+ }
1556
+ if (phase === "input") {
1557
+ if (key.return) return submit(question);
1558
+ if (key.backspace || key.delete) return setQuestion((q) => q.slice(0, -1));
1559
+ if (input && !key.ctrl && !key.meta) setQuestion((q) => q + input);
1560
+ return;
1561
+ }
1562
+ if (phase === "done" || phase === "error") {
1563
+ if (input === "a") return reset();
1564
+ if (input === "t" && onOpenTranscript) return onOpenTranscript();
1565
+ }
1566
+ });
1567
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 1, children: [
1568
+ /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
1569
+ "\u2039 Ask",
1570
+ title ? ` \xB7 ${title}` : ""
1571
+ ] }),
1572
+ phase === "input" ? /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
1573
+ /* @__PURE__ */ jsxs12(Text13, { children: [
1574
+ /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: "? " }),
1575
+ /* @__PURE__ */ jsx15(Text13, { children: question }),
1576
+ /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: "\u258F" })
1577
+ ] }),
1578
+ /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Type a question \xB7 \u23CE ask \xB7 esc back" }) })
1579
+ ] }) : /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
1580
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, wrap: "truncate-end", children: `? ${asked}` }),
1581
+ /* @__PURE__ */ jsx15(Box13, { marginTop: 1, flexDirection: "column", children: /* @__PURE__ */ jsx15(AnswerBody, { phase, content, citations, error: error51, spinnerFrame }) }),
1582
+ phase === "done" ? /* @__PURE__ */ jsx15(Sources, { citations }) : null,
1583
+ /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
1584
+ phase === "asking" ? "esc cancel" : "a ask again",
1585
+ (phase === "done" || phase === "error") && onOpenTranscript ? " \xB7 t transcript" : "",
1586
+ phase !== "asking" ? " \xB7 esc back" : ""
1587
+ ] }) })
1588
+ ] })
1589
+ ] });
1590
+ }
1591
+ function AnswerBody({
1592
+ phase,
1593
+ content,
1594
+ citations,
1595
+ error: error51,
1596
+ spinnerFrame
1597
+ }) {
1598
+ if (phase === "error") {
1599
+ return /* @__PURE__ */ jsx15(Text13, { color: "red", children: error51 ? `Ask failed: ${error51}` : "Ask failed" });
1600
+ }
1601
+ if (phase === "asking" && !content) {
1602
+ return /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: `${SPINNER2[spinnerFrame % SPINNER2.length]} Thinking\u2026` });
1603
+ }
1604
+ if (phase === "asking") {
1605
+ return /* @__PURE__ */ jsx15(Text13, { children: strippedAskAnswer(content) });
1606
+ }
1607
+ const segments = renderAskInline(content, citations);
1608
+ return /* @__PURE__ */ jsx15(Text13, { children: segments.map(
1609
+ (segment, i) => segment.kind === "text" ? /* @__PURE__ */ jsx15(Text13, { children: segment.text }, i) : /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: `\u27E8${segment.label}\u27E9` }, i)
1610
+ ) });
1611
+ }
1612
+ function Sources({ citations }) {
1613
+ const seen = /* @__PURE__ */ new Set();
1614
+ const unique = [];
1615
+ for (const citation of citations) {
1616
+ const key = citation.segmentId ?? askCitationInlineLabel(citation);
1617
+ if (seen.has(key)) continue;
1618
+ seen.add(key);
1619
+ unique.push(citation);
1620
+ }
1621
+ if (unique.length === 0) return null;
1622
+ return /* @__PURE__ */ jsxs12(Box13, { marginTop: 1, flexDirection: "column", children: [
1623
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Sources" }),
1624
+ unique.map((citation, i) => {
1625
+ const meta3 = [citation.speaker?.trim(), citation.snippet?.trim()].filter(Boolean).join(" \xB7 ");
1626
+ return /* @__PURE__ */ jsxs12(Text13, { wrap: "truncate-end", children: [
1627
+ /* @__PURE__ */ jsx15(Text13, { color: "cyan", children: `\u27E8${askCitationInlineLabel(citation)}\u27E9` }),
1628
+ meta3 ? /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: ` ${meta3}` }) : null
1629
+ ] }, i);
1630
+ })
1631
+ ] });
1632
+ }
1633
+ var SPINNER2;
1634
+ var init_AskScreen = __esm({
1635
+ "src/tui/AskScreen.tsx"() {
1636
+ "use strict";
1637
+ init_askInline();
1638
+ SPINNER2 = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F";
1639
+ }
1640
+ });
1641
+
1642
+ // src/tui/TranscriptView.tsx
1643
+ import { useMemo as useMemo3, useState as useState6 } from "react";
1644
+ import { Box as Box14, Text as Text14, useInput as useInput6 } from "ink";
1645
+ import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1371
1646
  function TranscriptView({ loading, data, error: error51 }) {
1372
1647
  const size = useTerminalSize();
1373
- const [scroll, setScroll] = useState5(0);
1648
+ const [scroll, setScroll] = useState6(0);
1374
1649
  const segments = data?.segments ?? [];
1375
1650
  const innerWidth = Math.max(10, size.columns - 2);
1376
1651
  const heights = useMemo3(
@@ -1383,7 +1658,7 @@ function TranscriptView({ loading, data, error: error51 }) {
1383
1658
  const budget = Math.max(3, size.rows - 3);
1384
1659
  const win = windowByHeights(heights, scroll, budget);
1385
1660
  const page = Math.max(1, budget - 1);
1386
- useInput5((input, key) => {
1661
+ useInput6((input, key) => {
1387
1662
  if (key.downArrow || input === "j") setScroll((s) => Math.min(win.maxScroll, s + 1));
1388
1663
  else if (key.upArrow || input === "k") setScroll((s) => Math.max(0, s - 1));
1389
1664
  else if (key.pageDown || input === " ") setScroll((s) => Math.min(win.maxScroll, s + page));
@@ -1392,42 +1667,42 @@ function TranscriptView({ loading, data, error: error51 }) {
1392
1667
  else if (input === "G") setScroll(win.maxScroll);
1393
1668
  });
1394
1669
  if (loading) {
1395
- return /* @__PURE__ */ jsx15(Box13, { paddingX: 1, children: /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Loading transcript\u2026" }) });
1670
+ return /* @__PURE__ */ jsx16(Box14, { paddingX: 1, children: /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Loading transcript\u2026" }) });
1396
1671
  }
1397
1672
  if (error51) {
1398
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 1, children: [
1399
- /* @__PURE__ */ jsxs12(Text13, { color: "red", children: [
1673
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, children: [
1674
+ /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
1400
1675
  "! ",
1401
1676
  error51
1402
1677
  ] }),
1403
- /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "q / esc / \u2190 back" })
1678
+ /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "q / esc / \u2190 back" })
1404
1679
  ] });
1405
1680
  }
1406
1681
  if (!data) {
1407
- return /* @__PURE__ */ jsx15(Box13, { paddingX: 1, children: /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "No transcript." }) });
1682
+ return /* @__PURE__ */ jsx16(Box14, { paddingX: 1, children: /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "No transcript." }) });
1408
1683
  }
1409
1684
  const title = data.summary?.title ?? "Transcript";
1410
1685
  const total = segments.length;
1411
1686
  const more = win.maxScroll > 0;
1412
1687
  const position = total === 0 ? "" : `${win.start + 1}\u2013${win.end} / ${total}`;
1413
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 1, children: [
1414
- /* @__PURE__ */ jsxs12(Text13, { bold: true, color: "green", children: [
1688
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, children: [
1689
+ /* @__PURE__ */ jsxs13(Text14, { bold: true, color: "green", children: [
1415
1690
  title,
1416
- more ? /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: ` ${position}` }) : null
1691
+ more ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: ` ${position}` }) : null
1417
1692
  ] }),
1418
- /* @__PURE__ */ jsx15(Box13, { marginTop: 1, flexDirection: "column", children: total === 0 ? /* @__PURE__ */ jsx15(Text13, { children: data.text }) : segments.slice(win.start, win.end).map((segment, index) => /* @__PURE__ */ jsxs12(Text13, { children: [
1419
- /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
1693
+ /* @__PURE__ */ jsx16(Box14, { marginTop: 1, flexDirection: "column", children: total === 0 ? /* @__PURE__ */ jsx16(Text14, { children: data.text }) : segments.slice(win.start, win.end).map((segment, index) => /* @__PURE__ */ jsxs13(Text14, { children: [
1694
+ /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
1420
1695
  "[",
1421
1696
  formatClockMs(segment.startMs),
1422
1697
  "] "
1423
1698
  ] }),
1424
- segment.speaker ? /* @__PURE__ */ jsxs12(Text13, { color: "cyan", children: [
1699
+ segment.speaker ? /* @__PURE__ */ jsxs13(Text14, { color: "cyan", children: [
1425
1700
  segment.speaker,
1426
1701
  ": "
1427
1702
  ] }) : null,
1428
1703
  segment.text
1429
1704
  ] }, win.start + index)) }),
1430
- /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
1705
+ /* @__PURE__ */ jsx16(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
1431
1706
  more ? "\u2191\u2193 scroll \xB7 PgUp/PgDn \xB7 g/G top/bottom \xB7 " : "",
1432
1707
  "q / esc / \u2190 back"
1433
1708
  ] }) })
@@ -1442,8 +1717,8 @@ var init_TranscriptView = __esm({
1442
1717
  });
1443
1718
 
1444
1719
  // src/tui/PermissionPreflightView.tsx
1445
- import { Box as Box14, Text as Text14 } from "ink";
1446
- import { jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1720
+ import { Box as Box15, Text as Text15 } from "ink";
1721
+ import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1447
1722
  function statusGlyph2(status) {
1448
1723
  switch (status) {
1449
1724
  case "granted":
@@ -1459,41 +1734,41 @@ function PermissionPreflightView({
1459
1734
  }) {
1460
1735
  const allGranted = items.length > 0 && items.every((item) => item.status === "granted" && !item.requiresProcessRestart);
1461
1736
  const hasRestartRequired = items.some((item) => item.requiresProcessRestart);
1462
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, children: [
1463
- /* @__PURE__ */ jsxs13(Text14, { children: [
1464
- /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "\u2039 " }),
1465
- /* @__PURE__ */ jsx16(Text14, { bold: true, children: "Recording permissions" })
1737
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, children: [
1738
+ /* @__PURE__ */ jsxs14(Text15, { children: [
1739
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "\u2039 " }),
1740
+ /* @__PURE__ */ jsx17(Text15, { bold: true, children: "Recording permissions" })
1466
1741
  ] }),
1467
- /* @__PURE__ */ jsx16(Box14, { marginTop: 1, flexDirection: "column", children: items.length === 0 ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Checking permissions\u2026" }) : items.map((item) => {
1742
+ /* @__PURE__ */ jsx17(Box15, { marginTop: 1, flexDirection: "column", children: items.length === 0 ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Checking permissions\u2026" }) : items.map((item) => {
1468
1743
  const status = statusGlyph2(item.status);
1469
1744
  const restart = item.requiresProcessRestart === true;
1470
1745
  const color = restart ? "yellow" : status.color;
1471
1746
  const hint = restart ? item.hint ?? `${item.name} enabled. Run recappi record again to start.` : item.status === "granted" ? void 0 : item.hint ?? DEFAULT_HINTS[item.name];
1472
- return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
1473
- /* @__PURE__ */ jsxs13(Text14, { children: [
1474
- /* @__PURE__ */ jsx16(Text14, { bold: true, color, children: status.glyph }),
1475
- /* @__PURE__ */ jsx16(Text14, { bold: true, children: ` ${item.name}` }),
1476
- /* @__PURE__ */ jsx16(Text14, { color, children: ` ${status.label}` })
1747
+ return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
1748
+ /* @__PURE__ */ jsxs14(Text15, { children: [
1749
+ /* @__PURE__ */ jsx17(Text15, { bold: true, color, children: status.glyph }),
1750
+ /* @__PURE__ */ jsx17(Text15, { bold: true, children: ` ${item.name}` }),
1751
+ /* @__PURE__ */ jsx17(Text15, { color, children: ` ${status.label}` })
1477
1752
  ] }),
1478
- hint ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: ` ${hint}` }) : null
1753
+ hint ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: ` ${hint}` }) : null
1479
1754
  ] }, item.name);
1480
1755
  }) }),
1481
- /* @__PURE__ */ jsx16(Box14, { marginTop: 1, children: allGranted ? /* @__PURE__ */ jsx16(Text14, { bold: true, color: "green", children: "\u2713 All set \u2014 ready to record." }) : hasRestartRequired ? /* @__PURE__ */ jsxs13(Text14, { children: [
1482
- /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Run recappi record again to start, or press " }),
1483
- /* @__PURE__ */ jsx16(Text14, { bold: true, color: "cyan", children: "r" }),
1484
- /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: " to retry." })
1485
- ] }) : /* @__PURE__ */ jsxs13(Text14, { children: [
1486
- /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Grant the permissions above, then press " }),
1487
- /* @__PURE__ */ jsx16(Text14, { bold: true, color: "cyan", children: "r" }),
1488
- /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: " to recheck." })
1756
+ /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: allGranted ? /* @__PURE__ */ jsx17(Text15, { bold: true, color: "green", children: "\u2713 All set \u2014 ready to record." }) : hasRestartRequired ? /* @__PURE__ */ jsxs14(Text15, { children: [
1757
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Run recappi record again to start, or press " }),
1758
+ /* @__PURE__ */ jsx17(Text15, { bold: true, color: "cyan", children: "r" }),
1759
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " to retry." })
1760
+ ] }) : /* @__PURE__ */ jsxs14(Text15, { children: [
1761
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Grant the permissions above, then press " }),
1762
+ /* @__PURE__ */ jsx17(Text15, { bold: true, color: "cyan", children: "r" }),
1763
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " to recheck." })
1489
1764
  ] }) }),
1490
- /* @__PURE__ */ jsx16(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { children: [
1491
- /* @__PURE__ */ jsx16(Text14, { color: "cyan", children: "r" }),
1492
- /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: " recheck \xB7 " }),
1493
- /* @__PURE__ */ jsx16(Text14, { color: "cyan", children: "o" }),
1494
- /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: " open System Settings \xB7 " }),
1495
- /* @__PURE__ */ jsx16(Text14, { color: "cyan", children: "esc" }),
1496
- /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: " back" })
1765
+ /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsxs14(Text15, { children: [
1766
+ /* @__PURE__ */ jsx17(Text15, { color: "cyan", children: "r" }),
1767
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " recheck \xB7 " }),
1768
+ /* @__PURE__ */ jsx17(Text15, { color: "cyan", children: "o" }),
1769
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " open System Settings \xB7 " }),
1770
+ /* @__PURE__ */ jsx17(Text15, { color: "cyan", children: "esc" }),
1771
+ /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " back" })
1497
1772
  ] }) })
1498
1773
  ] });
1499
1774
  }
@@ -1509,9 +1784,9 @@ var init_PermissionPreflightView = __esm({
1509
1784
  });
1510
1785
 
1511
1786
  // src/tui/RecordSetupView.tsx
1512
- import { useEffect as useEffect3, useRef as useRef2, useState as useState6 } from "react";
1513
- import { Box as Box15, Text as Text15, useInput as useInput6 } from "ink";
1514
- import { jsx as jsx17, jsxs as jsxs14 } from "react/jsx-runtime";
1787
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState7 } from "react";
1788
+ import { Box as Box16, Text as Text16, useInput as useInput7 } from "ink";
1789
+ import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1515
1790
  function levelDb2(level) {
1516
1791
  if (level <= 0.03) return "silent";
1517
1792
  return `${Math.round(level * 60 - 60)} dB`;
@@ -1522,11 +1797,11 @@ function meterBar(level, width) {
1522
1797
  return "\u2587".repeat(filled) + "\u2591".repeat(Math.max(0, width - filled));
1523
1798
  }
1524
1799
  function InputMeter({ level }) {
1525
- if (level == null) return /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "\u2014" });
1800
+ if (level == null) return /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "\u2014" });
1526
1801
  const silent = level <= 0.03;
1527
- return /* @__PURE__ */ jsxs14(Text15, { children: [
1528
- /* @__PURE__ */ jsx17(Text15, { color: silent ? "yellow" : "cyan", children: meterBar(level, METER_W) }),
1529
- /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: ` ${levelDb2(level)}` })
1802
+ return /* @__PURE__ */ jsxs15(Text16, { children: [
1803
+ /* @__PURE__ */ jsx18(Text16, { color: silent ? "yellow" : "cyan", children: meterBar(level, METER_W) }),
1804
+ /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: ` ${levelDb2(level)}` })
1530
1805
  ] });
1531
1806
  }
1532
1807
  function RecordSetupView({
@@ -1536,13 +1811,13 @@ function RecordSetupView({
1536
1811
  onCancel,
1537
1812
  onSelectionChange
1538
1813
  }) {
1539
- const [srcIdx, setSrcIdx] = useState6(0);
1540
- const [includeMic, setIncludeMic] = useState6(true);
1541
- const [micIdx, setMicIdx] = useState6(
1814
+ const [srcIdx, setSrcIdx] = useState7(0);
1815
+ const [includeMic, setIncludeMic] = useState7(true);
1816
+ const [micIdx, setMicIdx] = useState7(
1542
1817
  () => Math.max(0, model.microphones?.findIndex((device) => device.isDefault) ?? 0)
1543
1818
  );
1544
- const [sceneIdx, setSceneIdx] = useState6(0);
1545
- const userPickedMic = useRef2(false);
1819
+ const [sceneIdx, setSceneIdx] = useState7(0);
1820
+ const userPickedMic = useRef3(false);
1546
1821
  const sources = model.sources;
1547
1822
  const microphones = model.microphones ?? [];
1548
1823
  const selected = sources[Math.min(srcIdx, Math.max(0, sources.length - 1))];
@@ -1573,7 +1848,7 @@ function RecordSetupView({
1573
1848
  const di = microphones.findIndex((device) => device.isDefault);
1574
1849
  if (di > 0) setMicIdx(di);
1575
1850
  }, [microphones.length]);
1576
- useInput6((input, key) => {
1851
+ useInput7((input, key) => {
1577
1852
  if (key.upArrow || input === "k") setSrcIdx((i) => Math.max(0, i - 1));
1578
1853
  else if (key.downArrow || input === "j") setSrcIdx((i) => Math.min(sources.length - 1, i + 1));
1579
1854
  else if (input === " ") setIncludeMic((m) => !m);
@@ -1584,26 +1859,26 @@ function RecordSetupView({
1584
1859
  else if (key.return && selected) onStart(selection);
1585
1860
  else if (key.escape) onCancel();
1586
1861
  });
1587
- const sourceList = /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", children: [
1588
- /* @__PURE__ */ jsxs14(Box15, { children: [
1589
- /* @__PURE__ */ jsx17(Box15, { width: 36, children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "SOURCE" }) }),
1590
- /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "INPUT" })
1862
+ const sourceList = /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", children: [
1863
+ /* @__PURE__ */ jsxs15(Box16, { children: [
1864
+ /* @__PURE__ */ jsx18(Box16, { width: 36, children: /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "SOURCE" }) }),
1865
+ /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "INPUT" })
1591
1866
  ] }),
1592
1867
  sources.map((s, i) => {
1593
1868
  const on = i === srcIdx;
1594
- return /* @__PURE__ */ jsxs14(Box15, { children: [
1595
- /* @__PURE__ */ jsx17(Box15, { width: 36, children: /* @__PURE__ */ jsxs14(Text15, { color: on ? "cyan" : void 0, wrap: "truncate-end", children: [
1869
+ return /* @__PURE__ */ jsxs15(Box16, { children: [
1870
+ /* @__PURE__ */ jsx18(Box16, { width: 36, children: /* @__PURE__ */ jsxs15(Text16, { color: on ? "cyan" : void 0, wrap: "truncate-end", children: [
1596
1871
  on ? "\u25B8 \u25CF " : " \u25CB ",
1597
1872
  s.label
1598
1873
  ] }) }),
1599
- /* @__PURE__ */ jsx17(InputMeter, { level: levels?.bySourceId?.[s.id] })
1874
+ /* @__PURE__ */ jsx18(InputMeter, { level: levels?.bySourceId?.[s.id] })
1600
1875
  ] }, s.id);
1601
1876
  }),
1602
- !hasAppSource ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "No app-specific sources available right now" }) : null
1877
+ !hasAppSource ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "No app-specific sources available right now" }) : null
1603
1878
  ] });
1604
- const capturePlan = /* @__PURE__ */ jsxs14(Text15, { children: [
1605
- /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Capture " }),
1606
- /* @__PURE__ */ jsx17(Text15, { children: includeMic ? `${selected?.label ?? "System audio"} + microphone` : `${selected?.label ?? "System audio"} only` })
1879
+ const capturePlan = /* @__PURE__ */ jsxs15(Text16, { children: [
1880
+ /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Capture " }),
1881
+ /* @__PURE__ */ jsx18(Text16, { children: includeMic ? `${selected?.label ?? "System audio"} + microphone` : `${selected?.label ?? "System audio"} only` })
1607
1882
  ] });
1608
1883
  const shortcuts = [
1609
1884
  hasMultipleSources ? "\u2191\u2193 source" : void 0,
@@ -1613,33 +1888,33 @@ function RecordSetupView({
1613
1888
  "\u23CE start recording",
1614
1889
  "esc cancel"
1615
1890
  ].filter(Boolean).join(" \xB7 ");
1616
- return /* @__PURE__ */ jsxs14(Box15, { flexDirection: "column", paddingX: 1, children: [
1617
- /* @__PURE__ */ jsx17(Text15, { bold: true, color: "green", children: "New recording" }),
1618
- /* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection: "column", children: [
1891
+ return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 1, children: [
1892
+ /* @__PURE__ */ jsx18(Text16, { bold: true, color: "green", children: "New recording" }),
1893
+ /* @__PURE__ */ jsxs15(Box16, { marginTop: 1, flexDirection: "column", children: [
1619
1894
  sourceList,
1620
- /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: capturePlan })
1895
+ /* @__PURE__ */ jsx18(Box16, { marginTop: 1, children: capturePlan })
1621
1896
  ] }),
1622
- /* @__PURE__ */ jsxs14(Box15, { marginTop: 1, flexDirection: "column", children: [
1623
- /* @__PURE__ */ jsxs14(Text15, { children: [
1624
- /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Microphone " }),
1625
- /* @__PURE__ */ jsx17(Text15, { color: includeMic ? "green" : "gray", children: includeMic ? "[x] include mic" : "[ ] include mic" }),
1626
- /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " (space)" })
1897
+ /* @__PURE__ */ jsxs15(Box16, { marginTop: 1, flexDirection: "column", children: [
1898
+ /* @__PURE__ */ jsxs15(Text16, { children: [
1899
+ /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Microphone " }),
1900
+ /* @__PURE__ */ jsx18(Text16, { color: includeMic ? "green" : "gray", children: includeMic ? "[x] include mic" : "[ ] include mic" }),
1901
+ /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " (space)" })
1627
1902
  ] }),
1628
- selectedMic ? /* @__PURE__ */ jsxs14(Box15, { children: [
1629
- /* @__PURE__ */ jsx17(Box15, { width: 36, children: /* @__PURE__ */ jsxs14(Text15, { dimColor: !includeMic, wrap: "truncate-end", children: [
1630
- /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Mic device " }),
1903
+ selectedMic ? /* @__PURE__ */ jsxs15(Box16, { children: [
1904
+ /* @__PURE__ */ jsx18(Box16, { width: 36, children: /* @__PURE__ */ jsxs15(Text16, { dimColor: !includeMic, wrap: "truncate-end", children: [
1905
+ /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Mic device " }),
1631
1906
  selectedMic.label
1632
1907
  ] }) }),
1633
- includeMic ? /* @__PURE__ */ jsx17(InputMeter, { level: levels?.byMicrophoneId?.[selectedMic.id] }) : null
1908
+ includeMic ? /* @__PURE__ */ jsx18(InputMeter, { level: levels?.byMicrophoneId?.[selectedMic.id] }) : null
1634
1909
  ] }) : null,
1635
- includeMic && hasMultipleMicrophones ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " (m to change device)" }) : null,
1636
- model.scenes.length > 0 ? /* @__PURE__ */ jsxs14(Text15, { children: [
1637
- /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Scene " }),
1638
- /* @__PURE__ */ jsx17(Text15, { children: model.scenes[sceneIdx]?.label ?? "Default" }),
1639
- model.scenes.length > 1 ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: " (s to change)" }) : null
1910
+ includeMic && hasMultipleMicrophones ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " (m to change device)" }) : null,
1911
+ model.scenes.length > 0 ? /* @__PURE__ */ jsxs15(Text16, { children: [
1912
+ /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Scene " }),
1913
+ /* @__PURE__ */ jsx18(Text16, { children: model.scenes[sceneIdx]?.label ?? "Default" }),
1914
+ model.scenes.length > 1 ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " (s to change)" }) : null
1640
1915
  ] }) : null
1641
1916
  ] }),
1642
- /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: shortcuts }) })
1917
+ /* @__PURE__ */ jsx18(Box16, { marginTop: 1, children: /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: shortcuts }) })
1643
1918
  ] });
1644
1919
  }
1645
1920
  var METER_W;
@@ -1651,9 +1926,9 @@ var init_RecordSetupView = __esm({
1651
1926
  });
1652
1927
 
1653
1928
  // src/tui/RecordFrame.tsx
1654
- import { useState as useState7 } from "react";
1655
- import { Box as Box16, Text as Text16, useInput as useInput7 } from "ink";
1656
- import { jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1929
+ import { useState as useState8 } from "react";
1930
+ import { Box as Box17, Text as Text17, useInput as useInput8 } from "ink";
1931
+ import { jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
1657
1932
  function levelDb3(level) {
1658
1933
  if (level <= 0.03) return "silent";
1659
1934
  return `${Math.round(level * 60 - 60)} dB`;
@@ -1662,14 +1937,14 @@ function CompactMeter({ label, level }) {
1662
1937
  const width = 12;
1663
1938
  const filled = Math.max(0, Math.min(width, Math.round(Math.max(0, Math.min(1, level)) * width)));
1664
1939
  const silent = level <= 0.03;
1665
- return /* @__PURE__ */ jsxs15(Text16, { children: [
1666
- /* @__PURE__ */ jsxs15(Text16, { dimColor: true, children: [
1940
+ return /* @__PURE__ */ jsxs16(Text17, { children: [
1941
+ /* @__PURE__ */ jsxs16(Text17, { dimColor: true, children: [
1667
1942
  label,
1668
1943
  " "
1669
1944
  ] }),
1670
- /* @__PURE__ */ jsx18(Text16, { color: silent ? "yellow" : "cyan", children: "\u25CF".repeat(filled) }),
1671
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "\xB7".repeat(width - filled) }),
1672
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: ` ${levelDb3(level)}` })
1945
+ /* @__PURE__ */ jsx19(Text17, { color: silent ? "yellow" : "cyan", children: "\u25CF".repeat(filled) }),
1946
+ /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "\xB7".repeat(width - filled) }),
1947
+ /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: ` ${levelDb3(level)}` })
1673
1948
  ] });
1674
1949
  }
1675
1950
  function CaptionColumn({
@@ -1688,7 +1963,7 @@ function CaptionColumn({
1688
1963
  chosen.unshift(lines[i]);
1689
1964
  used += h;
1690
1965
  }
1691
- return /* @__PURE__ */ jsx18(Box16, { width, flexDirection: "column", children: chosen.length === 0 ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Listening for speech\u2026" }) : chosen.map((l, i) => /* @__PURE__ */ jsx18(Text16, { dimColor: dim, wrap: "wrap", children: l }, i)) });
1966
+ return /* @__PURE__ */ jsx19(Box17, { width, flexDirection: "column", children: chosen.length === 0 ? /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "Listening for speech\u2026" }) : chosen.map((l, i) => /* @__PURE__ */ jsx19(Text17, { dimColor: dim, wrap: "wrap", children: l }, i)) });
1692
1967
  }
1693
1968
  function outcomeLine(telemetry, artifact) {
1694
1969
  if (telemetry.status === "recording" || telemetry.status === "starting") {
@@ -1720,10 +1995,10 @@ function RecordFrame({
1720
1995
  spinnerFrame = 0
1721
1996
  }) {
1722
1997
  const size = useTerminalSize();
1723
- const [captionMode, setCaptionMode] = useState7("both");
1724
- const [scrollBack, setScrollBack] = useState7(0);
1998
+ const [captionMode, setCaptionMode] = useState8("both");
1999
+ const [scrollBack, setScrollBack] = useState8(0);
1725
2000
  const PAGE = 8;
1726
- useInput7((input, key) => {
2001
+ useInput8((input, key) => {
1727
2002
  if (input === "c") {
1728
2003
  setCaptionMode((m) => m === "both" ? "source" : m === "source" ? "translation" : "both");
1729
2004
  } else if (key.upArrow || input === "k") setScrollBack((s) => s + 1);
@@ -1751,49 +2026,49 @@ function RecordFrame({
1751
2026
  ] : [];
1752
2027
  const status = telemetry.sizeBytes ? formatBytes3(telemetry.sizeBytes) : "";
1753
2028
  const sourceLine = [telemetry.sourceLabel, telemetry.micEnabled ? "Microphone" : null, status || null].filter(Boolean).join(" \xB7 ");
1754
- return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", paddingX: 1, height: size.rows, children: [
1755
- /* @__PURE__ */ jsxs15(Box16, { justifyContent: "space-between", children: [
1756
- /* @__PURE__ */ jsxs15(Text16, { children: [
1757
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: "green", children: "recappi" }),
1758
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " \xB7 Recording" })
2029
+ return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", paddingX: 1, height: size.rows, children: [
2030
+ /* @__PURE__ */ jsxs16(Box17, { justifyContent: "space-between", children: [
2031
+ /* @__PURE__ */ jsxs16(Text17, { children: [
2032
+ /* @__PURE__ */ jsx19(Text17, { bold: true, color: "green", children: "recappi" }),
2033
+ /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: " \xB7 Recording" })
1759
2034
  ] }),
1760
- /* @__PURE__ */ jsxs15(Text16, { children: [
1761
- /* @__PURE__ */ jsx18(Text16, { bold: true, color: recording ? "red" : "gray", children: stateLabel }),
1762
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: ` ${elapsed}${ids ? ` \xB7 ${ids}` : ""}` })
2035
+ /* @__PURE__ */ jsxs16(Text17, { children: [
2036
+ /* @__PURE__ */ jsx19(Text17, { bold: true, color: recording ? "red" : "gray", children: stateLabel }),
2037
+ /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: ` ${elapsed}${ids ? ` \xB7 ${ids}` : ""}` })
1763
2038
  ] })
1764
2039
  ] }),
1765
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "\u2500".repeat(innerWidth) }),
1766
- /* @__PURE__ */ jsxs15(Box16, { flexGrow: 1, children: [
1767
- /* @__PURE__ */ jsxs15(Box16, { width: listWidth, flexDirection: "column", children: [
1768
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: `RECORDINGS \xB7 ${recordings.length}` }),
1769
- /* @__PURE__ */ jsx18(Box16, { marginTop: 1, flexDirection: "column", children: recordings.slice(0, Math.max(1, size.rows - 8)).map((rec, i) => {
2040
+ /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "\u2500".repeat(innerWidth) }),
2041
+ /* @__PURE__ */ jsxs16(Box17, { flexGrow: 1, children: [
2042
+ /* @__PURE__ */ jsxs16(Box17, { width: listWidth, flexDirection: "column", children: [
2043
+ /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: `RECORDINGS \xB7 ${recordings.length}` }),
2044
+ /* @__PURE__ */ jsx19(Box17, { marginTop: 1, flexDirection: "column", children: recordings.slice(0, Math.max(1, size.rows - 8)).map((rec, i) => {
1770
2045
  const st = recordingProcessingState(rec, void 0, spinnerFrame);
1771
2046
  const sel = i === selectedIndex;
1772
- return /* @__PURE__ */ jsxs15(Box16, { children: [
1773
- /* @__PURE__ */ jsx18(Box16, { width: 2, children: /* @__PURE__ */ jsx18(Text16, { color: "cyan", children: sel ? "\u25B8" : "" }) }),
1774
- /* @__PURE__ */ jsx18(Box16, { width: 2, children: /* @__PURE__ */ jsx18(Text16, { color: st.color, children: st.glyph }) }),
1775
- /* @__PURE__ */ jsx18(Box16, { width: listWidth - 4, children: /* @__PURE__ */ jsx18(Text16, { bold: sel, wrap: "truncate-end", children: recordingTitle2(rec) }) })
2047
+ return /* @__PURE__ */ jsxs16(Box17, { children: [
2048
+ /* @__PURE__ */ jsx19(Box17, { width: 2, children: /* @__PURE__ */ jsx19(Text17, { color: "cyan", children: sel ? "\u25B8" : "" }) }),
2049
+ /* @__PURE__ */ jsx19(Box17, { width: 2, children: /* @__PURE__ */ jsx19(Text17, { color: st.color, children: st.glyph }) }),
2050
+ /* @__PURE__ */ jsx19(Box17, { width: listWidth - 4, children: /* @__PURE__ */ jsx19(Text17, { bold: sel, wrap: "truncate-end", children: recordingTitle2(rec) }) })
1776
2051
  ] }, rec.recordingId);
1777
2052
  }) })
1778
2053
  ] }),
1779
- /* @__PURE__ */ jsx18(Box16, { width: 3, flexDirection: "column", alignItems: "center", children: Array.from({ length: Math.max(1, size.rows - 6) }, (_, i) => /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "\u2502" }, i)) }),
1780
- /* @__PURE__ */ jsxs15(Box16, { width: rightWidth, flexDirection: "column", children: [
1781
- /* @__PURE__ */ jsx18(Text16, { bold: true, wrap: "truncate-end", children: title }),
1782
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, wrap: "truncate-end", children: sourceLine }),
1783
- telemetry.level ? /* @__PURE__ */ jsxs15(Box16, { children: [
1784
- /* @__PURE__ */ jsx18(CompactMeter, { label: "System", level: telemetry.level.system ?? 0 }),
1785
- telemetry.micEnabled ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: " " }) : null,
1786
- telemetry.micEnabled ? /* @__PURE__ */ jsx18(CompactMeter, { label: "Mic", level: telemetry.level.mic ?? 0 }) : null
1787
- ] }) : /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "Capturing audio\u2026" }),
1788
- /* @__PURE__ */ jsxs15(Box16, { marginTop: 1, flexDirection: "column", flexGrow: 1, children: [
1789
- /* @__PURE__ */ jsxs15(Box16, { children: [
1790
- captionMode !== "translation" ? /* @__PURE__ */ jsx18(Box16, { width: captionMode === "both" ? Math.floor((rightWidth - 3) / 2) : rightWidth, children: /* @__PURE__ */ jsx18(Text16, { bold: true, dimColor: true, children: "ORIGINAL" }) }) : null,
1791
- captionMode === "both" ? /* @__PURE__ */ jsx18(Box16, { width: 3 }) : null,
1792
- captionMode !== "source" ? /* @__PURE__ */ jsx18(Text16, { bold: true, dimColor: true, children: "TRANSLATION" }) : null,
1793
- scrollBack > 0 ? /* @__PURE__ */ jsx18(Text16, { color: "yellow", children: " \u23F8 scrolled \xB7 G live" }) : null
2054
+ /* @__PURE__ */ jsx19(Box17, { width: 3, flexDirection: "column", alignItems: "center", children: Array.from({ length: Math.max(1, size.rows - 6) }, (_, i) => /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "\u2502" }, i)) }),
2055
+ /* @__PURE__ */ jsxs16(Box17, { width: rightWidth, flexDirection: "column", children: [
2056
+ /* @__PURE__ */ jsx19(Text17, { bold: true, wrap: "truncate-end", children: title }),
2057
+ /* @__PURE__ */ jsx19(Text17, { dimColor: true, wrap: "truncate-end", children: sourceLine }),
2058
+ telemetry.level ? /* @__PURE__ */ jsxs16(Box17, { children: [
2059
+ /* @__PURE__ */ jsx19(CompactMeter, { label: "System", level: telemetry.level.system ?? 0 }),
2060
+ telemetry.micEnabled ? /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: " " }) : null,
2061
+ telemetry.micEnabled ? /* @__PURE__ */ jsx19(CompactMeter, { label: "Mic", level: telemetry.level.mic ?? 0 }) : null
2062
+ ] }) : /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "Capturing audio\u2026" }),
2063
+ /* @__PURE__ */ jsxs16(Box17, { marginTop: 1, flexDirection: "column", flexGrow: 1, children: [
2064
+ /* @__PURE__ */ jsxs16(Box17, { children: [
2065
+ captionMode !== "translation" ? /* @__PURE__ */ jsx19(Box17, { width: captionMode === "both" ? Math.floor((rightWidth - 3) / 2) : rightWidth, children: /* @__PURE__ */ jsx19(Text17, { bold: true, dimColor: true, children: "ORIGINAL" }) }) : null,
2066
+ captionMode === "both" ? /* @__PURE__ */ jsx19(Box17, { width: 3 }) : null,
2067
+ captionMode !== "source" ? /* @__PURE__ */ jsx19(Text17, { bold: true, dimColor: true, children: "TRANSLATION" }) : null,
2068
+ scrollBack > 0 ? /* @__PURE__ */ jsx19(Text17, { color: "yellow", children: " \u23F8 scrolled \xB7 G live" }) : null
1794
2069
  ] }),
1795
- /* @__PURE__ */ jsxs15(Box16, { children: [
1796
- captionMode !== "translation" ? /* @__PURE__ */ jsx18(
2070
+ /* @__PURE__ */ jsxs16(Box17, { children: [
2071
+ captionMode !== "translation" ? /* @__PURE__ */ jsx19(
1797
2072
  CaptionColumn,
1798
2073
  {
1799
2074
  lines: sourceLines,
@@ -1802,8 +2077,8 @@ function RecordFrame({
1802
2077
  scrollBack
1803
2078
  }
1804
2079
  ) : null,
1805
- captionMode === "both" ? /* @__PURE__ */ jsx18(Box16, { width: 3, flexDirection: "column", children: Array.from({ length: Math.min(captionRows, 12) }, (_, i) => /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "\u2502" }, i)) }) : null,
1806
- captionMode !== "source" ? /* @__PURE__ */ jsx18(
2080
+ captionMode === "both" ? /* @__PURE__ */ jsx19(Box17, { width: 3, flexDirection: "column", children: Array.from({ length: Math.min(captionRows, 12) }, (_, i) => /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "\u2502" }, i)) }) : null,
2081
+ captionMode !== "source" ? /* @__PURE__ */ jsx19(
1807
2082
  CaptionColumn,
1808
2083
  {
1809
2084
  lines: translationLines,
@@ -1815,14 +2090,14 @@ function RecordFrame({
1815
2090
  ) : null
1816
2091
  ] })
1817
2092
  ] }),
1818
- /* @__PURE__ */ jsxs15(Box16, { marginTop: 1, children: [
1819
- /* @__PURE__ */ jsx18(Text16, { bold: true, dimColor: true, children: "OUTCOME " }),
1820
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: outcomeLine(telemetry, artifact) })
2093
+ /* @__PURE__ */ jsxs16(Box17, { marginTop: 1, children: [
2094
+ /* @__PURE__ */ jsx19(Text17, { bold: true, dimColor: true, children: "OUTCOME " }),
2095
+ /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: outcomeLine(telemetry, artifact) })
1821
2096
  ] })
1822
2097
  ] })
1823
2098
  ] }),
1824
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: "\u2500".repeat(innerWidth) }),
1825
- /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: recording ? `q stop & save \xB7 c captions (${captionMode}) \xB7 \u2191\u2193 scroll \xB7 G live` : `\u23CE open \xB7 T re-transcribe \xB7 c captions (${captionMode}) \xB7 \u2191\u2193 scroll \xB7 1 overview 2 jobs 3 account` })
2099
+ /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "\u2500".repeat(innerWidth) }),
2100
+ /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: recording ? `q stop & save \xB7 c captions (${captionMode}) \xB7 \u2191\u2193 scroll \xB7 G live` : `\u23CE open \xB7 T re-transcribe \xB7 c captions (${captionMode}) \xB7 \u2191\u2193 scroll \xB7 1 overview 2 jobs 3 account` })
1826
2101
  ] });
1827
2102
  }
1828
2103
  var trimLead2, wrappedRows2;
@@ -1838,9 +2113,9 @@ var init_RecordFrame = __esm({
1838
2113
  });
1839
2114
 
1840
2115
  // src/tui/AppShell.tsx
1841
- import { useCallback, useEffect as useEffect4, useRef as useRef3, useState as useState8 } from "react";
1842
- import { Box as Box17, Text as Text17, useApp, useInput as useInput8 } from "ink";
1843
- import { Fragment as Fragment6, jsx as jsx19, jsxs as jsxs16 } from "react/jsx-runtime";
2116
+ import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4, useState as useState9 } from "react";
2117
+ import { Box as Box18, Text as Text18, useApp, useInput as useInput9 } from "ink";
2118
+ import { Fragment as Fragment6, jsx as jsx20, jsxs as jsxs17 } from "react/jsx-runtime";
1844
2119
  function recordErrorCopy(code, message) {
1845
2120
  switch (code) {
1846
2121
  case "record.helper_unavailable":
@@ -2011,6 +2286,7 @@ function AppShell({
2011
2286
  onSyncRecordingText,
2012
2287
  onSyncRecordingAudio,
2013
2288
  onExportRecording,
2289
+ askRecording,
2014
2290
  initialView = "overview",
2015
2291
  openUrl: openUrl2,
2016
2292
  copyText: copyText2,
@@ -2020,47 +2296,47 @@ function AppShell({
2020
2296
  }) {
2021
2297
  const { exit } = useApp();
2022
2298
  const size = useTerminalSize();
2023
- const [jobs, setJobs] = useState8([]);
2024
- const [recordings, setRecordings] = useState8([]);
2025
- const [recordingsNextCursor, setRecordingsNextCursor] = useState8(null);
2026
- const [recordingsTotalCount, setRecordingsTotalCount] = useState8(void 0);
2027
- const [stats, setStats] = useState8(void 0);
2028
- const [accountStatus, setAccountStatus] = useState8("loading");
2029
- const [origin, setOrigin] = useState8("");
2030
- const [stack, setStack] = useState8([{ kind: initialView }]);
2031
- const [selected, setSelected] = useState8(0);
2032
- const [spinnerFrame, setSpinnerFrame] = useState8(0);
2033
- const [loadingMoreRecordings, setLoadingMoreRecordings] = useState8(false);
2034
- const [revalidatingRecordings, setRevalidatingRecordings] = useState8(false);
2035
- const [loaded, setLoaded] = useState8(false);
2036
- const [loadError, setLoadError] = useState8(void 0);
2037
- const [notice, setNotice] = useState8(void 0);
2038
- const [summaryCache, setSummaryCache] = useState8(() => /* @__PURE__ */ new Map());
2039
- const [transcriptCache, setTranscriptCache] = useState8(
2299
+ const [jobs, setJobs] = useState9([]);
2300
+ const [recordings, setRecordings] = useState9([]);
2301
+ const [recordingsNextCursor, setRecordingsNextCursor] = useState9(null);
2302
+ const [recordingsTotalCount, setRecordingsTotalCount] = useState9(void 0);
2303
+ const [stats, setStats] = useState9(void 0);
2304
+ const [accountStatus, setAccountStatus] = useState9("loading");
2305
+ const [origin, setOrigin] = useState9("");
2306
+ const [stack, setStack] = useState9([{ kind: initialView }]);
2307
+ const [selected, setSelected] = useState9(0);
2308
+ const [spinnerFrame, setSpinnerFrame] = useState9(0);
2309
+ const [loadingMoreRecordings, setLoadingMoreRecordings] = useState9(false);
2310
+ const [revalidatingRecordings, setRevalidatingRecordings] = useState9(false);
2311
+ const [loaded, setLoaded] = useState9(false);
2312
+ const [loadError, setLoadError] = useState9(void 0);
2313
+ const [notice, setNotice] = useState9(void 0);
2314
+ const [summaryCache, setSummaryCache] = useState9(() => /* @__PURE__ */ new Map());
2315
+ const [transcriptCache, setTranscriptCache] = useState9(
2040
2316
  () => /* @__PURE__ */ new Map()
2041
2317
  );
2042
- const [audioCache, setAudioCache] = useState8(() => /* @__PURE__ */ new Map());
2043
- const [downloadedIds, setDownloadedIds] = useState8(() => /* @__PURE__ */ new Set());
2044
- const summaryRefreshRef = useRef3(null);
2045
- const [liveRecord, setLiveRecord] = useState8(void 0);
2046
- const [recordSetupInputs, setRecordSetupInputs] = useState8({
2318
+ const [audioCache, setAudioCache] = useState9(() => /* @__PURE__ */ new Map());
2319
+ const [downloadedIds, setDownloadedIds] = useState9(() => /* @__PURE__ */ new Set());
2320
+ const summaryRefreshRef = useRef4(null);
2321
+ const [liveRecord, setLiveRecord] = useState9(void 0);
2322
+ const [recordSetupInputs, setRecordSetupInputs] = useState9({
2047
2323
  sources: DEFAULT_RECORDING_SOURCES,
2048
2324
  microphones: []
2049
2325
  });
2050
- const [recordSetupSelection, setRecordSetupSelection] = useState8(
2326
+ const [recordSetupSelection, setRecordSetupSelection] = useState9(
2051
2327
  DEFAULT_RECORDING_SELECTION
2052
2328
  );
2053
- const [recordSetupLevels, setRecordSetupLevels] = useState8({
2329
+ const [recordSetupLevels, setRecordSetupLevels] = useState9({
2054
2330
  bySourceId: {},
2055
2331
  byMicrophoneId: {}
2056
2332
  });
2057
- const autoTranscribeStartedSessionIds = useRef3(/* @__PURE__ */ new Set());
2333
+ const autoTranscribeStartedSessionIds = useRef4(/* @__PURE__ */ new Set());
2058
2334
  const recordSetupModel = {
2059
2335
  sources: recordSetupInputs.sources.length > 0 ? recordSetupInputs.sources : DEFAULT_RECORDING_SOURCES,
2060
2336
  microphones: recordSetupInputs.microphones ?? [],
2061
2337
  scenes: DEFAULT_RECORDING_SCENES
2062
2338
  };
2063
- const refreshDownloadedIds = useCallback(async () => {
2339
+ const refreshDownloadedIds = useCallback2(async () => {
2064
2340
  if (!listDownloadedRecordingIds) return;
2065
2341
  try {
2066
2342
  setDownloadedIds(await listDownloadedRecordingIds());
@@ -2071,11 +2347,11 @@ function AppShell({
2071
2347
  void refreshDownloadedIds();
2072
2348
  }, [refreshDownloadedIds]);
2073
2349
  const screen = stack[stack.length - 1];
2074
- const recordingsRef = useRef3(recordings);
2350
+ const recordingsRef = useRef4(recordings);
2075
2351
  recordingsRef.current = recordings;
2076
- const selectedRef = useRef3(selected);
2352
+ const selectedRef = useRef4(selected);
2077
2353
  selectedRef.current = selected;
2078
- const screenRef = useRef3(screen);
2354
+ const screenRef = useRef4(screen);
2079
2355
  screenRef.current = screen;
2080
2356
  useEffect4(() => {
2081
2357
  if (screen.kind !== "recordSetup" || !startRecordSetupPreview) return;
@@ -2125,7 +2401,7 @@ function AppShell({
2125
2401
  screen.kind,
2126
2402
  startRecordSetupPreview
2127
2403
  ]);
2128
- const beginLiveRecord = useCallback(
2404
+ const beginLiveRecord = useCallback2(
2129
2405
  (selection = DEFAULT_RECORDING_SELECTION) => {
2130
2406
  const capture = recordingCaptureMappingFromSelection(selection, recordSetupModel.sources);
2131
2407
  const telemetry = {
@@ -2167,7 +2443,7 @@ function AppShell({
2167
2443
  },
2168
2444
  [now, recordSetupModel.sources, startLiveRecord]
2169
2445
  );
2170
- const stopLiveRecord = useCallback(async () => {
2446
+ const stopLiveRecord = useCallback2(async () => {
2171
2447
  const current = liveRecord;
2172
2448
  if (current?.kind === "live") {
2173
2449
  const stoppingTelemetry = { ...current.telemetry, status: "stopping" };
@@ -2254,7 +2530,7 @@ function AppShell({
2254
2530
  };
2255
2531
  }, [peekTranscriptId, fetchTranscript]);
2256
2532
  const peekSummary = peekTranscriptId ? summaryCache.get(peekTranscriptId) : void 0;
2257
- const refresh = useCallback(async ({ resetRecordings = false } = {}) => {
2533
+ const refresh = useCallback2(async ({ resetRecordings = false } = {}) => {
2258
2534
  let showingCachedRecordings = false;
2259
2535
  if (resetRecordings && fetchCachedRecordings) {
2260
2536
  try {
@@ -2303,7 +2579,7 @@ function AppShell({
2303
2579
  setLoaded(true);
2304
2580
  if (showingCachedRecordings) setRevalidatingRecordings(false);
2305
2581
  }, [fetchJobs, fetchRecordings, fetchCachedRecordings, fetchDashboardStats, fetchAccountStatus]);
2306
- const transcribeStoppedRecording = useCallback(async () => {
2582
+ const transcribeStoppedRecording = useCallback2(async () => {
2307
2583
  const current = liveRecord;
2308
2584
  if (current?.kind !== "stopped") return;
2309
2585
  const artifact = current.artifact;
@@ -2383,7 +2659,7 @@ function AppShell({
2383
2659
  setNotice("Transcription failed. Press enter to retry.");
2384
2660
  }
2385
2661
  }, [liveRecord, refresh, transcribeRecordingArtifact]);
2386
- const retranscribeStoppedRecording = useCallback(async () => {
2662
+ const retranscribeStoppedRecording = useCallback2(async () => {
2387
2663
  const current = liveRecord;
2388
2664
  if (current?.kind !== "stopped") return;
2389
2665
  const artifact = current.artifact;
@@ -2442,7 +2718,7 @@ function AppShell({
2442
2718
  setNotice("Re-transcription failed. Press T to retry.");
2443
2719
  }
2444
2720
  }, [liveRecord, onRetranscribe, refresh]);
2445
- const retranscribeExistingRecording = useCallback(
2721
+ const retranscribeExistingRecording = useCallback2(
2446
2722
  async (recordingId) => {
2447
2723
  if (!onRetranscribe) {
2448
2724
  setNotice("Re-transcribe is not available in this CLI session.");
@@ -2459,7 +2735,7 @@ function AppShell({
2459
2735
  },
2460
2736
  [onRetranscribe, refresh]
2461
2737
  );
2462
- const refetchTranscript = useCallback(
2738
+ const refetchTranscript = useCallback2(
2463
2739
  (transcriptId) => {
2464
2740
  setTranscriptCache((m) => new Map(m).set(transcriptId, "loading"));
2465
2741
  setSummaryCache((m) => {
@@ -2471,7 +2747,7 @@ function AppShell({
2471
2747
  },
2472
2748
  [fetchTranscript]
2473
2749
  );
2474
- const resummarizeExistingRecording = useCallback(
2750
+ const resummarizeExistingRecording = useCallback2(
2475
2751
  async (recordingId) => {
2476
2752
  if (!onResummarize) {
2477
2753
  setNotice("Re-summarize is not available in this CLI session.");
@@ -2493,8 +2769,8 @@ function AppShell({
2493
2769
  },
2494
2770
  [onResummarize, refresh, recordings, now, refetchTranscript]
2495
2771
  );
2496
- const syncedTextRef = useRef3(/* @__PURE__ */ new Set());
2497
- const syncRecordingText2 = useCallback(
2772
+ const syncedTextRef = useRef4(/* @__PURE__ */ new Set());
2773
+ const syncRecordingText2 = useCallback2(
2498
2774
  async (recordingId, opts = {}) => {
2499
2775
  if (!onSyncRecordingText) return;
2500
2776
  if (opts.manual) setNotice("Syncing text locally\u2026");
@@ -2508,7 +2784,7 @@ function AppShell({
2508
2784
  },
2509
2785
  [onSyncRecordingText]
2510
2786
  );
2511
- const syncRecordingAudio2 = useCallback(
2787
+ const syncRecordingAudio2 = useCallback2(
2512
2788
  async (recordingId) => {
2513
2789
  if (!onSyncRecordingAudio) {
2514
2790
  setNotice("Audio sync is not available in this CLI session.");
@@ -2524,7 +2800,7 @@ function AppShell({
2524
2800
  },
2525
2801
  [onSyncRecordingAudio]
2526
2802
  );
2527
- const exportRecordingForAgent = useCallback(
2803
+ const exportRecordingForAgent = useCallback2(
2528
2804
  async (recordingId) => {
2529
2805
  if (!onExportRecording) {
2530
2806
  setNotice("Export is not available in this CLI session.");
@@ -2552,7 +2828,7 @@ function AppShell({
2552
2828
  autoTranscribeStartedSessionIds.current.add(artifact.sessionId);
2553
2829
  void transcribeStoppedRecording();
2554
2830
  }, [liveRecord, transcribeRecordingArtifact, transcribeStoppedRecording]);
2555
- const loadMoreRecordings = useCallback(async () => {
2831
+ const loadMoreRecordings = useCallback2(async () => {
2556
2832
  if (!fetchRecordings || !recordingsNextCursor || loadingMoreRecordings) return;
2557
2833
  setLoadingMoreRecordings(true);
2558
2834
  try {
@@ -2582,7 +2858,7 @@ function AppShell({
2582
2858
  const id = setInterval(() => void refresh(), pollMs);
2583
2859
  return () => clearInterval(id);
2584
2860
  }, [refresh, pollMs]);
2585
- const transcriptCacheRef = useRef3(transcriptCache);
2861
+ const transcriptCacheRef = useRef4(transcriptCache);
2586
2862
  transcriptCacheRef.current = transcriptCache;
2587
2863
  useEffect4(() => {
2588
2864
  const id = setInterval(() => {
@@ -2652,7 +2928,7 @@ function AppShell({
2652
2928
  selected,
2653
2929
  visibleRecordingRows
2654
2930
  ]);
2655
- const openTranscript = useCallback(
2931
+ const openTranscript = useCallback2(
2656
2932
  async (transcriptId) => {
2657
2933
  setStack((st) => [...st, { kind: "transcript", loading: true }]);
2658
2934
  try {
@@ -2691,7 +2967,7 @@ function AppShell({
2691
2967
  void syncRecordingText2(detailRecordingId);
2692
2968
  }, [detailRecordingId, syncRecordingText2]);
2693
2969
  const setAudio = (recordingId, action) => setAudioCache((m) => new Map(m).set(recordingId, action));
2694
- const runAudio = useCallback(
2970
+ const runAudio = useCallback2(
2695
2971
  async (recordingId, mode) => {
2696
2972
  if (!recordingAudio) {
2697
2973
  setNotice("Audio actions are not available");
@@ -2729,8 +3005,9 @@ function AppShell({
2729
3005
  setStack((st) => st[st.length - 1]?.kind === "record" ? st : [...st, { kind: "record" }]);
2730
3006
  setNotice(void 0);
2731
3007
  };
2732
- useInput8((input, key) => {
3008
+ useInput9((input, key) => {
2733
3009
  setNotice(void 0);
3010
+ if (screen.kind === "ask") return;
2734
3011
  if (screen.kind === "recordSetup") {
2735
3012
  if (input === "q" || key.leftArrow) back();
2736
3013
  return;
@@ -2839,6 +3116,8 @@ function AppShell({
2839
3116
  const links = rec ? resolveRecordingLinks(rec.recordingId, rec.origin) : {};
2840
3117
  if (input === "T" && rec) void retranscribeExistingRecording(rec.recordingId);
2841
3118
  else if ((input === "s" || input === "S") && rec) void resummarizeExistingRecording(rec.recordingId);
3119
+ else if (input === "a" && rec && askRecording)
3120
+ setStack((st) => [...st, { kind: "ask", recordingId: rec.recordingId }]);
2842
3121
  else if (input === "e" && rec) void exportRecordingForAgent(rec.recordingId);
2843
3122
  else if (input === "t" && rec?.activeTranscriptId) void openTranscript(rec.activeTranscriptId);
2844
3123
  else if (input === "o" && rec) void runAudio(rec.recordingId, "open");
@@ -2854,19 +3133,34 @@ function AppShell({
2854
3133
  return;
2855
3134
  }
2856
3135
  });
3136
+ if (screen.kind === "ask" && askRecording) {
3137
+ const rec = recordings.find((r) => r.recordingId === screen.recordingId);
3138
+ const activeTranscriptId = rec?.activeTranscriptId;
3139
+ return /* @__PURE__ */ jsx20(
3140
+ AskScreen,
3141
+ {
3142
+ recordingId: screen.recordingId,
3143
+ title: rec ? recordingTitle2(rec) : void 0,
3144
+ askRecording,
3145
+ spinnerFrame,
3146
+ onBack: back,
3147
+ onOpenTranscript: activeTranscriptId ? () => void openTranscript(activeTranscriptId) : void 0
3148
+ }
3149
+ );
3150
+ }
2857
3151
  if (screen.kind === "transcript") {
2858
- return /* @__PURE__ */ jsx19(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
3152
+ return /* @__PURE__ */ jsx20(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
2859
3153
  }
2860
3154
  if (screen.kind === "jobDetail") {
2861
3155
  const job = jobs.find((j) => j.jobId === screen.jobId);
2862
- if (!job) return !loaded ? /* @__PURE__ */ jsx19(Loading, { label: "job" }) : /* @__PURE__ */ jsx19(Missing, { label: "Job" });
2863
- return /* @__PURE__ */ jsx19(Detail, { notice, children: /* @__PURE__ */ jsx19(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
3156
+ if (!job) return !loaded ? /* @__PURE__ */ jsx20(Loading, { label: "job" }) : /* @__PURE__ */ jsx20(Missing, { label: "Job" });
3157
+ return /* @__PURE__ */ jsx20(Detail, { notice, children: /* @__PURE__ */ jsx20(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
2864
3158
  }
2865
3159
  if (screen.kind === "recordingDetail") {
2866
3160
  const rec = recordings.find((r) => r.recordingId === screen.recordingId);
2867
- if (!rec) return !loaded ? /* @__PURE__ */ jsx19(Loading, { label: "recording" }) : /* @__PURE__ */ jsx19(Missing, { label: "Recording" });
3161
+ if (!rec) return !loaded ? /* @__PURE__ */ jsx20(Loading, { label: "recording" }) : /* @__PURE__ */ jsx20(Missing, { label: "Recording" });
2868
3162
  const detailTranscript = rec.activeTranscriptId ? transcriptCache.get(rec.activeTranscriptId) : void 0;
2869
- return /* @__PURE__ */ jsx19(Detail, { notice, children: /* @__PURE__ */ jsx19(
3163
+ return /* @__PURE__ */ jsx20(Detail, { notice, children: /* @__PURE__ */ jsx20(
2870
3164
  RecordingDetailView,
2871
3165
  {
2872
3166
  item: rec,
@@ -2877,7 +3171,7 @@ function AppShell({
2877
3171
  ) });
2878
3172
  }
2879
3173
  if (screen.kind === "recordSetup") {
2880
- return /* @__PURE__ */ jsx19(Box17, { flexDirection: "column", height: size.rows, paddingX: 1, children: /* @__PURE__ */ jsx19(
3174
+ return /* @__PURE__ */ jsx20(Box18, { flexDirection: "column", height: size.rows, paddingX: 1, children: /* @__PURE__ */ jsx20(
2881
3175
  RecordSetupView,
2882
3176
  {
2883
3177
  model: recordSetupModel,
@@ -2890,10 +3184,10 @@ function AppShell({
2890
3184
  }
2891
3185
  if (screen.kind === "record") {
2892
3186
  if (liveRecord?.kind === "live" && liveRecord.session.mode === "live_captions") {
2893
- return /* @__PURE__ */ jsx19(LiveCaptionsScreen, { source: liveRecord.session.source, now });
3187
+ return /* @__PURE__ */ jsx20(LiveCaptionsScreen, { source: liveRecord.session.source, now });
2894
3188
  }
2895
3189
  if (liveRecord?.kind === "live" || liveRecord?.kind === "starting" || liveRecord?.kind === "stopping" || liveRecord?.kind === "stopped") {
2896
- return /* @__PURE__ */ jsx19(Detail, { notice, children: /* @__PURE__ */ jsx19(
3190
+ return /* @__PURE__ */ jsx20(Detail, { notice, children: /* @__PURE__ */ jsx20(
2897
3191
  RecordFrame,
2898
3192
  {
2899
3193
  telemetry: liveRecord.telemetry,
@@ -2914,18 +3208,18 @@ function AppShell({
2914
3208
  }
2915
3209
  ) });
2916
3210
  }
2917
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
2918
- /* @__PURE__ */ jsx19(Box17, { flexGrow: 1, flexDirection: "column", paddingX: 1, paddingTop: 1, children: liveRecord?.kind === "error" ? (() => {
3211
+ return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
3212
+ /* @__PURE__ */ jsx20(Box18, { flexGrow: 1, flexDirection: "column", paddingX: 1, paddingTop: 1, children: liveRecord?.kind === "error" ? (() => {
2919
3213
  if (liveRecord.code === "record.permission_required") {
2920
- return /* @__PURE__ */ jsx19(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
3214
+ return /* @__PURE__ */ jsx20(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
2921
3215
  }
2922
3216
  const copy = recordErrorCopy(liveRecord.code, liveRecord.message);
2923
- return /* @__PURE__ */ jsxs16(Fragment6, { children: [
2924
- /* @__PURE__ */ jsx19(Text17, { color: copy.tone, children: copy.title }),
2925
- copy.detail ? /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: copy.detail }) : null
3217
+ return /* @__PURE__ */ jsxs17(Fragment6, { children: [
3218
+ /* @__PURE__ */ jsx20(Text18, { color: copy.tone, children: copy.title }),
3219
+ copy.detail ? /* @__PURE__ */ jsx20(Text18, { dimColor: true, children: copy.detail }) : null
2926
3220
  ] });
2927
- })() : /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "Starting recording\u2026" }) }),
2928
- /* @__PURE__ */ jsx19(Footer, { keys: "r retry \xB7 o settings \xB7 q / esc / \u2190 back" })
3221
+ })() : /* @__PURE__ */ jsx20(Text18, { dimColor: true, children: "Starting recording\u2026" }) }),
3222
+ /* @__PURE__ */ jsx20(Footer, { keys: "r retry \xB7 o settings \xB7 q / esc / \u2190 back" })
2929
3223
  ] });
2930
3224
  }
2931
3225
  const tab = screen.kind === "jobs" ? "jobs" : screen.kind === "account" ? "account" : "overview";
@@ -2934,7 +3228,7 @@ function AppShell({
2934
3228
  if (!loaded) {
2935
3229
  position = "";
2936
3230
  const spin = "\u280B\u2819\u2839\u2838\u283C\u2834\u2826\u2827\u2807\u280F"[spinnerFrame % 10];
2937
- body = /* @__PURE__ */ jsx19(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text17, { color: "cyan", children: [
3231
+ body = /* @__PURE__ */ jsx20(Box18, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text18, { color: "cyan", children: [
2938
3232
  spin,
2939
3233
  " Loading\u2026"
2940
3234
  ] }) });
@@ -2950,7 +3244,7 @@ function AppShell({
2950
3244
  const showPeek = size.columns >= 100;
2951
3245
  const peekWidth = showPeek ? 34 : 0;
2952
3246
  const listColumns = showPeek ? Math.max(30, size.columns - peekWidth - 3) : size.columns;
2953
- body = /* @__PURE__ */ jsx19(
3247
+ body = /* @__PURE__ */ jsx20(
2954
3248
  OverviewView,
2955
3249
  {
2956
3250
  recordings: recordings.slice(win.start, win.end),
@@ -2971,11 +3265,11 @@ function AppShell({
2971
3265
  );
2972
3266
  } else if (screen.kind === "account") {
2973
3267
  position = "";
2974
- body = /* @__PURE__ */ jsx19(AccountView, { status: accountStatus, nowMs: now() });
3268
+ body = /* @__PURE__ */ jsx20(AccountView, { status: accountStatus, nowMs: now() });
2975
3269
  } else {
2976
3270
  const win = listWindow(selected, jobs.length, Math.max(3, size.rows - 4));
2977
3271
  position = jobs.length ? `${selected + 1} / ${jobs.length}` : "0";
2978
- body = /* @__PURE__ */ jsx19(
3272
+ body = /* @__PURE__ */ jsx20(
2979
3273
  JobsView,
2980
3274
  {
2981
3275
  items: jobs.slice(win.start, win.end),
@@ -2986,38 +3280,38 @@ function AppShell({
2986
3280
  );
2987
3281
  }
2988
3282
  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`;
2989
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
2990
- /* @__PURE__ */ jsx19(Header, { active: tab }),
2991
- /* @__PURE__ */ jsxs16(Box17, { flexGrow: 1, flexDirection: "column", children: [
3283
+ return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
3284
+ /* @__PURE__ */ jsx20(Header, { active: tab }),
3285
+ /* @__PURE__ */ jsxs17(Box18, { flexGrow: 1, flexDirection: "column", children: [
2992
3286
  body,
2993
- loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx19(Box17, { marginTop: 1, children: /* @__PURE__ */ jsxs16(Text17, { color: "red", children: [
3287
+ loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx20(Box18, { marginTop: 1, children: /* @__PURE__ */ jsxs17(Text18, { color: "red", children: [
2994
3288
  "! ",
2995
3289
  loadError
2996
3290
  ] }) }) : null
2997
3291
  ] }),
2998
- /* @__PURE__ */ jsx19(Footer, { keys: footerKeys })
3292
+ /* @__PURE__ */ jsx20(Footer, { keys: footerKeys })
2999
3293
  ] });
3000
3294
  }
3001
3295
  function Detail({
3002
3296
  notice,
3003
3297
  children
3004
3298
  }) {
3005
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", children: [
3299
+ return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", children: [
3006
3300
  children,
3007
- notice ? /* @__PURE__ */ jsx19(Box17, { paddingX: 1, children: /* @__PURE__ */ jsx19(Text17, { color: "green", children: notice }) }) : null
3301
+ notice ? /* @__PURE__ */ jsx20(Box18, { paddingX: 1, children: /* @__PURE__ */ jsx20(Text18, { color: "green", children: notice }) }) : null
3008
3302
  ] });
3009
3303
  }
3010
3304
  function Missing({ label }) {
3011
- return /* @__PURE__ */ jsxs16(Box17, { flexDirection: "column", paddingX: 1, children: [
3012
- /* @__PURE__ */ jsxs16(Text17, { dimColor: true, children: [
3305
+ return /* @__PURE__ */ jsxs17(Box18, { flexDirection: "column", paddingX: 1, children: [
3306
+ /* @__PURE__ */ jsxs17(Text18, { dimColor: true, children: [
3013
3307
  label,
3014
3308
  " no longer in the list."
3015
3309
  ] }),
3016
- /* @__PURE__ */ jsx19(Text17, { dimColor: true, children: "esc back \xB7 q quit" })
3310
+ /* @__PURE__ */ jsx20(Text18, { dimColor: true, children: "esc back \xB7 q quit" })
3017
3311
  ] });
3018
3312
  }
3019
3313
  function Loading({ label }) {
3020
- return /* @__PURE__ */ jsx19(Box17, { paddingX: 1, children: /* @__PURE__ */ jsxs16(Text17, { color: "cyan", children: [
3314
+ return /* @__PURE__ */ jsx20(Box18, { paddingX: 1, children: /* @__PURE__ */ jsxs17(Text18, { color: "cyan", children: [
3021
3315
  "Loading ",
3022
3316
  label,
3023
3317
  "\u2026"
@@ -3033,6 +3327,7 @@ var init_AppShell = __esm({
3033
3327
  init_OverviewView();
3034
3328
  init_JobDetailView();
3035
3329
  init_RecordingDetailView();
3330
+ init_AskScreen();
3036
3331
  init_TranscriptView();
3037
3332
  init_LiveCaptionsScreen();
3038
3333
  init_PermissionPreflightView();
@@ -3059,7 +3354,7 @@ __export(tui_exports, {
3059
3354
  runDashboard: () => runDashboard,
3060
3355
  useTerminalSize: () => useTerminalSize
3061
3356
  });
3062
- import React11 from "react";
3357
+ import React12 from "react";
3063
3358
  import { render as render2 } from "ink";
3064
3359
  import { spawn as spawn3 } from "child_process";
3065
3360
  function openUrl(url2) {
@@ -3080,7 +3375,7 @@ function copyText(text) {
3080
3375
  async function runDashboard(deps) {
3081
3376
  const renderApp = deps.renderApp ?? render2;
3082
3377
  const app = renderApp(
3083
- React11.createElement(AppShell, {
3378
+ React12.createElement(AppShell, {
3084
3379
  fetchJobs: deps.fetchJobs,
3085
3380
  fetchTranscript: deps.fetchTranscript,
3086
3381
  fetchRecordings: deps.fetchRecordings,
@@ -3098,6 +3393,9 @@ async function runDashboard(deps) {
3098
3393
  onSyncRecordingText: deps.syncRecordingText,
3099
3394
  onSyncRecordingAudio: deps.syncRecordingAudio,
3100
3395
  onExportRecording: deps.exportRecording,
3396
+ fetchAskThread: deps.fetchAskThread,
3397
+ fetchAskSuggestions: deps.fetchAskSuggestions,
3398
+ askRecording: deps.askRecording,
3101
3399
  initialView: deps.initialView ?? "overview",
3102
3400
  openUrl,
3103
3401
  copyText
@@ -19540,6 +19838,62 @@ var RecappiApiClient = class {
19540
19838
  summaryStatus: parsed.summaryStatus
19541
19839
  });
19542
19840
  }
19841
+ async fetchAskThread(recordingId) {
19842
+ const parsed = await this.getJson(
19843
+ `/api/recordings/${encodeURIComponent(recordingId)}/ask-thread`
19844
+ );
19845
+ const thread = isRecord2(parsed.thread) ? {
19846
+ id: stringValue2(parsed.thread.id) ?? "",
19847
+ recordingId: stringValue2(parsed.thread.recordingId) ?? recordingId,
19848
+ ...numberValue2(parsed.thread.createdAt) !== void 0 ? { createdAt: numberValue2(parsed.thread.createdAt) } : {},
19849
+ ...numberValue2(parsed.thread.updatedAt) !== void 0 ? { updatedAt: numberValue2(parsed.thread.updatedAt) } : {}
19850
+ } : null;
19851
+ return {
19852
+ thread: thread && thread.id ? thread : null,
19853
+ messages: Array.isArray(parsed.messages) ? parsed.messages.filter(isRecord2).map(mapAskThreadMessage) : [],
19854
+ origin: this.auth.origin
19855
+ };
19856
+ }
19857
+ async fetchAskSuggestions(recordingId, opts = {}) {
19858
+ const params = new URLSearchParams();
19859
+ if (opts.language) params.set("language", opts.language);
19860
+ const suffix = params.size > 0 ? `?${params}` : "";
19861
+ const parsed = await this.getJson(
19862
+ `/api/recordings/${encodeURIComponent(recordingId)}/ask-suggestions${suffix}`
19863
+ );
19864
+ return {
19865
+ suggestions: Array.isArray(parsed.suggestions) ? parsed.suggestions.filter(isRecord2).flatMap(mapAskSuggestion) : [],
19866
+ ...typeof parsed.transcriptId === "string" ? { transcriptId: parsed.transcriptId } : {},
19867
+ ...typeof parsed.model === "string" ? { model: parsed.model } : {},
19868
+ ...typeof parsed.language === "string" ? { language: parsed.language } : {},
19869
+ ...typeof parsed.cached === "boolean" ? { cached: parsed.cached } : {},
19870
+ origin: this.auth.origin
19871
+ };
19872
+ }
19873
+ async *askRecordingStream(opts) {
19874
+ const response = await this.request(
19875
+ "POST",
19876
+ `/api/recordings/${encodeURIComponent(opts.recordingId)}/ask-thread/messages`,
19877
+ JSON.stringify({
19878
+ question: opts.question,
19879
+ webSearch: opts.webSearch === true,
19880
+ ...opts.model ? { model: opts.model } : {}
19881
+ }),
19882
+ {
19883
+ headers: {
19884
+ accept: "text/event-stream",
19885
+ "content-type": "application/json"
19886
+ }
19887
+ }
19888
+ );
19889
+ if (!response.body) {
19890
+ throw cliError("cloud.invalid_response", "Ask stream response was empty.");
19891
+ }
19892
+ for await (const frame of parseSseFrames(response.body)) {
19893
+ const event = decodeAskStreamEvent(frame.event, frame.data);
19894
+ if (event) yield event;
19895
+ }
19896
+ }
19543
19897
  async downloadRecordingAudio(recordingId, opts = {}) {
19544
19898
  const response = await this.request(
19545
19899
  "GET",
@@ -20194,6 +20548,139 @@ function stringValue2(value) {
20194
20548
  function numberValue2(value) {
20195
20549
  return typeof value === "number" && Number.isFinite(value) ? value : void 0;
20196
20550
  }
20551
+ function mapAskThreadMessage(row) {
20552
+ const id = stringValue2(row.id) ?? "";
20553
+ const role = row.role === "user" ? "user" : "assistant";
20554
+ return {
20555
+ id,
20556
+ role,
20557
+ ...typeof row.status === "string" ? { status: row.status } : {},
20558
+ ...numberValue2(row.sequence) !== void 0 ? { sequence: numberValue2(row.sequence) } : {},
20559
+ content: stringValue2(row.content) ?? "",
20560
+ ...typeof row.model === "string" || row.model === null ? { model: row.model } : {},
20561
+ ...typeof row.webSearch === "boolean" ? { webSearch: row.webSearch } : {},
20562
+ ...typeof row.errorMessage === "string" || row.errorMessage === null ? { errorMessage: row.errorMessage } : {},
20563
+ ...numberValue2(row.createdAt) !== void 0 ? { createdAt: numberValue2(row.createdAt) } : {},
20564
+ ...numberValue2(row.updatedAt) !== void 0 ? { updatedAt: numberValue2(row.updatedAt) } : {},
20565
+ citations: Array.isArray(row.citations) ? row.citations.filter(isRecord2).map(mapAskCitation) : []
20566
+ };
20567
+ }
20568
+ function mapAskSuggestion(row) {
20569
+ const question = stringValue2(row.question)?.trim();
20570
+ if (!question) return [];
20571
+ return [
20572
+ {
20573
+ ...typeof row.id === "string" ? { id: row.id } : {},
20574
+ question,
20575
+ ...typeof row.reason === "string" && row.reason.trim() ? { reason: row.reason } : {}
20576
+ }
20577
+ ];
20578
+ }
20579
+ function mapAskCitation(row) {
20580
+ return {
20581
+ ...typeof row.id === "string" ? { id: row.id } : {},
20582
+ ...typeof row.segmentId === "string" ? { segmentId: row.segmentId } : {},
20583
+ ...numberValue2(row.index) !== void 0 ? { index: numberValue2(row.index) } : {},
20584
+ ...typeof row.startMs === "number" || row.startMs === null ? { startMs: row.startMs } : {},
20585
+ ...typeof row.endMs === "number" || row.endMs === null ? { endMs: row.endMs } : {},
20586
+ ...typeof row.label === "string" ? { label: row.label } : {},
20587
+ ...typeof row.speaker === "string" ? { speaker: row.speaker } : {},
20588
+ ...typeof row.snippet === "string" ? { snippet: row.snippet } : {},
20589
+ ...numberValue2(row.order) !== void 0 ? { order: numberValue2(row.order) } : {}
20590
+ };
20591
+ }
20592
+ async function* parseSseFrames(stream) {
20593
+ const reader = stream.getReader();
20594
+ const decoder = new TextDecoder();
20595
+ let buffer = "";
20596
+ try {
20597
+ for (; ; ) {
20598
+ const { value, done } = await reader.read();
20599
+ if (done) break;
20600
+ buffer += decoder.decode(value, { stream: true });
20601
+ let delimiter = findSseDelimiter(buffer);
20602
+ while (delimiter) {
20603
+ const frameText = buffer.slice(0, delimiter.index);
20604
+ buffer = buffer.slice(delimiter.index + delimiter.length);
20605
+ const frame = parseSseFrame(frameText);
20606
+ if (frame) yield frame;
20607
+ delimiter = findSseDelimiter(buffer);
20608
+ }
20609
+ }
20610
+ buffer += decoder.decode();
20611
+ if (buffer.trim()) {
20612
+ const frame = parseSseFrame(buffer);
20613
+ if (frame) yield frame;
20614
+ }
20615
+ } finally {
20616
+ reader.releaseLock();
20617
+ }
20618
+ }
20619
+ function findSseDelimiter(buffer) {
20620
+ const lf = buffer.indexOf("\n\n");
20621
+ const crlf = buffer.indexOf("\r\n\r\n");
20622
+ if (lf === -1 && crlf === -1) return void 0;
20623
+ if (lf === -1) return { index: crlf, length: 4 };
20624
+ if (crlf === -1) return { index: lf, length: 2 };
20625
+ return crlf < lf ? { index: crlf, length: 4 } : { index: lf, length: 2 };
20626
+ }
20627
+ function parseSseFrame(frameText) {
20628
+ let event = "message";
20629
+ const data = [];
20630
+ for (const rawLine of frameText.split(/\r?\n/)) {
20631
+ if (!rawLine || rawLine.startsWith(":")) continue;
20632
+ if (rawLine.startsWith("event:")) {
20633
+ event = rawLine.slice("event:".length).trim() || "message";
20634
+ } else if (rawLine.startsWith("data:")) {
20635
+ const value = rawLine.slice("data:".length);
20636
+ data.push(value.startsWith(" ") ? value.slice(1) : value);
20637
+ }
20638
+ }
20639
+ if (data.length === 0) return void 0;
20640
+ return { event, data: data.join("\n") };
20641
+ }
20642
+ function decodeAskStreamEvent(eventName, data) {
20643
+ const parsed = JSON.parse(data);
20644
+ if (!isRecord2(parsed)) return void 0;
20645
+ const type = stringValue2(parsed.type) ?? eventName;
20646
+ switch (eventName === "message" ? type : eventName) {
20647
+ case "metadata":
20648
+ return {
20649
+ type: "metadata",
20650
+ ...typeof parsed.recordingId === "string" ? { recordingId: parsed.recordingId } : {},
20651
+ ...typeof parsed.transcriptId === "string" ? { transcriptId: parsed.transcriptId } : {},
20652
+ ...typeof parsed.threadId === "string" ? { threadId: parsed.threadId } : {},
20653
+ ...typeof parsed.userMessageId === "string" ? { userMessageId: parsed.userMessageId } : {},
20654
+ ...typeof parsed.assistantMessageId === "string" ? { assistantMessageId: parsed.assistantMessageId } : {},
20655
+ ...typeof parsed.model === "string" ? { model: parsed.model } : {},
20656
+ ...typeof parsed.webSearch === "boolean" ? { webSearch: parsed.webSearch } : {},
20657
+ ...numberValue2(parsed.segmentCount) !== void 0 ? { segmentCount: numberValue2(parsed.segmentCount) } : {},
20658
+ ...typeof parsed.citationMarker === "string" ? { citationMarker: parsed.citationMarker } : {}
20659
+ };
20660
+ case "answer_delta":
20661
+ return { type: "answer_delta", delta: stringValue2(parsed.delta) ?? "" };
20662
+ case "citation": {
20663
+ const citation = isRecord2(parsed.citation) ? parsed.citation : parsed;
20664
+ return { type: "citation", citation: mapAskCitation(citation) };
20665
+ }
20666
+ case "done":
20667
+ return {
20668
+ type: "done",
20669
+ ...typeof parsed.threadId === "string" ? { threadId: parsed.threadId } : {},
20670
+ ...typeof parsed.userMessageId === "string" ? { userMessageId: parsed.userMessageId } : {},
20671
+ ...typeof parsed.assistantMessageId === "string" ? { assistantMessageId: parsed.assistantMessageId } : {},
20672
+ ...typeof parsed.content === "string" ? { content: parsed.content } : {},
20673
+ citations: Array.isArray(parsed.citations) ? parsed.citations.filter(isRecord2).map(mapAskCitation) : []
20674
+ };
20675
+ case "error":
20676
+ throw cliError(
20677
+ "cloud.http_error",
20678
+ stringValue2(parsed.message) ?? stringValue2(parsed.error) ?? "The assistant ran into an error."
20679
+ );
20680
+ default:
20681
+ return void 0;
20682
+ }
20683
+ }
20197
20684
  function recordingCloudUrl(origin, recordingId) {
20198
20685
  return `${origin.replace(/\/+$/, "")}/recordings/${encodeURIComponent(recordingId)}`;
20199
20686
  }
@@ -20469,6 +20956,7 @@ var COMMON_TASKS = [
20469
20956
  { label: "Export recording bundle", command: "recappi recordings export <recordingId> --dir ./bundle" },
20470
20957
  { label: "List / find recordings", command: "recappi recordings list" },
20471
20958
  { label: "Read a transcript", command: "recappi transcript get <transcriptId>" },
20959
+ { label: "Ask about a recording", command: 'recappi ask <recordingId> "<question>"' },
20472
20960
  { label: "Download / open audio", command: "recappi audio <recordingId> --open" },
20473
20961
  { label: "Check a transcription job", command: "recappi jobs wait <jobId>" },
20474
20962
  { label: "Account \xB7 quota \xB7 usage", command: "recappi account status" },
@@ -20647,6 +21135,23 @@ var COMMAND_METADATA = {
20647
21135
  { description: "Wait for a transcription job", command: "recappi jobs wait <jobId>" }
20648
21136
  ],
20649
21137
  relatedCommands: ["jobs list", "transcript get"]
21138
+ },
21139
+ ask: {
21140
+ capabilities: [
21141
+ "Ask a question about a recording and stream the answer",
21142
+ "Answers cite the transcript with inline \u27E8mm:ss\u27E9 references"
21143
+ ],
21144
+ examples: [
21145
+ {
21146
+ description: "Ask about a recording",
21147
+ command: 'recappi ask <recordingId> "What were the decisions?"'
21148
+ },
21149
+ {
21150
+ description: "Get the raw answer + citations for an agent",
21151
+ command: 'recappi ask <recordingId> "Summarize the risks" --json'
21152
+ }
21153
+ ],
21154
+ relatedCommands: ["recordings get", "transcript get", "recordings list"]
20650
21155
  }
20651
21156
  };
20652
21157
  function commonTasksHelpText() {
@@ -21281,6 +21786,9 @@ function formatTimestamp(ms) {
21281
21786
  return hours > 0 ? `${hours}:${mm}:${ss}` : `${mm}:${ss}`;
21282
21787
  }
21283
21788
 
21789
+ // src/cli.ts
21790
+ init_askInline();
21791
+
21284
21792
  // src/progressStepper.ts
21285
21793
  var STEP_DEFS = [
21286
21794
  { key: "check", label: "Check" },
@@ -23830,6 +24338,9 @@ async function runCli(deps = {}) {
23830
24338
  env: deps.env,
23831
24339
  homeDir: deps.homeDir
23832
24340
  }),
24341
+ fetchAskThread: (recordingId) => client.fetchAskThread(recordingId),
24342
+ fetchAskSuggestions: (recordingId, options) => client.fetchAskSuggestions(recordingId, options),
24343
+ askRecording: (options) => client.askRecordingStream(options),
23833
24344
  initialView: parsed.initialView
23834
24345
  });
23835
24346
  return 0;
@@ -24070,6 +24581,35 @@ async function runCli(deps = {}) {
24070
24581
  renderSuccess("recordings resummarize", data, render3);
24071
24582
  return 0;
24072
24583
  }
24584
+ if (parsed.kind === "ask") {
24585
+ if (render3.mode === "human") render3.stderr("Asking\u2026\n");
24586
+ let content = "";
24587
+ let citations = [];
24588
+ for await (const event of client.askRecordingStream({
24589
+ recordingId: parsed.recordingId,
24590
+ question: parsed.question,
24591
+ ...parsed.webSearch ? { webSearch: true } : {},
24592
+ ...parsed.model ? { model: parsed.model } : {}
24593
+ })) {
24594
+ if (event.type === "answer_delta") content += event.delta;
24595
+ else if (event.type === "citation") citations.push(event.citation);
24596
+ else if (event.type === "done") {
24597
+ if (typeof event.content === "string" && event.content) content = event.content;
24598
+ if (event.citations.length) citations = event.citations;
24599
+ }
24600
+ }
24601
+ if (render3.mode === "human") {
24602
+ render3.stdout(`${formatAskAnswerPlain(content, citations)}
24603
+ `);
24604
+ } else {
24605
+ renderSuccess(
24606
+ "ask",
24607
+ { recordingId: parsed.recordingId, question: parsed.question, answer: content, citations },
24608
+ render3
24609
+ );
24610
+ }
24611
+ return 0;
24612
+ }
24073
24613
  if (parsed.kind === "dashboard-stats") {
24074
24614
  const data = await client.dashboardStats();
24075
24615
  renderSuccess("dashboard stats", data, render3);
@@ -24301,6 +24841,19 @@ Agent mode:
24301
24841
  ...opts.force === true ? { force: true } : {}
24302
24842
  });
24303
24843
  });
24844
+ const ask = program.command("ask <recordingId> <question>").description("Ask a question about a recording; streams an answer with inline citations").option("--web-search", "allow web search for extra context").option("--model <name>", "Ask model", parseStringOption("--model")).addHelpText("after", commandMetadataHelpText("ask"));
24845
+ addCommonOptions(ask);
24846
+ ask.action((recordingId, question, opts, command) => {
24847
+ onSelect({
24848
+ kind: "ask",
24849
+ options: collectGlobalOptions(command),
24850
+ commandName: "ask",
24851
+ recordingId,
24852
+ question,
24853
+ ...opts.webSearch === true ? { webSearch: true } : {},
24854
+ ...typeof opts.model === "string" ? { model: opts.model } : {}
24855
+ });
24856
+ });
24304
24857
  const record2 = program.command("record").description("Record system/mic audio via the Recappi Mini sidecar").option("--title <title>", "recording title", parseStringOption("--title")).option("--live", "show live captions while recording").option("--no-system-audio", "record microphone only").option("--no-microphone", "record system audio only").option(
24305
24858
  "--translation-language <lang>",
24306
24859
  "live caption translation language",