@hasna/recordings 0.2.13 → 0.3.1

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.
Files changed (120) hide show
  1. package/Dockerfile.package +4 -5
  2. package/README.md +113 -8
  3. package/bun.lock +2 -2
  4. package/dist/__tests__/helpers/installer-guard-execution.d.ts +22 -0
  5. package/dist/__tests__/helpers/installer-guard-execution.d.ts.map +1 -0
  6. package/dist/__tests__/helpers/installer-preflight.d.ts +22 -0
  7. package/dist/__tests__/helpers/installer-preflight.d.ts.map +1 -0
  8. package/dist/__tests__/helpers/native-fs-guard.d.ts.map +1 -1
  9. package/dist/__tests__/helpers/source-assertions.d.ts +169 -0
  10. package/dist/__tests__/helpers/source-assertions.d.ts.map +1 -0
  11. package/dist/__tests__/preload.d.ts +1 -4
  12. package/dist/__tests__/preload.d.ts.map +1 -1
  13. package/dist/cli/desktop-snapshot.d.ts +20 -0
  14. package/dist/cli/desktop-snapshot.d.ts.map +1 -0
  15. package/dist/cli/index.js +2078 -277
  16. package/dist/cli/macos-permissions.d.ts +72 -0
  17. package/dist/cli/macos-permissions.d.ts.map +1 -1
  18. package/dist/cli/macos-shortcut.d.ts +139 -0
  19. package/dist/cli/macos-shortcut.d.ts.map +1 -0
  20. package/dist/cli/trigger-probe.d.ts +172 -0
  21. package/dist/cli/trigger-probe.d.ts.map +1 -0
  22. package/dist/db/database.d.ts +9 -0
  23. package/dist/db/database.d.ts.map +1 -1
  24. package/dist/http/client.d.ts +10 -7
  25. package/dist/http/client.d.ts.map +1 -1
  26. package/dist/index.d.ts +3 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +634 -49
  29. package/dist/lib/capture-probe.d.ts +216 -0
  30. package/dist/lib/capture-probe.d.ts.map +1 -0
  31. package/dist/lib/config.d.ts.map +1 -1
  32. package/dist/lib/macos-bundle.d.ts +50 -0
  33. package/dist/lib/macos-bundle.d.ts.map +1 -0
  34. package/dist/lib/persistence-probe.d.ts +208 -0
  35. package/dist/lib/persistence-probe.d.ts.map +1 -0
  36. package/dist/lib/recorder.d.ts.map +1 -1
  37. package/dist/lib/retired-deployment-modes.d.ts +24 -0
  38. package/dist/lib/retired-deployment-modes.d.ts.map +1 -0
  39. package/dist/lib/transcriber.d.ts +60 -0
  40. package/dist/lib/transcriber.d.ts.map +1 -1
  41. package/dist/mcp/index.js +140 -56
  42. package/dist/server/cloud-config.d.ts +25 -3
  43. package/dist/server/cloud-config.d.ts.map +1 -1
  44. package/dist/server/cloud-readiness.d.ts.map +1 -1
  45. package/dist/server/cloud.d.ts +8 -7
  46. package/dist/server/cloud.d.ts.map +1 -1
  47. package/dist/server/index.js +187 -79
  48. package/dist/server/migrate-command.d.ts.map +1 -1
  49. package/dist/server/serve.d.ts.map +1 -1
  50. package/dist/storage.d.ts +2 -2
  51. package/dist/storage.d.ts.map +1 -1
  52. package/dist/storage.js +108 -49
  53. package/dist/store.d.ts +1 -1
  54. package/dist/store.d.ts.map +1 -1
  55. package/dist/version.d.ts +1 -1
  56. package/dist/version.d.ts.map +1 -1
  57. package/package.json +25 -6
  58. package/scripts/build_native_fs_guard.sh +15 -2
  59. package/scripts/ci-linux-suite.ts +403 -0
  60. package/scripts/ci-native-build.ts +402 -0
  61. package/scripts/enforce_identity_migration.sh +110 -0
  62. package/scripts/generate-sdk.ts +1 -1
  63. package/scripts/install_macos_app.sh +138 -24
  64. package/scripts/macos_artifact.ts +205 -33
  65. package/scripts/native/realtime-settle-probe/README.md +39 -0
  66. package/scripts/native/realtime-settle-probe/main.swift +135 -0
  67. package/scripts/policy/local-only-approved-targets.txt +21 -0
  68. package/scripts/read_local_only_targets.sh +203 -0
  69. package/scripts/scan-artifact.ts +40 -0
  70. package/scripts/set-version.ts +145 -0
  71. package/scripts/vacuity-manifest-gen.ts +96 -0
  72. package/scripts/vacuity-manifests/enhancement-screen-mirror.tsv +44 -0
  73. package/scripts/vacuity-manifests/install-chain.tsv +13 -0
  74. package/scripts/vacuity-manifests/must-survive-controls.tsv +10 -0
  75. package/scripts/vacuity-manifests/reorder.tsv +4 -0
  76. package/scripts/vacuity-manifests/source-side.tsv +15 -0
  77. package/scripts/vacuity-manifests/variable-operands.tsv +16 -0
  78. package/scripts/vacuity-manifests/version-sites.tsv +61 -0
  79. package/scripts/vacuity-mutation-battery.ts +266 -0
  80. package/src/native/Recordings/App/MenuBarStatusView.swift +14 -7
  81. package/src/native/Recordings/App/RuntimeSmoke.swift +5 -1
  82. package/src/native/Recordings/App/SidebarView.swift +1 -1
  83. package/src/native/Recordings/Package.swift +24 -5
  84. package/src/native/Recordings/RecordingsLib/EnhancementScreen.swift +91 -0
  85. package/src/native/Recordings/RecordingsLib/FnKeyMonitor.swift +27 -4
  86. package/src/native/Recordings/RecordingsLib/Info.plist +2 -2
  87. package/src/native/Recordings/RecordingsLib/MenuBarPresentation.swift +56 -3
  88. package/src/native/Recordings/RecordingsLib/NativePCMRecorder.swift +13 -0
  89. package/src/native/Recordings/RecordingsLib/PasteDeliveryVerification.swift +460 -0
  90. package/src/native/Recordings/RecordingsLib/RealtimeTranscriptionClient.swift +41 -7
  91. package/src/native/Recordings/RecordingsLib/RecordingAttemptAlert.swift +36 -0
  92. package/src/native/Recordings/RecordingsLib/RecordingEngine.swift +1071 -75
  93. package/src/native/Recordings/RecordingsLib/SettingsView.swift +47 -3
  94. package/src/native/Recordings/RecordingsLib/VoiceShortcuts.swift +11 -6
  95. package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +3 -3
  96. package/src/native/Recordings/RecordingsTests/EnhancementScreenTests.swift +191 -0
  97. package/src/native/Recordings/RecordingsTests/MenuBarPresentationTests.swift +144 -4
  98. package/src/native/Recordings/RecordingsTests/PasteDeliveryVerificationTests.swift +463 -0
  99. package/src/native/Recordings/RecordingsTests/PasteTargetTests.swift +33 -20
  100. package/src/native/Recordings/RecordingsTests/ProjectStoreTests.swift +1 -1
  101. package/src/native/Recordings/RecordingsTests/RealtimeSettleBenchmark.swift +200 -0
  102. package/src/native/Recordings/RecordingsTests/RealtimeSettleBudgetTests.swift +80 -0
  103. package/src/native/Recordings/RecordingsTests/RealtimeTranscriptionTests.swift +22 -4
  104. package/src/native/Recordings/RecordingsTests/RecordingEngineDeliveryTests.swift +12 -6
  105. package/src/native/Recordings/RecordingsTests/RecordingEngineHomeIsolationTests.swift +67 -0
  106. package/src/native/Recordings/RecordingsTests/RecordingStartGateTests.swift +12 -3
  107. package/src/native/Recordings/RecordingsTests/RecordingStartTimingTests.swift +207 -11
  108. package/src/native/Recordings/RecordingsTests/SpeechIntentTests.swift +17 -6
  109. package/src/native/Recordings/RecordingsTests/TestHomeDirectory.swift +23 -0
  110. package/src/native/Recordings/Updater/Broker/ApplicationProcessQuiescence.swift +7 -1
  111. package/src/native/Recordings/Updater/Broker/AtomicActivation.swift +10 -10
  112. package/src/native/Recordings/Updater/Broker/BrokerMain.swift +5 -1
  113. package/src/native/Recordings/Updater/Broker/HostOSProductVersion.swift +1 -0
  114. package/src/native/Recordings/Updater/Broker/PeerIdentity.swift +59 -3
  115. package/src/native/Recordings/Updater/Protocol/UpdateProtocol.swift +21 -2
  116. package/src/native/Recordings/Updater/ProtocolTests/ExpectationIntegrityTests.swift +60 -0
  117. package/src/native/Recordings/Updater/ProtocolTests/UpdateXPCInterfaceTests.swift +113 -0
  118. package/src/native/Recordings/Updater/VerifierLauncher/RecordingsVerifierLauncher.c +106 -5
  119. package/src/native/Recordings/build.sh +75 -9
  120. package/scripts/native/prebuilds/darwin-universal/recordings_fs_guard.node +0 -0
