recappi 0.1.18 → 0.1.20

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
@@ -20,17 +20,18 @@ function recordingCaptureMappingFromSelection(selection = DEFAULT_RECORDING_SELE
20
20
  source,
21
21
  includeSystemAudio: false,
22
22
  includeMicrophone: true,
23
+ ...selection.microphoneDeviceId ? { microphoneDeviceId: selection.microphoneDeviceId } : {},
23
24
  sourceLabel: source.label,
24
25
  micEnabled: true
25
26
  };
26
27
  }
28
+ const microphoneDeviceId = selection.includeMicrophone && selection.microphoneDeviceId ? selection.microphoneDeviceId : void 0;
27
29
  return {
28
30
  source,
29
- // Current helper support is system-wide capture. App-specific targeting is
30
- // represented in the core model, then falls back to system capture until the
31
- // Darwin adapter exposes a target app option.
32
31
  includeSystemAudio: true,
33
32
  includeMicrophone: selection.includeMicrophone,
33
+ ...source.kind === "app" && source.bundleId ? { targetBundleId: source.bundleId } : {},
34
+ ...microphoneDeviceId ? { microphoneDeviceId } : {},
34
35
  sourceLabel: source.label,
35
36
  micEnabled: selection.includeMicrophone
36
37
  };
@@ -1465,21 +1466,30 @@ function RecordSetupView({
1465
1466
  const size = useTerminalSize();
1466
1467
  const [srcIdx, setSrcIdx] = useState5(0);
1467
1468
  const [includeMic, setIncludeMic] = useState5(true);
1469
+ const [micIdx, setMicIdx] = useState5(
1470
+ () => Math.max(0, model.microphones?.findIndex((device) => device.isDefault) ?? 0)
1471
+ );
1468
1472
  const [sceneIdx, setSceneIdx] = useState5(0);
1469
1473
  const sources = model.sources;
1474
+ const microphones = model.microphones ?? [];
1470
1475
  const selected = sources[Math.min(srcIdx, Math.max(0, sources.length - 1))];
1476
+ const selectedMic = microphones[Math.min(micIdx, Math.max(0, microphones.length - 1))];
1471
1477
  const wide = size.columns >= 100;
1472
1478
  const hasAppSource = sources.some((source) => source.kind === "app");
1473
1479
  const hasMultipleSources = sources.length > 1;
1480
+ const hasMultipleMicrophones = microphones.length > 1;
1474
1481
  useInput5((input, key) => {
1475
1482
  if (key.upArrow || input === "k") setSrcIdx((i) => Math.max(0, i - 1));
1476
1483
  else if (key.downArrow || input === "j") setSrcIdx((i) => Math.min(sources.length - 1, i + 1));
1477
1484
  else if (input === " ") setIncludeMic((m) => !m);
1485
+ else if (input === "m" && includeMic && hasMultipleMicrophones)
1486
+ setMicIdx((i) => (i + 1) % microphones.length);
1478
1487
  else if (input === "s" && model.scenes.length > 1) setSceneIdx((i) => (i + 1) % model.scenes.length);
1479
1488
  else if (key.return && selected) {
1480
1489
  onStart({
1481
1490
  sourceId: selected.id,
1482
1491
  includeMicrophone: includeMic,
1492
+ ...includeMic && selectedMic ? { microphoneDeviceId: selectedMic.id } : {},
1483
1493
  sceneId: model.scenes[sceneIdx]?.id
1484
1494
  });
1485
1495
  } else if (key.escape) onCancel();
@@ -1503,6 +1513,7 @@ function RecordSetupView({
1503
1513
  const shortcuts = [
1504
1514
  hasMultipleSources ? "\u2191\u2193 source" : void 0,
1505
1515
  "space mic",
1516
+ includeMic && hasMultipleMicrophones ? "m mic device" : void 0,
1506
1517
  model.scenes.length > 1 ? "s scene" : void 0,
1507
1518
  "\u23CE start recording",
1508
1519
  "esc cancel"
@@ -1511,7 +1522,7 @@ function RecordSetupView({
1511
1522
  /* @__PURE__ */ jsx16(Text14, { bold: true, color: "magenta", children: "New recording" }),
1512
1523
  /* @__PURE__ */ jsxs13(Box14, { marginTop: 1, flexDirection: wide ? "row" : "column", children: [
1513
1524
  /* @__PURE__ */ jsx16(Box14, { flexGrow: 1, flexDirection: "column", children: sourceList }),
1514
- wide ? /* @__PURE__ */ jsx16(Box14, { marginLeft: 4, children: capturePlan }) : null
1525
+ /* @__PURE__ */ jsx16(Box14, { marginLeft: wide ? 4 : 0, marginTop: wide ? 0 : 1, children: capturePlan })
1515
1526
  ] }),
1516
1527
  /* @__PURE__ */ jsxs13(Box14, { marginTop: 1, flexDirection: "column", children: [
1517
1528
  /* @__PURE__ */ jsxs13(Text14, { children: [
@@ -1519,6 +1530,12 @@ function RecordSetupView({
1519
1530
  /* @__PURE__ */ jsx16(Text14, { color: includeMic ? "green" : "gray", children: includeMic ? "[x] include mic" : "[ ] include mic" }),
1520
1531
  /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: " (space)" })
1521
1532
  ] }),
1533
+ selectedMic ? /* @__PURE__ */ jsxs13(Text14, { dimColor: !includeMic, children: [
1534
+ /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Mic device " }),
1535
+ /* @__PURE__ */ jsx16(Text14, { children: selectedMic.label }),
1536
+ selectedMic.isDefault ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: " \xB7 default" }) : null,
1537
+ includeMic && hasMultipleMicrophones ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: " (m to change)" }) : null
1538
+ ] }) : null,
1522
1539
  model.scenes.length > 0 ? /* @__PURE__ */ jsxs13(Text14, { children: [
1523
1540
  /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Scene " }),
1524
1541
  /* @__PURE__ */ jsx16(Text14, { children: model.scenes[sceneIdx]?.label ?? "Default" }),
@@ -1551,6 +1568,8 @@ function waveform(samples, width) {
1551
1568
  }
1552
1569
  function RecordingHeroScreen({
1553
1570
  telemetry,
1571
+ artifact,
1572
+ canTranscribe = false,
1554
1573
  canPause = false,
1555
1574
  now = () => Date.now()
1556
1575
  }) {
@@ -1568,6 +1587,7 @@ function RecordingHeroScreen({
1568
1587
  const elapsed = telemetry.startedAtMs != null ? formatClockMs(Math.max(0, tick - telemetry.startedAtMs)) : "00:00";
1569
1588
  const innerWidth = Math.max(10, size.columns - 4);
1570
1589
  if (telemetry.status === "stopped") {
1590
+ const handoff = stoppedHandoffCopy(artifact, canTranscribe);
1571
1591
  const meta3 = [
1572
1592
  telemetry.durationMs != null ? formatClockMs(telemetry.durationMs) : null,
1573
1593
  formatBytes2(telemetry.sizeBytes) || null
@@ -1579,7 +1599,10 @@ function RecordingHeroScreen({
1579
1599
  meta3 ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: meta3 }) : null,
1580
1600
  telemetry.savedPath ? /* @__PURE__ */ jsx17(Text15, { dimColor: true, wrap: "truncate-middle", children: telemetry.savedPath }) : null
1581
1601
  ] }),
1582
- /* @__PURE__ */ jsx17(Box15, { marginTop: 1, children: /* @__PURE__ */ jsx17(Text15, { dimColor: true, children: "Transcription handoff coming soon \xB7 esc back" }) })
1602
+ /* @__PURE__ */ jsxs14(Box15, { marginTop: 1, children: [
1603
+ /* @__PURE__ */ jsx17(Text15, { color: handoff.tone === "red" ? "red" : handoff.tone === "green" ? "green" : void 0, dimColor: handoff.tone === "dim", children: handoff.text }),
1604
+ artifact?.error ? /* @__PURE__ */ jsx17(Text15, { color: "red", wrap: "truncate-end", children: artifact.error }) : null
1605
+ ] })
1583
1606
  ] });
1584
1607
  }
1585
1608
  if (telemetry.status === "error") {
@@ -1612,6 +1635,27 @@ function RecordingHeroScreen({
1612
1635
  ] }) })
1613
1636
  ] });
1614
1637
  }
1638
+ function stoppedHandoffCopy(artifact, canTranscribe) {
1639
+ if (artifact?.uploadStatus === "uploading") {
1640
+ return { text: "Uploading\u2026", tone: "normal" };
1641
+ }
1642
+ if (artifact?.transcriptionStatus === "processing") {
1643
+ return { text: "Transcribing\u2026", tone: "normal" };
1644
+ }
1645
+ if (artifact?.transcriptionStatus === "queued") {
1646
+ return { text: "Transcription queued \xB7 \u23CE open recording \xB7 n not now", tone: "green" };
1647
+ }
1648
+ if (artifact?.transcriptionStatus === "ready") {
1649
+ return { text: "Transcription ready \xB7 \u23CE open recording \xB7 n not now", tone: "green" };
1650
+ }
1651
+ if (artifact?.uploadStatus === "failed" || artifact?.transcriptionStatus === "failed") {
1652
+ return { text: "Transcription failed \xB7 \u23CE retry \xB7 n not now", tone: "red" };
1653
+ }
1654
+ if (!canTranscribe || !artifact?.audioPath) {
1655
+ return { text: "Saved locally \xB7 n back", tone: "dim" };
1656
+ }
1657
+ return { text: "Transcribe now? \u23CE yes \xB7 n not now", tone: "normal" };
1658
+ }
1615
1659
  var BLOCKS;
1616
1660
  var init_RecordingHeroScreen = __esm({
1617
1661
  "src/tui/RecordingHeroScreen.tsx"() {
@@ -1703,7 +1747,9 @@ function AppShell({
1703
1747
  fetchAccountStatus,
1704
1748
  recordingAudio,
1705
1749
  listDownloadedRecordingIds,
1750
+ fetchRecordSetup,
1706
1751
  startLiveRecord,
1752
+ transcribeRecordingArtifact,
1707
1753
  initialView = "overview",
1708
1754
  openUrl: openUrl2,
1709
1755
  copyText: copyText2,
@@ -1733,8 +1779,13 @@ function AppShell({
1733
1779
  const [audioCache, setAudioCache] = useState7(() => /* @__PURE__ */ new Map());
1734
1780
  const [downloadedIds, setDownloadedIds] = useState7(() => /* @__PURE__ */ new Set());
1735
1781
  const [liveRecord, setLiveRecord] = useState7(void 0);
1736
- const recordSetupModel = {
1782
+ const [recordSetupInputs, setRecordSetupInputs] = useState7({
1737
1783
  sources: DEFAULT_RECORDING_SOURCES,
1784
+ microphones: []
1785
+ });
1786
+ const recordSetupModel = {
1787
+ sources: recordSetupInputs.sources.length > 0 ? recordSetupInputs.sources : DEFAULT_RECORDING_SOURCES,
1788
+ microphones: recordSetupInputs.microphones ?? [],
1738
1789
  scenes: DEFAULT_RECORDING_SCENES
1739
1790
  };
1740
1791
  const refreshDownloadedIds = useCallback(async () => {
@@ -1750,7 +1801,7 @@ function AppShell({
1750
1801
  const screen = stack[stack.length - 1];
1751
1802
  const beginLiveRecord = useCallback(
1752
1803
  (selection = DEFAULT_RECORDING_SELECTION) => {
1753
- const capture = recordingCaptureMappingFromSelection(selection, DEFAULT_RECORDING_SOURCES);
1804
+ const capture = recordingCaptureMappingFromSelection(selection, recordSetupModel.sources);
1754
1805
  const telemetry = {
1755
1806
  status: "starting",
1756
1807
  startedAtMs: now(),
@@ -1771,7 +1822,7 @@ function AppShell({
1771
1822
  return;
1772
1823
  }
1773
1824
  setLiveRecord({ kind: "starting", selection, telemetry });
1774
- startLiveRecord(selection).then((session) => {
1825
+ startLiveRecord(selection, recordSetupModel.sources).then((session) => {
1775
1826
  setLiveRecord((current) => {
1776
1827
  if (current?.kind !== "starting") return current;
1777
1828
  return {
@@ -1785,7 +1836,7 @@ function AppShell({
1785
1836
  setLiveRecord(recordErrorState(error51, selection));
1786
1837
  });
1787
1838
  },
1788
- [now, startLiveRecord]
1839
+ [now, recordSetupModel.sources, startLiveRecord]
1789
1840
  );
1790
1841
  const stopLiveRecord = useCallback(async () => {
1791
1842
  const current = liveRecord;
@@ -1884,6 +1935,63 @@ function AppShell({
1884
1935
  setAccountStatus("error");
1885
1936
  }
1886
1937
  }, [fetchJobs, fetchRecordings, fetchDashboardStats, fetchAccountStatus]);
1938
+ const transcribeStoppedRecording = useCallback(async () => {
1939
+ const current = liveRecord;
1940
+ if (current?.kind !== "stopped") return;
1941
+ const artifact = current.artifact;
1942
+ if (artifact?.recordingId && artifact.uploadStatus === "uploaded") {
1943
+ await refresh({ resetRecordings: true });
1944
+ setStack([{ kind: "overview" }, { kind: "recordingDetail", recordingId: artifact.recordingId }]);
1945
+ return;
1946
+ }
1947
+ if (!artifact?.audioPath) {
1948
+ setNotice("No local audio file is available to transcribe.");
1949
+ return;
1950
+ }
1951
+ if (!transcribeRecordingArtifact) {
1952
+ setNotice("Transcription is not available in this CLI session.");
1953
+ return;
1954
+ }
1955
+ setLiveRecord({
1956
+ ...current,
1957
+ artifact: {
1958
+ ...artifact,
1959
+ uploadStatus: "uploading",
1960
+ transcriptionStatus: "not_started",
1961
+ error: void 0
1962
+ }
1963
+ });
1964
+ try {
1965
+ const uploaded = await transcribeRecordingArtifact(artifact);
1966
+ const transcriptionStatus = uploaded.transcriptId != null || uploaded.status === "succeeded" || uploaded.status === "ready" ? "ready" : uploaded.status === "running" ? "processing" : uploaded.jobId ? "queued" : "not_started";
1967
+ setLiveRecord({
1968
+ ...current,
1969
+ artifact: {
1970
+ ...artifact,
1971
+ recordingId: uploaded.recordingId,
1972
+ ...uploaded.jobId ? { jobId: uploaded.jobId } : {},
1973
+ ...uploaded.transcriptId ? { transcriptId: uploaded.transcriptId } : {},
1974
+ uploadStatus: "uploaded",
1975
+ transcriptionStatus
1976
+ }
1977
+ });
1978
+ setNotice(
1979
+ transcriptionStatus === "ready" ? "Transcription ready." : uploaded.jobId ? "Transcription queued." : "Uploaded to Recappi Cloud."
1980
+ );
1981
+ await refresh({ resetRecordings: true });
1982
+ } catch (error51) {
1983
+ setLiveRecord({
1984
+ ...current,
1985
+ artifact: {
1986
+ ...artifact,
1987
+ uploadStatus: "failed",
1988
+ transcriptionStatus: "failed",
1989
+ error: error51 instanceof Error ? error51.message : String(error51)
1990
+ }
1991
+ });
1992
+ setNotice("Transcription failed. Press enter to retry.");
1993
+ }
1994
+ }, [liveRecord, refresh, transcribeRecordingArtifact]);
1887
1995
  const loadMoreRecordings = useCallback(async () => {
1888
1996
  if (!fetchRecordings || !recordingsNextCursor || loadingMoreRecordings) return;
1889
1997
  setLoadingMoreRecordings(true);
@@ -2029,7 +2137,7 @@ function AppShell({
2029
2137
  return;
2030
2138
  }
2031
2139
  if (liveRecord?.kind === "stopped" && key.return) {
2032
- setNotice("Transcribe handoff is coming next.");
2140
+ void transcribeStoppedRecording();
2033
2141
  return;
2034
2142
  }
2035
2143
  if (input === "q" || key.escape || key.leftArrow || input === "n") void stopLiveRecord();
@@ -2042,6 +2150,16 @@ function AppShell({
2042
2150
  if (input === "3") return goTab("account");
2043
2151
  if (input === "n") {
2044
2152
  setStack((st) => [...st, { kind: "recordSetup" }]);
2153
+ if (fetchRecordSetup) {
2154
+ fetchRecordSetup().then((model) => {
2155
+ setRecordSetupInputs({
2156
+ sources: model.sources.length > 0 ? model.sources : DEFAULT_RECORDING_SOURCES,
2157
+ microphones: model.microphones ?? []
2158
+ });
2159
+ }).catch(() => {
2160
+ setRecordSetupInputs({ sources: DEFAULT_RECORDING_SOURCES, microphones: [] });
2161
+ });
2162
+ }
2045
2163
  return;
2046
2164
  }
2047
2165
  if (input === "r") return void refresh({ resetRecordings: true });
@@ -2126,7 +2244,15 @@ function AppShell({
2126
2244
  return /* @__PURE__ */ jsx18(LiveCaptionsScreen, { source: liveRecord.session.source, now });
2127
2245
  }
2128
2246
  if (liveRecord?.kind === "live" || liveRecord?.kind === "starting" || liveRecord?.kind === "stopping" || liveRecord?.kind === "stopped") {
2129
- return /* @__PURE__ */ jsx18(Detail, { notice, children: /* @__PURE__ */ jsx18(RecordingHeroScreen, { telemetry: liveRecord.telemetry, now }) });
2247
+ return /* @__PURE__ */ jsx18(Detail, { notice, children: /* @__PURE__ */ jsx18(
2248
+ RecordingHeroScreen,
2249
+ {
2250
+ telemetry: liveRecord.telemetry,
2251
+ artifact: liveRecord.kind === "stopped" ? liveRecord.artifact : void 0,
2252
+ canTranscribe: Boolean(transcribeRecordingArtifact),
2253
+ now
2254
+ }
2255
+ ) });
2130
2256
  }
2131
2257
  return /* @__PURE__ */ jsxs15(Box16, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
2132
2258
  /* @__PURE__ */ jsx18(Box16, { flexGrow: 1, flexDirection: "column", paddingX: 1, paddingTop: 1, children: liveRecord?.kind === "error" ? (() => {
@@ -2284,7 +2410,9 @@ async function runDashboard(deps) {
2284
2410
  fetchAccountStatus: deps.fetchAccountStatus,
2285
2411
  recordingAudio: deps.recordingAudio,
2286
2412
  listDownloadedRecordingIds: deps.listDownloadedRecordingIds,
2413
+ fetchRecordSetup: deps.fetchRecordSetup,
2287
2414
  startLiveRecord: deps.startLiveRecord,
2415
+ transcribeRecordingArtifact: deps.transcribeRecordingArtifact,
2288
2416
  initialView: deps.initialView ?? "overview",
2289
2417
  openUrl,
2290
2418
  copyText
@@ -16955,11 +17083,31 @@ var sidecarInfoSchema = external_exports.object({
16955
17083
  var sidecarRecordingOptionsSchema = external_exports.object({
16956
17084
  includeSystemAudio: external_exports.boolean().default(true),
16957
17085
  includeMicrophone: external_exports.boolean().default(true),
17086
+ targetBundleId: external_exports.string().optional(),
17087
+ microphoneDeviceId: external_exports.string().optional(),
16958
17088
  liveCaptions: external_exports.boolean().default(false),
16959
17089
  translationLanguage: external_exports.string().optional(),
16960
17090
  transcriptionLanguage: external_exports.string().optional(),
16961
17091
  title: external_exports.string().optional()
16962
17092
  });
17093
+ var sidecarRecordingSourceSchema = external_exports.object({
17094
+ id: external_exports.string(),
17095
+ kind: external_exports.enum(["system", "app"]),
17096
+ label: external_exports.string(),
17097
+ appName: external_exports.string().optional(),
17098
+ bundleId: external_exports.string().optional()
17099
+ });
17100
+ var sidecarRecordingSourcesListResultSchema = external_exports.object({
17101
+ sources: external_exports.array(sidecarRecordingSourceSchema)
17102
+ });
17103
+ var sidecarMicrophoneDeviceSchema = external_exports.object({
17104
+ id: external_exports.string(),
17105
+ label: external_exports.string(),
17106
+ isDefault: external_exports.boolean().optional()
17107
+ });
17108
+ var sidecarMicrophonesListResultSchema = external_exports.object({
17109
+ microphones: external_exports.array(sidecarMicrophoneDeviceSchema)
17110
+ });
16963
17111
  var sidecarPermissionNameSchema = external_exports.enum(["screen_recording", "microphone"]);
16964
17112
  var sidecarPermissionStatusSchema = external_exports.enum(["granted", "denied", "unknown"]);
16965
17113
  var sidecarPermissionItemSchema = external_exports.object({
@@ -17038,6 +17186,18 @@ var sidecarRequestSchema = external_exports.discriminatedUnion("method", [
17038
17186
  method: external_exports.literal("recappi.handshake"),
17039
17187
  params: sidecarHandshakeParamsSchema
17040
17188
  }),
17189
+ external_exports.object({
17190
+ jsonrpc: external_exports.literal("2.0"),
17191
+ id: sidecarJsonRpcIdSchema,
17192
+ method: external_exports.literal("recappi.recording.sources.list"),
17193
+ params: external_exports.object({}).optional()
17194
+ }),
17195
+ external_exports.object({
17196
+ jsonrpc: external_exports.literal("2.0"),
17197
+ id: sidecarJsonRpcIdSchema,
17198
+ method: external_exports.literal("recappi.recording.microphones.list"),
17199
+ params: external_exports.object({}).optional()
17200
+ }),
17041
17201
  external_exports.object({
17042
17202
  jsonrpc: external_exports.literal("2.0"),
17043
17203
  id: sidecarJsonRpcIdSchema,
@@ -19899,6 +20059,20 @@ var MiniSidecarClient = class {
19899
20059
  sidecarHandshakeResultSchema
19900
20060
  );
19901
20061
  }
20062
+ listRecordingSources() {
20063
+ return this.request(
20064
+ "recappi.recording.sources.list",
20065
+ {},
20066
+ sidecarRecordingSourcesListResultSchema
20067
+ );
20068
+ }
20069
+ listMicrophones() {
20070
+ return this.request(
20071
+ "recappi.recording.microphones.list",
20072
+ {},
20073
+ sidecarMicrophonesListResultSchema
20074
+ );
20075
+ }
19902
20076
  startRecording(params) {
19903
20077
  return this.request(
19904
20078
  "recappi.recording.start",
@@ -20119,6 +20293,8 @@ async function startLiveRecordSession(opts, selection = {
20119
20293
  ...opts,
20120
20294
  includeSystemAudio: capture.includeSystemAudio,
20121
20295
  includeMicrophone: capture.includeMicrophone,
20296
+ targetBundleId: capture.targetBundleId,
20297
+ microphoneDeviceId: capture.microphoneDeviceId,
20122
20298
  live: false
20123
20299
  });
20124
20300
  return {
@@ -20127,6 +20303,32 @@ async function startLiveRecordSession(opts, selection = {
20127
20303
  stop: session.stop
20128
20304
  };
20129
20305
  }
20306
+ async function listRecordInputs(opts) {
20307
+ const command = resolveSidecarCommand(opts);
20308
+ const sidecarArgs = opts.sidecarArgs ?? [];
20309
+ const spawnSidecar = opts.runtime?.spawnSidecar ?? spawnMiniSidecar;
20310
+ const sidecar = spawnSidecar({ command, args: sidecarArgs, env: opts.env });
20311
+ try {
20312
+ await sidecar.client.handshake(
20313
+ defaultSidecarHandshakeParams({
20314
+ client: { name: "recappi-cli", version: opts.cliVersion },
20315
+ capabilities: ["recording.capture"]
20316
+ })
20317
+ );
20318
+ const [sourceResult, microphoneResult] = await Promise.all([
20319
+ sidecar.client.listRecordingSources(),
20320
+ sidecar.client.listMicrophones()
20321
+ ]);
20322
+ const sources = normalizeSidecarSources(sourceResult.sources);
20323
+ const microphones = normalizeSidecarMicrophones(microphoneResult.microphones);
20324
+ return {
20325
+ sources: sources.length > 0 ? sources : DEFAULT_RECORDING_SOURCES,
20326
+ microphones
20327
+ };
20328
+ } finally {
20329
+ sidecar.kill();
20330
+ }
20331
+ }
20130
20332
  async function startRecordSession(opts) {
20131
20333
  const command = resolveSidecarCommand(opts);
20132
20334
  const sidecarArgs = opts.sidecarArgs ?? [];
@@ -20178,6 +20380,8 @@ async function startRecordSession(opts) {
20178
20380
  const recordingOptions = {
20179
20381
  includeSystemAudio: opts.includeSystemAudio ?? true,
20180
20382
  includeMicrophone: opts.includeMicrophone ?? true,
20383
+ ...opts.targetBundleId ? { targetBundleId: opts.targetBundleId } : {},
20384
+ ...opts.microphoneDeviceId ? { microphoneDeviceId: opts.microphoneDeviceId } : {},
20181
20385
  liveCaptions: opts.live === true,
20182
20386
  ...opts.translationLanguage ? { translationLanguage: opts.translationLanguage } : {},
20183
20387
  ...opts.transcriptionLanguage ? { transcriptionLanguage: opts.transcriptionLanguage } : {},
@@ -20229,6 +20433,31 @@ async function startRecordSession(opts) {
20229
20433
  throw error51;
20230
20434
  }
20231
20435
  }
20436
+ function normalizeSidecarSources(sources) {
20437
+ const seen = /* @__PURE__ */ new Set();
20438
+ const out = [];
20439
+ for (const source of sources) {
20440
+ const id = source.kind === "app" && source.bundleId ? `app:${source.bundleId}` : source.id || source.kind;
20441
+ if (!id || seen.has(id)) continue;
20442
+ seen.add(id);
20443
+ out.push({
20444
+ id,
20445
+ kind: source.kind,
20446
+ label: source.label,
20447
+ ...source.appName ? { appName: source.appName } : {},
20448
+ ...source.bundleId ? { bundleId: source.bundleId } : {},
20449
+ canIncludeMicrophone: true
20450
+ });
20451
+ }
20452
+ return out;
20453
+ }
20454
+ function normalizeSidecarMicrophones(microphones) {
20455
+ return microphones.filter((device) => device.id && device.label).map((device) => ({
20456
+ id: device.id,
20457
+ label: device.label,
20458
+ ...device.isDefault === true ? { isDefault: true } : {}
20459
+ }));
20460
+ }
20232
20461
  function assertRecordingPermissions(permissions) {
20233
20462
  const blocked = permissions.find((permission) => permission.status !== "granted");
20234
20463
  if (!blocked) return;
@@ -20449,7 +20678,12 @@ async function runCli(deps = {}) {
20449
20678
  recordingAudio,
20450
20679
  listDownloadedRecordingIds: () => recordingAudio.listDownloadedRecordingIds(),
20451
20680
  listDownloads: () => recordingAudio.listDownloads(),
20452
- startLiveRecord: async (selection) => {
20681
+ fetchRecordSetup: async () => listRecordInputs({
20682
+ cliVersion: CLI_VERSION,
20683
+ env: deps.env,
20684
+ runtime: deps.recordRuntime
20685
+ }),
20686
+ startLiveRecord: async (selection, sources) => {
20453
20687
  const liveStatus = await client.authStatus();
20454
20688
  if (!liveStatus.loggedIn || !liveStatus.userId) {
20455
20689
  throw cliError("auth.not_logged_in", "Sign in before starting a sidecar recording.", {
@@ -20468,9 +20702,32 @@ async function runCli(deps = {}) {
20468
20702
  homeDir: deps.homeDir,
20469
20703
  runtime: deps.recordRuntime
20470
20704
  },
20471
- selection
20705
+ selection,
20706
+ sources
20472
20707
  );
20473
20708
  },
20709
+ transcribeRecordingArtifact: async (artifact) => {
20710
+ if (!artifact.audioPath) {
20711
+ throw cliError("input.not_found", "No local audio file is available to transcribe.");
20712
+ }
20713
+ const data = await client.uploadPathBatch({
20714
+ inputPath: artifact.audioPath,
20715
+ transcribe: true,
20716
+ wait: false
20717
+ });
20718
+ if (data.failures.length > 0) {
20719
+ const failure = data.failures[0];
20720
+ throw cliError(failure.error.code, failure.error.message, {
20721
+ hint: failure.error.hint,
20722
+ retryable: failure.error.retryable
20723
+ });
20724
+ }
20725
+ const success2 = data.successes[0];
20726
+ if (!success2) {
20727
+ throw cliError("input.unsupported_audio", "No supported local audio file was uploaded.");
20728
+ }
20729
+ return success2;
20730
+ },
20474
20731
  initialView: parsed.initialView
20475
20732
  });
20476
20733
  return 0;