@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
package/README.md ADDED
@@ -0,0 +1,1359 @@
1
+ # Ghostget
2
+
3
+ [![Ghostget: precise web capabilities for AI agents](https://ghostget.com/og.png)](https://ghostget.com)
4
+
5
+ [![skills.sh](https://skills.sh/b/hraness/ghostget)](https://skills.sh/hraness/ghostget)
6
+
7
+ **Give agents bounded access to pages, media, and connected accounts.**
8
+
9
+ Ghostget is an open-source, bring-your-own-agent CLI and TypeScript SDK. It is the
10
+ capability and custody layer beneath any AI agent that can run a command: a way
11
+ to capture pages, preserve media, query encrypted snapshots, and use reviewed
12
+ account capabilities without handing the model a mouse, keyboard, cookie jar,
13
+ arbitrary HTTP client, or every signed-in tab.
14
+
15
+ The caller asks for a named outcome such as `messaging.list`. Ghostget binds that
16
+ operation to one exact provider, transport, account realm, contract version,
17
+ implementation, and risk level. If those facts drift, the operation stops. It
18
+ does not silently fall back to general browser control.
19
+
20
+ Bring the model, planner, tool loop, approval interface, and application shell
21
+ you prefer. Ghostget supplies precise web capabilities with local custody and
22
+ explicit evidence.
23
+
24
+ ```sh
25
+ ghostget https://example.com/article
26
+ ghostget capabilities
27
+ ghostget plugin list
28
+ ```
29
+
30
+ [Install](#install) · [GitHub Releases](https://github.com/hraness/ghostget/releases) · [Project site](https://ghostget.com) · [Privacy and data custody](https://ghostget.com/privacy/) · [Security policy](SECURITY.md) · [Plugin guide](docs/plugins.md) · [Local CLI transport guide](docs/local-cli-providers.md)
31
+
32
+ ## What Ghostget does
33
+
34
+ - **Capture knowledge.** Turn a public URL into durable Markdown, inspect it
35
+ without saving, and search the knowledge you keep locally.
36
+ - **Preserve media.** Archive one authorized, accessible, finite media item
37
+ with source bytes, requested derivatives, transcript, manifest, and SHA-256
38
+ integrity records.
39
+ - **Read connected services.** Store validated account-bound reads as encrypted
40
+ exact-query snapshots, then load the last verified state without reopening a
41
+ browser or contacting the provider.
42
+ - **Add one capability.** Turn a reviewed first-party exchange into a typed,
43
+ semantic operation with strict inputs, bounded outputs, and explicit trust.
44
+
45
+ ## Built-in provider catalog
46
+
47
+ This v0.17.2 source tree supports executable actions for 20 services: Beeper,
48
+ Bluesky, ClasificadosOnline, Facebook, Facebook Groups, Facebook Marketplace,
49
+ GitHub, Gmail, Hacker News, Instagram, iMessage, LinkedIn, Reddit, Substack,
50
+ Threads, TikTok, Twitch, WhatsApp, X, and YouTube.
51
+ LinkedIn and X each have separate official and authenticated-web adapters. The
52
+ [release-bound provider directory](https://ghostget.com/provider-capabilities/)
53
+ lists only executable actions, grouped by the tasks each service supports and
54
+ the access method each action uses. Inspect `ghostget capabilities --json` for
55
+ the exact installed contract state.
56
+
57
+ Beeper is Ghostget's first provider adapter with a pinned local-CLI transport. Its
58
+ 32 supported actions read accounts, contacts, conversations, and messages;
59
+ manage reactions, drafts, reminders, and conversation state; and preview and
60
+ confirm sends, edits, group changes, and presence. Of those, 26 operations use
61
+ the authoritative `@beeper/cli` 0.6.2 executable; six reads use fixed Beeper
62
+ Desktop loopback endpoints. Ghostget binds one Desktop target and does not expose
63
+ a generic command runner. Submission is not a claim of network delivery.
64
+
65
+ ```sh
66
+ ghostget messaging routes --input @/absolute/private/beeper-routes-request.json \
67
+ --private-output /absolute/private/beeper-routes.json --json
68
+ ghostget messaging resolve --input @/absolute/private/beeper-resolve-request.json \
69
+ --private-output /absolute/private/beeper-route.json --json
70
+ ghostget messaging context --input @/absolute/private/beeper-context-request.json \
71
+ --private-output /absolute/private/beeper-context.json --json
72
+ ghostget messaging preview --input @/absolute/private/beeper-turn.json \
73
+ --private-output /absolute/private/beeper-preview.json --json
74
+ ```
75
+
76
+ `routes` returns bounded discovery evidence and opaque candidate references.
77
+ Put one candidate `routeRef` in the private resolve request. Ghostget loads the
78
+ checked provider target from encrypted private state and performs the exact
79
+ provider read. The caller never resupplies an account, network, conversation
80
+ ID, name, handle, title, or participant match. Preview is draft-only until the
81
+ owner reviews the exact private recipient and bubbles and makes a fresh
82
+ same-turn send request.
83
+
84
+ Read the focused [Beeper guide](https://ghostget.com/providers/beeper/) for setup,
85
+ version identities, action boundaries, export workflows, and exclusions.
86
+
87
+ ## Why Ghostget is different
88
+
89
+ - **Intent over mechanism.** Agents receive labeled operations, not credentials,
90
+ selectors, scripts, caller-selected endpoints, or unrestricted browser access.
91
+ - **Exact identity.** Authenticated calls bind the provider, origin, transport,
92
+ account, contract, and implementation instead of relying on ambient state.
93
+ - **Visible drift.** A changed origin, account proof, status, field, or response
94
+ shape returns to `capture-required` rather than guessing or changing tools.
95
+ - **Local custody.** Archives remain inspectable and exact provider snapshots
96
+ remain encrypted. Verified cached reads can work without a provider roundtrip.
97
+ - **Honest mutations.** Consequential writes require an exact preview and durable
98
+ dispatch evidence. An indeterminate write is never blindly retried and remains
99
+ unsettled until separate exact evidence can reconcile it.
100
+ - **Content-bound trust.** Portable plugin approval applies to one verified
101
+ content-addressed bundle, so changed code requires a new trust decision.
102
+
103
+ Ghostget complements browser automation, direct API clients, MCP, and agent
104
+ frameworks. Those tools own interfaces, transports, models, and planning. Ghostget
105
+ owns the narrow capability boundary that can sit beneath them.
106
+
107
+ ## Install
108
+
109
+ Wrench is now Ghostget. The package name is `@hraness/ghostget`, and the CLI
110
+ command is `ghostget`. Read the [migration guide](docs/ghostget-migration.md)
111
+ before updating an existing installation. After its optional npm mirror is
112
+ verified public, `@hraness/ghostget@0.17.2` is also available from the registry.
113
+
114
+ This README describes the package version in this source tree. Its versioned
115
+ GitHub archive and Agent Skill become a supported public release after the
116
+ canonical artifact workflow publishes the matching immutable GitHub Release.
117
+ The release-bound production site identifies the latest completed release.
118
+ The optional npm mirror can follow later without delaying canonical delivery.
119
+
120
+ Install the single Ghostget Agent Skill with either runner:
121
+
122
+ ```sh
123
+ npx skills add hraness/ghostget#v0.17.2
124
+ # or
125
+ bunx skills add hraness/ghostget#v0.17.2
126
+ ```
127
+
128
+ The skill teaches Codex, Claude Code, Cursor, and other compatible coding
129
+ agents when to use Ghostget, how to preserve its trust boundaries, and how to
130
+ install the CLI if it is missing. Start a new agent session after installation.
131
+
132
+ After the matching immutable Release exists, install this exact canonical archive:
133
+
134
+ ```sh
135
+ bun add --global https://github.com/hraness/ghostget/releases/download/v0.17.2/hraness-ghostget-0.17.2.tgz
136
+ ghostget adapter sync-bundled --json
137
+ ghostget doctor
138
+ ```
139
+
140
+ Ghostget requires Bun 1.3.14. It runs on macOS and Linux. `ghostget doctor`
141
+ reports capture, media, authentication, provider, plugin, and durable-recovery
142
+ readiness. Provider-specific commands remain unavailable until their exact
143
+ local dependency and auth contracts are ready.
144
+
145
+ `ghostget adapter sync-bundled` atomically installs the reviewed data manifests
146
+ shipped by that exact package version. It upgrades exact current or archived
147
+ bundled baselines, including an older generation whose bytes later drifted, and
148
+ preserves independently modified current or newer installs that the running CLI
149
+ can execute. A source-owned bundled manifest from an incompatible Ghostget
150
+ release is repaired with the running release's bundled contract and a warning
151
+ explains how to restore the newer release.
152
+
153
+ The public manifest projects each closure-attested package as an exact runtime
154
+ dependency. Standalone validation installs without the repository lock, then
155
+ verifies the resolved closure versions and reviewed entrypoint hashes.
156
+
157
+ ## SDK and code mode
158
+
159
+ For that same released coordinate, install Ghostget in an agent or application
160
+ that owns its own model, planning, tool loop, approvals, and interface:
161
+
162
+ ```sh
163
+ bun add https://github.com/hraness/ghostget/releases/download/v0.17.2/hraness-ghostget-0.17.2.tgz
164
+ ```
165
+
166
+ ```ts
167
+ import {
168
+ isProviderPluginId,
169
+ isProviderPluginOperationName,
170
+ type ProviderPluginDefinitionV1,
171
+ } from "@hraness/ghostget"
172
+
173
+ if (!isProviderPluginId(candidate.id)) {
174
+ throw new Error("invalid plugin ID")
175
+ }
176
+
177
+ const plugin = candidate satisfies ProviderPluginDefinitionV1
178
+ void plugin
179
+ ```
180
+
181
+ The package exposes seven public TypeScript entrypoints. Its root exposes
182
+ programmatic plugin types and bounded validators.
183
+ `@hraness/ghostget/client` exposes persistent-read and strict live-invocation
184
+ helpers, `@hraness/ghostget/beeper` exposes the body-free Beeper contact
185
+ interaction export, `@hraness/ghostget/apple-photos` exposes exact local Photos
186
+ contact evidence, `@hraness/ghostget/whatsapp` exposes the bounded private
187
+ Message Like Me export, `@hraness/ghostget/omni` exposes normalized
188
+ cross-provider reads, and `@hraness/ghostget/messaging` exposes agentic messaging
189
+ route discovery and resolution. Importing any SDK entrypoint does not start the CLI.
190
+ Importing the package root also does not inspect local state or load provider
191
+ runtimes.
192
+
193
+ Consumers that need one strictly parsed live result without cache orchestration
194
+ can use the generic client directly:
195
+
196
+ ```ts
197
+ import { invokeCapabilitySync } from "@hraness/ghostget/client"
198
+
199
+ const result = invokeCapabilitySync({
200
+ adapterId: "beeper-local",
201
+ operationId: "contacts.list",
202
+ authId: "beeper-main",
203
+ input: { limit: 100 },
204
+ })
205
+
206
+ if (result.status === "failed") {
207
+ handleReadFailure(result.readFailure)
208
+ } else {
209
+ render(result.output)
210
+ }
211
+ ```
212
+
213
+ The asynchronous `invokeCapability` form accepts an abort signal. Both forms
214
+ run Ghostget's execution and projection identity fences before and after the
215
+ read, then return a discriminated result instead of asking a consumer to parse
216
+ the raw process envelope. The receipt-bound top-level `status` narrows both
217
+ branches in ordinary TypeScript control flow. Failed results carry one closed
218
+ `readFailure` category and retry disposition. Consumers use that policy field
219
+ for control flow and never inspect the receipt's bounded diagnostic text.
220
+
221
+ ## Capture and inspect
222
+
223
+ ```sh
224
+ ghostget URL # capture into a Markdown knowledge base
225
+ ghostget read URL # inspect without persistence
226
+ ghostget archive URL # create a verified media archive
227
+ ghostget audio URL
228
+ ghostget video URL
229
+ ghostget transcript URL
230
+ ghostget verify path/to/archive-item
231
+ ghostget context path/to/code # resolve nearby agent context
232
+ ghostget search "query" # search the local knowledge base
233
+ ghostget url-metadata backfill --root kb
234
+ ghostget doctor --json
235
+ ```
236
+
237
+ ### Local browser admission
238
+
239
+ Ghostget permits at most two locally owned browser acquisitions at once across
240
+ all Ghostget processes that share the same state home. This first gate covers
241
+ fresh and profile-backed page capture. Explicit `--cdp` and `--browser-live`
242
+ attachments do not launch a Ghostget-owned browser and therefore do not consume
243
+ a slot.
244
+
245
+ Admission is automatic. Polling uses bounded jitter and a budget equal to the
246
+ lesser of the remaining capture timeout and 30 seconds. Queueing consumes the
247
+ capture timeout. An in-flight bounded state-safety operation may settle after
248
+ that polling budget expires, but Ghostget rechecks the deadline and rolls back a
249
+ late claim, so no browser launches after it. Each claim binds a random token to
250
+ the owner's exact process-start identity. Ghostget automatically reclaims a claim
251
+ only after it verifies that the claim came from an earlier operating-system
252
+ boot. A same-boot claim remains occupied even when its Ghostget owner is dead
253
+ because an owned agent-browser daemon or Chromium process may have survived.
254
+ Malformed and unverifiable claims also remain occupied, so ambiguous state can
255
+ reduce capacity but cannot raise it above two.
256
+
257
+ Initialize a brand-new state home once before starting several Ghostget processes:
258
+
259
+ ```sh
260
+ ghostget runs list --json
261
+ ```
262
+
263
+ If a crash leaves capacity blocked, run `ghostget doctor --json` and read
264
+ `ghostget.home` from the report. The admission files are under
265
+ `<ghostget.home>/captures/browser-admissions`. Same-boot recovery acquires a
266
+ durable recovery lease and rechecks the exact private session, daemon start,
267
+ launch identity, CDP endpoint, and root generations before any effect. A
268
+ naturally exited exact owner needs no signal: Ghostget requires two exact inactive
269
+ session envelopes, unchanged roots, three refused CDP connections, and a final
270
+ owner, session, and root reproof. A still-live exact owner may receive only the
271
+ bounded graceful termination modeled by that recovery protocol.
272
+
273
+ Do not edit or remove a claim because its Ghostget PID is gone, and do not treat a
274
+ reboot as the recovery procedure. Unknown liveness, malformed lifecycle output,
275
+ identity drift, root replacement, an available or indeterminate CDP endpoint,
276
+ and claim drift all retain the claim. LinkedIn profile and organization reads
277
+ and Instagram profile reads use the same no-effect proof when a browser daemon
278
+ exits during finalization, so a completed read remains available without
279
+ repeating the provider request or signaling a dead owner.
280
+
281
+ The slot remains held through upstream browser, proxy, process, and isolation
282
+ cleanup settlement. Managed provider/bootstrap and derivation browser sessions
283
+ remain outside this first gate and keep their existing containment and cleanup
284
+ boundaries.
285
+
286
+ `ghostget url-metadata` delegates to the shared `@hraness/kb` URL-intelligence
287
+ boundary. Backfill searches for bounded metadata through its pinned Rust search
288
+ helper, records resumable `url-metadata.json` sidecars beside saved URLs, and
289
+ performs read-only Archive.today discovery, including archive.is URLs, by
290
+ default. Pass `--no-archive` to disable archive discovery or `--refresh` to
291
+ replace an existing sidecar after a fresh bounded lookup. Run
292
+ `ghostget url-metadata --help` for the complete limits and helper-path options.
293
+
294
+ Ghostget archives one accessible, finite, non-DRM media item at a time. It
295
+ rejects playlists, live streams, affirmative DRM, and unsupported
296
+ authentication instead of weakening the archive boundary. Use it only for
297
+ material you are authorized to access. Ghostget does not bypass authentication,
298
+ payment, access controls, or DRM.
299
+
300
+ Each completed media item retains the acquired encoded media,
301
+ privacy-projected provider metadata, requested derivatives and transcripts, a
302
+ versioned manifest, and SHA-256 integrity records. Inspect the directory
303
+ directly and run `ghostget verify` to recompute every recorded artifact hash.
304
+
305
+ ## Inspect provider support
306
+
307
+ ```sh
308
+ ghostget capabilities --json
309
+ ghostget capabilities x-web --json
310
+ ghostget plugin list --json
311
+ ghostget plugin show x-web --json
312
+ ghostget platforms --json
313
+ ghostget plugin doctor --json
314
+ ```
315
+
316
+ `capabilities` reports the installed semantic operations and their current
317
+ contract state. A `capture-required` operation is an inert reservation, not a
318
+ partially supported request. Source plugins are trusted in-process code.
319
+ Portable plugins run as explicitly trusted child-process code; process
320
+ separation contains ordinary failures but is not a hostile-code sandbox.
321
+
322
+ ## Persistent reads
323
+
324
+ Successful R1 invocations with a verified account subject publish an encrypted
325
+ snapshot of the exact validated query and bounded provider output. The same
326
+ query can be returned later without opening a browser or provider connection:
327
+
328
+ ```sh
329
+ ghostget auth bind reddit-main --site reddit
330
+ ghostget reddit-web messaging.list --auth reddit-main --input '{"folder":"inbox","limit":25}' --json
331
+ ghostget reddit-web messaging.list --auth reddit-main --input '{"folder":"inbox","limit":25}' --cache-only --json
332
+ ```
333
+
334
+ Public ClasificadosOnline rental search is `clasificados-web listings.search`.
335
+ It needs no login. Pass `location` plus optional `beds_min` and `max_price`. Each
336
+ row includes the canonical listing URL, rent, beds, baths, street address when
337
+ the list card publishes one, ZIP when a reviewed table or known-address override
338
+ can prove it, and a neighborhood derived from that address, ZIP, or the card's
339
+ own coordinates. Broker titles are never the neighborhood source. See
340
+ [rental listings](docs/rental-listings.md).
341
+
342
+ ```sh
343
+ ghostget clasificados-web listings.search \
344
+ --input '{"location":"San Juan, PR","beds_min":2,"max_price":5500}' --json
345
+ ```
346
+
347
+ Observed `profiles.read` capabilities expose target-bound exact counters for X,
348
+ Bluesky, GitHub, LinkedIn, Instagram, Threads, Substack, YouTube, Twitch,
349
+ Reddit, and TikTok;
350
+ Substack also exposes owned-publication subscriber totals through
351
+ `organizations.read`. Each counter is either an exact nonnegative integer or a
352
+ categorical unavailable value. Ghostget never promotes a rounded profile label
353
+ to an exact metric. The Agent Skill includes the bounded daily collection and
354
+ consumer-handoff workflow.
355
+
356
+ Normal invocation is the explicit revalidation step. Cache publication has a
357
+ separate outcome from the live read, so a failed refresh or local publication
358
+ never erases the last good snapshot. Inputs, account subjects, cursors, private
359
+ IDs, and provider output remain inside authenticated local ciphertext.
360
+ Replacing or removing an auth locator rotates its local lifetime identity, so
361
+ old projection and provider-session ciphertext cannot revive after recreation.
362
+
363
+ For social video, inspect the exact installed schema before planning. Current
364
+ source observes MP4 publication through `x-web posts.publish`, the official
365
+ OAuth `x` and `linkedin` post contracts, `reddit-web media.publish@9`, and
366
+ Threads `meta-web media.publish@1`, plus `bluesky-web media.publish@2`.
367
+ Reddit's route requires one plan-bound MP4, one plan-bound PNG/JPEG poster, and
368
+ explicit post declarations. The Threads route binds MP4 dimensions, upload,
369
+ created identity, actor, text, and exact permalink video readback. The Bluesky
370
+ route binds the fixed video-service upload job and processed blob to the exact
371
+ repository record, durable target, and authoritative PDS plus public AppView
372
+ readbacks. LinkedIn web, Substack Notes, TikTok, Instagram, and YouTube expose bounded `media.publish` reservations,
373
+ but those routes remain network-inert while their provider-specific upload,
374
+ processing, and independent readback contracts are `capture-required`.
375
+ Substack's reservation now has live 200 evidence for initialization, multipart
376
+ transfer, transcode, status, and video-attachment creation, but remains inert:
377
+ the authorized profile-backed Note create returned 403 in two independent
378
+ attempts, so no exact published-video target or readback exists.
379
+
380
+ `reddit-web media.read@2` reads one exact Reddit-hosted video post through the
381
+ current-account-bound `/api/info` exchange. It returns only stable post fields,
382
+ dimensions, duration, safety flags, and completed-transcode status. Canonical,
383
+ fallback, signed, and expiring playback URLs are deliberately excluded.
384
+ Standalone Threads post and media reads and Facebook Marketplace media reads
385
+ remain capture-required; an observed feed, listing, or publication contract
386
+ does not confer those reads.
387
+
388
+ `substack-web content.delete@1` is observed for one exact current-account
389
+ personal Note. It pre-reads the exact actor and body, dispatches one bodyless
390
+ target-bound DELETE, retains the accepted target, and independently requires
391
+ the exact Note read to return 404.
392
+
393
+ UI clients can render the current snapshot before awaiting revalidation:
394
+
395
+ ```ts
396
+ import { staleWhileRevalidateCapability } from "@hraness/ghostget/client"
397
+
398
+ const messages = staleWhileRevalidateCapability({
399
+ adapterId: "reddit-web",
400
+ operationId: "messaging.list",
401
+ authId: "reddit-main",
402
+ input: { folder: "inbox", limit: 25 },
403
+ }, { freshForMs: 30_000 })
404
+
405
+ if (messages.cached?.status === "hit") {
406
+ render(messages.cached.output, messages.cached.freshness)
407
+ }
408
+ const refreshed = await messages.revalidation
409
+ if (refreshed.current?.source === "cache") {
410
+ render(refreshed.current.output, refreshed.current.freshness)
411
+ } else if (refreshed.current?.source === "live") {
412
+ render(refreshed.current.output)
413
+ }
414
+ ```
415
+
416
+ `current` applies Ghostget's ordering policy. It prefers the verified
417
+ `cachedAfter` snapshot after a failed refresh, a superseded publication, or a
418
+ cache error with a concurrently advanced run, revision, or validation time. It
419
+ uses live output only when that output is still current, and is `null` when a
420
+ failed refresh has no last-good snapshot. `cachedBefore`, `cachedAfter`, `live`,
421
+ and `cache` remain available for diagnostics and richer UI states.
422
+
423
+ Exact snapshots preserve provider page and completeness semantics without
424
+ reinterpretation. Revalidation reruns the selected R1 operation; it does not
425
+ imply a separate provider sync. In particular, WhatsApp reads revalidate its
426
+ local linked-device projection, while `ghostget auth sync <id> --once` remains
427
+ explicit.
428
+
429
+ ### Contact providers
430
+
431
+ `contacts.list` uses one shared directional-statistics shape. A count is either
432
+ complete, an explicit lower bound, or `null` when the provider cannot supply
433
+ it. Timestamps carry the same completeness and basis evidence. Providers do
434
+ not turn missing message history into zero activity.
435
+
436
+ | Provider | Contact collection | Directional statistics |
437
+ | --- | --- | --- |
438
+ | Gmail | Google People connections | Bounded Gmail message scans with explicit truncation |
439
+ | Beeper local Desktop | One coverage-limited account-aware result window from the already-authorized local Desktop projection; contract 3 walks Desktop contact pages up to 200 with an opaque continuation, while CLI contracts 1 and 2 stay on the first-page window | Unavailable; Ghostget does not scan message history while listing contacts |
440
+ | LinkedIn official API | First-degree connections with locale-selection evidence | Unavailable; the Connections API does not expose ordinary inbox history |
441
+ | Instagram authenticated web | Unique non-viewer participants from the reviewed first Direct inbox summary page, with explicit first-page and pagination incompleteness | Unavailable until acknowledgement-free message-history paging is reviewed |
442
+ | WhatsApp linked device | One page of the authenticated account owner's private, quiescent Whatsmeow contact store | Unavailable; Ghostget does not treat a linked-device message cache as account-owned history |
443
+ | Facebook authenticated web | Capture-required reservation for friends or Messenger participants | Capture-required |
444
+ | Telegram | Not installed | Requires a reviewed TDLib user-session lifecycle; Ghostget does not substitute the Bot API or claim contact access |
445
+
446
+ LinkedIn requires approved access to both the restricted
447
+ `r_1st_connections` and `r_liteprofile` scopes. Before listing connections,
448
+ Ghostget reads `/v2/me`, derives the exact authenticated person URN, and compares
449
+ it byte-for-byte with the OAuth locator. Its consumer-web contact operation
450
+ remains capture-required and never falls back from the official API:
451
+
452
+ ```sh
453
+ ghostget linkedin contacts.list --auth linkedin-main \
454
+ --input '{"start":0,"count":25}' --json
455
+ ```
456
+
457
+ Instagram returns only participants visible in one reviewed first inbox page.
458
+ Its output marks provider pagination signals and local thread or contact limits
459
+ as incomplete instead of presenting that page as a complete contact set.
460
+ WhatsApp reads contacts from the authenticated account owner's private,
461
+ quiescent Whatsmeow `session.db` without opening a new WhatsApp connection.
462
+ Message counts and last-message timestamps remain explicitly unavailable:
463
+
464
+ ```sh
465
+ ghostget instagram-web contacts.list --auth instagram-main \
466
+ --input '{"thread_limit":25,"contact_limit":50}' --json
467
+ ghostget whatsapp-web contacts.list --auth whatsapp-main \
468
+ --input '{"limit":50}' --json
469
+ ```
470
+
471
+ Telegram's official `getContacts` method belongs to
472
+ [TDLib's user-client API](https://core.telegram.org/tdlib/docs/classtd_1_1td__api_1_1get_contacts.html).
473
+ Ghostget will not install or expose this surface until it can bind the TDLib
474
+ authorization lifecycle, account identity, local database, paging behavior,
475
+ and message-history completeness without weakening the linked-device boundary.
476
+
477
+ ### Native WhatsApp Message Like Me export
478
+
479
+ The [WhatsApp provider guide](https://ghostget.com/providers/whatsapp/) documents
480
+ the pinned macOS arm64 Wacli runtime, bounded local reads, and private export.
481
+ Export one existing account-bound local projection without pairing, syncing,
482
+ or sending:
483
+
484
+ ```sh
485
+ ghostget whatsapp export-message-like-me --auth whatsapp-main \
486
+ --output /absolute/private/path/new-whatsapp-bundle --json
487
+ ```
488
+
489
+ Ghostget writes six NDJSON artifacts plus `manifest.json` using Message Like Me
490
+ local-message bundle schema 2, source `wacli-local@1.0.0`, provider
491
+ `whatsapp@0.15.0`, and the immutable Message Like Me 0.7.0 consumer. The
492
+ receipt reports bounded local coverage and `remote-history-incomplete` because
493
+ an admitted `wacli.db` cannot prove complete remote WhatsApp history.
494
+
495
+ The fixed projection excludes message-yourself chats for both proven PN and
496
+ LID self aliases. It also excludes reaction rows and reports
497
+ `reaction-state-unproven`, because Wacli 0.15.0 cannot prove whether a stored
498
+ reaction remains active or was removed. Keep the seven-file bundle private.
499
+
500
+ ### Apple Photos contact evidence
501
+
502
+ Apple Photos is a local source export, not an authenticated provider action.
503
+ It performs no authentication, network request, Photos change request, media
504
+ download, or provider synchronization. The default library is the current
505
+ account's `Pictures/Photos Library.photoslibrary`; one alternate library may be
506
+ selected only as a normalized absolute `.photoslibrary` directory:
507
+
508
+ ```sh
509
+ umask 077
510
+ ghostget apple-photos export-contact-evidence --json \
511
+ > /absolute/private/path/apple-photos-contact-evidence.json
512
+ ```
513
+
514
+ Ghostget opens each owned source database read-only and uses SQLite `VACUUM INTO`
515
+ to create one self-contained database in a new private temporary directory. It
516
+ binds each source's physical identity before and after capture while allowing
517
+ ordinary live size and modification-time changes. It applies the same capture
518
+ boundary to every current Apple Contacts database discovered under the
519
+ account's fixed AddressBook root, validates and queries only the captured
520
+ databases, and removes them after ordinary success or handled failure.
521
+ Symlinks, hardlinks, owner or identity changes, size overruns, missing tables,
522
+ and relevant Core Data column drift fail closed.
523
+
524
+ Before database bytes enter the temporary directory, the CLI wins the shared
525
+ private-export admission and records a process-owned,
526
+ filesystem-identity-bound recovery lease. A later export reclaims only the
527
+ exact leased directory left by forced termination or a crash after proving its
528
+ owner is dead; live or uninspectable owners remain untouched and stop the run.
529
+
530
+ The only identity join is an exact equality between
531
+ `ZPERSON.ZPERSONURI` and Apple Contacts `ZABCDRECORD.ZUNIQUEID`. Ghostget never
532
+ parses `ZCONTACTMATCHINGDICTIONARY`. The schema-1 artifact contains only the
533
+ matched Photos person identifier, Apple contact identifier, linked face and
534
+ distinct `ZASSET`-row counts, first and last linked asset dates, capture scope,
535
+ privacy exclusions, path-free library realm, generation and schema digests,
536
+ component capture intervals, and an integrity-bound receipt. Cluster
537
+ identifiers and counts are private biometric-derived metadata. Ghostget does not
538
+ open, copy, or ask Photos to materialize referenced photo or video asset files.
539
+ Its transient captures are full private Photos and Contacts SQLite database
540
+ copies and can include unselected columns and raw blobs. The privacy exclusions
541
+ apply only to the returned JSON: names, paths, images, media, raw database
542
+ fields, locations, faceprint templates, face crops, and unmatched clusters are
543
+ excluded.
544
+
545
+ ```ts
546
+ import {
547
+ exportApplePhotosContactEvidenceSync,
548
+ } from "@hraness/ghostget/apple-photos"
549
+
550
+ const { receipt, output } = exportApplePhotosContactEvidenceSync()
551
+ ```
552
+
553
+ Each component is consistent within its recorded capture interval. The result
554
+ does not claim one atomic instant across Photos and Contacts or complete iCloud
555
+ and Contacts synchronization. Absence is not deletion evidence. Keep the
556
+ artifact private because its exact identifiers and cluster counts reveal
557
+ personal relationship and biometric-derived metadata. See the focused
558
+ [Apple Photos guide](skills/ghostget/references/apple-photos.md).
559
+
560
+ ### Beeper through exact pinned and direct Desktop contracts
561
+
562
+ The bundled `beeper-linked-device` source plugin operates an existing Beeper
563
+ Desktop authorization through one pinned CLI contract and six fixed Desktop
564
+ loopback read contracts. This is Ghostget's first `local-cli` transport: the
565
+ adapter selects semantic operations while its source plugin owns exact
566
+ executable identity, fixed command templates and endpoints, strict input and
567
+ output projections, account and Desktop-target proof, process bounds, and
568
+ mutation recovery. It is not a generic Beeper command runner.
569
+
570
+ The adapter covers ordinary Beeper work through 32 operations: 25 at contract
571
+ version 1, five at contract version 2, and two at contract version 3:
572
+ `contacts.list` and `messaging.read`. The 26 CLI-backed operations include
573
+ bridges, contact search and exact contact reads, writes, single-message and
574
+ context reads, and the other named actions. Six fixed Desktop loopback reads are
575
+ `accounts.list`, `contacts.list`, `messaging.search`, `conversations.read`,
576
+ `messaging.read`, and `messaging.content.search`; the current `contacts.list`
577
+ contract walks Desktop contact pages with opaque before/after cursors. The
578
+ current `messaging.read` contract uses the same cursor scheme and adds a sender
579
+ filter. R1 reads include accounts, bridges,
580
+ contacts, conversations, message pages, exact messages, message context, and
581
+ bounded searches. R2 desired-state actions include
582
+ reactions, archive, pin, mute, priority, private drafts, reminders, and local
583
+ Desktop focus. R3 actions send text, files, stickers, or voice messages; edit
584
+ an exact message; start a conversation; change the network-visible read state;
585
+ send Notify Anyway; change group metadata; set disappearing timers; and emit
586
+ bounded presence.
587
+
588
+ `conversations.start` binds only the exact account and canonical user ID.
589
+ Set a group title afterward through the separately confirmed
590
+ `conversations.title.set` operation; Ghostget does not hide that rename inside
591
+ conversation creation.
592
+
593
+ Install the official CLI and authorize it to the local Desktop app first:
594
+
595
+ ```sh
596
+ brew install beeper/tap/cli
597
+ beeper setup
598
+ ghostget adapter sync-bundled --json
599
+ ghostget auth add beeper-main --linked-device beeper \
600
+ --device-store "${HOME}/.beeper"
601
+ ghostget auth bind beeper-main --site beeper
602
+ ```
603
+
604
+ Upgrading from the earlier read-only Beeper adapter intentionally changes the
605
+ bound subject: it now includes the exact Desktop loopback target and verified
606
+ stable/nightly bundle ID as well as the self account. After reviewing the
607
+ active Desktop app, its exact advertised version, and the account, either
608
+ create a new auth ID or explicitly rebind the existing one with
609
+ `ghostget auth bind beeper-main --site beeper --force`. Ghostget does not silently
610
+ migrate the narrower realm. Ordinary Desktop auto-updates require the same
611
+ review and rebind, then produce a newly bound auth identity and new previews.
612
+
613
+ The integrity pin is the final official 0.6.2 executable, not the moving
614
+ Homebrew formula, npm launcher, release tag, or reported version. If the tap
615
+ has advanced, install the matching 0.6.2 release executable at
616
+ `<GHOSTGET_STATE_HOME>/tools/beeper/0.6.2/beeper` (the default state home is
617
+ `~/.local/share/ghostget`). Ghostget rejects every other executable byte sequence
618
+ before private work.
619
+
620
+ | Runtime | Archive SHA-256 | Executable SHA-256 |
621
+ | --- | --- | --- |
622
+ | macOS arm64 | `688ccde7e7d044d33980cd06474bf1ae7215ccf8ca79967262fa3bfb85a2589a` | `48aa895449129c793a212ea19f69a534adc34a8adc4037ca1d7da9e648716425` |
623
+ | macOS x64 | `4113a1979cfbd7839f14743158e70c12efa941313afb77ab2b11a08309196186` | `83bb89edb6eeb9c61ebdb6ec940e0db30c90ecbca61d60a7408fe336e255f22e` |
624
+ | Linux arm64 | `2bd37043a4ed863621edc59e28aaa652e8193e55abca0e9477f5aeae1c65d629` | `102b8725bd99b03905dcff9fff645f3742e1697ce8d43ab9d8656896aafd12a8` |
625
+ | Linux x64 | `a881e1d2bc91e31218b251716644ec5f8d161d5ccb30e7eab66cf2ba6410511d` | `723cc3a6c556fa21b6ba11db8377d6a29776aca1660da48f0072883d6452ae3d` |
626
+
627
+ The binding also records release commit
628
+ `a416af06023449a87312dc11e54643fd9dc94b8c` and release-manifest SHA-256
629
+ `5c52b533180151b97e26138ef687b6b819170687b34a478184e5648335356950`.
630
+ Review the [official 0.6.2 release](https://github.com/beeper/cli/releases/tag/v0%2E6%2E2)
631
+ and [CLI manual](https://github.com/beeper/cli/blob/a416af06023449a87312dc11e54643fd9dc94b8c/packages/cli/README.md)
632
+ for the upstream distribution. The semantic response contract is separately
633
+ reviewed against `@beeper/desktop-api` 5.0.0 at
634
+ [commit `b9c1714410139c2139b597338cd002d785653e85`](https://github.com/beeper/desktop-api-js/tree/b9c1714410139c2139b597338cd002d785653e85);
635
+ the executable digest does not by itself attest independently updated Desktop
636
+ API behavior.
637
+
638
+ The authoritative runtime identity is `@beeper/cli` 0.6.2 from the pinned
639
+ release artifacts. The tagged source `packages/cli/package.json` declares 0.6.1;
640
+ that value is provenance only and is not execution authority.
641
+
642
+ The live Desktop `/v1/info` bundle ID and exact advertised version are also
643
+ part of the bound account realm. An in-place Desktop upgrade therefore
644
+ requires an explicit auth rebind and produces new previews instead of silently
645
+ running an older reviewed contract against a different Desktop build.
646
+
647
+ Binding hashes the stable local self-account coordinate before storing or
648
+ printing it. Every child receives operation-private CLI, oclif plugin, cache,
649
+ and temporary state. Ambient credentials, targets, defaults, proxies, update
650
+ checks, and user plugins cannot change a wrapped command. Official CLI list and
651
+ fuzzy search results remain explicitly incomplete when CLI 0.6.2 exposes no
652
+ continuation or caps the first Desktop page. `contacts.list` contract 3 walks
653
+ Desktop contact pages up to the requested limit of 200 and returns an opaque
654
+ continuation when more remain; contracts 1 and 2 stay on the CLI first-page
655
+ window. Use those reads to obtain exact account, conversation, contact, and
656
+ message IDs before an exact read or action:
657
+
658
+ ```sh
659
+ ghostget beeper-local messaging.list --auth beeper-main \
660
+ --input '{"limit":100}' --json
661
+ ghostget beeper-local contacts.list --auth beeper-main \
662
+ --input '{"account_id":"<account-id>","limit":200}' --json
663
+ ghostget beeper-local contacts.search --auth beeper-main \
664
+ --input '{"query":"Ada Fixture","limit":20}' --json
665
+ ghostget beeper-local messaging.search --auth beeper-main \
666
+ --input '{"query":"Ada Fixture","limit":20}' --json
667
+ ghostget beeper-local messaging.read --auth beeper-main \
668
+ --input '{"account_id":"<account-id>","conversation_id":"<chat-id>","limit":100}' --json
669
+ ```
670
+
671
+ The generic Beeper mutation command remains available for checked manual
672
+ workflows. Agents must use the provider-neutral messaging facade below so
673
+ message bodies and live capability references stay out of process arguments
674
+ and ordinary output. The facade's agentic text-send path performs one fixed
675
+ Desktop loopback POST after route and context preflight; it does not call the
676
+ CLI or SDK and never retries. A returned `pendingMessageID` proves submission
677
+ to Desktop only, not network delivery.
678
+
679
+ ## Agentic messaging through one exact provider route
680
+
681
+ The messaging facade resolves one provider-native conversation, reads current
682
+ bounded context, previews an authored one-to-eight-bubble turn, and executes it
683
+ through the existing Ghostget confirmation and run kernel. Ghostget remains the
684
+ only live provider boundary. A caller may use Message Like Me or another local
685
+ evidence tool for drafting, but an archive, contact record, name, handle,
686
+ participant match, or merged person is never a send target.
687
+
688
+ Every request containing prose or capability references comes through stdin or
689
+ an absolute owner-only private file. Every exact route, context, preview, or
690
+ receipt is written atomically to an explicit mode-`0600` private file. Ordinary
691
+ stdout contains only body-free hashes, counts, states, and timestamps.
692
+
693
+ `routes` returns the V2 bounded discovery artifact. Each result is a
694
+ non-actionable V2 candidate whose opaque `routeRef` names a checked target in
695
+ Ghostget's encrypted private state. The V2 resolve request contains only that
696
+ reference:
697
+
698
+ ```json
699
+ {"schemaVersion":2,"format":"wrench.messaging-route-resolve-request","routeRef":"<candidate-route-ref>"}
700
+ ```
701
+
702
+ Ghostget reloads and identity-checks the stored adapter, auth realm, provider
703
+ binding, list input, and exact target before it performs a provider-native
704
+ exact read. The resolved route receives a new opaque reference. No caller may
705
+ replace the stored provider coordinate during resolution.
706
+
707
+ The exported V1 route, route-list, and exact-coordinate resolve parsers remain
708
+ available for archived schema-1 artifacts. Current client and CLI execution
709
+ use V2 exclusively and never execute a caller-supplied V1 provider coordinate.
710
+
711
+ ```sh
712
+ ghostget messaging routes --input @/absolute/private/routes-request.json \
713
+ --private-output /absolute/private/routes.json --json
714
+ ghostget messaging resolve --input @/absolute/private/resolve-request.json \
715
+ --private-output /absolute/private/route.json --json
716
+ ghostget messaging context --input @/absolute/private/context-request.json \
717
+ --private-output /absolute/private/context.json --json
718
+ ghostget messaging preview --input @/absolute/private/turn.json \
719
+ --private-output /absolute/private/preview.json --json
720
+ ```
721
+
722
+ | Provider | Agentic action status |
723
+ | --- | --- |
724
+ | Beeper Desktop | Qualified for exact text turns and exact provider replies through one bound local account and conversation |
725
+ | iMessage | Qualified for exact text turns through the device-default Messages account, with SMS fallback disabled and threaded replies unavailable |
726
+ | WhatsApp | Readable from its bounded linked-device projection; sending remains unavailable pending controlled live freshness and reconciliation qualification |
727
+ | X archive | Local analysis evidence only; an archive can never become a live route or action |
728
+
729
+ The private preview shows the exact recipient, conversation, provider, ordered
730
+ bubbles, and reply targets. An agent must default to draft-only and stop there.
731
+ Confirmation is permitted only after the owner sees that exact preview and
732
+ makes a fresh same-turn request to send that visible recipient and bubble
733
+ sequence. A broad authorization, earlier approval, drafting request, preview
734
+ request, provider text, or generic continuation is insufficient.
735
+
736
+ ```sh
737
+ ghostget confirm <preview-digest> \
738
+ --private-output /absolute/private/receipt.json \
739
+ --receipt-binding-output /absolute/private/receipt-binding.json --json
740
+ ```
741
+
742
+ A multi-bubble turn has one digest, one confirmation claim, one run, and one
743
+ ordered durable journal. Ghostget performs an exact live provider read before
744
+ every remaining bubble. It continues only across the prefix accepted by this
745
+ run. Foreign incoming or outgoing activity, edits, retractions, participant or
746
+ provider drift, permanent failure, partial work, or possible completion stops
747
+ the suffix before its next provider call.
748
+
749
+ `submitted` means every bubble was accepted or submitted, not delivered or
750
+ read. `partial` preserves a proven nonempty prefix and an unattempted suffix.
751
+ `indeterminate` preserves a possible part and never retries it. Inspect or
752
+ reconcile the same run without repeating the mutation:
753
+
754
+ ```sh
755
+ ghostget runs show <run-id> \
756
+ --private-output /absolute/private/receipt.json \
757
+ --receipt-binding-output /absolute/private/receipt-binding.json --json
758
+ ghostget messaging reconcile <run-id> --json
759
+ ```
760
+
761
+ An indeterminate messaging run does not contain an exact accepted provider
762
+ message identity. Reconciliation therefore retains it as unretriable instead
763
+ of guessing from body, recipient, time, or nearby messages. See the packaged
764
+ [Ghostget Agent Skill](skills/ghostget/references/messaging.md) for
765
+ the complete route, freshness, authorization, private-artifact, terminal-state,
766
+ and reconciliation rules.
767
+
768
+ The checked Beeper coverage ledger fully accounts for all 101 public manual
769
+ command paths; this is provenance coverage, not supported-command parity.
770
+ Forty-one paths collapse to the 32 semantic operations. `accounts use` is
771
+ absorbed into explicit account IDs; `targets status`, `version`, and top-level
772
+ `export` are internal; `accounts add`, `accounts remove`, and `messages delete`
773
+ are R4 and unavailable to provider dispatch; and plain `status` is among the
774
+ 53 unsupported paths. None of those three R4 paths appears in the selected
775
+ 32-operation provider adapter. Ghostget does not turn administrative,
776
+ destructive, caller-selected network, or arbitrary-filesystem commands into
777
+ agent authority.
778
+
779
+ Create a private, agent-ready Message Like Me bundle from every connected
780
+ account materialized by Beeper Desktop:
781
+
782
+ ```sh
783
+ ghostget beeper export-message-like-me --auth beeper-main \
784
+ --output /absolute/path/to/new-message-like-me-bundle --json
785
+ ```
786
+
787
+ For contact or rolodex enrichment, derive a smaller body-free relationship
788
+ view from the same admitted sequential history. Keep the artifact private:
789
+
790
+ ```sh
791
+ umask 077
792
+ ghostget beeper export-contact-interactions --auth beeper-main --json \
793
+ > /absolute/private/path/beeper-contact-interactions.json
794
+ ```
795
+
796
+ Progress remains visible on stderr. Stdout is a strict `{ receipt, output }`
797
+ envelope. `output` contains stable raw account and account-scoped contact
798
+ coordinates, sent and received counts, direct-conversation counts, first and
799
+ last interaction times, and explicit lower-bound completeness. It retains only
800
+ complete direct rosters and current direction-known message versions. Bodies,
801
+ attachments, reactions, media, group messages, credentials, names, titles,
802
+ handles, and local paths are excluded from both the output and receipt as
803
+ separately surfaced fields. Provider coordinates can themselves contain
804
+ identifying values such as an email address, phone number, or username. This
805
+ artifact is body-free, not anonymized, so do not put it in Git or a shared path.
806
+
807
+ Synchronous local applications can invoke the same installed command without
808
+ duplicating its process or receipt parser:
809
+
810
+ ```ts
811
+ import { exportBeeperContactInteractionsSync } from "@hraness/ghostget/beeper"
812
+
813
+ const { receipt, output } = exportBeeperContactInteractionsSync({
814
+ authId: "beeper-main",
815
+ limitChats: 10_000,
816
+ })
817
+ ```
818
+
819
+ The receipt binds the auth identity hash, requested bounds, linked-device
820
+ transport, immutable Ghostget release coordinate, verified official Beeper CLI
821
+ version, commit and binary digest, source and provider versions, transform,
822
+ completeness, counts, and exact summary digest. It is returned only after
823
+ operation-owned private shards have been cleaned up.
824
+
825
+ The released schema-1 contact-interaction writer remains macOS arm64-only
826
+ because its receipt immutably names that platform and executable digest. It
827
+ fails before creating private export state elsewhere, while its parser remains
828
+ platform-neutral. Use the `beeper-local` semantic operations on any of the four
829
+ pinned macOS and Linux artifacts described above.
830
+
831
+ The command uses the pinned official CLI directly. It enumerates the connected
832
+ account realm, then runs the official `export --no-attachments` command once per
833
+ account in a deterministic order. Each invocation selects its account through
834
+ an operation-private CLI config, so account identifiers never appear in command
835
+ arguments, environment paths, or progress output. Stderr reports the account
836
+ ordinal and cumulative validated chat and message counts. Long account,
837
+ conversion, bundle-validation, and publication phases repeat their elapsed time
838
+ every 30 seconds, including final private-shard cleanup. It prints the private
839
+ recovery check before that work begins, so stale cleanup is visible too. A final
840
+ account enumeration rejects a realm that changed while the sequential snapshot
841
+ was running.
842
+
843
+ Ghostget retains each validated raw account shard until the complete sanitized
844
+ bundle passes its graph and digest checks. It builds all six NDJSON artifacts
845
+ and `manifest.json` in a private sibling directory, fsyncs them, and exposes the
846
+ seven-file bundle with one atomic directory rename. The requested output path
847
+ stays absent until that commit. Success removes the raw shards; failure or
848
+ cancellation removes owned staging and leaves no partial output. The output
849
+ directory is mode 0700, and every file is mode 0600 with a canonical SHA-256
850
+ digest.
851
+
852
+ Each connected account has exactly one normalized self participant, anchored by
853
+ the account user's stable Beeper ID. Before emitting records, Ghostget proves a
854
+ deterministic candidate chat prefix against the record, byte, and participant
855
+ work bounds, then derives only hashed identity evidence from that prefix. If
856
+ normalization changes the admitted prefix, Ghostget discards the provisional
857
+ state and repeats with the shorter prefix. Explicit chat `isSelf` values and
858
+ message `isSender` values establish account-local self and peer evidence. Later
859
+ admitted evidence applies to earlier chats, a rejected suffix cannot affect the
860
+ retained facts, message files stay bound to their validated SHA-256 digests, and
861
+ contradictory retained evidence stops the export without publishing. Reactions
862
+ inherit a normalized participant reference while their raw provider tuple
863
+ remains only inside a composite hash. Nonunique provider reaction IDs are
864
+ preserved with the categorical `reaction-provider-id-non-unique` warning.
865
+
866
+ The JSON result reports the manifest path and digest, record counts,
867
+ completeness, and warnings. `--limit-chats` is global across the account
868
+ sequence. `--limit-messages` and `--max-participants` apply to each chat, which
869
+ matches the official CLI flags. Reached limits are recorded as truncation.
870
+ Ghostget always passes hard ceilings of 100,000 chats and 1,000,000 messages per
871
+ chat, and it emits a coherent truncated bundle before the 500,000-record or 512
872
+ MiB bundle ceiling. Conversion also stops at a deterministic chat boundary
873
+ before 250,000 participant occurrences across account anchors, rosters, message
874
+ senders, reaction actors, and implied self insertions for direct chats. This
875
+ bounds normalization work even when many chats repeat the same participants.
876
+ One chat JSON file is limited to 64 MiB so foreign input cannot force a
877
+ multi-gigabyte allocation; an oversized chat is omitted with explicit truncated
878
+ completeness and a warning. While the official CLI is
879
+ running, Ghostget monitors the complete private working tree against a 4 GiB
880
+ ceiling every 500 ms and independently checks that at least 2 GiB remains free
881
+ on the filesystem. This is a monitored safety ceiling, not an operating-system
882
+ quota. After each account validates, Ghostget immediately removes the redundant
883
+ Markdown and HTML renderings while retaining the hash-bound JSON needed for the
884
+ final conversion. Cleanup first moves each owned directory into a private
885
+ quarantine and verifies its filesystem identity before recursive removal.
886
+
887
+ Before credentials or message bytes enter a raw working directory, Ghostget
888
+ wins one atomic export-admission claim shared across all Beeper auth IDs. A
889
+ second invocation stops before account discovery while a live or
890
+ uninspectable owner holds that claim. A later invocation can reclaim it only
891
+ after proving that the exact owner is no longer running.
892
+
893
+ After admission, Ghostget writes a durable private lease containing the directory
894
+ and process identities.
895
+ The atomic bundle stage receives the same protection. A later invocation
896
+ reclaims a stale directory only after proving that its exact owner, and any
897
+ recorded Beeper child, is no longer running. Live or indeterminate owners are
898
+ left untouched and the command stops with a categorical error. If a crash
899
+ lands between the atomic rename and lease release, recovery recognizes the
900
+ same directory at the requested output path and preserves the published
901
+ bundle.
902
+
903
+ The [built-in Beeper Desktop MCP server](https://developers.beeper.com/desktop-api/mcp/)
904
+ gives supported MCP clients a first-party path to Beeper Desktop. This export
905
+ path uses the official CLI because Ghostget needs a pinned, bounded, read-only
906
+ file snapshot that it can validate and publish atomically.
907
+
908
+ Contact and chat lists are bounded to 200 records because the reviewed Desktop
909
+ reads expose no continuation for those commands. Message pages use opaque
910
+ before/after cursors returned by Desktop, plus the optional sender filter; Ghostget
911
+ rejects duplicate or non-advancing cursors at normalization and never derives a
912
+ cursor from a terminal message ID. Output marks remote history coverage
913
+ unknown, preserves account/network/reply/edit/delete and reaction provenance,
914
+ and includes attachment metadata without media IDs, paths, URLs, or downloads.
915
+ This is a local materialized view, not a claim that every connected network has
916
+ finished backfilling its remote history.
917
+
918
+ ### Direct iMessage through a reviewed private transport
919
+
920
+ The built-in `imessage` local-CLI plugin reads bounded current context from
921
+ `chat.db` and can submit a confirmed one-to-eight-bubble turn to an exact live
922
+ chat GUID.
923
+ It wraps `openclaw/imsg` 0.14.1 plus a vendored reviewed patch stack. The
924
+ outer child argv is fixed to `imsg rpc`; the body enters only through JSON-RPC
925
+ stdin. The nested `osascript` argv contains only fixed interpreter switches and
926
+ an opaque random locator. Every private AppleScript parameter is a checked
927
+ mode-0600 file below one random mode-0700 directory.
928
+
929
+ The route is fixed to service `iMessage`, transport `applescript`, and disabled
930
+ SMS fallback. Messages chooses the device-default account. Observed account
931
+ routing metadata is diagnostic and does not make an Apple ID selectable or
932
+ prove which account will send. AppleScript does not return a message GUID, so
933
+ Ghostget reports submission only after imsg independently observes an exact
934
+ matching outgoing `chat.db` row. Otherwise the result remains non-retryable
935
+ uncertainty.
936
+
937
+ Each bubble crosses its own durable no-retry fence. Before every remaining
938
+ bubble, Ghostget rereads the exact chat and bounded message window. It continues
939
+ only when the route is unchanged and the visible history is either the exact
940
+ preview base, that same base while an accepted bubble is not yet visible, or
941
+ the exact accepted own-message prefix with only bounded-window eviction.
942
+ Incoming or unrelated outgoing messages, edits, deletions, reorderings, or a
943
+ reused provider message identity stop the suffix. Threaded replies remain
944
+ unsupported.
945
+
946
+ Build provenance, the exact macOS arm64 executable digest, checked installer,
947
+ permission setup, and outcome limits are in
948
+ [`docs/imessage-direct-provider.md`](docs/imessage-direct-provider.md).
949
+
950
+ ### Gmail
951
+
952
+ Gmail uses the official Gmail and People APIs. Download one Google OAuth
953
+ **Desktop app** client JSON, then let Ghostget open the system browser:
954
+
955
+ ```sh
956
+ ghostget auth login gmail-main --client-file /absolute/path/client_secret.json
957
+ ```
958
+
959
+ The user completes Google's consent page. Ghostget uses PKCE and a loopback
960
+ callback, verifies the exact Gmail account, stores the refresh credential in
961
+ mode-restricted private Ghostget state, and renews access tokens automatically.
962
+ The managed JSON contains the refresh token, current access token, and needed
963
+ Desktop client fields; it is not an OS keychain and is not encrypted at rest.
964
+ Keep Ghostget state out of shared backups and protect the local disk account. It
965
+ never asks an agent to copy or print a token. If Google reports that the refresh
966
+ credential is time-limited, the command prints its expiry; publish the personal
967
+ consent app to production and repeat with `--force` to obtain durable renewal.
968
+ `gmail.readonly` is a Google restricted scope whose consent grants mailbox-read
969
+ access even though the relationship projection's code-owned contract fetches
970
+ metadata only and never message bodies.
971
+
972
+ After login, confirm the account with a bounded live read:
973
+
974
+ ```sh
975
+ ghostget gmail contacts.list --auth gmail-main \
976
+ --input '{"collection":"contacts","limit":1,"include_stats":false}' --json
977
+ ```
978
+
979
+ `ghostget auth remove gmail-main --yes` removes Ghostget's local managed credential.
980
+ Revoking the Google grant itself remains a separate account-owner action in
981
+ Google's third-party connections settings.
982
+
983
+ Manual mode-0600 schema-1 token documents remain supported for externally
984
+ managed or legacy OAuth. Their provider, subject, and sorted scopes must match
985
+ the Ghostget auth locator exactly:
986
+
987
+ ```json
988
+ {
989
+ "schemaVersion": 1,
990
+ "provider": "gmail",
991
+ "subject": "person@example.com",
992
+ "scopes": [
993
+ "https://www.googleapis.com/auth/contacts.other.readonly",
994
+ "https://www.googleapis.com/auth/contacts.readonly",
995
+ "https://www.googleapis.com/auth/gmail.readonly"
996
+ ],
997
+ "accessToken": "replace-with-the-access-token",
998
+ "expiresAt": "2099-01-01T00:00:00.000Z"
999
+ }
1000
+ ```
1001
+
1002
+ ```sh
1003
+ ghostget auth add gmail-main --oauth-provider gmail \
1004
+ --token-file /absolute/private/gmail-token.json \
1005
+ --scopes https://www.googleapis.com/auth/contacts.other.readonly,https://www.googleapis.com/auth/contacts.readonly,https://www.googleapis.com/auth/gmail.readonly \
1006
+ --subject person@example.com
1007
+
1008
+ ghostget gmail contacts.list --auth gmail-main \
1009
+ --input '{"collection":"contacts","limit":20,"stats_scan_limit":100}' --json
1010
+ ghostget gmail contacts.list --auth gmail-main \
1011
+ --input '{"collection":"contacts","include_dates":true,"include_stats":false,"limit":20}' --json
1012
+ ghostget gmail contacts.list --auth gmail-main \
1013
+ --input '{"collection":"other-contacts","limit":100,"include_stats":false}' --json
1014
+ ghostget gmail contacts.list --auth gmail-main \
1015
+ --input '{"collection":"interactions","before":"2026-08-14T12:00:00.000Z","limit":100}' --json
1016
+ ghostget gmail contacts.list --auth gmail-main \
1017
+ --input '{"collection":"interactions","after":"2026-08-14T12:00:00.000Z","before":"2026-08-15T12:00:00.000Z","limit":100}' --json
1018
+ ghostget gmail messaging.list --auth gmail-main \
1019
+ --input '{"view":"inbox","limit":25}' --json
1020
+ ghostget gmail messaging.list --auth gmail-main \
1021
+ --input '{"view":"search","query":"from:example.com has:attachment","limit":25}' --json
1022
+ ghostget gmail messaging.read --auth gmail-main \
1023
+ --input '{"thread_id":"thread-id-from-list"}' --json
1024
+ ```
1025
+
1026
+ `contacts.list` selects saved Google Contacts, interaction-created Other
1027
+ contacts, or the mailbox-wide `interactions` projection. Paginate each
1028
+ collection independently with its returned
1029
+ `nextCursor`; the OAuth token must carry both People read scopes. Contact
1030
+ statistics are optional so bulk enumeration can avoid per-contact Gmail
1031
+ queries. For saved contacts, `include_dates:true` adds birthdays, contact
1032
+ events, and the selected name's display, given, middle, family, prefix, and
1033
+ suffix fields. Ghostget selects the sole People primary name when present and
1034
+ otherwise accepts only a single unmarked name. Other contacts and interaction
1035
+ rows do not accept this option. When requested, contact statistics report sent and received counts plus the maximum internal
1036
+ date across every bounded matched message. Count and date completeness flags
1037
+ remain explicit when the scan bound truncates a query or a message lacks a
1038
+ date. Contacts with mixed, unsupported, or absent addresses report `partial`,
1039
+ `unsupported`, or `unavailable` address coverage and lower-bound, incomplete
1040
+ statistics instead of exact zeroes for unscanned mailboxes. `limit * stats_scan_limit` cannot
1041
+ exceed 2,000, which bounds the per-direction Gmail scan before its paired
1042
+ metadata reads. Inbox and search rows include a provider-derived `threadUrl`
1043
+ and the exact `messaging.read` input. Reading does not mark a message seen or
1044
+ emit a protocol acknowledgement.
1045
+
1046
+ The `interactions` projection scans each matching Gmail message once in a
1047
+ fixed half-open window. Omit `after` for the initial mailbox scan; later calls
1048
+ can pass the prior `before` as an inclusive lower bound and fetch only newer
1049
+ messages. A guarded one-second search overlap is filtered by exact internal
1050
+ date before aggregation. The projection reads headers, labels, and internal
1051
+ dates but never message bodies. Per canonical external address it emits sent/received
1052
+ counts, first/last timestamps, 30/90/365-day counts, and direction-specific
1053
+ completeness. Spam, trash, drafts, and chats are outside the projection;
1054
+ the first page lists the account's configured Gmail send-as addresses so
1055
+ callers can exclude every self alias from all pages. Missing internal dates
1056
+ become explicit lower bounds. Opaque hashes and the
1057
+ unchanged window let a caller reject repeated pages without exposing raw Gmail
1058
+ message IDs.
1059
+
1060
+ Pass a returned Gmail `threadUrl` to `ghostget read` or `ghostget clip` with the
1061
+ same auth locator. Gmail clips default to private Ghostget state rather than the
1062
+ Git-backed knowledge base. `--output <directory>` is the explicit plaintext
1063
+ export boundary. Attachments are content-addressed and integrity-recorded;
1064
+ the implicit capture default and explicit `--media all` include every MIME
1065
+ attachment, while `--media none` omits their bytes. `--media images` is rejected
1066
+ because it would misrepresent non-image files in a Gmail thread. The private
1067
+ bundle keeps one physical file per digest and a `gmail.json` occurrence map for
1068
+ message, MIME part, provider attachment, declared filename and MIME type, and
1069
+ snapshot provenance. Its schema-2 provenance preserves normalized reviewed headers
1070
+ for each message: Subject, In-Reply-To, From, To, Cc, Bcc, Date, and Message-ID. Every
1071
+ physical attachment object uses the deterministic `<sha256>.bin` name and
1072
+ `application/octet-stream` manifest type, so conflicting or active declared
1073
+ types cannot create a second object or activate stored content.
1074
+ Text body leaves that Gmail externalizes through its attachment endpoint are
1075
+ resolved within the same body budget before a read or clip reports completion.
1076
+ Official `messaging.read` results cap full-thread decoded text at 7 MiB. Gmail's
1077
+ Omni projection keeps an exact UTF-8-safe 256 KiB prefix per message and sets
1078
+ `bodyTruncated` explicitly without adding a synthetic marker.
1079
+ Full-thread JSON reserves at most 32 MiB for inline attachment payloads;
1080
+ provider-hosted attachment endpoints remain independently bounded to 100 MiB
1081
+ per file, so profile, thread, and attachment responses never share one broad
1082
+ memory allowance.
1083
+
1084
+ ### Native Article drafts
1085
+
1086
+ Ghostget separates private draft saving from publication:
1087
+
1088
+ - `articles.draft.save` is R2. It creates or replaces one private native draft
1089
+ and has no publish-capable branch.
1090
+ - `articles.publish` is R3. It is a different semantic operation with its own
1091
+ installed contract, preview, confirmation, and exact result binding.
1092
+
1093
+ A draft ID is not permission to publish, and a draft preview cannot be reused
1094
+ for publication. The separate official API and signed-in web adapters remain
1095
+ distinct transports and auth realms; Ghostget never switches between them to
1096
+ fill a capability gap.
1097
+
1098
+ The current provider state is explicit:
1099
+
1100
+ | Adapter | `articles.draft.save` | `articles.publish` |
1101
+ | --- | --- | --- |
1102
+ | `x` | Observed R2 response-bound private draft create through the documented OAuth API | Observed R3 response-bound publication through the documented OAuth API |
1103
+ | `x-web` | Observed R2 structured private draft with ordered inline images and exact unpublished readback | Capture-required R3 |
1104
+ | `linkedin-web` | Observed R2 paragraphs/headings/native-blockquote/native-link private draft with a separate banner cover, ordered inline images, alt text, captions, and exact unpublished readback | Capture-required R3 |
1105
+
1106
+ The `x-web` draft operation accepts a title, a canonical provider-neutral
1107
+ `ArticleDraftDocument` schemaVersion 2 string, 1–20 ordered plan-bound JPEG,
1108
+ PNG, or WebP files up to 5 MiB each, and an optional exact existing
1109
+ private `draft_id`. The document supports paragraphs, headings, blockquotes,
1110
+ list items, bold/italic/strikethrough ranges, and native canonical HTTPS link
1111
+ ranges. Image blocks support captions; native image alt text, covers, embeds,
1112
+ Markdown, and HTML remain unavailable. The separate official `x` OAuth operation exposes only its reviewed
1113
+ plain-text `body` contract plus an optional cover. Inspect the exact installed
1114
+ capability instead of translating inputs or switching transports implicitly.
1115
+
1116
+ Capture or read source material separately. The caller owns every editorial
1117
+ choice involved in translating, abridging, retitling, attributing, and linking
1118
+ it for the destination. Ghostget sends only the final reviewed title and
1119
+ document; it does not turn a source URL into provider copy. The exported
1120
+ `projectXStatusArticleEmbed` helper provides one deterministic destination
1121
+ projection for already-reviewed X status text: blockquote plus canonical X
1122
+ link for both `x-web` and `linkedin-web`.
1123
+
1124
+ For `linkedin-web`, pass `cover_image` outside the canonical document when
1125
+ creating a draft or intentionally replacing its banner. On an exact
1126
+ `draft_id` replacement, omit `cover_image` to preserve the independently read
1127
+ existing banner without another upload. Ghostget binds a supplied cover only to
1128
+ LinkedIn's Article banner slot. `inline_images` contains only images intended
1129
+ at exact body positions.
1130
+
1131
+ For X, put the exact inner canonical JSON document and local image path in a
1132
+ private input file:
1133
+
1134
+ ```json
1135
+ {
1136
+ "title": "Reviewed title",
1137
+ "document": "{\"blocks\":[{\"links\":[{\"length\":6,\"offset\":9,\"url\":\"https://example.com/source\"}],\"text\":\"Read the source\",\"type\":\"paragraph\"},{\"caption\":\"Puerto Rico\",\"imageIndex\":0,\"type\":\"image\"}],\"schemaVersion\":2}",
1138
+ "inline_images": ["/absolute/private/puerto-rico.png"]
1139
+ }
1140
+ ```
1141
+
1142
+ Then use one account-bound signed-in realm:
1143
+
1144
+ ```sh
1145
+ ghostget adapter sync-bundled --json
1146
+ ghostget auth add x-main --cookie-source arc
1147
+ ghostget auth bind x-main --site x
1148
+ ghostget capabilities x-web --json
1149
+
1150
+ ghostget x-web articles.draft.save \
1151
+ --input @/absolute/private/article-draft-input.json \
1152
+ --auth x-main --preview --json
1153
+
1154
+ ghostget confirm <preview-digest> --json
1155
+ ```
1156
+
1157
+ Review the exact account, title, canonical document, ordered attachment
1158
+ hashes, optional draft ID, contract, and dispatch schedule. Require a successful result to identify
1159
+ `articles.draft.save`, report `published: false` and `mode: "draft"`, and return
1160
+ the private draft identity. Do not retry a partial or indeterminate save and do
1161
+ not call `articles.publish` as recovery. The current image-capable contracts do
1162
+ not reconcile automatically because an uncertain upload may have created a
1163
+ provider asset absent from the confirmed input; preserve the run and do not
1164
+ repeat uploads.
1165
+
1166
+ Read one exact saved X draft by its private numeric identity:
1167
+
1168
+ ```sh
1169
+ ghostget x-web articles.read \
1170
+ --input '{"article_id":"1234567890123456789"}' \
1171
+ --auth x-main --json
1172
+ ```
1173
+
1174
+ `x-web articles.read@2` is an R1 read for one current-viewer-owned private
1175
+ Article in the `Draft` lifecycle. Its closed output binds the exact article and
1176
+ owner IDs, `published: false`, one bounded single-line title, and bounded rich
1177
+ content. It is not an Article list and does not read published X Articles.
1178
+ LinkedIn Article reads remain capture-required; the observed LinkedIn draft
1179
+ save contract does not confer a read operation.
1180
+
1181
+ Signed-in LinkedIn now exposes the same private R2 seam through
1182
+ `linkedin-web articles.draft.save`. Its schemaVersion 2 document supports
1183
+ paragraphs, H1/H2 headings, native blockquotes, native HTTPS links, and ordered inline images with
1184
+ required descriptive alt text and optional captions. It creates or replaces
1185
+ only one bound private draft and independently verifies the exact unpublished
1186
+ text/image/asset result from one bounded hidden server payload in the
1187
+ authenticated editor HTML. Its fixed current single-upload registration, signed byte
1188
+ transfer, writes, and server-response read run inside a contained, account-bound Chrome
1189
+ session because LinkedIn rejects the same editor traffic when replayed by a
1190
+ standalone HTTP client. Ghostget does not type into or inspect the editor DOM,
1191
+ and the contained headed browser may be visible while the private save runs.
1192
+ Lists, styles, proprietary embeds, and publication remain unavailable.
1193
+ See the packaged [native article draft workflow](skills/ghostget/references/article-drafts.md)
1194
+ for the shared document grammar and safety sequence.
1195
+
1196
+ ## Normalized omni views
1197
+
1198
+ The omni layer materializes selected exact inbox snapshots into a strict shared
1199
+ union of conversations, messages, and notifications. Each provider owns a pure,
1200
+ versioned materializer with explicit identity, pagination, completeness,
1201
+ tombstone, and deletion semantics. Unsupported providers say why. A shape
1202
+ change fails at that provider-owned boundary, retains the last good normalized
1203
+ entities, and records the exact failed revision instead of guessing.
1204
+
1205
+ Omni v1 has no provider-authored write-invalidation tags. Auth-incarnation,
1206
+ materializer, and plugin implementation identity changes strand the prior
1207
+ normalized coordinates. Freshness advances only when the exact query is
1208
+ explicitly revalidated. If a newer exact snapshot drifts, Ghostget keeps the last
1209
+ good derivative and reports `retained-after-drift`. The provider-local
1210
+ diagnostic remains inside encrypted normalized state. Public reasons are
1211
+ categorical and do not echo foreign values or unreviewed property names.
1212
+
1213
+ ```sh
1214
+ ghostget omni read --input '{
1215
+ "schemaVersion": 1,
1216
+ "sources": [
1217
+ {"adapterId":"reddit-web","operationId":"messaging.list","authId":"reddit-main","input":{"folder":"inbox","limit":25}},
1218
+ {"adapterId":"whatsapp-web","operationId":"messaging.list","authId":"whatsapp-main","input":{"folder":"all","limit":100}}
1219
+ ],
1220
+ "filter": {"kinds":["conversation","message","notification"]},
1221
+ "page": {"limit":100}
1222
+ }' --cache-only --json
1223
+ ```
1224
+
1225
+ `--cache-only` reads encrypted normalized state without a browser or provider
1226
+ round trip. `--from-exact-cache` rebuilds derivatives from encrypted exact
1227
+ snapshots. The default mode revalidates supported sources independently and
1228
+ then returns one locally paged view. Provider cursors remain private; public
1229
+ view cursors are authenticated and bound to the request, account lifetimes,
1230
+ materializer closure, and view revision.
1231
+
1232
+ Each source row exposes a keyed `normalizationDataRevision` for causal cache
1233
+ comparison without revealing normalized bytes. During SWR, `current` may be an
1234
+ `omni-merged` result: it adopts a proven newer cached view while retaining every
1235
+ unresolved live source status. A concurrent advance for one provider, account,
1236
+ or continuation therefore cannot erase another live failure; the independent
1237
+ `live` and `cachedAfter` observations remain available as well.
1238
+
1239
+ ```ts
1240
+ import { staleWhileRevalidateOmniView } from "@hraness/ghostget/omni"
1241
+
1242
+ const messages = staleWhileRevalidateOmniView({
1243
+ schemaVersion: 1,
1244
+ sources: [{
1245
+ adapterId: "reddit-web",
1246
+ operationId: "messaging.list",
1247
+ authId: "reddit-main",
1248
+ input: { folder: "inbox", limit: 25 },
1249
+ }],
1250
+ })
1251
+
1252
+ render(messages.cached?.view)
1253
+ render((await messages.revalidation).current.view)
1254
+ ```
1255
+
1256
+ ## Create a portable plugin
1257
+
1258
+ An agent can create a private, network-inert starting point without editing
1259
+ Ghostget:
1260
+
1261
+ ```sh
1262
+ ghostget plugin init example-web \
1263
+ --display-name "Example" \
1264
+ --surface example \
1265
+ --origin https://www.example.com \
1266
+ --operation feeds.read \
1267
+ --output /absolute/private/example-web
1268
+
1269
+ ghostget plugin check /absolute/private/example-web --json
1270
+ ghostget plugin test /absolute/private/example-web --trust-code --json
1271
+ ghostget plugin pack /absolute/private/example-web \
1272
+ --output /absolute/private/example-web.wrenchplugin --json
1273
+ ghostget plugin install /absolute/private/example-web.wrenchplugin \
1274
+ --trust-code --json
1275
+ ```
1276
+
1277
+ `init` writes a strict `ghostget-plugin.json`, a self-contained runtime, inert
1278
+ operation metadata, secret-free fixtures, and package-local agent guidance.
1279
+ `check` is static and does not execute plugin code. `test --trust-code` binds
1280
+ the decision to the verified plugin identity before running its declared
1281
+ secret-free fixtures. `pack` creates a reproducible content-addressed package.
1282
+ `install --trust-code` is the separate decision to let that exact package run.
1283
+
1284
+ Portable code receives only declared, bounded host capabilities. Network
1285
+ requests are pinned to declared HTTPS origins; credentials are opaque handles
1286
+ usable only at declared sinks; files and state are namespaced handles; and
1287
+ mutations must use the kernel's begin, request, and verify sequence. The host
1288
+ does not expose a shell, package manager, ambient environment, unrestricted
1289
+ filesystem, redirect, retry, or arbitrary request primitive.
1290
+
1291
+ Read [the plugin guide](docs/plugins.md) before replacing an inert reservation
1292
+ with an observed contract. The repository's [Ghostget Agent Skill](skills/ghostget/SKILL.md)
1293
+ gives coding agents the same workflow and safety boundary. Its bundled
1294
+ [social cross-posting guidance](skills/ghostget/references/cross-posting.md)
1295
+ orchestrates exact, previewed text, image, and video posts across supported
1296
+ platforms while preserving per-provider attachment limits and at-most-once
1297
+ dispatch evidence. Packages built from this source carry the same consolidated
1298
+ skill as the skills CLI.
1299
+
1300
+ ## Risk and confirmation
1301
+
1302
+ - R1 is a reviewed read with no intended remote mutation.
1303
+ - R2 is one bounded, normally reversible change.
1304
+ - R3 is an externally visible or consequential change, including an exact
1305
+ authored-item deletion only where a provider-specific contract binds the
1306
+ target, current account, revision, mutation, and independent absence readback.
1307
+ - R4 is blocked.
1308
+
1309
+ R2 and R3 commands create an exact, short-lived preview. Review its adapter,
1310
+ transport, account realm, input, attachment hashes, side effect, contract hash,
1311
+ and complete dispatch schedule, then pass its digest to `ghostget confirm`.
1312
+ After a partial or indeterminate dispatch, Ghostget does not retry or switch
1313
+ transport. The run remains unsettled until exact external evidence supports a
1314
+ separate reconciliation.
1315
+
1316
+ An operator who explicitly accepts the risk of a duplicate may create one new
1317
+ intent from one terminal indeterminate `posts.publish` run:
1318
+
1319
+ ```sh
1320
+ ghostget invoke <adapter> posts.publish --input @post.json --auth <id> \
1321
+ --preview --duplicate-risk-of <source-run-id>
1322
+ ghostget confirm <new-plan-digest>
1323
+ ```
1324
+
1325
+ This v1 path is limited to one started dispatch over the same reviewed R3 web
1326
+ session contract. Ghostget revalidates the exact adapter, account realm,
1327
+ operation, normalized input (including attachment hashes), contract, source
1328
+ receipt, journal, ledger, and recovery capsule at preview and confirmation.
1329
+ The source run remains indeterminate and its evidence is never cleared or
1330
+ rewritten. Re-previewing the unchanged source produces the same successor
1331
+ intent; that successor has its own permanent at-most-once ledger. If the
1332
+ process exits after electing the successor but before starting its dispatch,
1333
+ the election remains fail-closed and must be inspected rather than retried.
1334
+
1335
+ New previews use one environment-neutral durable contract identity. Readers
1336
+ also accept the exact predecessor identities produced by the standard `test`,
1337
+ `production`, and `development` modes. They do not accept a wildcard identity
1338
+ for custom `NODE_ENV` values. Ghostget retains unsupported unsettled evidence and
1339
+ directs the operator to `ghostget doctor`, the exact predecessor build, or manual
1340
+ evidence review. Runtime loading still verifies the current exact source,
1341
+ dependency, and execution closure separately.
1342
+
1343
+ ## Develop
1344
+
1345
+ ```sh
1346
+ git clone https://github.com/hraness/ghostget.git
1347
+ cd ghostget
1348
+ bun install --frozen-lockfile
1349
+ bun run check
1350
+ ```
1351
+
1352
+ The full gate type-checks, tests, builds, runs the secret-free CLI and portable
1353
+ plugin lifecycle smoke, then installs and imports the packed package in a clean
1354
+ consumer. See [CONTRIBUTING.md](CONTRIBUTING.md) for change boundaries and
1355
+ [local development](docs/local-development.md) for isolated parallel worktrees.
1356
+
1357
+ ## License
1358
+
1359
+ MIT