recappi 0.1.36 → 0.1.38

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
@@ -597,7 +597,7 @@ var init_LiveCaptionsScreen = __esm({
597
597
  // src/tui/RecordingHeroScreen.tsx
598
598
  import { useEffect as useEffect2, useState as useState3 } from "react";
599
599
  import { Box as Box2, Text as Text2 } from "ink";
600
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
600
+ import { Fragment, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
601
601
  function waveform(samples, width) {
602
602
  if (width <= 0) return "";
603
603
  const tail = samples.slice(-width);
@@ -611,6 +611,7 @@ function waveform(samples, width) {
611
611
  function RecordingHeroScreen({
612
612
  telemetry,
613
613
  artifact,
614
+ captions,
614
615
  canTranscribe = false,
615
616
  canPause = false,
616
617
  now = () => Date.now()
@@ -674,7 +675,8 @@ function RecordingHeroScreen({
674
675
  /* @__PURE__ */ jsx3(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
675
676
  telemetry.sourceLabel,
676
677
  telemetry.micEnabled ? " + Microphone" : ""
677
- ] }) })
678
+ ] }) }),
679
+ captions ? /* @__PURE__ */ jsx3(Box2, { marginTop: 1, flexDirection: "column", alignItems: "center", width: innerWidth, children: /* @__PURE__ */ jsx3(HeroCaptions, { state: captions }) }) : null
678
680
  ] }),
679
681
  /* @__PURE__ */ jsx3(Box2, { children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
680
682
  "q stop & save",
@@ -682,6 +684,25 @@ function RecordingHeroScreen({
682
684
  ] }) })
683
685
  ] });
684
686
  }
