@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,338 @@
1
+ type IsoBox = Readonly<{
2
+ end: number;
3
+ payloadStart: number;
4
+ type: string;
5
+ }>;
6
+
7
+ type IsoBoxBudget = {
8
+ remaining: number;
9
+ };
10
+
11
+ const MAX_REVIEWED_ISO_BOXES = 4_096;
12
+ const ISO_BMFF_UNKNOWN_DURATION_V0 = 0xffff_ffffn;
13
+ const ISO_BMFF_UNKNOWN_DURATION_V1 = 0xffff_ffff_ffff_ffffn;
14
+ const IDENTITY_TRACK_MATRIX = Object.freeze([
15
+ 0x0001_0000,
16
+ 0,
17
+ 0,
18
+ 0,
19
+ 0x0001_0000,
20
+ 0,
21
+ 0,
22
+ 0,
23
+ 0x4000_0000,
24
+ ] as const);
25
+
26
+ function isoBoxType(bytes: Uint8Array, offset: number): string {
27
+ return String.fromCharCode(
28
+ bytes[offset] ?? 0,
29
+ bytes[offset + 1] ?? 0,
30
+ bytes[offset + 2] ?? 0,
31
+ bytes[offset + 3] ?? 0,
32
+ );
33
+ }
34
+
35
+ function isoBoxes(
36
+ bytes: Uint8Array,
37
+ start: number,
38
+ end: number,
39
+ label: string,
40
+ budget: IsoBoxBudget,
41
+ ): readonly IsoBox[] {
42
+ if (
43
+ !Number.isSafeInteger(start)
44
+ || !Number.isSafeInteger(end)
45
+ || start < 0
46
+ || end > bytes.byteLength
47
+ || start > end
48
+ ) throw new Error(`${label} has invalid ISO BMFF bounds`);
49
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
50
+ const boxes: IsoBox[] = [];
51
+ let offset = start;
52
+ while (offset < end) {
53
+ if (budget.remaining === 0) {
54
+ throw new Error(`${label} exceeds the reviewed ISO BMFF box-count bound`);
55
+ }
56
+ budget.remaining -= 1;
57
+ if (end - offset < 8) {
58
+ throw new Error(`${label} contains a truncated ISO BMFF box`);
59
+ }
60
+ const size32 = view.getUint32(offset);
61
+ const type = isoBoxType(bytes, offset + 4);
62
+ if (!/^[\x20-\x7e]{4}$/u.test(type)) {
63
+ throw new Error(`${label} contains an invalid ISO BMFF box type`);
64
+ }
65
+ let headerBytes = 8;
66
+ let size = size32;
67
+ if (size32 === 1) {
68
+ if (end - offset < 16) {
69
+ throw new Error(`${label} contains a truncated extended-size ISO BMFF box`);
70
+ }
71
+ const extendedSize = view.getBigUint64(offset + 8);
72
+ if (extendedSize > BigInt(Number.MAX_SAFE_INTEGER)) {
73
+ throw new Error(`${label} contains an oversized ISO BMFF box`);
74
+ }
75
+ size = Number(extendedSize);
76
+ headerBytes = 16;
77
+ } else if (size32 === 0) {
78
+ size = end - offset;
79
+ }
80
+ if (size < headerBytes || size > end - offset) {
81
+ throw new Error(`${label} contains an invalid ISO BMFF box size`);
82
+ }
83
+ boxes.push(Object.freeze({
84
+ end: offset + size,
85
+ payloadStart: offset + headerBytes,
86
+ type,
87
+ }));
88
+ offset += size;
89
+ }
90
+ if (offset !== end) throw new Error(`${label} contains trailing ISO BMFF bytes`);
91
+ return Object.freeze(boxes);
92
+ }
93
+
94
+ function oneIsoBox(
95
+ boxes: readonly IsoBox[],
96
+ type: string,
97
+ label: string,
98
+ ): IsoBox {
99
+ const matches = boxes.filter((box) => box.type === type);
100
+ if (matches.length !== 1) {
101
+ throw new Error(`${label} must contain exactly one ${type} box`);
102
+ }
103
+ return matches[0]!;
104
+ }
105
+
106
+ /**
107
+ * Validate one complete ISO BMFF file and return the dimensions from its one
108
+ * reviewed video track. This is deliberately a structural validator, not a
109
+ * codec or transcoder; the provider remains authoritative for media support.
110
+ */
111
+ export function isoBmffVideoDimensions(
112
+ bytes: Uint8Array,
113
+ label: string,
114
+ ): Readonly<{ height: number; width: number }> {
115
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
116
+ const budget: IsoBoxBudget = { remaining: MAX_REVIEWED_ISO_BOXES };
117
+ const topLevel = isoBoxes(bytes, 0, bytes.byteLength, label, budget);
118
+ if (
119
+ topLevel[0]?.type !== "ftyp"
120
+ || topLevel[0].end - topLevel[0].payloadStart < 8
121
+ ) throw new Error(`${label} must begin with one bounded ISO BMFF file-type box`);
122
+ const moov = oneIsoBox(topLevel, "moov", label);
123
+ const movieBoxes = isoBoxes(
124
+ bytes,
125
+ moov.payloadStart,
126
+ moov.end,
127
+ `${label} movie box`,
128
+ budget,
129
+ );
130
+ const dimensions: { height: number; width: number }[] = [];
131
+ for (const track of movieBoxes.filter((box) => box.type === "trak")) {
132
+ const trackBoxes = isoBoxes(
133
+ bytes,
134
+ track.payloadStart,
135
+ track.end,
136
+ `${label} track`,
137
+ budget,
138
+ );
139
+ const media = oneIsoBox(trackBoxes, "mdia", `${label} track`);
140
+ const mediaBoxes = isoBoxes(
141
+ bytes,
142
+ media.payloadStart,
143
+ media.end,
144
+ `${label} media box`,
145
+ budget,
146
+ );
147
+ const handler = oneIsoBox(mediaBoxes, "hdlr", `${label} media box`);
148
+ if (handler.end - handler.payloadStart < 12) {
149
+ throw new Error(`${label} media handler is truncated`);
150
+ }
151
+ if (isoBoxType(bytes, handler.payloadStart + 8) !== "vide") continue;
152
+ const trackHeader = oneIsoBox(trackBoxes, "tkhd", `${label} track`);
153
+ const payloadBytes = trackHeader.end - trackHeader.payloadStart;
154
+ const version = bytes[trackHeader.payloadStart];
155
+ const dimensionOffset = version === 0 ? 76 : version === 1 ? 88 : -1;
156
+ const expectedPayloadBytes = version === 0 ? 84 : version === 1 ? 96 : -1;
157
+ if (dimensionOffset < 0 || payloadBytes !== expectedPayloadBytes) {
158
+ throw new Error(`${label} track header changed from the reviewed shape`);
159
+ }
160
+ const matrixOffset = trackHeader.payloadStart + dimensionOffset - 36;
161
+ for (const [index, expected] of IDENTITY_TRACK_MATRIX.entries()) {
162
+ if (view.getUint32(matrixOffset + index * 4) !== expected) {
163
+ throw new Error(`${label} track header has an unsupported transform matrix`);
164
+ }
165
+ }
166
+ const widthFixed = view.getUint32(trackHeader.payloadStart + dimensionOffset);
167
+ const heightFixed = view.getUint32(trackHeader.payloadStart + dimensionOffset + 4);
168
+ if ((widthFixed & 0xffff) !== 0 || (heightFixed & 0xffff) !== 0) {
169
+ throw new Error(`${label} dimensions must be whole pixels`);
170
+ }
171
+ const width = widthFixed >>> 16;
172
+ const height = heightFixed >>> 16;
173
+ if (width < 1 || height < 1 || width > 20_000 || height > 20_000) {
174
+ throw new Error(`${label} dimensions are outside the reviewed bound`);
175
+ }
176
+ dimensions.push({ height, width });
177
+ }
178
+ if (dimensions.length !== 1) {
179
+ throw new Error(`${label} must contain exactly one reviewed video track`);
180
+ }
181
+ return Object.freeze(dimensions[0]!);
182
+ }
183
+
184
+ export type IsoBmffMp4CompatibilityPolicy = Readonly<{
185
+ compatibleBrands: readonly string[];
186
+ rejectedMajorBrands: readonly string[];
187
+ }>;
188
+
189
+ function isoBmffBrandSet(
190
+ brands: readonly string[],
191
+ label: string,
192
+ allowEmpty: boolean,
193
+ ): ReadonlySet<string> {
194
+ if (
195
+ !Array.isArray(brands)
196
+ || (!allowEmpty && brands.length < 1)
197
+ || brands.some((brand) => (
198
+ typeof brand !== "string"
199
+ || !/^[\x20-\x7e]{4}$/u.test(brand)
200
+ ))
201
+ || new Set(brands).size !== brands.length
202
+ ) throw new Error(`${label} MP4 compatibility policy is invalid`);
203
+ return new Set(brands);
204
+ }
205
+
206
+ /**
207
+ * Validate the provider's explicit MP4 brand policy and project the duration
208
+ * and dimensions from one reviewed video track. This remains structural media
209
+ * validation: each provider owns the brands it is prepared to submit.
210
+ */
211
+ export function isoBmffMp4VideoMetadata(
212
+ bytes: Uint8Array,
213
+ label: string,
214
+ policy: IsoBmffMp4CompatibilityPolicy,
215
+ ): Readonly<{
216
+ durationSeconds: number;
217
+ height: number;
218
+ width: number;
219
+ }> {
220
+ if (
221
+ !(bytes instanceof Uint8Array)
222
+ || (
223
+ typeof SharedArrayBuffer !== "undefined"
224
+ && bytes.buffer instanceof SharedArrayBuffer
225
+ )
226
+ || bytes.byteLength < 24
227
+ ) throw new Error(`${label} must be one complete MP4 file`);
228
+ const compatibleBrands = isoBmffBrandSet(
229
+ policy.compatibleBrands,
230
+ label,
231
+ false,
232
+ );
233
+ const rejectedMajorBrands = isoBmffBrandSet(
234
+ policy.rejectedMajorBrands,
235
+ label,
236
+ true,
237
+ );
238
+ if ([...compatibleBrands].some((brand) => rejectedMajorBrands.has(brand))) {
239
+ throw new Error(`${label} MP4 compatibility policy is contradictory`);
240
+ }
241
+
242
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
243
+ const fileTypeBytes = view.getUint32(0);
244
+ if (
245
+ fileTypeBytes < 16
246
+ || fileTypeBytes > bytes.byteLength
247
+ || isoBoxType(bytes, 4) !== "ftyp"
248
+ || (fileTypeBytes - 16) % 4 !== 0
249
+ ) throw new Error(`${label} must begin with one bounded MP4 file-type box`);
250
+ const budget: IsoBoxBudget = { remaining: MAX_REVIEWED_ISO_BOXES };
251
+ const topLevel = isoBoxes(bytes, 0, bytes.byteLength, label, budget);
252
+ const fileTypes = topLevel.filter((box) => box.type === "ftyp");
253
+ const fileType = fileTypes[0];
254
+ if (
255
+ topLevel[0] !== fileType
256
+ || fileTypes.length !== 1
257
+ || fileType === undefined
258
+ || fileType.end !== fileTypeBytes
259
+ ) throw new Error(`${label} must begin with one bounded MP4 file-type box`);
260
+ const majorBrand = isoBoxType(bytes, fileType.payloadStart);
261
+ const declaredBrands = [majorBrand];
262
+ for (
263
+ let offset = fileType.payloadStart + 8;
264
+ offset < fileType.end;
265
+ offset += 4
266
+ ) declaredBrands.push(isoBoxType(bytes, offset));
267
+ if (
268
+ rejectedMajorBrands.has(majorBrand)
269
+ || !declaredBrands.some((brand) => compatibleBrands.has(brand))
270
+ ) throw new Error(`${label} file-type box is not MP4-compatible`);
271
+
272
+ const dimensions = isoBmffVideoDimensions(bytes, label);
273
+ const movie = oneIsoBox(topLevel, "moov", label);
274
+ const movieBoxes = isoBoxes(
275
+ bytes,
276
+ movie.payloadStart,
277
+ movie.end,
278
+ `${label} movie box`,
279
+ budget,
280
+ );
281
+ const durations: number[] = [];
282
+ for (const track of movieBoxes.filter((box) => box.type === "trak")) {
283
+ const trackBoxes = isoBoxes(
284
+ bytes,
285
+ track.payloadStart,
286
+ track.end,
287
+ `${label} track`,
288
+ budget,
289
+ );
290
+ const media = oneIsoBox(trackBoxes, "mdia", `${label} track`);
291
+ const mediaBoxes = isoBoxes(
292
+ bytes,
293
+ media.payloadStart,
294
+ media.end,
295
+ `${label} media box`,
296
+ budget,
297
+ );
298
+ const handler = oneIsoBox(mediaBoxes, "hdlr", `${label} media box`);
299
+ if (handler.end - handler.payloadStart < 12) {
300
+ throw new Error(`${label} media handler is truncated`);
301
+ }
302
+ if (isoBoxType(bytes, handler.payloadStart + 8) !== "vide") continue;
303
+ const mediaHeader = oneIsoBox(mediaBoxes, "mdhd", `${label} media box`);
304
+ const payloadBytes = mediaHeader.end - mediaHeader.payloadStart;
305
+ const version = bytes[mediaHeader.payloadStart];
306
+ const timescaleOffset = version === 0 ? 12 : version === 1 ? 20 : -1;
307
+ const durationOffset = version === 0 ? 16 : version === 1 ? 24 : -1;
308
+ const expectedPayloadBytes = version === 0 ? 24 : version === 1 ? 36 : -1;
309
+ if (
310
+ timescaleOffset < 0
311
+ || durationOffset < 0
312
+ || payloadBytes !== expectedPayloadBytes
313
+ ) throw new Error(`${label} media header changed from the reviewed shape`);
314
+ const timescale = view.getUint32(mediaHeader.payloadStart + timescaleOffset);
315
+ if (timescale < 1) throw new Error(`${label} media timescale must be positive`);
316
+ const durationUnits = version === 0
317
+ ? BigInt(view.getUint32(mediaHeader.payloadStart + durationOffset))
318
+ : view.getBigUint64(mediaHeader.payloadStart + durationOffset);
319
+ const unknownDuration = version === 0
320
+ ? ISO_BMFF_UNKNOWN_DURATION_V0
321
+ : ISO_BMFF_UNKNOWN_DURATION_V1;
322
+ if (durationUnits === unknownDuration) {
323
+ throw new Error(`${label} media duration is the ISO BMFF unknown-duration sentinel`);
324
+ }
325
+ if (durationUnits < 1n || durationUnits > BigInt(Number.MAX_SAFE_INTEGER)) {
326
+ throw new Error(`${label} media duration is outside the reviewed bound`);
327
+ }
328
+ const durationSeconds = Number(durationUnits) / timescale;
329
+ if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) {
330
+ throw new Error(`${label} media duration is invalid`);
331
+ }
332
+ durations.push(durationSeconds);
333
+ }
334
+ if (durations.length !== 1) {
335
+ throw new Error(`${label} must contain exactly one reviewed video duration`);
336
+ }
337
+ return Object.freeze({ durationSeconds: durations[0]!, ...dimensions });
338
+ }
@@ -0,0 +1,42 @@
1
+ import * as Context from "effect/Context";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import { readAttempt } from "../read-effect";
5
+ import { readNative } from "../read-effect-platform";
6
+ import type { WebSessionClient } from "../web-session-client";
7
+ import type { LinkedInProfileBrowserTransport } from "./linkedin-web-profile-browser";
8
+
9
+ export type LinkedInCompanyIdentity = {
10
+ readonly subject: string;
11
+ };
12
+
13
+ export type LinkedInCompanyNative = {
14
+ readonly openBrowser: () => Promise<LinkedInProfileBrowserTransport>;
15
+ readonly openDirect: () => Promise<WebSessionClient>;
16
+ readonly directIdentity: (client: WebSessionClient) => Promise<LinkedInCompanyIdentity>;
17
+ readonly decodeIdentity: (value: unknown) => LinkedInCompanyIdentity;
18
+ readonly bindIdentity: (identity: LinkedInCompanyIdentity) => string;
19
+ readonly readCompany: (client: WebSessionClient, url: string) => Promise<string>;
20
+ readonly observedAt: () => string;
21
+ };
22
+
23
+ function platform(ports: LinkedInCompanyNative) {
24
+ return {
25
+ openBrowser: readNative(ports.openBrowser),
26
+ closeBrowser: (browser: LinkedInProfileBrowserTransport) => readNative(() => browser.close()),
27
+ browserIdentity: (browser: LinkedInProfileBrowserTransport) => readNative(() => browser.currentIdentityResponse()).pipe(
28
+ Effect.flatMap(value => readAttempt(() => ports.decodeIdentity(value))),
29
+ ),
30
+ browserCompany: (browser: LinkedInProfileBrowserTransport, url: string) => readNative(() => browser.readOrganizationHtml(url)),
31
+ openDirect: readNative(ports.openDirect),
32
+ directIdentity: (client: WebSessionClient) => readNative(() => ports.directIdentity(client)),
33
+ bindIdentity: (identity: LinkedInCompanyIdentity) => readAttempt(() => ports.bindIdentity(identity)),
34
+ directCompany: (client: WebSessionClient, url: string) => readNative(() => ports.readCompany(client, url)),
35
+ observedAt: readAttempt(ports.observedAt),
36
+ };
37
+ }
38
+
39
+ export class LinkedInCompanyPlatform extends Context.Tag("wrench/LinkedInCompanyPlatform/v1")<LinkedInCompanyPlatform, ReturnType<typeof platform>>() { }
40
+
41
+ export const LinkedInCompanyPlatformLive = (ports: LinkedInCompanyNative): Layer.Layer<LinkedInCompanyPlatform> =>
42
+ Layer.succeed(LinkedInCompanyPlatform, platform(ports));
@@ -0,0 +1,88 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Either from "effect/Either";
3
+ import { PreservedBrowserArtifactsError } from "../browser";
4
+ import { readAttempt, type ReadEffectFailure, withReadResource } from "../read-effect";
5
+ import { readFailureProjection, type WebSessionExecution } from "../web-session-execution";
6
+ import { LinkedInCompanyPlatform, type LinkedInCompanyIdentity } from "./linkedin-company-platform";
7
+ import {
8
+ linkedInProfileIdentityAllowsBrowserFallback,
9
+ linkedInProfileReadFailure,
10
+ } from "./linkedin-read-failure";
11
+ import { projectLinkedInOrganizationStats, type LinkedInProfileTarget } from "./linkedin-web";
12
+
13
+ /** The signed-in member authorizes the read; the company target is independently
14
+ * bound by the pure projection and need not match the member's personal slug. */
15
+ export function linkedInCompanyReadProgram(
16
+ target: LinkedInProfileTarget,
17
+ preferBrowser: boolean,
18
+ diagnostic: (error: unknown, requestStage: string) => string,
19
+ ): Effect.Effect<WebSessionExecution, ReadEffectFailure, LinkedInCompanyPlatform> {
20
+ return Effect.gen(function*() {
21
+ const platform = yield* LinkedInCompanyPlatform;
22
+ let requestStage = preferBrowser
23
+ ? "contained-browser signed-in identity preflight"
24
+ : "signed-in identity preflight";
25
+ let identityMismatch = false;
26
+ const report = (error: ReadEffectFailure): Effect.Effect<WebSessionExecution, ReadEffectFailure> => {
27
+ if (error.cause instanceof PreservedBrowserArtifactsError) return Effect.fail(error);
28
+ return Effect.succeed({
29
+ status: "failed",
30
+ output: null,
31
+ finalUrl: target.url,
32
+ dispatchStarted: false,
33
+ dispatch: { planned: 0, started: 0, verified: 0 },
34
+ error: diagnostic(error.cause, requestStage),
35
+ readFailure: identityMismatch
36
+ ? readFailureProjection("account-mismatch")
37
+ : linkedInProfileReadFailure(error.cause),
38
+ });
39
+ };
40
+ const readBoundCompany = (
41
+ identity: LinkedInCompanyIdentity,
42
+ browser: boolean,
43
+ company: Effect.Effect<string, ReadEffectFailure>,
44
+ ): Effect.Effect<WebSessionExecution, ReadEffectFailure> => Effect.gen(function*() {
45
+ yield* platform.bindIdentity(identity).pipe(Effect.tapError(() => Effect.sync(() => { identityMismatch = true; })));
46
+ requestStage = browser ? "contained-browser public company page read" : "public company page read";
47
+ const html = yield* company;
48
+ requestStage = "exact company metric projection";
49
+ const observedAt = yield* platform.observedAt;
50
+ const output = yield* readAttempt(() => projectLinkedInOrganizationStats({
51
+ html,
52
+ organizationUrl: target.url,
53
+ observedAt,
54
+ }));
55
+ return {
56
+ status: "succeeded",
57
+ output,
58
+ finalUrl: target.url,
59
+ dispatchStarted: false,
60
+ dispatch: { planned: 0, started: 0, verified: 0 },
61
+ };
62
+ });
63
+ const browserRead = (): Effect.Effect<WebSessionExecution, ReadEffectFailure> => {
64
+ let closeFailed = false;
65
+ return withReadResource(
66
+ platform.openBrowser,
67
+ browser => platform.browserIdentity(browser).pipe(Effect.flatMap(identity => readBoundCompany(
68
+ identity, true, platform.browserCompany(browser, target.url),
69
+ ))),
70
+ browser => platform.closeBrowser(browser).pipe(Effect.tapError(() => Effect.sync(() => { closeFailed = true; }))),
71
+ ).pipe(Effect.catchTag("ReadEffectFailure", error => closeFailed ? Effect.fail(error) : report(error)));
72
+ };
73
+ if (preferBrowser) return yield* browserRead();
74
+ const direct = yield* Effect.either(platform.openDirect);
75
+ if (Either.isLeft(direct)) return yield* report(direct.left);
76
+ const identity = yield* Effect.either(platform.directIdentity(direct.right));
77
+ if (Either.isLeft(identity)) {
78
+ if (linkedInProfileIdentityAllowsBrowserFallback(identity.left.cause)) {
79
+ requestStage = "contained-browser signed-in identity preflight";
80
+ return yield* browserRead();
81
+ }
82
+ return yield* report(identity.left);
83
+ }
84
+ return yield* readBoundCompany(
85
+ identity.right, false, platform.directCompany(direct.right, target.url),
86
+ ).pipe(Effect.catchTag("ReadEffectFailure", report));
87
+ });
88
+ }
@@ -0,0 +1,84 @@
1
+ import { OperationDeadlineError } from "../operation-deadline";
2
+ import { readFailureProjection, type ReadFailureProjection } from "../web-session-execution";
3
+ import {
4
+ LinkedInProfileBrowserFailure,
5
+ LinkedInProfileBrowserResponseRejectedError,
6
+ } from "./linkedin-web-profile-browser";
7
+
8
+ export type LinkedInContactStage = "identity" | "profile" | "contact" | "projection";
9
+
10
+ /** Only the account-binding producer creates this category. Native wording is
11
+ * diagnostic data and cannot authorize account mismatch or auth repair. */
12
+ export class LinkedInContactIdentityMismatch extends Error {
13
+ constructor(cause: unknown) {
14
+ super("LinkedIn current member no longer matches the bound auth subject", { cause });
15
+ this.name = "LinkedInContactIdentityMismatch";
16
+ }
17
+ }
18
+
19
+ export function linkedInContactReadFailure(
20
+ error: unknown,
21
+ stage: LinkedInContactStage,
22
+ ): ReadFailureProjection {
23
+ let deadlineCause: unknown = error;
24
+ for (let depth = 0; depth < 8 && deadlineCause !== undefined; depth += 1) {
25
+ if (deadlineCause instanceof OperationDeadlineError) {
26
+ return readFailureProjection(deadlineCause.failure === "timed-out" ? "operation-timeout" : "contract-drift");
27
+ }
28
+ deadlineCause = deadlineCause instanceof Error ? deadlineCause.cause : undefined;
29
+ }
30
+ let current: unknown = error;
31
+ for (let depth = 0; depth < 8 && current !== undefined; depth += 1) {
32
+ if (current instanceof LinkedInContactIdentityMismatch) return readFailureProjection("account-mismatch");
33
+ if (current instanceof LinkedInProfileBrowserResponseRejectedError) {
34
+ if (current.status === 401 || current.status === 403) return readFailureProjection("auth-repair-required");
35
+ if (current.status === 429) return readFailureProjection("provider-throttled");
36
+ if (current.status === 302 || current.status === 408 || current.status >= 500) {
37
+ return readFailureProjection("provider-temporary");
38
+ }
39
+ if (current.status === 404 && stage !== "identity") return readFailureProjection("target-unavailable");
40
+ return readFailureProjection("contract-drift");
41
+ }
42
+ if (current instanceof LinkedInProfileBrowserFailure) {
43
+ if (current.category === "authwall" || current.category === "session-cookie") {
44
+ return readFailureProjection("auth-repair-required");
45
+ }
46
+ if (
47
+ current.category === "startup"
48
+ || current.category === "execution-context"
49
+ || current.category === "provider-fetch"
50
+ ) {
51
+ return readFailureProjection("provider-temporary");
52
+ }
53
+ return readFailureProjection("contract-drift");
54
+ }
55
+ current = current instanceof Error ? current.cause : undefined;
56
+ }
57
+ return readFailureProjection("contract-drift");
58
+ }
59
+
60
+ const REQUEST_STAGE: Readonly<Record<LinkedInContactStage, string>> = {
61
+ identity: "contained-browser signed-in identity preflight",
62
+ profile: "contained-browser 1st-degree profile binding",
63
+ contact: "contained-browser Contact-info read",
64
+ projection: "exact Contact-info projection",
65
+ };
66
+
67
+ export function linkedInContactDiagnostic(error: unknown, stage: LinkedInContactStage): string {
68
+ let category = stage === "profile" ? "1st-degree relationship binding"
69
+ : stage === "projection" ? "exact Contact-info projection" : "reviewed response projection";
70
+ let current: unknown = error;
71
+ for (let depth = 0; depth < 8 && current !== undefined; depth += 1) {
72
+ if (current instanceof LinkedInContactIdentityMismatch) {
73
+ category = "signed-in account binding";
74
+ break;
75
+ }
76
+ if (current instanceof LinkedInProfileBrowserFailure) {
77
+ if (current.category === "authwall") category = "signed-out authwall";
78
+ if (current.category === "session-cookie") category = "signed-in session cookie";
79
+ break;
80
+ }
81
+ current = current instanceof Error ? current.cause : undefined;
82
+ }
83
+ return `LinkedIn contacts.read failed during ${REQUEST_STAGE[stage]} at ${category}; no remote write occurred`;
84
+ }
@@ -0,0 +1,45 @@
1
+ import * as Context from "effect/Context";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import { ReadEffectFailure, readAttempt } from "../read-effect";
5
+ import { readNative } from "../read-effect-platform";
6
+ import { LinkedInContactIdentityMismatch } from "./linkedin-contact-failure";
7
+ import type { LinkedInContactInfoJsonInput } from "./linkedin-web-contact";
8
+ import type { LinkedInProfileBrowserTransport } from "./linkedin-web-profile-browser";
9
+
10
+ export type LinkedInContactIdentity = { readonly subject: string };
11
+
12
+ export type LinkedInContactNative = {
13
+ readonly openBrowser: () => Promise<LinkedInProfileBrowserTransport>;
14
+ readonly decodeIdentity: (value: unknown) => LinkedInContactIdentity;
15
+ readonly bindIdentity: (identity: LinkedInContactIdentity) => string;
16
+ readonly observedAt: () => string;
17
+ };
18
+
19
+ function platform(ports: LinkedInContactNative) {
20
+ return {
21
+ openBrowser: readNative(ports.openBrowser).pipe(Effect.uninterruptible),
22
+ identity: (browser: LinkedInProfileBrowserTransport) => readNative(() => browser.currentIdentityResponse()).pipe(
23
+ Effect.uninterruptible,
24
+ Effect.flatMap(value => readAttempt(() => ports.decodeIdentity(value))),
25
+ ),
26
+ bindIdentity: (identity: LinkedInContactIdentity) => readAttempt(() => ports.bindIdentity(identity)).pipe(
27
+ Effect.mapError(error => new ReadEffectFailure({ cause: new LinkedInContactIdentityMismatch(error.cause) })),
28
+ ),
29
+ profileHtml: (browser: LinkedInProfileBrowserTransport, profileUrl: string) =>
30
+ readNative(() => browser.readProfileHtml(profileUrl)).pipe(Effect.uninterruptible),
31
+ contactPayload: (browser: LinkedInProfileBrowserTransport, input: LinkedInContactInfoJsonInput) =>
32
+ readNative(() => browser.readContactInfoJson(input)).pipe(Effect.uninterruptible),
33
+ observedAt: readAttempt(ports.observedAt),
34
+ closeBrowser: (browser: LinkedInProfileBrowserTransport) =>
35
+ readNative(() => browser.close()).pipe(Effect.uninterruptible),
36
+ };
37
+ }
38
+
39
+ export class LinkedInContactPlatform extends Context.Tag("wrench/LinkedInContactPlatform/v1")<
40
+ LinkedInContactPlatform,
41
+ ReturnType<typeof platform>
42
+ >() { }
43
+
44
+ export const LinkedInContactPlatformLive = (ports: LinkedInContactNative): Layer.Layer<LinkedInContactPlatform> =>
45
+ Layer.succeed(LinkedInContactPlatform, platform(ports));
@@ -0,0 +1,91 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { PreservedBrowserArtifactsError } from "../browser";
3
+ import { readAttempt, type ReadEffectFailure, withReadResource } from "../read-effect";
4
+ import type { WebSessionExecution } from "../web-session-execution";
5
+ import { LinkedInContactPlatform } from "./linkedin-contact-platform";
6
+ import {
7
+ linkedInContactDiagnostic,
8
+ linkedInContactReadFailure,
9
+ type LinkedInContactStage,
10
+ } from "./linkedin-contact-failure";
11
+ import {
12
+ projectLinkedInContactInfo,
13
+ projectLinkedInEmbeddedContactFields,
14
+ projectLinkedInProfileContactBinding,
15
+ resolveLinkedInProfileContactInfoQueryId,
16
+ type LinkedInContactInfoTarget,
17
+ } from "./linkedin-web-contact";
18
+
19
+ export function linkedInContactReadProgram(
20
+ target: LinkedInContactInfoTarget,
21
+ ): Effect.Effect<WebSessionExecution, ReadEffectFailure, LinkedInContactPlatform> {
22
+ return Effect.gen(function*() {
23
+ const platform = yield* LinkedInContactPlatform;
24
+ let stage: LinkedInContactStage = "identity";
25
+ let closeFailed = false;
26
+ return yield* withReadResource(
27
+ platform.openBrowser,
28
+ browser => Effect.gen(function*() {
29
+ const identity = yield* platform.identity(browser);
30
+ yield* platform.bindIdentity(identity);
31
+ stage = "profile";
32
+ const profileHtml = yield* platform.profileHtml(browser, target.url);
33
+ const binding = yield* readAttempt(() => projectLinkedInProfileContactBinding({
34
+ profileHtml,
35
+ profileUrl: target.url,
36
+ expectedViewerSubject: identity.subject,
37
+ }));
38
+ const embedded = yield* readAttempt(() =>
39
+ projectLinkedInEmbeddedContactFields(profileHtml, binding.vanity)
40
+ );
41
+ let contactPayload: unknown;
42
+ if (embedded !== undefined && embedded.email !== null) {
43
+ contactPayload = Object.freeze({
44
+ emailAddress: embedded.email,
45
+ connectedAt: embedded.connectedSince,
46
+ phoneNumbers: embedded.phones,
47
+ websites: embedded.websites,
48
+ birthDateOn: embedded.birthday,
49
+ profileUrl: embedded.profileUrl,
50
+ });
51
+ } else {
52
+ stage = "contact";
53
+ const queryId = resolveLinkedInProfileContactInfoQueryId(profileHtml);
54
+ contactPayload = yield* platform.contactPayload(browser, {
55
+ profileUrl: target.url,
56
+ profileUrn: binding.profileUrn,
57
+ ...(queryId === undefined ? {} : { queryId }),
58
+ });
59
+ }
60
+ stage = "projection";
61
+ const observedAt = yield* platform.observedAt;
62
+ const output = yield* readAttempt(() => projectLinkedInContactInfo({
63
+ profileHtml,
64
+ contactPayload,
65
+ profileUrl: target.url,
66
+ expectedViewerSubject: identity.subject,
67
+ observedAt,
68
+ }));
69
+ return {
70
+ status: "succeeded" as const,
71
+ output,
72
+ finalUrl: target.url,
73
+ dispatchStarted: false,
74
+ dispatch: { planned: 0, started: 0, verified: 0 },
75
+ };
76
+ }),
77
+ browser => platform.closeBrowser(browser).pipe(Effect.tapError(() => Effect.sync(() => { closeFailed = true; }))),
78
+ ).pipe(Effect.catchTag("ReadEffectFailure", error => {
79
+ if (closeFailed || error.cause instanceof PreservedBrowserArtifactsError) return Effect.fail(error);
80
+ return Effect.succeed({
81
+ status: "failed" as const,
82
+ output: null,
83
+ finalUrl: target.url,
84
+ dispatchStarted: false,
85
+ dispatch: { planned: 0, started: 0, verified: 0 },
86
+ error: linkedInContactDiagnostic(error.cause, stage),
87
+ readFailure: linkedInContactReadFailure(error.cause, stage),
88
+ });
89
+ }));
90
+ });
91
+ }