@jackwener/opencli 1.3.3 → 1.4.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 (680) hide show
  1. package/.github/actions/setup-chrome/action.yml +5 -4
  2. package/.github/pull_request_template.md +3 -1
  3. package/.github/workflows/build-extension.yml +7 -1
  4. package/.github/workflows/ci.yml +46 -6
  5. package/.github/workflows/docs.yml +1 -1
  6. package/.github/workflows/e2e-headed.yml +36 -3
  7. package/.github/workflows/release.yml +1 -1
  8. package/.github/workflows/security.yml +0 -3
  9. package/CHANGELOG.md +78 -0
  10. package/CONTRIBUTING.md +6 -3
  11. package/PRIVACY.md +57 -0
  12. package/README.md +31 -4
  13. package/README.zh-CN.md +31 -4
  14. package/SKILL.md +107 -2
  15. package/TESTING.md +1 -0
  16. package/chatwise-opencli.ps1 +82 -0
  17. package/dist/analysis.d.ts +38 -0
  18. package/dist/analysis.js +166 -0
  19. package/dist/browser/cdp.d.ts +0 -4
  20. package/dist/browser/cdp.js +53 -41
  21. package/dist/browser/cdp.test.d.ts +1 -0
  22. package/dist/browser/cdp.test.js +52 -0
  23. package/dist/browser/dom-snapshot.d.ts +2 -2
  24. package/dist/browser/dom-snapshot.js +54 -1
  25. package/dist/browser/dom-snapshot.test.js +36 -0
  26. package/dist/browser/index.d.ts +2 -2
  27. package/dist/browser/index.js +1 -1
  28. package/dist/browser/mcp.d.ts +0 -2
  29. package/dist/browser/mcp.js +2 -3
  30. package/dist/browser/page.d.ts +4 -3
  31. package/dist/browser/page.js +34 -37
  32. package/dist/browser/stealth.d.ts +0 -2
  33. package/dist/browser/stealth.js +24 -9
  34. package/dist/browser.test.js +2 -2
  35. package/dist/build-manifest.js +15 -9
  36. package/dist/build-manifest.test.js +12 -0
  37. package/dist/cascade.js +4 -2
  38. package/dist/cli-manifest.json +1325 -256
  39. package/dist/cli.js +57 -29
  40. package/dist/clis/_shared/desktop-commands.d.ts +22 -0
  41. package/dist/clis/_shared/desktop-commands.js +108 -0
  42. package/dist/clis/antigravity/serve.js +5 -2
  43. package/dist/clis/apple-podcasts/search.js +2 -1
  44. package/dist/clis/arxiv/search.js +3 -3
  45. package/dist/clis/bbc/news.js +0 -1
  46. package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
  47. package/dist/clis/bilibili/dynamic.test.js +68 -0
  48. package/dist/clis/bilibili/favorite.js +4 -2
  49. package/dist/clis/bilibili/following.js +3 -2
  50. package/dist/clis/bilibili/subtitle.js +8 -7
  51. package/dist/clis/bilibili/utils.js +2 -2
  52. package/dist/clis/boss/batchgreet.js +1 -1
  53. package/dist/clis/boss/chatlist.js +1 -1
  54. package/dist/clis/boss/chatmsg.js +1 -1
  55. package/dist/clis/boss/detail.js +1 -1
  56. package/dist/clis/boss/exchange.js +1 -1
  57. package/dist/clis/boss/greet.js +1 -1
  58. package/dist/clis/boss/invite.js +1 -1
  59. package/dist/clis/boss/joblist.js +1 -1
  60. package/dist/clis/boss/mark.js +4 -3
  61. package/dist/clis/boss/recommend.js +1 -1
  62. package/dist/clis/boss/resume.js +1 -1
  63. package/dist/clis/boss/search.js +1 -1
  64. package/dist/clis/boss/send.js +5 -4
  65. package/dist/clis/boss/stats.js +1 -1
  66. package/dist/clis/chatgpt/ask.js +4 -0
  67. package/dist/clis/chatgpt/new.js +5 -1
  68. package/dist/clis/chatgpt/read.js +5 -1
  69. package/dist/clis/chatgpt/send.js +2 -1
  70. package/dist/clis/chatgpt/status.js +5 -1
  71. package/dist/clis/chatwise/ask.js +8 -2
  72. package/dist/clis/chatwise/export.js +2 -0
  73. package/dist/clis/chatwise/history.js +2 -0
  74. package/dist/clis/chatwise/model.js +8 -3
  75. package/dist/clis/chatwise/new.js +3 -18
  76. package/dist/clis/chatwise/read.js +2 -0
  77. package/dist/clis/chatwise/screenshot.js +3 -27
  78. package/dist/clis/chatwise/send.js +8 -2
  79. package/dist/clis/chatwise/shared.d.ts +2 -0
  80. package/dist/clis/chatwise/shared.js +6 -0
  81. package/dist/clis/chatwise/status.js +3 -22
  82. package/dist/clis/codex/ask.js +6 -2
  83. package/dist/clis/codex/dump.js +2 -25
  84. package/dist/clis/codex/new.js +2 -25
  85. package/dist/clis/codex/screenshot.js +2 -27
  86. package/dist/clis/codex/send.js +6 -4
  87. package/dist/clis/codex/status.js +2 -22
  88. package/dist/clis/ctrip/search.js +0 -1
  89. package/dist/clis/cursor/ask.js +2 -1
  90. package/dist/clis/cursor/composer.js +2 -1
  91. package/dist/clis/cursor/dump.js +2 -25
  92. package/dist/clis/cursor/new.js +2 -18
  93. package/dist/clis/cursor/read.js +2 -1
  94. package/dist/clis/cursor/screenshot.js +1 -30
  95. package/dist/clis/cursor/send.js +2 -1
  96. package/dist/clis/cursor/status.js +2 -21
  97. package/dist/clis/dictionary/examples.yaml +25 -0
  98. package/dist/clis/dictionary/search.yaml +27 -0
  99. package/dist/clis/dictionary/synonyms.yaml +25 -0
  100. package/dist/clis/douban/book-hot.js +1 -1
  101. package/dist/clis/douban/movie-hot.js +1 -1
  102. package/dist/clis/douban/search.js +1 -1
  103. package/dist/clis/douban/utils.d.ts +4 -1
  104. package/dist/clis/douban/utils.js +156 -1
  105. package/dist/clis/doubao/ask.js +1 -1
  106. package/dist/clis/doubao/new.js +1 -1
  107. package/dist/clis/doubao/read.js +1 -1
  108. package/dist/clis/doubao/send.js +1 -1
  109. package/dist/clis/doubao/status.js +1 -1
  110. package/dist/clis/doubao-app/ask.js +1 -1
  111. package/dist/clis/doubao-app/new.js +1 -1
  112. package/dist/clis/doubao-app/read.js +1 -1
  113. package/dist/clis/doubao-app/send.js +1 -1
  114. package/dist/clis/douyin/_shared/browser-fetch.d.ts +10 -0
  115. package/dist/clis/douyin/_shared/browser-fetch.js +30 -0
  116. package/dist/clis/douyin/_shared/browser-fetch.test.d.ts +1 -0
  117. package/dist/clis/douyin/_shared/browser-fetch.test.js +31 -0
  118. package/dist/clis/douyin/_shared/creation-id.d.ts +1 -0
  119. package/dist/clis/douyin/_shared/creation-id.js +5 -0
  120. package/dist/clis/douyin/_shared/creation-id.test.d.ts +1 -0
  121. package/dist/clis/douyin/_shared/creation-id.test.js +22 -0
  122. package/dist/clis/douyin/_shared/imagex-upload.d.ts +20 -0
  123. package/dist/clis/douyin/_shared/imagex-upload.js +53 -0
  124. package/dist/clis/douyin/_shared/imagex-upload.test.d.ts +1 -0
  125. package/dist/clis/douyin/_shared/imagex-upload.test.js +87 -0
  126. package/dist/clis/douyin/_shared/sts2.d.ts +8 -0
  127. package/dist/clis/douyin/_shared/sts2.js +15 -0
  128. package/dist/clis/douyin/_shared/text-extra.d.ts +18 -0
  129. package/dist/clis/douyin/_shared/text-extra.js +15 -0
  130. package/dist/clis/douyin/_shared/text-extra.test.d.ts +1 -0
  131. package/dist/clis/douyin/_shared/text-extra.test.js +37 -0
  132. package/dist/clis/douyin/_shared/timing.d.ts +2 -0
  133. package/dist/clis/douyin/_shared/timing.js +22 -0
  134. package/dist/clis/douyin/_shared/timing.test.d.ts +1 -0
  135. package/dist/clis/douyin/_shared/timing.test.js +28 -0
  136. package/dist/clis/douyin/_shared/tos-upload-short-read.test.d.ts +11 -0
  137. package/dist/clis/douyin/_shared/tos-upload-short-read.test.js +83 -0
  138. package/dist/clis/douyin/_shared/tos-upload.d.ts +53 -0
  139. package/dist/clis/douyin/_shared/tos-upload.js +295 -0
  140. package/dist/clis/douyin/_shared/tos-upload.test.d.ts +1 -0
  141. package/dist/clis/douyin/_shared/tos-upload.test.js +229 -0
  142. package/dist/clis/douyin/_shared/transcode.d.ts +27 -0
  143. package/dist/clis/douyin/_shared/transcode.js +45 -0
  144. package/dist/clis/douyin/_shared/transcode.test.d.ts +1 -0
  145. package/dist/clis/douyin/_shared/transcode.test.js +93 -0
  146. package/dist/clis/douyin/_shared/types.d.ts +26 -0
  147. package/dist/clis/douyin/_shared/types.js +1 -0
  148. package/dist/clis/douyin/activities.d.ts +1 -0
  149. package/dist/clis/douyin/activities.js +20 -0
  150. package/dist/clis/douyin/activities.test.d.ts +1 -0
  151. package/dist/clis/douyin/activities.test.js +22 -0
  152. package/dist/clis/douyin/collections.d.ts +1 -0
  153. package/dist/clis/douyin/collections.js +22 -0
  154. package/dist/clis/douyin/collections.test.d.ts +1 -0
  155. package/dist/clis/douyin/collections.test.js +23 -0
  156. package/dist/clis/douyin/delete.d.ts +1 -0
  157. package/dist/clis/douyin/delete.js +18 -0
  158. package/dist/clis/douyin/delete.test.d.ts +1 -0
  159. package/dist/clis/douyin/delete.test.js +11 -0
  160. package/dist/clis/douyin/draft.d.ts +14 -0
  161. package/dist/clis/douyin/draft.js +237 -0
  162. package/dist/clis/douyin/draft.test.d.ts +1 -0
  163. package/dist/clis/douyin/draft.test.js +11 -0
  164. package/dist/clis/douyin/drafts.d.ts +1 -0
  165. package/dist/clis/douyin/drafts.js +23 -0
  166. package/dist/clis/douyin/drafts.test.d.ts +1 -0
  167. package/dist/clis/douyin/drafts.test.js +11 -0
  168. package/dist/clis/douyin/hashtag.d.ts +1 -0
  169. package/dist/clis/douyin/hashtag.js +45 -0
  170. package/dist/clis/douyin/hashtag.test.d.ts +1 -0
  171. package/dist/clis/douyin/hashtag.test.js +25 -0
  172. package/dist/clis/douyin/location.d.ts +1 -0
  173. package/dist/clis/douyin/location.js +24 -0
  174. package/dist/clis/douyin/location.test.d.ts +1 -0
  175. package/dist/clis/douyin/location.test.js +23 -0
  176. package/dist/clis/douyin/profile.d.ts +1 -0
  177. package/dist/clis/douyin/profile.js +28 -0
  178. package/dist/clis/douyin/profile.test.d.ts +1 -0
  179. package/dist/clis/douyin/profile.test.js +11 -0
  180. package/dist/clis/douyin/publish.d.ts +14 -0
  181. package/dist/clis/douyin/publish.js +288 -0
  182. package/dist/clis/douyin/publish.test.d.ts +1 -0
  183. package/dist/clis/douyin/publish.test.js +38 -0
  184. package/dist/clis/douyin/stats.d.ts +1 -0
  185. package/dist/clis/douyin/stats.js +27 -0
  186. package/dist/clis/douyin/stats.test.d.ts +1 -0
  187. package/dist/clis/douyin/stats.test.js +22 -0
  188. package/dist/clis/douyin/update.d.ts +1 -0
  189. package/dist/clis/douyin/update.js +31 -0
  190. package/dist/clis/douyin/update.test.d.ts +1 -0
  191. package/dist/clis/douyin/update.test.js +11 -0
  192. package/dist/clis/douyin/videos.d.ts +1 -0
  193. package/dist/clis/douyin/videos.js +34 -0
  194. package/dist/clis/douyin/videos.test.d.ts +1 -0
  195. package/dist/clis/douyin/videos.test.js +11 -0
  196. package/dist/clis/grok/ask.d.ts +4 -0
  197. package/dist/clis/grok/ask.js +28 -10
  198. package/dist/clis/grok/ask.test.js +18 -0
  199. package/dist/clis/hackernews/search.yaml +1 -1
  200. package/dist/clis/instagram/search.yaml +2 -1
  201. package/dist/clis/jd/item.d.ts +1 -0
  202. package/dist/clis/jd/item.js +96 -0
  203. package/dist/clis/jd/item.test.d.ts +1 -0
  204. package/dist/clis/jd/item.test.js +28 -0
  205. package/dist/clis/jike/feed.js +1 -1
  206. package/dist/clis/jike/search.js +1 -1
  207. package/dist/clis/linkedin/search.js +5 -4
  208. package/dist/clis/linkedin/timeline.d.ts +21 -0
  209. package/dist/clis/linkedin/timeline.js +503 -0
  210. package/dist/clis/linkedin/timeline.test.d.ts +1 -0
  211. package/dist/clis/linkedin/timeline.test.js +81 -0
  212. package/dist/clis/linux-do/search.yaml +3 -1
  213. package/dist/clis/medium/feed.js +1 -1
  214. package/dist/clis/medium/search.js +2 -2
  215. package/dist/clis/medium/user.js +1 -1
  216. package/dist/clis/medium/{shared.js → utils.js} +2 -1
  217. package/dist/clis/pixiv/detail.yaml +49 -0
  218. package/dist/clis/pixiv/download.d.ts +7 -0
  219. package/dist/clis/pixiv/download.js +78 -0
  220. package/dist/clis/pixiv/download.test.d.ts +1 -0
  221. package/dist/clis/pixiv/download.test.js +87 -0
  222. package/dist/clis/pixiv/illusts.d.ts +8 -0
  223. package/dist/clis/pixiv/illusts.js +65 -0
  224. package/dist/clis/pixiv/illusts.test.d.ts +1 -0
  225. package/dist/clis/pixiv/illusts.test.js +99 -0
  226. package/dist/clis/pixiv/ranking.yaml +53 -0
  227. package/dist/clis/pixiv/search.d.ts +6 -0
  228. package/dist/clis/pixiv/search.js +43 -0
  229. package/dist/clis/pixiv/search.test.d.ts +1 -0
  230. package/dist/clis/pixiv/search.test.js +83 -0
  231. package/dist/clis/pixiv/test-utils.d.ts +12 -0
  232. package/dist/clis/pixiv/test-utils.js +23 -0
  233. package/dist/clis/pixiv/user.yaml +46 -0
  234. package/dist/clis/pixiv/utils.d.ts +27 -0
  235. package/dist/clis/pixiv/utils.js +49 -0
  236. package/dist/clis/reddit/comment.js +2 -1
  237. package/dist/clis/reddit/read.js +4 -3
  238. package/dist/clis/reddit/read.test.d.ts +1 -0
  239. package/dist/clis/reddit/read.test.js +28 -0
  240. package/dist/clis/reddit/save.js +2 -1
  241. package/dist/clis/reddit/saved.js +7 -3
  242. package/dist/clis/reddit/subscribe.js +2 -1
  243. package/dist/clis/reddit/upvote.js +2 -1
  244. package/dist/clis/reddit/upvoted.js +7 -3
  245. package/dist/clis/reuters/search.js +0 -1
  246. package/dist/clis/sinablog/article.js +1 -1
  247. package/dist/clis/sinablog/hot.js +1 -1
  248. package/dist/clis/sinablog/user.js +1 -1
  249. package/dist/clis/substack/feed.js +1 -1
  250. package/dist/clis/substack/publication.js +1 -1
  251. package/dist/clis/substack/search.js +3 -2
  252. package/dist/clis/substack/{shared.js → utils.js} +3 -2
  253. package/dist/clis/tiktok/search.yaml +2 -1
  254. package/dist/clis/twitter/accept.js +2 -1
  255. package/dist/clis/twitter/article.js +4 -1
  256. package/dist/clis/twitter/block.js +2 -1
  257. package/dist/clis/twitter/bookmark.js +2 -1
  258. package/dist/clis/twitter/bookmarks.js +3 -2
  259. package/dist/clis/twitter/delete.js +2 -1
  260. package/dist/clis/twitter/follow.js +2 -1
  261. package/dist/clis/twitter/followers.js +3 -2
  262. package/dist/clis/twitter/following.js +3 -2
  263. package/dist/clis/twitter/hide-reply.js +2 -1
  264. package/dist/clis/twitter/like.js +2 -1
  265. package/dist/clis/twitter/notifications.js +2 -1
  266. package/dist/clis/twitter/post.js +2 -1
  267. package/dist/clis/twitter/profile.js +5 -2
  268. package/dist/clis/twitter/reply-dm.js +2 -1
  269. package/dist/clis/twitter/reply.js +2 -1
  270. package/dist/clis/twitter/search.js +32 -13
  271. package/dist/clis/twitter/search.test.d.ts +1 -0
  272. package/dist/clis/twitter/search.test.js +156 -0
  273. package/dist/clis/twitter/thread.js +2 -2
  274. package/dist/clis/twitter/timeline.js +3 -2
  275. package/dist/clis/twitter/trending.js +3 -2
  276. package/dist/clis/twitter/unblock.js +2 -1
  277. package/dist/clis/twitter/unbookmark.js +2 -1
  278. package/dist/clis/twitter/unfollow.js +2 -1
  279. package/dist/clis/v2ex/daily.js +3 -2
  280. package/dist/clis/v2ex/me.js +3 -2
  281. package/dist/clis/v2ex/notifications.js +4 -4
  282. package/dist/clis/web/read.d.ts +16 -0
  283. package/dist/clis/web/read.js +202 -0
  284. package/dist/clis/weibo/comments.d.ts +1 -0
  285. package/dist/clis/weibo/comments.js +53 -0
  286. package/dist/clis/weibo/feed.d.ts +1 -0
  287. package/dist/clis/weibo/feed.js +56 -0
  288. package/dist/clis/weibo/hot.js +0 -1
  289. package/dist/clis/weibo/me.d.ts +1 -0
  290. package/dist/clis/weibo/me.js +76 -0
  291. package/dist/clis/weibo/post.d.ts +1 -0
  292. package/dist/clis/weibo/post.js +75 -0
  293. package/dist/clis/weibo/user.d.ts +1 -0
  294. package/dist/clis/weibo/user.js +63 -0
  295. package/dist/clis/weibo/utils.d.ts +6 -0
  296. package/dist/clis/weibo/utils.js +30 -0
  297. package/dist/clis/weread/search.js +3 -2
  298. package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
  299. package/dist/clis/xueqiu/danjuan-utils.js +126 -0
  300. package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
  301. package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
  302. package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
  303. package/dist/clis/xueqiu/fund-holdings.js +28 -0
  304. package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
  305. package/dist/clis/xueqiu/fund-snapshot.js +25 -0
  306. package/dist/clis/xueqiu/search.yaml +2 -1
  307. package/dist/clis/yahoo-finance/quote.js +0 -1
  308. package/dist/clis/youtube/channel.d.ts +1 -0
  309. package/dist/clis/youtube/channel.js +150 -0
  310. package/dist/clis/youtube/comments.d.ts +1 -0
  311. package/dist/clis/youtube/comments.js +95 -0
  312. package/dist/clis/youtube/search.js +0 -1
  313. package/dist/clis/youtube/transcript.js +5 -4
  314. package/dist/clis/youtube/video.js +3 -2
  315. package/dist/clis/zhihu/search.yaml +2 -1
  316. package/dist/daemon.js +7 -3
  317. package/dist/discovery.js +11 -10
  318. package/dist/doctor.js +2 -1
  319. package/dist/download/index.d.ts +4 -12
  320. package/dist/download/index.js +33 -12
  321. package/dist/download/index.test.js +79 -2
  322. package/dist/download/media-download.js +4 -2
  323. package/dist/engine.test.js +76 -4
  324. package/dist/execution.d.ts +1 -9
  325. package/dist/execution.js +56 -46
  326. package/dist/explore.js +12 -111
  327. package/dist/external-clis.yaml +0 -25
  328. package/dist/external.js +7 -5
  329. package/dist/external.test.js +4 -0
  330. package/dist/generate.d.ts +0 -9
  331. package/dist/generate.js +4 -20
  332. package/dist/hooks.d.ts +46 -0
  333. package/dist/hooks.js +56 -0
  334. package/dist/hooks.test.d.ts +4 -0
  335. package/dist/hooks.test.js +92 -0
  336. package/dist/interceptor.js +70 -23
  337. package/dist/main.js +2 -0
  338. package/dist/output.js +12 -6
  339. package/dist/pipeline/executor.js +1 -1
  340. package/dist/pipeline/steps/browser.js +1 -3
  341. package/dist/pipeline/steps/download.js +42 -26
  342. package/dist/pipeline/steps/download.test.d.ts +1 -0
  343. package/dist/pipeline/steps/download.test.js +101 -0
  344. package/dist/pipeline/steps/fetch.js +40 -22
  345. package/dist/pipeline/steps/fetch.test.d.ts +1 -0
  346. package/dist/pipeline/steps/fetch.test.js +123 -0
  347. package/dist/pipeline/steps/transform.js +2 -6
  348. package/dist/pipeline/template.js +66 -52
  349. package/dist/pipeline/template.test.js +28 -0
  350. package/dist/pipeline/transform.test.js +18 -0
  351. package/dist/plugin.d.ts +40 -1
  352. package/dist/plugin.js +214 -17
  353. package/dist/plugin.test.d.ts +1 -1
  354. package/dist/plugin.test.js +219 -3
  355. package/dist/record.js +6 -98
  356. package/dist/registry-api.d.ts +2 -0
  357. package/dist/registry-api.js +1 -0
  358. package/dist/registry.d.ts +5 -2
  359. package/dist/registry.js +1 -2
  360. package/dist/runtime.d.ts +0 -1
  361. package/dist/runtime.js +14 -4
  362. package/dist/snapshotFormatter.d.ts +7 -14
  363. package/dist/snapshotFormatter.js +38 -78
  364. package/dist/utils.d.ts +9 -0
  365. package/dist/utils.js +29 -0
  366. package/dist/validate.js +3 -5
  367. package/dist/weread-search-regression.test.d.ts +1 -0
  368. package/dist/weread-search-regression.test.js +39 -0
  369. package/dist/yaml-schema.d.ts +26 -0
  370. package/dist/yaml-schema.js +5 -0
  371. package/docs/.vitepress/config.mts +16 -0
  372. package/docs/adapters/browser/dictionary.md +27 -0
  373. package/docs/adapters/browser/douyin.md +75 -0
  374. package/docs/adapters/browser/jd.md +27 -0
  375. package/docs/adapters/browser/linkedin.md +6 -0
  376. package/docs/adapters/browser/pixiv.md +92 -0
  377. package/docs/adapters/browser/twitter.md +6 -0
  378. package/docs/adapters/browser/web.md +30 -0
  379. package/docs/adapters/browser/xueqiu.md +27 -9
  380. package/docs/adapters/index.md +9 -2
  381. package/docs/comparison.md +125 -0
  382. package/docs/developer/contributing.md +21 -2
  383. package/docs/developer/testing.md +14 -8
  384. package/docs/developer/ts-adapter.md +18 -0
  385. package/docs/developer/yaml-adapter.md +16 -0
  386. package/docs/guide/plugins.md +10 -0
  387. package/docs/zh/guide/plugins.md +10 -0
  388. package/extension/dist/background.js +100 -35
  389. package/extension/manifest.json +6 -2
  390. package/extension/package.json +1 -1
  391. package/extension/popup.html +84 -0
  392. package/extension/popup.js +25 -0
  393. package/extension/src/background.test.ts +46 -1
  394. package/extension/src/background.ts +128 -34
  395. package/extension/src/cdp.ts +9 -9
  396. package/package.json +3 -2
  397. package/scripts/check-doc-coverage.sh +2 -0
  398. package/src/analysis.ts +170 -0
  399. package/src/browser/cdp.test.ts +66 -0
  400. package/src/browser/cdp.ts +59 -44
  401. package/src/browser/dom-snapshot.test.ts +42 -0
  402. package/src/browser/dom-snapshot.ts +56 -3
  403. package/src/browser/index.ts +2 -2
  404. package/src/browser/mcp.ts +2 -4
  405. package/src/browser/page.ts +34 -37
  406. package/src/browser/stealth.ts +24 -10
  407. package/src/browser.test.ts +2 -2
  408. package/src/build-manifest.test.ts +14 -0
  409. package/src/build-manifest.ts +13 -31
  410. package/src/cascade.ts +5 -3
  411. package/src/cli.ts +66 -34
  412. package/src/clis/_shared/desktop-commands.ts +121 -0
  413. package/src/clis/antigravity/serve.ts +6 -3
  414. package/src/clis/apple-podcasts/search.ts +2 -1
  415. package/src/clis/arxiv/search.ts +3 -3
  416. package/src/clis/bbc/news.ts +0 -1
  417. package/src/clis/bilibili/dynamic.test.ts +79 -0
  418. package/src/clis/bilibili/favorite.ts +5 -2
  419. package/src/clis/bilibili/following.ts +3 -2
  420. package/src/clis/bilibili/subtitle.ts +8 -7
  421. package/src/clis/bilibili/utils.ts +2 -2
  422. package/src/clis/boss/batchgreet.ts +1 -1
  423. package/src/clis/boss/chatlist.ts +1 -1
  424. package/src/clis/boss/chatmsg.ts +1 -1
  425. package/src/clis/boss/detail.ts +1 -1
  426. package/src/clis/boss/exchange.ts +1 -1
  427. package/src/clis/boss/greet.ts +1 -1
  428. package/src/clis/boss/invite.ts +1 -1
  429. package/src/clis/boss/joblist.ts +1 -1
  430. package/src/clis/boss/mark.ts +4 -3
  431. package/src/clis/boss/recommend.ts +1 -1
  432. package/src/clis/boss/resume.ts +1 -1
  433. package/src/clis/boss/search.ts +1 -1
  434. package/src/clis/boss/send.ts +5 -4
  435. package/src/clis/boss/stats.ts +1 -1
  436. package/src/clis/chatgpt/ask.ts +5 -0
  437. package/src/clis/chatgpt/new.ts +7 -2
  438. package/src/clis/chatgpt/read.ts +7 -2
  439. package/src/clis/chatgpt/send.ts +3 -2
  440. package/src/clis/chatgpt/status.ts +6 -1
  441. package/src/clis/chatwise/ask.ts +7 -2
  442. package/src/clis/chatwise/export.ts +2 -0
  443. package/src/clis/chatwise/history.ts +2 -0
  444. package/src/clis/chatwise/model.ts +7 -3
  445. package/src/clis/chatwise/new.ts +3 -20
  446. package/src/clis/chatwise/read.ts +2 -0
  447. package/src/clis/chatwise/screenshot.ts +3 -32
  448. package/src/clis/chatwise/send.ts +7 -2
  449. package/src/clis/chatwise/shared.ts +8 -0
  450. package/src/clis/chatwise/status.ts +3 -24
  451. package/src/clis/codex/ask.ts +5 -2
  452. package/src/clis/codex/dump.ts +2 -27
  453. package/src/clis/codex/new.ts +2 -28
  454. package/src/clis/codex/screenshot.ts +2 -32
  455. package/src/clis/codex/send.ts +5 -4
  456. package/src/clis/codex/status.ts +2 -24
  457. package/src/clis/ctrip/search.ts +0 -1
  458. package/src/clis/cursor/ask.ts +2 -1
  459. package/src/clis/cursor/composer.ts +2 -1
  460. package/src/clis/cursor/dump.ts +2 -27
  461. package/src/clis/cursor/new.ts +2 -20
  462. package/src/clis/cursor/read.ts +2 -1
  463. package/src/clis/cursor/screenshot.ts +1 -36
  464. package/src/clis/cursor/send.ts +2 -1
  465. package/src/clis/cursor/status.ts +2 -22
  466. package/src/clis/dictionary/examples.yaml +25 -0
  467. package/src/clis/dictionary/search.yaml +27 -0
  468. package/src/clis/dictionary/synonyms.yaml +25 -0
  469. package/src/clis/douban/book-hot.ts +1 -1
  470. package/src/clis/douban/movie-hot.ts +1 -1
  471. package/src/clis/douban/search.ts +1 -1
  472. package/src/clis/douban/utils.ts +165 -1
  473. package/src/clis/doubao/ask.ts +1 -1
  474. package/src/clis/doubao/new.ts +1 -1
  475. package/src/clis/doubao/read.ts +1 -1
  476. package/src/clis/doubao/send.ts +1 -1
  477. package/src/clis/doubao/status.ts +1 -1
  478. package/src/clis/doubao-app/ask.ts +1 -1
  479. package/src/clis/doubao-app/new.ts +1 -1
  480. package/src/clis/doubao-app/read.ts +1 -1
  481. package/src/clis/doubao-app/send.ts +1 -1
  482. package/src/clis/douyin/_shared/browser-fetch.test.ts +38 -0
  483. package/src/clis/douyin/_shared/browser-fetch.ts +45 -0
  484. package/src/clis/douyin/_shared/creation-id.test.ts +26 -0
  485. package/src/clis/douyin/_shared/creation-id.ts +8 -0
  486. package/src/clis/douyin/_shared/imagex-upload.test.ts +113 -0
  487. package/src/clis/douyin/_shared/imagex-upload.ts +76 -0
  488. package/src/clis/douyin/_shared/sts2.ts +20 -0
  489. package/src/clis/douyin/_shared/text-extra.test.ts +42 -0
  490. package/src/clis/douyin/_shared/text-extra.ts +33 -0
  491. package/src/clis/douyin/_shared/timing.test.ts +38 -0
  492. package/src/clis/douyin/_shared/timing.ts +22 -0
  493. package/src/clis/douyin/_shared/tos-upload-short-read.test.ts +102 -0
  494. package/src/clis/douyin/_shared/tos-upload.test.ts +281 -0
  495. package/src/clis/douyin/_shared/tos-upload.ts +444 -0
  496. package/src/clis/douyin/_shared/transcode.test.ts +117 -0
  497. package/src/clis/douyin/_shared/transcode.ts +78 -0
  498. package/src/clis/douyin/_shared/types.ts +29 -0
  499. package/src/clis/douyin/activities.test.ts +25 -0
  500. package/src/clis/douyin/activities.ts +23 -0
  501. package/src/clis/douyin/collections.test.ts +26 -0
  502. package/src/clis/douyin/collections.ts +25 -0
  503. package/src/clis/douyin/delete.test.ts +12 -0
  504. package/src/clis/douyin/delete.ts +20 -0
  505. package/src/clis/douyin/draft.test.ts +12 -0
  506. package/src/clis/douyin/draft.ts +282 -0
  507. package/src/clis/douyin/drafts.test.ts +12 -0
  508. package/src/clis/douyin/drafts.ts +27 -0
  509. package/src/clis/douyin/hashtag.test.ts +28 -0
  510. package/src/clis/douyin/hashtag.ts +56 -0
  511. package/src/clis/douyin/location.test.ts +26 -0
  512. package/src/clis/douyin/location.ts +27 -0
  513. package/src/clis/douyin/profile.test.ts +12 -0
  514. package/src/clis/douyin/profile.ts +37 -0
  515. package/src/clis/douyin/publish.test.ts +45 -0
  516. package/src/clis/douyin/publish.ts +340 -0
  517. package/src/clis/douyin/stats.test.ts +25 -0
  518. package/src/clis/douyin/stats.ts +30 -0
  519. package/src/clis/douyin/update.test.ts +12 -0
  520. package/src/clis/douyin/update.ts +43 -0
  521. package/src/clis/douyin/videos.test.ts +12 -0
  522. package/src/clis/douyin/videos.ts +49 -0
  523. package/src/clis/grok/ask.test.ts +25 -0
  524. package/src/clis/grok/ask.ts +25 -12
  525. package/src/clis/hackernews/search.yaml +1 -1
  526. package/src/clis/instagram/search.yaml +2 -1
  527. package/src/clis/jd/item.test.ts +35 -0
  528. package/src/clis/jd/item.ts +101 -0
  529. package/src/clis/jike/feed.ts +1 -1
  530. package/src/clis/jike/search.ts +1 -1
  531. package/src/clis/linkedin/search.ts +5 -4
  532. package/src/clis/linkedin/timeline.test.ts +99 -0
  533. package/src/clis/linkedin/timeline.ts +532 -0
  534. package/src/clis/linux-do/search.yaml +3 -1
  535. package/src/clis/medium/feed.ts +1 -1
  536. package/src/clis/medium/search.ts +2 -2
  537. package/src/clis/medium/user.ts +1 -1
  538. package/src/clis/medium/{shared.ts → utils.ts} +2 -1
  539. package/src/clis/pixiv/detail.yaml +49 -0
  540. package/src/clis/pixiv/download.test.ts +114 -0
  541. package/src/clis/pixiv/download.ts +91 -0
  542. package/src/clis/pixiv/illusts.test.ts +115 -0
  543. package/src/clis/pixiv/illusts.ts +78 -0
  544. package/src/clis/pixiv/ranking.yaml +53 -0
  545. package/src/clis/pixiv/search.test.ts +97 -0
  546. package/src/clis/pixiv/search.ts +53 -0
  547. package/src/clis/pixiv/test-utils.ts +29 -0
  548. package/src/clis/pixiv/user.yaml +46 -0
  549. package/src/clis/pixiv/utils.ts +62 -0
  550. package/src/clis/reddit/comment.ts +2 -1
  551. package/src/clis/reddit/read.test.ts +34 -0
  552. package/src/clis/reddit/read.ts +4 -3
  553. package/src/clis/reddit/save.ts +2 -1
  554. package/src/clis/reddit/saved.ts +6 -2
  555. package/src/clis/reddit/subscribe.ts +2 -1
  556. package/src/clis/reddit/upvote.ts +2 -1
  557. package/src/clis/reddit/upvoted.ts +6 -2
  558. package/src/clis/reuters/search.ts +0 -1
  559. package/src/clis/sinablog/article.ts +1 -1
  560. package/src/clis/sinablog/hot.ts +1 -1
  561. package/src/clis/sinablog/user.ts +1 -1
  562. package/src/clis/substack/feed.ts +1 -1
  563. package/src/clis/substack/publication.ts +1 -1
  564. package/src/clis/substack/search.ts +3 -2
  565. package/src/clis/substack/{shared.ts → utils.ts} +3 -2
  566. package/src/clis/tiktok/search.yaml +2 -1
  567. package/src/clis/twitter/accept.ts +2 -1
  568. package/src/clis/twitter/article.ts +3 -1
  569. package/src/clis/twitter/block.ts +2 -1
  570. package/src/clis/twitter/bookmark.ts +2 -1
  571. package/src/clis/twitter/bookmarks.ts +3 -2
  572. package/src/clis/twitter/delete.ts +2 -1
  573. package/src/clis/twitter/follow.ts +2 -1
  574. package/src/clis/twitter/followers.ts +3 -2
  575. package/src/clis/twitter/following.ts +3 -2
  576. package/src/clis/twitter/hide-reply.ts +2 -1
  577. package/src/clis/twitter/like.ts +2 -1
  578. package/src/clis/twitter/notifications.ts +2 -1
  579. package/src/clis/twitter/post.ts +2 -1
  580. package/src/clis/twitter/profile.ts +4 -2
  581. package/src/clis/twitter/reply-dm.ts +2 -1
  582. package/src/clis/twitter/reply.ts +2 -1
  583. package/src/clis/twitter/search.test.ts +180 -0
  584. package/src/clis/twitter/search.ts +40 -14
  585. package/src/clis/twitter/thread.ts +2 -2
  586. package/src/clis/twitter/timeline.ts +3 -2
  587. package/src/clis/twitter/trending.ts +3 -2
  588. package/src/clis/twitter/unblock.ts +2 -1
  589. package/src/clis/twitter/unbookmark.ts +2 -1
  590. package/src/clis/twitter/unfollow.ts +2 -1
  591. package/src/clis/v2ex/daily.ts +3 -2
  592. package/src/clis/v2ex/me.ts +3 -2
  593. package/src/clis/v2ex/notifications.ts +3 -4
  594. package/src/clis/web/read.ts +210 -0
  595. package/src/clis/weibo/comments.ts +54 -0
  596. package/src/clis/weibo/feed.ts +57 -0
  597. package/src/clis/weibo/hot.ts +0 -1
  598. package/src/clis/weibo/me.ts +77 -0
  599. package/src/clis/weibo/post.ts +77 -0
  600. package/src/clis/weibo/user.ts +64 -0
  601. package/src/clis/weibo/utils.ts +32 -0
  602. package/src/clis/weread/search.ts +3 -2
  603. package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
  604. package/src/clis/xueqiu/danjuan-utils.ts +176 -0
  605. package/src/clis/xueqiu/fund-holdings.ts +32 -0
  606. package/src/clis/xueqiu/fund-snapshot.ts +27 -0
  607. package/src/clis/xueqiu/search.yaml +2 -1
  608. package/src/clis/yahoo-finance/quote.ts +0 -1
  609. package/src/clis/youtube/channel.ts +155 -0
  610. package/src/clis/youtube/comments.ts +97 -0
  611. package/src/clis/youtube/search.ts +0 -1
  612. package/src/clis/youtube/transcript.ts +5 -4
  613. package/src/clis/youtube/video.ts +3 -2
  614. package/src/clis/zhihu/search.yaml +2 -1
  615. package/src/daemon.ts +5 -4
  616. package/src/discovery.ts +12 -34
  617. package/src/doctor.ts +3 -2
  618. package/src/download/index.test.ts +93 -2
  619. package/src/download/index.ts +44 -23
  620. package/src/download/media-download.ts +5 -3
  621. package/src/engine.test.ts +84 -3
  622. package/src/execution.ts +62 -46
  623. package/src/explore.ts +21 -90
  624. package/src/external-clis.yaml +0 -25
  625. package/src/external.test.ts +9 -0
  626. package/src/external.ts +12 -10
  627. package/src/generate.ts +4 -41
  628. package/src/hooks.test.ts +126 -0
  629. package/src/hooks.ts +90 -0
  630. package/src/interceptor.ts +73 -23
  631. package/src/main.ts +2 -0
  632. package/src/output.ts +14 -6
  633. package/src/pipeline/executor.ts +1 -1
  634. package/src/pipeline/steps/browser.ts +1 -3
  635. package/src/pipeline/steps/download.test.ts +136 -0
  636. package/src/pipeline/steps/download.ts +47 -34
  637. package/src/pipeline/steps/fetch.test.ts +179 -0
  638. package/src/pipeline/steps/fetch.ts +39 -23
  639. package/src/pipeline/steps/transform.ts +2 -6
  640. package/src/pipeline/template.test.ts +28 -0
  641. package/src/pipeline/template.ts +67 -79
  642. package/src/pipeline/transform.test.ts +20 -0
  643. package/src/plugin.test.ts +251 -3
  644. package/src/plugin.ts +265 -21
  645. package/src/record.ts +12 -84
  646. package/src/registry-api.ts +2 -0
  647. package/src/registry.ts +7 -4
  648. package/src/runtime.ts +14 -4
  649. package/src/snapshotFormatter.ts +43 -121
  650. package/src/utils.ts +39 -0
  651. package/src/validate.ts +3 -5
  652. package/src/weread-search-regression.test.ts +44 -0
  653. package/src/yaml-schema.ts +28 -0
  654. package/tests/e2e/browser-auth.test.ts +25 -0
  655. package/tests/e2e/browser-public-extended.test.ts +162 -0
  656. package/tests/e2e/browser-public.test.ts +7 -146
  657. package/tests/e2e/plugin-management.test.ts +137 -0
  658. package/tests/e2e/public-commands.test.ts +34 -1
  659. package/vitest.config.ts +33 -8
  660. package/.github/workflows/pkg-pr-new.yml +0 -30
  661. package/dist/clis/douban/shared.d.ts +0 -4
  662. package/dist/clis/douban/shared.js +0 -155
  663. package/src/clis/douban/shared.ts +0 -165
  664. /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
  665. /package/dist/clis/boss/{common.js → utils.js} +0 -0
  666. /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
  667. /package/dist/clis/doubao/{common.js → utils.js} +0 -0
  668. /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
  669. /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
  670. /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
  671. /package/dist/clis/jike/{shared.js → utils.js} +0 -0
  672. /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
  673. /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
  674. /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
  675. /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
  676. /package/src/clis/boss/{common.ts → utils.ts} +0 -0
  677. /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
  678. /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
  679. /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
  680. /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
