@jackwener/opencli 1.0.6 → 1.1.1

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 (400) hide show
  1. package/.agents/skills/cross-project-adapter-migration/SKILL.md +2 -2
  2. package/.github/pull_request_template.md +7 -0
  3. package/.github/workflows/doc-check.yml +36 -0
  4. package/.github/workflows/docs.yml +7 -42
  5. package/CHANGELOG.md +23 -0
  6. package/CLI-EXPLORER.md +9 -8
  7. package/README.md +51 -10
  8. package/README.zh-CN.md +29 -11
  9. package/SKILL.md +102 -33
  10. package/dist/browser/cdp.js +6 -1
  11. package/dist/browser/page.d.ts +4 -1
  12. package/dist/browser/page.js +7 -1
  13. package/dist/build-manifest.js +23 -16
  14. package/dist/cli-manifest.json +951 -296
  15. package/dist/cli.d.ts +6 -0
  16. package/dist/cli.js +225 -148
  17. package/dist/clis/antigravity/serve.js +296 -47
  18. package/dist/clis/apple-podcasts/commands.test.d.ts +2 -0
  19. package/dist/clis/apple-podcasts/commands.test.js +76 -0
  20. package/dist/clis/apple-podcasts/search.js +2 -2
  21. package/dist/clis/apple-podcasts/top.js +9 -2
  22. package/dist/clis/arxiv/paper.js +21 -0
  23. package/dist/clis/arxiv/search.js +24 -0
  24. package/dist/clis/arxiv/utils.d.ts +18 -0
  25. package/dist/clis/arxiv/utils.js +49 -0
  26. package/dist/clis/bilibili/dynamic.js +1 -1
  27. package/dist/clis/bilibili/favorite.js +1 -1
  28. package/dist/clis/bilibili/feed.js +1 -1
  29. package/dist/clis/bilibili/following.js +1 -1
  30. package/dist/clis/bilibili/history.js +1 -1
  31. package/dist/clis/bilibili/me.js +1 -1
  32. package/dist/clis/bilibili/ranking.js +1 -1
  33. package/dist/clis/bilibili/search.js +3 -3
  34. package/dist/clis/bilibili/subtitle.js +1 -1
  35. package/dist/clis/bilibili/user-videos.js +1 -1
  36. package/dist/{bilibili.d.ts → clis/bilibili/utils.d.ts} +1 -1
  37. package/dist/clis/bloomberg/businessweek.d.ts +1 -0
  38. package/dist/clis/bloomberg/businessweek.js +17 -0
  39. package/dist/clis/bloomberg/economics.d.ts +1 -0
  40. package/dist/clis/bloomberg/economics.js +17 -0
  41. package/dist/clis/bloomberg/feeds.d.ts +1 -0
  42. package/dist/clis/bloomberg/feeds.js +15 -0
  43. package/dist/clis/bloomberg/industries.d.ts +1 -0
  44. package/dist/clis/bloomberg/industries.js +17 -0
  45. package/dist/clis/bloomberg/main.d.ts +1 -0
  46. package/dist/clis/bloomberg/main.js +17 -0
  47. package/dist/clis/bloomberg/markets.d.ts +1 -0
  48. package/dist/clis/bloomberg/markets.js +17 -0
  49. package/dist/clis/bloomberg/news.d.ts +1 -0
  50. package/dist/clis/bloomberg/news.js +105 -0
  51. package/dist/clis/bloomberg/opinions.d.ts +1 -0
  52. package/dist/clis/bloomberg/opinions.js +17 -0
  53. package/dist/clis/bloomberg/politics.d.ts +1 -0
  54. package/dist/clis/bloomberg/politics.js +17 -0
  55. package/dist/clis/bloomberg/tech.d.ts +1 -0
  56. package/dist/clis/bloomberg/tech.js +17 -0
  57. package/dist/clis/bloomberg/utils.d.ts +34 -0
  58. package/dist/clis/bloomberg/utils.js +364 -0
  59. package/dist/clis/bloomberg/utils.test.d.ts +1 -0
  60. package/dist/clis/bloomberg/utils.test.js +129 -0
  61. package/dist/clis/boss/batchgreet.d.ts +1 -0
  62. package/dist/clis/boss/batchgreet.js +147 -0
  63. package/dist/clis/boss/chatlist.js +2 -2
  64. package/dist/clis/boss/detail.js +2 -2
  65. package/dist/clis/boss/exchange.d.ts +1 -0
  66. package/dist/clis/boss/exchange.js +111 -0
  67. package/dist/clis/boss/greet.d.ts +1 -0
  68. package/dist/clis/boss/greet.js +175 -0
  69. package/dist/clis/boss/invite.d.ts +1 -0
  70. package/dist/clis/boss/invite.js +158 -0
  71. package/dist/clis/boss/joblist.d.ts +1 -0
  72. package/dist/clis/boss/joblist.js +55 -0
  73. package/dist/clis/boss/mark.d.ts +1 -0
  74. package/dist/clis/boss/mark.js +141 -0
  75. package/dist/clis/boss/recommend.d.ts +1 -0
  76. package/dist/clis/boss/recommend.js +83 -0
  77. package/dist/clis/boss/search.js +1 -1
  78. package/dist/clis/boss/send.js +1 -1
  79. package/dist/clis/boss/stats.d.ts +1 -0
  80. package/dist/clis/boss/stats.js +116 -0
  81. package/dist/clis/chaoxing/assignments.js +1 -1
  82. package/dist/clis/chaoxing/exams.js +1 -1
  83. package/dist/{chaoxing.d.ts → clis/chaoxing/utils.d.ts} +1 -1
  84. package/dist/{chaoxing.js → clis/chaoxing/utils.js} +0 -2
  85. package/dist/clis/chaoxing/utils.test.d.ts +1 -0
  86. package/dist/{chaoxing.test.js → clis/chaoxing/utils.test.js} +1 -1
  87. package/dist/clis/chatgpt/read.js +1 -1
  88. package/dist/clis/chatwise/export.js +1 -1
  89. package/dist/clis/chatwise/model.js +2 -2
  90. package/dist/clis/chatwise/screenshot.js +1 -1
  91. package/dist/clis/codex/export.js +1 -1
  92. package/dist/clis/codex/model.js +2 -2
  93. package/dist/clis/codex/screenshot.js +1 -1
  94. package/dist/clis/coupang/add-to-cart.js +3 -4
  95. package/dist/clis/coupang/search.js +2 -4
  96. package/dist/clis/coupang/utils.test.d.ts +1 -0
  97. package/dist/{coupang.test.js → clis/coupang/utils.test.js} +1 -1
  98. package/dist/clis/ctrip/search.js +1 -1
  99. package/dist/clis/cursor/export.js +1 -1
  100. package/dist/clis/cursor/model.js +2 -2
  101. package/dist/clis/cursor/screenshot.js +1 -1
  102. package/dist/clis/jike/comment.js +2 -3
  103. package/dist/clis/jike/create.js +1 -2
  104. package/dist/clis/jike/feed.js +0 -1
  105. package/dist/clis/jike/like.js +1 -2
  106. package/dist/clis/jike/notifications.js +0 -1
  107. package/dist/clis/jike/post.yaml +1 -0
  108. package/dist/clis/jike/repost.js +1 -2
  109. package/dist/clis/jike/search.js +2 -3
  110. package/dist/clis/jike/topic.yaml +1 -0
  111. package/dist/clis/jike/user.yaml +1 -0
  112. package/dist/clis/jimeng/history.yaml +0 -1
  113. package/dist/clis/linkedin/search.js +7 -7
  114. package/dist/clis/linux-do/category.yaml +1 -0
  115. package/dist/clis/linux-do/search.yaml +4 -3
  116. package/dist/clis/linux-do/topic.yaml +1 -0
  117. package/dist/clis/notion/export.js +1 -1
  118. package/dist/clis/reddit/comment.js +3 -4
  119. package/dist/clis/reddit/read.js +4 -5
  120. package/dist/clis/reddit/save.js +2 -3
  121. package/dist/clis/reddit/saved.js +0 -1
  122. package/dist/clis/reddit/search.yaml +1 -0
  123. package/dist/clis/reddit/subscribe.js +0 -1
  124. package/dist/clis/reddit/upvote.js +2 -3
  125. package/dist/clis/reddit/upvoted.js +0 -1
  126. package/dist/clis/reddit/user-comments.yaml +1 -0
  127. package/dist/clis/reddit/user-posts.yaml +1 -0
  128. package/dist/clis/reddit/user.yaml +1 -0
  129. package/dist/clis/reuters/search.js +1 -1
  130. package/dist/clis/sinafinance/news.d.ts +7 -0
  131. package/dist/clis/sinafinance/news.js +61 -0
  132. package/dist/clis/smzdm/search.js +2 -3
  133. package/dist/clis/stackoverflow/search.yaml +1 -0
  134. package/dist/clis/steam/top-sellers.yaml +29 -0
  135. package/dist/clis/twitter/accept.js +2 -2
  136. package/dist/clis/twitter/article.js +2 -2
  137. package/dist/clis/twitter/block.d.ts +1 -0
  138. package/dist/clis/twitter/block.js +88 -0
  139. package/dist/clis/twitter/delete.js +1 -1
  140. package/dist/clis/twitter/hide-reply.d.ts +1 -0
  141. package/dist/clis/twitter/hide-reply.js +66 -0
  142. package/dist/clis/twitter/like.js +1 -1
  143. package/dist/clis/twitter/post.js +1 -1
  144. package/dist/clis/twitter/reply-dm.js +1 -1
  145. package/dist/clis/twitter/reply.js +2 -2
  146. package/dist/clis/twitter/search.js +1 -1
  147. package/dist/clis/twitter/thread.js +2 -2
  148. package/dist/clis/twitter/trending.d.ts +1 -0
  149. package/dist/clis/twitter/trending.js +91 -0
  150. package/dist/clis/twitter/unblock.d.ts +1 -0
  151. package/dist/clis/twitter/unblock.js +71 -0
  152. package/dist/clis/v2ex/topic.yaml +1 -0
  153. package/dist/clis/weibo/hot.js +0 -1
  154. package/dist/clis/weread/book.js +1 -1
  155. package/dist/clis/weread/highlights.js +1 -1
  156. package/dist/clis/weread/notes.js +1 -1
  157. package/dist/clis/weread/search.js +1 -1
  158. package/dist/clis/wikipedia/search.d.ts +1 -0
  159. package/dist/clis/wikipedia/search.js +30 -0
  160. package/dist/clis/wikipedia/summary.d.ts +1 -0
  161. package/dist/clis/wikipedia/summary.js +28 -0
  162. package/dist/clis/wikipedia/utils.d.ts +8 -0
  163. package/dist/clis/wikipedia/utils.js +18 -0
  164. package/dist/clis/xiaohongshu/creator-note-detail.d.ts +79 -5
  165. package/dist/clis/xiaohongshu/creator-note-detail.js +323 -70
  166. package/dist/clis/xiaohongshu/creator-note-detail.test.d.ts +1 -0
  167. package/dist/clis/xiaohongshu/creator-note-detail.test.js +258 -0
  168. package/dist/clis/xiaohongshu/creator-notes-summary.d.ts +28 -0
  169. package/dist/clis/xiaohongshu/creator-notes-summary.js +92 -0
  170. package/dist/clis/xiaohongshu/creator-notes-summary.test.d.ts +1 -0
  171. package/dist/clis/xiaohongshu/creator-notes-summary.test.js +49 -0
  172. package/dist/clis/xiaohongshu/creator-notes.d.ts +18 -5
  173. package/dist/clis/xiaohongshu/creator-notes.js +189 -71
  174. package/dist/clis/xiaohongshu/creator-notes.test.d.ts +1 -0
  175. package/dist/clis/xiaohongshu/creator-notes.test.js +191 -0
  176. package/dist/clis/xiaohongshu/creator-profile.js +0 -1
  177. package/dist/clis/xiaohongshu/creator-stats.js +0 -1
  178. package/dist/clis/xiaohongshu/download.js +2 -3
  179. package/dist/clis/xiaohongshu/feed.yaml +0 -1
  180. package/dist/clis/xiaohongshu/notifications.yaml +0 -1
  181. package/dist/clis/xiaohongshu/search.js +2 -2
  182. package/dist/clis/xiaohongshu/user.js +1 -2
  183. package/dist/clis/yahoo-finance/quote.js +0 -1
  184. package/dist/clis/youtube/search.js +1 -1
  185. package/dist/clis/youtube/transcript.js +1 -1
  186. package/dist/clis/youtube/video.js +1 -1
  187. package/dist/clis/zhihu/download.js +1 -2
  188. package/dist/clis/zhihu/question.js +1 -1
  189. package/dist/clis/zhihu/search.yaml +4 -3
  190. package/dist/commanderAdapter.d.ts +21 -0
  191. package/dist/commanderAdapter.js +111 -0
  192. package/dist/{engine.d.ts → discovery.d.ts} +0 -6
  193. package/dist/{engine.js → discovery.js} +1 -98
  194. package/dist/download/index.d.ts +2 -6
  195. package/dist/download/index.js +19 -46
  196. package/dist/engine.test.d.ts +1 -1
  197. package/dist/engine.test.js +8 -7
  198. package/dist/execution.d.ts +22 -0
  199. package/dist/execution.js +129 -0
  200. package/dist/explore.js +121 -107
  201. package/dist/external-clis.yaml +48 -0
  202. package/dist/external.d.ts +25 -0
  203. package/dist/external.js +156 -0
  204. package/dist/main.js +1 -1
  205. package/dist/pipeline/steps/browser.js +8 -2
  206. package/dist/registry.d.ts +2 -0
  207. package/dist/registry.js +2 -0
  208. package/dist/runtime.d.ts +5 -0
  209. package/dist/runtime.js +8 -0
  210. package/dist/serialization.d.ts +34 -0
  211. package/dist/serialization.js +63 -0
  212. package/dist/types.d.ts +4 -1
  213. package/docs/.vitepress/config.mts +14 -3
  214. package/docs/adapters/browser/arxiv.md +27 -0
  215. package/docs/adapters/browser/barchart.md +32 -0
  216. package/docs/adapters/browser/bloomberg.md +70 -0
  217. package/docs/adapters/browser/chaoxing.md +39 -0
  218. package/docs/adapters/browser/grok.md +35 -0
  219. package/docs/adapters/browser/hf.md +42 -0
  220. package/docs/adapters/browser/jike.md +45 -0
  221. package/docs/adapters/browser/jimeng.md +39 -0
  222. package/docs/adapters/browser/linux-do.md +45 -0
  223. package/docs/adapters/browser/sinafinance.md +35 -0
  224. package/docs/adapters/browser/stackoverflow.md +35 -0
  225. package/docs/adapters/browser/steam.md +26 -0
  226. package/docs/adapters/browser/twitter.md +3 -0
  227. package/docs/adapters/browser/weread.md +48 -0
  228. package/docs/adapters/browser/wikipedia.md +30 -0
  229. package/docs/adapters/browser/xiaohongshu.md +5 -1
  230. package/docs/adapters/desktop/chatgpt.md +3 -3
  231. package/docs/adapters/index.md +13 -0
  232. package/docs/advanced/download.md +4 -4
  233. package/docs/developer/architecture.md +17 -4
  234. package/package.json +1 -1
  235. package/scripts/check-doc-coverage.sh +69 -0
  236. package/scripts/copy-yaml.cjs +7 -0
  237. package/src/browser/cdp.ts +9 -4
  238. package/src/browser/page.ts +7 -1
  239. package/src/build-manifest.ts +25 -19
  240. package/src/cli.ts +253 -119
  241. package/src/clis/antigravity/serve.ts +323 -50
  242. package/src/clis/apple-podcasts/commands.test.ts +95 -0
  243. package/src/clis/apple-podcasts/search.ts +2 -2
  244. package/src/clis/apple-podcasts/top.ts +12 -2
  245. package/src/clis/arxiv/paper.ts +21 -0
  246. package/src/clis/arxiv/search.ts +24 -0
  247. package/src/clis/arxiv/utils.ts +63 -0
  248. package/src/clis/bilibili/dynamic.ts +1 -1
  249. package/src/clis/bilibili/favorite.ts +1 -1
  250. package/src/clis/bilibili/feed.ts +1 -1
  251. package/src/clis/bilibili/following.ts +1 -1
  252. package/src/clis/bilibili/history.ts +1 -1
  253. package/src/clis/bilibili/me.ts +1 -1
  254. package/src/clis/bilibili/ranking.ts +1 -1
  255. package/src/clis/bilibili/search.ts +3 -3
  256. package/src/clis/bilibili/subtitle.ts +1 -1
  257. package/src/clis/bilibili/user-videos.ts +1 -1
  258. package/src/{bilibili.ts → clis/bilibili/utils.ts} +1 -1
  259. package/src/clis/bloomberg/businessweek.ts +18 -0
  260. package/src/clis/bloomberg/economics.ts +18 -0
  261. package/src/clis/bloomberg/feeds.ts +16 -0
  262. package/src/clis/bloomberg/industries.ts +18 -0
  263. package/src/clis/bloomberg/main.ts +18 -0
  264. package/src/clis/bloomberg/markets.ts +18 -0
  265. package/src/clis/bloomberg/news.ts +136 -0
  266. package/src/clis/bloomberg/opinions.ts +18 -0
  267. package/src/clis/bloomberg/politics.ts +18 -0
  268. package/src/clis/bloomberg/tech.ts +18 -0
  269. package/src/clis/bloomberg/utils.test.ts +135 -0
  270. package/src/clis/bloomberg/utils.ts +429 -0
  271. package/src/clis/boss/batchgreet.ts +167 -0
  272. package/src/clis/boss/chatlist.ts +2 -2
  273. package/src/clis/boss/detail.ts +2 -2
  274. package/src/clis/boss/exchange.ts +126 -0
  275. package/src/clis/boss/greet.ts +198 -0
  276. package/src/clis/boss/invite.ts +177 -0
  277. package/src/clis/boss/joblist.ts +63 -0
  278. package/src/clis/boss/mark.ts +155 -0
  279. package/src/clis/boss/recommend.ts +94 -0
  280. package/src/clis/boss/search.ts +1 -1
  281. package/src/clis/boss/send.ts +1 -1
  282. package/src/clis/boss/stats.ts +130 -0
  283. package/src/clis/chaoxing/assignments.ts +1 -1
  284. package/src/clis/chaoxing/exams.ts +1 -1
  285. package/src/{chaoxing.test.ts → clis/chaoxing/utils.test.ts} +1 -1
  286. package/src/{chaoxing.ts → clis/chaoxing/utils.ts} +1 -3
  287. package/src/clis/chatgpt/README.zh-CN.md +3 -3
  288. package/src/clis/chatgpt/read.ts +1 -1
  289. package/src/clis/chatwise/export.ts +1 -1
  290. package/src/clis/chatwise/model.ts +2 -2
  291. package/src/clis/chatwise/screenshot.ts +1 -1
  292. package/src/clis/codex/export.ts +1 -1
  293. package/src/clis/codex/model.ts +2 -2
  294. package/src/clis/codex/screenshot.ts +1 -1
  295. package/src/clis/coupang/add-to-cart.ts +3 -4
  296. package/src/clis/coupang/search.ts +2 -4
  297. package/src/{coupang.test.ts → clis/coupang/utils.test.ts} +1 -1
  298. package/src/clis/ctrip/search.ts +1 -1
  299. package/src/clis/cursor/export.ts +1 -1
  300. package/src/clis/cursor/model.ts +2 -2
  301. package/src/clis/cursor/screenshot.ts +1 -1
  302. package/src/clis/jike/comment.ts +2 -3
  303. package/src/clis/jike/create.ts +1 -2
  304. package/src/clis/jike/feed.ts +0 -1
  305. package/src/clis/jike/like.ts +1 -2
  306. package/src/clis/jike/notifications.ts +0 -1
  307. package/src/clis/jike/post.yaml +1 -0
  308. package/src/clis/jike/repost.ts +1 -2
  309. package/src/clis/jike/search.ts +2 -3
  310. package/src/clis/jike/topic.yaml +1 -0
  311. package/src/clis/jike/user.yaml +1 -0
  312. package/src/clis/jimeng/history.yaml +0 -1
  313. package/src/clis/linkedin/search.ts +7 -7
  314. package/src/clis/linux-do/category.yaml +1 -0
  315. package/src/clis/linux-do/search.yaml +4 -3
  316. package/src/clis/linux-do/topic.yaml +1 -0
  317. package/src/clis/notion/export.ts +1 -1
  318. package/src/clis/reddit/comment.ts +3 -4
  319. package/src/clis/reddit/read.ts +4 -5
  320. package/src/clis/reddit/save.ts +2 -3
  321. package/src/clis/reddit/saved.ts +0 -1
  322. package/src/clis/reddit/search.yaml +1 -0
  323. package/src/clis/reddit/subscribe.ts +0 -1
  324. package/src/clis/reddit/upvote.ts +2 -3
  325. package/src/clis/reddit/upvoted.ts +0 -1
  326. package/src/clis/reddit/user-comments.yaml +1 -0
  327. package/src/clis/reddit/user-posts.yaml +1 -0
  328. package/src/clis/reddit/user.yaml +1 -0
  329. package/src/clis/reuters/search.ts +1 -1
  330. package/src/clis/sinafinance/news.ts +76 -0
  331. package/src/clis/smzdm/search.ts +2 -3
  332. package/src/clis/stackoverflow/search.yaml +1 -0
  333. package/src/clis/steam/top-sellers.yaml +29 -0
  334. package/src/clis/twitter/accept.ts +2 -2
  335. package/src/clis/twitter/article.ts +2 -2
  336. package/src/clis/twitter/block.ts +92 -0
  337. package/src/clis/twitter/delete.ts +1 -1
  338. package/src/clis/twitter/hide-reply.ts +70 -0
  339. package/src/clis/twitter/like.ts +1 -1
  340. package/src/clis/twitter/post.ts +1 -1
  341. package/src/clis/twitter/reply-dm.ts +1 -1
  342. package/src/clis/twitter/reply.ts +2 -2
  343. package/src/clis/twitter/search.ts +1 -1
  344. package/src/clis/twitter/thread.ts +2 -2
  345. package/src/clis/twitter/trending.ts +113 -0
  346. package/src/clis/twitter/unblock.ts +75 -0
  347. package/src/clis/v2ex/topic.yaml +1 -0
  348. package/src/clis/weibo/hot.ts +0 -1
  349. package/src/clis/weread/book.ts +1 -1
  350. package/src/clis/weread/highlights.ts +1 -1
  351. package/src/clis/weread/notes.ts +1 -1
  352. package/src/clis/weread/search.ts +1 -1
  353. package/src/clis/wikipedia/search.ts +32 -0
  354. package/src/clis/wikipedia/summary.ts +28 -0
  355. package/src/clis/wikipedia/utils.ts +20 -0
  356. package/src/clis/xiaohongshu/creator-note-detail.test.ts +272 -0
  357. package/src/clis/xiaohongshu/creator-note-detail.ts +425 -73
  358. package/src/clis/xiaohongshu/creator-notes-summary.test.ts +54 -0
  359. package/src/clis/xiaohongshu/creator-notes-summary.ts +120 -0
  360. package/src/clis/xiaohongshu/creator-notes.test.ts +211 -0
  361. package/src/clis/xiaohongshu/creator-notes.ts +254 -75
  362. package/src/clis/xiaohongshu/creator-profile.ts +0 -1
  363. package/src/clis/xiaohongshu/creator-stats.ts +0 -1
  364. package/src/clis/xiaohongshu/download.ts +2 -3
  365. package/src/clis/xiaohongshu/feed.yaml +0 -1
  366. package/src/clis/xiaohongshu/notifications.yaml +0 -1
  367. package/src/clis/xiaohongshu/search.ts +2 -2
  368. package/src/clis/xiaohongshu/user.ts +1 -2
  369. package/src/clis/yahoo-finance/quote.ts +0 -1
  370. package/src/clis/youtube/search.ts +1 -1
  371. package/src/clis/youtube/transcript.ts +1 -1
  372. package/src/clis/youtube/video.ts +1 -1
  373. package/src/clis/zhihu/download.ts +1 -2
  374. package/src/clis/zhihu/question.ts +1 -1
  375. package/src/clis/zhihu/search.yaml +4 -3
  376. package/src/commanderAdapter.ts +113 -0
  377. package/src/daemon.ts +3 -3
  378. package/src/{engine.ts → discovery.ts} +1 -108
  379. package/src/download/index.ts +21 -54
  380. package/src/engine.test.ts +8 -7
  381. package/src/execution.ts +138 -0
  382. package/src/explore.ts +135 -109
  383. package/src/external-clis.yaml +48 -0
  384. package/src/external.ts +185 -0
  385. package/src/main.ts +1 -1
  386. package/src/pipeline/steps/browser.ts +7 -2
  387. package/src/registry.ts +5 -0
  388. package/src/runtime.ts +9 -0
  389. package/src/serialization.ts +79 -0
  390. package/src/types.ts +1 -1
  391. package/tests/e2e/browser-public.test.ts +25 -0
  392. package/tests/e2e/public-commands.test.ts +55 -1
  393. package/dist/clis/twitter/trending.yaml +0 -46
  394. package/src/clis/twitter/trending.yaml +0 -46
  395. /package/dist/{chaoxing.test.d.ts → clis/arxiv/paper.d.ts} +0 -0
  396. /package/dist/{coupang.test.d.ts → clis/arxiv/search.d.ts} +0 -0
  397. /package/dist/{bilibili.js → clis/bilibili/utils.js} +0 -0
  398. /package/dist/{coupang.d.ts → clis/coupang/utils.d.ts} +0 -0
  399. /package/dist/{coupang.js → clis/coupang/utils.js} +0 -0
  400. /package/src/{coupang.ts → clis/coupang/utils.ts} +0 -0