@@ -0,0 +1,216 @@
1
+ import type { RecordingsConfig } from "../types/index.js";
2
+ import { RECORDINGS_BUNDLE_IDENTIFIER } from "./macos-bundle.js";
3
+ /**
4
+ * Why this module exists.
5
+ *
6
+ * On macOS a process that has NOT been granted the Microphone TCC permission
7
+ * does not fail when it opens an input device. CoreAudio hands it
8
+ * ZERO-FILLED buffers instead. `rec` therefore exits 0 and writes a
9
+ * correctly-sized, structurally valid WAV file that contains digital silence.
10
+ *
11
+ * Observed on a fleet Mac (macOS 26) with Microphone = not_determined:
12
+ * $ rec -q -c 1 -r 16000 /tmp/probe.wav trim 0 1 # exit 0, 64080 bytes
13
+ * $ sox /tmp/probe.wav -n stat # Maximum amplitude: 0.000000
14
+ *
15
+ * Consequence: exit status, file existence and file size prove NOTHING about
16
+ * whether capture works. The only sound evidence is signal amplitude. Every
17
+ * capture check in this package must therefore assert on amplitude.
18
+ */
19
+ /** Peak amplitude of a 16-bit PCM WAV, normalised to 0..1. */
20
+ export interface WavPeak {
21
+ samples: number;
22
+ peak: number;
23
+ }
24
+ /**
25
+ * Read the peak amplitude of a 16-bit PCM WAV file.
26
+ *
27
+ * Walks the RIFF chunk list rather than assuming a fixed 44-byte header, since
28
+ * sox emits a LIST/INFO chunk ahead of `data` for some output formats.
29
+ *
30
+ * The whole list is scanned before anything is decoded. Stopping at `data`
31
+ * looked like a harmless optimisation and was not: RIFF does not mandate chunk
32
+ * order, so a file with `data` ahead of `fmt ` left the format unread and fell
33
+ * back to "assume 16-bit PCM" — which silently reinterpreted 32-bit float
34
+ * samples as integers and reported a plausible, wrong peak. A format this
35
+ * function cannot decode must raise, never guess.
36
+ *
37
+ * KNOWN LIMIT, stated rather than implied. Two of the robustness measures above do NOT compose:
38
+ * a file with `data` ahead of `fmt ` AND an over-declared `data` size (a truncated capture) has
39
+ * no recoverable format. The declared size is a lie, so the bytes following the data body are
40
+ * indistinguishable from audio payload without pattern-matching for a `fmt ` signature — and
41
+ * guessing the format is precisely what this function refuses to do. It raises instead. The chunk
42
+ * walk is clamped to the file length so the scan cannot run off the end, which is as far as
43
+ * ground truth reaches. Not reachable from sox output; documented because the two guarantees read
44
+ * as if they compose and they do not.
45
+ */
46
+ export declare function readWavPeak(filepath: string): WavPeak;
47
+ export interface CaptureProbeResult {
48
+ ok: boolean;
49
+ tool: string | null;
50
+ seconds: number;
51
+ samples: number;
52
+ /** Normalised 0..1 peak amplitude. Exactly 0 means no signal reached us. */
53
+ peak: number;
54
+ /**
55
+ * `true` when a capture happened and its amplitude was at or below
56
+ * `SILENCE_PEAK_THRESHOLD`; `false` when a capture happened and carried signal;
57
+ * **`null` when no amplitude was measured at all**.
58
+ *
59
+ * The null state is the point. This used to be a plain `boolean` defaulted to `true` on the
60
+ * shared `base` object, so every early return inherited it — a missing `rec` binary reported
61
+ * `silent=true samples=0 peak=0`, and a `--json` consumer reading `capture_probe.silent`
62
+ * concluded "the microphone delivered digital silence" when no capture was ever attempted.
63
+ * That is the same conflation of "could not measure" with "measured a bad value" that this
64
+ * module exists to remove, reproduced inside the module itself.
65
+ */
66
+ silent: boolean | null;
67
+ message: string;
68
+ }
69
+ export declare const DEFAULT_PROBE_SECONDS = 1;
70
+ export declare const MAX_PROBE_SECONDS = 60;
71
+ export declare const DEFAULT_RECORD_EXECUTABLE = "rec";
72
+ /**
73
+ * Amplitude at or below which a capture counts as no signal.
74
+ *
75
+ * Not exactly zero. A device can emit a trickle of dither or preamp noise while
76
+ * delivering nothing, and `peak === 0` would call that a pass. One LSB is
77
+ * 0.0000305; this threshold is ~33 LSB (32.77), still 40x below the 0.040466 peak
78
+ * of a real quiet dictation measured on a fleet Mac, so genuine speech passes with
79
+ * wide margin while a dead input does not.
80
+ *
81
+ * The multiplier read "80x" until review recomputed it: 0.040466 / 0.001 is 40.5,
82
+ * not 80. The threshold value itself measures correctly — verified at the boundary,
83
+ * int16 peak 32 fails and 34 passes — so only the stated margin was wrong. Corrected
84
+ * rather than left, because an overstated margin inside the justification for a
85
+ * threshold is the same defect class this module exists to remove.
86
+ */
87
+ export declare const SILENCE_PEAK_THRESHOLD = 0.001;
88
+ /**
89
+ * Actually capture from the microphone and assert that signal arrived.
90
+ *
91
+ * This records for `seconds` (default 1) and fails when the result carries no
92
+ * signal, which on macOS is the signature of a missing Microphone grant.
93
+ *
94
+ * `executable` is injected rather than read from the environment. It used to be
95
+ * `process.env.RECORDINGS_TEST_RECORD_EXECUTABLE || "rec"` — a test hook in the
96
+ * shipped path, letting an env var choose which program this runs, in a package
97
+ * whose own diagnostics exist because invisible inherited env vars caused an
98
+ * outage. The repo idiom is injection (see cli/macos-permissions.ts).
99
+ */
100
+ export declare function probeMicrophoneCapture(config: RecordingsConfig, options?: {
101
+ seconds?: number;
102
+ executable?: string;
103
+ timeoutMs?: number;
104
+ }): CaptureProbeResult;
105
+ export interface CaptureProbeSubject {
106
+ /** True when this process is known to have no GUI session to be prompted in. */
107
+ headless: boolean;
108
+ /**
109
+ * False when the responsible process could not be identified. An unidentified
110
+ * subject must not be narrated as if it were a local terminal.
111
+ */
112
+ subject_known: boolean;
113
+ /** What macOS will attribute the probe's microphone access to. */
114
+ subject: string;
115
+ note: string;
116
+ }
117
+ /**
118
+ * Name whose Microphone grant this probe actually exercises.
119
+ *
120
+ * macOS attributes microphone access to the RESPONSIBLE process — for a CLI that
121
+ * is the terminal application (or sshd), never Recordings.app. So a silent probe
122
+ * means "the thing running this command has no grant", which is a different
123
+ * finding, with a different fix, from "the app has no grant". Reporting the one
124
+ * as the other is how a diagnostic sends someone to the wrong settings pane.
125
+ * The user TCC db bears this out: it tracks `tmux` and `bun` as separate clients
126
+ * with their own grants, independent of any app bundle.
127
+ *
128
+ * Detection is deliberately three-valued. An earlier version inferred "local
129
+ * terminal" from the absence of SSH variables, which is wrong in the environment
130
+ * this org actually drives these machines from: tmux snapshots the environment
131
+ * when a pane is created and never refreshes it, so inside a pane SSH_CONNECTION
132
+ * and TERM_PROGRAM are both absent even though the session is remote. `sudo`
133
+ * (env_reset), launchd and cron produce the same false negative. When the
134
+ * subject cannot be identified, say so rather than emit the reassuring note.
135
+ */
136
+ export declare function captureProbeSubject(env?: NodeJS.ProcessEnv | Record<string, string | undefined>, options?: {
137
+ platform?: string;
138
+ hasTty?: boolean;
139
+ }): CaptureProbeSubject;
140
+ export interface MicrophoneGrantInstruction {
141
+ /** The bundle that must receive the grant, or null when none was found. */
142
+ bundle_path: string | null;
143
+ bundle_identifier: string;
144
+ /** Every Recordings.app on disk — more than one makes the grant ambiguous. */
145
+ candidate_bundle_paths: string[];
146
+ /** Ordered steps, already naming the pane, the section and the bundle. */
147
+ steps: string[];
148
+ }
149
+ export { RECORDINGS_BUNDLE_IDENTIFIER };
150
+ /**
151
+ * Value the TCC reader returns when a database existed but could not be read.
152
+ *
153
+ * Rebase note (#24 x #25): this used to be its own literal,
154
+ * `"unreadable_no_full_disk_access"`, because this branch also owned the reader that produced
155
+ * it. #24 replaced that reader with `resolveTccGrant()` in `cli/macos-permissions.ts`, which
156
+ * reports the same condition as `"undetermined_tcc_database_unreadable"`. Two different strings
157
+ * for one condition made every `=== TCC_UNREADABLE_STATE` comparison in the `check` renderer
158
+ * dead code, so a refused database read fell through to the red "denied" branch and printed
159
+ * grant instructions for a permission that may already have been granted — the exact false
160
+ * statement this module exists to remove.
161
+ *
162
+ * The name is kept because it is part of the package's public surface; only the value moves, to
163
+ * the single definition both sides now read.
164
+ */
165
+ export declare const TCC_UNREADABLE_STATE = "undetermined_tcc_database_unreadable";
166
+ /**
167
+ * Has the app ever asked the user for this permission?
168
+ *
169
+ * Three-valued on purpose, and that is the whole point. The previous boolean
170
+ * collapsed "I could not read the database" into "it never asked", so on a box
171
+ * where TCC was unreadable — every tmux pane on the affected machine, because it had no
172
+ * Full Disk Access — the CLI asserted "the app has never requested microphone
173
+ * access (no TCC entry exists)" while the row
174
+ * `kTCCServiceMicrophone|com.hasna.recordings|2` demonstrably existed. Emitting a
175
+ * confident false statement to the operator is the exact failure this module was
176
+ * written to remove, so unknown must stay unknown.
177
+ *
178
+ * `unknown` (TCC auth_value 1) counts as REQUESTED: a row exists, so the app has
179
+ * asked and the Settings list will contain a Recordings entry.
180
+ */
181
+ export type PermissionRequestState = "requested" | "never_requested" | "unknown";
182
+ export declare function classifyPermissionState(permissionState: string): PermissionRequestState;
183
+ /**
184
+ * Build the instruction a human must follow at the keyboard, naming the exact
185
+ * pane, section, control and bundle.
186
+ *
187
+ * Microphone consent CANNOT be granted remotely: `tccutil` only resets entries
188
+ * (`tccutil reset SERVICE [BUNDLE_ID]` is its entire interface — there is no
189
+ * insert), and the user TCC.db is SIP-protected so it is not writable even as
190
+ * root. MDM/PPPC profiles can pre-approve many TCC services, but Apple restricts
191
+ * Camera and Microphone to deny-only by profile, so for Microphone specifically
192
+ * a human at the keyboard is the only path. The Settings toggle only exists once
193
+ * the app has asked at least once, so the request must come first.
194
+ * "Grant Microphone" on its own is not actionable; this spells out what to click
195
+ * and which binary receives it.
196
+ */
197
+ export declare function microphoneGrantInstruction(options: {
198
+ /** Path the installer treats as canonical. */
199
+ installedAppPath?: string | null;
200
+ /** Other Recordings.app bundles found on disk (e.g. legacy install sites). */
201
+ otherAppPaths?: string[];
202
+ /**
203
+ * Whether the app has ever prompted. `unknown` means the TCC state could not
204
+ * be resolved and MUST NOT be narrated as "it never asked".
205
+ */
206
+ requestState?: PermissionRequestState;
207
+ }): MicrophoneGrantInstruction;
208
+ /**
209
+ * Guard so callers do not probe when `rec` is missing entirely — that is a
210
+ * different, clearer failure and deserves its own message.
211
+ */
212
+ export declare function captureProbePrecondition(): Promise<{
213
+ ready: boolean;
214
+ message: string;
215
+ }>;
216
+ //# sourceMappingURL=capture-probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capture-probe.d.ts","sourceRoot":"","sources":["../../src/lib/capture-probe.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAW9E,eAAO,MAAM,2BAA2B,sBAAsB,CAAC;AAC/D,eAAO,MAAM,8BAA8B,iBAAiB,CAAC;AAC7D,eAAO,MAAM,oCAAoC,yBAAyB,CAAC;AAE3E,eAAO,MAAM,cAAc,EAAE,gBAkC5B,CAAC;AAEF,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,gBAAgB,CA2HhE;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAoC9E;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAK/D;AAaD,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,GAAE,kBAA2B,GACpC,kBAAkB,CAMpB;AAED,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,gBAAgB,EACxB,wBAAwB,UAAO,GAC9B,gBAAgB,CAoBlB;AA0BD,wBAAgB,UAAU,IAAI,MAAM,CAyBnC;AAqHD,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAiB5D"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAW9E,eAAO,MAAM,2BAA2B,sBAAsB,CAAC;AAC/D,eAAO,MAAM,8BAA8B,iBAAiB,CAAC;AAC7D,eAAO,MAAM,oCAAoC,yBAAyB,CAAC;AAE3E,eAAO,MAAM,cAAc,EAAE,gBAkC5B,CAAC;AAEF,wBAAgB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,gBAAgB,CA2HhE;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,gBAAgB,GAAG,gBAAgB,CAoC9E;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAK/D;AAaD,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,GAAE,kBAA2B,GACpC,kBAAkB,CAMpB;AAED,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,gBAAgB,EACxB,wBAAwB,UAAO,GAC9B,gBAAgB,CAoBlB;AA0BD,wBAAgB,UAAU,IAAI,MAAM,CAyBnC;AA2ID,wBAAgB,aAAa,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAiB5D"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Canonical macOS bundle identity for Recordings.app.
3
+ *
4
+ * Why this module exists at all, rather than the constant living next to its first caller:
5
+ * three branches in the #24-#29 queue each introduced their own copy of this literal —
6
+ * `src/cli/macos-permissions.ts` (#24), `src/lib/capture-probe.ts` (#25) and
7
+ * `src/cli/macos-shortcut.ts` (#26, as `RECORDINGS_BUNDLE_ID`). None of them exists on the
8
+ * base commit, so the duplication is entirely a product of parallel work.
9
+ *
10
+ * The review ruling was "canonical is `RECORDINGS_BUNDLE_IDENTIFIER` in `macos-permissions.ts`".
11
+ * That ruling was made before #25 was known to add a copy in `src/lib`, which is the LOWER
12
+ * layer and re-exports the constant as part of the package's public surface via `src/index.ts`.
13
+ * Honouring the ruling literally would make `src/lib` import from `src/cli`, inverting the
14
+ * dependency direction for a published symbol. So the ruled NAME is kept and the owner moves
15
+ * one layer down to here; `macos-permissions.ts` re-exports it, so the ruled import path still
16
+ * resolves and every caller in the queue ends up on one definition.
17
+ *
18
+ * This identifier is not cosmetic. It is the TCC lookup key, the `tccutil reset` argument, and
19
+ * the value the app's own UserDefaults domain keys on — a bundle that does not carry it is not
20
+ * this app whatever it is named on disk.
21
+ */
22
+ export declare const RECORDINGS_BUNDLE_IDENTIFIER = "com.hasna.recordings";
23
+ /**
24
+ * The state `resolveTccGrant()` reports when a TCC database existed but could not be read.
25
+ *
26
+ * Reading the user TCC database requires Full Disk Access (`kTCCServiceSystemPolicyAllFiles`).
27
+ * That grant is a property of the **session's responsible process**, not of the tool you happen
28
+ * to be running. Measured on a fleet Mac: `bun` carries `auth_value=0` — explicitly DENIED — and a
29
+ * Bun script over SSH reads `TCC.db` anyway, because `sshd-keygen-wrapper` holds
30
+ * `SystemPolicyAllFiles auth_value=2` and its children inherit that. So "re-run from a plain ssh
31
+ * shell" is the right remedy for the wrong reason, and the intuitive reason is actively
32
+ * misleading: granting Full Disk Access to `tmux` or `bun` is not what governs the outcome.
33
+ *
34
+ * The failure this state exists for is that a reader inspecting only stdout cannot tell an empty
35
+ * result from a refused one, which is how the CLI came to assert "the app has never requested
36
+ * microphone access" on a machine where the grant row demonstrably existed.
37
+ *
38
+ * The name is deliberately cause-neutral. A non-zero `sqlite3` exit also covers a missing
39
+ * binary, `SQLITE_BUSY`, and a corrupt file — none of which is an authorization problem — so
40
+ * naming Full Disk Access in the state itself would assert a cause that was never established.
41
+ *
42
+ * Kept here, and not as a private literal in each consumer, because it is a value two modules
43
+ * must agree on byte-for-byte: `macos-permissions.ts` produces it and the `check` renderer
44
+ * compares against it to choose a yellow "cannot tell" marker over a red "denied" one. When
45
+ * those two strings drifted apart during the #24 x #25 rebase, the comparison silently became
46
+ * dead code and the renderer fell through to "denied" — telling the operator to grant a
47
+ * permission that was already granted.
48
+ */
49
+ export declare const TCC_DATABASE_UNREADABLE_STATE = "undetermined_tcc_database_unreadable";
50
+ //# sourceMappingURL=macos-bundle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"macos-bundle.d.ts","sourceRoot":"","sources":["../../src/lib/macos-bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,4BAA4B,yBAAyB,CAAC;AAEnE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,eAAO,MAAM,6BAA6B,yCAAyC,CAAC"}
@@ -0,0 +1,208 @@
1
+ import { type Store } from "../store.js";
2
+ import type { RecordingsConfig } from "../types/index.js";
3
+ /**
4
+ * Why this module exists.
5
+ *
6
+ * A transcript is only "recorded" once it is durably stored, and this package
7
+ * has TWO stores behind one interface (src/store.ts): on-box SQLite and the
8
+ * server's `/v1` HTTP API. Which one is active is decided entirely by the
9
+ * environment — the presence of HASNA_RECORDINGS_API_URL +
10
+ * HASNA_RECORDINGS_API_KEY is itself the signal to use the API, with no
11
+ * variable set explicitly to make it visible.
12
+ *
13
+ * `check` never reported which store it had resolved. That blind spot is not
14
+ * theoretical: on an affected fleet Mac those two variables are set in the
15
+ * LAUNCHD USER SESSION, so every process in the session inherits them — the GUI
16
+ * app, its CLI helper, and any ssh shell alike. Writes go to the API while
17
+ * ~/.hasna/recordings/recordings.db sits frozen at the row count it held when the
18
+ * machine last read from that file, and the live store has since grown well past
19
+ * it. Two separate audits read the stale SQLite file and concluded that recording
20
+ * had stopped persisting. It had not; they were looking at the wrong store.
21
+ *
22
+ * Deliberately no hostname, row counts or dates here: they are live production
23
+ * figures and fleet topology, this repo is public, and the mechanism above is the
24
+ * part that has engineering value.
25
+ *
26
+ * So: naming the active store is part of proving persistence, and a persistence
27
+ * probe must round-trip against the ACTIVE store, never against whichever one is
28
+ * easier to open.
29
+ */
30
+ /** The name we report when the transport came from URL+key presence alone. */
31
+ export declare const AUTO_FLIP_MODE_SOURCE = "auto:api-url+api-key";
32
+ /**
33
+ * Strip credentials out of a URL before it is printed anywhere.
34
+ *
35
+ * `toV1BaseUrl` clears the query and fragment but preserves userinfo, so a
36
+ * configured `https://svc:PASSWORD@host` reaches every reporter in this module
37
+ * intact — stdout, the `--json` payload, and two warning strings. This module
38
+ * exists to make the store visible; leaking a password while doing it would be a
39
+ * worse bug than the one it fixes.
40
+ *
41
+ * Applied at the reporting boundary rather than in `toV1BaseUrl` itself, so the
42
+ * URL the transport actually requests is left exactly as configured.
43
+ */
44
+ export declare function safeBaseUrl(baseUrl: string | null): string | null;
45
+ export interface ActiveStoreDescription {
46
+ transport: "sqlite" | "http";
47
+ /**
48
+ * What decided the transport: an env var NAME, `auto:api-url+api-key`, or
49
+ * `default`. Never a value — these variables hold credentials.
50
+ */
51
+ mode_source: string;
52
+ /** `<url>/v1` when routed to the API, else null. Carries no credential. */
53
+ base_url: string | null;
54
+ /** Path the LocalStore would use, reported whether or not it is active. */
55
+ local_db_path: string;
56
+ local_db_present: boolean;
57
+ /**
58
+ * Rows in the local SQLite file, or null when it is absent or unreadable.
59
+ * Only read when the file already exists: a diagnostic must not create a
60
+ * store, or it would invent the very divergence it is looking for.
61
+ */
62
+ local_db_recordings: number | null;
63
+ /**
64
+ * True when writes are routed to the API while a NON-EMPTY local SQLite file
65
+ * also exists. Both datasets then look plausible to a human, and only one is
66
+ * live — the exact condition that produced two wrong audits.
67
+ */
68
+ divergent: boolean;
69
+ warning: string | null;
70
+ }
71
+ /**
72
+ * Is an existing local store behind the current schema?
73
+ *
74
+ * Read-only, because the only other way to find out is to open it read-write,
75
+ * which applies the migrations — the exact side effect being checked for.
76
+ * Returns null when the answer cannot be established.
77
+ */
78
+ export declare function localStoreIsBehindSchema(dbPath: string): boolean | null;
79
+ /**
80
+ * Report which store this process would read and write, and whether a second,
81
+ * stale dataset is sitting next to it.
82
+ */
83
+ export declare function describeActiveStore(config: RecordingsConfig, env?: NodeJS.ProcessEnv | Record<string, string | undefined>): ActiveStoreDescription;
84
+ export interface PersistenceProbeResult {
85
+ /**
86
+ * True ONLY when a write was performed and read back. Never true for a skip.
87
+ *
88
+ * This used to be `true` on the skip path, which is how `check --probe` came to print
89
+ * `✓ Persistence round-trip: SKIPPED…` and exit 0 while writing, reading and deleting nothing —
90
+ * and because a shared API store is skipped by default, that was the DEFAULT outcome on
91
+ * the fleet Mac whose blind spot this probe was written to close. Read `outcome` to
92
+ * distinguish "declined to measure" from "measured and passed"; `ok` is exactly
93
+ * `outcome === "proved"`.
94
+ */
95
+ ok: boolean;
96
+ /**
97
+ * Three-valued because "I declined to measure" is not a pass and not a failure.
98
+ *
99
+ * - `proved` — a marker was written, read back byte-identical, and cleaned up.
100
+ * - `failed` — a write, read-back or cleanup step actively failed.
101
+ * - `skipped` — the probe refused to write to a shared API store without `allowRemoteWrite`.
102
+ *
103
+ * The exit code must key off `failed` alone; the rendered marker must distinguish all three.
104
+ */
105
+ outcome: "proved" | "failed" | "skipped";
106
+ /** False when the probe declined to write, e.g. to a live API store. */
107
+ attempted: boolean;
108
+ /**
109
+ * Whether the active store answered a read-only request, and what it said.
110
+ *
111
+ * Only meaningful on the skip path, and it exists because the skip previously made ZERO network
112
+ * contact: it did not establish that the store was resolvable, reachable or authenticating —
113
+ * only that the probe had chosen not to write. A read-only `getRecordingStats()` proves
114
+ * reachability plus credential acceptance without adding a row to production.
115
+ */
116
+ reachable: boolean | null;
117
+ transport: "sqlite" | "http";
118
+ base_url: string | null;
119
+ /** Id of the marker recording, kept for cleanup follow-up when needed. */
120
+ recording_id: string | null;
121
+ /** The written transcript was read back byte-identical. */
122
+ read_back: boolean;
123
+ /**
124
+ * The marker was removed AND its absence confirmed by a follow-up read. The
125
+ * delete call's own return value is not sufficient — see the comment below.
126
+ */
127
+ cleaned_up: boolean;
128
+ /** True when the probe itself brought the local SQLite store into existence. */
129
+ created_local_store: boolean;
130
+ message: string;
131
+ }
132
+ /**
133
+ * How a probe outcome must READ, separated from the colour so it can be asserted without a
134
+ * terminal.
135
+ *
136
+ * `PersistenceProbeResult.outcome` already documents that "the rendered marker must distinguish
137
+ * all three" — and nothing asserted it, so flipping the `skipped` arm of the CLI's inline ternary
138
+ * from `chalk.yellow("?")` to `chalk.green("✓")` put a green tick back on the word SKIPPED and
139
+ * survived the entire suite with a byte-identical failure set. The mapping lives here, is
140
+ * exhaustive, and is the only thing the CLI is allowed to key its colour off.
141
+ */
142
+ export type ProbeMarkerSeverity = "pass" | "warn" | "fail";
143
+ /** Glyph per severity. `?` for warn, because it is not a tick and not a cross. */
144
+ export declare const PROBE_SEVERITY_GLYPHS: Record<ProbeMarkerSeverity, string>;
145
+ /**
146
+ * Severity of a persistence outcome. `skipped` is `warn`, never `pass`: declining to measure is
147
+ * not a measurement that passed.
148
+ *
149
+ * Exhaustive on `outcome` so a fourth state has to make its own decision instead of falling into
150
+ * a tick by default.
151
+ */
152
+ export declare function persistenceOutcomeSeverity(outcome: PersistenceProbeResult["outcome"]): ProbeMarkerSeverity;
153
+ /**
154
+ * The marker the CLI prints for a persistence outcome.
155
+ *
156
+ * `paint` is injected rather than imported so the whole mapping — outcome to severity to glyph to
157
+ * colour — is one testable expression instead of a ternary chain inside a 2900-line command file
158
+ * that no test could reach.
159
+ */
160
+ export declare function renderPersistenceMarker(outcome: PersistenceProbeResult["outcome"], paint: Record<ProbeMarkerSeverity, (text: string) => string>): string;
161
+ /**
162
+ * Tag carried by probe rows so a leftover marker is identifiable.
163
+ *
164
+ * Do not rely on it alone to find one: the deployed 0.2.10 server ignores the
165
+ * `tags` filter, so `list --tags check-probe` returns everything. Full-text
166
+ * search on the marker prefix below does work against that server.
167
+ */
168
+ export declare const PERSISTENCE_PROBE_TAG = "check-probe";
169
+ /** Searchable prefix every probe marker carries, for recovering a leftover row. */
170
+ export declare const PERSISTENCE_PROBE_MARKER_PREFIX = "recordings check --probe persistence round-trip";
171
+ /**
172
+ * Prove the active store accepts a write, returns it, and lets it be deleted.
173
+ *
174
+ * Presence of a database file, a green `stats` call, or a row count all prove
175
+ * only that SOMETHING is readable. This writes a tagged marker, reads it back,
176
+ * compares the text, and deletes it — the same write-probe shape the native app
177
+ * already uses to validate its helper (RecordingsCLI.probePackagedCompanion).
178
+ *
179
+ * Scope note: this proves the store this process resolves is writable. It does
180
+ * not prove the macOS app resolves the same store; when the two disagree, the
181
+ * cause is env inheritance, which `describeActiveStore` reports on.
182
+ *
183
+ * Writing to a shared API store is NOT done by default. That store is production
184
+ * for every machine pointed at it, a probe row is briefly visible to concurrent
185
+ * readers in `stats.total` and `by_model`, and the server's idempotency ledger
186
+ * keeps a row per create with `ON DELETE SET NULL`, so each run leaves a
187
+ * permanent orphan that deleting the recording does not remove. A diagnostic may
188
+ * not do that to production unless the operator asks for it explicitly, so
189
+ * `allowRemoteWrite` must be set for the `http` transport.
190
+ */
191
+ export declare function probeRecordingPersistence(options?: {
192
+ store?: Store;
193
+ now?: () => Date;
194
+ /** Required before this will write to a shared API store. */
195
+ allowRemoteWrite?: boolean;
196
+ /** Pre-probe existence of the local SQLite file, for honest reporting. */
197
+ localStoreExistedBefore?: boolean;
198
+ /**
199
+ * Required before this will migrate a LEGACY local store. Writing through
200
+ * the LocalStore opens the database read-write, which applies pending
201
+ * migrations — and this module calls migrating a legacy file "a destructive
202
+ * surprise" 80 lines above. It does not get to do it silently either.
203
+ */
204
+ allowLocalMigration?: boolean;
205
+ /** Whether the local store is behind the current schema; null = unknown. */
206
+ localStoreIsLegacy?: boolean | null;
207
+ }): Promise<PersistenceProbeResult>;
208
+ //# sourceMappingURL=persistence-probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"persistence-probe.d.ts","sourceRoot":"","sources":["../../src/lib/persistence-probe.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/lib/recorder.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAU1D;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC;IAClD,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,CAoBD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAqD/D;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,IAAI,CAa7C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAE9C;AAwBD;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,MAAM,CAAC,CAwCjB"}
1
+ {"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/lib/recorder.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAU1D;;;GAGG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC;IAClD,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC,CAoBD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAyD/D;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,GAAG,IAAI,CAa7C;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,OAAO,CAErC;AAED;;GAEG;AACH,wBAAgB,cAAc,IAAI,MAAM,GAAG,IAAI,CAE9C;AAwBD;;GAEG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,gBAAgB,GACvB,OAAO,CAAC,MAAM,CAAC,CAwCjB"}
@@ -0,0 +1,24 @@
1
+ export declare const RETIRED_DEPLOYMENT_MODES: readonly ["local", "self_hosted", "cloud", "remote", "hybrid"];
2
+ export type RetiredDeploymentMode = (typeof RETIRED_DEPLOYMENT_MODES)[number];
3
+ /** Fold case, surrounding space, and the `self-hosted`/`self_hosted` spelling split. */
4
+ export declare function normalizeModeToken(value: string): string;
5
+ /** The retired mode this value names, or null if it names something else. */
6
+ export declare function asRetiredDeploymentMode(value: string): RetiredDeploymentMode | null;
7
+ export interface RetiredModeReplacement {
8
+ /** The variable the caller should set instead. */
9
+ envKey: string;
10
+ /** Value that replaces `local` — the on-this-box arm. */
11
+ onBox: string;
12
+ /** Value that replaces `self_hosted` / `cloud` / `remote` / `hybrid`. */
13
+ offBox: string;
14
+ }
15
+ /**
16
+ * The error a retired mode word must produce.
17
+ *
18
+ * It names three things a bare "unknown value" error does not: the variable that
19
+ * carried the retired word, the variable to set instead, and the exact value —
20
+ * because the operator reading this cannot be assumed to know the collapse
21
+ * happened.
22
+ */
23
+ export declare function retiredDeploymentModeError(rawValue: string, sourceEnvKey: string, replacement: RetiredModeReplacement): Error;
24
+ //# sourceMappingURL=retired-deployment-modes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retired-deployment-modes.d.ts","sourceRoot":"","sources":["../../src/lib/retired-deployment-modes.ts"],"names":[],"mappings":"AA8BA,eAAO,MAAM,wBAAwB,gEAM3B,CAAC;AAEX,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,wBAAwB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9E,wFAAwF;AACxF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,6EAA6E;AAC7E,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,qBAAqB,GAAG,IAAI,CAKnF;AAED,MAAM,WAAW,sBAAsB;IACrC,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;IACd,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,sBAAsB,GAClC,KAAK,CASP"}
@@ -4,6 +4,66 @@ export interface TranscriptionOptions {
4
4
  onDelta?: (delta: string, textSoFar: string) => void;
5
5
  }
6
6
  export declare function resetClient(): void;
7
+ /**
8
+ * Strip anything key-shaped out of a provider error before we print or log it.
9
+ *
10
+ * OpenAI's 401 body echoes a partially masked form of the key that was sent
11
+ * ("sk-abcd1234**********wxyz"). Even masked, that is credential material and
12
+ * must not land in terminal scrollback, CI logs or a diagnostics file.
13
+ */
14
+ export declare function redactKeyMaterial(text: string): string;
15
+ export interface CredentialProbeResult {
16
+ ok: boolean;
17
+ /** HTTP status when the API answered, null when we never reached it. */
18
+ status: number | null;
19
+ model: string;
20
+ /** Which configured role this probe covered: transcription or enhancement. */
21
+ role: "transcription" | "enhancement";
22
+ message: string;
23
+ }
24
+ /**
25
+ * Performs the one authenticated request the credential probe rests on.
26
+ *
27
+ * Injected so the request path can be tested. Before this existed, review deleted the
28
+ * `models.retrieve` call and its success return outright — replacing them with an unconditional
29
+ * `{ok: true, status: 200}` that made no request at all — and `bun test
30
+ * src/__tests__/credential-probe.test.ts` still reported **7 pass / 0 fail, exit 0**. Every
31
+ * existing test passed an empty key, so all of them returned at the missing-key branch before a
32
+ * client was ever constructed, leaving the 401 rejection (the probe's headline claim), the 404
33
+ * model-unavailable case, the 200 success path, and `redactKeyMaterial` applied to a real error
34
+ * body all uncovered. A probe whose request can be removed without a failing test proves nothing.
35
+ *
36
+ * Mirrors the `executable` injection the capture probe already uses for the same reason.
37
+ */
38
+ export type CredentialModelRetriever = (model: string, apiKey: string) => Promise<{
39
+ id?: string;
40
+ }>;
41
+ /**
42
+ * Verify that the configured credential is ACCEPTED by the API, not merely
43
+ * present in config.
44
+ *
45
+ * A non-empty string in `openai_api_key` proves nothing: any placeholder
46
+ * satisfies it. This makes one real authenticated request, so a rejected or
47
+ * revoked key surfaces as a failure instead of a green tick.
48
+ *
49
+ * The model and key must be passed in as a matched PAIR, and the caller must not
50
+ * mix roles. An earlier version probed `resolveTranscriberModel(config)` — which
51
+ * is `transcriber_model || enhancement_model`, the POST-PROCESSING model — using
52
+ * `openai_api_key`, under a line labelled "Transcription credential". Two ways
53
+ * to be wrong: with split keys it retrieved the enhancement model with the
54
+ * transcription key and printed a red 404 on a working machine; and it never
55
+ * touched `config.transcription_model`, the model `transcribeAudio` actually
56
+ * uses, so that model being unavailable produced a green tick.
57
+ *
58
+ * Scope note: this proves authentication and model access. It does not prove
59
+ * quota or that a given audio file will transcribe.
60
+ */
61
+ export declare function verifyTranscriptionCredential(config: RecordingsConfig, model: string, options?: {
62
+ apiKey?: string;
63
+ role?: "transcription" | "enhancement";
64
+ /** Test seam. Defaults to a real authenticated `models.retrieve`. */
65
+ retrieveModel?: CredentialModelRetriever;
66
+ }): Promise<CredentialProbeResult>;
7
67
  export declare function transcribeAudio(audioPath: string, config: RecordingsConfig, options?: Pick<TranscriptionOptions, "prompt">): Promise<TranscriptionResult>;
8
68
  export declare function transcribeBuffer(buffer: Buffer, filename: string, config: RecordingsConfig, options?: Pick<TranscriptionOptions, "prompt">): Promise<TranscriptionResult>;
9
69
  export declare function transcribeAudioStream(audioPath: string, config: RecordingsConfig, options?: TranscriptionOptions): Promise<TranscriptionResult>;
@@ -1 +1 @@
1
- {"version":3,"file":"transcriber.d.ts","sourceRoot":"","sources":["../../src/lib/transcriber.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAM/E,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACtD;AAcD,wBAAgB,WAAW,IAAI,IAAI,CAGlC;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAM,GACjD,OAAO,CAAC,mBAAmB,CAAC,CA4B9B;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAM,GACjD,OAAO,CAAC,mBAAmB,CAAC,CA6B9B;AAED,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CA6C9B;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQpE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAM5D"}
1
+ {"version":3,"file":"transcriber.d.ts","sourceRoot":"","sources":["../../src/lib/transcriber.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAM/E,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACtD;AAcD,wBAAgB,WAAW,IAAI,IAAI,CAGlC;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBtD;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,OAAO,CAAC;IACZ,wEAAwE;IACxE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,IAAI,EAAE,eAAe,GAAG,aAAa,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,wBAAwB,GAAG,CACrC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,KACX,OAAO,CAAC;IAAE,EAAE,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAU9B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,6BAA6B,CACjD,MAAM,EAAE,gBAAgB,EACxB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE;IACP,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,eAAe,GAAG,aAAa,CAAC;IACvC,qEAAqE;IACrE,aAAa,CAAC,EAAE,wBAAwB,CAAC;CACrC,GACL,OAAO,CAAC,qBAAqB,CAAC,CA6ChC;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAM,GACjD,OAAO,CAAC,mBAAmB,CAAC,CA4B9B;AAED,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,IAAI,CAAC,oBAAoB,EAAE,QAAQ,CAAM,GACjD,OAAO,CAAC,mBAAmB,CAAC,CA6B9B;AAED,wBAAsB,qBAAqB,CACzC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,gBAAgB,EACxB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CA6C9B;AAED,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQpE;AAED,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAM5D"}