@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,3 +1,4 @@
1
+ import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  cli({
3
4
  site: 'twitter',
@@ -21,7 +22,7 @@ cli({
21
22
  return link ? link.getAttribute('href') : null;
22
23
  }`);
23
24
  if (!href)
24
- throw new Error('Could not detect logged-in user. Are you logged in?');
25
+ throw new AuthRequiredError('x.com', 'Could not detect logged-in user. Are you logged in?');
25
26
  username = href.replace('/', '');
26
27
  }
27
28
  // Navigate directly to the user's profile page (gives us cookie context)
@@ -117,7 +118,9 @@ cli({
117
118
  }
118
119
  `);
119
120
  if (result?.error) {
120
- throw new Error(result.error + (result.hint ? ` (${result.hint})` : ''));
121
+ if (String(result.error).includes('No ct0 cookie'))
122
+ throw new AuthRequiredError('x.com', result.error);
123
+ throw new CommandExecutionError(result.error + (result.hint ? ` (${result.hint})` : ''));
121
124
  }
122
125
  return result || [];
123
126
  }
@@ -1,3 +1,4 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  cli({
3
4
  site: 'twitter',
@@ -15,7 +16,7 @@ cli({
15
16
  columns: ['index', 'status', 'user', 'message'],
16
17
  func: async (page, kwargs) => {
17
18
  if (!page)
18
- throw new Error('Requires browser');
19
+ throw new CommandExecutionError('Browser session required for twitter reply-dm');
19
20
  const messageText = kwargs.text;
20
21
  const maxSend = kwargs.max ?? 20;
21
22
  const skipReplied = kwargs['skip-replied'] !== false;
@@ -1,3 +1,4 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  cli({
3
4
  site: 'twitter',
@@ -13,7 +14,7 @@ cli({
13
14
  columns: ['status', 'message', 'text'],
14
15
  func: async (page, kwargs) => {
15
16
  if (!page)
16
- throw new Error('Requires browser');
17
+ throw new CommandExecutionError('Browser session required for twitter reply');
17
18
  // 1. Navigate to the tweet page
18
19
  await page.goto(kwargs.url);
19
20
  await page.wait(5); // Wait for the react application to hydrate
@@ -1,4 +1,33 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
3
+ /**
4
+ * Trigger Twitter search SPA navigation and retry once on transient failures.
5
+ *
6
+ * Twitter/X sometimes keeps the page on /explore for a short period even after
7
+ * pushState + popstate. A second attempt is enough for the intermittent cases
8
+ * reported in issue #353 while keeping the flow narrowly scoped.
9
+ */
10
+ async function navigateToSearch(page, query, filter) {
11
+ const searchUrl = JSON.stringify(`/search?q=${encodeURIComponent(query)}&f=${filter}`);
12
+ let lastPath = '';
13
+ for (let attempt = 1; attempt <= 2; attempt++) {
14
+ await page.evaluate(`
15
+ (() => {
16
+ window.history.pushState({}, '', ${searchUrl});
17
+ window.dispatchEvent(new PopStateEvent('popstate', { state: {} }));
18
+ })()
19
+ `);
20
+ await page.wait(5);
21
+ lastPath = String(await page.evaluate('() => window.location.pathname') || '');
22
+ if (lastPath.startsWith('/search')) {
23
+ return;
24
+ }
25
+ if (attempt < 2) {
26
+ await page.wait(1);
27
+ }
28
+ }
29
+ throw new CommandExecutionError(`SPA navigation to /search failed. Final path: ${lastPath || '(empty)'}. Twitter may have changed its routing.`);
30
+ }
2
31
  cli({
3
32
  site: 'twitter',
4
33
  name: 'search',
@@ -8,11 +37,13 @@ cli({
8
37
  browser: true,
9
38
  args: [
10
39
  { name: 'query', type: 'string', required: true, positional: true },
40
+ { name: 'filter', type: 'string', default: 'top', choices: ['top', 'live'] },
11
41
  { name: 'limit', type: 'int', default: 15 },
12
42
  ],
13
43
  columns: ['id', 'author', 'text', 'likes', 'views', 'url'],
14
44
  func: async (page, kwargs) => {
15
45
  const query = kwargs.query;
46
+ const filter = kwargs.filter === 'live' ? 'live' : 'top';
16
47
  // 1. Navigate to x.com/explore (has a search input at the top)
17
48
  await page.goto('https://x.com/explore');
18
49
  await page.wait(3);
@@ -25,19 +56,7 @@ cli({
25
56
  // a full page reload, so the interceptor stays alive.
26
57
  // Note: the previous approach (nativeSetter + Enter keydown on the
27
58
  // search input) does not reliably trigger Twitter's form submission.
28
- const searchUrl = JSON.stringify(`/search?q=${encodeURIComponent(query)}&f=top`);
29
- await page.evaluate(`
30
- (() => {
31
- window.history.pushState({}, '', ${searchUrl});
32
- window.dispatchEvent(new PopStateEvent('popstate', { state: {} }));
33
- })()
34
- `);
35
- await page.wait(5);
36
- // Verify SPA navigation succeeded
37
- const currentPath = await page.evaluate('() => window.location.pathname');
38
- if (!currentPath?.startsWith('/search')) {
39
- throw new Error('SPA navigation to /search failed. Twitter may have changed its routing.');
40
- }
59
+ await navigateToSearch(page, query, filter);
41
60
  // 4. Scroll to trigger additional pagination
42
61
  await page.autoScroll({ times: 3, delayMs: 2000 });
43
62
  // 6. Retrieve captured data
@@ -0,0 +1 @@
1
+ import './search.js';
@@ -0,0 +1,156 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { getRegistry } from '../../registry.js';
3
+ import './search.js';
4
+ describe('twitter search command', () => {
5
+ it('retries transient SPA navigation failures before giving up', async () => {
6
+ const command = getRegistry().get('twitter/search');
7
+ expect(command?.func).toBeTypeOf('function');
8
+ const evaluate = vi.fn()
9
+ .mockResolvedValueOnce(undefined)
10
+ .mockResolvedValueOnce('/explore')
11
+ .mockResolvedValueOnce(undefined)
12
+ .mockResolvedValueOnce('/search');
13
+ const page = {
14
+ goto: vi.fn().mockResolvedValue(undefined),
15
+ wait: vi.fn().mockResolvedValue(undefined),
16
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
17
+ evaluate,
18
+ autoScroll: vi.fn().mockResolvedValue(undefined),
19
+ getInterceptedRequests: vi.fn().mockResolvedValue([
20
+ {
21
+ data: {
22
+ search_by_raw_query: {
23
+ search_timeline: {
24
+ timeline: {
25
+ instructions: [
26
+ {
27
+ type: 'TimelineAddEntries',
28
+ entries: [
29
+ {
30
+ entryId: 'tweet-1',
31
+ content: {
32
+ itemContent: {
33
+ tweet_results: {
34
+ result: {
35
+ rest_id: '1',
36
+ legacy: {
37
+ full_text: 'hello world',
38
+ favorite_count: 7,
39
+ },
40
+ core: {
41
+ user_results: {
42
+ result: {
43
+ core: {
44
+ screen_name: 'alice',
45
+ },
46
+ },
47
+ },
48
+ },
49
+ views: {
50
+ count: '12',
51
+ },
52
+ },
53
+ },
54
+ },
55
+ },
56
+ },
57
+ ],
58
+ },
59
+ ],
60
+ },
61
+ },
62
+ },
63
+ },
64
+ },
65
+ ]),
66
+ };
67
+ const result = await command.func(page, { query: 'from:alice', filter: 'top', limit: 5 });
68
+ expect(result).toEqual([
69
+ {
70
+ id: '1',
71
+ author: 'alice',
72
+ text: 'hello world',
73
+ likes: 7,
74
+ views: '12',
75
+ url: 'https://x.com/i/status/1',
76
+ },
77
+ ]);
78
+ expect(page.installInterceptor).toHaveBeenCalledWith('SearchTimeline');
79
+ expect(evaluate).toHaveBeenCalledTimes(4);
80
+ });
81
+ it('uses f=live in search URL when filter is live', async () => {
82
+ const command = getRegistry().get('twitter/search');
83
+ const evaluate = vi.fn()
84
+ .mockResolvedValueOnce(undefined)
85
+ .mockResolvedValueOnce('/search');
86
+ const page = {
87
+ goto: vi.fn().mockResolvedValue(undefined),
88
+ wait: vi.fn().mockResolvedValue(undefined),
89
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
90
+ evaluate,
91
+ autoScroll: vi.fn().mockResolvedValue(undefined),
92
+ getInterceptedRequests: vi.fn().mockResolvedValue([]),
93
+ };
94
+ await command.func(page, { query: 'breaking news', filter: 'live', limit: 5 });
95
+ const pushStateCall = evaluate.mock.calls[0][0];
96
+ expect(pushStateCall).toContain('f=live');
97
+ expect(pushStateCall).toContain(encodeURIComponent('breaking news'));
98
+ });
99
+ it('uses f=top in search URL when filter is top', async () => {
100
+ const command = getRegistry().get('twitter/search');
101
+ const evaluate = vi.fn()
102
+ .mockResolvedValueOnce(undefined)
103
+ .mockResolvedValueOnce('/search');
104
+ const page = {
105
+ goto: vi.fn().mockResolvedValue(undefined),
106
+ wait: vi.fn().mockResolvedValue(undefined),
107
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
108
+ evaluate,
109
+ autoScroll: vi.fn().mockResolvedValue(undefined),
110
+ getInterceptedRequests: vi.fn().mockResolvedValue([]),
111
+ };
112
+ await command.func(page, { query: 'test', filter: 'top', limit: 5 });
113
+ const pushStateCall = evaluate.mock.calls[0][0];
114
+ expect(pushStateCall).toContain('f=top');
115
+ });
116
+ it('falls back to top when filter is omitted', async () => {
117
+ const command = getRegistry().get('twitter/search');
118
+ const evaluate = vi.fn()
119
+ .mockResolvedValueOnce(undefined)
120
+ .mockResolvedValueOnce('/search');
121
+ const page = {
122
+ goto: vi.fn().mockResolvedValue(undefined),
123
+ wait: vi.fn().mockResolvedValue(undefined),
124
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
125
+ evaluate,
126
+ autoScroll: vi.fn().mockResolvedValue(undefined),
127
+ getInterceptedRequests: vi.fn().mockResolvedValue([]),
128
+ };
129
+ await command.func(page, { query: 'test', limit: 5 });
130
+ const pushStateCall = evaluate.mock.calls[0][0];
131
+ expect(pushStateCall).toContain('f=top');
132
+ });
133
+ it('throws with the final path after both attempts fail', async () => {
134
+ const command = getRegistry().get('twitter/search');
135
+ expect(command?.func).toBeTypeOf('function');
136
+ const evaluate = vi.fn()
137
+ .mockResolvedValueOnce(undefined)
138
+ .mockResolvedValueOnce('/explore')
139
+ .mockResolvedValueOnce(undefined)
140
+ .mockResolvedValueOnce('/login');
141
+ const page = {
142
+ goto: vi.fn().mockResolvedValue(undefined),
143
+ wait: vi.fn().mockResolvedValue(undefined),
144
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
145
+ evaluate,
146
+ autoScroll: vi.fn().mockResolvedValue(undefined),
147
+ getInterceptedRequests: vi.fn(),
148
+ };
149
+ await expect(command.func(page, { query: 'from:alice', filter: 'top', limit: 5 }))
150
+ .rejects
151
+ .toThrow('Final path: /login');
152
+ expect(page.autoScroll).not.toHaveBeenCalled();
153
+ expect(page.getInterceptedRequests).not.toHaveBeenCalled();
154
+ expect(evaluate).toHaveBeenCalledTimes(4);
155
+ });
156
+ });
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { AuthRequiredError } from '../../errors.js';
2
+ import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
3
3
  // ── Twitter GraphQL constants ──────────────────────────────────────────
4
4
  const BEARER_TOKEN = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
5
5
  const TWEET_DETAIL_QUERY_ID = 'nBS-WpgA6ZG0CyNHD517JQ';
@@ -136,7 +136,7 @@ cli({
136
136
  }`);
137
137
  if (data?.error) {
138
138
  if (allTweets.length === 0)
139
- throw new Error(`HTTP ${data.error}: Tweet not found or queryId expired`);
139
+ throw new CommandExecutionError(`HTTP ${data.error}: Tweet not found or queryId expired`);
140
140
  break;
141
141
  }
142
142
  // TypeScript-side: type-safe parsing + cursor extraction
@@ -1,3 +1,4 @@
1
+ import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  // ── Twitter GraphQL constants ──────────────────────────────────────────
3
4
  const BEARER_TOKEN = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
@@ -159,7 +160,7 @@ cli({
159
160
  return document.cookie.split(';').map(c=>c.trim()).find(c=>c.startsWith('ct0='))?.split('=')[1] || null;
160
161
  }`);
161
162
  if (!ct0)
162
- throw new Error('Not logged into x.com (no ct0 cookie)');
163
+ throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');
163
164
  // Dynamically resolve queryId for the selected endpoint
164
165
  const resolved = await page.evaluate(`async () => {
165
166
  try {
@@ -195,7 +196,7 @@ cli({
195
196
  }`);
196
197
  if (data?.error) {
197
198
  if (allTweets.length === 0)
198
- throw new Error(`HTTP ${data.error}: Failed to fetch timeline. queryId may have expired.`);
199
+ throw new CommandExecutionError(`HTTP ${data.error}: Failed to fetch timeline. queryId may have expired.`);
199
200
  break;
200
201
  }
201
202
  const { tweets, nextCursor } = parseHomeTimeline(data, seen);
@@ -1,4 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import { AuthRequiredError, EmptyResultError } from '../../errors.js';
2
3
  // ── Twitter GraphQL constants ──────────────────────────────────────────
3
4
  const BEARER_TOKEN = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
4
5
  // ── CLI definition ────────────────────────────────────────────────────
@@ -23,7 +24,7 @@ cli({
23
24
  return document.cookie.split(';').map(c=>c.trim()).find(c=>c.startsWith('ct0='))?.split('=')[1] || null;
24
25
  })()`);
25
26
  if (!ct0)
26
- throw new Error('Not logged into x.com (no ct0 cookie)');
27
+ throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');
27
28
  // Try legacy guide.json API first (faster than DOM scraping)
28
29
  let trends = [];
29
30
  const apiData = await page.evaluate(`(async () => {
@@ -84,7 +85,7 @@ cli({
84
85
  }
85
86
  }
86
87
  if (trends.length === 0) {
87
- throw new Error('No trending data found. API may have changed or login may be required.');
88
+ throw new EmptyResultError('twitter trending', 'API may have changed or login may be required.');
88
89
  }
89
90
  return trends.slice(0, limit);
90
91
  },
@@ -1,3 +1,4 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  cli({
3
4
  site: 'twitter',
@@ -12,7 +13,7 @@ cli({
12
13
  columns: ['status', 'message'],
13
14
  func: async (page, kwargs) => {
14
15
  if (!page)
15
- throw new Error('Requires browser');
16
+ throw new CommandExecutionError('Browser session required for twitter unblock');
16
17
  const username = kwargs.username.replace(/^@/, '');
17
18
  await page.goto(`https://x.com/${username}`);
18
19
  await page.wait(5);
@@ -1,3 +1,4 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  cli({
3
4
  site: 'twitter',
@@ -12,7 +13,7 @@ cli({
12
13
  columns: ['status', 'message'],
13
14
  func: async (page, kwargs) => {
14
15
  if (!page)
15
- throw new Error('Requires browser');
16
+ throw new CommandExecutionError('Browser session required for twitter unbookmark');
16
17
  await page.goto(kwargs.url);
17
18
  await page.wait(5);
18
19
  const result = await page.evaluate(`(async () => {
@@ -1,4 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import { CommandExecutionError } from '../../errors.js';
2
3
  cli({
3
4
  site: 'twitter',
4
5
  name: 'unfollow',
@@ -12,7 +13,7 @@ cli({
12
13
  columns: ['status', 'message'],
13
14
  func: async (page, kwargs) => {
14
15
  if (!page)
15
- throw new Error('Requires browser');
16
+ throw new CommandExecutionError('Browser session required for twitter unfollow');
16
17
  const username = kwargs.username.replace(/^@/, '');
17
18
  await page.goto(`https://x.com/${username}`);
18
19
  await page.wait(5);
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * V2EX Daily Check-in adapter.
3
3
  */
4
+ import { CommandExecutionError } from '../../errors.js';
4
5
  import { cli, Strategy } from '../../registry.js';
5
6
  cli({
6
7
  site: 'v2ex',
@@ -13,7 +14,7 @@ cli({
13
14
  columns: ['status', 'message'],
14
15
  func: async (page) => {
15
16
  if (!page)
16
- throw new Error('Browser page required');
17
+ throw new CommandExecutionError('Browser page required');
17
18
  if (process.env.OPENCLI_VERBOSE) {
18
19
  console.error('[opencli:v2ex] Navigating to /mission/daily');
19
20
  }
@@ -56,7 +57,7 @@ cli({
56
57
  console.error(`[opencli:v2ex:debug] Page Title: ${checkResult.debug_title}`);
57
58
  console.error(`[opencli:v2ex:debug] Page Body: ${checkResult.debug_body}`);
58
59
  }
59
- throw new Error(checkResult.error);
60
+ throw new CommandExecutionError(checkResult.error);
60
61
  }
61
62
  if (checkResult.claimed) {
62
63
  return [{ status: '✅ 已签到', message: checkResult.message }];
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * V2EX Me (Profile/Balance) adapter.
3
3
  */
4
+ import { CommandExecutionError } from '../../errors.js';
4
5
  import { cli, Strategy } from '../../registry.js';
5
6
  cli({
6
7
  site: 'v2ex',
@@ -13,7 +14,7 @@ cli({
13
14
  columns: ['username', 'balance', 'unread_notifications', 'daily_reward_ready'],
14
15
  func: async (page) => {
15
16
  if (!page)
16
- throw new Error('Browser page required');
17
+ throw new CommandExecutionError('Browser page required');
17
18
  if (process.env.OPENCLI_VERBOSE) {
18
19
  console.error('[opencli:v2ex] Navigating to /');
19
20
  }
@@ -91,7 +92,7 @@ cli({
91
92
  console.error(`[opencli:v2ex:debug] Page Title: ${data.debug_title}`);
92
93
  console.error(`[opencli:v2ex:debug] Page Body: ${data.debug_body}`);
93
94
  }
94
- throw new Error(data.error);
95
+ throw new CommandExecutionError(data.error);
95
96
  }
96
97
  return [data];
97
98
  },
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * V2EX Notifications adapter.
3
3
  */
4
+ import { CommandExecutionError } from '../../errors.js';
4
5
  import { cli, Strategy } from '../../registry.js';
5
6
  cli({
6
7
  site: 'v2ex',
@@ -15,7 +16,7 @@ cli({
15
16
  columns: ['type', 'content', 'time'],
16
17
  func: async (page, kwargs) => {
17
18
  if (!page)
18
- throw new Error('Browser page required');
19
+ throw new CommandExecutionError('Browser page required');
19
20
  if (process.env.OPENCLI_VERBOSE) {
20
21
  console.error('[opencli:v2ex] Navigating to /notifications');
21
22
  }
@@ -62,9 +63,8 @@ cli({
62
63
  });
63
64
  }
64
65
  `);
65
- if (!Array.isArray(data)) {
66
- throw new Error('Failed to parse notifications data');
67
- }
66
+ if (!Array.isArray(data))
67
+ throw new CommandExecutionError('Failed to parse notifications data');
68
68
  const limit = kwargs.limit || 20;
69
69
  return data.slice(0, limit);
70
70
  },
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Generic web page reader — fetch any URL and export as Markdown.
3
+ *
4
+ * Uses browser-side DOM heuristics to extract the main content:
5
+ * 1. <article> element
6
+ * 2. [role="main"] element
7
+ * 3. <main> element
8
+ * 4. Largest text-dense block as fallback
9
+ *
10
+ * Pipes through the shared article-download pipeline (Turndown + image download).
11
+ *
12
+ * Usage:
13
+ * opencli web read --url "https://www.anthropic.com/research/..." --output ./articles
14
+ * opencli web read --url "https://..." --download-images false
15
+ */
16
+ export {};