687
+ function HeroCaptions({ state }) {
688
+ const MAX_LINES = 3;
689
+ const recent = state.lines.slice(-MAX_LINES);
690
+ const hasPartial = Boolean(state.partial && state.partial.length > 0);
691
+ if (recent.length === 0 && !hasPartial) {
692
+ return /* @__PURE__ */ jsx3(Text2, { dimColor: true, children: state.status === "live" ? "Listening for speech\u2026" : liveCaptionStatusLabel(state.status) });
693
+ }
694
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
695
+ recent.map((line) => /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", alignItems: "center", children: [
696
+ /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate-end", children: [
697
+ line.speaker ? `${line.speaker}: ` : "",
698
+ line.text
699
+ ] }),
700
+ line.translation ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate-end", children: `\u21B3 ${line.translation}` }) : null
701
+ ] }, line.id)),
702
+ hasPartial ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate-end", children: state.partial }) : null,
703
+ state.translationPartial ? /* @__PURE__ */ jsx3(Text2, { dimColor: true, wrap: "truncate-end", children: `\u21B3 ${state.translationPartial}` }) : null
704
+ ] });
705
+ }
685
706
  function stoppedHandoffCopy(artifact, canTranscribe) {
686
707
  if (artifact?.uploadStatus === "uploading") {
687
708
  return { text: "Uploading\u2026", tone: "normal" };
@@ -708,6 +729,7 @@ var init_RecordingHeroScreen = __esm({
708
729
  "src/tui/RecordingHeroScreen.tsx"() {
709
730
  "use strict";
710
731
  init_format();
732
+ init_liveCaptions();
711
733
  init_terminal();
712
734
  BLOCKS = " \u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588";
713
735
  }
@@ -715,7 +737,7 @@ var init_RecordingHeroScreen = __esm({
715
737
 
716
738
  // src/tui/AccountView.tsx
717
739
  import { Box as Box3, Text as Text3 } from "ink";
718
- import { Fragment, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
740
+ import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
719
741
  function AccountView({ status }) {
720
742
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, children: [
721
743
  /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: "\u2039 Account" }),
@@ -728,7 +750,7 @@ function AccountView({ status }) {
728
750
  ] });
729
751
  }
730
752
  function AccountBody({ status }) {
731
- return /* @__PURE__ */ jsxs3(Fragment, { children: [
753
+ return /* @__PURE__ */ jsxs3(Fragment2, { children: [
732
754
  /* @__PURE__ */ jsxs3(Box3, { marginTop: 1, flexDirection: "column", children: [
733
755
  /* @__PURE__ */ jsx5(Text3, { bold: true, color: "green", children: status.email ?? status.userId ?? "Signed in" }),
734
756
  status.email && status.userId ? /* @__PURE__ */ jsx5(Text3, { dimColor: true, children: status.userId }) : null,
@@ -1009,7 +1031,7 @@ var init_RecordingsView = __esm({
1009
1031
 
1010
1032
  // src/tui/RecordingPeek.tsx
1011
1033
  import { Box as Box9, Text as Text9 } from "ink";
1012
- import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1034
+ import { Fragment as Fragment3, jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1013
1035
  function RecordingPeek({
1014
1036
  item,
1015
1037
  summary,
@@ -1029,7 +1051,7 @@ function PeekBody({
1029
1051
  formatBytes2(item.sizeBytes) || null,
1030
1052
  item.contentType || null
1031
1053
  ].filter(Boolean).join(" \xB7 ");
1032
- return /* @__PURE__ */ jsxs8(Fragment2, { children: [
1054
+ return /* @__PURE__ */ jsxs8(Fragment3, { children: [
1033
1055
  /* @__PURE__ */ jsx11(Text9, { bold: true, color: "green", wrap: "truncate-end", children: recordingTitle2(item) }),
1034
1056
  /* @__PURE__ */ jsx11(Text9, { color: style.color, children: `${style.glyph} ${style.label}` }),
1035
1057
  meta3 ? /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: meta3 }) : null,
@@ -1049,7 +1071,7 @@ function SummarySection({
1049
1071
  return /* @__PURE__ */ jsx11(Text9, { dimColor: true, children: `Summary ${summary.status}` });
1050
1072
  }
1051
1073
  const points = (summary.keyPoints ?? []).slice(0, 3);
1052
- return /* @__PURE__ */ jsxs8(Fragment2, { children: [
1074
+ return /* @__PURE__ */ jsxs8(Fragment3, { children: [
1053
1075
  /* @__PURE__ */ jsx11(Text9, { bold: true, children: "Summary" }),
1054
1076
  /* @__PURE__ */ jsx11(Text9, { children: summary.tldr }),
1055
1077
  points.length > 0 ? /* @__PURE__ */ jsx11(Box9, { marginTop: 1, flexDirection: "column", children: points.map((point, i) => /* @__PURE__ */ jsx11(Text9, { dimColor: true, wrap: "truncate-end", children: `\u2022 ${point}` }, i)) }) : null
@@ -1262,7 +1284,7 @@ var init_JobDetailView = __esm({
1262
1284
  // src/tui/RecordingDetailView.tsx
1263
1285
  import React6, { useMemo as useMemo2, useState as useState4 } from "react";
1264
1286
  import { Box as Box12, Text as Text12, useInput as useInput3 } from "ink";
1265
- import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1287
+ import { Fragment as Fragment4, jsx as jsx14, jsxs as jsxs11 } from "react/jsx-runtime";
1266
1288
  function RecordingDetailView({
1267
1289
  item,
1268
1290
  nowMs,
@@ -1334,7 +1356,7 @@ function RecordingDetailView({
1334
1356
  ] }),
1335
1357
  meta3 ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: meta3 }) : null,
1336
1358
  /* @__PURE__ */ jsx14(AudioActionRow, { item, audio }),
1337
- !item.activeTranscriptId ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Transcript not available yet" }) }) : transcript === "loading" || transcript === void 0 ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Loading\u2026" }) }) : transcript === "error" ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "(transcript unavailable)" }) }) : /* @__PURE__ */ jsxs11(Fragment3, { children: [
1359
+ !item.activeTranscriptId ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Transcript not available yet" }) }) : transcript === "loading" || transcript === void 0 ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "Loading\u2026" }) }) : transcript === "error" ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, children: /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "(transcript unavailable)" }) }) : /* @__PURE__ */ jsxs11(Fragment4, { children: [
1338
1360
  /* @__PURE__ */ jsx14(TabBar, { active: tab }),
1339
1361
  /* @__PURE__ */ jsx14(Box12, { marginTop: 1, flexDirection: "column", children: tab === "summary" ? /* @__PURE__ */ jsx14(SummaryPane, { summary, budget: paneBudget }) : tab === "chapters" ? /* @__PURE__ */ jsx14(ChaptersPane, { chapters, win: chapWin, selectedIndex: chapterSel }) : /* @__PURE__ */ jsx14(TranscriptPane, { segments, win: segWin }) })
1340
1362
  ] }),
@@ -1399,7 +1421,7 @@ function SummaryPane({
1399
1421
  return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `Summary ${summary?.status ?? "unavailable"}` });
1400
1422
  }
1401
1423
  const points = (summary.keyPoints ?? []).slice(0, Math.max(1, budget - 4));
1402
- return /* @__PURE__ */ jsxs11(Fragment3, { children: [
1424
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1403
1425
  /* @__PURE__ */ jsx14(Text12, { children: summary.tldr }),
1404
1426
  points.length > 0 ? /* @__PURE__ */ jsx14(Box12, { marginTop: 1, flexDirection: "column", children: points.map((point, i) => /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `\u2022 ${point}` }, i)) }) : null
1405
1427
  ] });
@@ -1410,7 +1432,7 @@ function ChaptersPane({
1410
1432
  selectedIndex
1411
1433
  }) {
1412
1434
  if (chapters.length === 0) return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "No chapters" });
1413
- return /* @__PURE__ */ jsxs11(Fragment3, { children: [
1435
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1414
1436
  chapters.slice(win.start, win.end).map((chapter, i) => {
1415
1437
  const index = win.start + i;
1416
1438
  const selected = index === selectedIndex;
@@ -1428,7 +1450,7 @@ function TranscriptPane({
1428
1450
  win
1429
1451
  }) {
1430
1452
  if (segments.length === 0) return /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: "(no segments)" });
1431
- return /* @__PURE__ */ jsxs11(Fragment3, { children: [
1453
+ return /* @__PURE__ */ jsxs11(Fragment4, { children: [
1432
1454
  segments.slice(win.start, win.end).map((seg, i) => /* @__PURE__ */ jsxs11(Text12, { children: [
1433
1455
  /* @__PURE__ */ jsx14(Text12, { color: "blue", children: `[${formatClockMs(seg.startMs)}] ` }),
1434
1456
  seg.speaker ? /* @__PURE__ */ jsx14(Text12, { dimColor: true, children: `${seg.speaker} ` }) : null,
@@ -1698,7 +1720,7 @@ var init_RecordSetupView = __esm({
1698
1720
  // src/tui/AppShell.tsx
1699
1721
  import { useCallback, useEffect as useEffect3, useState as useState7 } from "react";
1700
1722
  import { Box as Box16, Text as Text16, useApp, useInput as useInput6 } from "ink";
1701
- import { Fragment as Fragment4, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1723
+ import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs15 } from "react/jsx-runtime";
1702
1724
  function recordErrorCopy(code, message) {
1703
1725
  switch (code) {
1704
1726
  case "record.helper_unavailable":
@@ -1897,7 +1919,8 @@ function AppShell({
1897
1919
  kind: "live",
1898
1920
  session,
1899
1921
  selection,
1900
- telemetry: { ...current.telemetry, status: "recording" }
1922
+ telemetry: { ...current.telemetry, status: "recording" },
1923
+ captions: session.captionStreamEnabled ? initialLiveCaptionsState() : void 0
1901
1924
  };
1902
1925
  });
1903
1926
  }).catch((error51) => {
@@ -1914,7 +1937,8 @@ function AppShell({
1914
1937
  kind: "stopping",
1915
1938
  session: current.session,
1916
1939
  selection: current.selection,
1917
- telemetry: stoppingTelemetry
1940
+ telemetry: stoppingTelemetry,
1941
+ captions: current.captions
1918
1942
  });
1919
1943
  try {
1920
1944
  const data = await current.session.stop();
@@ -1948,11 +1972,18 @@ function AppShell({
1948
1972
  if (!liveSession) return;
1949
1973
  const session = liveSession;
1950
1974
  const unsubscribe = session.source.onEvent((event) => {
1975
+ const captionEvent = session.captionStreamEnabled ? sidecarToLiveCaptionEvent(event) : null;
1951
1976
  setLiveRecord((current) => {
1952
1977
  if (current?.kind !== "live" || current.session !== session) return current;
1953
1978
  return {
1954
1979
  ...current,
1955
- telemetry: applyRecordingEventToTelemetry(current.telemetry, event)
1980
+ telemetry: applyRecordingEventToTelemetry(current.telemetry, event),
1981
+ ...captionEvent ? {
1982
+ captions: liveCaptionReducer(
1983
+ current.captions ?? initialLiveCaptionsState(),
1984
+ captionEvent
1985
+ )
1986
+ } : {}
1956
1987
  };
1957
1988
  });
1958
1989
  });
@@ -2316,6 +2347,7 @@ function AppShell({
2316
2347
  RecordingHeroScreen,
2317
2348
  {
2318
2349
  telemetry: liveRecord.telemetry,
2350
+ captions: liveRecord.kind === "live" || liveRecord.kind === "stopping" ? liveRecord.captions : void 0,
2319
2351
  artifact: liveRecord.kind === "stopped" ? liveRecord.artifact : void 0,
2320
2352
  canTranscribe: Boolean(transcribeRecordingArtifact),
2321
2353
  now
@@ -2328,7 +2360,7 @@ function AppShell({
2328
2360
  return /* @__PURE__ */ jsx18(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
2329
2361
  }
2330
2362
  const copy = recordErrorCopy(liveRecord.code, liveRecord.message);
2331
- return /* @__PURE__ */ jsxs15(Fragment4, { children: [
2363
+ return /* @__PURE__ */ jsxs15(Fragment5, { children: [
2332
2364
  /* @__PURE__ */ jsx18(Text16, { color: copy.tone, children: copy.title }),
2333
2365
  copy.detail ? /* @__PURE__ */ jsx18(Text16, { dimColor: true, children: copy.detail }) : null
2334
2366
  ] });
@@ -2430,6 +2462,7 @@ var init_AppShell = __esm({
2430
2462
  init_PermissionPreflightView();
2431
2463
  init_RecordSetupView();
2432
2464
  init_RecordingHeroScreen();
2465
+ init_liveCaptions();
2433
2466
  init_recordingCore();
2434
2467
  init_format();
2435
2468
  init_terminal();
@@ -20423,6 +20456,7 @@ function createFifo(path6) {
20423
20456
  // src/record.tsx
20424
20457
  init_LiveCaptionsScreen();
20425
20458
  init_RecordingHeroScreen();
20459
+ init_liveCaptions();
20426
20460
  import { jsx as jsx4 } from "react/jsx-runtime";
20427
20461
  var SIDECAR_COMMAND_ENV = "RECAPPI_MINI_SIDECAR";
20428
20462
  var SIDECAR_HELPER_NAME = "RecappiMiniSidecar";
@@ -20444,6 +20478,7 @@ async function recordViaSidecar(opts) {
20444
20478
  source: session.source,
20445
20479
  sourceLabel: opts.includeSystemAudio === false ? "Microphone" : "System audio \xB7 all apps",
20446
20480
  micEnabled: opts.includeMicrophone !== false,
20481
+ captionStreamEnabled: opts.live === true,
20447
20482
  renderApp: opts.runtime?.renderApp,
20448
20483
  now: opts.runtime?.now
20449
20484
  });
@@ -20482,6 +20517,7 @@ async function startLiveRecordSession(opts, selection = {
20482
20517
  });
20483
20518
  return {
20484
20519
  mode: "local",
20520
+ captionStreamEnabled: true,
20485
20521
  source: session.source,
20486
20522
  stop: session.stop
20487
20523
  };
@@ -20917,6 +20953,7 @@ function createInkRecordingHeroRenderer(opts) {
20917
20953
  source: opts.source,
20918
20954
  sourceLabel: opts.sourceLabel,
20919
20955
  micEnabled: opts.micEnabled,
20956
+ captionStreamEnabled: opts.captionStreamEnabled,
20920
20957
  onStop,
20921
20958
  now: opts.now ?? Date.now
20922
20959
  }
@@ -20935,6 +20972,7 @@ function RecordingHeroLive({
20935
20972
  source,
20936
20973
  sourceLabel,
20937
20974
  micEnabled,
20975
+ captionStreamEnabled,
20938
20976
  onStop,
20939
20977
  now
20940
20978
  }) {
@@ -20944,15 +20982,26 @@ function RecordingHeroLive({
20944
20982
  sourceLabel,
20945
20983
  micEnabled
20946
20984
  }));
20985
+ const [captions, setCaptions] = React4.useState(initialLiveCaptionsState);
20947
20986
  React4.useEffect(() => {
20948
20987
  return source.onEvent((event) => {
20949
20988
  setTelemetry((prev) => applyRecordingEventToTelemetry(prev, event));
20989
+ if (!captionStreamEnabled) return;
20990
+ const mapped = sidecarToLiveCaptionEvent(event);
20991
+ if (mapped) setCaptions((prev) => liveCaptionReducer(prev, mapped));
20950
20992
  });
20951
- }, [source]);
20993
+ }, [captionStreamEnabled, source]);
20952
20994
  useInput2((input, key) => {
20953
20995
  if (input === "q" || key.escape) onStop();
20954
20996
  });
20955
- return /* @__PURE__ */ jsx4(RecordingHeroScreen, { telemetry, now });
20997
+ return /* @__PURE__ */ jsx4(
20998
+ RecordingHeroScreen,
20999
+ {
21000
+ telemetry,
21001
+ captions: captionStreamEnabled ? captions : void 0,
21002
+ now
21003
+ }
21004
+ );
20956
21005
  }
20957
21006
 
20958
21007
  // src/cli.ts