recappi 0.1.11 → 0.1.13

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
@@ -1293,33 +1293,124 @@ var init_TranscriptView = __esm({
1293
1293
  }
1294
1294
  });
1295
1295
 
1296
+ // src/tui/PermissionPreflightView.tsx
1297
+ import { Box as Box13, Text as Text13 } from "ink";
1298
+ import { jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
1299
+ function statusGlyph2(status) {
1300
+ switch (status) {
1301
+ case "granted":
1302
+ return { glyph: "\u2713", color: "green", label: "granted" };
1303
+ case "denied":
1304
+ return { glyph: "\u2717", color: "red", label: "not allowed" };
1305
+ case "unknown":
1306
+ return { glyph: "\u25CB", color: "yellow", label: "not requested yet" };
1307
+ }
1308
+ }
1309
+ function PermissionPreflightView({
1310
+ items
1311
+ }) {
1312
+ const allGranted = items.length > 0 && items.every((item) => item.status === "granted");
1313
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 1, children: [
1314
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "\u2039 Recording permissions" }),
1315
+ /* @__PURE__ */ jsx15(Box13, { marginTop: 1, flexDirection: "column", children: items.length === 0 ? /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Checking permissions\u2026" }) : items.map((item) => {
1316
+ const status = statusGlyph2(item.status);
1317
+ const hint = item.status === "granted" ? void 0 : item.hint ?? DEFAULT_HINTS[item.name];
1318
+ return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
1319
+ /* @__PURE__ */ jsxs12(Text13, { children: [
1320
+ /* @__PURE__ */ jsx15(Text13, { color: status.color, children: status.glyph }),
1321
+ /* @__PURE__ */ jsx15(Text13, { bold: true, children: ` ${item.name}` }),
1322
+ /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: ` ${status.label}` })
1323
+ ] }),
1324
+ hint ? /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: ` ${hint}` }) : null
1325
+ ] }, item.name);
1326
+ }) }),
1327
+ /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: allGranted ? /* @__PURE__ */ jsx15(Text13, { color: "green", children: "All set \u2014 ready to record." }) : /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Grant the permissions above, then press r to recheck." }) }),
1328
+ /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "r recheck \xB7 o open System Settings \xB7 esc back" }) })
1329
+ ] });
1330
+ }
1331
+ var DEFAULT_HINTS;
1332
+ var init_PermissionPreflightView = __esm({
1333
+ "src/tui/PermissionPreflightView.tsx"() {
1334
+ "use strict";
1335
+ DEFAULT_HINTS = {
1336
+ "Screen Recording": "Open System Settings \u203A Privacy & Security \u203A Screen Recording, enable recappi, then recheck.",
1337
+ Microphone: "Open System Settings \u203A Privacy & Security \u203A Microphone, enable recappi, then recheck."
1338
+ };
1339
+ }
1340
+ });
1341
+
1296
1342
  // src/tui/AppShell.tsx
1297
1343
  import { useCallback, useEffect as useEffect2, useState as useState5 } from "react";