@@ -1,5 +1,5 @@
1
1
  /**
2
- * E2E tests for browser commands that access PUBLIC data (no login required).
2
+ * E2E tests for core browser commands (bilibili, zhihu, v2ex).
3
3
  * These use OPENCLI_HEADLESS=1 to launch a headless Chromium.
4
4
  *
5
5
  * NOTE: Some sites may block headless browsers with bot detection.
@@ -9,9 +9,6 @@
9
9
  import { describe, it, expect } from 'vitest';
10
10
  import { runCli, parseJsonOutput } from './helpers.js';
11
11
 
12
- /**
13
- * Run a browser command — returns parsed data or null on failure.
14
- */
15
12
  async function tryBrowserCommand(args: string[]): Promise<any[] | null> {
16
13
  const { stdout, code } = await runCli(args, { timeout: 60_000 });
17
14
  if (code !== 0) return null;
@@ -23,10 +20,6 @@ async function tryBrowserCommand(args: string[]): Promise<any[] | null> {
23
20
  }
24
21
  }
25
22
 
26
- /**
27
- * Assert browser command returns data OR log a warning if blocked.
28
- * Empty results (bot detection, geo-blocking) are treated as a warning, not a failure.
29
- */
30
23
  function expectDataOrSkip(data: any[] | null, label: string) {
31
24
  if (data === null || data.length === 0) {
32
25
  console.warn(`${label}: skipped — no data returned (likely bot detection or geo-blocking)`);
@@ -37,47 +30,7 @@ function expectDataOrSkip(data: any[] | null, label: string) {
37
30
 
38
31
  describe('browser public-data commands E2E', () => {
39
32
 
40
- // ── bbc (browser: true, strategy: public) ──
41
- it('bbc news returns headlines', async () => {
42
- const data = await tryBrowserCommand(['bbc', 'news', '--limit', '3', '-f', 'json']);
43
- expectDataOrSkip(data, 'bbc news');
44
- if (data) {
45
- expect(data[0]).toHaveProperty('title');
46
- }
47
- }, 60_000);
48
-
49
- it('bloomberg news returns article detail when the article page is accessible', async () => {
50
- const feedResult = await runCli(['bloomberg', 'tech', '--limit', '1', '-f', 'json']);
51
- if (feedResult.code !== 0) {
52
- console.warn('bloomberg news: skipped — could not load Bloomberg tech feed');
53
- return;
54
- }
55
-
56
- const feedItems = parseJsonOutput(feedResult.stdout);
57
- const link = Array.isArray(feedItems) ? feedItems[0]?.link : null;
58
- if (!link) {
59
- console.warn('bloomberg news: skipped — tech feed returned no link');
60
- return;
61
- }
62
-
63
- const data = await tryBrowserCommand(['bloomberg', 'news', link, '-f', 'json']);
64
- expectDataOrSkip(data, 'bloomberg news');
65
- if (data) {
66
- expect(data[0]).toHaveProperty('title');
67
- expect(data[0]).toHaveProperty('summary');
68
- expect(data[0]).toHaveProperty('link');
69
- expect(data[0]).toHaveProperty('mediaLinks');
70
- expect(data[0]).toHaveProperty('content');
71
- }
72
- }, 60_000);
73
-
74
- // ── v2ex daily (browser: true) ──
75
- it('v2ex daily returns topics', async () => {
76
- const data = await tryBrowserCommand(['v2ex', 'daily', '--limit', '3', '-f', 'json']);
77
- expectDataOrSkip(data, 'v2ex daily');
78
- }, 60_000);
79
-
80
- // ── bilibili (browser: true, cookie strategy) ──
33
+ // ── bilibili ──
81
34
  it('bilibili hot returns trending videos', async () => {
82
35
  const data = await tryBrowserCommand(['bilibili', 'hot', '--limit', '5', '-f', 'json']);
83
36
  expectDataOrSkip(data, 'bilibili hot');
@@ -96,18 +49,7 @@ describe('browser public-data commands E2E', () => {
96
49
  expectDataOrSkip(data, 'bilibili search');
97
50
  }, 60_000);
98
51
 
99
- // ── weibo (browser: true, cookie strategy) ──
100
- it('weibo hot returns trending topics', async () => {
101
- const data = await tryBrowserCommand(['weibo', 'hot', '--limit', '5', '-f', 'json']);
102
- expectDataOrSkip(data, 'weibo hot');
103
- }, 60_000);
104
-
105
- it('weibo search returns results', async () => {
106
- const data = await tryBrowserCommand(['weibo', 'search', 'openai', '--limit', '3', '-f', 'json']);
107
- expectDataOrSkip(data, 'weibo search');
108
- }, 60_000);
109
-
110
- // ── zhihu (browser: true, cookie strategy) ──
52
+ // ── zhihu ──
111
53
  it('zhihu hot returns trending questions', async () => {
112
54
  const data = await tryBrowserCommand(['zhihu', 'hot', '--limit', '5', '-f', 'json']);
113
55
  expectDataOrSkip(data, 'zhihu hot');
@@ -121,90 +63,9 @@ describe('browser public-data commands E2E', () => {
121
63
  expectDataOrSkip(data, 'zhihu search');
122
64
  }, 60_000);
123
65
 
124
- // ── reddit (browser: true, cookie strategy) ──
125
- it('reddit hot returns posts', async () => {
126
- const data = await tryBrowserCommand(['reddit', 'hot', '--limit', '5', '-f', 'json']);
127
- expectDataOrSkip(data, 'reddit hot');
128
- }, 60_000);
129
-
130
- it('reddit frontpage returns posts', async () => {
131
- const data = await tryBrowserCommand(['reddit', 'frontpage', '--limit', '5', '-f', 'json']);
132
- expectDataOrSkip(data, 'reddit frontpage');
133
- }, 60_000);
134
-
135
- // ── twitter (browser: true) ──
136
- it('twitter trending returns trends', async () => {
137
- const data = await tryBrowserCommand(['twitter', 'trending', '--limit', '5', '-f', 'json']);
138
- expectDataOrSkip(data, 'twitter trending');
139
- }, 60_000);
140
-
141
- // ── xueqiu (browser: true, cookie strategy) ──
142
- it('xueqiu hot returns hot posts', async () => {
143
- const data = await tryBrowserCommand(['xueqiu', 'hot', '--limit', '5', '-f', 'json']);
144
- expectDataOrSkip(data, 'xueqiu hot');
145
- }, 60_000);
146
-
147
- it('xueqiu hot-stock returns stocks', async () => {
148
- const data = await tryBrowserCommand(['xueqiu', 'hot-stock', '--limit', '5', '-f', 'json']);
149
- expectDataOrSkip(data, 'xueqiu hot-stock');
150
- }, 60_000);
151
-
152
- // ── reuters (browser: true) ──
153
- it('reuters search returns articles', async () => {
154
- const data = await tryBrowserCommand(['reuters', 'search', 'technology', '--limit', '3', '-f', 'json']);
155
- expectDataOrSkip(data, 'reuters search');
156
- }, 60_000);
157
-
158
- // ── youtube (browser: true) ──
159
- it('youtube search returns videos', async () => {
160
- const data = await tryBrowserCommand(['youtube', 'search', 'typescript tutorial', '--limit', '3', '-f', 'json']);
161
- expectDataOrSkip(data, 'youtube search');
162
- }, 60_000);
163
-
164
- // ── smzdm (browser: true) ──
165
- it('smzdm search returns deals', async () => {
166
- const data = await tryBrowserCommand(['smzdm', 'search', '键盘', '--limit', '3', '-f', 'json']);
167
- expectDataOrSkip(data, 'smzdm search');
168
- }, 60_000);
169
-
170
- // ── boss (browser: true) ──
171
- it('boss search returns jobs', async () => {
172
- const data = await tryBrowserCommand(['boss', 'search', 'golang', '--limit', '3', '-f', 'json']);
173
- expectDataOrSkip(data, 'boss search');
174
- }, 60_000);
175
-
176
- // ── ctrip (browser: true) ──
177
- it('ctrip search returns flights', async () => {
178
- const data = await tryBrowserCommand(['ctrip', 'search', '-f', 'json']);
179
- expectDataOrSkip(data, 'ctrip search');
180
- }, 60_000);
181
-
182
- // ── coupang (browser: true) ──
183
- it('coupang search returns products', async () => {
184
- const data = await tryBrowserCommand(['coupang', 'search', 'laptop', '--limit', '3', '-f', 'json']);
185
- expectDataOrSkip(data, 'coupang search');
186
- }, 60_000);
187
-
188
- // ── xiaohongshu (browser: true) ──
189
- it('xiaohongshu search returns notes', async () => {
190
- const data = await tryBrowserCommand(['xiaohongshu', 'search', '美食', '--limit', '3', '-f', 'json']);
191
- expectDataOrSkip(data, 'xiaohongshu search');
192
- }, 60_000);
193
-
194
- // ── google search (browser: true, public strategy) ──
195
- it('google search returns results', async () => {
196
- const data = await tryBrowserCommand(['google', 'search', 'typescript', '--limit', '5', '-f', 'json']);
197
- expectDataOrSkip(data, 'google search');
198
- if (data) {
199
- expect(data[0]).toHaveProperty('type');
200
- expect(data[0]).toHaveProperty('title');
201
- expect(data[0]).toHaveProperty('url');
202
- }
203
- }, 60_000);
204
-
205
- // ── yahoo-finance (browser: true) ──
206
- it('yahoo-finance quote returns stock data', async () => {
207
- const data = await tryBrowserCommand(['yahoo-finance', 'quote', '--symbol', 'AAPL', '-f', 'json']);
208
- expectDataOrSkip(data, 'yahoo-finance quote');
66
+ // ── v2ex ──
67
+ it('v2ex daily returns topics', async () => {
68
+ const data = await tryBrowserCommand(['v2ex', 'daily', '--limit', '3', '-f', 'json']);
69
+ expectDataOrSkip(data, 'v2ex daily');
209
70
  }, 60_000);
210
71
  });
@@ -0,0 +1,137 @@
1
+ /**
2
+ * E2E integration tests for plugin management commands.
3
+ * Uses a real GitHub plugin (opencli-plugin-hot-digest) to verify the full
4
+ * install → list → update → uninstall lifecycle in an isolated HOME.
5
+ */
6
+
7
+ import { describe, it, expect, afterAll } from 'vitest';
8
+ import * as fs from 'node:fs';
9
+ import * as path from 'node:path';
10
+ import * as os from 'node:os';
11
+ import { runCli, parseJsonOutput } from './helpers.js';
12
+
13
+ const TEST_HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-plugin-e2e-'));
14
+ const OPENCLI_HOME = path.join(TEST_HOME, '.opencli');
15
+ const PLUGINS_DIR = path.join(OPENCLI_HOME, 'plugins');
16
+ const PLUGIN_SOURCE = 'github:ByteYue/opencli-plugin-hot-digest';
17
+ const PLUGIN_NAME = 'hot-digest';
18
+ const PLUGIN_DIR = path.join(PLUGINS_DIR, PLUGIN_NAME);
19
+ const LOCK_FILE = path.join(OPENCLI_HOME, 'plugins.lock.json');
20
+
21
+ function runPluginCli(
22
+ args: string[],
23
+ opts: { timeout?: number; env?: Record<string, string> } = {},
24
+ ) {
25
+ return runCli(args, {
26
+ ...opts,
27
+ env: {
28
+ HOME: TEST_HOME,
29
+ USERPROFILE: TEST_HOME,
30
+ ...opts.env,
31
+ },
32
+ });
33
+ }
34
+
35
+ describe('plugin management E2E', () => {
36
+ afterAll(() => {
37
+ fs.rmSync(TEST_HOME, { recursive: true, force: true });
38
+ });
39
+
40
+ // ── plugin list (empty) ──
41
+ it('plugin list shows "No plugins installed" when none exist', async () => {
42
+ const { stdout, code } = await runPluginCli(['plugin', 'list']);
43
+ expect(code).toBe(0);
44
+ expect(stdout).toContain('No plugins installed');
45
+ });
46
+
47
+ // ── plugin install ──
48
+ it('plugin install clones and sets up a real plugin', async () => {
49
+ const { stdout, code } = await runPluginCli(['plugin', 'install', PLUGIN_SOURCE], {
50
+ timeout: 60_000,
51
+ });
52
+ expect(code).toBe(0);
53
+ expect(stdout).toContain('installed successfully');
54
+ expect(stdout).toContain(PLUGIN_NAME);
55
+
56
+ // Verify the plugin directory was created
57
+ expect(fs.existsSync(PLUGIN_DIR)).toBe(true);
58
+
59
+ // Verify lock file was updated
60
+ const lock = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf-8'));
61
+ expect(lock[PLUGIN_NAME]).toBeDefined();
62
+ expect(lock[PLUGIN_NAME].commitHash).toBeTruthy();
63
+ expect(lock[PLUGIN_NAME].source).toContain('opencli-plugin-hot-digest');
64
+ expect(lock[PLUGIN_NAME].installedAt).toBeTruthy();
65
+ }, 60_000);
66
+
67
+ // ── plugin list (after install) ──
68
+ it('plugin list shows the installed plugin', async () => {
69
+ const { stdout, code } = await runPluginCli(['plugin', 'list']);
70
+ expect(code).toBe(0);
71
+ expect(stdout).toContain(PLUGIN_NAME);
72
+ });
73
+
74
+ it('plugin list -f json returns structured data', async () => {
75
+ const { stdout, code } = await runPluginCli(['plugin', 'list', '-f', 'json']);
76
+ expect(code).toBe(0);
77
+ const data = parseJsonOutput(stdout);
78
+ expect(Array.isArray(data)).toBe(true);
79
+
80
+ const plugin = data.find((p: any) => p.name === PLUGIN_NAME);
81
+ expect(plugin).toBeDefined();
82
+ expect(plugin.name).toBe(PLUGIN_NAME);
83
+ expect(Array.isArray(plugin.commands)).toBe(true);
84
+ expect(plugin.commands.length).toBeGreaterThan(0);
85
+ });
86
+
87
+ // ── plugin update ──
88
+ it('plugin update succeeds on an installed plugin', async () => {
89
+ const { stdout, code } = await runPluginCli(['plugin', 'update', PLUGIN_NAME], {
90
+ timeout: 30_000,
91
+ });
92
+ expect(code).toBe(0);
93
+ expect(stdout).toContain('updated successfully');
94
+
95
+ // Verify lock file has updatedAt
96
+ const lock = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf-8'));
97
+ expect(lock[PLUGIN_NAME].updatedAt).toBeTruthy();
98
+ }, 30_000);
99
+
100
+ // ── plugin uninstall ──
101
+ it('plugin uninstall removes the plugin', async () => {
102
+ const { stdout, code } = await runPluginCli(['plugin', 'uninstall', PLUGIN_NAME]);
103
+ expect(code).toBe(0);
104
+ expect(stdout).toContain('uninstalled');
105
+
106
+ // Verify directory was removed
107
+ expect(fs.existsSync(PLUGIN_DIR)).toBe(false);
108
+
109
+ // Verify lock entry was removed
110
+ const lock = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf-8'));
111
+ expect(lock[PLUGIN_NAME]).toBeUndefined();
112
+ });
113
+
114
+ // ── error paths ──
115
+ it('plugin install rejects invalid source', async () => {
116
+ const { stderr, code } = await runPluginCli(['plugin', 'install', 'invalid-source-format']);
117
+ expect(code).toBe(1);
118
+ expect(stderr).toContain('Invalid plugin source');
119
+ });
120
+
121
+ it('plugin uninstall rejects non-existent plugin', async () => {
122
+ const { stderr, code } = await runPluginCli(['plugin', 'uninstall', '__nonexistent_plugin_xyz__']);
123
+ expect(code).toBe(1);
124
+ expect(stderr).toContain('not installed');
125
+ });
126
+
127
+ it('plugin update rejects non-existent plugin', async () => {
128
+ const { stderr, code } = await runPluginCli(['plugin', 'update', '__nonexistent_plugin_xyz__']);
129
+ expect(code).toBe(1);
130
+ });
131
+
132
+ it('plugin update without name or --all shows error', async () => {
133
+ const { stderr, code } = await runPluginCli(['plugin', 'update']);
134
+ expect(code).toBe(1);
135
+ expect(stderr).toContain('specify a plugin name');
136
+ });
137
+ });
@@ -16,7 +16,8 @@ function isExpectedChineseSiteRestriction(code: number, stderr: string): boolean
16
16
 
17
17
  function isExpectedApplePodcastsRestriction(code: number, stderr: string): boolean {
18
18
  if (code === 0) return false;
19
- return /Error \[FETCH_ERROR\]: (Charts API HTTP \d+|Unable to reach Apple Podcasts charts)/.test(stderr);
19
+ return /Error \[FETCH_ERROR\]: (Charts API HTTP \d+|Unable to reach Apple Podcasts charts)/.test(stderr)
20
+ || stderr === ''; // timeout killed the process before any output
20
21
  }
21
22
 
22
23
  function isExpectedGoogleRestriction(code: number, stderr: string): boolean {
@@ -488,4 +489,36 @@ describe('public commands E2E', () => {
488
489
  expect(data.length).toBeGreaterThan(0);
489
490
  expect(data.every((d: any) => d.type === 'image')).toBe(true);
490
491
  }, 30_000);
492
+
493
+ // ── dictionary (public API, browser: false) ──
494
+ it('dictionary search returns word definitions', async () => {
495
+ const { stdout, code } = await runCli(['dictionary', 'search', 'serendipity', '-f', 'json']);
496
+ expect(code).toBe(0);
497
+ const data = parseJsonOutput(stdout);
498
+ expect(Array.isArray(data)).toBe(true);
499
+ expect(data.length).toBeGreaterThanOrEqual(1);
500
+ expect(data[0]).toHaveProperty('word', 'serendipity');
501
+ expect(data[0]).toHaveProperty('phonetic');
502
+ expect(data[0]).toHaveProperty('definition');
503
+ }, 30_000);
504
+
505
+ it('dictionary synonyms returns synonyms', async () => {
506
+ const { stdout, code } = await runCli(['dictionary', 'synonyms', 'serendipity', '-f', 'json']);
507
+ expect(code).toBe(0);
508
+ const data = parseJsonOutput(stdout);
509
+ expect(Array.isArray(data)).toBe(true);
510
+ expect(data.length).toBeGreaterThanOrEqual(1);
511
+ expect(data[0]).toHaveProperty('word', 'serendipity');
512
+ expect(data[0]).toHaveProperty('synonyms');
513
+ }, 30_000);
514
+
515
+ it('dictionary examples returns examples', async () => {
516
+ const { stdout, code } = await runCli(['dictionary', 'examples', 'perfect', '-f', 'json']);
517
+ expect(code).toBe(0);
518
+ const data = parseJsonOutput(stdout);
519
+ expect(Array.isArray(data)).toBe(true);
520
+ expect(data.length).toBeGreaterThanOrEqual(1);
521
+ expect(data[0]).toHaveProperty('word', 'perfect');
522
+ expect(data[0]).toHaveProperty('example');
523
+ }, 30_000);
491
524
  });
package/vitest.config.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { defineConfig } from 'vitest/config';
2
2
 
3
+ const includeExtendedE2e = process.env.OPENCLI_E2E === '1';
4
+
3
5
  export default defineConfig({
4
6
  test: {
5
7
  projects: [
@@ -7,20 +9,43 @@ export default defineConfig({
7
9
  test: {
8
10
  name: 'unit',
9
11
  include: ['src/**/*.test.ts'],
10
- // Run unit tests before e2e tests to avoid project-level contention in CI.
11
- sequence: {
12
- groupOrder: 0,
13
- },
12
+ exclude: ['src/clis/**/*.test.ts'],
13
+ sequence: { groupOrder: 0 },
14
+ },
15
+ },
16
+ {
17
+ test: {
18
+ name: 'adapter',
19
+ include: [
20
+ 'src/clis/bilibili/**/*.test.ts',
21
+ 'src/clis/zhihu/**/*.test.ts',
22
+ 'src/clis/v2ex/**/*.test.ts',
23
+ ],
24
+ sequence: { groupOrder: 1 },
14
25
  },
15
26
  },
16
27
  {
17
28
  test: {
18
29
  name: 'e2e',
19
- include: ['tests/**/*.test.ts'],
30
+ include: [
31
+ 'tests/e2e/browser-public.test.ts',
32
+ 'tests/e2e/public-commands.test.ts',
33
+ 'tests/e2e/management.test.ts',
34
+ 'tests/e2e/output-formats.test.ts',
35
+ 'tests/e2e/plugin-management.test.ts',
36
+ // Extended browser tests (20+ sites) — opt-in only:
37
+ // OPENCLI_E2E=1 npx vitest run
38
+ ...(includeExtendedE2e ? ['tests/e2e/browser-public-extended.test.ts', 'tests/e2e/browser-auth.test.ts'] : []),
39
+ ],
20
40
  maxWorkers: 2,
21
- sequence: {
22
- groupOrder: 1,
23
- },
41
+ sequence: { groupOrder: 2 },
42
+ },
43
+ },
44
+ {
45
+ test: {
46
+ name: 'smoke',
47
+ include: ['tests/smoke/**/*.test.ts'],
48
+ sequence: { groupOrder: 3 },
24
49
  },
25
50
  },
26
51
  ],
@@ -1,30 +0,0 @@
1
- name: Publish Any Commit
2
-
3
- on:
4
- push:
5
- branches: [main, dev]
6
- pull_request:
7
- branches: [main, dev]
8
-
9
- permissions: {}
10
-
11
- jobs:
12
- publish:
13
- if: ${{ vars.PKG_PR_NEW_ENABLED == 'true' }}
14
- runs-on: ubuntu-latest
15
- steps:
16
- - uses: actions/checkout@v6
17
-
18
- - uses: actions/setup-node@v6
19
- with:
20
- node-version: '22'
21
- cache: 'npm'
22
-
23
- - name: Install dependencies
24
- run: npm ci
25
-
26
- - name: Build
27
- run: npm run build
28
-
29
- - name: Publish to pkg.pr.new
30
- run: npx pkg-pr-new publish
@@ -1,4 +0,0 @@
1
- import type { IPage } from '../../types.js';
2
- export declare function loadDoubanBookHot(page: IPage, limit: number): Promise<any[]>;
3
- export declare function loadDoubanMovieHot(page: IPage, limit: number): Promise<any[]>;
4
- export declare function searchDouban(page: IPage, type: string, keyword: string, limit: number): Promise<any[]>;
@@ -1,155 +0,0 @@
1
- import { CliError } from '../../errors.js';
2
- function clampLimit(limit) {
3
- return Math.max(1, Math.min(limit || 20, 50));
4
- }
5
- async function ensureDoubanReady(page) {
6
- const state = await page.evaluate(`
7
- (() => {
8
- const title = (document.title || '').trim();
9
- const href = (location.href || '').trim();
10
- const blocked = href.includes('sec.douban.com') || /登录跳转/.test(title) || /异常请求/.test(document.body?.innerText || '');
11
- return { blocked, title, href };
12
- })()
13
- `);
14
- if (state?.blocked) {
15
- throw new CliError('AUTH_REQUIRED', 'Douban requires a logged-in browser session before these commands can load data.', 'Please sign in to douban.com in the browser that opencli reuses, then rerun the command.');
16
- }
17
- }
18
- export async function loadDoubanBookHot(page, limit) {
19
- const safeLimit = clampLimit(limit);
20
- await page.goto('https://book.douban.com/chart');
21
- await page.wait(4);
22
- await ensureDoubanReady(page);
23
- const data = await page.evaluate(`
24
- (() => {
25
- const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
26
- const books = [];
27
- for (const el of Array.from(document.querySelectorAll('.media.clearfix'))) {
28
- try {
29
- const titleEl = el.querySelector('h2 a[href*="/subject/"]');
30
- const title = normalize(titleEl?.textContent);
31
- let url = titleEl?.getAttribute('href') || '';
32
- if (!title || !url) continue;
33
- if (!url.startsWith('http')) url = 'https://book.douban.com' + url;
34
-
35
- const info = normalize(el.querySelector('.subject-abstract, .pl, .pub')?.textContent);
36
- const infoParts = info.split('/').map((part) => part.trim()).filter(Boolean);
37
- const ratingText = normalize(el.querySelector('.subject-rating .font-small, .rating_nums, .rating')?.textContent);
38
- const quote = Array.from(el.querySelectorAll('.subject-tags .tag'))
39
- .map((node) => normalize(node.textContent))
40
- .filter(Boolean)
41
- .join(' / ');
42
-
43
- books.push({
44
- rank: parseInt(normalize(el.querySelector('.green-num-box')?.textContent), 10) || books.length + 1,
45
- title,
46
- rating: parseFloat(ratingText) || 0,
47
- quote,
48
- author: infoParts[0] || '',
49
- publisher: infoParts.find((part) => /出版社|出版公司|Press/i.test(part)) || infoParts[2] || '',
50
- year: infoParts.find((part) => /\\d{4}(?:-\\d{1,2})?/.test(part))?.match(/\\d{4}/)?.[0] || '',
51
- price: infoParts.find((part) => /元|USD|\\$|¥/.test(part)) || '',
52
- url,
53
- cover: el.querySelector('img')?.getAttribute('src') || '',
54
- });
55
- } catch {}
56
- }
57
- return books.slice(0, ${safeLimit});
58
- })()
59
- `);
60
- return Array.isArray(data) ? data : [];
61
- }
62
- export async function loadDoubanMovieHot(page, limit) {
63
- const safeLimit = clampLimit(limit);
64
- await page.goto('https://movie.douban.com/chart');
65
- await page.wait(4);
66
- await ensureDoubanReady(page);
67
- const data = await page.evaluate(`
68
- (() => {
69
- const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
70
- const results = [];
71
- for (const el of Array.from(document.querySelectorAll('.item'))) {
72
- const titleEl = el.querySelector('.pl2 a');
73
- const title = normalize(titleEl?.textContent);
74
- let url = titleEl?.getAttribute('href') || '';
75
- if (!title || !url) continue;
76
- if (!url.startsWith('http')) url = 'https://movie.douban.com' + url;
77
-
78
- const info = normalize(el.querySelector('.pl2 p')?.textContent);
79
- const infoParts = info.split('/').map((part) => part.trim()).filter(Boolean);
80
- const releaseIndex = (() => {
81
- for (let i = infoParts.length - 1; i >= 0; i -= 1) {
82
- if (/\\d{4}-\\d{2}-\\d{2}|\\d{4}\\/\\d{2}\\/\\d{2}/.test(infoParts[i])) return i;
83
- }
84
- return -1;
85
- })();
86
- const directorPart = releaseIndex >= 1 ? infoParts[releaseIndex - 1] : '';
87
- const regionPart = releaseIndex >= 2 ? infoParts[releaseIndex - 2] : '';
88
- const yearMatch = info.match(/\\b(19|20)\\d{2}\\b/);
89
- results.push({
90
- rank: results.length + 1,
91
- title,
92
- rating: parseFloat(normalize(el.querySelector('.rating_nums')?.textContent)) || 0,
93
- quote: normalize(el.querySelector('.inq')?.textContent),
94
- director: directorPart.replace(/^导演:\\s*/, ''),
95
- year: yearMatch?.[0] || '',
96
- region: regionPart,
97
- url,
98
- cover: el.querySelector('img')?.getAttribute('src') || '',
99
- });
100
- if (results.length >= ${safeLimit}) break;
101
- }
102
- return results;
103
- })()
104
- `);
105
- return Array.isArray(data) ? data : [];
106
- }
107
- export async function searchDouban(page, type, keyword, limit) {
108
- const safeLimit = clampLimit(limit);
109
- await page.goto(`https://search.douban.com/${encodeURIComponent(type)}/subject_search?search_text=${encodeURIComponent(keyword)}`);
110
- await page.wait(2);
111
- await ensureDoubanReady(page);
112
- const data = await page.evaluate(`
113
- (async () => {
114
- const type = ${JSON.stringify(type)};
115
- const normalize = (value) => (value || '').replace(/\\s+/g, ' ').trim();
116
- const seen = new Set();
117
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
118
-
119
- for (let i = 0; i < 20; i += 1) {
120
- if (document.querySelector('.item-root .title-text, .item-root .title a')) break;
121
- await sleep(300);
122
- }
123
-
124
- const items = Array.from(document.querySelectorAll('.item-root'));
125
-
126
- const results = [];
127
- for (const el of items) {
128
- const titleEl = el.querySelector('.title-text, .title a, a[title]');
129
- const title = normalize(titleEl?.textContent) || normalize(titleEl?.getAttribute('title'));
130
- let url = titleEl?.getAttribute('href') || '';
131
- if (!title || !url) continue;
132
- if (!url.startsWith('http')) url = 'https://search.douban.com' + url;
133
- if (!url.includes('/subject/') || seen.has(url)) continue;
134
- seen.add(url);
135
- const ratingText = normalize(el.querySelector('.rating_nums')?.textContent);
136
- const abstract = normalize(
137
- el.querySelector('.meta.abstract, .meta, .abstract, p')?.textContent,
138
- );
139
- results.push({
140
- rank: results.length + 1,
141
- id: url.match(/subject\\/(\\d+)/)?.[1] || '',
142
- type,
143
- title,
144
- rating: ratingText.includes('.') ? parseFloat(ratingText) : 0,
145
- abstract: abstract.slice(0, 100) + (abstract.length > 100 ? '...' : ''),
146
- url,
147
- cover: el.querySelector('img')?.getAttribute('src') || '',
148
- });
149
- if (results.length >= ${safeLimit}) break;
150
- }
151
- return results;
152
- })()
153
- `);
154
- return Array.isArray(data) ? data : [];
155
- }