@@ -0,0 +1,63 @@
1
+ /**
2
+ * arXiv adapter utilities.
3
+ *
4
+ * arXiv exposes a public Atom/XML API — no key required.
5
+ * https://info.arxiv.org/help/api/index.html
6
+ */
7
+
8
+ import { CliError } from '../../errors.js';
9
+
10
+ export const ARXIV_BASE = 'https://export.arxiv.org/api/query';
11
+
12
+ export async function arxivFetch(params: string): Promise<string> {
13
+ const resp = await fetch(`${ARXIV_BASE}?${params}`);
14
+ if (!resp.ok) {
15
+ throw new CliError('FETCH_ERROR', `arXiv API HTTP ${resp.status}`, 'Check your search term or paper ID');
16
+ }
17
+ return resp.text();
18
+ }
19
+
20
+ /** Extract the text content of the first matching XML tag. */
21
+ function extract(xml: string, tag: string): string {
22
+ const m = xml.match(new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`));
23
+ return m ? m[1].trim() : '';
24
+ }
25
+
26
+ /** Extract all text contents of a repeated XML tag. */
27
+ function extractAll(xml: string, tag: string): string[] {
28
+ const re = new RegExp(`<${tag}[^>]*>([\\s\\S]*?)<\\/${tag}>`, 'g');
29
+ const results: string[] = [];
30
+ let m: RegExpExecArray | null;
31
+ while ((m = re.exec(xml)) !== null) results.push(m[1].trim());
32
+ return results;
33
+ }
34
+
35
+ export interface ArxivEntry {
36
+ id: string;
37
+ title: string;
38
+ authors: string;
39
+ abstract: string;
40
+ published: string;
41
+ url: string;
42
+ }
43
+
44
+ /** Parse Atom XML feed into structured entries. */
45
+ export function parseEntries(xml: string): ArxivEntry[] {
46
+ const entryRe = /<entry>([\s\S]*?)<\/entry>/g;
47
+ const entries: ArxivEntry[] = [];
48
+ let m: RegExpExecArray | null;
49
+ while ((m = entryRe.exec(xml)) !== null) {
50
+ const e = m[1];
51
+ const rawId = extract(e, 'id');
52
+ const arxivId = rawId.replace(/^https?:\/\/arxiv\.org\/abs\//, '').replace(/v\d+$/, '');
53
+ entries.push({
54
+ id: arxivId,
55
+ title: extract(e, 'title').replace(/\s+/g, ' '),
56
+ authors: extractAll(e, 'name').slice(0, 3).join(', '),
57
+ abstract: (() => { const s = extract(e, 'summary').replace(/\s+/g, ' '); return s.length > 200 ? s.slice(0, 200) + '...' : s; })(),
58
+ published: extract(e, 'published').slice(0, 10),
59
+ url: `https://arxiv.org/abs/${arxivId}`,
60
+ });
61
+ }
62
+ return entries;
63
+ }
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { apiGet } from '../../bilibili.js';
2
+ import { apiGet } from './utils.js';
3
3
 