1298
- import { Box as Box13, Text as Text13, useApp, useInput as useInput5 } from "ink";
1299
- import { Fragment as Fragment4, jsx as jsx15, jsxs as jsxs12 } from "react/jsx-runtime";
1344
+ import { Box as Box14, Text as Text14, useApp, useInput as useInput5 } from "ink";
1345
+ import { Fragment as Fragment4, jsx as jsx16, jsxs as jsxs13 } from "react/jsx-runtime";
1300
1346
  function recordErrorCopy(code, message) {
1301
1347
  switch (code) {
1302
1348
  case "record.helper_unavailable":
1303
1349
  return {
1304
- title: "Live recording isn't available on this machine yet.",
1305
- detail: "The recorder helper isn't installed for your platform.",
1350
+ title: "This CLI install is missing its local recorder.",
1351
+ detail: "Run npm install -g recappi@latest, or use npx -y recappi@latest.",
1306
1352
  tone: "yellow"
1307
1353
  };
1308
1354
  case "record.unsupported_platform":
1309
1355
  return {
1310
- title: "Live recording isn't supported on this platform yet.",
1356
+ title: "CLI recording isn't supported on this platform yet.",
1357
+ detail: "Use Recappi Mini on macOS to record for now.",
1311
1358
  tone: "yellow"
1312
1359
  };
1313
1360
  case "record.capture_unavailable":
1314
1361
  return {
1315
- title: "Live recording isn't ready yet.",
1316
- detail: "The on-device recorder is still being finished \u2014 coming soon.",
1362
+ title: "CLI recording isn't ready yet.",
1363
+ detail: "Use the Recappi Mini app to record for now; CLI recording is coming soon.",
1364
+ tone: "yellow"
1365
+ };
1366
+ case "record.permission_required":
1367
+ return {
1368
+ title: "Recording needs macOS permission first.",
1369
+ detail: "Open System Settings > Privacy & Security, allow recording access, then retry.",
1317
1370
  tone: "yellow"
1318
1371
  };
1372
+ case "record.capture_failed":
1373
+ return {
1374
+ title: "Recording couldn't capture audio.",
1375
+ detail: "Check permissions, make sure audio is playing, then try again.",
1376
+ tone: "red"
1377
+ };
1319
1378
  default:
1320
1379
  return { title: "Couldn't start recording.", detail: message, tone: "red" };
1321
1380
  }
1322
1381
  }
