@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,874 @@
1
+ import {
2
+ closeSync,
3
+ constants,
4
+ fstatSync,
5
+ lstatSync,
6
+ openSync,
7
+ readSync,
8
+ realpathSync,
9
+ type BigIntStats,
10
+ } from "node:fs";
11
+
12
+ import { Database } from "bun:sqlite";
13
+
14
+ import {
15
+ WHATSAPP_CONTACT_PROJECTION_MAX_STDIN_BYTES,
16
+ WHATSAPP_CONTACT_PROJECTION_MAX_STDOUT_BYTES,
17
+ WHATSAPP_CONTACT_PROJECTION_PROTOCOL_VERSION,
18
+ createWhatsAppContactProjectionFailure,
19
+ isExactWhatsAppContactProjectionMode,
20
+ parseWhatsAppContactProjectionJid,
21
+ parseWhatsAppContactProjectionRequest,
22
+ parseWhatsAppContactProjectionSubject,
23
+ type WhatsAppContactDisplayNameBasis,
24
+ type WhatsAppContactProjectionContact,
25
+ type WhatsAppContactProjectionErrorCode,
26
+ type WhatsAppContactProjectionRequest,
27
+ type WhatsAppContactProjectionResponse,
28
+ type WhatsAppContactProjectionSuccess,
29
+ } from "./whatsapp-contact-projection-protocol";
30
+
31
+ const SESSION_DATABASE_NAME = "session.db";
32
+ const MAX_SESSION_DATABASE_BYTES = 128 * 1024 * 1024;
33
+ const MAX_DEVICE_ROWS = 1_024;
34
+ const SQLITE_CACHE_KIB = 2_048;
35
+
36
+ export type WhatsAppMatchedOwnerIdentity = Readonly<{
37
+ storageJid: string;
38
+ selfJids: readonly [string] | readonly [string, string];
39
+ accountJidAliases: Readonly<{
40
+ pnJid: string;
41
+ lidJid: string | null;
42
+ }>;
43
+ }>;
44
+
45
+ export const WHATSAPP_MATCHED_OWNER_IDENTITY = Symbol(
46
+ "wrench.whatsapp.matched-owner-identity",
47
+ );
48
+
49
+ type WhatsAppContactProjectionInternalSuccess = WhatsAppContactProjectionSuccess & Readonly<{
50
+ [WHATSAPP_MATCHED_OWNER_IDENTITY]: WhatsAppMatchedOwnerIdentity;
51
+ }>;
52
+
53
+ class HelperFailure extends Error {
54
+ readonly code: WhatsAppContactProjectionErrorCode;
55
+
56
+ constructor(code: WhatsAppContactProjectionErrorCode) {
57
+ super("WhatsApp contact projection helper failed");
58
+ this.name = "WhatsAppContactProjectionHelperFailure";
59
+ this.code = code;
60
+ }
61
+ }
62
+
63
+ function fail(code: WhatsAppContactProjectionErrorCode): never {
64
+ throw new HelperFailure(code);
65
+ }
66
+
67
+ function errorCode(error: unknown): WhatsAppContactProjectionErrorCode {
68
+ return error instanceof HelperFailure ? error.code : "database-invalid";
69
+ }
70
+
71
+ function isNoEntry(error: unknown): boolean {
72
+ return typeof error === "object"
73
+ && error !== null
74
+ && "code" in error
75
+ && error.code === "ENOENT";
76
+ }
77
+
78
+ function currentUid(): bigint | null {
79
+ return typeof process.getuid === "function" ? BigInt(process.getuid()) : null;
80
+ }
81
+
82
+ function sameSnapshot(left: BigIntStats, right: BigIntStats): boolean {
83
+ return left.dev === right.dev
84
+ && left.ino === right.ino
85
+ && left.mode === right.mode
86
+ && left.uid === right.uid
87
+ && left.gid === right.gid
88
+ && left.nlink === right.nlink
89
+ && left.size === right.size
90
+ && left.mtimeNs === right.mtimeNs
91
+ && left.ctimeNs === right.ctimeNs;
92
+ }
93
+
94
+ function matchesIdentity(
95
+ stats: BigIntStats,
96
+ identity: WhatsAppContactProjectionRequest["storeIdentity"],
97
+ ): boolean {
98
+ return stats.dev.toString() === identity.dev
99
+ && stats.ino.toString() === identity.ino;
100
+ }
101
+
102
+ function assertBoundCwd(
103
+ request: WhatsAppContactProjectionRequest,
104
+ initial?: BigIntStats,
105
+ ): BigIntStats {
106
+ let cwd: string;
107
+ let pathStats: BigIntStats;
108
+ let dotStats: BigIntStats;
109
+ try {
110
+ cwd = process.cwd();
111
+ pathStats = lstatSync(cwd, { bigint: true });
112
+ dotStats = lstatSync(".", { bigint: true });
113
+ if (realpathSync(".") !== cwd) fail("store-binding-invalid");
114
+ } catch (error) {
115
+ if (error instanceof HelperFailure) throw error;
116
+ return fail("store-binding-invalid");
117
+ }
118
+ const uid = currentUid();
119
+ if (
120
+ !pathStats.isDirectory()
121
+ || !dotStats.isDirectory()
122
+ || pathStats.isSymbolicLink()
123
+ || (uid !== null && (pathStats.uid !== uid || dotStats.uid !== uid))
124
+ || !isExactWhatsAppContactProjectionMode(pathStats.mode, 0o700)
125
+ || !sameSnapshot(pathStats, dotStats)
126
+ || !matchesIdentity(pathStats, request.storeIdentity)
127
+ || (initial !== undefined && !sameSnapshot(initial, pathStats))
128
+ ) fail("store-binding-invalid");
129
+ return pathStats;
130
+ }
131
+
132
+ function assertNoSidecars(): void {
133
+ for (const suffix of ["-journal", "-wal", "-shm"] as const) {
134
+ try {
135
+ lstatSync(`${SESSION_DATABASE_NAME}${suffix}`);
136
+ } catch (error) {
137
+ if (isNoEntry(error)) continue;
138
+ return fail("session-sidecar-state-unverified");
139
+ }
140
+ fail("session-sidecar-present");
141
+ }
142
+ }
143
+
144
+ function assertSessionFile(
145
+ stats: BigIntStats,
146
+ request: WhatsAppContactProjectionRequest,
147
+ ): void {
148
+ const uid = currentUid();
149
+ if (
150
+ !stats.isFile()
151
+ || stats.isSymbolicLink()
152
+ || stats.nlink !== 1n
153
+ || (uid !== null && stats.uid !== uid)
154
+ || !isExactWhatsAppContactProjectionMode(stats.mode, 0o600)
155
+ || !matchesIdentity(stats, request.sessionIdentity)
156
+ ) fail("session-file-invalid");
157
+ if (stats.size < 1n || stats.size > BigInt(MAX_SESSION_DATABASE_BYTES)) {
158
+ fail("session-file-too-large");
159
+ }
160
+ }
161
+
162
+ function exactSessionPathSnapshot(
163
+ initial: BigIntStats,
164
+ request: WhatsAppContactProjectionRequest,
165
+ ): void {
166
+ let pathStats: BigIntStats;
167
+ try {
168
+ pathStats = lstatSync(SESSION_DATABASE_NAME, { bigint: true });
169
+ } catch {
170
+ return fail("session-file-invalid");
171
+ }
172
+ assertSessionFile(pathStats, request);
173
+ if (!sameSnapshot(initial, pathStats)) fail("session-file-invalid");
174
+ }
175
+
176
+ function readExactSessionDatabase(
177
+ descriptor: number,
178
+ stats: BigIntStats,
179
+ ): Buffer {
180
+ const length = Number(stats.size);
181
+ if (!Number.isSafeInteger(length) || length < 1) {
182
+ return fail("session-file-too-large");
183
+ }
184
+ const buffer = Buffer.allocUnsafe(length);
185
+ let offset = 0;
186
+ try {
187
+ while (offset < buffer.length) {
188
+ const bytesRead = readSync(
189
+ descriptor,
190
+ buffer,
191
+ offset,
192
+ buffer.length - offset,
193
+ offset,
194
+ );
195
+ if (bytesRead < 1) fail("session-read-failed");
196
+ offset += bytesRead;
197
+ }
198
+ } catch (error) {
199
+ buffer.fill(0);
200
+ if (error instanceof HelperFailure) throw error;
201
+ return fail("session-read-failed");
202
+ }
203
+ return buffer;
204
+ }
205
+
206
+ type SqliteRow = Readonly<Record<string, unknown>>;
207
+
208
+ function rows(
209
+ value: Iterable<unknown>,
210
+ maximum: number,
211
+ failureCode: WhatsAppContactProjectionErrorCode = "schema-mismatch",
212
+ ): readonly SqliteRow[] {
213
+ const result: SqliteRow[] = [];
214
+ for (const item of value) {
215
+ if (result.length >= maximum) fail(failureCode);
216
+ if (
217
+ typeof item !== "object"
218
+ || item === null
219
+ || Array.isArray(item)
220
+ || Object.getPrototypeOf(item) !== Object.prototype
221
+ ) fail(failureCode);
222
+ result.push(item as SqliteRow);
223
+ }
224
+ return result;
225
+ }
226
+
227
+ function exactRowKeys(row: SqliteRow, expected: readonly string[]): void {
228
+ const actual = Object.keys(row).sort();
229
+ const wanted = [...expected].sort();
230
+ if (
231
+ actual.length !== wanted.length
232
+ || actual.some((key, index) => key !== wanted[index])
233
+ ) fail("schema-mismatch");
234
+ }
235
+
236
+ function pragmaInteger(value: unknown, expected: bigint): boolean {
237
+ return value === expected || value === Number(expected);
238
+ }
239
+
240
+ function singlePragmaInteger(database: Database, sql: string): bigint {
241
+ const result = rows(
242
+ database.query(sql).iterate(),
243
+ 1,
244
+ "database-invalid",
245
+ );
246
+ if (result.length !== 1) fail("database-invalid");
247
+ const values = Object.values(result[0]!);
248
+ if (values.length !== 1) fail("database-invalid");
249
+ const value = values[0];
250
+ if (typeof value === "bigint") return value;
251
+ if (typeof value === "number" && Number.isSafeInteger(value)) {
252
+ return BigInt(value);
253
+ }
254
+ return fail("database-invalid");
255
+ }
256
+
257
+ function configureDatabase(database: Database): void {
258
+ try {
259
+ database.exec(`
260
+ PRAGMA query_only = ON;
261
+ PRAGMA trusted_schema = OFF;
262
+ PRAGMA foreign_keys = ON;
263
+ PRAGMA temp_store = MEMORY;
264
+ PRAGMA cell_size_check = ON;
265
+ PRAGMA recursive_triggers = OFF;
266
+ PRAGMA writable_schema = OFF;
267
+ PRAGMA ignore_check_constraints = OFF;
268
+ PRAGMA automatic_index = OFF;
269
+ PRAGMA mmap_size = 0;
270
+ PRAGMA cache_size = -${SQLITE_CACHE_KIB};
271
+ `);
272
+ const checks = [
273
+ ["PRAGMA query_only", 1n],
274
+ ["PRAGMA trusted_schema", 0n],
275
+ ["PRAGMA foreign_keys", 1n],
276
+ ["PRAGMA temp_store", 2n],
277
+ ["PRAGMA cell_size_check", 1n],
278
+ ["PRAGMA recursive_triggers", 0n],
279
+ ["PRAGMA writable_schema", 0n],
280
+ ["PRAGMA ignore_check_constraints", 0n],
281
+ ["PRAGMA automatic_index", 0n],
282
+ ["PRAGMA cache_size", BigInt(-SQLITE_CACHE_KIB)],
283
+ ] as const;
284
+ for (const [sql, expected] of checks) {
285
+ if (singlePragmaInteger(database, sql) !== expected) {
286
+ fail("database-invalid");
287
+ }
288
+ }
289
+ const mmap = rows(
290
+ database.query("PRAGMA mmap_size").iterate(),
291
+ 1,
292
+ "database-invalid",
293
+ );
294
+ if (
295
+ mmap.length === 1
296
+ && !pragmaInteger(Object.values(mmap[0]!)[0], 0n)
297
+ ) fail("database-invalid");
298
+ } catch (error) {
299
+ if (error instanceof HelperFailure) throw error;
300
+ fail("database-invalid");
301
+ }
302
+ }
303
+
304
+ function assertIntegrity(database: Database): void {
305
+ try {
306
+ const integrityCheck = rows(
307
+ database.query("PRAGMA integrity_check(1)").iterate(),
308
+ 1,
309
+ "database-integrity-failed",
310
+ );
311
+ if (
312
+ integrityCheck.length !== 1
313
+ || Object.values(integrityCheck[0]!).length !== 1
314
+ || Object.values(integrityCheck[0]!)[0] !== "ok"
315
+ ) fail("database-integrity-failed");
316
+ if (rows(
317
+ database.query("PRAGMA foreign_key_check").iterate(),
318
+ 1,
319
+ "database-integrity-failed",
320
+ ).length !== 0) {
321
+ fail("database-integrity-failed");
322
+ }
323
+ } catch (error) {
324
+ if (error instanceof HelperFailure) throw error;
325
+ fail("database-integrity-failed");
326
+ }
327
+ }
328
+
329
+ const CONTACT_COLUMNS = Object.freeze([
330
+ { cid: 0n, name: "our_jid", type: "TEXT", notnull: 0n, dflt_value: null, pk: 1n, hidden: 0n },
331
+ { cid: 1n, name: "their_jid", type: "TEXT", notnull: 0n, dflt_value: null, pk: 2n, hidden: 0n },
332
+ { cid: 2n, name: "first_name", type: "TEXT", notnull: 0n, dflt_value: null, pk: 0n, hidden: 0n },
333
+ { cid: 3n, name: "full_name", type: "TEXT", notnull: 0n, dflt_value: null, pk: 0n, hidden: 0n },
334
+ { cid: 4n, name: "push_name", type: "TEXT", notnull: 0n, dflt_value: null, pk: 0n, hidden: 0n },
335
+ { cid: 5n, name: "business_name", type: "TEXT", notnull: 0n, dflt_value: null, pk: 0n, hidden: 0n },
336
+ { cid: 6n, name: "redacted_phone", type: "TEXT", notnull: 0n, dflt_value: null, pk: 0n, hidden: 0n },
337
+ ] as const);
338
+
339
+ function assertTableList(database: Database): void {
340
+ const tableList = rows(database.query(`
341
+ SELECT schema, name, type, ncol, wr, strict
342
+ FROM pragma_table_list
343
+ WHERE schema = 'main'
344
+ AND name IN ('whatsmeow_contacts', 'whatsmeow_device')
345
+ LIMIT 3
346
+ `).iterate(), 3);
347
+ for (const [name, ncol] of [["whatsmeow_contacts", 7n]] as const) {
348
+ const matches = tableList.filter((row) => row.schema === "main" && row.name === name);
349
+ if (matches.length !== 1) fail("schema-mismatch");
350
+ const row = matches[0]!;
351
+ exactRowKeys(row, ["schema", "name", "type", "ncol", "wr", "strict"]);
352
+ if (
353
+ row.type !== "table"
354
+ || !pragmaInteger(row.ncol, ncol)
355
+ || !pragmaInteger(row.wr, 0n)
356
+ || !pragmaInteger(row.strict, 0n)
357
+ ) fail("schema-mismatch");
358
+ }
359
+ const devices = tableList.filter(
360
+ (row) => row.schema === "main" && row.name === "whatsmeow_device",
361
+ );
362
+ if (devices.length !== 1) fail("schema-mismatch");
363
+ const device = devices[0]!;
364
+ exactRowKeys(device, ["schema", "name", "type", "ncol", "wr", "strict"]);
365
+ if (
366
+ device.type !== "table"
367
+ || (typeof device.ncol !== "bigint" && typeof device.ncol !== "number")
368
+ || BigInt(device.ncol) < 2n
369
+ || !pragmaInteger(device.wr, 0n)
370
+ || !pragmaInteger(device.strict, 0n)
371
+ ) fail("schema-mismatch");
372
+ }
373
+
374
+ function assertContactColumns(database: Database): void {
375
+ const actual = rows(
376
+ database.query("PRAGMA table_xinfo('whatsmeow_contacts')").iterate(),
377
+ CONTACT_COLUMNS.length,
378
+ );
379
+ if (actual.length !== CONTACT_COLUMNS.length) fail("schema-mismatch");
380
+ for (const [index, expected] of CONTACT_COLUMNS.entries()) {
381
+ const row = actual[index]!;
382
+ exactRowKeys(row, ["cid", "name", "type", "notnull", "dflt_value", "pk", "hidden"]);
383
+ for (const [key, value] of Object.entries(expected)) {
384
+ if (typeof value === "bigint") {
385
+ if (!pragmaInteger(row[key], value)) fail("schema-mismatch");
386
+ } else if (row[key] !== value) fail("schema-mismatch");
387
+ }
388
+ }
389
+ }
390
+
391
+ function assertDeviceIdentityColumns(database: Database): void {
392
+ const actual = rows(
393
+ database.query("PRAGMA table_xinfo('whatsmeow_device')").iterate(),
394
+ 256,
395
+ );
396
+ const jidRows = actual.filter((row) => row.name === "jid");
397
+ const lidRows = actual.filter((row) => row.name === "lid");
398
+ if (jidRows.length !== 1 || lidRows.length !== 1) fail("schema-mismatch");
399
+ for (const row of [jidRows[0]!, lidRows[0]!]) {
400
+ exactRowKeys(row, ["cid", "name", "type", "notnull", "dflt_value", "pk", "hidden"]);
401
+ if (
402
+ row.type !== "TEXT"
403
+ || row.dflt_value !== null
404
+ || !pragmaInteger(row.notnull, 0n)
405
+ || !pragmaInteger(row.hidden, 0n)
406
+ ) fail("schema-mismatch");
407
+ }
408
+ if (
409
+ !pragmaInteger(jidRows[0]!.pk, 1n)
410
+ || !pragmaInteger(lidRows[0]!.pk, 0n)
411
+ ) fail("schema-mismatch");
412
+ }
413
+
414
+ function assertPrimaryKeyIndex(
415
+ database: Database,
416
+ table: "whatsmeow_contacts" | "whatsmeow_device",
417
+ expectedColumns: readonly string[],
418
+ requireOnlyIndex: boolean,
419
+ ): void {
420
+ const expectedIndexName = table === "whatsmeow_contacts"
421
+ ? "sqlite_autoindex_whatsmeow_contacts_1"
422
+ : "sqlite_autoindex_whatsmeow_device_1";
423
+ const indexList = rows(
424
+ database.query(`PRAGMA index_list('${table}')`).iterate(),
425
+ 256,
426
+ );
427
+ if (requireOnlyIndex && indexList.length !== 1) fail("schema-mismatch");
428
+ const primary = indexList.filter((row) => row.origin === "pk");
429
+ if (primary.length !== 1) fail("schema-mismatch");
430
+ const index = primary[0]!;
431
+ exactRowKeys(index, ["seq", "name", "unique", "origin", "partial"]);
432
+ if (
433
+ index.name !== expectedIndexName
434
+ || !pragmaInteger(index.unique, 1n)
435
+ || !pragmaInteger(index.partial, 0n)
436
+ ) fail("schema-mismatch");
437
+ const indexInfo = rows(table === "whatsmeow_contacts"
438
+ ? database.query(
439
+ "PRAGMA index_xinfo('sqlite_autoindex_whatsmeow_contacts_1')",
440
+ ).iterate()
441
+ : database.query(
442
+ "PRAGMA index_xinfo('sqlite_autoindex_whatsmeow_device_1')",
443
+ ).iterate(), expectedColumns.length + 1);
444
+ if (indexInfo.length !== expectedColumns.length + 1) fail("schema-mismatch");
445
+ for (const [position, column] of expectedColumns.entries()) {
446
+ const row = indexInfo[position]!;
447
+ exactRowKeys(row, ["seqno", "cid", "name", "desc", "coll", "key"]);
448
+ if (
449
+ !pragmaInteger(row.seqno, BigInt(position))
450
+ || row.name !== column
451
+ || !pragmaInteger(row.desc, 0n)
452
+ || row.coll !== "BINARY"
453
+ || !pragmaInteger(row.key, 1n)
454
+ ) fail("schema-mismatch");
455
+ }
456
+ const trailing = indexInfo.at(-1)!;
457
+ exactRowKeys(trailing, ["seqno", "cid", "name", "desc", "coll", "key"]);
458
+ if (
459
+ !pragmaInteger(trailing.seqno, BigInt(expectedColumns.length))
460
+ || !pragmaInteger(trailing.cid, -1n)
461
+ || trailing.name !== null
462
+ || !pragmaInteger(trailing.desc, 0n)
463
+ || trailing.coll !== "BINARY"
464
+ || !pragmaInteger(trailing.key, 0n)
465
+ ) fail("schema-mismatch");
466
+ }
467
+
468
+ function assertContactForeignKey(database: Database): void {
469
+ const foreignKeys = rows(
470
+ database.query("PRAGMA foreign_key_list('whatsmeow_contacts')").iterate(),
471
+ 2,
472
+ );
473
+ if (foreignKeys.length !== 1) fail("schema-mismatch");
474
+ const row = foreignKeys[0]!;
475
+ exactRowKeys(row, [
476
+ "id",
477
+ "seq",
478
+ "table",
479
+ "from",
480
+ "to",
481
+ "on_update",
482
+ "on_delete",
483
+ "match",
484
+ ]);
485
+ if (
486
+ !pragmaInteger(row.id, 0n)
487
+ || !pragmaInteger(row.seq, 0n)
488
+ || row.table !== "whatsmeow_device"
489
+ || row.from !== "our_jid"
490
+ || row.to !== "jid"
491
+ || row.on_update !== "CASCADE"
492
+ || row.on_delete !== "CASCADE"
493
+ || row.match !== "NONE"
494
+ ) fail("schema-mismatch");
495
+ }
496
+
497
+ function assertPinnedSchema(database: Database): void {
498
+ try {
499
+ assertTableList(database);
500
+ assertContactColumns(database);
501
+ assertDeviceIdentityColumns(database);
502
+ assertPrimaryKeyIndex(
503
+ database,
504
+ "whatsmeow_contacts",
505
+ ["our_jid", "their_jid"],
506
+ true,
507
+ );
508
+ assertPrimaryKeyIndex(database, "whatsmeow_device", ["jid"], false);
509
+ assertContactForeignKey(database);
510
+ } catch (error) {
511
+ if (error instanceof HelperFailure) throw error;
512
+ fail("schema-mismatch");
513
+ }
514
+ }
515
+
516
+ const DEVICE_PN_JID_PATTERN = /^([1-9][0-9]{4,14})(?::[0-9]{1,5})?@s\.whatsapp\.net$/u;
517
+ const LID_VALUE_PATTERN = /^([1-9][0-9]{4,19})(?::[0-9]{1,5})?@lid$/u;
518
+ const LID_DIGITS_PATTERN = /^[1-9][0-9]{4,19}$/u;
519
+
520
+ function normalizedDeviceJid(value: unknown): Readonly<{
521
+ id: string;
522
+ }> {
523
+ if (typeof value !== "string" || value.length > 96) fail("owner-mismatch");
524
+ const phone = DEVICE_PN_JID_PATTERN.exec(value);
525
+ const id = phone?.[1];
526
+ if (id === undefined) {
527
+ return fail("owner-mismatch");
528
+ }
529
+ return Object.freeze({ id });
530
+ }
531
+
532
+ function normalizedLid(value: unknown): string | null {
533
+ if (value === null || value === "") return null;
534
+ if (typeof value !== "string" || value.length > 96) fail("owner-mismatch");
535
+ if (LID_DIGITS_PATTERN.test(value)) return value;
536
+ const match = LID_VALUE_PATTERN.exec(value);
537
+ if (match === null || match[1] === undefined) fail("owner-mismatch");
538
+ return match[1];
539
+ }
540
+
541
+ function matchedOwnerIdentity(
542
+ database: Database,
543
+ accountSubject: string,
544
+ ): WhatsAppMatchedOwnerIdentity {
545
+ const subject = parseWhatsAppContactProjectionSubject(accountSubject);
546
+ let deviceRows: readonly SqliteRow[];
547
+ try {
548
+ deviceRows = rows(database.query(`
549
+ SELECT jid, lid
550
+ FROM whatsmeow_device
551
+ ORDER BY jid COLLATE BINARY ASC
552
+ LIMIT ${MAX_DEVICE_ROWS + 1}
553
+ `).iterate(), MAX_DEVICE_ROWS + 1);
554
+ } catch (error) {
555
+ if (error instanceof HelperFailure) throw error;
556
+ return fail("owner-mismatch");
557
+ }
558
+ if (deviceRows.length > MAX_DEVICE_ROWS) fail("owner-mismatch");
559
+ const matches: WhatsAppMatchedOwnerIdentity[] = [];
560
+ for (const row of deviceRows) {
561
+ exactRowKeys(row, ["jid", "lid"]);
562
+ if (typeof row.jid !== "string") fail("owner-mismatch");
563
+ const jid = normalizedDeviceJid(row.jid);
564
+ const lid = normalizedLid(row.lid);
565
+ const pnJid = `${jid.id}@s.whatsapp.net`;
566
+ const lidJid = lid === null ? null : `${lid}@lid`;
567
+ if (
568
+ (subject.kind === "pn" && jid.id === subject.id)
569
+ || (subject.kind === "lid" && lid === subject.id)
570
+ ) {
571
+ const selfJids = lidJid === null
572
+ ? Object.freeze([pnJid] as [string])
573
+ : Object.freeze([pnJid, lidJid].sort() as [string, string]);
574
+ matches.push(Object.freeze({
575
+ storageJid: row.jid,
576
+ selfJids,
577
+ accountJidAliases: Object.freeze({ pnJid, lidJid }),
578
+ }));
579
+ }
580
+ }
581
+ if (matches.length !== 1) fail("owner-mismatch");
582
+ return matches[0]!;
583
+ }
584
+
585
+ function optionalContactText(
586
+ value: unknown,
587
+ maximum: number,
588
+ ): string | null {
589
+ if (value === null || value === "") return null;
590
+ if (
591
+ typeof value !== "string"
592
+ || value.length > maximum
593
+ || /[\0\r]/u.test(value)
594
+ ) return fail("projection-invalid");
595
+ return value;
596
+ }
597
+
598
+ function projectContact(row: SqliteRow): WhatsAppContactProjectionContact {
599
+ exactRowKeys(row, [
600
+ "their_jid",
601
+ "first_name",
602
+ "full_name",
603
+ "push_name",
604
+ "business_name",
605
+ "redacted_phone",
606
+ ]);
607
+ let jid: ReturnType<typeof parseWhatsAppContactProjectionJid>;
608
+ try {
609
+ jid = parseWhatsAppContactProjectionJid(row.their_jid);
610
+ } catch {
611
+ return fail("projection-invalid");
612
+ }
613
+ const firstName = optionalContactText(row.first_name, 512);
614
+ const fullName = optionalContactText(row.full_name, 512);
615
+ const pushName = optionalContactText(row.push_name, 512);
616
+ const businessName = optionalContactText(row.business_name, 512);
617
+ const redactedPhone = optionalContactText(row.redacted_phone, 64);
618
+ const phone = jid.kind === "user" ? jid.id : null;
619
+ const candidates = [
620
+ [fullName, "full-name"],
621
+ [pushName, "push-name"],
622
+ [businessName, "business-name"],
623
+ [firstName, "first-name"],
624
+ [redactedPhone, "redacted-phone"],
625
+ [phone, "phone"],
626
+ ] as const satisfies readonly (readonly [string | null, WhatsAppContactDisplayNameBasis])[];
627
+ const selected = candidates.find(([value]) => value !== null);
628
+ return Object.freeze({
629
+ providerId: jid.jid,
630
+ jidKind: jid.kind,
631
+ phone,
632
+ redactedPhone,
633
+ firstName,
634
+ fullName,
635
+ pushName,
636
+ businessName,
637
+ displayName: selected?.[0] ?? null,
638
+ displayNameBasis: selected?.[1] ?? "unavailable",
639
+ });
640
+ }
641
+
642
+ function projectContacts(
643
+ database: Database,
644
+ request: WhatsAppContactProjectionRequest,
645
+ ): WhatsAppContactProjectionInternalSuccess {
646
+ const owner = matchedOwnerIdentity(database, request.accountSubject);
647
+ let projectedRows: readonly SqliteRow[];
648
+ try {
649
+ projectedRows = rows(database.query(`
650
+ SELECT
651
+ their_jid,
652
+ first_name,
653
+ full_name,
654
+ push_name,
655
+ business_name,
656
+ redacted_phone
657
+ FROM whatsmeow_contacts
658
+ WHERE our_jid = ?1 COLLATE BINARY
659
+ AND (?2 IS NULL OR their_jid > ?2 COLLATE BINARY)
660
+ AND (
661
+ their_jid GLOB '[0-9]*@s.whatsapp.net'
662
+ OR their_jid GLOB '[0-9]*@lid'
663
+ )
664
+ ORDER BY their_jid COLLATE BINARY ASC
665
+ LIMIT ?3
666
+ `).iterate(owner.storageJid, request.cursor, request.limit + 1), request.limit + 1);
667
+ } catch (error) {
668
+ if (error instanceof HelperFailure) throw error;
669
+ return fail("projection-invalid");
670
+ }
671
+ const projectedContacts = projectedRows.map(projectContact);
672
+ for (let index = 0; index < projectedContacts.length; index += 1) {
673
+ const previous = index === 0
674
+ ? request.cursor
675
+ : projectedContacts[index - 1]?.providerId;
676
+ if (
677
+ previous !== null
678
+ && previous !== undefined
679
+ && projectedContacts[index]!.providerId <= previous
680
+ ) {
681
+ fail("projection-invalid");
682
+ }
683
+ }
684
+ const hasMore = projectedContacts.length > request.limit;
685
+ const contacts = Object.freeze(projectedContacts.slice(0, request.limit));
686
+ const result = {
687
+ schemaVersion: WHATSAPP_CONTACT_PROJECTION_PROTOCOL_VERSION,
688
+ status: "succeeded",
689
+ contacts,
690
+ nextCursor: hasMore ? contacts.at(-1)?.providerId ?? null : null,
691
+ localContactTablePageComplete: !hasMore,
692
+ } as WhatsAppContactProjectionSuccess;
693
+ Object.defineProperty(result, WHATSAPP_MATCHED_OWNER_IDENTITY, {
694
+ configurable: false,
695
+ enumerable: false,
696
+ value: owner,
697
+ writable: false,
698
+ });
699
+ return Object.freeze(result) as WhatsAppContactProjectionInternalSuccess;
700
+ }
701
+
702
+ function projectDatabaseBuffer(
703
+ buffer: Buffer,
704
+ request: WhatsAppContactProjectionRequest,
705
+ ): WhatsAppContactProjectionInternalSuccess {
706
+ let database: Database;
707
+ try {
708
+ database = Database.deserialize(buffer, {
709
+ readonly: true,
710
+ strict: true,
711
+ safeIntegers: true,
712
+ });
713
+ } catch {
714
+ return fail("database-invalid");
715
+ }
716
+ try {
717
+ configureDatabase(database);
718
+ assertIntegrity(database);
719
+ assertPinnedSchema(database);
720
+ return projectContacts(database, request);
721
+ } finally {
722
+ try {
723
+ database.close();
724
+ } catch {
725
+ fail("database-invalid");
726
+ }
727
+ }
728
+ }
729
+
730
+ export function projectWhatsAppContactsFromBoundCwd(
731
+ requestValue: unknown,
732
+ ): WhatsAppContactProjectionInternalSuccess {
733
+ const request = parseWhatsAppContactProjectionRequest(requestValue);
734
+ const initialCwd = assertBoundCwd(request);
735
+ assertNoSidecars();
736
+ const noFollow = constants.O_NOFOLLOW;
737
+ if (typeof noFollow !== "number" || noFollow === 0) {
738
+ return fail("session-file-invalid");
739
+ }
740
+ const nonBlock = typeof constants.O_NONBLOCK === "number"
741
+ ? constants.O_NONBLOCK
742
+ : 0;
743
+ let descriptor: number;
744
+ try {
745
+ descriptor = openSync(
746
+ SESSION_DATABASE_NAME,
747
+ constants.O_RDONLY | noFollow | nonBlock,
748
+ );
749
+ } catch {
750
+ return fail("session-file-invalid");
751
+ }
752
+
753
+ let initialFile: BigIntStats | undefined;
754
+ let buffer: Buffer | undefined;
755
+ let result: WhatsAppContactProjectionInternalSuccess | undefined;
756
+ let failure: unknown;
757
+ try {
758
+ initialFile = fstatSync(descriptor, { bigint: true });
759
+ assertSessionFile(initialFile, request);
760
+ exactSessionPathSnapshot(initialFile, request);
761
+ buffer = readExactSessionDatabase(descriptor, initialFile);
762
+ const afterRead = fstatSync(descriptor, { bigint: true });
763
+ if (!sameSnapshot(initialFile, afterRead)) fail("session-file-invalid");
764
+ exactSessionPathSnapshot(initialFile, request);
765
+ assertBoundCwd(request, initialCwd);
766
+ result = projectDatabaseBuffer(buffer, request);
767
+ } catch (error) {
768
+ failure = error;
769
+ }
770
+
771
+ try {
772
+ if (initialFile !== undefined) {
773
+ const afterProjection = fstatSync(descriptor, { bigint: true });
774
+ if (!sameSnapshot(initialFile, afterProjection)) fail("session-file-invalid");
775
+ exactSessionPathSnapshot(initialFile, request);
776
+ }
777
+ assertBoundCwd(request, initialCwd);
778
+ } catch (error) {
779
+ failure = error;
780
+ } finally {
781
+ buffer?.fill(0);
782
+ try {
783
+ closeSync(descriptor);
784
+ } catch {
785
+ failure = new HelperFailure("session-read-failed");
786
+ }
787
+ }
788
+
789
+ try {
790
+ if (initialFile !== undefined) exactSessionPathSnapshot(initialFile, request);
791
+ assertBoundCwd(request, initialCwd);
792
+ assertNoSidecars();
793
+ } catch (error) {
794
+ failure = error;
795
+ }
796
+ if (failure !== undefined) {
797
+ throw failure instanceof Error
798
+ ? failure
799
+ : new HelperFailure("database-invalid");
800
+ }
801
+ if (result === undefined) return fail("projection-invalid");
802
+ return result;
803
+ }
804
+
805
+ async function readBoundedStdin(): Promise<unknown> {
806
+ const reader = Bun.stdin.stream().getReader();
807
+ const chunks: Uint8Array[] = [];
808
+ let length = 0;
809
+ try {
810
+ for (;;) {
811
+ const item = await reader.read();
812
+ if (item.done) break;
813
+ length += item.value.byteLength;
814
+ if (length > WHATSAPP_CONTACT_PROJECTION_MAX_STDIN_BYTES) {
815
+ return fail("request-invalid");
816
+ }
817
+ chunks.push(item.value);
818
+ }
819
+ } catch {
820
+ return fail("request-invalid");
821
+ } finally {
822
+ reader.releaseLock();
823
+ }
824
+ if (length < 2) return fail("request-invalid");
825
+ let text: string;
826
+ try {
827
+ const bytes = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)), length);
828
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
829
+ bytes.fill(0);
830
+ } catch {
831
+ return fail("request-invalid");
832
+ }
833
+ try {
834
+ return JSON.parse(text) as unknown;
835
+ } catch {
836
+ return fail("request-invalid");
837
+ }
838
+ }
839
+
840
+ export async function runWhatsAppContactProjectionHelper(): Promise<
841
+ WhatsAppContactProjectionResponse
842
+ > {
843
+ let request: unknown;
844
+ try {
845
+ if (process.argv.length !== 2) fail("request-invalid");
846
+ request = await readBoundedStdin();
847
+ request = parseWhatsAppContactProjectionRequest(request);
848
+ } catch {
849
+ return createWhatsAppContactProjectionFailure("request-invalid");
850
+ }
851
+ try {
852
+ return projectWhatsAppContactsFromBoundCwd(request);
853
+ } catch (error) {
854
+ return createWhatsAppContactProjectionFailure(errorCode(error));
855
+ }
856
+ }
857
+
858
+ async function main(): Promise<void> {
859
+ let response = await runWhatsAppContactProjectionHelper();
860
+ let output = `${JSON.stringify(response)}\n`;
861
+ if (Buffer.byteLength(output, "utf8") > WHATSAPP_CONTACT_PROJECTION_MAX_STDOUT_BYTES) {
862
+ response = createWhatsAppContactProjectionFailure("output-too-large");
863
+ output = `${JSON.stringify(response)}\n`;
864
+ }
865
+ await Bun.write(Bun.stdout, output);
866
+ }
867
+
868
+ if (import.meta.main) {
869
+ try {
870
+ await main();
871
+ } catch {
872
+ process.exitCode = 1;
873
+ }
874
+ }