@jackwener/opencli 1.4.0 → 1.5.0

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 (514) hide show
  1. package/.github/actions/setup-chrome/action.yml +5 -4
  2. package/.github/workflows/build-extension.yml +2 -6
  3. package/.github/workflows/ci.yml +37 -3
  4. package/.github/workflows/e2e-headed.yml +16 -3
  5. package/CHANGELOG.md +23 -0
  6. package/PRIVACY.md +57 -0
  7. package/README.md +36 -7
  8. package/README.zh-CN.md +13 -6
  9. package/SKILL.md +103 -2
  10. package/dist/browser/cdp.d.ts +2 -1
  11. package/dist/browser/discover.d.ts +4 -1
  12. package/dist/browser/discover.js +6 -2
  13. package/dist/browser/errors.d.ts +2 -2
  14. package/dist/browser/errors.js +4 -12
  15. package/dist/browser/mcp.d.ts +2 -1
  16. package/dist/build-manifest.d.ts +2 -0
  17. package/dist/build-manifest.js +39 -14
  18. package/dist/build-manifest.test.js +21 -0
  19. package/dist/capabilityRouting.d.ts +2 -0
  20. package/dist/capabilityRouting.js +2 -1
  21. package/dist/cli-manifest.json +1838 -151
  22. package/dist/cli.js +34 -3
  23. package/dist/clis/36kr/article.d.ts +1 -0
  24. package/dist/clis/36kr/article.js +62 -0
  25. package/dist/clis/36kr/hot.d.ts +3 -0
  26. package/dist/clis/36kr/hot.js +80 -0
  27. package/dist/clis/36kr/hot.test.d.ts +1 -0
  28. package/dist/clis/36kr/hot.test.js +15 -0
  29. package/dist/clis/36kr/news.d.ts +1 -0
  30. package/dist/clis/36kr/news.js +51 -0
  31. package/dist/clis/36kr/news.test.d.ts +1 -0
  32. package/dist/clis/36kr/news.test.js +85 -0
  33. package/dist/clis/36kr/search.d.ts +1 -0
  34. package/dist/clis/36kr/search.js +72 -0
  35. package/dist/clis/apple-podcasts/search.js +2 -1
  36. package/dist/clis/arxiv/search.js +2 -2
  37. package/dist/clis/bbc/news.js +0 -1
  38. package/dist/clis/bilibili/comments.d.ts +5 -0
  39. package/dist/clis/bilibili/comments.js +40 -0
  40. package/dist/clis/bilibili/comments.test.d.ts +1 -0
  41. package/dist/clis/bilibili/comments.test.js +82 -0
  42. package/dist/clis/chatgpt/ask.js +29 -14
  43. package/dist/clis/chatgpt/ax.d.ts +6 -0
  44. package/dist/clis/chatgpt/ax.js +172 -1
  45. package/dist/clis/chatgpt/model.d.ts +1 -0
  46. package/dist/clis/chatgpt/model.js +24 -0
  47. package/dist/clis/chatgpt/send.js +12 -3
  48. package/dist/clis/ctrip/search.js +0 -1
  49. package/dist/clis/douban/download.d.ts +1 -0
  50. package/dist/clis/douban/download.js +67 -0
  51. package/dist/clis/douban/download.test.d.ts +1 -0
  52. package/dist/clis/douban/download.test.js +170 -0
  53. package/dist/clis/douban/photos.d.ts +1 -0
  54. package/dist/clis/douban/photos.js +34 -0
  55. package/dist/clis/douban/utils.d.ts +25 -0
  56. package/dist/clis/douban/utils.js +190 -1
  57. package/dist/clis/douban/utils.test.d.ts +1 -0
  58. package/dist/clis/douban/utils.test.js +64 -0
  59. package/dist/clis/douyin/_shared/browser-fetch.d.ts +10 -0
  60. package/dist/clis/douyin/_shared/browser-fetch.js +30 -0
  61. package/dist/clis/douyin/_shared/browser-fetch.test.d.ts +1 -0
  62. package/dist/clis/douyin/_shared/browser-fetch.test.js +31 -0
  63. package/dist/clis/douyin/_shared/creation-id.d.ts +1 -0
  64. package/dist/clis/douyin/_shared/creation-id.js +5 -0
  65. package/dist/clis/douyin/_shared/creation-id.test.d.ts +1 -0
  66. package/dist/clis/douyin/_shared/creation-id.test.js +22 -0
  67. package/dist/clis/douyin/_shared/imagex-upload.d.ts +20 -0
  68. package/dist/clis/douyin/_shared/imagex-upload.js +53 -0
  69. package/dist/clis/douyin/_shared/imagex-upload.test.d.ts +1 -0
  70. package/dist/clis/douyin/_shared/imagex-upload.test.js +87 -0
  71. package/dist/clis/douyin/_shared/sts2.d.ts +8 -0
  72. package/dist/clis/douyin/_shared/sts2.js +15 -0
  73. package/dist/clis/douyin/_shared/text-extra.d.ts +18 -0
  74. package/dist/clis/douyin/_shared/text-extra.js +15 -0
  75. package/dist/clis/douyin/_shared/text-extra.test.d.ts +1 -0
  76. package/dist/clis/douyin/_shared/text-extra.test.js +37 -0
  77. package/dist/clis/douyin/_shared/timing.d.ts +2 -0
  78. package/dist/clis/douyin/_shared/timing.js +22 -0
  79. package/dist/clis/douyin/_shared/timing.test.d.ts +1 -0
  80. package/dist/clis/douyin/_shared/timing.test.js +28 -0
  81. package/dist/clis/douyin/_shared/tos-upload-short-read.test.d.ts +11 -0
  82. package/dist/clis/douyin/_shared/tos-upload-short-read.test.js +83 -0
  83. package/dist/clis/douyin/_shared/tos-upload.d.ts +53 -0
  84. package/dist/clis/douyin/_shared/tos-upload.js +295 -0
  85. package/dist/clis/douyin/_shared/tos-upload.test.d.ts +1 -0
  86. package/dist/clis/douyin/_shared/tos-upload.test.js +229 -0
  87. package/dist/clis/douyin/_shared/transcode.d.ts +27 -0
  88. package/dist/clis/douyin/_shared/transcode.js +45 -0
  89. package/dist/clis/douyin/_shared/transcode.test.d.ts +1 -0
  90. package/dist/clis/douyin/_shared/transcode.test.js +93 -0
  91. package/dist/clis/douyin/_shared/types.d.ts +26 -0
  92. package/dist/clis/douyin/_shared/types.js +1 -0
  93. package/dist/clis/douyin/activities.d.ts +1 -0
  94. package/dist/clis/douyin/activities.js +20 -0
  95. package/dist/clis/douyin/activities.test.d.ts +1 -0
  96. package/dist/clis/douyin/activities.test.js +22 -0
  97. package/dist/clis/douyin/collections.d.ts +1 -0
  98. package/dist/clis/douyin/collections.js +22 -0
  99. package/dist/clis/douyin/collections.test.d.ts +1 -0
  100. package/dist/clis/douyin/collections.test.js +23 -0
  101. package/dist/clis/douyin/delete.d.ts +1 -0
  102. package/dist/clis/douyin/delete.js +18 -0
  103. package/dist/clis/douyin/delete.test.d.ts +1 -0
  104. package/dist/clis/douyin/delete.test.js +11 -0
  105. package/dist/clis/douyin/draft.d.ts +14 -0
  106. package/dist/clis/douyin/draft.js +237 -0
  107. package/dist/clis/douyin/draft.test.d.ts +1 -0
  108. package/dist/clis/douyin/draft.test.js +11 -0
  109. package/dist/clis/douyin/drafts.d.ts +1 -0
  110. package/dist/clis/douyin/drafts.js +23 -0
  111. package/dist/clis/douyin/drafts.test.d.ts +1 -0
  112. package/dist/clis/douyin/drafts.test.js +11 -0
  113. package/dist/clis/douyin/hashtag.d.ts +1 -0
  114. package/dist/clis/douyin/hashtag.js +45 -0
  115. package/dist/clis/douyin/hashtag.test.d.ts +1 -0
  116. package/dist/clis/douyin/hashtag.test.js +25 -0
  117. package/dist/clis/douyin/location.d.ts +1 -0
  118. package/dist/clis/douyin/location.js +24 -0
  119. package/dist/clis/douyin/location.test.d.ts +1 -0
  120. package/dist/clis/douyin/location.test.js +23 -0
  121. package/dist/clis/douyin/profile.d.ts +1 -0
  122. package/dist/clis/douyin/profile.js +28 -0
  123. package/dist/clis/douyin/profile.test.d.ts +1 -0
  124. package/dist/clis/douyin/profile.test.js +11 -0
  125. package/dist/clis/douyin/publish.d.ts +14 -0
  126. package/dist/clis/douyin/publish.js +288 -0
  127. package/dist/clis/douyin/publish.test.d.ts +1 -0
  128. package/dist/clis/douyin/publish.test.js +38 -0
  129. package/dist/clis/douyin/stats.d.ts +1 -0
  130. package/dist/clis/douyin/stats.js +27 -0
  131. package/dist/clis/douyin/stats.test.d.ts +1 -0
  132. package/dist/clis/douyin/stats.test.js +22 -0
  133. package/dist/clis/douyin/update.d.ts +1 -0
  134. package/dist/clis/douyin/update.js +31 -0
  135. package/dist/clis/douyin/update.test.d.ts +1 -0
  136. package/dist/clis/douyin/update.test.js +11 -0
  137. package/dist/clis/douyin/videos.d.ts +1 -0
  138. package/dist/clis/douyin/videos.js +34 -0
  139. package/dist/clis/douyin/videos.test.d.ts +1 -0
  140. package/dist/clis/douyin/videos.test.js +11 -0
  141. package/dist/clis/hackernews/search.yaml +1 -1
  142. package/dist/clis/imdb/person.d.ts +1 -0
  143. package/dist/clis/imdb/person.js +203 -0
  144. package/dist/clis/imdb/reviews.d.ts +1 -0
  145. package/dist/clis/imdb/reviews.js +88 -0
  146. package/dist/clis/imdb/search.d.ts +1 -0
  147. package/dist/clis/imdb/search.js +161 -0
  148. package/dist/clis/imdb/title.d.ts +1 -0
  149. package/dist/clis/imdb/title.js +93 -0
  150. package/dist/clis/imdb/top.d.ts +1 -0
  151. package/dist/clis/imdb/top.js +53 -0
  152. package/dist/clis/imdb/trending.d.ts +1 -0
  153. package/dist/clis/imdb/trending.js +52 -0
  154. package/dist/clis/imdb/utils.d.ts +46 -0
  155. package/dist/clis/imdb/utils.js +285 -0
  156. package/dist/clis/imdb/utils.test.d.ts +1 -0
  157. package/dist/clis/imdb/utils.test.js +88 -0
  158. package/dist/clis/instagram/search.yaml +2 -1
  159. package/dist/clis/jd/item.d.ts +4 -0
  160. package/dist/clis/jd/item.js +16 -15
  161. package/dist/clis/jd/item.test.js +16 -1
  162. package/dist/clis/linux-do/categories.yaml +38 -9
  163. package/dist/clis/linux-do/category.d.ts +1 -0
  164. package/dist/clis/linux-do/category.js +36 -0
  165. package/dist/clis/linux-do/feed.d.ts +45 -0
  166. package/dist/clis/linux-do/feed.js +397 -0
  167. package/dist/clis/linux-do/feed.test.d.ts +1 -0
  168. package/dist/clis/linux-do/feed.test.js +118 -0
  169. package/dist/clis/linux-do/hot.d.ts +1 -0
  170. package/dist/clis/linux-do/hot.js +25 -0
  171. package/dist/clis/linux-do/latest.d.ts +1 -0
  172. package/dist/clis/linux-do/latest.js +18 -0
  173. package/dist/clis/linux-do/search.yaml +3 -1
  174. package/dist/clis/linux-do/tags.yaml +41 -0
  175. package/dist/clis/linux-do/topic.yaml +41 -3
  176. package/dist/clis/linux-do/user-posts.yaml +67 -0
  177. package/dist/clis/linux-do/user-topics.yaml +54 -0
  178. package/dist/clis/medium/search.js +1 -1
  179. package/dist/clis/paperreview/commands.test.d.ts +3 -0
  180. package/dist/clis/paperreview/commands.test.js +243 -0
  181. package/dist/clis/paperreview/feedback.d.ts +1 -0
  182. package/dist/clis/paperreview/feedback.js +52 -0
  183. package/dist/clis/paperreview/review.d.ts +1 -0
  184. package/dist/clis/paperreview/review.js +37 -0
  185. package/dist/clis/paperreview/submit.d.ts +1 -0
  186. package/dist/clis/paperreview/submit.js +85 -0
  187. package/dist/clis/paperreview/utils.d.ts +46 -0
  188. package/dist/clis/paperreview/utils.js +197 -0
  189. package/dist/clis/paperreview/utils.test.d.ts +1 -0
  190. package/dist/clis/paperreview/utils.test.js +49 -0
  191. package/dist/clis/producthunt/browse.d.ts +1 -0
  192. package/dist/clis/producthunt/browse.js +99 -0
  193. package/dist/clis/producthunt/hot.d.ts +1 -0
  194. package/dist/clis/producthunt/hot.js +110 -0
  195. package/dist/clis/producthunt/posts.d.ts +1 -0
  196. package/dist/clis/producthunt/posts.js +28 -0
  197. package/dist/clis/producthunt/today.d.ts +1 -0
  198. package/dist/clis/producthunt/today.js +35 -0
  199. package/dist/clis/producthunt/utils.d.ts +29 -0
  200. package/dist/clis/producthunt/utils.js +99 -0
  201. package/dist/clis/producthunt/utils.test.d.ts +1 -0
  202. package/dist/clis/producthunt/utils.test.js +64 -0
  203. package/dist/clis/reuters/search.js +0 -1
  204. package/dist/clis/twitter/article.js +4 -28
  205. package/dist/clis/twitter/likes.d.ts +24 -0
  206. package/dist/clis/twitter/likes.js +217 -0
  207. package/dist/clis/twitter/likes.test.d.ts +1 -0
  208. package/dist/clis/twitter/likes.test.js +85 -0
  209. package/dist/clis/twitter/profile.js +4 -28
  210. package/dist/clis/twitter/search.js +7 -4
  211. package/dist/clis/twitter/search.test.js +56 -2
  212. package/dist/clis/twitter/shared.d.ts +6 -0
  213. package/dist/clis/twitter/shared.js +35 -0
  214. package/dist/clis/twitter/timeline.js +2 -13
  215. package/dist/clis/weibo/comments.d.ts +1 -0
  216. package/dist/clis/weibo/comments.js +53 -0
  217. package/dist/clis/weibo/feed.d.ts +1 -0
  218. package/dist/clis/weibo/feed.js +56 -0
  219. package/dist/clis/weibo/hot.js +0 -1
  220. package/dist/clis/weibo/me.d.ts +1 -0
  221. package/dist/clis/weibo/me.js +76 -0
  222. package/dist/clis/weibo/post.d.ts +1 -0
  223. package/dist/clis/weibo/post.js +75 -0
  224. package/dist/clis/weibo/user.d.ts +1 -0
  225. package/dist/clis/weibo/user.js +63 -0
  226. package/dist/clis/weibo/utils.d.ts +6 -0
  227. package/dist/clis/weibo/utils.js +30 -0
  228. package/dist/clis/weixin/download.d.ts +17 -0
  229. package/dist/clis/weixin/download.js +88 -20
  230. package/dist/clis/weread/book.js +2 -2
  231. package/dist/clis/weread/commands.test.d.ts +3 -0
  232. package/dist/clis/weread/commands.test.js +43 -0
  233. package/dist/clis/weread/highlights.js +2 -2
  234. package/dist/clis/weread/notebooks.js +2 -2
  235. package/dist/clis/weread/notes.js +3 -3
  236. package/dist/clis/weread/search.js +3 -2
  237. package/dist/clis/weread/shelf.js +2 -2
  238. package/dist/clis/weread/utils.d.ts +4 -4
  239. package/dist/clis/weread/utils.js +32 -14
  240. package/dist/clis/weread/utils.test.js +1 -28
  241. package/dist/clis/xiaohongshu/comments.d.ts +5 -0
  242. package/dist/clis/xiaohongshu/comments.js +74 -0
  243. package/dist/clis/xiaohongshu/comments.test.d.ts +1 -0
  244. package/dist/clis/xiaohongshu/comments.test.js +79 -0
  245. package/dist/clis/xiaohongshu/publish.js +114 -18
  246. package/dist/clis/xiaohongshu/publish.test.d.ts +1 -0
  247. package/dist/clis/xiaohongshu/publish.test.js +119 -0
  248. package/dist/clis/xueqiu/search.yaml +2 -1
  249. package/dist/clis/yahoo-finance/quote.js +0 -1
  250. package/dist/clis/youtube/channel.d.ts +1 -0
  251. package/dist/clis/youtube/channel.js +150 -0
  252. package/dist/clis/youtube/comments.d.ts +1 -0
  253. package/dist/clis/youtube/comments.js +95 -0
  254. package/dist/clis/youtube/search.js +0 -1
  255. package/dist/clis/zhihu/search.yaml +2 -1
  256. package/dist/commanderAdapter.d.ts +1 -0
  257. package/dist/commanderAdapter.js +176 -29
  258. package/dist/commanderAdapter.test.d.ts +1 -0
  259. package/dist/commanderAdapter.test.js +62 -0
  260. package/dist/daemon.js +17 -1
  261. package/dist/discovery.js +8 -14
  262. package/dist/doctor.d.ts +1 -0
  263. package/dist/doctor.js +9 -2
  264. package/dist/download/index.js +63 -51
  265. package/dist/download/index.test.js +17 -4
  266. package/dist/errors.d.ts +3 -1
  267. package/dist/errors.js +15 -32
  268. package/dist/execution.d.ts +1 -3
  269. package/dist/execution.js +21 -1
  270. package/dist/external-clis.yaml +0 -17
  271. package/dist/hooks.js +2 -0
  272. package/dist/main.js +5 -0
  273. package/dist/output.js +5 -1
  274. package/dist/pipeline/executor.js +3 -4
  275. package/dist/plugin-manifest.d.ts +70 -0
  276. package/dist/plugin-manifest.js +160 -0
  277. package/dist/plugin-manifest.test.d.ts +4 -0
  278. package/dist/plugin-manifest.test.js +179 -0
  279. package/dist/plugin.d.ts +38 -5
  280. package/dist/plugin.js +267 -33
  281. package/dist/plugin.test.js +220 -3
  282. package/dist/registry.d.ts +4 -0
  283. package/dist/registry.js +2 -0
  284. package/dist/runtime-detect.d.ts +21 -0
  285. package/dist/runtime-detect.js +32 -0
  286. package/dist/runtime-detect.test.d.ts +1 -0
  287. package/dist/runtime-detect.test.js +27 -0
  288. package/dist/runtime.js +1 -1
  289. package/dist/serialization.d.ts +2 -0
  290. package/dist/serialization.js +6 -0
  291. package/dist/types.d.ts +1 -0
  292. package/dist/update-check.d.ts +22 -0
  293. package/dist/update-check.js +112 -0
  294. package/dist/weixin-download.test.d.ts +1 -0
  295. package/dist/weixin-download.test.js +30 -0
  296. package/dist/weread-private-api-regression.test.d.ts +1 -0
  297. package/dist/weread-private-api-regression.test.js +122 -0
  298. package/dist/weread-search-regression.test.d.ts +1 -0
  299. package/dist/weread-search-regression.test.js +39 -0
  300. package/dist/yaml-schema.d.ts +3 -0
  301. package/dist/yaml-schema.js +18 -1
  302. package/docs/.vitepress/config.mts +17 -0
  303. package/docs/adapters/browser/36kr.md +47 -0
  304. package/docs/adapters/browser/douban.md +14 -0
  305. package/docs/adapters/browser/douyin.md +75 -0
  306. package/docs/adapters/browser/imdb.md +47 -0
  307. package/docs/adapters/browser/jd.md +2 -2
  308. package/docs/adapters/browser/linux-do.md +181 -20
  309. package/docs/adapters/browser/paperreview.md +43 -0
  310. package/docs/adapters/browser/producthunt.md +49 -0
  311. package/docs/adapters/browser/twitter.md +6 -0
  312. package/docs/adapters/desktop/chatgpt.md +5 -0
  313. package/docs/adapters/index.md +12 -3
  314. package/docs/advanced/download.md +4 -0
  315. package/docs/advanced/rate-limiter-plugin.md +99 -0
  316. package/docs/guide/electron-app-cli.md +200 -0
  317. package/docs/guide/getting-started.md +1 -0
  318. package/docs/guide/plugins.md +87 -0
  319. package/docs/zh/guide/electron-app-cli.md +188 -0
  320. package/docs/zh/guide/getting-started.md +1 -0
  321. package/docs/zh/guide/plugins.md +65 -0
  322. package/extension/dist/background.js +508 -518
  323. package/extension/manifest.json +6 -2
  324. package/extension/package.json +2 -1
  325. package/extension/popup.html +84 -0
  326. package/extension/popup.js +25 -0
  327. package/extension/scripts/package-release.mjs +179 -0
  328. package/extension/src/background.ts +22 -1
  329. package/package.json +4 -1
  330. package/scripts/postinstall.js +10 -0
  331. package/src/browser/cdp.ts +2 -1
  332. package/src/browser/discover.ts +8 -3
  333. package/src/browser/errors.ts +13 -14
  334. package/src/browser/mcp.ts +2 -1
  335. package/src/build-manifest.test.ts +23 -0
  336. package/src/build-manifest.ts +40 -15
  337. package/src/capabilityRouting.ts +2 -1
  338. package/src/cli.ts +35 -3
  339. package/src/clis/36kr/article.ts +69 -0
  340. package/src/clis/36kr/hot.test.ts +19 -0
  341. package/src/clis/36kr/hot.ts +100 -0
  342. package/src/clis/36kr/news.test.ts +90 -0
  343. package/src/clis/36kr/news.ts +54 -0
  344. package/src/clis/36kr/search.ts +78 -0
  345. package/src/clis/apple-podcasts/search.ts +2 -1
  346. package/src/clis/arxiv/search.ts +2 -2
  347. package/src/clis/bbc/news.ts +0 -1
  348. package/src/clis/bilibili/comments.test.ts +102 -0
  349. package/src/clis/bilibili/comments.ts +44 -0
  350. package/src/clis/chatgpt/ask.ts +28 -14
  351. package/src/clis/chatgpt/ax.ts +180 -1
  352. package/src/clis/chatgpt/model.ts +27 -0
  353. package/src/clis/chatgpt/send.ts +16 -6
  354. package/src/clis/ctrip/search.ts +0 -1
  355. package/src/clis/douban/download.test.ts +196 -0
  356. package/src/clis/douban/download.ts +78 -0
  357. package/src/clis/douban/photos.ts +36 -0
  358. package/src/clis/douban/utils.test.ts +97 -0
  359. package/src/clis/douban/utils.ts +232 -1
  360. package/src/clis/douyin/_shared/browser-fetch.test.ts +38 -0
  361. package/src/clis/douyin/_shared/browser-fetch.ts +45 -0
  362. package/src/clis/douyin/_shared/creation-id.test.ts +26 -0
  363. package/src/clis/douyin/_shared/creation-id.ts +8 -0
  364. package/src/clis/douyin/_shared/imagex-upload.test.ts +113 -0
  365. package/src/clis/douyin/_shared/imagex-upload.ts +76 -0
  366. package/src/clis/douyin/_shared/sts2.ts +20 -0
  367. package/src/clis/douyin/_shared/text-extra.test.ts +42 -0
  368. package/src/clis/douyin/_shared/text-extra.ts +33 -0
  369. package/src/clis/douyin/_shared/timing.test.ts +38 -0
  370. package/src/clis/douyin/_shared/timing.ts +22 -0
  371. package/src/clis/douyin/_shared/tos-upload-short-read.test.ts +102 -0
  372. package/src/clis/douyin/_shared/tos-upload.test.ts +281 -0
  373. package/src/clis/douyin/_shared/tos-upload.ts +444 -0
  374. package/src/clis/douyin/_shared/transcode.test.ts +117 -0
  375. package/src/clis/douyin/_shared/transcode.ts +78 -0
  376. package/src/clis/douyin/_shared/types.ts +29 -0
  377. package/src/clis/douyin/activities.test.ts +25 -0
  378. package/src/clis/douyin/activities.ts +23 -0
  379. package/src/clis/douyin/collections.test.ts +26 -0
  380. package/src/clis/douyin/collections.ts +25 -0
  381. package/src/clis/douyin/delete.test.ts +12 -0
  382. package/src/clis/douyin/delete.ts +20 -0
  383. package/src/clis/douyin/draft.test.ts +12 -0
  384. package/src/clis/douyin/draft.ts +282 -0
  385. package/src/clis/douyin/drafts.test.ts +12 -0
  386. package/src/clis/douyin/drafts.ts +27 -0
  387. package/src/clis/douyin/hashtag.test.ts +28 -0
  388. package/src/clis/douyin/hashtag.ts +56 -0
  389. package/src/clis/douyin/location.test.ts +26 -0
  390. package/src/clis/douyin/location.ts +27 -0
  391. package/src/clis/douyin/profile.test.ts +12 -0
  392. package/src/clis/douyin/profile.ts +37 -0
  393. package/src/clis/douyin/publish.test.ts +45 -0
  394. package/src/clis/douyin/publish.ts +340 -0
  395. package/src/clis/douyin/stats.test.ts +25 -0
  396. package/src/clis/douyin/stats.ts +30 -0
  397. package/src/clis/douyin/update.test.ts +12 -0
  398. package/src/clis/douyin/update.ts +43 -0
  399. package/src/clis/douyin/videos.test.ts +12 -0
  400. package/src/clis/douyin/videos.ts +49 -0
  401. package/src/clis/hackernews/search.yaml +1 -1
  402. package/src/clis/imdb/person.ts +232 -0
  403. package/src/clis/imdb/reviews.ts +111 -0
  404. package/src/clis/imdb/search.ts +179 -0
  405. package/src/clis/imdb/title.ts +121 -0
  406. package/src/clis/imdb/top.ts +67 -0
  407. package/src/clis/imdb/trending.ts +66 -0
  408. package/src/clis/imdb/utils.test.ts +117 -0
  409. package/src/clis/imdb/utils.ts +305 -0
  410. package/src/clis/instagram/search.yaml +2 -1
  411. package/src/clis/jd/item.test.ts +18 -1
  412. package/src/clis/jd/item.ts +18 -15
  413. package/src/clis/linux-do/categories.yaml +38 -9
  414. package/src/clis/linux-do/category.ts +37 -0
  415. package/src/clis/linux-do/feed.test.ts +132 -0
  416. package/src/clis/linux-do/feed.ts +501 -0
  417. package/src/clis/linux-do/hot.ts +26 -0
  418. package/src/clis/linux-do/latest.ts +19 -0
  419. package/src/clis/linux-do/search.yaml +3 -1
  420. package/src/clis/linux-do/tags.yaml +41 -0
  421. package/src/clis/linux-do/topic.yaml +41 -3
  422. package/src/clis/linux-do/user-posts.yaml +67 -0
  423. package/src/clis/linux-do/user-topics.yaml +54 -0
  424. package/src/clis/medium/search.ts +1 -1
  425. package/src/clis/paperreview/commands.test.ts +283 -0
  426. package/src/clis/paperreview/feedback.ts +64 -0
  427. package/src/clis/paperreview/review.ts +47 -0
  428. package/src/clis/paperreview/submit.ts +119 -0
  429. package/src/clis/paperreview/utils.test.ts +68 -0
  430. package/src/clis/paperreview/utils.ts +276 -0
  431. package/src/clis/producthunt/browse.ts +109 -0
  432. package/src/clis/producthunt/hot.ts +127 -0
  433. package/src/clis/producthunt/posts.ts +29 -0
  434. package/src/clis/producthunt/today.ts +37 -0
  435. package/src/clis/producthunt/utils.test.ts +72 -0
  436. package/src/clis/producthunt/utils.ts +122 -0
  437. package/src/clis/reuters/search.ts +0 -1
  438. package/src/clis/twitter/article.ts +5 -28
  439. package/src/clis/twitter/likes.test.ts +91 -0
  440. package/src/clis/twitter/likes.ts +256 -0
  441. package/src/clis/twitter/profile.ts +5 -28
  442. package/src/clis/twitter/search.test.ts +71 -2
  443. package/src/clis/twitter/search.ts +8 -4
  444. package/src/clis/twitter/shared.ts +45 -0
  445. package/src/clis/twitter/timeline.ts +2 -13
  446. package/src/clis/weibo/comments.ts +54 -0
  447. package/src/clis/weibo/feed.ts +57 -0
  448. package/src/clis/weibo/hot.ts +0 -1
  449. package/src/clis/weibo/me.ts +77 -0
  450. package/src/clis/weibo/post.ts +77 -0
  451. package/src/clis/weibo/user.ts +64 -0
  452. package/src/clis/weibo/utils.ts +32 -0
  453. package/src/clis/weixin/download.ts +114 -20
  454. package/src/clis/weread/book.ts +2 -2
  455. package/src/clis/weread/commands.test.ts +57 -0
  456. package/src/clis/weread/highlights.ts +2 -2
  457. package/src/clis/weread/notebooks.ts +2 -2
  458. package/src/clis/weread/notes.ts +3 -3
  459. package/src/clis/weread/search.ts +3 -2
  460. package/src/clis/weread/shelf.ts +2 -2
  461. package/src/clis/weread/utils.test.ts +1 -32
  462. package/src/clis/weread/utils.ts +41 -16
  463. package/src/clis/xiaohongshu/comments.test.ts +96 -0
  464. package/src/clis/xiaohongshu/comments.ts +81 -0
  465. package/src/clis/xiaohongshu/publish.test.ts +137 -0
  466. package/src/clis/xiaohongshu/publish.ts +129 -18
  467. package/src/clis/xueqiu/search.yaml +2 -1
  468. package/src/clis/yahoo-finance/quote.ts +0 -1
  469. package/src/clis/youtube/channel.ts +155 -0
  470. package/src/clis/youtube/comments.ts +97 -0
  471. package/src/clis/youtube/search.ts +0 -1
  472. package/src/clis/zhihu/search.yaml +2 -1
  473. package/src/commanderAdapter.test.ts +78 -0
  474. package/src/commanderAdapter.ts +188 -24
  475. package/src/daemon.ts +19 -1
  476. package/src/discovery.ts +8 -15
  477. package/src/doctor.ts +13 -2
  478. package/src/download/index.test.ts +14 -4
  479. package/src/download/index.ts +67 -55
  480. package/src/errors.ts +25 -66
  481. package/src/execution.ts +28 -3
  482. package/src/external-clis.yaml +0 -17
  483. package/src/hooks.ts +1 -0
  484. package/src/main.ts +6 -0
  485. package/src/output.ts +3 -1
  486. package/src/pipeline/executor.ts +4 -6
  487. package/src/plugin-manifest.test.ts +223 -0
  488. package/src/plugin-manifest.ts +206 -0
  489. package/src/plugin.test.ts +246 -2
  490. package/src/plugin.ts +338 -36
  491. package/src/registry.ts +6 -1
  492. package/src/runtime-detect.test.ts +30 -0
  493. package/src/runtime-detect.ts +36 -0
  494. package/src/runtime.ts +1 -1
  495. package/src/serialization.ts +4 -0
  496. package/src/types.ts +1 -0
  497. package/src/update-check.ts +114 -0
  498. package/src/weixin-download.test.ts +64 -0
  499. package/src/weread-private-api-regression.test.ts +150 -0
  500. package/src/weread-search-regression.test.ts +44 -0
  501. package/src/yaml-schema.ts +20 -0
  502. package/tests/e2e/browser-auth.test.ts +13 -9
  503. package/tests/e2e/browser-public-extended.test.ts +162 -0
  504. package/tests/e2e/browser-public.test.ts +55 -136
  505. package/tests/e2e/helpers.ts +2 -1
  506. package/tests/e2e/public-commands.test.ts +37 -3
  507. package/tests/smoke/api-health.test.ts +1 -1
  508. package/vitest.config.ts +34 -17
  509. package/dist/clis/linux-do/category.yaml +0 -51
  510. package/dist/clis/linux-do/hot.yaml +0 -50
  511. package/dist/clis/linux-do/latest.yaml +0 -40
  512. package/src/clis/linux-do/category.yaml +0 -51
  513. package/src/clis/linux-do/hot.yaml +0 -50
  514. package/src/clis/linux-do/latest.yaml +0 -40
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Non-blocking update checker.
3
+ *
4
+ * Pattern: register exit-hook + kick-off-background-fetch
5
+ * - On startup: kick off background fetch (non-blocking)
6
+ * - On process exit: read cache, print notice if newer version exists
7
+ * - Check interval: 24 hours
8
+ * - Notice appears AFTER command output, not before (same as npm/gh/yarn)
9
+ * - Never delays or blocks the CLI command
10
+ */
11
+
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import * as os from 'node:os';
15
+ import chalk from 'chalk';
16
+ import { PKG_VERSION } from './version.js';
17
+
18
+ const CACHE_DIR = path.join(os.homedir(), '.opencli');
19
+ const CACHE_FILE = path.join(CACHE_DIR, 'update-check.json');
20
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h
21
+ const NPM_REGISTRY_URL = 'https://registry.npmjs.org/@jackwener/opencli/latest';
22
+
23
+ interface UpdateCache {
24
+ lastCheck: number;
25
+ latestVersion: string;
26
+ }
27
+
28
+ // Read cache once at module load — shared by both exported functions
29
+ const _cache: UpdateCache | null = (() => {
30
+ try {
31
+ return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8')) as UpdateCache;
32
+ } catch {
33
+ return null;
34
+ }
35
+ })();
36
+
37
+ function writeCache(latestVersion: string): void {
38
+ try {
39
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
40
+ fs.writeFileSync(CACHE_FILE, JSON.stringify({ lastCheck: Date.now(), latestVersion }), 'utf-8');
41
+ } catch {
42
+ // Best-effort; never fail
43
+ }
44
+ }
45
+
46
+ /** Compare semver strings. Returns true if `a` is strictly newer than `b`. */
47
+ function isNewer(a: string, b: string): boolean {
48
+ const parse = (v: string) => v.replace(/^v/, '').split('-')[0].split('.').map(Number);
49
+ const pa = parse(a);
50
+ const pb = parse(b);
51
+ if (pa.some(isNaN) || pb.some(isNaN)) return false;
52
+ const [aMaj, aMin, aPat] = pa;
53
+ const [bMaj, bMin, bPat] = pb;
54
+ if (aMaj !== bMaj) return aMaj > bMaj;
55
+ if (aMin !== bMin) return aMin > bMin;
56
+ return aPat > bPat;
57
+ }
58
+
59
+ function isCI(): boolean {
60
+ return !!(process.env.CI || process.env.CONTINUOUS_INTEGRATION);
61
+ }
62
+
63
+ /**
64
+ * Register a process exit hook that prints an update notice if a newer
65
+ * version was found on the last background check.
66
+ * Notice appears after command output — same pattern as npm/gh/yarn.
67
+ * Skipped during --get-completions to avoid polluting shell completion output.
68
+ */
69
+ export function registerUpdateNoticeOnExit(): void {
70
+ if (isCI()) return;
71
+ if (process.argv.includes('--get-completions')) return;
72
+
73
+ process.on('exit', (code) => {
74
+ if (code !== 0) return; // Don't show update notice on error exit
75
+ if (!_cache) return;
76
+ if (!isNewer(_cache.latestVersion, PKG_VERSION)) return;
77
+ try {
78
+ process.stderr.write(
79
+ chalk.yellow(`\n Update available: v${PKG_VERSION} → v${_cache.latestVersion}\n`) +
80
+ chalk.dim(` Run: npm install -g @jackwener/opencli\n\n`),
81
+ );
82
+ } catch {
83
+ // Ignore broken pipe (stderr closed before process exits)
84
+ }
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Kick off a background fetch to npm registry. Writes to cache for next run.
90
+ * Fully non-blocking — never awaited.
91
+ */
92
+ export function checkForUpdateBackground(): void {
93
+ if (isCI()) return;
94
+ if (_cache && Date.now() - _cache.lastCheck < CHECK_INTERVAL_MS) return;
95
+
96
+ void (async () => {
97
+ try {
98
+ const controller = new AbortController();
99
+ const timer = setTimeout(() => controller.abort(), 3000);
100
+ const res = await fetch(NPM_REGISTRY_URL, {
101
+ signal: controller.signal,
102
+ headers: { 'User-Agent': `opencli/${PKG_VERSION}` },
103
+ });
104
+ clearTimeout(timer);
105
+ if (!res.ok) return;
106
+ const data = await res.json() as { version?: string };
107
+ if (typeof data.version === 'string') {
108
+ writeCache(data.version);
109
+ }
110
+ } catch {
111
+ // Network error: silently skip, try again next run
112
+ }
113
+ })();
114
+ }
@@ -0,0 +1,64 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ async function loadModule() {
4
+ return import('./clis/weixin/download.js');
5
+ }
6
+
7
+ describe('weixin publish time extraction', () => {
8
+ it('prefers publish_time text over create_time-like date strings', async () => {
9
+ const mod = await loadModule();
10
+
11
+ expect(mod.extractWechatPublishTime(
12
+ '2026年3月24日 22:38',
13
+ 'var create_time = "2026年3月24日 22:38";',
14
+ )).toBe('2026年3月24日 22:38');
15
+ });
16
+
17
+ it('falls back to unix timestamp create_time values', async () => {
18
+ const mod = await loadModule();
19
+
20
+ expect(mod.extractWechatPublishTime(
21
+ '',
22
+ 'var create_time = "1711291080";',
23
+ )).toBe('2024-03-24 22:38:00');
24
+ });
25
+
26
+ it('rejects malformed create_time values', async () => {
27
+ const mod = await loadModule();
28
+
29
+ expect(mod.extractWechatPublishTime(
30
+ '',
31
+ 'var create_time = "2026年3月24日 22:38";',
32
+ )).toBe('');
33
+ expect(mod.extractWechatPublishTime(
34
+ '',
35
+ 'var create_time = "1711291080abc";',
36
+ )).toBe('');
37
+ expect(mod.extractWechatPublishTime(
38
+ '',
39
+ 'var create_time = "17112910800";',
40
+ )).toBe('');
41
+ });
42
+
43
+ it('builds a self-contained browser helper that matches fallback behavior', async () => {
44
+ const mod = await loadModule();
45
+
46
+ const extractInPage = eval(mod.buildExtractWechatPublishTimeJs()) as (publishTimeText: string, htmlStr: string) => string;
47
+
48
+ expect(extractInPage(
49
+ '',
50
+ 'var create_time = "1711291080";',
51
+ )).toBe('2024-03-24 22:38:00');
52
+ });
53
+
54
+ it('browser helper still prefers DOM publish_time text', async () => {
55
+ const mod = await loadModule();
56
+
57
+ const extractInPage = eval(mod.buildExtractWechatPublishTimeJs()) as (publishTimeText: string, htmlStr: string) => string;
58
+
59
+ expect(extractInPage(
60
+ '2026年3月24日 22:38',
61
+ 'var create_time = "1711291080";',
62
+ )).toBe('2026年3月24日 22:38');
63
+ });
64
+ });
@@ -0,0 +1,150 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { getRegistry } from './registry.js';
3
+ import { fetchPrivateApi } from './clis/weread/utils.js';
4
+ import './clis/weread/shelf.js';
5
+
6
+ describe('weread private API regression', () => {
7
+ beforeEach(() => {
8
+ vi.restoreAllMocks();
9
+ });
10
+
11
+ it('uses browser cookies and Node fetch for private API requests', async () => {
12
+ const mockPage = {
13
+ getCookies: vi.fn()
14
+ .mockResolvedValueOnce([
15
+ { name: 'wr_name', value: 'alice', domain: 'weread.qq.com' },
16
+ { name: 'wr_vid', value: 'vid123', domain: 'i.weread.qq.com' },
17
+ ]),
18
+ evaluate: vi.fn(),
19
+ } as any;
20
+
21
+ const fetchMock = vi.fn().mockResolvedValue({
22
+ ok: true,
23
+ status: 200,
24
+ json: () => Promise.resolve({ title: 'Test Book', errcode: 0 }),
25
+ });
26
+ vi.stubGlobal('fetch', fetchMock);
27
+
28
+ const result = await fetchPrivateApi(mockPage, '/book/info', { bookId: '123' });
29
+
30
+ expect(result.title).toBe('Test Book');
31
+ expect(mockPage.getCookies).toHaveBeenCalledTimes(1);
32
+ expect(mockPage.getCookies).toHaveBeenCalledWith({ url: 'https://i.weread.qq.com/book/info?bookId=123' });
33
+ expect(mockPage.evaluate).not.toHaveBeenCalled();
34
+ expect(fetchMock).toHaveBeenCalledWith(
35
+ 'https://i.weread.qq.com/book/info?bookId=123',
36
+ expect.objectContaining({
37
+ headers: expect.objectContaining({
38
+ Cookie: 'wr_name=alice; wr_vid=vid123',
39
+ }),
40
+ }),
41
+ );
42
+ });
43
+
44
+ it('maps unauthenticated private API responses to AUTH_REQUIRED', async () => {
45
+ const mockPage = {
46
+ getCookies: vi.fn().mockResolvedValue([]),
47
+ evaluate: vi.fn(),
48
+ } as any;
49
+
50
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
51
+ ok: false,
52
+ status: 401,
53
+ json: () => Promise.resolve({ errcode: -2010, errmsg: '用户不存在' }),
54
+ }));
55
+
56
+ await expect(fetchPrivateApi(mockPage, '/book/info')).rejects.toThrow('Not logged in');
57
+ });
58
+
59
+ it('maps non-auth API errors to API_ERROR', async () => {
60
+ const mockPage = {
61
+ getCookies: vi.fn().mockResolvedValue([]),
62
+ evaluate: vi.fn(),
63
+ } as any;
64
+
65
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
66
+ ok: true,
67
+ status: 200,
68
+ json: () => Promise.resolve({ errcode: -1, errmsg: 'unknown error' }),
69
+ }));
70
+
71
+ await expect(fetchPrivateApi(mockPage, '/book/info')).rejects.toThrow('unknown error');
72
+ });
73
+
74
+ it('maps non-401 HTTP failures to FETCH_ERROR', async () => {
75
+ const mockPage = {
76
+ getCookies: vi.fn().mockResolvedValue([]),
77
+ evaluate: vi.fn(),
78
+ } as any;
79
+
80
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
81
+ ok: false,
82
+ status: 403,
83
+ json: () => Promise.resolve({ errmsg: 'forbidden' }),
84
+ }));
85
+
86
+ await expect(fetchPrivateApi(mockPage, '/book/info')).rejects.toThrow('HTTP 403');
87
+ });
88
+
89
+ it('maps invalid JSON to PARSE_ERROR', async () => {
90
+ const mockPage = {
91
+ getCookies: vi.fn().mockResolvedValue([]),
92
+ evaluate: vi.fn(),
93
+ } as any;
94
+
95
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
96
+ ok: true,
97
+ status: 200,
98
+ json: () => Promise.reject(new SyntaxError('Unexpected token <')),
99
+ }));
100
+
101
+ await expect(fetchPrivateApi(mockPage, '/book/info')).rejects.toThrow('Invalid JSON');
102
+ });
103
+
104
+ it('routes weread shelf through the private API helper path', async () => {
105
+ const command = getRegistry().get('weread/shelf');
106
+ expect(command?.func).toBeTypeOf('function');
107
+
108
+ const mockPage = {
109
+ getCookies: vi.fn()
110
+ .mockResolvedValueOnce([
111
+ { name: 'wr_name', value: 'alice', domain: 'weread.qq.com' },
112
+ { name: 'wr_vid', value: 'vid123', domain: 'i.weread.qq.com' },
113
+ ]),
114
+ evaluate: vi.fn(),
115
+ } as any;
116
+
117
+ const fetchMock = vi.fn().mockResolvedValue({
118
+ ok: true,
119
+ status: 200,
120
+ json: () => Promise.resolve({
121
+ books: [{
122
+ title: 'Deep Work',
123
+ author: 'Cal Newport',
124
+ readingProgress: 42,
125
+ bookId: 'abc123',
126
+ }],
127
+ }),
128
+ });
129
+ vi.stubGlobal('fetch', fetchMock);
130
+
131
+ const result = await command!.func!(mockPage, { limit: 1 });
132
+
133
+ expect(mockPage.evaluate).not.toHaveBeenCalled();
134
+ expect(fetchMock).toHaveBeenCalledWith(
135
+ 'https://i.weread.qq.com/shelf/sync?synckey=0&lectureSynckey=0',
136
+ expect.any(Object),
137
+ );
138
+ expect(mockPage.getCookies).toHaveBeenCalledWith({
139
+ url: 'https://i.weread.qq.com/shelf/sync?synckey=0&lectureSynckey=0',
140
+ });
141
+ expect(result).toEqual([
142
+ {
143
+ title: 'Deep Work',
144
+ author: 'Cal Newport',
145
+ progress: '42%',
146
+ bookId: 'abc123',
147
+ },
148
+ ]);
149
+ });
150
+ });
@@ -0,0 +1,44 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { getRegistry } from './registry.js';
3
+ import './clis/weread/search.js';
4
+
5
+ describe('weread/search regression', () => {
6
+ beforeEach(() => {
7
+ vi.restoreAllMocks();
8
+ });
9
+
10
+ it('uses the query argument for the search API and returns urls', async () => {
11
+ const command = getRegistry().get('weread/search');
12
+ expect(command?.func).toBeTypeOf('function');
13
+
14
+ const fetchMock = vi.fn().mockResolvedValue({
15
+ ok: true,
16
+ json: () => Promise.resolve({
17
+ books: [
18
+ {
19
+ bookInfo: {
20
+ title: 'Deep Work',
21
+ author: 'Cal Newport',
22
+ bookId: 'abc123',
23
+ },
24
+ },
25
+ ],
26
+ }),
27
+ });
28
+ vi.stubGlobal('fetch', fetchMock);
29
+
30
+ const result = await command!.func!(null as any, { query: 'deep work', limit: 5 });
31
+
32
+ expect(fetchMock).toHaveBeenCalledTimes(1);
33
+ expect(String(fetchMock.mock.calls[0][0])).toContain('keyword=deep+work');
34
+ expect(result).toEqual([
35
+ {
36
+ rank: 1,
37
+ title: 'Deep Work',
38
+ author: 'Cal Newport',
39
+ bookId: 'abc123',
40
+ url: 'https://weread.qq.com/web/bookDetail/abc123',
41
+ },
42
+ ]);
43
+ });
44
+ });
@@ -26,3 +26,23 @@ export interface YamlCliDefinition {
26
26
  timeout?: number;
27
27
  navigateBefore?: boolean | string;
28
28
  }