4
4
  cli({
5
5
  site: 'bilibili',
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { apiGet, payloadData } from '../../bilibili.js';
2
+ import { apiGet, payloadData } from './utils.js';
3
3
 
4
4
  cli({
5
5
  site: 'bilibili',
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { apiGet, payloadData, getSelfUid, stripHtml } from '../../bilibili.js';
2
+ import { apiGet, payloadData, getSelfUid, stripHtml } from './utils.js';
3
3
 
4
4
  cli({
5
5
  site: 'bilibili',
@@ -1,6 +1,6 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
2
  import type { IPage } from '../../types.js';
3
- import { fetchJson, getSelfUid, resolveUid } from '../../bilibili.js';
3
+ import { fetchJson, getSelfUid, resolveUid } from './utils.js';
4
4
 
5
5
  cli({
6
6
  site: 'bilibili',
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { apiGet, payloadData } from '../../bilibili.js';
2
+ import { apiGet, payloadData } from './utils.js';
3
3
 
4
4
  cli({
5
5
  site: 'bilibili',
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { apiGet, getSelfUid } from '../../bilibili.js';
2
+ import { apiGet, getSelfUid } from './utils.js';
3
3
 
4
4
  cli({
5
5
  site: 'bilibili', name: 'me', description: 'My Bilibili profile info', domain: 'www.bilibili.com', strategy: Strategy.COOKIE,
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { apiGet } from '../../bilibili.js';
2
+ import { apiGet } from './utils.js';
3
3
 
4
4
  cli({
5
5
  site: 'bilibili',
@@ -1,17 +1,17 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { apiGet, stripHtml } from '../../bilibili.js';
2
+ import { apiGet, stripHtml } from './utils.js';
3
3
 
4
4
  cli({
5
5
  site: 'bilibili', name: 'search', description: 'Search Bilibili videos or users', domain: 'www.bilibili.com', strategy: Strategy.COOKIE,
6
6
  args: [
7
- { name: 'keyword', required: true, help: 'Search keyword' },
7
+ { name: 'query', required: true, positional: true, help: 'Search keyword' },
8
8
  { name: 'type', default: 'video', help: 'video or user' },
9
9
  { name: 'page', type: 'int', default: 1, help: 'Result page' },
10
10
  { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
11
11
  ],
12
12
  columns: ['rank', 'title', 'author', 'score', 'url'],
13
13
  func: async (page, kwargs) => {
14
- const { keyword, type = 'video', page: pageNum = 1, limit = 20 } = kwargs;
14
+ const { query: keyword, type = 'video', page: pageNum = 1, limit = 20 } = kwargs;
15
15
  const searchType = type === 'user' ? 'bili_user' : 'video';
16
16
  const payload = await apiGet(page, '/x/web-interface/wbi/search/type', { params: { search_type: searchType, keyword, page: pageNum }, signed: true });
17
17
  const results: any[] = payload?.data?.result ?? [];
@@ -1,6 +1,6 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
2
  import type { IPage } from '../../types.js';
3
- import { apiGet } from '../../bilibili.js';
3
+ import { apiGet } from './utils.js';
4
4
 
5
5
  cli({
6
6
  site: 'bilibili',
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { apiGet, payloadData, resolveUid } from '../../bilibili.js';
2
+ import { apiGet, payloadData, resolveUid } from './utils.js';
3
3
 
4
4
  cli({
5
5
  site: 'bilibili',
@@ -2,7 +2,7 @@
2
2
  * Bilibili shared helpers: WBI signing, authenticated fetch, nav data, UID resolution.
3
3
  */
4
4
 
5
- import type { IPage } from './types.js';
5
+ import type { IPage } from '../../types.js';
6
6
 
7
7
  const MIXIN_KEY_ENC_TAB = [
8
8
  46,47,18,2,53,8,23,32,15,50,10,31,58,3,45,35,27,43,5,49,
@@ -0,0 +1,18 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { fetchBloombergFeed } from './utils.js';
3
+
4
+ cli({
5
+ site: 'bloomberg',
6
+ name: 'businessweek',
7
+ description: 'Bloomberg Businessweek top stories (RSS)',
8
+ domain: 'feeds.bloomberg.com',
9
+ strategy: Strategy.PUBLIC,
10
+ browser: false,
11
+ args: [
12
+ { name: 'limit', type: 'int', default: 1, help: 'Number of feed items to return (max 20)' },
13
+ ],
14
+ columns: ['title', 'summary', 'link', 'mediaLinks'],
15
+ func: async (_page, kwargs) => {
16
+ return fetchBloombergFeed('businessweek', kwargs.limit ?? 1);
17
+ },
18
+ });
@@ -0,0 +1,18 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { fetchBloombergFeed } from './utils.js';
3
+
4
+ cli({
5
+ site: 'bloomberg',
6
+ name: 'economics',
7
+ description: 'Bloomberg Economics top stories (RSS)',
8
+ domain: 'feeds.bloomberg.com',
9
+ strategy: Strategy.PUBLIC,
10
+ browser: false,
11
+ args: [
12
+ { name: 'limit', type: 'int', default: 1, help: 'Number of feed items to return (max 20)' },
13
+ ],
14
+ columns: ['title', 'summary', 'link', 'mediaLinks'],
15
+ func: async (_page, kwargs) => {
16
+ return fetchBloombergFeed('economics', kwargs.limit ?? 1);
17
+ },
18
+ });
@@ -0,0 +1,16 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { BLOOMBERG_FEEDS } from './utils.js';
3
+
4
+ cli({
5
+ site: 'bloomberg',
6
+ name: 'feeds',
7
+ description: 'List the Bloomberg RSS feed aliases used by the adapter',
8
+ domain: 'feeds.bloomberg.com',
9
+ strategy: Strategy.PUBLIC,
10
+ browser: false,
11
+ args: [],
12
+ columns: ['name', 'url'],
13
+ func: async () => {
14
+ return Object.entries(BLOOMBERG_FEEDS).map(([name, url]) => ({ name, url }));
15
+ },
16
+ });
@@ -0,0 +1,18 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { fetchBloombergFeed } from './utils.js';
3
+
4
+ cli({
5
+ site: 'bloomberg',
6
+ name: 'industries',
7
+ description: 'Bloomberg Industries top stories (RSS)',
8
+ domain: 'feeds.bloomberg.com',
9
+ strategy: Strategy.PUBLIC,
10
+ browser: false,
11
+ args: [
12
+ { name: 'limit', type: 'int', default: 1, help: 'Number of feed items to return (max 20)' },
13
+ ],
14
+ columns: ['title', 'summary', 'link', 'mediaLinks'],
15
+ func: async (_page, kwargs) => {
16
+ return fetchBloombergFeed('industries', kwargs.limit ?? 1);
17
+ },
18
+ });
@@ -0,0 +1,18 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { fetchBloombergFeed } from './utils.js';
3
+
4
+ cli({
5
+ site: 'bloomberg',
6
+ name: 'main',
7
+ description: 'Bloomberg homepage top stories (RSS)',
8
+ domain: 'feeds.bloomberg.com',
9
+ strategy: Strategy.PUBLIC,
10
+ browser: false,
11
+ args: [
12
+ { name: 'limit', type: 'int', default: 1, help: 'Number of feed items to return (max 20)' },
13
+ ],
14
+ columns: ['title', 'summary', 'link', 'mediaLinks'],
15
+ func: async (_page, kwargs) => {
16
+ return fetchBloombergFeed('main', kwargs.limit ?? 1);
17
+ },
18
+ });
@@ -0,0 +1,18 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { fetchBloombergFeed } from './utils.js';
3
+
4
+ cli({
5
+ site: 'bloomberg',
6
+ name: 'markets',
7
+ description: 'Bloomberg Markets top stories (RSS)',
8
+ domain: 'feeds.bloomberg.com',
9
+ strategy: Strategy.PUBLIC,
10
+ browser: false,
11
+ args: [
12
+ { name: 'limit', type: 'int', default: 1, help: 'Number of feed items to return (max 20)' },
13
+ ],
14
+ columns: ['title', 'summary', 'link', 'mediaLinks'],
15
+ func: async (_page, kwargs) => {
16
+ return fetchBloombergFeed('markets', kwargs.limit ?? 1);
17
+ },
18
+ });
@@ -0,0 +1,136 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { CliError } from '../../errors.js';
3
+ import {
4
+ extractStoryMediaLinks,
5
+ renderStoryBody,
6
+ validateBloombergLink,
7
+ type BloombergStory,
8
+ } from './utils.js';
9
+
10
+ cli({
11
+ site: 'bloomberg',
12
+ name: 'news',
13
+ description: 'Read a Bloomberg story/article page and return title, full content, and media links',
14
+ domain: 'www.bloomberg.com',
15
+ strategy: Strategy.COOKIE,
16
+ browser: true,
17
+ args: [
18
+ { name: 'link', positional: true, required: true, help: 'Bloomberg story/article URL or relative Bloomberg path' },
19
+ ],
20
+ columns: ['title', 'summary', 'link', 'mediaLinks', 'content'],
21
+ func: async (page, kwargs) => {
22
+ const url = validateBloombergLink(kwargs.link);
23
+
24
+ // Navigate and wait for the page to hydrate before extracting story data.
25
+ await page.goto(url);
26
+ await page.wait(5);
27
+
28
+ const loadStory = async () => page.evaluate(`(() => {
29
+ const isRobot = /Are you a robot/i.test(document.title)
30
+ || /unusual activity/i.test(document.body.innerText)
31
+ || /click the box below to let us know you're not a robot/i.test(document.body.innerText);
32
+
33
+ if (isRobot) {
34
+ return {
35
+ errorCode: 'ROBOT_PAGE',
36
+ title: document.title,
37
+ preview: document.body.innerText.slice(0, 400),
38
+ };
39
+ }
40
+
41
+ const raw = document.querySelector('#__NEXT_DATA__')?.textContent;
42
+ if (!raw) {
43
+ return {
44
+ errorCode: 'NO_NEXT_DATA',
45
+ title: document.title,
46
+ preview: document.body.innerText.slice(0, 400),
47
+ };
48
+ }
49
+
50
+ let parsed;
51
+ try {
52
+ parsed = JSON.parse(raw);
53
+ } catch (err) {
54
+ return {
55
+ errorCode: 'BAD_NEXT_DATA',
56
+ title: document.title,
57
+ preview: document.body.innerText.slice(0, 400),
58
+ message: String(err),
59
+ };
60
+ }
61
+
62
+ const story = parsed?.props?.pageProps?.story;
63
+ if (!story) {
64
+ return {
65
+ errorCode: 'NO_STORY',
66
+ title: document.title,
67
+ preview: document.body.innerText.slice(0, 400),
68
+ };
69
+ }
70
+
71
+ return {
72
+ story: {
73
+ headline: story.headline || story.seoHeadline || story.seoTitle || document.querySelector('h1')?.textContent?.trim() || document.title,
74
+ summary: story.summary || story.socialDescription || story.seoDescription || document.querySelector('meta[name="description"]')?.getAttribute('content') || '',
75
+ url: story.url || story.readingUrl || location.href,
76
+ body: story.body || null,
77
+ lede: story.lede || null,
78
+ ledeImageUrl: story.ledeImageUrl || null,
79
+ socialImageUrl: story.socialImageUrl || null,
80
+ imageAttachments: story.imageAttachments || {},
81
+ videoAttachments: story.videoAttachments || {},
82
+ }
83
+ };
84
+ })()`);
85
+
86
+ let result: any = await loadStory();
87
+
88
+ // Retry once — Bloomberg pages sometimes hydrate slowly.
89
+ if (result?.errorCode === 'NO_NEXT_DATA' || result?.errorCode === 'NO_STORY') {
90
+ await page.wait(4);
91
+ result = await loadStory();
92
+ }
93
+
94
+ if (result?.errorCode === 'ROBOT_PAGE') {
95
+ throw new CliError(
96
+ 'FETCH_ERROR',
97
+ 'Bloomberg served the bot-protection page instead of article content',
98
+ 'Try again later or open the article in a regular Chrome session first, then rerun the command. This command uses your current Bloomberg access and does not bypass paywall or entitlement checks.',
99
+ );
100
+ }
101
+
102
+ if (result?.errorCode) {
103
+ throw new CliError(
104
+ 'PARSE_ERROR',
105
+ `Bloomberg page did not expose article story data (${result.errorCode})`,
106
+ 'This command currently works on standard Bloomberg story/article pages that expose __NEXT_DATA__. Audio, video, newsletter, or other non-standard/blocked pages may not work. Access still depends on your current Bloomberg session.',
107
+ );
108
+ }
109
+
110
+ const story = result?.story as BloombergStory | undefined;
111
+ if (!story) {
112
+ throw new CliError(
113
+ 'PARSE_ERROR',
114
+ 'Failed to extract Bloomberg story data',
115
+ 'Bloomberg may have changed the page structure.',
116
+ );
117
+ }
118
+
119
+ const content = renderStoryBody(story.body);
120
+ if (!content) {
121
+ throw new CliError(
122
+ 'PARSE_ERROR',
123
+ 'Bloomberg article body was empty after parsing',
124
+ 'Bloomberg may have changed the story-body format, the URL may not point to a standard article page, or the page may not be accessible in your current Bloomberg session.',
125
+ );
126
+ }
127
+
128
+ return [{
129
+ title: story.headline || '',
130
+ summary: story.summary || '',
131
+ link: story.url || url,
132
+ mediaLinks: extractStoryMediaLinks(story),
133
+ content,
134
+ }];
135
+ },
136
+ });
@@ -0,0 +1,18 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { fetchBloombergFeed } from './utils.js';
3
+
4
+ cli({
5
+ site: 'bloomberg',
6
+ name: 'opinions',
7
+ description: 'Bloomberg Opinion top stories (RSS)',
8
+ domain: 'feeds.bloomberg.com',
9
+ strategy: Strategy.PUBLIC,
10
+ browser: false,
11
+ args: [
12
+ { name: 'limit', type: 'int', default: 1, help: 'Number of feed items to return (max 20)' },
13
+ ],
14
+ columns: ['title', 'summary', 'link', 'mediaLinks'],
15
+ func: async (_page, kwargs) => {
16
+ return fetchBloombergFeed('opinions', kwargs.limit ?? 1);
17
+ },
18
+ });
@@ -0,0 +1,18 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { fetchBloombergFeed } from './utils.js';
3
+
4
+ cli({
5
+ site: 'bloomberg',
6
+ name: 'politics',
7
+ description: 'Bloomberg Politics top stories (RSS)',
8
+ domain: 'feeds.bloomberg.com',
9
+ strategy: Strategy.PUBLIC,
10
+ browser: false,
11
+ args: [
12
+ { name: 'limit', type: 'int', default: 1, help: 'Number of feed items to return (max 20)' },
13
+ ],
14
+ columns: ['title', 'summary', 'link', 'mediaLinks'],
15
+ func: async (_page, kwargs) => {
16
+ return fetchBloombergFeed('politics', kwargs.limit ?? 1);
17
+ },
18
+ });
@@ -0,0 +1,18 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { fetchBloombergFeed } from './utils.js';
3
+
4
+ cli({
5
+ site: 'bloomberg',
6
+ name: 'tech',
7
+ description: 'Bloomberg Tech top stories (RSS)',
8
+ domain: 'feeds.bloomberg.com',
9
+ strategy: Strategy.PUBLIC,
10
+ browser: false,
11
+ args: [
12
+ { name: 'limit', type: 'int', default: 1, help: 'Number of feed items to return (max 20)' },
13
+ ],
14
+ columns: ['title', 'summary', 'link', 'mediaLinks'],
15
+ func: async (_page, kwargs) => {
16
+ return fetchBloombergFeed('tech', kwargs.limit ?? 1);
17
+ },
18
+ });
@@ -0,0 +1,135 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { extractStoryMediaLinks, parseBloombergRss, renderStoryBody } from './utils.js';
3
+
4
+ describe('Bloomberg utils', () => {
5
+ it('parses Bloomberg RSS items with summary, link, and deduped media links', () => {
6
+ const xml = `<?xml version="1.0" encoding="UTF-8"?>
7
+ <rss xmlns:media="http://search.yahoo.com/mrss/">
8
+ <channel>
9
+ <item>
10
+ <title><![CDATA[Headline One]]></title>
11
+ <description><![CDATA[Summary <b>One</b> &amp; more]]></description>
12
+ <link>https://www.bloomberg.com/news/articles/2026-03-19/example-one</link>
13
+ <media:content url="https://assets.bwbx.io/example-one.jpg" type="image/jpeg">
14
+ <media:thumbnail url="https://assets.bwbx.io/example-one.jpg" />
15
+ </media:content>
16
+ </item>
17
+ <item>
18
+ <title>Headline Two</title>
19
+ <description>Summary Two</description>
20
+ <guid isPermaLink="true">https://www.bloomberg.com/news/articles/2026-03-19/example-two</guid>
21
+ <enclosure url="https://assets.bwbx.io/example-two.png" type="image/png" />
22
+ </item>
23
+ </channel>
24
+ </rss>`;
25
+
26
+ const items = parseBloombergRss(xml);
27
+ expect(items).toHaveLength(2);
28
+ expect(items[0]).toEqual({
29
+ title: 'Headline One',
30
+ summary: 'Summary One & more',
31
+ link: 'https://www.bloomberg.com/news/articles/2026-03-19/example-one',
32
+ mediaLinks: ['https://assets.bwbx.io/example-one.jpg'],
33
+ });
34
+ expect(items[1]).toEqual({
35
+ title: 'Headline Two',
36
+ summary: 'Summary Two',
37
+ link: 'https://www.bloomberg.com/news/articles/2026-03-19/example-two',
38
+ mediaLinks: ['https://assets.bwbx.io/example-two.png'],
39
+ });
40
+ });
41
+
42
+ it('renders Bloomberg story rich-text body into readable text', () => {
43
+ const body = {
44
+ type: 'document',
45
+ content: [
46
+ { type: 'inline-newsletter', data: { position: 'top' }, content: [] },
47
+ {
48
+ type: 'paragraph',
49
+ data: {},
50
+ content: [
51
+ { type: 'text', value: 'Lead paragraph with ' },
52
+ { type: 'entity', content: [{ type: 'text', value: 'linked text' }] },
53
+ { type: 'text', value: '.' },
54
+ ],
55
+ },
56
+ {
57
+ type: 'heading',
58
+ data: { level: 2 },
59
+ content: [{ type: 'text', value: 'Key Points' }],
60
+ },
61
+ {
62
+ type: 'list',
63
+ data: { style: 'unordered' },
64
+ content: [
65
+ {
66
+ type: 'list-item',
67
+ content: [
68
+ { type: 'paragraph', content: [{ type: 'text', value: 'Point one' }] },
69
+ ],
70
+ },
71
+ {
72
+ type: 'list-item',
73
+ content: [
74
+ { type: 'paragraph', content: [{ type: 'text', value: 'Point two' }] },
75
+ ],
76
+ },
77
+ ],
78
+ },
79
+ {
80
+ type: 'blockquote',
81
+ content: [{ type: 'text', value: 'Quoted line' }],
82
+ },
83
+ {
84
+ type: 'media',
85
+ data: {
86
+ attachment: {
87
+ caption: '<p>Chart caption</p>',
88
+ },
89
+ },
90
+ },
91
+ { type: 'ad', data: { num: 1 }, content: [] },
92
+ ],
93
+ };
94
+
95
+ expect(renderStoryBody(body)).toBe([
96
+ 'Lead paragraph with linked text.',
97
+ '## Key Points',
98
+ '- Point one\n- Point two',
99
+ '> Quoted line',
100
+ 'Chart caption',
101
+ ].join('\n\n'));
102
+ });
103
+
104
+ it('collects deduped story media links from lede, attachments, and body media', () => {
105
+ const story = {
106
+ ledeImageUrl: 'https://assets.bwbx.io/lede.webp',
107
+ lede: { url: 'https://assets.bwbx.io/lede.webp' },
108
+ socialImageUrl: 'https://assets.bwbx.io/social.png',
109
+ imageAttachments: {
110
+ one: { url: 'https://assets.bwbx.io/figure.jpg' },
111
+ },
112
+ body: {
113
+ content: [
114
+ {
115
+ type: 'media',
116
+ data: {
117
+ chart: {
118
+ src: 'https://resource.bloomberg.com/images/chart.png',
119
+ fallback: 'https://assets.bwbx.io/chart-fallback.png',
120
+ },
121
+ },
122
+ },
123
+ ],
124
+ },
125
+ };
126
+
127
+ expect(extractStoryMediaLinks(story)).toEqual([
128
+ 'https://assets.bwbx.io/lede.webp',
129
+ 'https://assets.bwbx.io/social.png',
130
+ 'https://assets.bwbx.io/figure.jpg',
131
+ 'https://resource.bloomberg.com/images/chart.png',
132
+ 'https://assets.bwbx.io/chart-fallback.png',
133
+ ]);
134
+ });
135
+ });