@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
@@ -52,68 +52,254 @@ function jsonResponse(res, status, data) {
52
52
  function sleep(ms) {
53
53
  return new Promise(resolve => setTimeout(resolve, ms));
54
54
  }
55
+ // ─── DOM helpers ─────────────────────────────────────────────────────
56
+ /**
57
+ * Click the 'New Conversation' button to reset context.
58
+ */
59
+ async function startNewConversation(page) {
60
+ await page.evaluate(`
61
+ (() => {
62
+ const btn = document.querySelector('[data-tooltip-id="new-conversation-tooltip"]');
63
+ if (btn) btn.click();
64
+ })()
65
+ `);
66
+ await sleep(1000); // Give UI time to clear
67
+ }
68
+ /**
69
+ * Switch the active model in Antigravity UI.
70
+ */
71
+ async function switchModel(page, anthropicModelId) {
72
+ // Map standard model IDs to Antigravity UI names based on actual UI
73
+ let targetName = 'claude sonnet 4.6'; // Default fallback
74
+ const id = anthropicModelId.toLowerCase();
75
+ if (id.includes('sonnet')) {
76
+ targetName = 'claude sonnet 4.6';
77
+ }
78
+ else if (id.includes('opus')) {
79
+ targetName = 'claude opus 4.6';
80
+ }
81
+ else if (id.includes('gemini') && id.includes('pro')) {
82
+ targetName = 'gemini 3.1 pro (high)';
83
+ }
84
+ else if (id.includes('gemini') && id.includes('flash')) {
85
+ targetName = 'gemini 3 flash';
86
+ }
87
+ else if (id.includes('gpt')) {
88
+ targetName = 'gpt-oss 120b';
89
+ }
90
+ try {
91
+ await page.evaluate(`
92
+ async () => {
93
+ const targetModelName = ${JSON.stringify(targetName)};
94
+ const trigger = document.querySelector('div[aria-haspopup="dialog"] > div[tabindex="0"]');
95
+ if (!trigger) return; // Silent fail if UI changed
96
+
97
+ // Open dropdown only if not already selected
98
+ if (trigger.innerText.toLowerCase().includes(targetModelName)) return;
99
+
100
+ trigger.click();
101
+ await new Promise(r => setTimeout(r, 200));
102
+
103
+ const spans = Array.from(document.querySelectorAll('[role="dialog"] span'));
104
+ const target = spans.find(s => s.innerText.toLowerCase().includes(targetModelName));
105
+ if (target) {
106
+ const optionNode = target.closest('.cursor-pointer') || target;
107
+ optionNode.click();
108
+ } else {
109
+ // Close if not found
110
+ trigger.click();
111
+ }
112
+ }
113
+ `);
114
+ await sleep(500); // Wait for switch
115
+ }
116
+ catch (err) {
117
+ console.error(`[serve] Warning: Could not switch to model ${targetName}:`, err);
118
+ }
119
+ }
120
+ /**
121
+ * Check if the Antigravity UI is currently generating a response
122
+ * by looking for Stop/Cancel buttons or loading indicators.
123
+ */
124
+ async function isGenerating(page) {
125
+ const result = await page.evaluate(`
126
+ (() => {
127
+ // Look for a cancel/stop button in the UI
128
+ const cancelBtn = document.querySelector('button[aria-label*="cancel" i], button[aria-label*="stop" i], button[title*="cancel" i], button[title*="stop" i]');
129
+ return !!cancelBtn;
130
+ })()
131
+ `);
132
+ return Boolean(result);
133
+ }
134
+ /**
135
+ * Walk from the scroll container and find the deepest element that
136
+ * has multiple non-empty children (our message container).
137
+ */
138
+ function findMessageContainer(root, depth = 0) {
139
+ if (!root || depth > 12)
140
+ return null;
141
+ const nonEmpty = Array.from(root.children).filter(c => c.innerText?.trim().length > 5);
142
+ if (nonEmpty.length >= 2)
143
+ return root;
144
+ if (nonEmpty.length === 1)
145
+ return findMessageContainer(nonEmpty[0], depth + 1);
146
+ return root;
147
+ }
55
148
  // ─── Antigravity CDP Operations ──────────────────────────────────────
149
+ /**
150
+ * Get the full chat text for change-detection polling.
151
+ */
56
152
  async function getConversationText(page) {
57
153
  const text = await page.evaluate(`
58
154
  (() => {
59
155
  const container = document.getElementById('conversation');
60
- return container ? container.innerText : '';
156
+ if (!container) return '';
157
+ // Read only the first child div (actual chat content),
158
+ // skipping UI chrome like file change panels, model selectors, etc.
159
+ const chatContent = container.children[0];
160
+ return chatContent ? chatContent.innerText : container.innerText;
61
161
  })()
62
162
  `);
63
163
  return String(text ?? '');
64
164
  }
65
- async function sendMessage(page, message) {
66
- await page.evaluate(`
67
- (async () => {
165
+ /**
166
+ * Get the text of the last assistant reply by navigating to the message container
167
+ * and extracting the last non-empty message block.
168
+ */
169
+ async function getLastAssistantReply(page, userText) {
170
+ const text = await page.evaluate(`
171
+ (() => {
172
+ const conv = document.getElementById('conversation')?.children[0];
173
+ const scroll = conv?.querySelector('.overflow-y-auto');
174
+
175
+ // Walk down until we find a container with multiple message siblings
176
+ function findMsgContainer(el, depth) {
177
+ if (!el || depth > 12) return null;
178
+ const nonEmpty = Array.from(el.children).filter(c => c.innerText && c.innerText.trim().length > 5);
179
+ if (nonEmpty.length >= 2) return el;
180
+ if (nonEmpty.length === 1) return findMsgContainer(nonEmpty[0], depth + 1);
181
+ return null;
182
+ }
183
+
184
+ const container = findMsgContainer(scroll || conv, 0);
185
+ if (!container) return '';
186
+
187
+ // Get all non-empty children (skip trailing empty UI divs)
188
+ const msgs = Array.from(container.children).filter(
189
+ c => c.innerText && c.innerText.trim().length > 5
190
+ );
191
+
192
+ if (msgs.length === 0) return '';
193
+
194
+ // The last element is the last assistant reply
195
+ const last = msgs[msgs.length - 1];
196
+ return last.innerText || '';
197
+ })()
198
+ `);
199
+ let reply = String(text ?? '').trim();
200
+ // Strip echoed user message from the top (Antigravity sometimes includes it)
201
+ if (userText && reply.startsWith(userText)) {
202
+ reply = reply.slice(userText.length).trim();
203
+ }
204
+ // Strip thinking block: "Thought for Xs\n..." at the start
205
+ reply = reply.replace(/^Thought for[^\n]*\n+/i, '').trim();
206
+ // Strip "Copy" button text at the end
207
+ reply = reply.replace(/\s*\bCopy\b\s*$/m, '').trim();
208
+ // De-duplicate trailing repeated content (e.g., "OK\n\nOK" → "OK")
209
+ const half = Math.floor(reply.length / 2);
210
+ const firstHalf = reply.slice(0, half).trim();
211
+ const secondHalf = reply.slice(half).trim();
212
+ if (firstHalf && firstHalf === secondHalf) {
213
+ reply = firstHalf;
214
+ }
215
+ return reply;
216
+ }
217
+ async function sendMessage(page, message, bridge) {
218
+ if (!bridge) {
219
+ // Fallback: use JS-based approach
220
+ await page.evaluate(`
221
+ (() => {
222
+ const container = document.getElementById('antigravity.agentSidePanelInputBox');
223
+ const editor = container?.querySelector('[data-lexical-editor="true"]');
224
+ if (!editor) throw new Error('Could not find input box');
225
+ editor.focus();
226
+ document.execCommand('insertText', false, ${JSON.stringify(message)});
227
+ })()
228
+ `);
229
+ await sleep(500);
230
+ await page.pressKey('Enter');
231
+ return;
232
+ }
233
+ // Get the bounding box of the Lexical editor for a physical mouse click
234
+ const rect = await page.evaluate(`
235
+ (() => {
68
236
  const container = document.getElementById('antigravity.agentSidePanelInputBox');
69
237
  if (!container) throw new Error('Could not find antigravity.agentSidePanelInputBox');
70
238
  const editor = container.querySelector('[data-lexical-editor="true"]');
71
239
  if (!editor) throw new Error('Could not find Antigravity input box');
72
-
73
- editor.focus();
74
- document.execCommand('insertText', false, ${JSON.stringify(message)});
240
+ const r = editor.getBoundingClientRect();
241
+ return JSON.stringify({ x: r.left + r.width / 2, y: r.top + r.height / 2 });
75
242
  })()
76
243
  `);
77
- await sleep(500);
78
- await page.pressKey('Enter');
244
+ const { x, y } = JSON.parse(String(rect));
245
+ // Physical mouse click to give the element real browser focus
246
+ await bridge.send('Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button: 'left', clickCount: 1 });
247
+ await sleep(50);
248
+ await bridge.send('Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button: 'left', clickCount: 1 });
249
+ await sleep(200);
250
+ // Inject text at the CDP level (no deprecated execCommand)
251
+ await bridge.send('Input.insertText', { text: message });
252
+ await sleep(300);
253
+ // Send Enter via native CDP key event
254
+ await bridge.send('Input.dispatchKeyEvent', { type: 'keyDown', key: 'Enter', code: 'Enter', windowsVirtualKeyCode: 13, nativeVirtualKeyCode: 13 });
255
+ await sleep(50);
256
+ await bridge.send('Input.dispatchKeyEvent', { type: 'keyUp', key: 'Enter', code: 'Enter', windowsVirtualKeyCode: 13, nativeVirtualKeyCode: 13 });
79
257
  }
80
258
  async function waitForReply(page, beforeText, opts = {}) {
81
259
  const timeout = opts.timeout ?? 120_000; // 2 minutes max
82
260
  const pollInterval = opts.pollInterval ?? 500; // 500ms polling
83
- const stableThreshold = opts.stableThreshold ?? 6; // 6 × 500ms = 3s stable
84
261
  const deadline = Date.now() + timeout;
262
+ // Wait a bit to ensure the UI transitions to "generating" state after we hit Enter
263
+ await sleep(1000);
264
+ let hasStartedGenerating = false;
85
265
  let lastText = beforeText;
86
266
  let stableCount = 0;
87
- // Wait a bit for the model to start generating
88
- await sleep(1000);
267
+ const stableThreshold = 4; // 4 * 500ms = 2s of stability fallback
89
268
  while (Date.now() < deadline) {
90
- const current = await getConversationText(page);
91
- if (current.length > beforeText.length) {
92
- // New content appeared
93
- if (current === lastText) {
94
- stableCount++;
95
- if (stableCount >= stableThreshold) {
96
- // Text has been stable — reply is complete
97
- return current.slice(beforeText.length).trim();
98
- }
269
+ const generating = await isGenerating(page);
270
+ const currentText = await getConversationText(page);
271
+ const textChanged = currentText !== beforeText && currentText.length > 0;
272
+ if (generating) {
273
+ hasStartedGenerating = true;
274
+ stableCount = 0; // Reset stability while generating
275
+ }
276
+ else {
277
+ if (hasStartedGenerating) {
278
+ // It actively generated and now it stopped -> DONE
279
+ // Provide a small buffer to let React render the final message fully
280
+ await sleep(500);
281
+ return;
99
282
  }
100
- else {
101
- // Still generating
102
- stableCount = 0;
103
- lastText = current;
283
+ // Fallback: If it never showed "Generating/Cancel", but text changed and is stable
284
+ if (textChanged) {
285
+ if (currentText === lastText) {
286
+ stableCount++;
287
+ if (stableCount >= stableThreshold) {
288
+ return; // Text has been stable for 2 seconds -> DONE
289
+ }
290
+ }
291
+ else {
292
+ stableCount = 0;
293
+ lastText = currentText;
294
+ }
104
295
  }
105
296
  }
106
297
  await sleep(pollInterval);
107
298
  }
108
- // Timeout — return whatever we have
109
- const finalText = await getConversationText(page);
110
- if (finalText.length > beforeText.length) {
111
- return finalText.slice(beforeText.length).trim();
112
- }
113
299
  throw new Error('Timeout waiting for Antigravity reply');
114
300
  }
115
301
  // ─── Request Handlers ────────────────────────────────────────────────
116
- async function handleMessages(body, page) {
302
+ async function handleMessages(body, page, bridge) {
117
303
  // Extract the last user message
118
304
  const userMessages = body.messages.filter(m => m.role === 'user');
119
305
  if (userMessages.length === 0) {
@@ -124,14 +310,25 @@ async function handleMessages(body, page) {
124
310
  if (!userText.trim()) {
125
311
  throw new Error('Empty user message');
126
312
  }
313
+ // Optimization 1: New conversation if this is the first message in the session
314
+ if (body.messages.length === 1) {
315
+ console.error(`[serve] New session detected (1 message). Starting new conversation in UI.`);
316
+ await startNewConversation(page);
317
+ }
318
+ // Optimization 3: Switch model if requested
319
+ if (body.model) {
320
+ await switchModel(page, body.model);
321
+ }
127
322
  // Get conversation state before sending
128
323
  const beforeText = await getConversationText(page);
129
324
  // Send the message
130
325
  console.error(`[serve] Sending: "${userText.slice(0, 80)}${userText.length > 80 ? '...' : ''}"`);
131
- await sendMessage(page, userText);
132
- // Poll for reply
326
+ await sendMessage(page, userText, bridge);
327
+ // Poll for reply (change detection)
133
328
  console.error('[serve] Waiting for reply...');
134
- const replyText = await waitForReply(page, beforeText);
329
+ await waitForReply(page, beforeText);
330
+ // Extract the actual reply text precisely from the DOM
331
+ const replyText = await getLastAssistantReply(page, userText);
135
332
  console.error(`[serve] Got reply: "${replyText.slice(0, 80)}${replyText.length > 80 ? '...' : ''}"`);
136
333
  return {
137
334
  id: generateMsgId(),
@@ -150,15 +347,65 @@ async function handleMessages(body, page) {
150
347
  // ─── Server ──────────────────────────────────────────────────────────
151
348
  export async function startServe(opts = {}) {
152
349
  const port = opts.port ?? 8082;
153
- // Establish persistent CDP connection
154
- console.error('[serve] Connecting to Antigravity via CDP...');
155
- const cdp = new CDPBridge();
156
- const page = await cdp.connect({ timeout: 15_000 });
157
- console.error('[serve] CDP connected successfully.');
158
- // Verify we can read conversation
159
- const testText = await getConversationText(page);
160
- console.error(`[serve] Conversation element found (${testText.length} chars).`);
350
+ // Lazy CDP connection — connect when first request comes in
351
+ let cdp = null;
352
+ let page = null;
161
353
  let requestInFlight = false;
354
+ async function ensureConnected() {
355
+ if (page) {
356
+ try {
357
+ await page.evaluate('1+1');
358
+ return page;
359
+ }
360
+ catch {
361
+ console.error('[serve] CDP connection lost, reconnecting...');
362
+ cdp?.close().catch(() => { });
363
+ cdp = null;
364
+ page = null;
365
+ }
366
+ }
367
+ const endpoint = process.env.OPENCLI_CDP_ENDPOINT;
368
+ if (!endpoint) {
369
+ throw new Error('OPENCLI_CDP_ENDPOINT is not set.\n' +
370
+ 'Usage: OPENCLI_CDP_ENDPOINT=http://127.0.0.1:9224 opencli antigravity serve');
371
+ }
372
+ // Note: Antigravity chat panel lives inside editor windows, not in Launchpad.
373
+ // If multiple editor windows are open, set OPENCLI_CDP_TARGET to the window title.
374
+ if (process.env.OPENCLI_CDP_TARGET) {
375
+ console.error(`[serve] Using OPENCLI_CDP_TARGET=${process.env.OPENCLI_CDP_TARGET}`);
376
+ }
377
+ // List available targets for debugging
378
+ try {
379
+ const res = await fetch(`${endpoint.replace(/\/$/, '')}/json`);
380
+ const targets = await res.json();
381
+ const pages = targets.filter(t => t.type === 'page');
382
+ console.error(`[serve] Available targets: ${pages.map(t => `"${t.title}"`).join(', ')}`);
383
+ }
384
+ catch { /* ignore */ }
385
+ console.error(`[serve] Connecting via CDP (target pattern: "${process.env.OPENCLI_CDP_TARGET}")...`);
386
+ cdp = new CDPBridge();
387
+ try {
388
+ page = await cdp.connect({ timeout: 15_000 });
389
+ }
390
+ catch (err) {
391
+ cdp = null;
392
+ const isRefused = err?.cause?.code === 'ECONNREFUSED' || err?.message?.includes('ECONNREFUSED');
393
+ throw new Error(isRefused
394
+ ? `Cannot connect to Antigravity at ${endpoint}.\n` +
395
+ ' 1. Make sure Antigravity is running\n' +
396
+ ' 2. Launch with: --remote-debugging-port=9224'
397
+ : `CDP connection failed: ${err.message}`);
398
+ }
399
+ console.error('[serve] ✅ CDP connected.');
400
+ // Quick verification
401
+ const hasUI = await page.evaluate(`
402
+ (() => !!document.getElementById('conversation') || !!document.getElementById('antigravity.agentSidePanelInputBox'))()
403
+ `);
404
+ if (!hasUI) {
405
+ console.error('[serve] ⚠️ Warning: chat UI elements not found in this target. Try setting OPENCLI_CDP_TARGET to the correct window title.');
406
+ }
407
+ return page;
408
+ }
162
409
  const server = createServer(async (req, res) => {
163
410
  // CORS preflight
164
411
  if (req.method === 'OPTIONS') {
@@ -204,7 +451,6 @@ export async function startServe(opts = {}) {
204
451
  const rawBody = await readBody(req);
205
452
  const body = JSON.parse(rawBody);
206
453
  if (body.stream) {
207
- // We don't support streaming — return error
208
454
  jsonResponse(res, 400, {
209
455
  type: 'error',
210
456
  error: {
@@ -214,7 +460,9 @@ export async function startServe(opts = {}) {
214
460
  });
215
461
  return;
216
462
  }
217
- const response = await handleMessages(body, page);
463
+ // Lazy connect on first request
464
+ const activePage = await ensureConnected();
465
+ const response = await handleMessages(body, activePage, cdp ?? undefined);
218
466
  jsonResponse(res, 200, response);
219
467
  }
220
468
  finally {
@@ -224,7 +472,7 @@ export async function startServe(opts = {}) {
224
472
  }
225
473
  // Health check
226
474
  if (req.method === 'GET' && (pathname === '/' || pathname === '/health')) {
227
- jsonResponse(res, 200, { ok: true, status: 'connected' });
475
+ jsonResponse(res, 200, { ok: true, cdpConnected: page !== null });
228
476
  return;
229
477
  }
230
478
  jsonResponse(res, 404, {
@@ -233,7 +481,7 @@ export async function startServe(opts = {}) {
233
481
  });
234
482
  }
235
483
  catch (err) {
236
- console.error('[serve] Error:', err);
484
+ console.error('[serve] Error:', err instanceof Error ? err.message : err);
237
485
  jsonResponse(res, 500, {
238
486
  type: 'error',
239
487
  error: {
@@ -246,13 +494,14 @@ export async function startServe(opts = {}) {
246
494
  server.listen(port, '127.0.0.1', () => {
247
495
  console.error(`\n[serve] ✅ Antigravity API proxy running at http://127.0.0.1:${port}`);
248
496
  console.error(`[serve] Compatible with Anthropic /v1/messages API`);
497
+ console.error(`[serve] CDP connection will be established on first request.`);
249
498
  console.error(`\n[serve] Usage with Claude Code:`);
250
499
  console.error(` ANTHROPIC_BASE_URL=http://localhost:${port} claude\n`);
251
500
  });
252
501
  // Graceful shutdown
253
502
  const shutdown = () => {
254
503
  console.error('\n[serve] Shutting down...');
255
- cdp.close().catch(() => { });
504
+ cdp?.close().catch(() => { });
256
505
  server.close();
257
506
  process.exit(0);
258
507
  };
@@ -0,0 +1,2 @@
1
+ import './search.js';
2
+ import './top.js';
@@ -0,0 +1,76 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { getRegistry } from '../../registry.js';
3
+ import './search.js';
4
+ import './top.js';
5
+ describe('apple-podcasts search command', () => {
6
+ beforeEach(() => {
7
+ vi.restoreAllMocks();
8
+ });
9
+ it('uses the positional query argument for the iTunes search request', async () => {
10
+ const cmd = getRegistry().get('apple-podcasts/search');
11
+ expect(cmd?.func).toBeTypeOf('function');
12
+ const fetchMock = vi.fn().mockResolvedValue({
13
+ ok: true,
14
+ json: () => Promise.resolve({
15
+ results: [
16
+ {
17
+ collectionId: 42,
18
+ collectionName: 'Machine Learning Guide',
19
+ artistName: 'OpenCLI',
20
+ trackCount: 12,
21
+ primaryGenreName: 'Technology',
22
+ },
23
+ ],
24
+ }),
25
+ });
26
+ vi.stubGlobal('fetch', fetchMock);
27
+ const result = await cmd.func(null, {
28
+ query: 'machine learning',
29
+ keyword: 'sports',
30
+ limit: 5,
31
+ });
32
+ expect(fetchMock).toHaveBeenCalledWith('https://itunes.apple.com/search?term=machine%20learning&media=podcast&limit=5');
33
+ expect(result).toEqual([
34
+ {
35
+ id: 42,
36
+ title: 'Machine Learning Guide',
37
+ author: 'OpenCLI',
38
+ episodes: 12,
39
+ genre: 'Technology',
40
+ },
41
+ ]);
42
+ });
43
+ });
44
+ describe('apple-podcasts top command', () => {
45
+ beforeEach(() => {
46
+ vi.restoreAllMocks();
47
+ });
48
+ it('uses the canonical Apple charts host and maps ranked results', async () => {
49
+ const cmd = getRegistry().get('apple-podcasts/top');
50
+ expect(cmd?.func).toBeTypeOf('function');
51
+ const fetchMock = vi.fn().mockResolvedValue({
52
+ ok: true,
53
+ json: () => Promise.resolve({
54
+ feed: {
55
+ results: [
56
+ { id: '100', name: 'Top Show', artistName: 'Host A' },
57
+ { id: '101', name: 'Second Show', artistName: 'Host B' },
58
+ ],
59
+ },
60
+ }),
61
+ });
62
+ vi.stubGlobal('fetch', fetchMock);
63
+ const result = await cmd.func(null, { country: 'US', limit: 2 });
64
+ expect(fetchMock).toHaveBeenCalledWith('https://rss.marketingtools.apple.com/api/v2/us/podcasts/top/2/podcasts.json');
65
+ expect(result).toEqual([
66
+ { rank: 1, title: 'Top Show', author: 'Host A', id: '100' },
67
+ { rank: 2, title: 'Second Show', author: 'Host B', id: '101' },
68
+ ]);
69
+ });
70
+ it('normalizes network failures into CliError output', async () => {
71
+ const cmd = getRegistry().get('apple-podcasts/top');
72
+ expect(cmd?.func).toBeTypeOf('function');
73
+ vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('socket hang up')));
74
+ await expect(cmd.func(null, { country: 'us', limit: 3 })).rejects.toThrow('Unable to reach Apple Podcasts charts for US');
75
+ });
76
+ });
@@ -8,12 +8,12 @@ cli({
8
8
  strategy: Strategy.PUBLIC,
9
9
  browser: false,
10
10
  args: [
11
- { name: 'keyword', positional: true, required: true, help: 'Search keyword' },
11
+ { name: 'query', positional: true, required: true, help: 'Search keyword' },
12
12
  { name: 'limit', type: 'int', default: 10, help: 'Max results' },
13
13
  ],
14
14
  columns: ['id', 'title', 'author', 'episodes', 'genre'],
15
15
  func: async (_page, args) => {
16
- const term = encodeURIComponent(args.keyword);
16
+ const term = encodeURIComponent(args.query);
17
17
  const limit = Math.max(1, Math.min(Number(args.limit), 25));
18
18
  const data = await itunesFetch(`/search?term=${term}&media=podcast&limit=${limit}`);
19
19
  if (!data.results?.length)
@@ -1,7 +1,7 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
2
  import { CliError } from '../../errors.js';
3
3
  // Apple Marketing Tools RSS API — public, no key required
4
- const CHARTS_URL = 'https://rss.applemarketingtools.com/api/v2';
4
+ const CHARTS_URL = 'https://rss.marketingtools.apple.com/api/v2';
5
5
  cli({
6
6
  site: 'apple-podcasts',
7
7
  name: 'top',
@@ -17,7 +17,14 @@ cli({
17
17
  const limit = Math.max(1, Math.min(Number(args.limit), 100));
18
18
  const country = String(args.country || 'us').trim().toLowerCase();
19
19
  const url = `${CHARTS_URL}/${country}/podcasts/top/${limit}/podcasts.json`;
20
- const resp = await fetch(url);
20
+ let resp;
21
+ try {
22
+ resp = await fetch(url);
23
+ }
24
+ catch (error) {
25
+ const reason = error?.cause?.code ?? error?.message ?? 'unknown network error';
26
+ throw new CliError('FETCH_ERROR', `Unable to reach Apple Podcasts charts for ${country.toUpperCase()}`, `Apple charts may be temporarily unavailable (${reason}). Try again later.`);
27
+ }
21
28
  if (!resp.ok)
22
29
  throw new CliError('FETCH_ERROR', `Charts API HTTP ${resp.status}`, `Check country code: ${country}`);
23
30
  const data = await resp.json();
@@ -0,0 +1,21 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { CliError } from '../../errors.js';
3
+ import { arxivFetch, parseEntries } from './utils.js';
4
+ cli({
5
+ site: 'arxiv',
6
+ name: 'paper',
7
+ description: 'Get arXiv paper details by ID',
8
+ strategy: Strategy.PUBLIC,
9
+ browser: false,
10
+ args: [
11
+ { name: 'id', positional: true, required: true, help: 'arXiv paper ID (e.g. 1706.03762)' },
12
+ ],
13
+ columns: ['id', 'title', 'authors', 'published', 'abstract', 'url'],
14
+ func: async (_page, args) => {
15
+ const xml = await arxivFetch(`id_list=${encodeURIComponent(args.id)}`);
16
+ const entries = parseEntries(xml);
17
+ if (!entries.length)
18
+ throw new CliError('NOT_FOUND', `Paper ${args.id} not found`, 'Check the arXiv ID format, e.g. 1706.03762');
19
+ return entries;
20
+ },
21
+ });
@@ -0,0 +1,24 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { CliError } from '../../errors.js';
3
+ import { arxivFetch, parseEntries } from './utils.js';
4
+ cli({
5
+ site: 'arxiv',
6
+ name: 'search',
7
+ description: 'Search arXiv papers',
8
+ strategy: Strategy.PUBLIC,
9
+ browser: false,
10
+ args: [
11
+ { name: 'query', positional: true, required: true, help: 'Search keyword (e.g. "attention is all you need")' },
12
+ { name: 'limit', type: 'int', default: 10, help: 'Max results (max 25)' },
13
+ ],
14
+ columns: ['id', 'title', 'authors', 'published'],
15
+ func: async (_page, args) => {
16
+ const limit = Math.max(1, Math.min(Number(args.limit), 25));
17
+ const query = encodeURIComponent(`all:${args.keyword}`);
18
+ const xml = await arxivFetch(`search_query=${query}&max_results=${limit}&sortBy=relevance`);
19
+ const entries = parseEntries(xml);
20
+ if (!entries.length)
21
+ throw new CliError('NOT_FOUND', 'No papers found', 'Try a different keyword');
22
+ return entries.map(e => ({ id: e.id, title: e.title, authors: e.authors, published: e.published }));
23
+ },
24
+ });
@@ -0,0 +1,18 @@
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
+ export declare const ARXIV_BASE = "https://export.arxiv.org/api/query";
8
+ export declare function arxivFetch(params: string): Promise<string>;
9
+ export interface ArxivEntry {
10
+ id: string;
11
+ title: string;
12
+ authors: string;
13
+ abstract: string;
14
+ published: string;
15
+ url: string;
16
+ }
17
+ /** Parse Atom XML feed into structured entries. */
18
+ export declare function parseEntries(xml: string): ArxivEntry[];