29
+
30
+ import type { Arg } from './registry.js';
31
+
32
+ /** Convert YAML args definition to the internal Arg[] format. */
33
+ export function parseYamlArgs(args: Record<string, YamlArgDefinition> | undefined): Arg[] {
34
+ if (!args || typeof args !== 'object') return [];
35
+ const result: Arg[] = [];
36
+ for (const [argName, argDef] of Object.entries(args)) {
37
+ result.push({
38
+ name: argName,
39
+ type: argDef?.type ?? 'str',
40
+ default: argDef?.default,
41
+ required: argDef?.required ?? false,
42
+ positional: argDef?.positional ?? false,
43
+ help: argDef?.description ?? argDef?.help ?? '',
44
+ choices: argDef?.choices,
45
+ });
46
+ }
47
+ return result;
48
+ }
@@ -80,30 +80,34 @@ describe('login-required commands — graceful failure', () => {
80
80
  }, 60_000);
81
81
 
82
82
  // ── linux-do (requires login — all endpoints need authentication) ──
83
- it('linux-do hot fails gracefully without login', async () => {
84
- await expectGracefulAuthFailure(['linux-do', 'hot', '--limit', '3', '-f', 'json'], 'linux-do hot');
85
- }, 60_000);
86
-
87
- it('linux-do latest fails gracefully without login', async () => {
88
- await expectGracefulAuthFailure(['linux-do', 'latest', '--limit', '3', '-f', 'json'], 'linux-do latest');
83
+ it('linux-do feed fails gracefully without login', async () => {
84
+ await expectGracefulAuthFailure(['linux-do', 'feed', '--limit', '3', '-f', 'json'], 'linux-do feed');
89
85
  }, 60_000);
90
86
 
91
87
  it('linux-do categories fails gracefully without login', async () => {
92
88
  await expectGracefulAuthFailure(['linux-do', 'categories', '--limit', '3', '-f', 'json'], 'linux-do categories');
93
89
  }, 60_000);
94
90
 
95
- it('linux-do category fails gracefully without login', async () => {
96
- await expectGracefulAuthFailure(['linux-do', 'category', '--slug', 'general', '--id', '1', '--limit', '3', '-f', 'json'], 'linux-do category');
91
+ it('linux-do tags fails gracefully without login', async () => {
92
+ await expectGracefulAuthFailure(['linux-do', 'tags', '--limit', '3', '-f', 'json'], 'linux-do tags');
97
93
  }, 60_000);
98
94
 
99
95
  it('linux-do topic fails gracefully without login', async () => {
100
- await expectGracefulAuthFailure(['linux-do', 'topic', '--id', '1', '-f', 'json'], 'linux-do topic');
96
+ await expectGracefulAuthFailure(['linux-do', 'topic', '1', '-f', 'json'], 'linux-do topic');
101
97
  }, 60_000);
102
98
 
103
99
  it('linux-do search fails gracefully without login', async () => {
104
100
  await expectGracefulAuthFailure(['linux-do', 'search', 'test', '--limit', '3', '-f', 'json'], 'linux-do search');
105
101
  }, 60_000);
106
102
 
103
+ it('linux-do user-topics fails gracefully without login', async () => {
104
+ await expectGracefulAuthFailure(['linux-do', 'user-topics', 'test', '--limit', '3', '-f', 'json'], 'linux-do user-topics');
105
+ }, 60_000);
106
+
107
+ it('linux-do user-posts fails gracefully without login', async () => {
108
+ await expectGracefulAuthFailure(['linux-do', 'user-posts', 'test', '--limit', '3', '-f', 'json'], 'linux-do user-posts');
109
+ }, 60_000);
110
+
107
111
  // ── xiaohongshu (requires login) ──
108
112
  it('xiaohongshu feed fails gracefully without login', async () => {
109
113
  await expectGracefulAuthFailure(['xiaohongshu', 'feed', '--limit', '3', '-f', 'json'], 'xiaohongshu feed');
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Extended E2E tests for all other browser commands.
3
+ * Opt-in only: OPENCLI_E2E=1 npx vitest run
4
+ */
5
+
6
+ import { describe, it, expect } from 'vitest';
7
+ import { runCli, parseJsonOutput } from './helpers.js';
8
+
9
+ async function tryBrowserCommand(args: string[]): Promise<any[] | null> {
10
+ const { stdout, code } = await runCli(args, { timeout: 60_000 });
11
+ if (code !== 0) return null;
12
+ try {
13
+ const data = parseJsonOutput(stdout);
14
+ return Array.isArray(data) ? data : null;
15
+ } catch {
16
+ return null;
17
+ }
18
+ }
19
+
20
+ function expectDataOrSkip(data: any[] | null, label: string) {
21
+ if (data === null || data.length === 0) {
22
+ console.warn(`${label}: skipped — no data returned (likely bot detection or geo-blocking)`);
23
+ return;
24
+ }
25
+ expect(data.length).toBeGreaterThanOrEqual(1);
26
+ }
27
+
28
+ describe('browser extended public-data commands E2E', () => {
29
+
30
+ // ── bbc ──
31
+ it('bbc news returns headlines', async () => {
32
+ const data = await tryBrowserCommand(['bbc', 'news', '--limit', '3', '-f', 'json']);
33
+ expectDataOrSkip(data, 'bbc news');
34
+ if (data) {
35
+ expect(data[0]).toHaveProperty('title');
36
+ }
37
+ }, 60_000);
38
+
39
+ // ── bloomberg ──
40
+ it('bloomberg news returns article detail when the article page is accessible', async () => {
41
+ const feedResult = await runCli(['bloomberg', 'tech', '--limit', '1', '-f', 'json']);
42
+ if (feedResult.code !== 0) {
43
+ console.warn('bloomberg news: skipped — could not load Bloomberg tech feed');
44
+ return;
45
+ }
46
+
47
+ const feedItems = parseJsonOutput(feedResult.stdout);
48
+ const link = Array.isArray(feedItems) ? feedItems[0]?.link : null;
49
+ if (!link) {
50
+ console.warn('bloomberg news: skipped — tech feed returned no link');
51
+ return;
52
+ }
53
+
54
+ const data = await tryBrowserCommand(['bloomberg', 'news', link, '-f', 'json']);
55
+ expectDataOrSkip(data, 'bloomberg news');
56
+ if (data) {
57
+ expect(data[0]).toHaveProperty('title');
58
+ expect(data[0]).toHaveProperty('summary');
59
+ expect(data[0]).toHaveProperty('link');
60
+ expect(data[0]).toHaveProperty('mediaLinks');
61
+ expect(data[0]).toHaveProperty('content');
62
+ }
63
+ }, 60_000);
64
+
65
+ // ── weibo ──
66
+ it('weibo hot returns trending topics', async () => {
67
+ const data = await tryBrowserCommand(['weibo', 'hot', '--limit', '5', '-f', 'json']);
68
+ expectDataOrSkip(data, 'weibo hot');
69
+ }, 60_000);
70
+
71
+ it('weibo search returns results', async () => {
72
+ const data = await tryBrowserCommand(['weibo', 'search', 'openai', '--limit', '3', '-f', 'json']);
73
+ expectDataOrSkip(data, 'weibo search');
74
+ }, 60_000);
75
+
76
+ // ── reddit ──
77
+ it('reddit hot returns posts', async () => {
78
+ const data = await tryBrowserCommand(['reddit', 'hot', '--limit', '5', '-f', 'json']);
79
+ expectDataOrSkip(data, 'reddit hot');
80
+ }, 60_000);
81
+
82
+ it('reddit frontpage returns posts', async () => {
83
+ const data = await tryBrowserCommand(['reddit', 'frontpage', '--limit', '5', '-f', 'json']);
84
+ expectDataOrSkip(data, 'reddit frontpage');
85
+ }, 60_000);
86
+
87
+ // ── twitter ──
88
+ it('twitter trending returns trends', async () => {
89
+ const data = await tryBrowserCommand(['twitter', 'trending', '--limit', '5', '-f', 'json']);
90
+ expectDataOrSkip(data, 'twitter trending');
91
+ }, 60_000);
92
+
93
+ // ── xueqiu ──
94
+ it('xueqiu hot returns hot posts', async () => {
95
+ const data = await tryBrowserCommand(['xueqiu', 'hot', '--limit', '5', '-f', 'json']);
96
+ expectDataOrSkip(data, 'xueqiu hot');
97
+ }, 60_000);
98
+
99
+ it('xueqiu hot-stock returns stocks', async () => {
100
+ const data = await tryBrowserCommand(['xueqiu', 'hot-stock', '--limit', '5', '-f', 'json']);
101
+ expectDataOrSkip(data, 'xueqiu hot-stock');
102
+ }, 60_000);
103
+
104
+ // ── reuters ──
105
+ it('reuters search returns articles', async () => {
106
+ const data = await tryBrowserCommand(['reuters', 'search', 'technology', '--limit', '3', '-f', 'json']);
107
+ expectDataOrSkip(data, 'reuters search');
108
+ }, 60_000);
109
+
110
+ // ── youtube ──
111
+ it('youtube search returns videos', async () => {
112
+ const data = await tryBrowserCommand(['youtube', 'search', 'typescript tutorial', '--limit', '3', '-f', 'json']);
113
+ expectDataOrSkip(data, 'youtube search');
114
+ }, 60_000);
115
+
116
+ // ── smzdm ──
117
+ it('smzdm search returns deals', async () => {
118
+ const data = await tryBrowserCommand(['smzdm', 'search', '键盘', '--limit', '3', '-f', 'json']);
119
+ expectDataOrSkip(data, 'smzdm search');
120
+ }, 60_000);
121
+
122
+ // ── boss ──
123
+ it('boss search returns jobs', async () => {
124
+ const data = await tryBrowserCommand(['boss', 'search', 'golang', '--limit', '3', '-f', 'json']);
125
+ expectDataOrSkip(data, 'boss search');
126
+ }, 60_000);
127
+
128
+ // ── ctrip ──
129
+ it('ctrip search returns flights', async () => {
130
+ const data = await tryBrowserCommand(['ctrip', 'search', '-f', 'json']);
131
+ expectDataOrSkip(data, 'ctrip search');
132
+ }, 60_000);
133
+
134
+ // ── coupang ──
135
+ it('coupang search returns products', async () => {
136
+ const data = await tryBrowserCommand(['coupang', 'search', 'laptop', '--limit', '3', '-f', 'json']);
137
+ expectDataOrSkip(data, 'coupang search');
138
+ }, 60_000);
139
+
140
+ // ── xiaohongshu ──
141
+ it('xiaohongshu search returns notes', async () => {
142
+ const data = await tryBrowserCommand(['xiaohongshu', 'search', '美食', '--limit', '3', '-f', 'json']);
143
+ expectDataOrSkip(data, 'xiaohongshu search');
144
+ }, 60_000);
145
+
146
+ // ── google ──
147
+ it('google search returns results', async () => {
148
+ const data = await tryBrowserCommand(['google', 'search', 'typescript', '--limit', '5', '-f', 'json']);
149
+ expectDataOrSkip(data, 'google search');
150
+ if (data) {
151
+ expect(data[0]).toHaveProperty('type');
152
+ expect(data[0]).toHaveProperty('title');
153
+ expect(data[0]).toHaveProperty('url');
154
+ }
155
+ }, 60_000);
156
+
157
+ // ── yahoo-finance ──
158
+ it('yahoo-finance quote returns stock data', async () => {
159
+ const data = await tryBrowserCommand(['yahoo-finance', 'quote', 'AAPL', '-f', 'json']);
160
+ expectDataOrSkip(data, 'yahoo-finance quote');
161
+ }, 60_000);
162
+ });