@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,301 @@
1
+ import { types as nodeTypes } from "node:util";
2
+
3
+ export type ContactDirectionStatsLastAtBasis =
4
+ | "bounded-local-message-timestamp"
5
+ | "bounded-matched-message-internal-date"
6
+ | "unavailable";
7
+
8
+ export type ContactDirectionStats = Readonly<{
9
+ count: number | null;
10
+ complete: boolean;
11
+ lowerBound: boolean;
12
+ truncated: boolean;
13
+ lastAt: string | null;
14
+ lastAtComplete: boolean;
15
+ lastAtBasis: ContactDirectionStatsLastAtBasis;
16
+ incompleteReasons: readonly string[];
17
+ }>;
18
+
19
+ export type ContactDirectionStatsProjection = Readonly<{
20
+ sentCount: number | null;
21
+ sentCountComplete: boolean;
22
+ sentCountLowerBound: boolean;
23
+ sentCountTruncated: boolean;
24
+ receivedCount: number | null;
25
+ receivedCountComplete: boolean;
26
+ receivedCountLowerBound: boolean;
27
+ receivedCountTruncated: boolean;
28
+ lastSentAt: string | null;
29
+ lastSentAtComplete: boolean;
30
+ lastSentAtBasis: ContactDirectionStatsLastAtBasis;
31
+ sentStatsIncompleteReasons: readonly string[];
32
+ lastReceivedAt: string | null;
33
+ lastReceivedAtComplete: boolean;
34
+ lastReceivedAtBasis: ContactDirectionStatsLastAtBasis;
35
+ receivedStatsIncompleteReasons: readonly string[];
36
+ }>;
37
+
38
+ type JsonRecord = Readonly<Record<string, unknown>>;
39
+
40
+ const DIRECTION_STATS_KEYS = Object.freeze([
41
+ "count",
42
+ "complete",
43
+ "lowerBound",
44
+ "truncated",
45
+ "lastAt",
46
+ "lastAtComplete",
47
+ "lastAtBasis",
48
+ "incompleteReasons",
49
+ ] as const);
50
+ const LAST_AT_BASES: ReadonlySet<unknown> = new Set<ContactDirectionStatsLastAtBasis>([
51
+ "bounded-local-message-timestamp",
52
+ "bounded-matched-message-internal-date",
53
+ "unavailable",
54
+ ]);
55
+ const MAX_INCOMPLETE_REASONS = 32;
56
+ const MAX_INCOMPLETE_REASON_LENGTH = 128;
57
+ const INCOMPLETE_REASON_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
58
+ const RFC3339_UTC_PATTERN = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?Z$/;
59
+
60
+ function fail(label: string, message: string): never {
61
+ throw new Error(`${label} ${message}`);
62
+ }
63
+
64
+ function strictRecord(value: unknown, label: string): JsonRecord {
65
+ if (
66
+ nodeTypes.isProxy(value)
67
+ || typeof value !== "object"
68
+ || value === null
69
+ || Array.isArray(value)
70
+ || (
71
+ Object.getPrototypeOf(value) !== Object.prototype
72
+ && Object.getPrototypeOf(value) !== null
73
+ )
74
+ ) return fail(label, "must be a plain object");
75
+ const descriptors = Object.getOwnPropertyDescriptors(value);
76
+ for (const key of Reflect.ownKeys(descriptors)) {
77
+ if (typeof key !== "string") return fail(label, "must not contain symbol properties");
78
+ const descriptor = descriptors[key];
79
+ if (
80
+ descriptor === undefined
81
+ || !("value" in descriptor)
82
+ || !descriptor.enumerable
83
+ ) return fail(`${label}.${key}`, "must be an enumerable data property");
84
+ }
85
+ return value as JsonRecord;
86
+ }
87
+
88
+ function exactDirectionStatsKeys(value: JsonRecord, label: string): void {
89
+ const allowed = new Set<string>(DIRECTION_STATS_KEYS);
90
+ for (const key of Object.keys(value)) {
91
+ if (!allowed.has(key)) return fail(label, `contains unreviewed property ${key}`);
92
+ }
93
+ for (const key of DIRECTION_STATS_KEYS) {
94
+ if (!Object.hasOwn(value, key)) return fail(`${label}.${key}`, "is required");
95
+ }
96
+ }
97
+
98
+ function booleanValue(value: unknown, label: string): boolean {
99
+ if (typeof value !== "boolean") return fail(label, "must be boolean");
100
+ return value;
101
+ }
102
+
103
+ function countValue(value: unknown, label: string): number | null {
104
+ if (value === null) return null;
105
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
106
+ return fail(label, "must be null or a non-negative safe integer");
107
+ }
108
+ return value;
109
+ }
110
+
111
+ function isLeapYear(year: number): boolean {
112
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
113
+ }
114
+
115
+ function daysInMonth(year: number, month: number): number {
116
+ if (month === 2) return isLeapYear(year) ? 29 : 28;
117
+ return month === 4 || month === 6 || month === 9 || month === 11 ? 30 : 31;
118
+ }
119
+
120
+ function rfc3339UtcValue(value: unknown, label: string): string | null {
121
+ if (value === null) return null;
122
+ if (typeof value !== "string") return fail(label, "must be null or an RFC 3339 UTC timestamp");
123
+ const match = RFC3339_UTC_PATTERN.exec(value);
124
+ if (match === null) return fail(label, "must be null or an RFC 3339 UTC timestamp");
125
+ const [, yearText, monthText, dayText, hourText, minuteText, secondText] = match;
126
+ const year = Number(yearText);
127
+ const month = Number(monthText);
128
+ const day = Number(dayText);
129
+ const hour = Number(hourText);
130
+ const minute = Number(minuteText);
131
+ const second = Number(secondText);
132
+ if (
133
+ !Number.isInteger(year)
134
+ || !Number.isInteger(month)
135
+ || !Number.isInteger(day)
136
+ || !Number.isInteger(hour)
137
+ || !Number.isInteger(minute)
138
+ || !Number.isInteger(second)
139
+ || month < 1
140
+ || month > 12
141
+ || day < 1
142
+ || day > daysInMonth(year, month)
143
+ || hour > 23
144
+ || minute > 59
145
+ || second > 60
146
+ ) return fail(label, "must be null or an RFC 3339 UTC timestamp");
147
+ return value;
148
+ }
149
+
150
+ function lastAtBasisValue(value: unknown, label: string): ContactDirectionStatsLastAtBasis {
151
+ if (!LAST_AT_BASES.has(value)) {
152
+ return fail(
153
+ label,
154
+ "must be bounded-local-message-timestamp, bounded-matched-message-internal-date, or unavailable",
155
+ );
156
+ }
157
+ return value as ContactDirectionStatsLastAtBasis;
158
+ }
159
+
160
+ function strictReasons(value: unknown, label: string): readonly string[] {
161
+ if (
162
+ nodeTypes.isProxy(value)
163
+ || !Array.isArray(value)
164
+ || Object.getPrototypeOf(value) !== Array.prototype
165
+ ) return fail(label, "must be a plain array");
166
+ if (value.length > MAX_INCOMPLETE_REASONS) {
167
+ return fail(label, `must contain at most ${MAX_INCOMPLETE_REASONS} reasons`);
168
+ }
169
+ const descriptors = Object.getOwnPropertyDescriptors(value);
170
+ for (const key of Reflect.ownKeys(descriptors)) {
171
+ if (typeof key !== "string") return fail(label, "must not contain symbol properties");
172
+ if (key === "length") continue;
173
+ const descriptor = descriptors[key];
174
+ if (
175
+ descriptor === undefined
176
+ || !("value" in descriptor)
177
+ || !descriptor.enumerable
178
+ ) return fail(`${label}.${key}`, "must be an enumerable data property");
179
+ const index = Number(key);
180
+ if (!Number.isSafeInteger(index) || index < 0 || String(index) !== key || index >= value.length) {
181
+ return fail(label, `contains unreviewed property ${key}`);
182
+ }
183
+ }
184
+ const reasons: string[] = [];
185
+ const seen = new Set<string>();
186
+ for (let index = 0; index < value.length; index += 1) {
187
+ if (!Object.hasOwn(value, index)) return fail(label, "must not be sparse");
188
+ const reason: unknown = value[index];
189
+ if (
190
+ typeof reason !== "string"
191
+ || reason.length === 0
192
+ || reason.length > MAX_INCOMPLETE_REASON_LENGTH
193
+ || !INCOMPLETE_REASON_PATTERN.test(reason)
194
+ ) {
195
+ return fail(
196
+ `${label}[${index}]`,
197
+ `must be a kebab-case reason of at most ${MAX_INCOMPLETE_REASON_LENGTH} code units`,
198
+ );
199
+ }
200
+ if (seen.has(reason)) return fail(label, `contains duplicate reason ${reason}`);
201
+ seen.add(reason);
202
+ reasons.push(reason);
203
+ }
204
+ return Object.freeze(reasons);
205
+ }
206
+
207
+ /** Parse and freeze one provider's completeness-aware sent or received statistics. */
208
+ export function parseContactDirectionStats(
209
+ value: unknown,
210
+ label = "contact direction stats",
211
+ ): ContactDirectionStats {
212
+ const source = strictRecord(value, label);
213
+ exactDirectionStatsKeys(source, label);
214
+ const count = countValue(source.count, `${label}.count`);
215
+ const complete = booleanValue(source.complete, `${label}.complete`);
216
+ const lowerBound = booleanValue(source.lowerBound, `${label}.lowerBound`);
217
+ const truncated = booleanValue(source.truncated, `${label}.truncated`);
218
+ const lastAt = rfc3339UtcValue(source.lastAt, `${label}.lastAt`);
219
+ const lastAtComplete = booleanValue(source.lastAtComplete, `${label}.lastAtComplete`);
220
+ const lastAtBasis = lastAtBasisValue(source.lastAtBasis, `${label}.lastAtBasis`);
221
+ const incompleteReasons = strictReasons(
222
+ source.incompleteReasons,
223
+ `${label}.incompleteReasons`,
224
+ );
225
+
226
+ if (count === null && (complete || lowerBound || truncated)) {
227
+ return fail(label, "cannot mark an unavailable count complete, lower-bound, or truncated");
228
+ }
229
+ if (count !== null && complete === lowerBound) {
230
+ return fail(label, "a numeric count must be exactly complete or lower-bound");
231
+ }
232
+ if (lowerBound && complete) return fail(label, "a lower-bound count cannot be complete");
233
+ if (truncated && !lowerBound) return fail(label, "a truncated count must be a lower bound");
234
+ if (lastAt === null && lastAtBasis !== "unavailable") {
235
+ return fail(label, "must use an unavailable basis when lastAt is null");
236
+ }
237
+ if (lastAt !== null && lastAtBasis === "unavailable") {
238
+ return fail(label, "must identify a bounded basis when lastAt is available");
239
+ }
240
+ if (
241
+ lastAt === null
242
+ && lastAtComplete
243
+ && !(count === 0 && complete)
244
+ ) {
245
+ return fail(
246
+ label,
247
+ "can mark a missing lastAt complete only for an exactly complete zero count",
248
+ );
249
+ }
250
+ if (count === 0 && lastAt !== null) {
251
+ return fail(label, "cannot have lastAt when the observed count is zero");
252
+ }
253
+ if (count !== null && count > 0 && lastAtComplete && lastAt === null) {
254
+ return fail(label, "a complete lastAt for a positive count must be available");
255
+ }
256
+ const whollyComplete = complete && lastAtComplete;
257
+ if ((incompleteReasons.length === 0) !== whollyComplete) {
258
+ return fail(
259
+ label,
260
+ "must have no incomplete reasons exactly when both count and lastAt are complete",
261
+ );
262
+ }
263
+
264
+ return Object.freeze({
265
+ count,
266
+ complete,
267
+ lowerBound,
268
+ truncated,
269
+ lastAt,
270
+ lastAtComplete,
271
+ lastAtBasis,
272
+ incompleteReasons,
273
+ });
274
+ }
275
+
276
+ /** Project sent and received statistics into the stable flat contact output fields. */
277
+ export function projectContactDirectionStats(
278
+ sentValue: unknown,
279
+ receivedValue: unknown,
280
+ ): ContactDirectionStatsProjection {
281
+ const sent = parseContactDirectionStats(sentValue, "sent contact direction stats");
282
+ const received = parseContactDirectionStats(receivedValue, "received contact direction stats");
283
+ return Object.freeze({
284
+ sentCount: sent.count,
285
+ sentCountComplete: sent.complete,
286
+ sentCountLowerBound: sent.lowerBound,
287
+ sentCountTruncated: sent.truncated,
288
+ receivedCount: received.count,
289
+ receivedCountComplete: received.complete,
290
+ receivedCountLowerBound: received.lowerBound,
291
+ receivedCountTruncated: received.truncated,
292
+ lastSentAt: sent.lastAt,
293
+ lastSentAtComplete: sent.lastAtComplete,
294
+ lastSentAtBasis: sent.lastAtBasis,
295
+ sentStatsIncompleteReasons: sent.incompleteReasons,
296
+ lastReceivedAt: received.lastAt,
297
+ lastReceivedAtComplete: received.lastAtComplete,
298
+ lastReceivedAtBasis: received.lastAtBasis,
299
+ receivedStatsIncompleteReasons: received.incompleteReasons,
300
+ });
301
+ }
@@ -0,0 +1,94 @@
1
+ import { GITHUB_API_ORIGIN, GITHUB_REPOSITORIES_PER_PAGE } from "./github-web";
2
+
3
+ export function parseJson(
4
+ bytes: Uint8Array,
5
+ label: string
6
+
7
+ ): unknown {
8
+ if (bytes.byteLength === 0) {
9
+ throw new Error(`${label} was empty`);
10
+ }
11
+ let text: string;
12
+ try {
13
+ text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
14
+ } catch {
15
+ throw new Error(`${label} was not valid UTF-8`);
16
+ }
17
+ try {
18
+ return JSON.parse(text) as unknown;
19
+ } catch {
20
+ throw new Error(`${label} was not valid JSON`);
21
+ }
22
+ }
23
+
24
+ export function organizationRepositoriesUrl(
25
+ organization: string,
26
+ page: number
27
+
28
+ ): URL {
29
+ const url = new URL(`/orgs/${organization}/repos`, GITHUB_API_ORIGIN);
30
+ url.searchParams.set("type", "public");
31
+ url.searchParams.set("per_page", String(GITHUB_REPOSITORIES_PER_PAGE));
32
+ url.searchParams.set("page", String(page));
33
+ return url;
34
+ }
35
+
36
+ export function exactNextPageLink(
37
+ raw: string | null,
38
+ organization: string,
39
+ expectedPage: number,
40
+
41
+ ): boolean {
42
+ if (raw === null) return false;
43
+ let next = false;
44
+ for (const segment of raw.split(",")) {
45
+ const match = /^<([^<>]+)>;\s*rel="(first|last|next|prev)"$/u.exec(
46
+ segment.trim(),
47
+ );
48
+ if (match === null) {
49
+ throw new Error("GitHub organization repository pagination header drifted");
50
+ }
51
+ if (match[2] !== "next") continue;
52
+ if (next) {
53
+ throw new Error("GitHub organization repository pagination repeated next page");
54
+ }
55
+ const nextTarget = match[1];
56
+ if (nextTarget === undefined) {
57
+ throw new Error("GitHub organization repository pagination next link was invalid");
58
+ }
59
+ let url: URL;
60
+ try {
61
+ url = new URL(nextTarget);
62
+ } catch {
63
+ throw new Error("GitHub organization repository pagination next link was invalid");
64
+ }
65
+ const expected = organizationRepositoriesUrl(organization, expectedPage);
66
+ const keys = [...url.searchParams.keys()].sort();
67
+ if (
68
+ url.origin !== expected.origin
69
+ || url.pathname !== expected.pathname
70
+ || url.username !== ""
71
+ || url.password !== ""
72
+ || url.hash !== ""
73
+ || keys.join(",") !== "page,per_page,type"
74
+ || url.searchParams.getAll("page").join(",") !== String(expectedPage)
75
+ || url.searchParams.getAll("per_page").join(",")
76
+ !== String(GITHUB_REPOSITORIES_PER_PAGE)
77
+ || url.searchParams.getAll("type").join(",") !== "public"
78
+ ) {
79
+ throw new Error("GitHub organization repository pagination next link drifted");
80
+ }
81
+ next = true;
82
+ }
83
+ return next;
84
+ }
85
+
86
+ export function repositoryPage(value: unknown): readonly unknown[] {
87
+ if (
88
+ !Array.isArray(value)
89
+ || value.length > GITHUB_REPOSITORIES_PER_PAGE
90
+ ) {
91
+ throw new Error("GitHub organization repository page was not one bounded array");
92
+ }
93
+ return Object.freeze([...value]);
94
+ }
@@ -0,0 +1,165 @@
1
+ import * as Context from "effect/Context";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import type { WebSessionRecipe } from "../model";
5
+ import { OperationDeadlineError } from "../operation-deadline";
6
+ import { pinnedHttpsFetch, type PinnedHttpsFetch } from "../pinned-https";
7
+ import { readAttempt, ReadEffectFailure, withReadResource } from "../read-effect";
8
+ import type { WebSessionOperationDeadline } from "../web-session-execution";
9
+ import { parseJson } from "./github-read-model";
10
+ import {
11
+ ProviderReadResponseRejectedError,
12
+ ProviderReadThrottledError,
13
+ ProviderReadTransportError
14
+ } from "./read-failure";
15
+
16
+ export type GitHubReadRequest = {
17
+ readonly url: URL;
18
+ readonly userAgent: string;
19
+ readonly operationLabel: string;
20
+ readonly apiLabel: string;
21
+ readonly responseLabel: string;
22
+ readonly pagination: boolean;
23
+ };
24
+
25
+ export type GitHubReadDependencies = { readonly fetch?: PinnedHttpsFetch; readonly now?: () => number; };
26
+
27
+ export class GitHubReadPlatform extends Context.Tag("wrench/GitHubReadPlatform/v1")<GitHubReadPlatform, {
28
+ readonly request: (request: GitHubReadRequest) => Effect.Effect<{ readonly value: unknown; readonly link: string | null; }, ReadEffectFailure>;
29
+ readonly observedAt: Effect.Effect<string, ReadEffectFailure>;
30
+ }>() { }
31
+
32
+ const native = <A>(work: () => Promise<A>): Effect.Effect<A, ReadEffectFailure> =>
33
+ Effect.tryPromise({ try: work, catch: cause => new ReadEffectFailure({ cause }) });
34
+
35
+ export function GitHubReadPlatformLive(
36
+ recipe: WebSessionRecipe,
37
+ dependencies: GitHubReadDependencies | undefined,
38
+ deadline: WebSessionOperationDeadline | undefined
39
+
40
+ ): Layer.Layer<GitHubReadPlatform, ReadEffectFailure> {
41
+ return Layer.scoped(
42
+ GitHubReadPlatform,
43
+ Effect.gen(function*() {
44
+ const operation = yield* Effect.acquireRelease(
45
+ readAttempt(() => {
46
+ if (deadline !== undefined) return { signal: deadline.signal, dispose: () => { } };
47
+ const controller = new AbortController();
48
+ const timeout = setTimeout(() => controller.abort(), recipe.timeoutMs);
49
+ return { signal: controller.signal, dispose: () => clearTimeout(timeout) };
50
+ }),
51
+ operation => Effect.sync(operation.dispose)
52
+ );
53
+ const fetch = dependencies?.fetch ?? pinnedHttpsFetch;
54
+ const maximum = Math.min(recipe.maxOutputBytes, 2 * 1024 * 1024);
55
+ return {
56
+ observedAt: readAttempt(() => new Date(dependencies?.now?.() ?? Date.now()).toISOString()),
57
+ request: (request: GitHubReadRequest) => Effect.gen(function*() {
58
+ const timeout = yield* readAttempt(() => {
59
+ deadline?.throwIfUnavailable(request.operationLabel);
60
+ const remaining = Math.min(recipe.timeoutMs, deadline?.remainingTimeMs() ?? recipe.timeoutMs);
61
+ if (remaining < 1_000) throw new OperationDeadlineError(request.operationLabel, "timed-out");
62
+ return remaining;
63
+ });
64
+ const response = yield* native(() => fetch(
65
+ request.url,
66
+ {
67
+ method: "GET",
68
+ headers: {
69
+ accept: "application/vnd.github+json",
70
+ "user-agent": request.userAgent,
71
+ "x-github-api-version": "2026-03-10"
72
+ },
73
+ redirect: "error",
74
+ signal: operation.signal,
75
+ },
76
+ timeout
77
+ )).pipe(Effect.mapError(error => {
78
+ if (operation.signal.aborted) {
79
+ try { deadline?.throwIfUnavailable(request.operationLabel); }
80
+ catch (cause) { return new ReadEffectFailure({ cause }); }
81
+ return new ReadEffectFailure({ cause: new OperationDeadlineError(request.operationLabel, "timed-out") });
82
+ }
83
+ return new ReadEffectFailure({ cause: new ProviderReadTransportError(error.cause) });
84
+ }));
85
+ const metadata = yield* readAttempt(() => {
86
+ if (response.status !== 200) {
87
+ const retryAfter = response.headers.get("retry-after");
88
+ if (response.status === 403
89
+ && (response.headers.get("x-ratelimit-remaining") === "0"
90
+ || (retryAfter !== null && /^(?:0|[1-9][0-9]{0,8})$/u.test(retryAfter)))) throw new ProviderReadThrottledError();
91
+ throw new ProviderReadResponseRejectedError(response.status);
92
+ }
93
+ const type = response.headers.get("content-type")?.split(";", 1)[0]?.trim().toLowerCase();
94
+ if (type !== "application/json" && type?.endsWith("+json") !== true) throw new Error(`${request.apiLabel} returned an unreviewed content type`);
95
+ const link = request.pagination ? response.headers.get("link") : null;
96
+ if (link !== null && (link.length > 8 * 1024 || /[\0\r\n]/u.test(link))) throw new Error("GitHub organization repository pagination header exceeded its reviewed bounds");
97
+ const declared = response.headers.get("content-length");
98
+ if (declared !== null) {
99
+ const length = Number(declared);
100
+ if (!Number.isSafeInteger(length) || length < 0 || length > maximum) throw new Error(`${request.responseLabel} exceeded its byte limit`);
101
+ }
102
+ return link;
103
+ }).pipe(Effect.catchTag(
104
+ "ReadEffectFailure",
105
+ primary => native(() => response.body?.cancel() ?? Promise.resolve()).pipe(
106
+ Effect.timeoutFail({
107
+ duration: 500,
108
+ onTimeout: () => new ReadEffectFailure({ cause: new Error("GitHub response cleanup did not settle") })
109
+ }),
110
+ Effect.matchEffect({
111
+ onFailure: cleanup => Effect.fail(new ReadEffectFailure({ cause: primary.cause, cleanupCause: cleanup.cause })),
112
+ onSuccess: () => Effect.fail(primary)
113
+ })
114
+ )
115
+ ));
116
+ const bytes = response.body === null
117
+ ? new Uint8Array()
118
+ : yield* withReadResource(
119
+ readAttempt(() => response.body!.getReader()),
120
+ reader => Effect.gen(function*() {
121
+ const chunks: Uint8Array[] = [];
122
+ let length = 0;
123
+ while (true) {
124
+ const result = yield* native(() => deadline === undefined
125
+ ? reader.read()
126
+ : deadline.run(() => reader.read(), request.responseLabel)).pipe(
127
+ Effect.mapError(error => error.cause instanceof OperationDeadlineError
128
+ ? error
129
+ : new ReadEffectFailure({ cause: new ProviderReadTransportError(error.cause) })),
130
+ );
131
+ if (result.done) break;
132
+ yield* readAttempt(() => {
133
+ if (!(result.value instanceof Uint8Array)) throw new Error(`${request.responseLabel} returned an unreviewed body chunk`);
134
+ length += result.value.byteLength;
135
+ if (length > maximum) throw new Error(`${request.responseLabel} exceeded its byte limit`);
136
+ chunks.push(result.value);
137
+ });
138
+ }
139
+ return yield* readAttempt(() => {
140
+ const joined = new Uint8Array(length);
141
+ let offset = 0;
142
+ for (const chunk of chunks) { joined.set(chunk, offset); offset += chunk.byteLength; }
143
+ return joined;
144
+ });
145
+ }).pipe(Effect.catchTag(
146
+ "ReadEffectFailure",
147
+ primary => native(() => reader.cancel()).pipe(
148
+ Effect.timeoutFail({
149
+ duration: 500,
150
+ onTimeout: () => new ReadEffectFailure({ cause: new Error("GitHub response cleanup did not settle") })
151
+ }),
152
+ Effect.matchEffect({
153
+ onFailure: cleanup => Effect.fail(new ReadEffectFailure({ cause: primary.cause, cleanupCause: cleanup.cause })),
154
+ onSuccess: () => Effect.fail(primary)
155
+ })
156
+ )
157
+ )),
158
+ reader => readAttempt(() => reader.releaseLock()),
159
+ );
160
+ return { value: yield* readAttempt(() => parseJson(bytes, request.responseLabel)), link: metadata };
161
+ }),
162
+ };
163
+ })
164
+ );
165
+ }
@@ -0,0 +1,97 @@
1
+ import * as Effect from "effect/Effect";
2
+ import { readAttempt, type ReadEffectFailure } from "../read-effect";
3
+ import {
4
+ readFailureProjection,
5
+ type WebSessionExecution,
6
+ } from "../web-session-execution";
7
+ import { GitHubReadPlatform } from "./github-read-platform";
8
+ import {
9
+ exactNextPageLink,
10
+ organizationRepositoriesUrl,
11
+ repositoryPage
12
+ } from "./github-read-model";
13
+ import {
14
+ GITHUB_API_ORIGIN,
15
+ GITHUB_MAX_ORGANIZATION_REPOSITORIES,
16
+ GITHUB_MAX_ORGANIZATION_REPOSITORY_PAGES,
17
+ GITHUB_REPOSITORIES_PER_PAGE,
18
+ parseGitHubOrganizationRead,
19
+ projectGitHubOrganizationRepository,
20
+ projectGitHubOrganizationStats
21
+ } from "./github-web";
22
+ import { failedProviderRead, type ProviderReadFailureStage } from "./read-failure";
23
+
24
+ export function githubOrganizationReadProgram(requestedOrganization: string): Effect.Effect<WebSessionExecution, ReadEffectFailure, GitHubReadPlatform> {
25
+ return Effect.gen(function*() {
26
+ const platform = yield* GitHubReadPlatform;
27
+ let stage: ProviderReadFailureStage = "target";
28
+ return yield* Effect.gen(function*() {
29
+ const organizationResponse = yield* platform.request({
30
+ url: new URL(`/orgs/${requestedOrganization}`, GITHUB_API_ORIGIN),
31
+ userAgent: "wrench-github-organization-stats/1.1.0",
32
+ operationLabel: "GitHub public organization statistics read deadline",
33
+ apiLabel: "GitHub public organization API",
34
+ responseLabel: "GitHub organization response",
35
+ pagination: false,
36
+ });
37
+ const organization = yield* readAttempt(() => parseGitHubOrganizationRead(organizationResponse.value, requestedOrganization));
38
+ const pageCount = yield* readAttempt(() => {
39
+ if (organization.publicRepositories > GITHUB_MAX_ORGANIZATION_REPOSITORIES) throw new Error("GitHub organization public repository count exceeded the reviewed pagination bound");
40
+ const count = Math.ceil(organization.publicRepositories / GITHUB_REPOSITORIES_PER_PAGE);
41
+ if (count > GITHUB_MAX_ORGANIZATION_REPOSITORY_PAGES) throw new Error("GitHub organization public repository page count exceeded the reviewed pagination bound");
42
+ return count;
43
+ });
44
+ const repositoryIds = new Set<number>();
45
+ let totalStars = 0;
46
+ stage = "supplemental";
47
+ for (let page = 1; page <= pageCount; page += 1) {
48
+ const response = yield* platform.request({
49
+ url: organizationRepositoriesUrl(organization.organization, page),
50
+ userAgent: "wrench-github-organization-stats/1.1.0",
51
+ operationLabel: "GitHub public organization statistics read deadline",
52
+ apiLabel: "GitHub public organization repository API",
53
+ responseLabel: "GitHub organization repository response",
54
+ pagination: true,
55
+ });
56
+ yield* readAttempt(() => {
57
+ const repositories = repositoryPage(response.value);
58
+ const remaining = organization.publicRepositories - (page - 1) * GITHUB_REPOSITORIES_PER_PAGE;
59
+ if (repositories.length !== Math.min(GITHUB_REPOSITORIES_PER_PAGE, remaining)) throw new Error("GitHub organization repository page did not complete the declared public repository set");
60
+ if (exactNextPageLink(response.link, organization.organization, page + 1) !== (page < pageCount)) throw new Error("GitHub organization repository pagination did not complete the declared public repository set");
61
+ for (const value of repositories) {
62
+ const repository = projectGitHubOrganizationRepository(value, organization);
63
+ if (repositoryIds.has(repository.id)) throw new Error("GitHub organization repository pagination repeated one repository");
64
+ repositoryIds.add(repository.id);
65
+ if (repository.stars > Number.MAX_SAFE_INTEGER - totalStars) throw new Error("GitHub organization star total exceeded a safe integer");
66
+ totalStars += repository.stars;
67
+ }
68
+ });
69
+ }
70
+ const observedAt = yield* platform.observedAt;
71
+ const output = yield* readAttempt(() => {
72
+ if (repositoryIds.size !== organization.publicRepositories) throw new Error("GitHub organization repository pagination did not complete the declared public repository set");
73
+ return projectGitHubOrganizationStats(organization, totalStars, observedAt);
74
+ });
75
+ return {
76
+ status: "succeeded" as const,
77
+ output,
78
+ finalUrl: output.target.url,
79
+ dispatchStarted: false,
80
+ dispatch: { planned: 0, started: 0, verified: 0 }
81
+ };
82
+ }).pipe(Effect.catchTag(
83
+ "ReadEffectFailure",
84
+ error => {
85
+ const failure = failedProviderRead(
86
+ "GitHub organization",
87
+ error.cause,
88
+ `https://github.com/${requestedOrganization}`,
89
+ { stage, authenticated: false, targetStatusUnavailable: true }
90
+ );
91
+ return Effect.succeed(Object.hasOwn(error, "cleanupCause")
92
+ ? { ...failure, readFailure: readFailureProjection("cleanup-required") }
93
+ : failure);
94
+ }
95
+ ));
96
+ });
97
+ }