recappi 0.1.12 → 0.1.14

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,10 +1293,56 @@ 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":
@@ -1317,10 +1363,54 @@ function recordErrorCopy(code, message) {
1317
1363
  detail: "Use the Recappi Mini app to record for now; CLI recording is coming soon.",
1318
1364
  tone: "yellow"
1319
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.",
1370
+ tone: "yellow"
1371
+ };
1372
+ case "record.capture_failed":
1373
+ return {
1374
+ title: "Couldn't start recording.",
1375
+ detail: "Recording failed to start \u2014 please try again.",
1376
+ tone: "red"
1377
+ };
1320
1378
  default:
1321
1379
  return { title: "Couldn't start recording.", detail: message, tone: "red" };
1322
1380
  }
1323
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
+ }
1324
1414
  function AppShell({
1325
1415
  fetchJobs,
1326
1416
  fetchTranscript,
@@ -1359,6 +1449,7 @@ function AppShell({
1359
1449
  const [audioCache, setAudioCache] = useState5(() => /* @__PURE__ */ new Map());
1360
1450
  const [downloadedIds, setDownloadedIds] = useState5(() => /* @__PURE__ */ new Set());
1361
1451
  const [liveRecord, setLiveRecord] = useState5(void 0);
1452
+ const [recordRetryNonce, setRecordRetryNonce] = useState5(0);
1362
1453
  const refreshDownloadedIds = useCallback(async () => {
1363
1454
  if (!listDownloadedRecordingIds) return;
1364
1455
  try {
@@ -1403,18 +1494,13 @@ function AppShell({
1403
1494
  setLiveRecord({ kind: "live", session });
1404
1495
  }).catch((error51) => {
1405
1496
  if (!cancelled) {
1406
- const code = error51 && typeof error51 === "object" && "code" in error51 ? String(error51.code) : void 0;
1407
- setLiveRecord({
1408
- kind: "error",
1409
- code,
1410
- message: error51 instanceof Error ? error51.message : String(error51)
1411
- });
1497
+ setLiveRecord(recordErrorState(error51));
1412
1498
  }
1413
1499
  });
1414
1500
  return () => {
1415
1501
  cancelled = true;
1416
1502
  };
1417
- }, [screen.kind, startLiveRecord]);
1503
+ }, [screen.kind, startLiveRecord, recordRetryNonce]);
1418
1504
  const selectedRecording = screen.kind === "overview" ? recordings[selected] : void 0;
1419
1505
  const peekTranscriptId = selectedRecording?.activeTranscriptId ?? void 0;
1420
1506
  useEffect2(() => {
@@ -1592,6 +1678,14 @@ function AppShell({
1592
1678
  useInput5((input, key) => {
1593
1679
  setNotice(void 0);
1594
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
+ }
1595
1689
  if (input === "q" || key.escape || key.leftArrow) void stopLiveRecord();
1596
1690
  return;
1597
1691
  }
@@ -1647,18 +1741,18 @@ function AppShell({
1647
1741
  }
1648
1742
  });
1649
1743
  if (screen.kind === "transcript") {
1650
- 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 });
1651
1745
  }
1652
1746
  if (screen.kind === "jobDetail") {
1653
1747
  const job = jobs.find((j) => j.jobId === screen.jobId);
1654
- if (!job) return /* @__PURE__ */ jsx15(Missing, { label: "Job" });
1655
- 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() }) });
1656
1750
  }