1382
+ function recordErrorState(error51) {
1383
+ if (error51 instanceof Error) {
1384
+ const descriptor = isRecord6(error51) && isRecord6(error51.descriptor) ? error51.descriptor : void 0;
1385
+ return {
1386
+ kind: "error",
1387
+ message: error51.message,
1388
+ code: typeof descriptor?.code === "string" ? descriptor.code : "code" in error51 && typeof error51.code === "string" ? error51.code : void 0,
1389
+ data: isRecord6(error51) ? error51.data : void 0
1390
+ };
1391
+ }
1392
+ return { kind: "error", message: String(error51) };
1393
+ }
1394
+ function permissionItemsFromRecordError(data) {
1395
+ const sidecarError = isRecord6(data) ? data : void 0;
1396
+ const sidecarData = isRecord6(sidecarError?.data) ? sidecarError.data : void 0;
1397
+ const permission = typeof sidecarData?.permission === "string" ? sidecarData.permission : "";
1398
+ const hint = typeof sidecarData?.recovery === "string" ? sidecarData.recovery : void 0;
1399
+ const item = permission === "microphone" ? "Microphone" : permission === "screen_recording" ? "Screen Recording" : "Recording";
1400
+ return [{ name: item, status: "denied", ...hint ? { hint } : {} }];
1401
+ }
1402
+ function settingsUrlFromRecordError(data) {
1403
+ const sidecarError = isRecord6(data) ? data : void 0;
1404
+ const sidecarData = isRecord6(sidecarError?.data) ? sidecarError.data : void 0;
1405
+ const permission = typeof sidecarData?.permission === "string" ? sidecarData.permission : "";
1406
+ if (permission === "microphone") {
1407
+ return "x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone";
1408
+ }
1409
+ return "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture";
1410
+ }
1411
+ function isRecord6(value) {
1412
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1413
+ }
1323
1414
  function AppShell({
1324
1415
  fetchJobs,
1325
1416
  fetchTranscript,
@@ -1358,6 +1449,7 @@ function AppShell({
1358
1449
  const [audioCache, setAudioCache] = useState5(() => /* @__PURE__ */ new Map());
1359
1450
  const [downloadedIds, setDownloadedIds] = useState5(() => /* @__PURE__ */ new Set());
1360
1451
  const [liveRecord, setLiveRecord] = useState5(void 0);
1452
+ const [recordRetryNonce, setRecordRetryNonce] = useState5(0);
1361
1453
  const refreshDownloadedIds = useCallback(async () => {
1362
1454
  if (!listDownloadedRecordingIds) return;
1363
1455
  try {
@@ -1402,18 +1494,13 @@ function AppShell({
1402
1494
  setLiveRecord({ kind: "live", session });
1403
1495
  }).catch((error51) => {
1404
1496
  if (!cancelled) {
1405
- const code = error51 && typeof error51 === "object" && "code" in error51 ? String(error51.code) : void 0;
1406
- setLiveRecord({
1407
- kind: "error",
1408
- code,
1409
- message: error51 instanceof Error ? error51.message : String(error51)
1410
- });
1497
+ setLiveRecord(recordErrorState(error51));
1411
1498
  }
1412
1499
  });
1413
1500
  return () => {
1414
1501
  cancelled = true;
1415
1502
  };
1416
- }, [screen.kind, startLiveRecord]);
1503
+ }, [screen.kind, startLiveRecord, recordRetryNonce]);
1417
1504
  const selectedRecording = screen.kind === "overview" ? recordings[selected] : void 0;
1418
1505
  const peekTranscriptId = selectedRecording?.activeTranscriptId ?? void 0;
1419
1506
  useEffect2(() => {
@@ -1591,6 +1678,14 @@ function AppShell({
1591
1678
  useInput5((input, key) => {
1592
1679
  setNotice(void 0);
1593
1680
  if (screen.kind === "record") {
1681
+ if (liveRecord?.kind === "error" && input === "r") {
1682
+ setRecordRetryNonce((value) => value + 1);
1683
+ return;
1684
+ }
1685
+ if (liveRecord?.kind === "error" && input === "o") {
1686
+ openUrl2?.(settingsUrlFromRecordError(liveRecord.data));
1687
+ return;
1688
+ }
1594
1689
  if (input === "q" || key.escape || key.leftArrow) void stopLiveRecord();
1595
1690
  return;
1596
1691
  }
@@ -1646,18 +1741,18 @@ function AppShell({
1646
1741
  }
1647
1742
  });
1648
1743
  if (screen.kind === "transcript") {
1649
- return /* @__PURE__ */ jsx15(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
1744
+ return /* @__PURE__ */ jsx16(TranscriptView, { loading: screen.loading, data: screen.data, error: screen.error });
1650
1745
  }
1651
1746
  if (screen.kind === "jobDetail") {
1652
1747
  const job = jobs.find((j) => j.jobId === screen.jobId);
1653
- if (!job) return /* @__PURE__ */ jsx15(Missing, { label: "Job" });
1654
- return /* @__PURE__ */ jsx15(Detail, { notice, children: /* @__PURE__ */ jsx15(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
1748
+ if (!job) return /* @__PURE__ */ jsx16(Missing, { label: "Job" });
1749
+ return /* @__PURE__ */ jsx16(Detail, { notice, children: /* @__PURE__ */ jsx16(JobDetailView, { item: job, origin, spinnerFrame, nowMs: now() }) });
1655
1750
  }
1656
1751
  if (screen.kind === "recordingDetail") {
1657
1752
  const rec = recordings.find((r) => r.recordingId === screen.recordingId);
1658
- if (!rec) return /* @__PURE__ */ jsx15(Missing, { label: "Recording" });
1753
+ if (!rec) return /* @__PURE__ */ jsx16(Missing, { label: "Recording" });
1659
1754
  const detailTranscript = rec.activeTranscriptId ? transcriptCache.get(rec.activeTranscriptId) : void 0;
1660
- return /* @__PURE__ */ jsx15(Detail, { notice, children: /* @__PURE__ */ jsx15(
1755
+ return /* @__PURE__ */ jsx16(Detail, { notice, children: /* @__PURE__ */ jsx16(
1661
1756
  RecordingDetailView,
1662
1757
  {
1663
1758
  item: rec,
@@ -1669,18 +1764,21 @@ function AppShell({
1669
1764
  }
1670
1765
  if (screen.kind === "record") {
1671
1766
  if (liveRecord?.kind === "live") {
1672
- return /* @__PURE__ */ jsx15(LiveCaptionsScreen, { source: liveRecord.session.source, now });
1767
+ return /* @__PURE__ */ jsx16(LiveCaptionsScreen, { source: liveRecord.session.source, now });
1673
1768
  }
1674
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
1675
- /* @__PURE__ */ jsx15(Header, { active: "record" }),
1676
- /* @__PURE__ */ jsx15(Box13, { flexGrow: 1, flexDirection: "column", paddingX: 1, paddingTop: 1, children: liveRecord?.kind === "error" ? (() => {
1769
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
1770
+ /* @__PURE__ */ jsx16(Header, { active: "record" }),
1771
+ /* @__PURE__ */ jsx16(Box14, { flexGrow: 1, flexDirection: "column", paddingX: 1, paddingTop: 1, children: liveRecord?.kind === "error" ? (() => {
1772
+ if (liveRecord.code === "record.permission_required") {
1773
+ return /* @__PURE__ */ jsx16(PermissionPreflightView, { items: permissionItemsFromRecordError(liveRecord.data) });
1774
+ }
1677
1775
  const copy = recordErrorCopy(liveRecord.code, liveRecord.message);
1678
- return /* @__PURE__ */ jsxs12(Fragment4, { children: [
1679
- /* @__PURE__ */ jsx15(Text13, { color: copy.tone, children: copy.title }),
1680
- copy.detail ? /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: copy.detail }) : null
1776
+ return /* @__PURE__ */ jsxs13(Fragment4, { children: [
1777
+ /* @__PURE__ */ jsx16(Text14, { color: copy.tone, children: copy.title }),
1778
+ copy.detail ? /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: copy.detail }) : null
1681
1779
  ] });
1682
- })() : /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Starting live recording\u2026" }) }),
1683
- /* @__PURE__ */ jsx15(Footer, { keys: "q / esc / \u2190 back" })
1780
+ })() : /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "Starting live recording\u2026" }) }),
1781
+ /* @__PURE__ */ jsx16(Footer, { keys: "q / esc / \u2190 back" })
1684
1782
  ] });
1685
1783
  }
1686
1784
  const tab = screen.kind === "jobs" ? "jobs" : screen.kind === "account" ? "account" : "overview";
@@ -1698,7 +1796,7 @@ function AppShell({
1698
1796
  const showPeek = size.columns >= 100;
1699
1797
  const peekWidth = showPeek ? 34 : 0;
1700
1798
  const listColumns = showPeek ? Math.max(30, size.columns - peekWidth - 3) : size.columns;
1701
- body = /* @__PURE__ */ jsx15(
1799
+ body = /* @__PURE__ */ jsx16(
1702
1800
  OverviewView,
1703
1801
  {
1704
1802
  recordings: recordings.slice(win.start, win.end),
@@ -1718,11 +1816,11 @@ function AppShell({
1718
1816
  );
1719
1817
  } else if (screen.kind === "account") {
1720
1818
  position = "";
1721
- body = /* @__PURE__ */ jsx15(AccountView, { status: accountStatus });
1819
+ body = /* @__PURE__ */ jsx16(AccountView, { status: accountStatus });
1722
1820
  } else {
1723
1821
  const win = listWindow(selected, jobs.length, Math.max(3, size.rows - 4));
1724
1822
  position = jobs.length ? `${selected + 1} / ${jobs.length}` : "0";
1725
- body = /* @__PURE__ */ jsx15(
1823
+ body = /* @__PURE__ */ jsx16(
1726
1824
  JobsView,
1727
1825
  {
1728
1826
  items: jobs.slice(win.start, win.end),
@@ -1732,34 +1830,34 @@ function AppShell({
1732
1830
  );
1733
1831
  }
1734
1832
  const footerKeys = screen.kind === "jobs" ? `${position} \xB7 \u2191\u2193 select \xB7 \u23CE job \xB7 t transcript \xB7 1 overview \xB7 3 account \xB7 4 record \xB7 r refresh \xB7 q quit` : screen.kind === "account" ? "3 account \xB7 1 overview \xB7 2 jobs \xB7 4 record \xB7 r refresh \xB7 q quit" : `${position} \xB7 \u2191\u2193 scroll \xB7 \u23CE open \xB7 t transcript \xB7 2 jobs \xB7 3 account \xB7 4 record \xB7 r refresh \xB7 q quit`;
1735
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
1736
- /* @__PURE__ */ jsx15(Header, { active: tab }),
1737
- /* @__PURE__ */ jsxs12(Box13, { flexGrow: 1, flexDirection: "column", children: [
1833
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
1834
+ /* @__PURE__ */ jsx16(Header, { active: tab }),
1835
+ /* @__PURE__ */ jsxs13(Box14, { flexGrow: 1, flexDirection: "column", children: [
1738
1836
  body,
1739
- loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx15(Box13, { marginTop: 1, children: /* @__PURE__ */ jsxs12(Text13, { color: "red", children: [
1837
+ loadError && jobs.length === 0 && recordings.length === 0 ? /* @__PURE__ */ jsx16(Box14, { marginTop: 1, children: /* @__PURE__ */ jsxs13(Text14, { color: "red", children: [
1740
1838
  "! ",
1741
1839
  loadError
1742
1840
  ] }) }) : null
1743
1841
  ] }),
1744
- /* @__PURE__ */ jsx15(Footer, { keys: footerKeys })
1842
+ /* @__PURE__ */ jsx16(Footer, { keys: footerKeys })
1745
1843
  ] });
1746
1844
  }
1747
1845
  function Detail({
1748
1846
  notice,
1749
1847
  children
1750
1848
  }) {
1751
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
1849
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
1752
1850
  children,
1753
- notice ? /* @__PURE__ */ jsx15(Box13, { paddingX: 1, children: /* @__PURE__ */ jsx15(Text13, { color: "green", children: notice }) }) : null
1851
+ notice ? /* @__PURE__ */ jsx16(Box14, { paddingX: 1, children: /* @__PURE__ */ jsx16(Text14, { color: "green", children: notice }) }) : null
1754
1852
  ] });
1755
1853
  }
1756
1854
  function Missing({ label }) {
1757
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 1, children: [
1758
- /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
1855
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, children: [
1856
+ /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
1759
1857
  label,
1760
1858
  " no longer in the list."
1761
1859
  ] }),
1762
- /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "esc back \xB7 q quit" })
1860
+ /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "esc back \xB7 q quit" })
1763
1861
  ] });
1764
1862
  }
1765
1863
  var RECORDINGS_PAGE_SIZE, RECORDINGS_PREFETCH_REMAINING;
@@ -1774,6 +1872,7 @@ var init_AppShell = __esm({
1774
1872
  init_RecordingDetailView();
1775
1873
  init_TranscriptView();
1776
1874
  init_LiveCaptionsScreen();
1875
+ init_PermissionPreflightView();
1777
1876
  init_format();
1778
1877
  init_terminal();
1779
1878
  RECORDINGS_PAGE_SIZE = 50;
@@ -16399,6 +16498,8 @@ var cliErrorCodeSchema = external_exports.enum([
16399
16498
  "record.helper_unavailable",
16400
16499
  "record.unsupported_platform",
16401
16500
  "record.capture_unavailable",
16501
+ "record.permission_required",
16502
+ "record.capture_failed",
16402
16503
  "cloud.conflict.upload_in_progress",
16403
16504
  "cloud.recording_not_ready",
16404
16505
  "cloud.job_failed",
@@ -16495,6 +16596,19 @@ var sidecarRecordingOptionsSchema = external_exports.object({
16495
16596
  transcriptionLanguage: external_exports.string().optional(),
16496
16597
  title: external_exports.string().optional()
16497
16598
  });
16599
+ var sidecarPermissionNameSchema = external_exports.enum(["screen_recording", "microphone"]);
16600
+ var sidecarPermissionStatusSchema = external_exports.enum(["granted", "denied", "unknown"]);
16601
+ var sidecarPermissionItemSchema = external_exports.object({
16602
+ name: sidecarPermissionNameSchema,
16603
+ status: sidecarPermissionStatusSchema,
16604
+ hint: external_exports.string().optional()
16605
+ });
16606
+ var sidecarPermissionStatusParamsSchema = external_exports.object({
16607
+ options: sidecarRecordingOptionsSchema
16608
+ });
16609
+ var sidecarPermissionStatusResultSchema = external_exports.object({
16610
+ permissions: external_exports.array(sidecarPermissionItemSchema)
16611
+ });
16498
16612
  var sidecarRecordingStateSchema = external_exports.enum([
16499
16613
  "idle",
16500
16614
  "starting",
@@ -16566,6 +16680,12 @@ var sidecarRequestSchema = external_exports.discriminatedUnion("method", [
16566
16680
  method: external_exports.literal("recappi.recording.start"),
16567
16681
  params: sidecarRecordingStartParamsSchema
16568
16682
  }),
16683
+ external_exports.object({
16684
+ jsonrpc: external_exports.literal("2.0"),
16685
+ id: sidecarJsonRpcIdSchema,
16686
+ method: external_exports.literal("recappi.permissions.status"),
16687
+ params: sidecarPermissionStatusParamsSchema
16688
+ }),
16569
16689
  external_exports.object({
16570
16690
  jsonrpc: external_exports.literal("2.0"),
16571
16691
  id: sidecarJsonRpcIdSchema,
@@ -16893,6 +17013,8 @@ var DEFAULT_EXIT_CODES = {
16893
17013
  "record.helper_unavailable": 2,
16894
17014
  "record.unsupported_platform": 2,
16895
17015
  "record.capture_unavailable": 2,
17016
+ "record.permission_required": 2,
17017
+ "record.capture_failed": 1,
16896
17018
  "cloud.conflict.upload_in_progress": 5,
16897
17019
  "cloud.recording_not_ready": 5,
16898
17020
  "cloud.job_failed": 5,
@@ -19419,6 +19541,13 @@ var MiniSidecarClient = class {
19419
19541
  sidecarRecordingStartResultSchema
19420
19542
  );
19421
19543
  }
19544
+ getPermissionStatus(params) {
19545
+ return this.request(
19546
+ "recappi.permissions.status",
19547
+ sidecarPermissionStatusParamsSchema.parse(params),
19548
+ sidecarPermissionStatusResultSchema
19549
+ );
19550
+ }
19422
19551
  stopRecording(params) {
19423
19552
  return this.request(
19424
19553
  "recappi.recording.stop",
@@ -19519,12 +19648,7 @@ var MiniSidecarClient = class {
19519
19648
  this.pending.delete(id);
19520
19649
  clearTimeout(pending.timer);
19521
19650
  if ("error" in response.data) {
19522
- pending.reject(
19523
- cliError("internal.unexpected", response.data.error.message, {
19524
- data: response.data.error,
19525
- retryable: response.data.error.code >= -32099 && response.data.error.code <= -32e3
19526
- })
19527
- );
19651
+ pending.reject(sidecarErrorToCliError(response.data.error));
19528
19652
  return;
19529
19653
  }
19530
19654
  pending.resolve(response.data.result);
@@ -19537,6 +19661,27 @@ var MiniSidecarClient = class {
19537
19661
  }
19538
19662
  }
19539
19663
  };
19664
+ function sidecarErrorToCliError(error51) {
19665
+ const data = isRecord5(error51.data) ? error51.data : void 0;
19666
+ const maybeCode = typeof data?.cliCode === "string" ? cliErrorCodeSchema.safeParse(data.cliCode) : void 0;
19667
+ const hint = typeof data?.recovery === "string" ? data.recovery : void 0;
19668
+ const retryable = typeof data?.retryable === "boolean" ? data.retryable : error51.code >= -32099 && error51.code <= -32e3;
19669
+ if (maybeCode?.success) {
19670
+ return cliError(maybeCode.data, error51.message, {
19671
+ data: error51,
19672
+ hint,
19673
+ retryable
19674
+ });
19675
+ }
19676
+ return cliError("internal.unexpected", error51.message, {
19677
+ data: error51,
19678
+ hint,
19679
+ retryable
19680
+ });
19681
+ }
19682
+ function isRecord5(value) {
19683
+ return typeof value === "object" && value !== null && !Array.isArray(value);
19684
+ }
19540
19685
  function spawnMiniSidecar(opts) {
19541
19686
  const spawnProcess = opts.spawnProcess ?? spawn2;
19542
19687
  const child = spawnProcess(opts.command, opts.args ?? [], {
@@ -19601,7 +19746,7 @@ async function recordViaSidecar(opts) {
19601
19746
  }
19602
19747
  }
19603
19748
  async function startLiveRecordSession(opts) {
19604
- const session = await startRecordSession({ ...opts, live: true });
19749
+ const session = await startRecordSession({ ...opts, live: false });
19605
19750
  return {
19606
19751
  source: session.source,
19607
19752
  stop: async () => {
@@ -19657,16 +19802,19 @@ async function startRecordSession(opts) {
19657
19802
  })
19658
19803
  );
19659
19804
  assertSidecarCapabilities(handshake, opts);
19805
+ const recordingOptions = {
19806
+ includeSystemAudio: opts.includeSystemAudio ?? true,
19807
+ includeMicrophone: opts.includeMicrophone ?? true,
19808
+ liveCaptions: opts.live === true,
19809
+ ...opts.translationLanguage ? { translationLanguage: opts.translationLanguage } : {},
19810
+ ...opts.transcriptionLanguage ? { transcriptionLanguage: opts.transcriptionLanguage } : {},
19811
+ ...opts.title ? { title: opts.title } : {}
19812
+ };
19813
+ const preflight = await sidecar.client.getPermissionStatus({ options: recordingOptions });
19814
+ assertRecordingPermissions(preflight.permissions);
19660
19815
  const started = await sidecar.client.startRecording({
19661
19816
  account,
19662
- options: {
19663
- includeSystemAudio: opts.includeSystemAudio ?? true,
19664
- includeMicrophone: opts.includeMicrophone ?? true,
19665
- liveCaptions: opts.live === true,
19666
- ...opts.translationLanguage ? { translationLanguage: opts.translationLanguage } : {},
19667
- ...opts.transcriptionLanguage ? { transcriptionLanguage: opts.transcriptionLanguage } : {},
19668
- ...opts.title ? { title: opts.title } : {}
19669
- }
19817
+ options: recordingOptions
19670
19818
  });
19671
19819
  sessionId = started.sessionId;
19672
19820
  latestState = started.state;
@@ -19708,6 +19856,24 @@ async function startRecordSession(opts) {
19708
19856
  throw error51;
19709
19857
  }
19710
19858
  }
19859
+ function assertRecordingPermissions(permissions) {
19860
+ const blocked = permissions.find((permission) => permission.status !== "granted");
19861
+ if (!blocked) return;
19862
+ const label = blocked.name === "microphone" ? "Microphone" : blocked.name === "screen_recording" ? "Screen Recording" : "Recording";
19863
+ throw cliError("record.permission_required", `${label} permission is required before recording.`, {
19864
+ hint: blocked.hint,
19865
+ data: {
19866
+ code: -32020,
19867
+ message: `${label} permission is required before recording.`,
19868
+ data: {
19869
+ cliCode: "record.permission_required",
19870
+ permission: blocked.name,
19871
+ ...blocked.hint ? { recovery: blocked.hint } : {},
19872
+ permissions
19873
+ }
19874
+ }
19875
+ });
19876
+ }
19711
19877
  function assertSidecarCapabilities(handshake, opts) {
19712
19878
  const capabilities = new Set(handshake.capabilities);
19713
19879
  const missing = [];