@hraness/ghostget 0.17.2

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 (500) hide show
  1. package/CHANGELOG.md +458 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1359 -0
  4. package/bunfig.toml +2 -0
  5. package/dist/apple-photos-client.js +704 -0
  6. package/dist/beeper-client.js +4602 -0
  7. package/dist/client.js +1605 -0
  8. package/dist/index-26yq8q16.js +31 -0
  9. package/dist/index-9t13b4k7.js +508 -0
  10. package/dist/index-axwm9b3v.js +5 -0
  11. package/dist/index-gwk7rbyj.js +30 -0
  12. package/dist/index.js +701 -0
  13. package/dist/messaging.js +1130 -0
  14. package/dist/omni-client.js +1406 -0
  15. package/dist/whatsapp-client.js +355 -0
  16. package/docs/imessage-direct-provider.md +106 -0
  17. package/docs/rental-listings.md +94 -0
  18. package/package.json +600 -0
  19. package/skills/ghostget/SKILL.md +251 -0
  20. package/skills/ghostget/agents/openai.yaml +4 -0
  21. package/skills/ghostget/references/adapter-contract.md +181 -0
  22. package/skills/ghostget/references/apple-photos.md +115 -0
  23. package/skills/ghostget/references/article-drafts.md +235 -0
  24. package/skills/ghostget/references/code-owned-provider-scaffold.md +134 -0
  25. package/skills/ghostget/references/cross-posting-video.md +139 -0
  26. package/skills/ghostget/references/cross-posting.md +82 -0
  27. package/skills/ghostget/references/derivation.md +390 -0
  28. package/skills/ghostget/references/hraness-social-profile-stats.json +269 -0
  29. package/skills/ghostget/references/install.md +66 -0
  30. package/skills/ghostget/references/linkedin-adapter.md +442 -0
  31. package/skills/ghostget/references/messaging.md +168 -0
  32. package/skills/ghostget/references/meta-comet-contract.md +153 -0
  33. package/skills/ghostget/references/platform-patterns.md +122 -0
  34. package/skills/ghostget/references/provider-plugins.md +496 -0
  35. package/skills/ghostget/references/reddit-flair.md +78 -0
  36. package/skills/ghostget/references/safety-and-state.md +214 -0
  37. package/skills/ghostget/references/settlement-and-duplicate-cleanup.md +79 -0
  38. package/skills/ghostget/references/social-platform-routing.md +66 -0
  39. package/skills/ghostget/references/social-profile-stats.md +195 -0
  40. package/skills/ghostget/references/social-video-platform-routing.md +89 -0
  41. package/skills/ghostget/references/whatsapp-adapter.md +101 -0
  42. package/skills/ghostget/references/x-adapter.md +332 -0
  43. package/skills/ghostget/references/x-ai-disclosure.md +71 -0
  44. package/src/apple-photos-cli.ts +639 -0
  45. package/src/apple-photos-client-types.ts +168 -0
  46. package/src/apple-photos-client.ts +201 -0
  47. package/src/apple-photos-contact-evidence.ts +838 -0
  48. package/src/apple-photos-local-source.ts +1131 -0
  49. package/src/args.ts +2053 -0
  50. package/src/article-draft-document.ts +417 -0
  51. package/src/article-draft-embeds.ts +123 -0
  52. package/src/article-draft-images.ts +214 -0
  53. package/src/assets/adapter-template/wrench-adapter.json +40 -0
  54. package/src/assets/adapters/beeper/wrench-web-adapter.json +1500 -0
  55. package/src/assets/adapters/beeper/wrench-web-adapter.v1.0.0.json +129 -0
  56. package/src/assets/adapters/beeper/wrench-web-adapter.v1.1.0.json +207 -0
  57. package/src/assets/adapters/beeper/wrench-web-adapter.v2.0.0.json +1463 -0
  58. package/src/assets/adapters/beeper/wrench-web-adapter.v2.1.0.json +1463 -0
  59. package/src/assets/adapters/beeper/wrench-web-adapter.v2.2.0.json +1475 -0
  60. package/src/assets/adapters/beeper/wrench-web-adapter.v2.3.0.json +1488 -0
  61. package/src/assets/adapters/bluesky/wrench-web-adapter.json +684 -0
  62. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.0.0.json +581 -0
  63. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.1.0.json +581 -0
  64. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.2.0.json +581 -0
  65. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.3.0.json +615 -0
  66. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.4.0.json +684 -0
  67. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.5.0.json +684 -0
  68. package/src/assets/adapters/bluesky/wrench-web-adapter.v1.6.0.json +684 -0
  69. package/src/assets/adapters/clasificados/wrench-web-adapter.json +54 -0
  70. package/src/assets/adapters/facebook/wrench-web-adapter.json +750 -0
  71. package/src/assets/adapters/facebook/wrench-web-adapter.v1.0.0.json +706 -0
  72. package/src/assets/adapters/facebook/wrench-web-adapter.v1.1.0.json +703 -0
  73. package/src/assets/adapters/facebook/wrench-web-adapter.v1.2.0.json +750 -0
  74. package/src/assets/adapters/facebook-group/wrench-web-adapter.json +682 -0
  75. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.0.0.json +583 -0
  76. package/src/assets/adapters/facebook-group/wrench-web-adapter.v1.1.0.json +682 -0
  77. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.json +437 -0
  78. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.0.0.json +383 -0
  79. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.0.json +437 -0
  80. package/src/assets/adapters/facebook-marketplace/wrench-web-adapter.v1.1.1.json +437 -0
  81. package/src/assets/adapters/facebook-page/wrench-web-adapter.json +880 -0
  82. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.0.0.json +740 -0
  83. package/src/assets/adapters/facebook-page/wrench-web-adapter.v1.1.0.json +880 -0
  84. package/src/assets/adapters/github/wrench-web-adapter.json +69 -0
  85. package/src/assets/adapters/github/wrench-web-adapter.v1.0.0.json +42 -0
  86. package/src/assets/adapters/github/wrench-web-adapter.v1.1.0.json +69 -0
  87. package/src/assets/adapters/gmail/wrench-adapter.json +161 -0
  88. package/src/assets/adapters/gmail/wrench-adapter.v1.2.0.json +157 -0
  89. package/src/assets/adapters/hacker-news/wrench-web-adapter.json +315 -0
  90. package/src/assets/adapters/hacker-news/wrench-web-adapter.v1.0.0.json +315 -0
  91. package/src/assets/adapters/imessage/wrench-web-adapter.json +236 -0
  92. package/src/assets/adapters/instagram/wrench-web-adapter.json +719 -0
  93. package/src/assets/adapters/instagram/wrench-web-adapter.v1.0.0.json +627 -0
  94. package/src/assets/adapters/instagram/wrench-web-adapter.v1.1.0.json +609 -0
  95. package/src/assets/adapters/instagram/wrench-web-adapter.v1.2.0.json +640 -0
  96. package/src/assets/adapters/instagram/wrench-web-adapter.v1.3.0.json +671 -0
  97. package/src/assets/adapters/instagram/wrench-web-adapter.v1.4.0.json +710 -0
  98. package/src/assets/adapters/instagram/wrench-web-adapter.v1.5.0.json +710 -0
  99. package/src/assets/adapters/instagram/wrench-web-adapter.v1.6.0.json +719 -0
  100. package/src/assets/adapters/instagram/wrench-web-adapter.v1.7.0.json +719 -0
  101. package/src/assets/adapters/linkedin/wrench-adapter.json +442 -0
  102. package/src/assets/adapters/linkedin/wrench-adapter.v0.4.0.json +124 -0
  103. package/src/assets/adapters/linkedin/wrench-adapter.v1.0.0.json +411 -0
  104. package/src/assets/adapters/linkedin/wrench-web-adapter.json +914 -0
  105. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.0.0.json +604 -0
  106. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.1.0.json +604 -0
  107. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.10.0.json +809 -0
  108. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.11.0.json +809 -0
  109. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.12.0.json +820 -0
  110. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.13.0.json +819 -0
  111. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.14.0.json +819 -0
  112. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.15.0.json +819 -0
  113. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.16.0.json +874 -0
  114. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.17.0.json +874 -0
  115. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.18.0.json +874 -0
  116. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.19.0.json +874 -0
  117. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.2.0.json +731 -0
  118. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.3.0.json +768 -0
  119. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.4.0.json +808 -0
  120. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.5.0.json +808 -0
  121. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.6.0.json +808 -0
  122. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.7.0.json +825 -0
  123. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.8.0.json +792 -0
  124. package/src/assets/adapters/linkedin/wrench-web-adapter.v1.9.0.json +809 -0
  125. package/src/assets/adapters/reddit/wrench-web-adapter.json +855 -0
  126. package/src/assets/adapters/reddit/wrench-web-adapter.v1.0.0.json +577 -0
  127. package/src/assets/adapters/reddit/wrench-web-adapter.v1.1.0.json +604 -0
  128. package/src/assets/adapters/reddit/wrench-web-adapter.v1.10.0.json +712 -0
  129. package/src/assets/adapters/reddit/wrench-web-adapter.v1.11.0.json +712 -0
  130. package/src/assets/adapters/reddit/wrench-web-adapter.v1.12.0.json +712 -0
  131. package/src/assets/adapters/reddit/wrench-web-adapter.v1.2.0.json +653 -0
  132. package/src/assets/adapters/reddit/wrench-web-adapter.v1.3.0.json +712 -0
  133. package/src/assets/adapters/reddit/wrench-web-adapter.v1.4.0.json +713 -0
  134. package/src/assets/adapters/reddit/wrench-web-adapter.v1.5.0.json +712 -0
  135. package/src/assets/adapters/reddit/wrench-web-adapter.v1.6.0.json +713 -0
  136. package/src/assets/adapters/reddit/wrench-web-adapter.v1.7.0.json +713 -0
  137. package/src/assets/adapters/reddit/wrench-web-adapter.v1.8.0.json +713 -0
  138. package/src/assets/adapters/reddit/wrench-web-adapter.v1.9.0.json +713 -0
  139. package/src/assets/adapters/substack/wrench-web-adapter.json +936 -0
  140. package/src/assets/adapters/substack/wrench-web-adapter.v1.0.0.json +817 -0
  141. package/src/assets/adapters/substack/wrench-web-adapter.v1.1.0.json +812 -0
  142. package/src/assets/adapters/substack/wrench-web-adapter.v1.2.0.json +812 -0
  143. package/src/assets/adapters/substack/wrench-web-adapter.v1.3.0.json +902 -0
  144. package/src/assets/adapters/substack/wrench-web-adapter.v1.4.0.json +936 -0
  145. package/src/assets/adapters/substack/wrench-web-adapter.v1.5.0.json +936 -0
  146. package/src/assets/adapters/substack/wrench-web-adapter.v1.6.0.json +936 -0
  147. package/src/assets/adapters/threads/wrench-web-adapter.json +692 -0
  148. package/src/assets/adapters/threads/wrench-web-adapter.v1.0.0.json +629 -0
  149. package/src/assets/adapters/threads/wrench-web-adapter.v1.1.0.json +623 -0
  150. package/src/assets/adapters/threads/wrench-web-adapter.v1.2.0.json +622 -0
  151. package/src/assets/adapters/threads/wrench-web-adapter.v1.3.0.json +622 -0
  152. package/src/assets/adapters/threads/wrench-web-adapter.v1.4.0.json +622 -0
  153. package/src/assets/adapters/threads/wrench-web-adapter.v1.5.0.json +699 -0
  154. package/src/assets/adapters/threads/wrench-web-adapter.v1.6.0.json +693 -0
  155. package/src/assets/adapters/threads/wrench-web-adapter.v1.7.0.json +692 -0
  156. package/src/assets/adapters/tiktok/wrench-web-adapter.json +762 -0
  157. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.0.0.json +681 -0
  158. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.1.0.json +708 -0
  159. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.2.0.json +762 -0
  160. package/src/assets/adapters/tiktok/wrench-web-adapter.v1.3.0.json +762 -0
  161. package/src/assets/adapters/twitch/wrench-web-adapter.json +42 -0
  162. package/src/assets/adapters/twitch/wrench-web-adapter.v1.0.0.json +42 -0
  163. package/src/assets/adapters/whatsapp/wrench-web-adapter.json +400 -0
  164. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.0.0.json +355 -0
  165. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.1.0.json +386 -0
  166. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.2.0.json +386 -0
  167. package/src/assets/adapters/whatsapp/wrench-web-adapter.v1.3.0.json +400 -0
  168. package/src/assets/adapters/x/wrench-adapter.json +749 -0
  169. package/src/assets/adapters/x/wrench-adapter.v1.0.0.json +700 -0
  170. package/src/assets/adapters/x/wrench-adapter.v1.1.0.json +707 -0
  171. package/src/assets/adapters/x/wrench-web-adapter.json +712 -0
  172. package/src/assets/adapters/x/wrench-web-adapter.v1.1.0.json +579 -0
  173. package/src/assets/adapters/x/wrench-web-adapter.v1.10.0.json +685 -0
  174. package/src/assets/adapters/x/wrench-web-adapter.v1.11.0.json +712 -0
  175. package/src/assets/adapters/x/wrench-web-adapter.v1.12.0.json +712 -0
  176. package/src/assets/adapters/x/wrench-web-adapter.v1.13.0.json +712 -0
  177. package/src/assets/adapters/x/wrench-web-adapter.v1.2.0.json +578 -0
  178. package/src/assets/adapters/x/wrench-web-adapter.v1.3.0.json +610 -0
  179. package/src/assets/adapters/x/wrench-web-adapter.v1.4.0.json +617 -0
  180. package/src/assets/adapters/x/wrench-web-adapter.v1.5.0.json +634 -0
  181. package/src/assets/adapters/x/wrench-web-adapter.v1.6.0.json +632 -0
  182. package/src/assets/adapters/x/wrench-web-adapter.v1.7.0.json +649 -0
  183. package/src/assets/adapters/x/wrench-web-adapter.v1.8.0.json +649 -0
  184. package/src/assets/adapters/x/wrench-web-adapter.v1.9.0.json +683 -0
  185. package/src/assets/adapters/youtube/wrench-web-adapter.json +561 -0
  186. package/src/assets/adapters/youtube/wrench-web-adapter.v1.0.0.json +465 -0
  187. package/src/assets/adapters/youtube/wrench-web-adapter.v1.1.0.json +519 -0
  188. package/src/assets/adapters/youtube/wrench-web-adapter.v1.2.0.json +561 -0
  189. package/src/assets/adapters/youtube/wrench-web-adapter.v1.3.0.json +561 -0
  190. package/src/assets/code-owned-provider-template/plugin.test.ts.template +54 -0
  191. package/src/assets/code-owned-provider-template/plugin.ts.template +102 -0
  192. package/src/assets/code-owned-provider-template/runtime.internal.test.ts.template +44 -0
  193. package/src/assets/code-owned-provider-template/runtime.ts.template +59 -0
  194. package/src/assets/code-owned-provider-template/wrench-adapter.json.template +33 -0
  195. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  196. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  197. package/src/assets/fonts/nebula-sans/PROVENANCE.md +14 -0
  198. package/src/assets.d.ts +4 -0
  199. package/src/auth.ts +1157 -0
  200. package/src/beeper-client-types.ts +169 -0
  201. package/src/beeper-client.ts +250 -0
  202. package/src/beeper-contact-interactions-cli.ts +180 -0
  203. package/src/beeper-contact-interactions.ts +1612 -0
  204. package/src/beeper-message-bundle-v1.ts +257 -0
  205. package/src/beeper-message-like-me-cli.ts +84 -0
  206. package/src/beeper-message-like-me-export.ts +2274 -0
  207. package/src/beeper-message-like-me-recovery.ts +931 -0
  208. package/src/beeper-message-like-me-source.ts +4034 -0
  209. package/src/browser-admission.ts +928 -0
  210. package/src/browser-snapshots.ts +260 -0
  211. package/src/browser.ts +3873 -0
  212. package/src/canonical-json.ts +60 -0
  213. package/src/catalog-cli.ts +638 -0
  214. package/src/cli.ts +208 -0
  215. package/src/client-types.ts +301 -0
  216. package/src/client.ts +2726 -0
  217. package/src/confirmed-write-failure.ts +44 -0
  218. package/src/confirmed-write-model.ts +72 -0
  219. package/src/confirmed-write-platform.ts +1055 -0
  220. package/src/confirmed-write-program.ts +224 -0
  221. package/src/confirmed-write-runtime.ts +16 -0
  222. package/src/cursor-token.ts +228 -0
  223. package/src/derivation-dnr-readiness.ts +367 -0
  224. package/src/derivation-file-chooser.ts +384 -0
  225. package/src/derivation-network-boundary.ts +835 -0
  226. package/src/derivation-network-guard.ts +782 -0
  227. package/src/derivation-network-proxy-helper.ts +440 -0
  228. package/src/derivation-network-proxy.ts +385 -0
  229. package/src/derive-command-helper.ts +693 -0
  230. package/src/derive-fixtures.ts +343 -0
  231. package/src/derive-review.ts +980 -0
  232. package/src/derive.ts +4754 -0
  233. package/src/fixtures/beeper-message-like-me-v1/accounts.ndjson +2 -0
  234. package/src/fixtures/beeper-message-like-me-v1/conversations.ndjson +1 -0
  235. package/src/fixtures/beeper-message-like-me-v1/manifest.json +1 -0
  236. package/src/fixtures/beeper-message-like-me-v1/messages.ndjson +2 -0
  237. package/src/fixtures/beeper-message-like-me-v1/participants.ndjson +3 -0
  238. package/src/fixtures/beeper-message-like-me-v1/reactions.ndjson +1 -0
  239. package/src/fixtures/beeper-message-like-me-v1/tombstones.ndjson +1 -0
  240. package/src/ghostget.ts +3672 -0
  241. package/src/gmail-capture.ts +1244 -0
  242. package/src/har-internal.ts +1242 -0
  243. package/src/har.ts +743 -0
  244. package/src/index.ts +218 -0
  245. package/src/invocation-read-platform.ts +55 -0
  246. package/src/invocation-read-program.ts +93 -0
  247. package/src/invocation-read-runtime.ts +11 -0
  248. package/src/linked-device-lifecycle-admission.ts +450 -0
  249. package/src/linked-device-lifecycle-journal.ts +1560 -0
  250. package/src/linked-device-lifecycle-runtime.ts +1602 -0
  251. package/src/local-cli-admission.ts +135 -0
  252. package/src/local-cli-contracts.ts +271 -0
  253. package/src/local-cli-execution.ts +98 -0
  254. package/src/local-cli-surface-contract.ts +1510 -0
  255. package/src/local-cli-tool-identity.ts +336 -0
  256. package/src/media/archive.ts +2325 -0
  257. package/src/media/args.ts +303 -0
  258. package/src/media/cli.ts +287 -0
  259. package/src/media/doctor.ts +475 -0
  260. package/src/media/ffmpeg.ts +1330 -0
  261. package/src/media/http-capture.ts +711 -0
  262. package/src/media/http-probe.ts +593 -0
  263. package/src/media/http.ts +387 -0
  264. package/src/media/index.ts +10 -0
  265. package/src/media/local-transcription.ts +613 -0
  266. package/src/media/lock.ts +321 -0
  267. package/src/media/manifest.ts +1712 -0
  268. package/src/media/metadata.ts +906 -0
  269. package/src/media/process.ts +723 -0
  270. package/src/media/revision.ts +166 -0
  271. package/src/media/runtime-closure.ts +1063 -0
  272. package/src/media/source-router.ts +187 -0
  273. package/src/media/transcriber-config.ts +1122 -0
  274. package/src/media/transcript-persistence-model.ts +15 -0
  275. package/src/media/transcript-persistence-platform.ts +89 -0
  276. package/src/media/transcript-persistence-program.ts +39 -0
  277. package/src/media/transcript-persistence-runtime.ts +24 -0
  278. package/src/media/transcript.ts +616 -0
  279. package/src/media/utf8-order.ts +30 -0
  280. package/src/media/whisper-cpp.ts +464 -0
  281. package/src/media/whisper-language.ts +58 -0
  282. package/src/media/yt-dlp.ts +439 -0
  283. package/src/message-like-me-agentic-messaging.ts +882 -0
  284. package/src/messaging-action-store.ts +793 -0
  285. package/src/messaging-runtime.ts +2136 -0
  286. package/src/messaging-store.ts +539 -0
  287. package/src/messaging-types.ts +1663 -0
  288. package/src/messaging.ts +334 -0
  289. package/src/model.ts +2671 -0
  290. package/src/oauth-google.ts +888 -0
  291. package/src/omni-client-types.ts +306 -0
  292. package/src/omni-client.ts +2225 -0
  293. package/src/omni-limits.ts +34 -0
  294. package/src/omni-model.ts +1614 -0
  295. package/src/omni-request.ts +379 -0
  296. package/src/omni-runtime.ts +1632 -0
  297. package/src/operation-deadline.ts +189 -0
  298. package/src/path-helper.ts +1656 -0
  299. package/src/pinned-https.ts +194 -0
  300. package/src/plan-assets.ts +832 -0
  301. package/src/platform-catalog.ts +1832 -0
  302. package/src/plugins/beeper-linked-device/plugin.ts +310 -0
  303. package/src/plugins/bluesky-web/plugin.ts +178 -0
  304. package/src/plugins/clasificados-web/plugin.ts +66 -0
  305. package/src/plugins/github-web/plugin.ts +80 -0
  306. package/src/plugins/gmail-official/plugin.ts +110 -0
  307. package/src/plugins/hacker-news-web/plugin.ts +52 -0
  308. package/src/plugins/imessage-direct/plugin.ts +195 -0
  309. package/src/plugins/imessage-direct/vendor/0001-fix-keep-AppleScript-send-payloads-out-of-child-argv.patch +1430 -0
  310. package/src/plugins/imessage-direct/vendor/0002-feat-rpc-add-exact-chat-lookup.patch +225 -0
  311. package/src/plugins/imessage-direct/vendor/provenance.json +80 -0
  312. package/src/plugins/linkedin-official/plugin.ts +84 -0
  313. package/src/plugins/linkedin-web/plugin.ts +412 -0
  314. package/src/plugins/meta-web/plugin.ts +317 -0
  315. package/src/plugins/reddit-web/flair.ts +492 -0
  316. package/src/plugins/reddit-web/plugin.ts +477 -0
  317. package/src/plugins/substack-web/plugin.ts +124 -0
  318. package/src/plugins/tiktok-web/plugin.ts +67 -0
  319. package/src/plugins/twitch-web/plugin.ts +54 -0
  320. package/src/plugins/whatsapp-linked-device/plugin.ts +105 -0
  321. package/src/plugins/x-official/plugin.ts +64 -0
  322. package/src/plugins/x-web/plugin.ts +488 -0
  323. package/src/plugins/youtube-web/plugin.ts +101 -0
  324. package/src/portable-run-recovery.ts +466 -0
  325. package/src/process-identity.ts +501 -0
  326. package/src/profile-clone-helper.ts +104 -0
  327. package/src/provider-catalog-views.ts +51 -0
  328. package/src/provider-context.ts +69 -0
  329. package/src/provider-contract-definitions-x.ts +45 -0
  330. package/src/provider-contract-definitions.ts +948 -0
  331. package/src/provider-contract-input-gmail.ts +176 -0
  332. package/src/provider-contract-input-linkedin.ts +77 -0
  333. package/src/provider-contract-input-x.ts +304 -0
  334. package/src/provider-contract-planning.ts +23 -0
  335. package/src/provider-contract-semantic-identity.ts +54 -0
  336. package/src/provider-contracts.ts +251 -0
  337. package/src/provider-http.ts +723 -0
  338. package/src/provider-plugin-auth.ts +36 -0
  339. package/src/provider-plugin-builtins.ts +229 -0
  340. package/src/provider-plugin-cleanup-barrier.ts +229 -0
  341. package/src/provider-plugin-cleanup-execution.ts +79 -0
  342. package/src/provider-plugin-cleanup-resource.ts +470 -0
  343. package/src/provider-plugin-contract-identity.ts +707 -0
  344. package/src/provider-plugin-host.ts +1972 -0
  345. package/src/provider-plugin-identifiers.ts +60 -0
  346. package/src/provider-plugin-invocation-lease.ts +889 -0
  347. package/src/provider-plugin-lifecycle-kernel.ts +566 -0
  348. package/src/provider-plugin-lifecycle.ts +1455 -0
  349. package/src/provider-plugin-module-analysis.ts +336 -0
  350. package/src/provider-plugin-package.ts +2530 -0
  351. package/src/provider-plugin-portable-authority.ts +106 -0
  352. package/src/provider-plugin-portable-catalog.ts +94 -0
  353. package/src/provider-plugin-portable-identity.ts +315 -0
  354. package/src/provider-plugin-portable-runtime.ts +2057 -0
  355. package/src/provider-plugin-protocol.ts +1625 -0
  356. package/src/provider-plugin-registry.ts +3342 -0
  357. package/src/provider-plugin-store.ts +2155 -0
  358. package/src/provider-plugin-test-fixture.ts +5 -0
  359. package/src/provider-plugin.ts +4907 -0
  360. package/src/provider-plugins.generated.ts +43 -0
  361. package/src/provider-plugins.ts +8 -0
  362. package/src/provider-subject.ts +41 -0
  363. package/src/provider.ts +315 -0
  364. package/src/providers/beeper-local-runtime.ts +5426 -0
  365. package/src/providers/beeper-local.ts +2739 -0
  366. package/src/providers/beeper-messaging.ts +462 -0
  367. package/src/providers/beeper-omni.ts +1159 -0
  368. package/src/providers/bluesky-web-runtime.ts +2895 -0
  369. package/src/providers/bluesky-web.ts +1614 -0
  370. package/src/providers/clasificados-web-runtime.ts +255 -0
  371. package/src/providers/clasificados-web.ts +503 -0
  372. package/src/providers/contact-projection.ts +301 -0
  373. package/src/providers/github-read-model.ts +94 -0
  374. package/src/providers/github-read-platform.ts +165 -0
  375. package/src/providers/github-read-program.ts +97 -0
  376. package/src/providers/github-web-runtime.ts +320 -0
  377. package/src/providers/github-web.ts +403 -0
  378. package/src/providers/gmail-api.ts +2834 -0
  379. package/src/providers/gmail-omni.ts +830 -0
  380. package/src/providers/gmail.ts +1130 -0
  381. package/src/providers/hacker-news-web-runtime.ts +220 -0
  382. package/src/providers/hacker-news-web.ts +809 -0
  383. package/src/providers/imessage-direct-install.ts +354 -0
  384. package/src/providers/imessage-direct-messaging.ts +532 -0
  385. package/src/providers/imessage-direct-omni.ts +400 -0
  386. package/src/providers/imessage-direct-runtime.ts +1498 -0
  387. package/src/providers/imessage-direct.ts +351 -0
  388. package/src/providers/instagram-video-foundations.ts +913 -0
  389. package/src/providers/instagram-web-profile-browser.ts +600 -0
  390. package/src/providers/iso-bmff.ts +338 -0
  391. package/src/providers/linkedin-company-platform.ts +42 -0
  392. package/src/providers/linkedin-company-program.ts +88 -0
  393. package/src/providers/linkedin-contact-failure.ts +84 -0
  394. package/src/providers/linkedin-contact-platform.ts +45 -0
  395. package/src/providers/linkedin-contact-program.ts +91 -0
  396. package/src/providers/linkedin-profile-activity-failure.ts +76 -0
  397. package/src/providers/linkedin-profile-activity-platform.ts +41 -0
  398. package/src/providers/linkedin-profile-activity-program.ts +67 -0
  399. package/src/providers/linkedin-read-failure.ts +52 -0
  400. package/src/providers/linkedin-self-platform.ts +53 -0
  401. package/src/providers/linkedin-self-program.ts +122 -0
  402. package/src/providers/linkedin-web-article-browser.ts +794 -0
  403. package/src/providers/linkedin-web-bootstrap.ts +146 -0
  404. package/src/providers/linkedin-web-contact.ts +830 -0
  405. package/src/providers/linkedin-web-feed-browser.ts +832 -0
  406. package/src/providers/linkedin-web-feed.ts +864 -0
  407. package/src/providers/linkedin-web-post-browser.ts +653 -0
  408. package/src/providers/linkedin-web-profile-browser.ts +683 -0
  409. package/src/providers/linkedin-web-runtime.ts +2388 -0
  410. package/src/providers/linkedin-web.ts +3305 -0
  411. package/src/providers/linkedin.ts +1885 -0
  412. package/src/providers/meta-bootstrap.ts +1043 -0
  413. package/src/providers/meta-facebook-group.ts +690 -0
  414. package/src/providers/meta-marketplace-relay.ts +747 -0
  415. package/src/providers/meta-omni.ts +373 -0
  416. package/src/providers/meta-relay-bundle.ts +1049 -0
  417. package/src/providers/meta-web-descriptors.ts +1740 -0
  418. package/src/providers/meta-web-runtime.ts +3388 -0
  419. package/src/providers/meta-web.ts +2931 -0
  420. package/src/providers/read-failure.ts +139 -0
  421. package/src/providers/reddit-omni.ts +462 -0
  422. package/src/providers/reddit-web-runtime.ts +1929 -0
  423. package/src/providers/reddit-web.ts +1611 -0
  424. package/src/providers/rental-listings.ts +460 -0
  425. package/src/providers/substack-omni.ts +406 -0
  426. package/src/providers/substack-video-mp4.ts +33 -0
  427. package/src/providers/substack-web-runtime.ts +2812 -0
  428. package/src/providers/substack-web.ts +1502 -0
  429. package/src/providers/tiktok-video-mp4.ts +33 -0
  430. package/src/providers/tiktok-web-runtime.ts +770 -0
  431. package/src/providers/tiktok-web.ts +2786 -0
  432. package/src/providers/twitch-web-runtime.ts +226 -0
  433. package/src/providers/twitch-web.ts +274 -0
  434. package/src/providers/whatsapp-account-identity.ts +55 -0
  435. package/src/providers/whatsapp-contact-projection-helper.ts +874 -0
  436. package/src/providers/whatsapp-contact-projection-protocol.ts +403 -0
  437. package/src/providers/whatsapp-interaction-projection-helper.ts +1452 -0
  438. package/src/providers/whatsapp-interaction-projection-protocol.ts +462 -0
  439. package/src/providers/whatsapp-message-export-projection-protocol.ts +532 -0
  440. package/src/providers/whatsapp-messaging.ts +132 -0
  441. package/src/providers/whatsapp-omni.ts +586 -0
  442. package/src/providers/whatsapp-web-runtime.ts +3108 -0
  443. package/src/providers/whatsapp-web.ts +1037 -0
  444. package/src/providers/x-image-provenance.ts +523 -0
  445. package/src/providers/x-made-with-ai.ts +106 -0
  446. package/src/providers/x-transaction-id.ts +413 -0
  447. package/src/providers/x-web-runtime.ts +3485 -0
  448. package/src/providers/x-web.ts +2332 -0
  449. package/src/providers/x.ts +1889 -0
  450. package/src/providers/youtube-web-runtime.ts +1400 -0
  451. package/src/providers/youtube-web.ts +1221 -0
  452. package/src/read-admission-runtime.ts +91 -0
  453. package/src/read-client.ts +521 -0
  454. package/src/read-effect-platform.ts +7 -0
  455. package/src/read-effect-runtime.ts +14 -0
  456. package/src/read-effect.ts +52 -0
  457. package/src/read-projection-admission.ts +866 -0
  458. package/src/read-projections.ts +3151 -0
  459. package/src/recovery.ts +1492 -0
  460. package/src/reviewed-template.ts +146 -0
  461. package/src/run-journal.ts +1272 -0
  462. package/src/runtime.ts +5116 -0
  463. package/src/scripts/generate-provider-plugin-catalog.ts +121 -0
  464. package/src/scripts/install-whatsapp-protocol.sh +232 -0
  465. package/src/scripts/recover-install-stages.ts +1491 -0
  466. package/src/scripts/resolve-state-home.ts +30 -0
  467. package/src/scripts/scaffold-web-provider.ts +973 -0
  468. package/src/scripts/sync-bundled-adapters.ts +695 -0
  469. package/src/session-secrets.ts +1183 -0
  470. package/src/state-helper.bunfig.toml +2 -0
  471. package/src/state-helper.ts +2914 -0
  472. package/src/storage.ts +3842 -0
  473. package/src/transport-policy.ts +2 -0
  474. package/src/usage.ts +182 -0
  475. package/src/version.ts +2 -0
  476. package/src/web-session-authentication-policy.ts +136 -0
  477. package/src/web-session-cleanup-admission.ts +2344 -0
  478. package/src/web-session-cleanup-join.ts +99 -0
  479. package/src/web-session-client.ts +996 -0
  480. package/src/web-session-contract-definitions.ts +820 -0
  481. package/src/web-session-contract-planning.ts +42 -0
  482. package/src/web-session-contracts.ts +228 -0
  483. package/src/web-session-cookies.ts +500 -0
  484. package/src/web-session-execution.ts +385 -0
  485. package/src/web-session-http.ts +373 -0
  486. package/src/web-session-read-errors.ts +19 -0
  487. package/src/web-session-read-runtime.ts +124 -0
  488. package/src/web-session-recovery.ts +818 -0
  489. package/src/web-session-sites.ts +58 -0
  490. package/src/web-session-template.ts +994 -0
  491. package/src/web-session.ts +93 -0
  492. package/src/whatsapp-client-binding.ts +21 -0
  493. package/src/whatsapp-client-types.ts +75 -0
  494. package/src/whatsapp-client.ts +395 -0
  495. package/src/whatsapp-export-coordinate.ts +17 -0
  496. package/src/whatsapp-message-bundle-v2.ts +195 -0
  497. package/src/whatsapp-message-like-me-cli.ts +195 -0
  498. package/src/whatsapp-message-like-me-export.ts +1072 -0
  499. package/src/whatsapp-message-like-me-source.ts +1012 -0
  500. package/tsconfig.json +67 -0