1657
1751
  if (screen.kind === "recordingDetail") {
1658
1752
  const rec = recordings.find((r) => r.recordingId === screen.recordingId);
1659
- if (!rec) return /* @__PURE__ */ jsx15(Missing, { label: "Recording" });
1753
+ if (!rec) return /* @__PURE__ */ jsx16(Missing, { label: "Recording" });
1660
1754
  const detailTranscript = rec.activeTranscriptId ? transcriptCache.get(rec.activeTranscriptId) : void 0;
1661
- return /* @__PURE__ */ jsx15(Detail, { notice, children: /* @__PURE__ */ jsx15(
1755
+ return /* @__PURE__ */ jsx16(Detail, { notice, children: /* @__PURE__ */ jsx16(
1662
1756
  RecordingDetailView,
1663
1757
  {
1664
1758
  item: rec,
@@ -1670,18 +1764,21 @@ function AppShell({
1670
1764
  }
1671
1765
  if (screen.kind === "record") {
1672
1766
  if (liveRecord?.kind === "live") {
1673
- return /* @__PURE__ */ jsx15(LiveCaptionsScreen, { source: liveRecord.session.source, now });
1767
+ return /* @__PURE__ */ jsx16(LiveCaptionsScreen, { source: liveRecord.session.source, now });
1674
1768
  }
1675
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
1676
- /* @__PURE__ */ jsx15(Header, { active: "record" }),
1677
- /* @__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
+ }
1678
1775
  const copy = recordErrorCopy(liveRecord.code, liveRecord.message);
1679
- return /* @__PURE__ */ jsxs12(Fragment4, { children: [
1680
- /* @__PURE__ */ jsx15(Text13, { color: copy.tone, children: copy.title }),
1681
- 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
1682
1779
  ] });
1683
- })() : /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "Starting live recording\u2026" }) }),
1684
- /* @__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" })
1685
1782
  ] });
1686
1783
  }
1687
1784
  const tab = screen.kind === "jobs" ? "jobs" : screen.kind === "account" ? "account" : "overview";
@@ -1699,7 +1796,7 @@ function AppShell({
1699
1796
  const showPeek = size.columns >= 100;
1700
1797
  const peekWidth = showPeek ? 34 : 0;
1701
1798
  const listColumns = showPeek ? Math.max(30, size.columns - peekWidth - 3) : size.columns;
1702
- body = /* @__PURE__ */ jsx15(
1799
+ body = /* @__PURE__ */ jsx16(
1703
1800
  OverviewView,
1704
1801
  {
1705
1802
  recordings: recordings.slice(win.start, win.end),
@@ -1719,11 +1816,11 @@ function AppShell({
1719
1816
  );
1720
1817
  } else if (screen.kind === "account") {
1721
1818
  position = "";
1722
- body = /* @__PURE__ */ jsx15(AccountView, { status: accountStatus });
1819
+ body = /* @__PURE__ */ jsx16(AccountView, { status: accountStatus });
1723
1820
  } else {
1724
1821
  const win = listWindow(selected, jobs.length, Math.max(3, size.rows - 4));
1725
1822
  position = jobs.length ? `${selected + 1} / ${jobs.length}` : "0";
1726
- body = /* @__PURE__ */ jsx15(
1823
+ body = /* @__PURE__ */ jsx16(
1727
1824
  JobsView,
1728
1825
  {
1729
1826
  items: jobs.slice(win.start, win.end),
@@ -1733,34 +1830,34 @@ function AppShell({
1733
1830
  );
1734
1831
  }
1735
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`;
1736
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", height: size.rows, paddingX: 1, children: [
1737
- /* @__PURE__ */ jsx15(Header, { active: tab }),
1738
- /* @__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: [
1739
1836
  body,
1740
- 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: [
1741
1838
  "! ",
1742
1839
  loadError
1743
1840
  ] }) }) : null
1744
1841
  ] }),
1745
- /* @__PURE__ */ jsx15(Footer, { keys: footerKeys })
1842
+ /* @__PURE__ */ jsx16(Footer, { keys: footerKeys })
1746
1843
  ] });
1747
1844
  }
1748
1845
  function Detail({
1749
1846
  notice,
1750
1847
  children
1751
1848
  }) {
1752
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", children: [
1849
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", children: [
1753
1850
  children,
1754
- 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
1755
1852
  ] });
1756
1853
  }
1757
1854
  function Missing({ label }) {
1758
- return /* @__PURE__ */ jsxs12(Box13, { flexDirection: "column", paddingX: 1, children: [
1759
- /* @__PURE__ */ jsxs12(Text13, { dimColor: true, children: [
1855
+ return /* @__PURE__ */ jsxs13(Box14, { flexDirection: "column", paddingX: 1, children: [
1856
+ /* @__PURE__ */ jsxs13(Text14, { dimColor: true, children: [
1760
1857
  label,
1761
1858
  " no longer in the list."
1762
1859
  ] }),
1763
- /* @__PURE__ */ jsx15(Text13, { dimColor: true, children: "esc back \xB7 q quit" })
1860
+ /* @__PURE__ */ jsx16(Text14, { dimColor: true, children: "esc back \xB7 q quit" })
1764
1861
  ] });
1765
1862
  }
1766
1863
  var RECORDINGS_PAGE_SIZE, RECORDINGS_PREFETCH_REMAINING;
@@ -1775,6 +1872,7 @@ var init_AppShell = __esm({
1775
1872
  init_RecordingDetailView();
1776
1873
  init_TranscriptView();
1777
1874
  init_LiveCaptionsScreen();
1875
+ init_PermissionPreflightView();
1778
1876
  init_format();
1779
1877
  init_terminal();
1780
1878
  RECORDINGS_PAGE_SIZE = 50;
@@ -16400,6 +16498,8 @@ var cliErrorCodeSchema = external_exports.enum([
16400
16498
  "record.helper_unavailable",
16401
16499
  "record.unsupported_platform",
16402
16500
  "record.capture_unavailable",
16501
+ "record.permission_required",
16502
+ "record.capture_failed",
16403
16503
  "cloud.conflict.upload_in_progress",
16404
16504
  "cloud.recording_not_ready",
16405
16505
  "cloud.job_failed",
@@ -16496,6 +16596,19 @@ var sidecarRecordingOptionsSchema = external_exports.object({
16496
16596
  transcriptionLanguage: external_exports.string().optional(),
16497
16597
  title: external_exports.string().optional()
16498
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
+ });
16499
16612
  var sidecarRecordingStateSchema = external_exports.enum([
16500
16613
  "idle",
16501
16614
  "starting",
@@ -16567,6 +16680,12 @@ var sidecarRequestSchema = external_exports.discriminatedUnion("method", [
16567
16680
  method: external_exports.literal("recappi.recording.start"),
16568
16681
  params: sidecarRecordingStartParamsSchema
16569
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
+ }),
16570
16689
  external_exports.object({
16571
16690
  jsonrpc: external_exports.literal("2.0"),
16572
16691
  id: sidecarJsonRpcIdSchema,
@@ -16894,6 +17013,8 @@ var DEFAULT_EXIT_CODES = {
16894
17013
  "record.helper_unavailable": 2,
16895
17014
  "record.unsupported_platform": 2,
16896
17015
  "record.capture_unavailable": 2,
17016
+ "record.permission_required": 2,
17017
+ "record.capture_failed": 1,
16897
17018
  "cloud.conflict.upload_in_progress": 5,
16898
17019
  "cloud.recording_not_ready": 5,
16899
17020
  "cloud.job_failed": 5,
@@ -19420,6 +19541,13 @@ var MiniSidecarClient = class {
19420
19541
  sidecarRecordingStartResultSchema
19421
19542
  );
19422
19543
  }
19544
+ getPermissionStatus(params) {
19545
+ return this.request(
19546
+ "recappi.permissions.status",
19547
+ sidecarPermissionStatusParamsSchema.parse(params),
19548
+ sidecarPermissionStatusResultSchema
19549
+ );
19550
+ }
19423
19551
  stopRecording(params) {
19424
19552
  return this.request(
19425
19553
  "recappi.recording.stop",
@@ -19520,12 +19648,7 @@ var MiniSidecarClient = class {
19520
19648
  this.pending.delete(id);
19521
19649
  clearTimeout(pending.timer);
19522
19650
  if ("error" in response.data) {
19523
- pending.reject(
19524
- cliError("internal.unexpected", response.data.error.message, {
19525
- data: response.data.error,
19526
- retryable: response.data.error.code >= -32099 && response.data.error.code <= -32e3
19527
- })
19528
- );
19651
+ pending.reject(sidecarErrorToCliError(response.data.error));
19529
19652
  return;
19530
19653
  }
19531
19654
  pending.resolve(response.data.result);
@@ -19538,6 +19661,27 @@ var MiniSidecarClient = class {
19538
19661
  }
19539
19662
  }
19540
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
+ }
19541
19685
  function spawnMiniSidecar(opts) {
19542
19686
  const spawnProcess = opts.spawnProcess ?? spawn2;
19543
19687
  const child = spawnProcess(opts.command, opts.args ?? [], {
@@ -19602,7 +19746,7 @@ async function recordViaSidecar(opts) {
19602
19746
  }
19603
19747
  }
19604
19748
  async function startLiveRecordSession(opts) {
19605
- const session = await startRecordSession({ ...opts, live: true });
19749
+ const session = await startRecordSession({ ...opts, live: false });
19606
19750
  return {
19607
19751
  source: session.source,
19608
19752
  stop: async () => {
@@ -19658,16 +19802,19 @@ async function startRecordSession(opts) {
19658
19802
  })
19659
19803
  );
19660
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);
19661
19815
  const started = await sidecar.client.startRecording({
19662
19816
  account,
19663
- options: {
19664
- includeSystemAudio: opts.includeSystemAudio ?? true,
19665
- includeMicrophone: opts.includeMicrophone ?? true,
19666
- liveCaptions: opts.live === true,
19667
- ...opts.translationLanguage ? { translationLanguage: opts.translationLanguage } : {},
19668
- ...opts.transcriptionLanguage ? { transcriptionLanguage: opts.transcriptionLanguage } : {},
19669
- ...opts.title ? { title: opts.title } : {}
19670
- }
19817
+ options: recordingOptions
19671
19818
  });
19672
19819
  sessionId = started.sessionId;
19673
19820
  latestState = started.state;
@@ -19709,6 +19856,24 @@ async function startRecordSession(opts) {
19709
19856
  throw error51;
19710
19857
  }
19711
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
+ }
19712
19877
  function assertSidecarCapabilities(handshake, opts) {
19713
19878
  const capabilities = new Set(handshake.capabilities);
19714
19879
  const missing = [];