@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,92 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ cli({
5
+ site: 'twitter',
6
+ name: 'block',
7
+ description: 'Block a Twitter user',
8
+ domain: 'x.com',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [
12
+ { name: 'username', type: 'string', positional: true, required: true, help: 'Twitter screen name (without @)' },
13
+ ],
14
+ columns: ['status', 'message'],
15
+ func: async (page: IPage | null, kwargs: any) => {
16
+ if (!page) throw new Error('Requires browser');
17
+ const username = kwargs.username.replace(/^@/, '');
18
+
19
+ await page.goto(`https://x.com/${username}`);
20
+ await page.wait(5);
21
+
22
+ const result = await page.evaluate(`(async () => {
23
+ try {
24
+ let attempts = 0;
25
+
26
+ // Check if already blocked (profile shows "Blocked" / unblock button)
27
+ while (attempts < 20) {
28
+ const blockedIndicator = document.querySelector('[data-testid$="-unblock"]');
29
+ if (blockedIndicator) {
30
+ return { ok: true, message: 'Already blocking @${username}.' };
31
+ }
32
+
33
+ const moreBtn = document.querySelector('[data-testid="userActions"]');
34
+ if (moreBtn) break;
35
+
36
+ await new Promise(r => setTimeout(r, 500));
37
+ attempts++;
38
+ }
39
+
40
+ const moreBtn = document.querySelector('[data-testid="userActions"]');
41
+ if (!moreBtn) {
42
+ return { ok: false, message: 'Could not find user actions menu. Are you logged in?' };
43
+ }
44
+
45
+ // Open the more actions menu
46
+ moreBtn.click();
47
+ await new Promise(r => setTimeout(r, 1000));
48
+
49
+ // Find the Block menu item
50
+ const menuItems = document.querySelectorAll('[role="menuitem"]');
51
+ let blockItem = null;
52
+ for (const item of menuItems) {
53
+ if (item.textContent && item.textContent.includes('Block')) {
54
+ blockItem = item;
55
+ break;
56
+ }
57
+ }
58
+
59
+ if (!blockItem) {
60
+ return { ok: false, message: 'Could not find Block option in menu.' };
61
+ }
62
+
63
+ blockItem.click();
64
+ await new Promise(r => setTimeout(r, 1000));
65
+
66
+ // Confirm the block in the dialog
67
+ const confirmBtn = document.querySelector('[data-testid="confirmationSheetConfirm"]');
68
+ if (confirmBtn) {
69
+ confirmBtn.click();
70
+ await new Promise(r => setTimeout(r, 1500));
71
+ }
72
+
73
+ // Verify
74
+ const verify = document.querySelector('[data-testid$="-unblock"]');
75
+ if (verify) {
76
+ return { ok: true, message: 'Successfully blocked @${username}.' };
77
+ } else {
78
+ return { ok: false, message: 'Block action initiated but UI did not update.' };
79
+ }
80
+ } catch (e) {
81
+ return { ok: false, message: e.toString() };
82
+ }
83
+ })()`);
84
+
85
+ if (result.ok) await page.wait(2);
86
+
87
+ return [{
88
+ status: result.ok ? 'success' : 'failed',
89
+ message: result.message
90
+ }];
91
+ }
92
+ });
@@ -9,7 +9,7 @@ cli({
9
9
  strategy: Strategy.UI, // Utilizes internal DOM flows for interaction
10
10
  browser: true,
11
11
  args: [
12
- { name: 'url', type: 'string', required: true, help: 'The URL of the tweet to delete' },
12
+ { name: 'url', type: 'string', required: true, positional: true, help: 'The URL of the tweet to delete' },
13
13
  ],
14
14
  columns: ['status', 'message'],
15
15
  func: async (page: IPage | null, kwargs: any) => {
@@ -0,0 +1,70 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ cli({
5
+ site: 'twitter',
6
+ name: 'hide-reply',
7
+ description: 'Hide a reply on your tweet (useful for hiding bot/spam replies)',
8
+ domain: 'x.com',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [
12
+ { name: 'url', type: 'string', required: true, positional: true, help: 'The URL of the reply tweet to hide' },
13
+ ],
14
+ columns: ['status', 'message'],
15
+ func: async (page: IPage | null, kwargs: any) => {
16
+ if (!page) throw new Error('Requires browser');
17
+
18
+ await page.goto(kwargs.url);
19
+ await page.wait(5);
20
+
21
+ const result = await page.evaluate(`(async () => {
22
+ try {
23
+ let attempts = 0;
24
+ let moreMenu = null;
25
+
26
+ while (attempts < 20) {
27
+ moreMenu = document.querySelector('[aria-label="More"]');
28
+ if (moreMenu) break;
29
+ await new Promise(r => setTimeout(r, 500));
30
+ attempts++;
31
+ }
32
+
33
+ if (!moreMenu) {
34
+ return { ok: false, message: 'Could not find the "More" menu on this tweet. Are you logged in?' };
35
+ }
36
+
37
+ moreMenu.click();
38
+ await new Promise(r => setTimeout(r, 1000));
39
+
40
+ // Look for the "Hide reply" menu item
41
+ const items = document.querySelectorAll('[role="menuitem"]');
42
+ let hideItem = null;
43
+ for (const item of items) {
44
+ if (item.textContent && item.textContent.includes('Hide reply')) {
45
+ hideItem = item;
46
+ break;
47
+ }
48
+ }
49
+
50
+ if (!hideItem) {
51
+ return { ok: false, message: 'Could not find "Hide reply" option. This may not be a reply on your tweet.' };
52
+ }
53
+
54
+ hideItem.click();
55
+ await new Promise(r => setTimeout(r, 1500));
56
+
57
+ return { ok: true, message: 'Reply successfully hidden.' };
58
+ } catch (e) {
59
+ return { ok: false, message: e.toString() };
60
+ }
61
+ })()`);
62
+
63
+ if (result.ok) await page.wait(2);
64
+
65
+ return [{
66
+ status: result.ok ? 'success' : 'failed',
67
+ message: result.message
68
+ }];
69
+ }
70
+ });
@@ -9,7 +9,7 @@ cli({
9
9
  strategy: Strategy.UI, // Utilizes internal DOM flows for interaction
10
10
  browser: true,
11
11
  args: [
12
- { name: 'url', type: 'string', required: true, help: 'The URL of the tweet to like' },
12
+ { name: 'url', type: 'string', required: true, positional: true, help: 'The URL of the tweet to like' },
13
13
  ],
14
14
  columns: ['status', 'message'],
15
15
  func: async (page: IPage | null, kwargs: any) => {
@@ -9,7 +9,7 @@ cli({
9
9
  strategy: Strategy.UI,
10
10
  browser: true,
11
11
  args: [
12
- { name: 'text', type: 'string', required: true, help: 'The text content of the tweet' },
12
+ { name: 'text', type: 'string', required: true, positional: true, help: 'The text content of the tweet' },
13
13
  ],
14
14
  columns: ['status', 'message', 'text'],
15
15
  func: async (page: IPage | null, kwargs: any) => {
@@ -10,7 +10,7 @@ cli({
10
10
  browser: true,
11
11
  timeoutSeconds: 600, // 10 min — batch operation
12
12
  args: [
13
- { name: 'text', type: 'string', required: true, help: 'Message text to send (e.g. "我的微信 wxkabi")' },
13
+ { name: 'text', type: 'string', required: true, positional: true, help: 'Message text to send (e.g. "我的微信 wxkabi")' },
14
14
  { name: 'max', type: 'int', required: false, default: 20, help: 'Maximum number of conversations to reply to (default: 20)' },
15
15
  { name: 'skip-replied', type: 'boolean', required: false, default: true, help: 'Skip conversations where you already sent the same text (default: true)' },
16
16
  ],
@@ -9,8 +9,8 @@ cli({
9
9
  strategy: Strategy.UI, // Uses the UI directly to input and click post
10
10
  browser: true,
11
11
  args: [
12
- { name: 'url', type: 'string', required: true, help: 'The URL of the tweet to reply to' },
13
- { name: 'text', type: 'string', required: true, help: 'The text content of your reply' },
12
+ { name: 'url', type: 'string', required: true, positional: true, help: 'The URL of the tweet to reply to' },
13
+ { name: 'text', type: 'string', required: true, positional: true, help: 'The text content of your reply' },
14
14
  ],
15
15
  columns: ['status', 'message', 'text'],
16
16
  func: async (page: IPage | null, kwargs: any) => {
@@ -8,7 +8,7 @@ cli({
8
8
  strategy: Strategy.INTERCEPT, // Use intercept strategy
9
9
  browser: true,
10
10
  args: [
11
- { name: 'query', type: 'string', required: true },
11
+ { name: 'query', type: 'string', required: true, positional: true },
12
12
  { name: 'limit', type: 'int', default: 15 },
13
13
  ],
14
14
  columns: ['id', 'author', 'text', 'likes', 'views', 'url'],
@@ -122,12 +122,12 @@ cli({
122
122
  strategy: Strategy.COOKIE,
123
123
  browser: true,
124
124
  args: [
125
- { name: 'tweet_id', type: 'string', required: true },
125
+ { name: 'tweet-id', type: 'string', required: true },
126
126
  { name: 'limit', type: 'int', default: 50 },
127
127
  ],
128
128
  columns: ['id', 'author', 'text', 'likes', 'retweets', 'url'],
129
129
  func: async (page, kwargs) => {
130
- let tweetId = kwargs.tweet_id;
130
+ let tweetId = kwargs['tweet-id'];
131
131
  const urlMatch = tweetId.match(/\/status\/(\d+)/);
132
132
  if (urlMatch) tweetId = urlMatch[1];
133
133
 
@@ -0,0 +1,113 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+
3
+ // ── Twitter GraphQL constants ──────────────────────────────────────────
4
+
5
+ const BEARER_TOKEN = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
6
+
7
+ // ── Types ──────────────────────────────────────────────────────────────
8
+
9
+ interface TrendItem {
10
+ rank: number;
11
+ topic: string;
12
+ tweets: string;
13
+ category: string;
14
+ }
15
+
16
+ // ── CLI definition ────────────────────────────────────────────────────
17
+
18
+ cli({
19
+ site: 'twitter',
20
+ name: 'trending',
21
+ description: 'Twitter/X trending topics',
22
+ domain: 'x.com',
23
+ strategy: Strategy.COOKIE,
24
+ browser: true,
25
+ args: [
26
+ { name: 'limit', type: 'int', default: 20, help: 'Number of trends to show' },
27
+ ],
28
+ columns: ['rank', 'topic', 'tweets', 'category'],
29
+ func: async (page, kwargs) => {
30
+ const limit = kwargs.limit || 20;
31
+
32
+ // Navigate to trending page
33
+ await page.goto('https://x.com/explore/tabs/trending');
34
+ await page.wait(3);
35
+
36
+ // Extract CSRF token to verify login
37
+ const ct0 = await page.evaluate(`(() => {
38
+ return document.cookie.split(';').map(c=>c.trim()).find(c=>c.startsWith('ct0='))?.split('=')[1] || null;
39
+ })()`);
40
+ if (!ct0) throw new Error('Not logged into x.com (no ct0 cookie)');
41
+
42
+ // Try legacy guide.json API first (faster than DOM scraping)
43
+ let trends: TrendItem[] = [];
44
+
45
+ const apiData = await page.evaluate(`(async () => {
46
+ const ct0 = document.cookie.split(';').map(c=>c.trim()).find(c=>c.startsWith('ct0='))?.split('=')[1] || '';
47
+ const r = await fetch('/i/api/2/guide.json?include_page_configuration=true', {
48
+ credentials: 'include',
49
+ headers: {
50
+ 'x-twitter-active-user': 'yes',
51
+ 'x-csrf-token': ct0,
52
+ 'authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA'
53
+ }
54
+ });
55
+ return r.ok ? await r.json() : null;
56
+ })()`);
57
+
58
+ if (apiData) {
59
+ const instructions = apiData?.timeline?.instructions || [];
60
+ const entries = instructions.flatMap((inst: any) => inst?.addEntries?.entries || inst?.entries || []);
61
+ const apiTrends = entries
62
+ .filter((e: any) => e.content?.timelineModule)
63
+ .flatMap((e: any) => e.content.timelineModule.items || [])
64
+ .map((t: any) => t?.item?.content?.trend)
65
+ .filter(Boolean);
66
+
67
+ trends = apiTrends.map((t: any, i: number) => ({
68
+ rank: i + 1,
69
+ topic: t.name,
70
+ tweets: t.tweetCount ? String(t.tweetCount) : 'N/A',
71
+ category: t.trendMetadata?.domainContext || '',
72
+ }));
73
+ }
74
+
75
+ // Fallback: scrape from the loaded DOM
76
+ if (trends.length === 0) {
77
+ await page.wait(2);
78
+ const domTrends = await page.evaluate(`(() => {
79
+ const items = [];
80
+ const cells = document.querySelectorAll('[data-testid="trend"]');
81
+ cells.forEach((cell) => {
82
+ const text = cell.textContent || '';
83
+ if (text.includes('Promoted')) return;
84
+ const container = cell.querySelector(':scope > div');
85
+ if (!container) return;
86
+ const divs = container.children;
87
+ // Structure: divs[0] = rank + category, divs[1] = topic name, divs[2] = extra
88
+ const topicEl = divs.length >= 2 ? divs[1] : null;
89
+ const topic = topicEl ? topicEl.textContent.trim() : '';
90
+ const catEl = divs.length >= 1 ? divs[0] : null;
91
+ const catText = catEl ? catEl.textContent.trim() : '';
92
+ const category = catText.replace(/^\\d+\\s*/, '').replace(/^\\xB7\\s*/, '').trim();
93
+ const extraEl = divs.length >= 3 ? divs[2] : null;
94
+ const extra = extraEl ? extraEl.textContent.trim() : '';
95
+ if (topic) {
96
+ items.push({ rank: items.length + 1, topic, tweets: extra || 'N/A', category });
97
+ }
98
+ });
99
+ return items;
100
+ })()`);
101
+
102
+ if (Array.isArray(domTrends) && domTrends.length > 0) {
103
+ trends = domTrends;
104
+ }
105
+ }
106
+
107
+ if (trends.length === 0) {
108
+ throw new Error('No trending data found. API may have changed or login may be required.');
109
+ }
110
+
111
+ return trends.slice(0, limit);
112
+ },
113
+ });
@@ -0,0 +1,75 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ cli({
5
+ site: 'twitter',
6
+ name: 'unblock',
7
+ description: 'Unblock a Twitter user',
8
+ domain: 'x.com',
9
+ strategy: Strategy.UI,
10
+ browser: true,
11
+ args: [
12
+ { name: 'username', type: 'string', positional: true, required: true, help: 'Twitter screen name (without @)' },
13
+ ],
14
+ columns: ['status', 'message'],
15
+ func: async (page: IPage | null, kwargs: any) => {
16
+ if (!page) throw new Error('Requires browser');
17
+ const username = kwargs.username.replace(/^@/, '');
18
+
19
+ await page.goto(`https://x.com/${username}`);
20
+ await page.wait(5);
21
+
22
+ const result = await page.evaluate(`(async () => {
23
+ try {
24
+ let attempts = 0;
25
+ let unblockBtn = null;
26
+
27
+ while (attempts < 20) {
28
+ // Check if not blocked (follow button visible means not blocked)
29
+ const followBtn = document.querySelector('[data-testid$="-follow"]');
30
+ if (followBtn) {
31
+ return { ok: true, message: 'Not blocking @${username} (already unblocked).' };
32
+ }
33
+
34
+ unblockBtn = document.querySelector('[data-testid$="-unblock"]');
35
+ if (unblockBtn) break;
36
+
37
+ await new Promise(r => setTimeout(r, 500));
38
+ attempts++;
39
+ }
40
+
41
+ if (!unblockBtn) {
42
+ return { ok: false, message: 'Could not find Unblock button. Are you logged in?' };
43
+ }
44
+
45
+ // Click the unblock button — this opens a confirmation dialog
46
+ unblockBtn.click();
47
+ await new Promise(r => setTimeout(r, 1000));
48
+
49
+ // Confirm the unblock in the dialog
50
+ const confirmBtn = document.querySelector('[data-testid="confirmationSheetConfirm"]');
51
+ if (confirmBtn) {
52
+ confirmBtn.click();
53
+ await new Promise(r => setTimeout(r, 1000));
54
+ }
55
+
56
+ // Verify
57
+ const verify = document.querySelector('[data-testid$="-follow"]');
58
+ if (verify) {
59
+ return { ok: true, message: 'Successfully unblocked @${username}.' };
60
+ } else {
61
+ return { ok: false, message: 'Unblock action initiated but UI did not update.' };
62
+ }
63
+ } catch (e) {
64
+ return { ok: false, message: e.toString() };
65
+ }
66
+ })()`);
67
+
68
+ if (result.ok) await page.wait(2);
69
+
70
+ return [{
71
+ status: result.ok ? 'success' : 'failed',
72
+ message: result.message
73
+ }];
74
+ }
75
+ });
@@ -7,6 +7,7 @@ browser: false
7
7
 
8
8
  args:
9
9
  id:
10
+ positional: true
10
11
  type: str
11
12
  required: true
12
13
  description: Topic ID
@@ -17,7 +17,6 @@ cli({
17
17
  func: async (page, kwargs) => {
18
18
  const count = Math.min(kwargs.limit || 30, 50);
19
19
  await page.goto('https://weibo.com');
20
- await page.wait(2);
21
20
  const data = await page.evaluate(`
22
21
  (async () => {
23
22
  const resp = await fetch('/ajax/statuses/hot_band', {credentials: 'include'});
@@ -9,7 +9,7 @@ cli({
9
9
  domain: 'weread.qq.com',
10
10
  strategy: Strategy.COOKIE,
11
11
  args: [
12
- { name: 'bookId', positional: true, required: true, help: 'Book ID (numeric, from search or shelf results)' },
12
+ { name: 'book-id', positional: true, required: true, help: 'Book ID (numeric, from search or shelf results)' },
13
13
  ],
14
14
  columns: ['title', 'author', 'publisher', 'intro', 'category', 'rating'],
15
15
  func: async (page: IPage, args) => {
@@ -9,7 +9,7 @@ cli({
9
9
  domain: 'weread.qq.com',
10
10
  strategy: Strategy.COOKIE,
11
11
  args: [
12
- { name: 'bookId', positional: true, required: true, help: 'Book ID (from shelf or search results)' },
12
+ { name: 'book-id', positional: true, required: true, help: 'Book ID (from shelf or search results)' },
13
13
  { name: 'limit', type: 'int', default: 20, help: 'Max results' },
14
14
  ],
15
15
  columns: ['chapter', 'text', 'createTime'],
@@ -9,7 +9,7 @@ cli({
9
9
  domain: 'weread.qq.com',
10
10
  strategy: Strategy.COOKIE,
11
11
  args: [
12
- { name: 'bookId', positional: true, required: true, help: 'Book ID (from shelf or search results)' },
12
+ { name: 'book-id', positional: true, required: true, help: 'Book ID (from shelf or search results)' },
13
13
  { name: 'limit', type: 'int', default: 20, help: 'Max results' },
14
14
  ],
15
15
  columns: ['chapter', 'text', 'review', 'createTime'],
@@ -9,7 +9,7 @@ cli({
9
9
  strategy: Strategy.PUBLIC,
10
10
  browser: false,
11
11
  args: [
12
- { name: 'keyword', positional: true, required: true, help: 'Search keyword' },
12
+ { name: 'query', positional: true, required: true, help: 'Search keyword' },
13
13
  { name: 'limit', type: 'int', default: 10, help: 'Max results' },
14
14
  ],
15
15
  columns: ['rank', 'title', 'author', 'bookId'],
@@ -0,0 +1,32 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { CliError } from '../../errors.js';
3
+ import { wikiFetch } from './utils.js';
4
+
5
+ interface WikiSearchResult { title: string; snippet: string; }
6
+
7
+ cli({
8
+ site: 'wikipedia',
9
+ name: 'search',
10
+ description: 'Search Wikipedia articles',
11
+ strategy: Strategy.PUBLIC,
12
+ browser: false,
13
+ args: [
14
+ { name: 'query', positional: true, required: true, help: 'Search keyword' },
15
+ { name: 'limit', type: 'int', default: 10, help: 'Max results' },
16
+ { name: 'lang', default: 'en', help: 'Language code (e.g. en, zh, ja)' },
17
+ ],
18
+ columns: ['title', 'snippet', 'url'],
19
+ func: async (_page, args) => {
20
+ const limit = Math.max(1, Math.min(Number(args.limit), 50));
21
+ const lang = args.lang || 'en';
22
+ const q = encodeURIComponent(args.keyword);
23
+ const data = await wikiFetch(lang, `/w/api.php?action=query&list=search&srsearch=${q}&srlimit=${limit}&format=json&utf8=1`) as { query?: { search?: WikiSearchResult[] } };
24
+ const results = data?.query?.search;
25
+ if (!results?.length) throw new CliError('NOT_FOUND', 'No articles found', 'Try a different keyword');
26
+ return results.map((r) => ({
27
+ title: r.title,
28
+ snippet: r.snippet.replace(/<[^>]+>/g, '').slice(0, 120),
29
+ url: `https://${lang}.wikipedia.org/wiki/${encodeURIComponent(r.title.replace(/ /g, '_'))}`,
30
+ }));
31
+ },
32
+ });
@@ -0,0 +1,28 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { CliError } from '../../errors.js';
3
+ import { wikiFetch } from './utils.js';
4
+
5
+ cli({
6
+ site: 'wikipedia',
7
+ name: 'summary',
8
+ description: 'Get Wikipedia article summary',
9
+ strategy: Strategy.PUBLIC,
10
+ browser: false,
11
+ args: [
12
+ { name: 'title', positional: true, required: true, help: 'Article title (e.g. "Transformer (machine learning model)")' },
13
+ { name: 'lang', default: 'en', help: 'Language code (e.g. en, zh, ja)' },
14
+ ],
15
+ columns: ['title', 'description', 'extract', 'url'],
16
+ func: async (_page, args) => {
17
+ const lang = args.lang || 'en';
18
+ const title = encodeURIComponent(args.title.replace(/ /g, '_'));
19
+ const data = await wikiFetch(lang, `/api/rest_v1/page/summary/${title}`) as { title?: string; description?: string; extract?: string; content_urls?: { desktop?: { page?: string } } };
20
+ if (!data?.title) throw new CliError('NOT_FOUND', `Article "${args.title}" not found`, 'Try searching first: opencli wikipedia search <keyword>');
21
+ return [{
22
+ title: data.title,
23
+ description: data.description ?? '-',
24
+ extract: (data.extract ?? '').slice(0, 300),
25
+ url: data.content_urls?.desktop?.page ?? `https://${lang}.wikipedia.org/wiki/${title}`,
26
+ }];
27
+ },
28
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Wikipedia adapter utilities.
3
+ *
4
+ * Uses the public MediaWiki REST API and Action API — no key required.
5
+ * REST API: https://en.wikipedia.org/api/rest_v1/
6
+ * Action API: https://en.wikipedia.org/w/api.php
7
+ */
8
+
9
+ import { CliError } from '../../errors.js';
10
+
11
+ export async function wikiFetch(lang: string, path: string): Promise<unknown> {
12
+ const url = `https://${lang}.wikipedia.org${path}`;
13
+ const resp = await fetch(url, {
14
+ headers: { 'User-Agent': 'opencli/1.0 (https://github.com/jackwener/opencli)' },
15
+ });
16
+ if (!resp.ok) {
17
+ throw new CliError('FETCH_ERROR', `Wikipedia API HTTP ${resp.status}`, `Check your title or search term`);
18
+ }
19
+ return resp.json();
20
+ }