@@ -0,0 +1,613 @@
1
+ import { chmod, lstat, mkdir, opendir, realpath } from "node:fs/promises";
2
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
3
+
4
+ import {
5
+ normalizeAudioForTranscription,
6
+ PCM_NORMALIZATION_MAX_BYTES,
7
+ PCM_NORMALIZATION_PROFILE,
8
+ PCM_NORMALIZATION_TIMEOUT_MS,
9
+ type NormalizeAudioForTranscriptionOptions,
10
+ type PcmNormalizationResult,
11
+ } from "./ffmpeg";
12
+ import {
13
+ createMediaArtifact,
14
+ type ArtifactRole,
15
+ type MediaArtifact,
16
+ type MediaLocalTranscriptProvenance,
17
+ } from "./manifest";
18
+ import {
19
+ reverifyReadyTranscriber,
20
+ reverifyReadyTranscriberAfterRun,
21
+ WHISPER_CPP_PROFILE,
22
+ type ReadyTranscriber,
23
+ type ReverifyReadyTranscriberOptions,
24
+ type ReverifyReadyTranscriberResult,
25
+ } from "./transcriber-config";
26
+ import {
27
+ parseRuntimeClosureRecord,
28
+ RUNTIME_CLOSURE_PROFILE,
29
+ sameRuntimeClosureRecord,
30
+ type RuntimeClosureAttestation,
31
+ } from "./runtime-closure";
32
+ import { validateTranscriptCues, type TranscriptCue } from "./transcript";
33
+ import {
34
+ normalizeWhisperCppLanguage,
35
+ runWhisperCpp,
36
+ type RunWhisperCppOptions,
37
+ type WhisperCppResult,
38
+ } from "./whisper-cpp";
39
+
40
+ const NORMALIZED_PCM_PATH = "input.wav" as const;
41
+ const WHISPER_DIRECTORY = "whisper" as const;
42
+ const PRIVATE_DIRECTORY_MODE = 0o700;
43
+ const SHA256_PATTERN = /^[0-9a-f]{64}$/u;
44
+ const MAX_PATH_CODE_UNITS = 4_096;
45
+
46
+ export type TranscribeAudioLocallyOptions = Readonly<{
47
+ audioPath: string;
48
+ audioArtifact: MediaArtifact;
49
+ attemptDirectory: string;
50
+ ffmpegExecutable: string;
51
+ requestedLanguage: string;
52
+ transcriber: ReadyTranscriber;
53
+ signal?: AbortSignal;
54
+ timeoutMs?: number;
55
+ }>;
56
+
57
+ export type LocalTranscriptionTranscript = Readonly<{
58
+ vtt: string;
59
+ text: string;
60
+ json: string;
61
+ cues: readonly TranscriptCue[];
62
+ }>;
63
+
64
+ export type LocalTranscriptionFailureStage =
65
+ | "cancelled"
66
+ | "preflight"
67
+ | "normalization"
68
+ | "hash"
69
+ | "transcriber";
70
+
71
+ export type LocalTranscriptionResult =
72
+ | Readonly<{
73
+ status: "transcribed";
74
+ language: string;
75
+ transcript: LocalTranscriptionTranscript;
76
+ provenance: MediaLocalTranscriptProvenance;
77
+ }>
78
+ | Readonly<{
79
+ status: "no-speech";
80
+ language: string;
81
+ provenance: MediaLocalTranscriptProvenance;
82
+ }>
83
+ | Readonly<{
84
+ status: "failed";
85
+ stage: LocalTranscriptionFailureStage;
86
+ diagnostic: string;
87
+ }>;
88
+
89
+ export type LocalTranscriptionDependencies = Readonly<{
90
+ normalizeAudioForTranscription: (
91
+ options: NormalizeAudioForTranscriptionOptions,
92
+ ) => Promise<PcmNormalizationResult>;
93
+ createMediaArtifact: (
94
+ itemRoot: string,
95
+ path: string,
96
+ role: ArtifactRole,
97
+ ) => Promise<MediaArtifact>;
98
+ runWhisperCpp: (options: RunWhisperCppOptions) => Promise<WhisperCppResult>;
99
+ reverifyReadyTranscriber: (
100
+ expected: ReadyTranscriber,
101
+ options?: ReverifyReadyTranscriberOptions,
102
+ ) => Promise<ReverifyReadyTranscriberResult>;
103
+ reverifyReadyTranscriberAfterRun: (
104
+ expected: ReadyTranscriber,
105
+ observedRuntimeClosure: RuntimeClosureAttestation,
106
+ ) => Promise<ReverifyReadyTranscriberResult>;
107
+ }>;
108
+
109
+ interface ParsedRequest {
110
+ readonly audioPath: string;
111
+ readonly audioArtifact: MediaArtifact & Readonly<{ role: "audio" }>;
112
+ readonly attemptDirectory: string;
113
+ readonly ffmpegExecutable: string;
114
+ readonly requestedLanguage: string;
115
+ readonly transcriber: ReadyTranscriber;
116
+ readonly signal: AbortSignal | undefined;
117
+ readonly timeoutMs: number | undefined;
118
+ }
119
+
120
+ interface AttemptSnapshot {
121
+ readonly root: string;
122
+ readonly dev: number;
123
+ readonly ino: number;
124
+ }
125
+
126
+ const defaultDependencies: LocalTranscriptionDependencies = {
127
+ normalizeAudioForTranscription,
128
+ createMediaArtifact,
129
+ runWhisperCpp,
130
+ reverifyReadyTranscriber: async (expected, options) =>
131
+ await reverifyReadyTranscriber(expected, undefined, options),
132
+ reverifyReadyTranscriberAfterRun,
133
+ };
134
+
135
+ const FAILURE_DIAGNOSTICS = {
136
+ cancelled: "The local transcription attempt was cancelled.",
137
+ preflight: "The local transcription request or private attempt directory is invalid.",
138
+ normalization: "Audio normalization did not produce Ghostget media's canonical PCM input.",
139
+ hash: "Ghostget media could not verify the normalized PCM input.",
140
+ transcriber: "The local whisper.cpp transcription attempt did not produce a valid result.",
141
+ } as const satisfies Readonly<Record<LocalTranscriptionFailureStage, string>>;
142
+
143
+ function failure(stage: LocalTranscriptionFailureStage): LocalTranscriptionResult {
144
+ return { status: "failed", stage, diagnostic: FAILURE_DIAGNOSTICS[stage] };
145
+ }
146
+
147
+ function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
148
+ return typeof value === "object" && value !== null && !Array.isArray(value);
149
+ }
150
+
151
+ function hasControlCharacter(value: string): boolean {
152
+ for (const character of value) {
153
+ const point = character.codePointAt(0);
154
+ if (point !== undefined && (point <= 0x1f || (point >= 0x7f && point <= 0x9f))) {
155
+ return true;
156
+ }
157
+ }
158
+ return false;
159
+ }
160
+
161
+ function isSafeAbsolutePath(value: unknown): value is string {
162
+ return typeof value === "string"
163
+ && value.length > 0
164
+ && value.length <= MAX_PATH_CODE_UNITS
165
+ && !hasControlCharacter(value)
166
+ && isAbsolute(value)
167
+ && resolve(value) === value;
168
+ }
169
+
170
+ function isSafeRelativeArtifactPath(value: unknown): value is string {
171
+ if (
172
+ typeof value !== "string"
173
+ || value.length === 0
174
+ || value.length > MAX_PATH_CODE_UNITS
175
+ || isAbsolute(value)
176
+ || value.includes("\\")
177
+ ) return false;
178
+ return value.split("/").every(
179
+ (segment) => segment.length > 0 && segment !== "." && segment !== ".." && !segment.includes("\0"),
180
+ );
181
+ }
182
+
183
+ function parseArtifact(value: unknown): (MediaArtifact & Readonly<{ role: "audio" }>) | null {
184
+ if (!isRecord(value)) return null;
185
+ const path = value["path"];
186
+ const bytes = value["bytes"];
187
+ const sha256 = value["sha256"];
188
+ const mediaType = value["mediaType"];
189
+ if (
190
+ value["role"] !== "audio"
191
+ || !isSafeRelativeArtifactPath(path)
192
+ || typeof bytes !== "number"
193
+ || !Number.isSafeInteger(bytes)
194
+ || bytes <= 0
195
+ || typeof sha256 !== "string"
196
+ || !SHA256_PATTERN.test(sha256)
197
+ || typeof mediaType !== "string"
198
+ || mediaType.length === 0
199
+ || mediaType.length > 256
200
+ ) return null;
201
+ return { role: "audio", path, bytes, sha256, mediaType };
202
+ }
203
+
204
+ function parseTranscriber(value: unknown): ReadyTranscriber | null {
205
+ if (!isRecord(value)) return null;
206
+ const descriptor = value["descriptor"];
207
+ if (!isRecord(descriptor)) return null;
208
+ const executablePath = value["executablePath"];
209
+ const modelPath = value["modelPath"];
210
+ const executableSha256 = descriptor["executableSha256"];
211
+ const modelSha256 = descriptor["modelSha256"];
212
+ const modelBytes = descriptor["modelBytes"];
213
+ const runtimeSha256 = descriptor["runtimeSha256"];
214
+ const runtimeDependencyCount = descriptor["runtimeDependencyCount"];
215
+ const runtimeClosure = parseRuntimeClosureRecord(value["runtimeClosure"]);
216
+ if (
217
+ !isSafeAbsolutePath(executablePath)
218
+ || !isSafeAbsolutePath(modelPath)
219
+ || descriptor["adapter"] !== "whisper-cpp"
220
+ || descriptor["profile"] !== WHISPER_CPP_PROFILE
221
+ || typeof executableSha256 !== "string"
222
+ || !SHA256_PATTERN.test(executableSha256)
223
+ || typeof modelSha256 !== "string"
224
+ || !SHA256_PATTERN.test(modelSha256)
225
+ || typeof modelBytes !== "number"
226
+ || !Number.isSafeInteger(modelBytes)
227
+ || modelBytes <= 0
228
+ || descriptor["runtimeProfile"] !== RUNTIME_CLOSURE_PROFILE
229
+ || typeof runtimeSha256 !== "string"
230
+ || !SHA256_PATTERN.test(runtimeSha256)
231
+ || typeof runtimeDependencyCount !== "number"
232
+ || !Number.isSafeInteger(runtimeDependencyCount)
233
+ || runtimeDependencyCount < 0
234
+ || !runtimeClosure.ok
235
+ || runtimeClosure.record.executableSha256 !== executableSha256
236
+ || runtimeClosure.record.closureSha256 !== runtimeSha256
237
+ || runtimeClosure.record.dependencyCount !== runtimeDependencyCount
238
+ ) return null;
239
+ return {
240
+ executablePath,
241
+ modelPath,
242
+ descriptor: {
243
+ adapter: "whisper-cpp",
244
+ profile: WHISPER_CPP_PROFILE,
245
+ executableSha256,
246
+ modelSha256,
247
+ modelBytes,
248
+ runtimeProfile: RUNTIME_CLOSURE_PROFILE,
249
+ runtimeSha256,
250
+ runtimeDependencyCount,
251
+ },
252
+ runtimeClosure: runtimeClosure.record,
253
+ };
254
+ }
255
+
256
+ function sameReadyTranscriber(
257
+ left: ReadyTranscriber,
258
+ right: ReadyTranscriber,
259
+ ): boolean {
260
+ return left.executablePath === right.executablePath
261
+ && left.modelPath === right.modelPath
262
+ && left.descriptor.adapter === right.descriptor.adapter
263
+ && left.descriptor.profile === right.descriptor.profile
264
+ && left.descriptor.executableSha256 === right.descriptor.executableSha256
265
+ && left.descriptor.modelSha256 === right.descriptor.modelSha256
266
+ && left.descriptor.modelBytes === right.descriptor.modelBytes
267
+ && left.descriptor.runtimeProfile === right.descriptor.runtimeProfile
268
+ && left.descriptor.runtimeSha256 === right.descriptor.runtimeSha256
269
+ && left.descriptor.runtimeDependencyCount === right.descriptor.runtimeDependencyCount
270
+ && sameRuntimeClosureRecord(left.runtimeClosure, right.runtimeClosure);
271
+ }
272
+
273
+ function parseRequest(value: unknown): ParsedRequest | null {
274
+ if (!isRecord(value)) return null;
275
+ const audioPath = value["audioPath"];
276
+ const attemptDirectory = value["attemptDirectory"];
277
+ const ffmpegExecutable = value["ffmpegExecutable"];
278
+ const requestedLanguage = normalizeWhisperCppLanguage(value["requestedLanguage"]);
279
+ const audioArtifact = parseArtifact(value["audioArtifact"]);
280
+ const transcriber = parseTranscriber(value["transcriber"]);
281
+ const signal = value["signal"];
282
+ const timeoutMs = value["timeoutMs"];
283
+ if (
284
+ !isSafeAbsolutePath(audioPath)
285
+ || !isSafeAbsolutePath(attemptDirectory)
286
+ || !isSafeAbsolutePath(ffmpegExecutable)
287
+ || requestedLanguage === null
288
+ || audioArtifact === null
289
+ || transcriber === null
290
+ || (signal !== undefined && !(signal instanceof AbortSignal))
291
+ || (
292
+ timeoutMs !== undefined
293
+ && (
294
+ typeof timeoutMs !== "number"
295
+ || !Number.isSafeInteger(timeoutMs)
296
+ || timeoutMs <= 0
297
+ || timeoutMs > PCM_NORMALIZATION_TIMEOUT_MS
298
+ )
299
+ )
300
+ ) return null;
301
+ return {
302
+ audioPath,
303
+ audioArtifact,
304
+ attemptDirectory,
305
+ ffmpegExecutable,
306
+ requestedLanguage,
307
+ transcriber,
308
+ signal,
309
+ timeoutMs,
310
+ };
311
+ }
312
+
313
+ function wasCancelled(request: ParsedRequest): boolean {
314
+ return request.signal?.aborted === true;
315
+ }
316
+
317
+ async function readDirectoryNames(path: string, limit: number): Promise<readonly string[]> {
318
+ const directory = await opendir(path);
319
+ const names: string[] = [];
320
+ try {
321
+ for (let count = 0; count < limit; count += 1) {
322
+ const entry = await directory.read();
323
+ if (entry === null) break;
324
+ names.push(entry.name);
325
+ }
326
+ } finally {
327
+ try {
328
+ await directory.close();
329
+ } catch {
330
+ // A failed close must not expose host diagnostics.
331
+ }
332
+ }
333
+ return names;
334
+ }
335
+
336
+ async function inspectFreshAttempt(path: string): Promise<AttemptSnapshot | null> {
337
+ try {
338
+ const metadata = await lstat(path);
339
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) return null;
340
+ if (process.platform !== "win32" && (metadata.mode & 0o777) !== PRIVATE_DIRECTORY_MODE) {
341
+ return null;
342
+ }
343
+ if (await realpath(path) !== path) return null;
344
+ if ((await readDirectoryNames(path, 1)).length !== 0) return null;
345
+ return { root: path, dev: metadata.dev, ino: metadata.ino };
346
+ } catch {
347
+ return null;
348
+ }
349
+ }
350
+
351
+ async function createWhisperDirectory(snapshot: AttemptSnapshot): Promise<string | null> {
352
+ const whisperDirectory = join(snapshot.root, WHISPER_DIRECTORY);
353
+ try {
354
+ await mkdir(whisperDirectory, { mode: PRIVATE_DIRECTORY_MODE });
355
+ await chmod(whisperDirectory, PRIVATE_DIRECTORY_MODE);
356
+ const [root, whisper, physicalWhisper, entries] = await Promise.all([
357
+ lstat(snapshot.root),
358
+ lstat(whisperDirectory),
359
+ realpath(whisperDirectory),
360
+ readDirectoryNames(snapshot.root, 2),
361
+ ]);
362
+ if (
363
+ !root.isDirectory()
364
+ || root.isSymbolicLink()
365
+ || root.dev !== snapshot.dev
366
+ || root.ino !== snapshot.ino
367
+ || !whisper.isDirectory()
368
+ || whisper.isSymbolicLink()
369
+ || physicalWhisper !== whisperDirectory
370
+ || (process.platform !== "win32" && (whisper.mode & 0o777) !== PRIVATE_DIRECTORY_MODE)
371
+ || entries.length !== 1
372
+ || entries[0] !== WHISPER_DIRECTORY
373
+ ) return null;
374
+ return whisperDirectory;
375
+ } catch {
376
+ return null;
377
+ }
378
+ }
379
+
380
+ function artifactRootForAudioPath(audioPath: string, artifactPath: string): string | null {
381
+ const segments = artifactPath.split("/");
382
+ let cursor = audioPath;
383
+ for (let index = segments.length - 1; index >= 0; index -= 1) {
384
+ const segment = segments[index];
385
+ if (segment === undefined || basename(cursor) !== segment) return null;
386
+ cursor = dirname(cursor);
387
+ }
388
+ return cursor;
389
+ }
390
+
391
+ function sameInputArtifact(expected: MediaArtifact, observed: MediaArtifact): boolean {
392
+ return observed.role === "audio"
393
+ && observed.path === expected.path
394
+ && observed.bytes === expected.bytes
395
+ && observed.sha256 === expected.sha256;
396
+ }
397
+
398
+ function validNormalizedArtifact(
399
+ artifact: MediaArtifact,
400
+ normalization: Extract<PcmNormalizationResult, { readonly status: "created" }>,
401
+ ): boolean {
402
+ return artifact.role === "audio"
403
+ && artifact.path === NORMALIZED_PCM_PATH
404
+ && artifact.bytes === normalization.bytes
405
+ && artifact.bytes > 0
406
+ && artifact.bytes <= PCM_NORMALIZATION_MAX_BYTES
407
+ && SHA256_PATTERN.test(artifact.sha256);
408
+ }
409
+
410
+ function provenance(
411
+ request: ParsedRequest,
412
+ transcriber: ReadyTranscriber,
413
+ normalizedArtifact: MediaArtifact,
414
+ ): MediaLocalTranscriptProvenance {
415
+ return {
416
+ adapter: "whisper-cpp",
417
+ profile: transcriber.descriptor.profile,
418
+ executableSha256: transcriber.descriptor.executableSha256,
419
+ runtimeProfile: transcriber.descriptor.runtimeProfile,
420
+ runtimeSha256: transcriber.descriptor.runtimeSha256,
421
+ runtimeDependencyCount: transcriber.descriptor.runtimeDependencyCount,
422
+ modelSha256: transcriber.descriptor.modelSha256,
423
+ requestedLanguage: request.requestedLanguage,
424
+ input: {
425
+ path: request.audioArtifact.path,
426
+ bytes: request.audioArtifact.bytes,
427
+ sha256: request.audioArtifact.sha256,
428
+ normalized: {
429
+ profile: PCM_NORMALIZATION_PROFILE,
430
+ bytes: normalizedArtifact.bytes,
431
+ sha256: normalizedArtifact.sha256,
432
+ },
433
+ },
434
+ };
435
+ }
436
+
437
+ function effectiveLanguage(detected: unknown, requested: string): string | null {
438
+ if (detected === null) return requested === "auto" ? null : requested;
439
+ const normalized = normalizeWhisperCppLanguage(detected);
440
+ return normalized === null || normalized === "auto" ? null : normalized;
441
+ }
442
+
443
+ function completedResult(
444
+ value: unknown,
445
+ request: ParsedRequest,
446
+ attemptProvenance: MediaLocalTranscriptProvenance,
447
+ ): LocalTranscriptionResult {
448
+ if (!isRecord(value) || value["ok"] !== true) return failure("transcriber");
449
+ if (value["status"] === "no-speech") {
450
+ const language = effectiveLanguage(value["language"], request.requestedLanguage);
451
+ return language === null
452
+ ? failure("transcriber")
453
+ : { status: "no-speech", language, provenance: attemptProvenance };
454
+ }
455
+ if (value["status"] !== "transcribed") return failure("transcriber");
456
+ const transcript = value["transcript"];
457
+ if (!isRecord(transcript)) return failure("transcriber");
458
+ const language = effectiveLanguage(transcript["language"], request.requestedLanguage);
459
+ if (language === null) return failure("transcriber");
460
+ const validated = validateTranscriptCues(transcript["cues"]);
461
+ if (
462
+ !validated.ok
463
+ || transcript["vtt"] !== validated.vtt
464
+ || transcript["text"] !== validated.text
465
+ || transcript["json"] !== validated.json
466
+ ) return failure("transcriber");
467
+ return {
468
+ status: "transcribed",
469
+ language,
470
+ transcript: {
471
+ vtt: validated.vtt,
472
+ text: validated.text,
473
+ json: validated.json,
474
+ cues: validated.cues,
475
+ },
476
+ provenance: attemptProvenance,
477
+ };
478
+ }
479
+
480
+ /**
481
+ * Runs one private, path-free local transcription attempt. The caller owns the
482
+ * attempt directory and decides whether to retain or discard its contents.
483
+ */
484
+ export async function transcribeAudioLocally(
485
+ options: TranscribeAudioLocallyOptions,
486
+ dependencies: LocalTranscriptionDependencies = defaultDependencies,
487
+ ): Promise<LocalTranscriptionResult> {
488
+ let request: ParsedRequest | null;
489
+ try {
490
+ request = parseRequest(options);
491
+ } catch {
492
+ return failure("preflight");
493
+ }
494
+ if (request === null) return failure("preflight");
495
+ if (wasCancelled(request)) return failure("cancelled");
496
+
497
+ const artifactRoot = artifactRootForAudioPath(
498
+ request.audioPath,
499
+ request.audioArtifact.path,
500
+ );
501
+ if (artifactRoot === null) return failure("preflight");
502
+ const attempt = await inspectFreshAttempt(request.attemptDirectory);
503
+ if (attempt === null) return failure("preflight");
504
+ if (wasCancelled(request)) return failure("cancelled");
505
+
506
+ let observedInput: MediaArtifact;
507
+ try {
508
+ observedInput = await dependencies.createMediaArtifact(
509
+ artifactRoot,
510
+ request.audioArtifact.path,
511
+ "audio",
512
+ );
513
+ } catch {
514
+ return failure("preflight");
515
+ }
516
+ if (wasCancelled(request)) return failure("cancelled");
517
+ if (!sameInputArtifact(request.audioArtifact, observedInput)) return failure("preflight");
518
+
519
+ const whisperDirectory = await createWhisperDirectory(attempt);
520
+ if (whisperDirectory === null) return failure("preflight");
521
+ const normalizedPath = join(attempt.root, NORMALIZED_PCM_PATH);
522
+
523
+ let normalization: PcmNormalizationResult;
524
+ try {
525
+ normalization = await dependencies.normalizeAudioForTranscription({
526
+ inputPath: request.audioPath,
527
+ outputPath: normalizedPath,
528
+ ffmpegExecutable: request.ffmpegExecutable,
529
+ ...(request.signal === undefined ? {} : { signal: request.signal }),
530
+ ...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
531
+ });
532
+ } catch {
533
+ return wasCancelled(request) ? failure("cancelled") : failure("normalization");
534
+ }
535
+ if (wasCancelled(request)) return failure("cancelled");
536
+ if (
537
+ normalization.status !== "created"
538
+ || normalization.path !== normalizedPath
539
+ || normalization.profile !== PCM_NORMALIZATION_PROFILE
540
+ || !Number.isSafeInteger(normalization.bytes)
541
+ || normalization.bytes <= 0
542
+ || normalization.bytes > PCM_NORMALIZATION_MAX_BYTES
543
+ ) return failure("normalization");
544
+
545
+ let normalizedArtifact: MediaArtifact;
546
+ try {
547
+ normalizedArtifact = await dependencies.createMediaArtifact(
548
+ attempt.root,
549
+ NORMALIZED_PCM_PATH,
550
+ "audio",
551
+ );
552
+ } catch {
553
+ return failure("hash");
554
+ }
555
+ if (wasCancelled(request)) return failure("cancelled");
556
+ if (!validNormalizedArtifact(normalizedArtifact, normalization)) return failure("hash");
557
+
558
+ let preRunVerification: ReverifyReadyTranscriberResult;
559
+ try {
560
+ preRunVerification = await dependencies.reverifyReadyTranscriber(
561
+ request.transcriber,
562
+ request.signal === undefined ? {} : { signal: request.signal },
563
+ );
564
+ } catch {
565
+ return wasCancelled(request) ? failure("cancelled") : failure("transcriber");
566
+ }
567
+ if (wasCancelled(request)) return failure("cancelled");
568
+ if (
569
+ preRunVerification.kind !== "ready"
570
+ || !sameReadyTranscriber(preRunVerification.transcriber, request.transcriber)
571
+ ) return failure("transcriber");
572
+ const preRunTranscriber = preRunVerification.transcriber;
573
+
574
+ let result: WhisperCppResult;
575
+ try {
576
+ result = await dependencies.runWhisperCpp({
577
+ executable: preRunTranscriber.executablePath,
578
+ modelPath: preRunTranscriber.modelPath,
579
+ pcmPath: normalizedPath,
580
+ requestedLanguage: request.requestedLanguage,
581
+ workDirectory: whisperDirectory,
582
+ runtimeClosure: preRunTranscriber.runtimeClosure,
583
+ ...(request.signal === undefined ? {} : { signal: request.signal }),
584
+ ...(request.timeoutMs === undefined ? {} : { timeoutMs: request.timeoutMs }),
585
+ });
586
+ } catch {
587
+ return wasCancelled(request) ? failure("cancelled") : failure("transcriber");
588
+ }
589
+ if (wasCancelled(request)) return failure("cancelled");
590
+ if (!result.ok) return failure("transcriber");
591
+
592
+ let postRunVerification: ReverifyReadyTranscriberResult;
593
+ try {
594
+ postRunVerification = await dependencies.reverifyReadyTranscriberAfterRun(
595
+ preRunTranscriber,
596
+ result.runtimeClosure,
597
+ );
598
+ } catch {
599
+ return failure("transcriber");
600
+ }
601
+ if (wasCancelled(request)) return failure("cancelled");
602
+ if (
603
+ postRunVerification.kind !== "ready"
604
+ || !sameReadyTranscriber(postRunVerification.transcriber, preRunTranscriber)
605
+ ) return failure("transcriber");
606
+
607
+ const attemptProvenance = provenance(
608
+ request,
609
+ postRunVerification.transcriber,
610
+ normalizedArtifact,
611
+ );
612
+ return completedResult(result, request, attemptProvenance);
613
+ }