@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,586 @@
1
+ import { types as nodeTypes } from "node:util";
2
+
3
+ import type { OperationInput } from "../model";
4
+ import { OmniMaterializerDriftError } from "../omni-model";
5
+ import type {
6
+ OmniAttachmentV1,
7
+ OmniParticipantV1,
8
+ ProviderConversationV1,
9
+ ProviderMaterializedPageV1,
10
+ ProviderMessageV1,
11
+ } from "../omni-model";
12
+
13
+ type WhatsAppFolder = "active" | "all" | "archived" | "unread";
14
+
15
+ type ParsedWhatsAppConversation = Readonly<{
16
+ providerId: string;
17
+ kind: string;
18
+ name: string | null;
19
+ orderedAt: string;
20
+ archived: boolean;
21
+ pinned: boolean;
22
+ mutedUntil: number;
23
+ unread: boolean;
24
+ unreadCount: number;
25
+ }>;
26
+
27
+ type ParsedWhatsAppMessage = Readonly<{
28
+ providerId: string;
29
+ conversationId: string;
30
+ conversationName: string | null;
31
+ senderId: string | null;
32
+ senderName: string | null;
33
+ orderedAt: string;
34
+ fromMe: boolean;
35
+ text: string;
36
+ displayText: string;
37
+ quotedMessageId: string | null;
38
+ quotedSenderId: string | null;
39
+ forwarded: boolean;
40
+ forwardingScore: number;
41
+ reactionToId: string | null;
42
+ reactionEmoji: string | null;
43
+ attachments: readonly OmniAttachmentV1[];
44
+ starred: boolean;
45
+ revoked: boolean;
46
+ deletedForMe: boolean;
47
+ snippet: string | null;
48
+ }>;
49
+
50
+ function drift(path: string, message: string): never {
51
+ throw new OmniMaterializerDriftError("whatsapp", path, message);
52
+ }
53
+
54
+ function record(value: unknown, path: string): Readonly<Record<string, unknown>> {
55
+ if (
56
+ nodeTypes.isProxy(value)
57
+ || typeof value !== "object"
58
+ || value === null
59
+ || Array.isArray(value)
60
+ ) {
61
+ return drift(path, "must be a plain object");
62
+ }
63
+ const prototype = Object.getPrototypeOf(value) as unknown;
64
+ if (prototype !== Object.prototype && prototype !== null) {
65
+ return drift(path, "must be a plain object");
66
+ }
67
+ const descriptors = Object.getOwnPropertyDescriptors(value);
68
+ for (const name of Reflect.ownKeys(descriptors)) {
69
+ if (typeof name !== "string") return drift(path, "must not have symbol properties");
70
+ const descriptor = descriptors[name];
71
+ if (
72
+ descriptor === undefined
73
+ || !("value" in descriptor)
74
+ || !descriptor.enumerable
75
+ ) {
76
+ return drift(`${path}.*`, "must be an enumerable data property");
77
+ }
78
+ }
79
+ return value as Readonly<Record<string, unknown>>;
80
+ }
81
+
82
+ function exactKeys(
83
+ value: Readonly<Record<string, unknown>>,
84
+ required: readonly string[],
85
+ optional: readonly string[],
86
+ path: string,
87
+ ): void {
88
+ const allowed = new Set([...required, ...optional]);
89
+ for (const key of Object.keys(value)) {
90
+ if (!allowed.has(key)) drift(path, "contains an unreviewed property");
91
+ }
92
+ for (const key of required) {
93
+ if (!Object.hasOwn(value, key)) drift(`${path}.${key}`, "is required");
94
+ }
95
+ }
96
+
97
+ function array(value: unknown, path: string, maximum: number): readonly unknown[] {
98
+ if (
99
+ nodeTypes.isProxy(value)
100
+ || !Array.isArray(value)
101
+ || Object.getPrototypeOf(value) !== Array.prototype
102
+ || value.length > maximum
103
+ ) {
104
+ return drift(path, `must be an array of at most ${maximum} items`);
105
+ }
106
+ for (let index = 0; index < value.length; index += 1) {
107
+ if (!Object.hasOwn(value, index)) drift(`${path}[${index}]`, "must not be sparse");
108
+ const descriptor = Object.getOwnPropertyDescriptor(value, index);
109
+ if (
110
+ descriptor === undefined
111
+ || !("value" in descriptor)
112
+ || !descriptor.enumerable
113
+ ) drift(`${path}[${index}]`, "must be an enumerable data property");
114
+ }
115
+ const expected = new Set<PropertyKey>([
116
+ "length",
117
+ ...Array.from({ length: value.length }, (_unused, index) => String(index)),
118
+ ]);
119
+ const ownKeys = Reflect.ownKeys(value);
120
+ if (ownKeys.length !== expected.size || ownKeys.some((key) => !expected.has(key))) {
121
+ drift(path, "must be a dense array without named properties");
122
+ }
123
+ return value;
124
+ }
125
+
126
+ function hasDisallowedControl(value: string): boolean {
127
+ for (const character of value) {
128
+ const code = character.charCodeAt(0);
129
+ if ((code < 32 && code !== 9 && code !== 10 && code !== 13) || code === 127) {
130
+ return true;
131
+ }
132
+ }
133
+ return false;
134
+ }
135
+
136
+ function string(
137
+ value: unknown,
138
+ path: string,
139
+ maximum: number,
140
+ allowEmpty = false,
141
+ ): string {
142
+ if (
143
+ typeof value !== "string"
144
+ || Buffer.byteLength(value, "utf8") > maximum
145
+ || (!allowEmpty && value.length === 0)
146
+ || hasDisallowedControl(value)
147
+ ) return drift(path, `must be a bounded${allowEmpty ? "" : " non-empty"} string`);
148
+ return value;
149
+ }
150
+
151
+ function nullableString(
152
+ value: unknown,
153
+ path: string,
154
+ maximum: number,
155
+ ): string | null {
156
+ return value === null ? null : string(value, path, maximum);
157
+ }
158
+
159
+ function boolean(value: unknown, path: string): boolean {
160
+ if (typeof value !== "boolean") return drift(path, "must be boolean");
161
+ return value;
162
+ }
163
+
164
+ function integer(
165
+ value: unknown,
166
+ path: string,
167
+ minimum: number,
168
+ maximum: number,
169
+ ): number {
170
+ if (
171
+ typeof value !== "number"
172
+ || !Number.isSafeInteger(value)
173
+ || value < minimum
174
+ || value > maximum
175
+ ) return drift(path, `must be an integer from ${minimum} through ${maximum}`);
176
+ return value;
177
+ }
178
+
179
+ function jid(value: unknown, path: string): string {
180
+ const result = string(value, path, 96);
181
+ if (!(
182
+ /^[0-9]{5,20}(?::[0-9]{1,5})?@s\.whatsapp\.net$/u.test(result)
183
+ || /^[0-9]{5,32}(?::[0-9]{1,5})?@lid$/u.test(result)
184
+ || /^[0-9]{5,32}(?:-[0-9]{5,20})?@g\.us$/u.test(result)
185
+ || /^[0-9]{5,32}@newsletter$/u.test(result)
186
+ || /^(?:status|[0-9]{5,32})@broadcast$/u.test(result)
187
+ )) return drift(path, "must be an exact WhatsApp JID");
188
+ return result;
189
+ }
190
+
191
+ function addressableJid(value: unknown, path: string): string {
192
+ const result = jid(value, path);
193
+ if (/@(?:newsletter|broadcast)$/u.test(result) || result.includes(":")) {
194
+ return drift(path, "must identify an addressable conversation JID");
195
+ }
196
+ return result;
197
+ }
198
+
199
+ function nullableJid(value: unknown, path: string): string | null {
200
+ return value === null ? null : jid(value, path);
201
+ }
202
+
203
+ function messageId(value: unknown, path: string): string {
204
+ const result = string(value, path, 256);
205
+ if (!/^[A-Za-z0-9._~:-]{1,256}$/u.test(result)) {
206
+ return drift(path, "must be an exact WhatsApp message ID");
207
+ }
208
+ return result;
209
+ }
210
+
211
+ function nullableMessageId(value: unknown, path: string): string | null {
212
+ return value === null ? null : messageId(value, path);
213
+ }
214
+
215
+ function accountSubject(value: unknown, path: string): string {
216
+ const result = string(value, path, 128);
217
+ if (!/^whatsapp:(?:pn:[0-9]{5,20}|lid:[0-9]{5,32})$/u.test(result)) {
218
+ return drift(path, "must be an exact linked-device account subject");
219
+ }
220
+ return result;
221
+ }
222
+
223
+ function timestamp(value: unknown, path: string): string {
224
+ const result = string(value, path, 64);
225
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?Z$/u.test(result)) {
226
+ return drift(path, "must be a UTC RFC3339 timestamp");
227
+ }
228
+ const milliseconds = Date.parse(result);
229
+ if (!Number.isFinite(milliseconds)) return drift(path, "must be a valid UTC RFC3339 timestamp");
230
+ return new Date(milliseconds).toISOString();
231
+ }
232
+
233
+ function folder(value: unknown, path: string): WhatsAppFolder {
234
+ if (
235
+ value !== "all"
236
+ && value !== "active"
237
+ && value !== "archived"
238
+ && value !== "unread"
239
+ ) return drift(path, "must be all, active, archived, or unread");
240
+ return value;
241
+ }
242
+
243
+ function listInput(input: OperationInput): {
244
+ readonly folder: WhatsAppFolder;
245
+ readonly limit: number;
246
+ } {
247
+ const source = record(input, "messaging.list input");
248
+ exactKeys(source, [], ["folder", "limit"], "messaging.list input");
249
+ return Object.freeze({
250
+ folder: source.folder === undefined
251
+ ? "all"
252
+ : folder(source.folder, "messaging.list input.folder"),
253
+ limit: source.limit === undefined
254
+ ? 100
255
+ : integer(source.limit, "messaging.list input.limit", 1, 100),
256
+ });
257
+ }
258
+
259
+ function readInput(input: OperationInput): {
260
+ readonly conversationId: string;
261
+ readonly limit: number;
262
+ } {
263
+ const source = record(input, "messaging.read input");
264
+ exactKeys(source, ["conversation_jid"], ["limit"], "messaging.read input");
265
+ return Object.freeze({
266
+ conversationId: addressableJid(
267
+ source.conversation_jid,
268
+ "messaging.read input.conversation_jid",
269
+ ),
270
+ limit: source.limit === undefined
271
+ ? 200
272
+ : integer(source.limit, "messaging.read input.limit", 1, 200),
273
+ });
274
+ }
275
+
276
+ function parseConversation(value: unknown, path: string): ParsedWhatsAppConversation {
277
+ const source = record(value, path);
278
+ exactKeys(source, [
279
+ "jid",
280
+ "kind",
281
+ "name",
282
+ "lastMessageAt",
283
+ "archived",
284
+ "pinned",
285
+ "mutedUntil",
286
+ "unread",
287
+ "unreadCount",
288
+ ], [], path);
289
+ const unread = boolean(source.unread, `${path}.unread`);
290
+ const unreadCount = integer(source.unreadCount, `${path}.unreadCount`, 0, 1_000_000);
291
+ return Object.freeze({
292
+ providerId: jid(source.jid, `${path}.jid`),
293
+ kind: string(source.kind, `${path}.kind`, 64),
294
+ name: nullableString(source.name, `${path}.name`, 512),
295
+ orderedAt: timestamp(source.lastMessageAt, `${path}.lastMessageAt`),
296
+ archived: boolean(source.archived, `${path}.archived`),
297
+ pinned: boolean(source.pinned, `${path}.pinned`),
298
+ mutedUntil: integer(
299
+ source.mutedUntil,
300
+ `${path}.mutedUntil`,
301
+ -1,
302
+ Number.MAX_SAFE_INTEGER,
303
+ ),
304
+ unread,
305
+ unreadCount,
306
+ });
307
+ }
308
+
309
+ function parseButtons(value: unknown, path: string): void {
310
+ for (const [index, entry] of array(value, path, 100).entries()) {
311
+ const itemPath = `${path}[${index}]`;
312
+ const source = record(entry, itemPath);
313
+ exactKeys(source, ["type", "displayText", "index"], [], itemPath);
314
+ string(source.type, `${itemPath}.type`, 64);
315
+ string(source.displayText, `${itemPath}.displayText`, 1_024, true);
316
+ if (source.index !== null) integer(source.index, `${itemPath}.index`, 0, 1_000);
317
+ }
318
+ }
319
+
320
+ function attachmentKind(value: string): OmniAttachmentV1["kind"] {
321
+ const normalized = value.toLowerCase();
322
+ if (normalized.includes("audio") || normalized.includes("voice")) return "audio";
323
+ if (normalized.includes("document") || normalized.includes("file")) return "document";
324
+ if (normalized.includes("image") || normalized.includes("photo")) return "image";
325
+ if (normalized.includes("sticker")) return "sticker";
326
+ if (normalized.includes("video")) return "video";
327
+ return "unknown";
328
+ }
329
+
330
+ function parseMedia(value: unknown, path: string): readonly OmniAttachmentV1[] {
331
+ if (value === null) return Object.freeze([]);
332
+ const source = record(value, path);
333
+ exactKeys(source, ["type", "caption", "filename", "mimeType", "downloaded"], [], path);
334
+ const type = string(source.type, `${path}.type`, 64);
335
+ nullableString(source.caption, `${path}.caption`, 100_000);
336
+ const filename = nullableString(source.filename, `${path}.filename`, 512);
337
+ const mimeType = nullableString(source.mimeType, `${path}.mimeType`, 255);
338
+ boolean(source.downloaded, `${path}.downloaded`);
339
+ return Object.freeze([Object.freeze({
340
+ kind: attachmentKind(type),
341
+ mimeType,
342
+ name: filename,
343
+ sizeBytes: null,
344
+ })]);
345
+ }
346
+
347
+ function parseMessage(
348
+ value: unknown,
349
+ path: string,
350
+ expectedConversationId: string,
351
+ ): ParsedWhatsAppMessage {
352
+ const source = record(value, path);
353
+ exactKeys(source, [
354
+ "chatJid",
355
+ "chatName",
356
+ "messageId",
357
+ "senderJid",
358
+ "senderName",
359
+ "timestamp",
360
+ "fromMe",
361
+ "text",
362
+ "displayText",
363
+ "quotedMessageId",
364
+ "quotedSenderJid",
365
+ "buttons",
366
+ "forwarded",
367
+ "forwardingScore",
368
+ "reactionToId",
369
+ "reactionEmoji",
370
+ "media",
371
+ "starred",
372
+ "revoked",
373
+ "deletedForMe",
374
+ "snippet",
375
+ ], [], path);
376
+ const conversationId = jid(source.chatJid, `${path}.chatJid`);
377
+ if (conversationId !== expectedConversationId) {
378
+ drift(`${path}.chatJid`, "must bind the requested conversation");
379
+ }
380
+ parseButtons(source.buttons, `${path}.buttons`);
381
+ const reactionToId = nullableMessageId(source.reactionToId, `${path}.reactionToId`);
382
+ const reactionEmoji = nullableString(source.reactionEmoji, `${path}.reactionEmoji`, 64);
383
+ if ((reactionToId === null) !== (reactionEmoji === null)) {
384
+ drift(`${path}.reactionEmoji`, "must be present exactly when reactionToId is present");
385
+ }
386
+ return Object.freeze({
387
+ providerId: messageId(source.messageId, `${path}.messageId`),
388
+ conversationId,
389
+ conversationName: nullableString(source.chatName, `${path}.chatName`, 512),
390
+ senderId: nullableJid(source.senderJid, `${path}.senderJid`),
391
+ senderName: nullableString(source.senderName, `${path}.senderName`, 512),
392
+ orderedAt: timestamp(source.timestamp, `${path}.timestamp`),
393
+ fromMe: boolean(source.fromMe, `${path}.fromMe`),
394
+ text: string(source.text, `${path}.text`, 100_000, true),
395
+ displayText: string(source.displayText, `${path}.displayText`, 100_000, true),
396
+ quotedMessageId: nullableMessageId(
397
+ source.quotedMessageId,
398
+ `${path}.quotedMessageId`,
399
+ ),
400
+ quotedSenderId: nullableJid(source.quotedSenderJid, `${path}.quotedSenderJid`),
401
+ forwarded: boolean(source.forwarded, `${path}.forwarded`),
402
+ forwardingScore: integer(
403
+ source.forwardingScore,
404
+ `${path}.forwardingScore`,
405
+ 0,
406
+ 1_000_000,
407
+ ),
408
+ reactionToId,
409
+ reactionEmoji,
410
+ attachments: parseMedia(source.media, `${path}.media`),
411
+ starred: boolean(source.starred, `${path}.starred`),
412
+ revoked: boolean(source.revoked, `${path}.revoked`),
413
+ deletedForMe: boolean(source.deletedForMe, `${path}.deletedForMe`),
414
+ snippet: nullableString(source.snippet, `${path}.snippet`, 4_096),
415
+ });
416
+ }
417
+
418
+ function participant(
419
+ providerId: string | null,
420
+ displayName: string | null,
421
+ ): OmniParticipantV1 | null {
422
+ return providerId === null && displayName === null
423
+ ? null
424
+ : Object.freeze({ providerId, displayName, handle: null });
425
+ }
426
+
427
+ function materializedConversation(
428
+ value: ParsedWhatsAppConversation,
429
+ ): ProviderConversationV1 {
430
+ const directParticipant = value.kind === "dm" || value.kind === "user"
431
+ ? participant(value.providerId, value.name)
432
+ : null;
433
+ return Object.freeze({
434
+ kind: "conversation",
435
+ conversationKind: value.kind === "group" ? "group" : "single",
436
+ providerId: value.providerId,
437
+ providerRevision: null,
438
+ orderedAt: value.orderedAt,
439
+ detail: "summary",
440
+ title: value.name,
441
+ summary: null,
442
+ participants: Object.freeze(
443
+ directParticipant === null ? [] : [directParticipant],
444
+ ),
445
+ unread: value.unread,
446
+ unreadCount: value.unreadCount,
447
+ archived: value.archived,
448
+ pending: null,
449
+ });
450
+ }
451
+
452
+ function messageState(value: ParsedWhatsAppMessage): ProviderMessageV1["state"] {
453
+ if (value.revoked && value.deletedForMe) return "revoked-and-deleted-for-me";
454
+ if (value.revoked) return "revoked";
455
+ if (value.deletedForMe) return "deleted-for-me";
456
+ return "active";
457
+ }
458
+
459
+ function materializedMessage(value: ParsedWhatsAppMessage): ProviderMessageV1 {
460
+ return Object.freeze({
461
+ kind: "message",
462
+ providerId: value.providerId,
463
+ providerRevision: null,
464
+ orderedAt: value.orderedAt,
465
+ conversationProviderId: value.conversationId,
466
+ sender: participant(value.senderId, value.senderName),
467
+ recipients: Object.freeze([]),
468
+ direction: value.fromMe ? "outgoing" : "incoming",
469
+ subject: null,
470
+ body: value.displayText === "" ? value.text : value.displayText,
471
+ unread: null,
472
+ replyToProviderId: value.quotedMessageId,
473
+ state: messageState(value),
474
+ attachments: value.attachments,
475
+ });
476
+ }
477
+
478
+ function parseEnvelope(output: unknown, operation: "messaging.list" | "messaging.read") {
479
+ const source = record(output, `${operation} output`);
480
+ if (source.projection !== "local-store") {
481
+ drift(`${operation} output.projection`, "must be local-store");
482
+ }
483
+ if (source.completeness !== "bounded-current-local-projection") {
484
+ drift(
485
+ `${operation} output.completeness`,
486
+ "must be bounded-current-local-projection",
487
+ );
488
+ }
489
+ return {
490
+ source,
491
+ accountSubject: accountSubject(
492
+ source.accountSubject,
493
+ `${operation} output.accountSubject`,
494
+ ),
495
+ } as const;
496
+ }
497
+
498
+ /** Strict linked-device conversation projection with bounded-local completeness. */
499
+ export function materializeWhatsAppMessagingList(
500
+ input: OperationInput,
501
+ output: unknown,
502
+ ): ProviderMaterializedPageV1 {
503
+ const parsedInput = listInput(input);
504
+ const { source, accountSubject: subject } = parseEnvelope(output, "messaging.list");
505
+ exactKeys(source, [
506
+ "accountSubject",
507
+ "projection",
508
+ "completeness",
509
+ "chats",
510
+ ], [], "messaging.list output");
511
+ const entities = array(source.chats, "messaging.list output.chats", parsedInput.limit)
512
+ .map((value, index) =>
513
+ parseConversation(value, `messaging.list output.chats[${index}]`));
514
+ const ids = entities.map((entity) => entity.providerId);
515
+ if (new Set(ids).size !== ids.length) {
516
+ drift("messaging.list output.chats", "contains duplicate stable JIDs");
517
+ }
518
+ const materialized = Object.freeze(entities.map(materializedConversation));
519
+ const partition = `${subject}:conversations:${parsedInput.folder}`;
520
+ return Object.freeze({
521
+ schemaVersion: 1,
522
+ partition,
523
+ completeness: Object.freeze({
524
+ kind: "bounded-local",
525
+ reason: "WhatsApp exposed a bounded current projection from the local linked-device store, not a complete remote inbox.",
526
+ }),
527
+ cursor: Object.freeze({ direction: "none", request: null, nextInput: null }),
528
+ entities: materialized,
529
+ tombstones: Object.freeze([]),
530
+ });
531
+ }
532
+
533
+ /** Strict linked-device message projection with explicit revocation/local deletion state. */
534
+ export function materializeWhatsAppMessagingRead(
535
+ input: OperationInput,
536
+ output: unknown,
537
+ ): ProviderMaterializedPageV1 {
538
+ const parsedInput = readInput(input);
539
+ const { source, accountSubject: subject } = parseEnvelope(output, "messaging.read");
540
+ exactKeys(source, [
541
+ "accountSubject",
542
+ "projection",
543
+ "completeness",
544
+ "conversationJid",
545
+ "messages",
546
+ "fullTextSearch",
547
+ ], [], "messaging.read output");
548
+ const conversationId = addressableJid(
549
+ source.conversationJid,
550
+ "messaging.read output.conversationJid",
551
+ );
552
+ if (conversationId !== parsedInput.conversationId) {
553
+ drift(
554
+ "messaging.read output.conversationJid",
555
+ "must bind messaging.read input.conversation_jid",
556
+ );
557
+ }
558
+ const entities = array(
559
+ source.messages,
560
+ "messaging.read output.messages",
561
+ parsedInput.limit,
562
+ ).map((value, index) =>
563
+ parseMessage(
564
+ value,
565
+ `messaging.read output.messages[${index}]`,
566
+ conversationId,
567
+ ));
568
+ const ids = entities.map((entity) => entity.providerId);
569
+ if (new Set(ids).size !== ids.length) {
570
+ drift("messaging.read output.messages", "contains duplicate stable message IDs");
571
+ }
572
+ boolean(source.fullTextSearch, "messaging.read output.fullTextSearch");
573
+ const materialized = Object.freeze(entities.map(materializedMessage));
574
+ const partition = `${subject}:messages:${conversationId}`;
575
+ return Object.freeze({
576
+ schemaVersion: 1,
577
+ partition,
578
+ completeness: Object.freeze({
579
+ kind: "bounded-local",
580
+ reason: "WhatsApp exposed bounded current messages from the local linked-device store, not a complete remote conversation archive.",
581
+ }),
582
+ cursor: Object.freeze({ direction: "none", request: null, nextInput: null }),
583
+ entities: materialized,
584
+ tombstones: Object.freeze([]),
585
+ });
586
+ }