@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,889 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { join } from "node:path";
3
+
4
+ import { canonicalJson, sha256 } from "./canonical-json";
5
+ import {
6
+ parsePortableOperationIdentityV1,
7
+ type PortableOperationIdentityV1,
8
+ } from "./provider-plugin-portable-identity";
9
+ import {
10
+ loadInstalledPortableProviderPlugin,
11
+ withPortableProviderPluginCatalogLock,
12
+ } from "./provider-plugin-store";
13
+ import {
14
+ currentProcessStartIdentity,
15
+ processOwnerStatus,
16
+ type ProcessOwnerIdentity,
17
+ type ProcessOwnerStatus,
18
+ } from "./process-identity";
19
+ import type {
20
+ PortableProviderPluginCleanupContainment,
21
+ } from "./provider-plugin-cleanup-barrier";
22
+ import {
23
+ createPrivateJsonIfAbsent,
24
+ ensurePrivateStateDirectory,
25
+ ghostgetStateHome,
26
+ readPrivateStateFilesBatched,
27
+ removePrivateStateFileIfUnchanged,
28
+ snapshotPrivateStateDirectory,
29
+ writePrivateJsonIfUnchanged,
30
+ } from "./storage";
31
+
32
+ type Environment = Readonly<Record<string, string | undefined>>;
33
+ type JsonRecord = Record<string, unknown>;
34
+
35
+ const LEASE_SCHEMA_VERSION = 2 as const;
36
+ const LEGACY_LEASE_SCHEMA_VERSION = 1 as const;
37
+ const MAX_LEASE_BYTES = 32 * 1024;
38
+ const uuidPattern =
39
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
40
+
41
+ type PortableProviderPluginInvocationLeaseBase = {
42
+ readonly leaseId: string;
43
+ readonly runId: string;
44
+ readonly identity: PortableOperationIdentityV1;
45
+ readonly owner: ProcessOwnerIdentity & {
46
+ readonly token: string;
47
+ };
48
+ readonly acquiredAt: string;
49
+ };
50
+
51
+ export type PortableProviderPluginInvocationLeaseContainment =
52
+ | {
53
+ readonly status: "parent-owned";
54
+ }
55
+ | {
56
+ readonly status: "host-starting" | "host-active";
57
+ readonly host: ProcessOwnerIdentity;
58
+ }
59
+ | {
60
+ readonly status: "cleanup-complete" | "cleanup-unsafe";
61
+ readonly host: ProcessOwnerIdentity | null;
62
+ };
63
+
64
+ export type PortableProviderPluginInvocationLease =
65
+ | PortableProviderPluginInvocationLeaseBase & {
66
+ readonly schemaVersion: typeof LEGACY_LEASE_SCHEMA_VERSION;
67
+ }
68
+ | PortableProviderPluginInvocationLeaseBase & {
69
+ readonly schemaVersion: typeof LEASE_SCHEMA_VERSION;
70
+ readonly containment: PortableProviderPluginInvocationLeaseContainment;
71
+ };
72
+
73
+ export type PortableProviderPluginInvocationLeaseSnapshot = {
74
+ readonly lease: PortableProviderPluginInvocationLease;
75
+ readonly contentSha256: string;
76
+ };
77
+
78
+ export type PortableProviderPluginInvocationLeaseListEntry =
79
+ | PortableProviderPluginInvocationLeaseSnapshot
80
+ | {
81
+ readonly leaseId: string;
82
+ readonly invalid: true;
83
+ };
84
+
85
+ function record(value: unknown, label: string): JsonRecord {
86
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
87
+ throw new Error(`${label} must be an object`);
88
+ }
89
+ const prototype: unknown = Object.getPrototypeOf(value) as unknown;
90
+ if (prototype !== Object.prototype && prototype !== null) {
91
+ throw new Error(`${label} has an unsupported prototype`);
92
+ }
93
+ const descriptors = Object.getOwnPropertyDescriptors(value);
94
+ const result: JsonRecord = {};
95
+ for (const key of Reflect.ownKeys(descriptors)) {
96
+ if (typeof key !== "string") {
97
+ throw new Error(`${label} has unsupported symbol fields`);
98
+ }
99
+ const descriptor = descriptors[key];
100
+ if (
101
+ descriptor === undefined
102
+ || !descriptor.enumerable
103
+ || !("value" in descriptor)
104
+ ) {
105
+ throw new Error(`${label} has unsupported accessor fields`);
106
+ }
107
+ Object.defineProperty(result, key, {
108
+ value: descriptor.value,
109
+ enumerable: true,
110
+ configurable: false,
111
+ writable: false,
112
+ });
113
+ }
114
+ return result;
115
+ }
116
+
117
+ function exactKeys(
118
+ value: JsonRecord,
119
+ expectedKeys: readonly string[],
120
+ label: string,
121
+ ): void {
122
+ const actual = Object.keys(value).sort();
123
+ const expected = [...expectedKeys].sort();
124
+ if (
125
+ actual.length !== expected.length
126
+ || actual.some((key, index) => key !== expected[index])
127
+ ) {
128
+ throw new Error(`${label} has unsupported fields`);
129
+ }
130
+ }
131
+
132
+ function uuid(value: unknown, label: string): string {
133
+ if (typeof value !== "string" || !uuidPattern.test(value)) {
134
+ throw new Error(`${label} is malformed`);
135
+ }
136
+ return value;
137
+ }
138
+
139
+ function timestamp(value: unknown, label: string): string {
140
+ if (
141
+ typeof value !== "string"
142
+ || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value)
143
+ || new Date(value).toISOString() !== value
144
+ ) {
145
+ throw new Error(`${label} is malformed`);
146
+ }
147
+ return value;
148
+ }
149
+
150
+ function parseProcessOwner(
151
+ value: unknown,
152
+ label: string,
153
+ ): ProcessOwnerIdentity {
154
+ const owner = record(value, label);
155
+ exactKeys(
156
+ owner,
157
+ ["pid", "bootId", "processStartId"],
158
+ label,
159
+ );
160
+ if (
161
+ typeof owner.pid !== "number"
162
+ || !Number.isSafeInteger(owner.pid)
163
+ || owner.pid < 1
164
+ || typeof owner.bootId !== "string"
165
+ || !/^[a-f0-9]{64}$/u.test(owner.bootId)
166
+ || typeof owner.processStartId !== "string"
167
+ || !/^[a-f0-9]{64}$/u.test(owner.processStartId)
168
+ ) {
169
+ throw new Error(`${label} is malformed`);
170
+ }
171
+ return Object.freeze({
172
+ pid: owner.pid,
173
+ bootId: owner.bootId,
174
+ processStartId: owner.processStartId,
175
+ });
176
+ }
177
+
178
+ function parseOwner(value: unknown): PortableProviderPluginInvocationLease["owner"] {
179
+ const owner = record(value, "portable invocation lease owner");
180
+ exactKeys(
181
+ owner,
182
+ ["pid", "token", "bootId", "processStartId"],
183
+ "portable invocation lease owner",
184
+ );
185
+ if (
186
+ typeof owner.token !== "string"
187
+ || !uuidPattern.test(owner.token)
188
+ ) {
189
+ throw new Error("portable invocation lease owner is malformed");
190
+ }
191
+ return Object.freeze({
192
+ ...parseProcessOwner(
193
+ {
194
+ pid: owner.pid,
195
+ bootId: owner.bootId,
196
+ processStartId: owner.processStartId,
197
+ },
198
+ "portable invocation lease owner",
199
+ ),
200
+ token: owner.token,
201
+ });
202
+ }
203
+
204
+ function parseContainment(
205
+ value: unknown,
206
+ ): PortableProviderPluginInvocationLeaseContainment {
207
+ const containment = record(value, "portable invocation lease containment");
208
+ if (containment.status === "parent-owned") {
209
+ exactKeys(
210
+ containment,
211
+ ["status"],
212
+ "portable invocation lease containment",
213
+ );
214
+ return Object.freeze({ status: "parent-owned" });
215
+ }
216
+ if (
217
+ containment.status === "host-starting"
218
+ || containment.status === "host-active"
219
+ ) {
220
+ exactKeys(
221
+ containment,
222
+ ["status", "host"],
223
+ "portable invocation lease containment",
224
+ );
225
+ return Object.freeze({
226
+ status: containment.status,
227
+ host: parseProcessOwner(
228
+ containment.host,
229
+ "portable invocation lease host",
230
+ ),
231
+ });
232
+ }
233
+ if (
234
+ containment.status === "cleanup-complete"
235
+ || containment.status === "cleanup-unsafe"
236
+ ) {
237
+ exactKeys(
238
+ containment,
239
+ ["status", "host"],
240
+ "portable invocation lease containment",
241
+ );
242
+ return Object.freeze({
243
+ status: containment.status,
244
+ host: containment.host === null
245
+ ? null
246
+ : parseProcessOwner(
247
+ containment.host,
248
+ "portable invocation lease host",
249
+ ),
250
+ });
251
+ }
252
+ throw new Error("portable invocation lease containment is malformed");
253
+ }
254
+
255
+ export function parsePortableProviderPluginInvocationLease(
256
+ value: unknown,
257
+ ): PortableProviderPluginInvocationLease {
258
+ const lease = record(value, "portable invocation lease");
259
+ if (lease.schemaVersion === LEGACY_LEASE_SCHEMA_VERSION) {
260
+ exactKeys(
261
+ lease,
262
+ [
263
+ "schemaVersion",
264
+ "leaseId",
265
+ "runId",
266
+ "identity",
267
+ "owner",
268
+ "acquiredAt",
269
+ ],
270
+ "portable invocation lease",
271
+ );
272
+ return Object.freeze({
273
+ schemaVersion: LEGACY_LEASE_SCHEMA_VERSION,
274
+ leaseId: uuid(lease.leaseId, "portable invocation lease ID"),
275
+ runId: uuid(lease.runId, "portable invocation run ID"),
276
+ identity: parsePortableOperationIdentityV1(lease.identity),
277
+ owner: parseOwner(lease.owner),
278
+ acquiredAt: timestamp(
279
+ lease.acquiredAt,
280
+ "portable invocation lease acquisition time",
281
+ ),
282
+ });
283
+ }
284
+ exactKeys(
285
+ lease,
286
+ [
287
+ "schemaVersion",
288
+ "leaseId",
289
+ "runId",
290
+ "identity",
291
+ "owner",
292
+ "acquiredAt",
293
+ "containment",
294
+ ],
295
+ "portable invocation lease",
296
+ );
297
+ if (lease.schemaVersion !== LEASE_SCHEMA_VERSION) {
298
+ throw new Error("portable invocation lease schema is unsupported");
299
+ }
300
+ return Object.freeze({
301
+ schemaVersion: LEASE_SCHEMA_VERSION,
302
+ leaseId: uuid(lease.leaseId, "portable invocation lease ID"),
303
+ runId: uuid(lease.runId, "portable invocation run ID"),
304
+ identity: parsePortableOperationIdentityV1(lease.identity),
305
+ owner: parseOwner(lease.owner),
306
+ acquiredAt: timestamp(
307
+ lease.acquiredAt,
308
+ "portable invocation lease acquisition time",
309
+ ),
310
+ containment: parseContainment(lease.containment),
311
+ });
312
+ }
313
+
314
+ function leaseDirectory(environment: Environment): string {
315
+ return join(ghostgetStateHome(environment), "provider-plugin-state", ".invocations");
316
+ }
317
+
318
+ function leasePath(
319
+ leaseId: string,
320
+ environment: Environment,
321
+ ): string {
322
+ return join(
323
+ leaseDirectory(environment),
324
+ `${uuid(leaseId, "portable invocation lease ID")}.json`,
325
+ );
326
+ }
327
+
328
+ function storeRoot(environment: Environment): string {
329
+ return join(ghostgetStateHome(environment), "provider-plugins");
330
+ }
331
+
332
+ function snapshot(
333
+ leaseValue: PortableProviderPluginInvocationLease,
334
+ ): PortableProviderPluginInvocationLeaseSnapshot {
335
+ const lease = parsePortableProviderPluginInvocationLease(leaseValue);
336
+ return Object.freeze({
337
+ lease,
338
+ contentSha256: sha256(`${canonicalJson(lease)}\n`),
339
+ });
340
+ }
341
+
342
+ function assertExactIdentityIsActive(
343
+ identity: PortableOperationIdentityV1,
344
+ environment: Environment,
345
+ ): void {
346
+ const installed = loadInstalledPortableProviderPlugin(
347
+ storeRoot(environment),
348
+ identity.pluginId,
349
+ );
350
+ if (
351
+ installed === null
352
+ || installed.package.manifest.id !== identity.pluginId
353
+ || installed.package.manifest.version !== identity.pluginVersion
354
+ || installed.package.manifest.hostApiVersion !== identity.hostApiVersion
355
+ || installed.package.bundleSha256 !== identity.bundleSha256
356
+ || installed.package.manifestSha256 !== identity.manifestSha256
357
+ ) {
358
+ throw new Error(
359
+ `portable provider plugin ${identity.pluginId} changed or was disabled before execution; retry from current capabilities`,
360
+ );
361
+ }
362
+ }
363
+
364
+ function assertPortableProviderPluginInvocationAdmitted(
365
+ identity: PortableOperationIdentityV1,
366
+ environment: Environment,
367
+ ): void {
368
+ for (const entry of listPortableProviderPluginInvocationLeases(environment)) {
369
+ if ("invalid" in entry) {
370
+ throw new Error(
371
+ `portable provider plugin invocation is blocked by malformed lease ${entry.leaseId}; run ghostget plugin doctor before retrying`,
372
+ );
373
+ }
374
+ const existing = entry.lease;
375
+ if (existing.identity.pluginId !== identity.pluginId) continue;
376
+ if (
377
+ existing.schemaVersion === LEASE_SCHEMA_VERSION
378
+ && existing.containment.status === "cleanup-complete"
379
+ ) {
380
+ continue;
381
+ }
382
+ if (
383
+ existing.schemaVersion === LEASE_SCHEMA_VERSION
384
+ && existing.containment.status === "cleanup-unsafe"
385
+ ) {
386
+ throw new Error(
387
+ `portable provider plugin ${identity.pluginId} invocation is blocked by cleanup-unsafe lease ${existing.leaseId}; run ghostget plugin doctor before retrying`,
388
+ );
389
+ }
390
+ if (
391
+ portableProviderPluginInvocationLeaseOwnerStatus(entry)
392
+ !== "exact-live-owner"
393
+ ) {
394
+ throw new Error(
395
+ `portable provider plugin ${identity.pluginId} invocation is blocked by unverifiable non-complete lease ${existing.leaseId}; run ghostget plugin doctor before retrying`,
396
+ );
397
+ }
398
+ }
399
+ }
400
+
401
+ export function acquirePortableProviderPluginInvocationLease(
402
+ identityValue: PortableOperationIdentityV1,
403
+ runIdValue: string,
404
+ environment: Environment = process.env,
405
+ now = new Date(),
406
+ ): PortableProviderPluginInvocationLeaseSnapshot {
407
+ const identity = parsePortableOperationIdentityV1(identityValue);
408
+ const runId = uuid(runIdValue, "portable invocation run ID");
409
+ return withPortableProviderPluginCatalogLock(
410
+ storeRoot(environment),
411
+ now,
412
+ () => {
413
+ assertExactIdentityIsActive(identity, environment);
414
+ ensurePrivateStateDirectory(leaseDirectory(environment), environment);
415
+ assertPortableProviderPluginInvocationAdmitted(identity, environment);
416
+ const lease: PortableProviderPluginInvocationLease =
417
+ Object.freeze({
418
+ schemaVersion: LEASE_SCHEMA_VERSION,
419
+ leaseId: randomUUID(),
420
+ runId,
421
+ identity,
422
+ owner: Object.freeze({
423
+ pid: process.pid,
424
+ token: randomUUID(),
425
+ ...currentProcessStartIdentity(),
426
+ }),
427
+ acquiredAt: now.toISOString(),
428
+ containment: Object.freeze({
429
+ status: "parent-owned",
430
+ }),
431
+ });
432
+ const created = createPrivateJsonIfAbsent(
433
+ leasePath(lease.leaseId, environment),
434
+ lease,
435
+ { environment },
436
+ );
437
+ if (!created.created) {
438
+ throw new Error("portable invocation lease ID collided");
439
+ }
440
+ return snapshot(lease);
441
+ },
442
+ );
443
+ }
444
+
445
+ function sameProcessOwner(
446
+ left: ProcessOwnerIdentity,
447
+ right: ProcessOwnerIdentity,
448
+ ): boolean {
449
+ return (
450
+ left.pid === right.pid
451
+ && left.bootId === right.bootId
452
+ && left.processStartId === right.processStartId
453
+ );
454
+ }
455
+
456
+ function replacePortableProviderPluginInvocationLease(
457
+ current: PortableProviderPluginInvocationLeaseSnapshot,
458
+ containment: PortableProviderPluginInvocationLeaseContainment,
459
+ environment: Environment,
460
+ now = new Date(),
461
+ ): PortableProviderPluginInvocationLeaseSnapshot {
462
+ const checked = snapshot(current.lease);
463
+ if (checked.contentSha256 !== current.contentSha256) {
464
+ throw new Error("portable invocation lease snapshot is not content-bound");
465
+ }
466
+ if (checked.lease.schemaVersion !== LEASE_SCHEMA_VERSION) {
467
+ throw new Error("legacy portable invocation leases cannot bind containment");
468
+ }
469
+ const next = snapshot(Object.freeze({
470
+ ...checked.lease,
471
+ containment,
472
+ }));
473
+ withPortableProviderPluginCatalogLock(
474
+ storeRoot(environment),
475
+ now,
476
+ () => {
477
+ if (!writePrivateJsonIfUnchanged(
478
+ leasePath(checked.lease.leaseId, environment),
479
+ next.lease,
480
+ { expectedCurrentContentSha256: checked.contentSha256 },
481
+ )) {
482
+ throw new Error(
483
+ "portable invocation lease changed or disappeared before containment update",
484
+ );
485
+ }
486
+ },
487
+ );
488
+ return next;
489
+ }
490
+
491
+ export type PortableProviderPluginInvocationLeaseContainmentController =
492
+ PortableProviderPluginCleanupContainment & {
493
+ readonly current: PortableProviderPluginInvocationLeaseSnapshot;
494
+ readonly cleanupComplete: () => void;
495
+ };
496
+
497
+ /**
498
+ * Bind host containment transitions to one exact durable lease. Every update
499
+ * is a content-addressed CAS under the catalog lock.
500
+ */
501
+ export function createPortableProviderPluginInvocationLeaseContainmentController(
502
+ initial: PortableProviderPluginInvocationLeaseSnapshot,
503
+ environment: Environment = process.env,
504
+ ): PortableProviderPluginInvocationLeaseContainmentController {
505
+ let current = snapshot(initial.lease);
506
+ if (current.contentSha256 !== initial.contentSha256) {
507
+ throw new Error("portable invocation lease snapshot is not content-bound");
508
+ }
509
+ const controller: PortableProviderPluginInvocationLeaseContainmentController = {
510
+ get current() {
511
+ return current;
512
+ },
513
+ hostStarting: (host) => {
514
+ const lease = current.lease;
515
+ if (lease.schemaVersion !== LEASE_SCHEMA_VERSION) {
516
+ throw new Error("legacy portable invocation leases cannot start a host");
517
+ }
518
+ if (lease.containment.status !== "parent-owned") {
519
+ throw new Error("portable invocation lease host started out of order");
520
+ }
521
+ current = replacePortableProviderPluginInvocationLease(
522
+ current,
523
+ Object.freeze({
524
+ status: "host-starting",
525
+ host: Object.freeze({ ...host }),
526
+ }),
527
+ environment,
528
+ );
529
+ },
530
+ hostStarted: (host) => {
531
+ const lease = current.lease;
532
+ if (
533
+ lease.schemaVersion !== LEASE_SCHEMA_VERSION
534
+ || lease.containment.status !== "host-starting"
535
+ || !sameProcessOwner(lease.containment.host, host)
536
+ ) {
537
+ throw new Error("portable invocation lease host admission changed");
538
+ }
539
+ current = replacePortableProviderPluginInvocationLease(
540
+ current,
541
+ Object.freeze({
542
+ status: "host-active",
543
+ host: Object.freeze({ ...host }),
544
+ }),
545
+ environment,
546
+ );
547
+ },
548
+ cleanupComplete: () => {
549
+ const lease = current.lease;
550
+ if (lease.schemaVersion !== LEASE_SCHEMA_VERSION) {
551
+ throw new Error("legacy portable invocation lease cleanup is unverifiable");
552
+ }
553
+ if (lease.containment.status === "cleanup-complete") return;
554
+ if (lease.containment.status === "cleanup-unsafe") {
555
+ throw new Error(
556
+ "portable invocation lease cleanup cannot complete after becoming unsafe",
557
+ );
558
+ }
559
+ const host = lease.containment.status === "parent-owned"
560
+ ? null
561
+ : lease.containment.host;
562
+ current = replacePortableProviderPluginInvocationLease(
563
+ current,
564
+ Object.freeze({
565
+ status: "cleanup-complete",
566
+ host,
567
+ }),
568
+ environment,
569
+ );
570
+ },
571
+ cleanupUnsafe: () => {
572
+ const lease = current.lease;
573
+ if (lease.schemaVersion !== LEASE_SCHEMA_VERSION) {
574
+ throw new Error("legacy portable invocation lease cleanup is unverifiable");
575
+ }
576
+ if (lease.containment.status === "cleanup-unsafe") return;
577
+ if (lease.containment.status === "cleanup-complete") {
578
+ throw new Error(
579
+ "portable invocation lease cleanup became unsafe after completion",
580
+ );
581
+ }
582
+ const host = lease.containment.status === "parent-owned"
583
+ ? null
584
+ : lease.containment.host;
585
+ current = replacePortableProviderPluginInvocationLease(
586
+ current,
587
+ Object.freeze({
588
+ status: "cleanup-unsafe",
589
+ host,
590
+ }),
591
+ environment,
592
+ );
593
+ },
594
+ };
595
+ return Object.freeze(controller);
596
+ }
597
+
598
+ export function releasePortableProviderPluginInvocationLease(
599
+ current: PortableProviderPluginInvocationLeaseSnapshot,
600
+ environment: Environment = process.env,
601
+ now = new Date(),
602
+ ): void {
603
+ const checked = snapshot(current.lease);
604
+ if (checked.contentSha256 !== current.contentSha256) {
605
+ throw new Error("portable invocation lease snapshot is not content-bound");
606
+ }
607
+ if (
608
+ checked.lease.schemaVersion !== LEASE_SCHEMA_VERSION
609
+ || checked.lease.containment.status !== "cleanup-complete"
610
+ ) {
611
+ throw new Error(
612
+ "portable invocation lease cannot be released before durable cleanup completion",
613
+ );
614
+ }
615
+ withPortableProviderPluginCatalogLock(
616
+ storeRoot(environment),
617
+ now,
618
+ () => {
619
+ if (!removePrivateStateFileIfUnchanged(
620
+ leasePath(checked.lease.leaseId, environment),
621
+ { expectedCurrentContentSha256: checked.contentSha256 },
622
+ environment,
623
+ )) {
624
+ throw new Error(
625
+ "portable invocation lease changed or disappeared before release",
626
+ );
627
+ }
628
+ },
629
+ );
630
+ }
631
+
632
+ export function listPortableProviderPluginInvocationLeases(
633
+ environment: Environment = process.env,
634
+ ): readonly PortableProviderPluginInvocationLeaseListEntry[] {
635
+ const directory = leaseDirectory(environment);
636
+ const directorySnapshot = snapshotPrivateStateDirectory(
637
+ directory,
638
+ environment,
639
+ );
640
+ if (directorySnapshot.identity === null) return Object.freeze([]);
641
+ const candidates = directorySnapshot.entries.filter((entry) =>
642
+ uuidPattern.test(entry.name.slice(0, -5))
643
+ && entry.name.endsWith(".json")
644
+ );
645
+ const files = readPrivateStateFilesBatched(
646
+ directory,
647
+ candidates
648
+ .filter((entry) => entry.kind === "file")
649
+ .map((entry) => entry.name),
650
+ {
651
+ maximumBytesPerFile: MAX_LEASE_BYTES,
652
+ environment,
653
+ expectedDirectoryIdentity: directorySnapshot.identity,
654
+ },
655
+ );
656
+ const byName = new Map(files.map((file) => [file.name, file] as const));
657
+ return Object.freeze(candidates.map((entry) => {
658
+ const leaseId = entry.name.slice(0, -5);
659
+ if (entry.kind !== "file") return { leaseId, invalid: true as const };
660
+ const file = byName.get(entry.name);
661
+ if (file?.status !== "present") {
662
+ return { leaseId, invalid: true as const };
663
+ }
664
+ try {
665
+ const lease = parsePortableProviderPluginInvocationLease(
666
+ JSON.parse(file.content) as unknown,
667
+ );
668
+ if (
669
+ lease.leaseId !== leaseId
670
+ || file.content !== `${canonicalJson(lease)}\n`
671
+ ) {
672
+ return { leaseId, invalid: true as const };
673
+ }
674
+ return Object.freeze({
675
+ lease,
676
+ contentSha256: sha256(file.content),
677
+ });
678
+ } catch {
679
+ return { leaseId, invalid: true as const };
680
+ }
681
+ }));
682
+ }
683
+
684
+ function combinedOwnerStatus(
685
+ lease: PortableProviderPluginInvocationLease,
686
+ inspectOwner: (
687
+ owner: ProcessOwnerIdentity,
688
+ ) => ProcessOwnerStatus,
689
+ ): ProcessOwnerStatus {
690
+ const parent = inspectOwner(lease.owner);
691
+ if (
692
+ lease.schemaVersion === LEGACY_LEASE_SCHEMA_VERSION
693
+ || lease.containment.status === "parent-owned"
694
+ ) {
695
+ return parent;
696
+ }
697
+ const host = lease.containment.host;
698
+ if (host === null) return parent;
699
+ const child = inspectOwner(host);
700
+ if (
701
+ parent === "exact-live-owner"
702
+ || child === "exact-live-owner"
703
+ ) return "exact-live-owner";
704
+ if (parent === "unknown" || child === "unknown") return "unknown";
705
+ return "different-or-dead";
706
+ }
707
+
708
+ export function portableProviderPluginInvocationLeaseOwnerStatus(
709
+ snapshotValue: PortableProviderPluginInvocationLeaseSnapshot,
710
+ inspectOwner: (
711
+ owner: ProcessOwnerIdentity,
712
+ ) => ProcessOwnerStatus = processOwnerStatus,
713
+ ): ProcessOwnerStatus {
714
+ const checked = snapshot(snapshotValue.lease);
715
+ if (checked.contentSha256 !== snapshotValue.contentSha256) {
716
+ throw new Error("portable invocation lease snapshot is not content-bound");
717
+ }
718
+ const status = combinedOwnerStatus(checked.lease, inspectOwner);
719
+ if (checked.lease.schemaVersion === LEGACY_LEASE_SCHEMA_VERSION) {
720
+ // Schema v1 did not persist the detached host identity. A dead parent on
721
+ // the same boot is therefore not proof that its old host is gone.
722
+ return status === "exact-live-owner" ? status : "unknown";
723
+ }
724
+ if (
725
+ checked.lease.containment.status === "parent-owned"
726
+ || checked.lease.containment.status === "host-starting"
727
+ || checked.lease.containment.status === "host-active"
728
+ || checked.lease.containment.status === "cleanup-unsafe"
729
+ ) {
730
+ // Every pre-completion state is a tombstone. Parent/host death alone does
731
+ // not prove that detached descendants or parent-side helpers are gone.
732
+ return status === "exact-live-owner" ? status : "unknown";
733
+ }
734
+ return status;
735
+ }
736
+
737
+ export type PortableProviderPluginInvocationLeaseRepairReport = {
738
+ readonly inspected: number;
739
+ readonly removed: number;
740
+ readonly active: number;
741
+ readonly unknown: number;
742
+ readonly invalid: number;
743
+ };
744
+
745
+ type PortableProviderPluginInvocationLeaseRepairOptions = {
746
+ readonly recoverContainmentTombstones?: boolean;
747
+ };
748
+
749
+ /**
750
+ * Remove only leases whose exact process identity is definitely gone. Unknown
751
+ * ownership is retained and reported; wall-clock age is never used as proof.
752
+ */
753
+ export function repairPortableProviderPluginInvocationLeases(
754
+ environment: Environment = process.env,
755
+ now = new Date(),
756
+ inspectOwner: (
757
+ owner: ProcessOwnerIdentity,
758
+ ) => ProcessOwnerStatus = processOwnerStatus,
759
+ options: PortableProviderPluginInvocationLeaseRepairOptions = {},
760
+ ): PortableProviderPluginInvocationLeaseRepairReport {
761
+ return withPortableProviderPluginCatalogLock(
762
+ storeRoot(environment),
763
+ now,
764
+ () => {
765
+ const entries = listPortableProviderPluginInvocationLeases(environment);
766
+ let removed = 0;
767
+ let active = 0;
768
+ let unknown = 0;
769
+ let invalid = 0;
770
+ for (const entry of entries) {
771
+ if ("invalid" in entry) {
772
+ invalid += 1;
773
+ continue;
774
+ }
775
+ let ownerStatus =
776
+ portableProviderPluginInvocationLeaseOwnerStatus(entry, inspectOwner);
777
+ const containmentTombstone = (
778
+ entry.lease.schemaVersion === LEGACY_LEASE_SCHEMA_VERSION
779
+ || (
780
+ entry.lease.containment.status === "parent-owned"
781
+ || entry.lease.containment.status === "host-starting"
782
+ || entry.lease.containment.status === "host-active"
783
+ || entry.lease.containment.status === "cleanup-unsafe"
784
+ )
785
+ );
786
+ if (
787
+ containmentTombstone
788
+ && options.recoverContainmentTombstones === true
789
+ && entry.lease.schemaVersion === LEASE_SCHEMA_VERSION
790
+ ) {
791
+ if (
792
+ entry.lease.containment.status === "parent-owned"
793
+ || entry.lease.containment.status === "host-starting"
794
+ ) {
795
+ ownerStatus = combinedOwnerStatus(entry.lease, inspectOwner);
796
+ } else if (
797
+ entry.lease.containment.status === "host-active"
798
+ || entry.lease.containment.status === "cleanup-unsafe"
799
+ ) {
800
+ try {
801
+ const currentBootId = currentProcessStartIdentity().bootId;
802
+ const hostBootId = entry.lease.containment.host?.bootId;
803
+ // Once plugin code crossed admission, cleanup uncertainty can
804
+ // include detached descendants or parent-side helpers. Only a
805
+ // reboot proves every process from that boundary is gone.
806
+ ownerStatus = (
807
+ entry.lease.owner.bootId !== currentBootId
808
+ && (hostBootId === undefined || hostBootId !== currentBootId)
809
+ )
810
+ ? "different-or-dead"
811
+ : "unknown";
812
+ } catch {
813
+ ownerStatus = "unknown";
814
+ }
815
+ }
816
+ }
817
+ if (
818
+ entry.lease.schemaVersion === LEGACY_LEASE_SCHEMA_VERSION
819
+ && options.recoverContainmentTombstones === true
820
+ ) {
821
+ try {
822
+ // A different boot is OS-level proof that no process from the
823
+ // legacy lease can remain. Same-boot v1 state has no safe automatic
824
+ // recovery because it never recorded the detached child.
825
+ if (
826
+ entry.lease.owner.bootId
827
+ !== currentProcessStartIdentity().bootId
828
+ ) {
829
+ ownerStatus = "different-or-dead";
830
+ }
831
+ } catch {
832
+ ownerStatus = "unknown";
833
+ }
834
+ }
835
+ if (ownerStatus === "exact-live-owner") {
836
+ active += 1;
837
+ continue;
838
+ }
839
+ if (
840
+ ownerStatus === "unknown"
841
+ || (
842
+ containmentTombstone
843
+ && options.recoverContainmentTombstones !== true
844
+ )
845
+ ) {
846
+ unknown += 1;
847
+ continue;
848
+ }
849
+ if (removePrivateStateFileIfUnchanged(
850
+ leasePath(entry.lease.leaseId, environment),
851
+ { expectedCurrentContentSha256: entry.contentSha256 },
852
+ environment,
853
+ )) {
854
+ removed += 1;
855
+ } else {
856
+ invalid += 1;
857
+ }
858
+ }
859
+ return Object.freeze({
860
+ inspected: entries.length,
861
+ removed,
862
+ active,
863
+ unknown,
864
+ invalid,
865
+ });
866
+ },
867
+ );
868
+ }
869
+
870
+ /**
871
+ * Explicit operator recovery used by plugin doctor. A pre-admission tombstone
872
+ * is removed only after exact parent-and-host death. Active or unsafe
873
+ * containment is removed only after reboot proves every potentially escaped
874
+ * descendant is gone.
875
+ */
876
+ export function recoverPortableProviderPluginInvocationLeaseTombstones(
877
+ environment: Environment = process.env,
878
+ now = new Date(),
879
+ inspectOwner: (
880
+ owner: ProcessOwnerIdentity,
881
+ ) => ProcessOwnerStatus = processOwnerStatus,
882
+ ): PortableProviderPluginInvocationLeaseRepairReport {
883
+ return repairPortableProviderPluginInvocationLeases(
884
+ environment,
885
+ now,
886
+ inspectOwner,
887
+ { recoverContainmentTombstones: true },
888
+ );
889
+ }