@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
@@ -0,0 +1,180 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { getRegistry } from '../../registry.js';
3
+ import './search.js';
4
+
5
+ describe('twitter search command', () => {
6
+ it('retries transient SPA navigation failures before giving up', async () => {
7
+ const command = getRegistry().get('twitter/search');
8
+ expect(command?.func).toBeTypeOf('function');
9
+
10
+ const evaluate = vi.fn()
11
+ .mockResolvedValueOnce(undefined)
12
+ .mockResolvedValueOnce('/explore')
13
+ .mockResolvedValueOnce(undefined)
14
+ .mockResolvedValueOnce('/search');
15
+
16
+ const page = {
17
+ goto: vi.fn().mockResolvedValue(undefined),
18
+ wait: vi.fn().mockResolvedValue(undefined),
19
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
20
+ evaluate,
21
+ autoScroll: vi.fn().mockResolvedValue(undefined),
22
+ getInterceptedRequests: vi.fn().mockResolvedValue([
23
+ {
24
+ data: {
25
+ search_by_raw_query: {
26
+ search_timeline: {
27
+ timeline: {
28
+ instructions: [
29
+ {
30
+ type: 'TimelineAddEntries',
31
+ entries: [
32
+ {
33
+ entryId: 'tweet-1',
34
+ content: {
35
+ itemContent: {
36
+ tweet_results: {
37
+ result: {
38
+ rest_id: '1',
39
+ legacy: {
40
+ full_text: 'hello world',
41
+ favorite_count: 7,
42
+ },
43
+ core: {
44
+ user_results: {
45
+ result: {
46
+ core: {
47
+ screen_name: 'alice',
48
+ },
49
+ },
50
+ },
51
+ },
52
+ views: {
53
+ count: '12',
54
+ },
55
+ },
56
+ },
57
+ },
58
+ },
59
+ },
60
+ ],
61
+ },
62
+ ],
63
+ },
64
+ },
65
+ },
66
+ },
67
+ },
68
+ ]),
69
+ };
70
+
71
+ const result = await command!.func!(page as any, { query: 'from:alice', filter: 'top', limit: 5 });
72
+
73
+ expect(result).toEqual([
74
+ {
75
+ id: '1',
76
+ author: 'alice',
77
+ text: 'hello world',
78
+ likes: 7,
79
+ views: '12',
80
+ url: 'https://x.com/i/status/1',
81
+ },
82
+ ]);
83
+ expect(page.installInterceptor).toHaveBeenCalledWith('SearchTimeline');
84
+ expect(evaluate).toHaveBeenCalledTimes(4);
85
+ });
86
+
87
+ it('uses f=live in search URL when filter is live', async () => {
88
+ const command = getRegistry().get('twitter/search');
89
+
90
+ const evaluate = vi.fn()
91
+ .mockResolvedValueOnce(undefined)
92
+ .mockResolvedValueOnce('/search');
93
+
94
+ const page = {
95
+ goto: vi.fn().mockResolvedValue(undefined),
96
+ wait: vi.fn().mockResolvedValue(undefined),
97
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
98
+ evaluate,
99
+ autoScroll: vi.fn().mockResolvedValue(undefined),
100
+ getInterceptedRequests: vi.fn().mockResolvedValue([]),
101
+ };
102
+
103
+ await command!.func!(page as any, { query: 'breaking news', filter: 'live', limit: 5 });
104
+
105
+ const pushStateCall = evaluate.mock.calls[0][0] as string;
106
+ expect(pushStateCall).toContain('f=live');
107
+ expect(pushStateCall).toContain(encodeURIComponent('breaking news'));
108
+ });
109
+
110
+ it('uses f=top in search URL when filter is top', async () => {
111
+ const command = getRegistry().get('twitter/search');
112
+
113
+ const evaluate = vi.fn()
114
+ .mockResolvedValueOnce(undefined)
115
+ .mockResolvedValueOnce('/search');
116
+
117
+ const page = {
118
+ goto: vi.fn().mockResolvedValue(undefined),
119
+ wait: vi.fn().mockResolvedValue(undefined),
120
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
121
+ evaluate,
122
+ autoScroll: vi.fn().mockResolvedValue(undefined),
123
+ getInterceptedRequests: vi.fn().mockResolvedValue([]),
124
+ };
125
+
126
+ await command!.func!(page as any, { query: 'test', filter: 'top', limit: 5 });
127
+
128
+ const pushStateCall = evaluate.mock.calls[0][0] as string;
129
+ expect(pushStateCall).toContain('f=top');
130
+ });
131
+
132
+ it('falls back to top when filter is omitted', async () => {
133
+ const command = getRegistry().get('twitter/search');
134
+
135
+ const evaluate = vi.fn()
136
+ .mockResolvedValueOnce(undefined)
137
+ .mockResolvedValueOnce('/search');
138
+
139
+ const page = {
140
+ goto: vi.fn().mockResolvedValue(undefined),
141
+ wait: vi.fn().mockResolvedValue(undefined),
142
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
143
+ evaluate,
144
+ autoScroll: vi.fn().mockResolvedValue(undefined),
145
+ getInterceptedRequests: vi.fn().mockResolvedValue([]),
146
+ };
147
+
148
+ await command!.func!(page as any, { query: 'test', limit: 5 });
149
+
150
+ const pushStateCall = evaluate.mock.calls[0][0] as string;
151
+ expect(pushStateCall).toContain('f=top');
152
+ });
153
+
154
+ it('throws with the final path after both attempts fail', async () => {
155
+ const command = getRegistry().get('twitter/search');
156
+ expect(command?.func).toBeTypeOf('function');
157
+
158
+ const evaluate = vi.fn()
159
+ .mockResolvedValueOnce(undefined)
160
+ .mockResolvedValueOnce('/explore')
161
+ .mockResolvedValueOnce(undefined)
162
+ .mockResolvedValueOnce('/login');
163
+
164
+ const page = {
165
+ goto: vi.fn().mockResolvedValue(undefined),
166
+ wait: vi.fn().mockResolvedValue(undefined),
167
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
168
+ evaluate,
169
+ autoScroll: vi.fn().mockResolvedValue(undefined),
170
+ getInterceptedRequests: vi.fn(),
171
+ };
172
+
173
+ await expect(command!.func!(page as any, { query: 'from:alice', filter: 'top', limit: 5 }))
174
+ .rejects
175
+ .toThrow('Final path: /login');
176
+ expect(page.autoScroll).not.toHaveBeenCalled();
177
+ expect(page.getInterceptedRequests).not.toHaveBeenCalled();
178
+ expect(evaluate).toHaveBeenCalledTimes(4);
179
+ });
180
+ });
@@ -1,4 +1,41 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
3
+ import type { IPage } from '../../types.js';
4
+
5
+ /**
6
+ * Trigger Twitter search SPA navigation and retry once on transient failures.
7
+ *
8
+ * Twitter/X sometimes keeps the page on /explore for a short period even after
9
+ * pushState + popstate. A second attempt is enough for the intermittent cases
10
+ * reported in issue #353 while keeping the flow narrowly scoped.
11
+ */
12
+ async function navigateToSearch(page: Pick<IPage, 'evaluate' | 'wait'>, query: string, filter: string): Promise<void> {
13
+ const searchUrl = JSON.stringify(`/search?q=${encodeURIComponent(query)}&f=${filter}`);
14
+ let lastPath = '';
15
+
16
+ for (let attempt = 1; attempt <= 2; attempt++) {
17
+ await page.evaluate(`
18
+ (() => {
19
+ window.history.pushState({}, '', ${searchUrl});
20
+ window.dispatchEvent(new PopStateEvent('popstate', { state: {} }));
21
+ })()
22
+ `);
23
+ await page.wait(5);
24
+
25
+ lastPath = String(await page.evaluate('() => window.location.pathname') || '');
26
+ if (lastPath.startsWith('/search')) {
27
+ return;
28
+ }
29
+
30
+ if (attempt < 2) {
31
+ await page.wait(1);
32
+ }
33
+ }
34
+
35
+ throw new CommandExecutionError(
36
+ `SPA navigation to /search failed. Final path: ${lastPath || '(empty)'}. Twitter may have changed its routing.`,
37
+ );
38
+ }
2
39
 
3
40
  cli({
4
41
  site: 'twitter',
@@ -9,11 +46,13 @@ cli({
9
46
  browser: true,
10
47
  args: [
11
48
  { name: 'query', type: 'string', required: true, positional: true },
49
+ { name: 'filter', type: 'string', default: 'top', choices: ['top', 'live'] },
12
50
  { name: 'limit', type: 'int', default: 15 },
13
51
  ],
14
52
  columns: ['id', 'author', 'text', 'likes', 'views', 'url'],
15
53
  func: async (page, kwargs) => {
16
54
  const query = kwargs.query;
55
+ const filter = kwargs.filter === 'live' ? 'live' : 'top';
17
56
 
18
57
  // 1. Navigate to x.com/explore (has a search input at the top)
19
58
  await page.goto('https://x.com/explore');
@@ -29,20 +68,7 @@ cli({
29
68
  // a full page reload, so the interceptor stays alive.
30
69
  // Note: the previous approach (nativeSetter + Enter keydown on the
31
70
  // search input) does not reliably trigger Twitter's form submission.
32
- const searchUrl = JSON.stringify(`/search?q=${encodeURIComponent(query)}&f=top`);
33
- await page.evaluate(`
34
- (() => {
35
- window.history.pushState({}, '', ${searchUrl});
36
- window.dispatchEvent(new PopStateEvent('popstate', { state: {} }));
37
- })()
38
- `);
39
- await page.wait(5);
40
-
41
- // Verify SPA navigation succeeded
42
- const currentPath = await page.evaluate('() => window.location.pathname');
43
- if (!currentPath?.startsWith('/search')) {
44
- throw new Error('SPA navigation to /search failed. Twitter may have changed its routing.');
45
- }
71
+ await navigateToSearch(page, query, filter);
46
72
 
47
73
  // 4. Scroll to trigger additional pagination
48
74
  await page.autoScroll({ times: 3, delayMs: 2000 });
@@ -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
 
4
4
  // ── Twitter GraphQL constants ──────────────────────────────────────────
5
5
 
@@ -165,7 +165,7 @@ cli({
165
165
  }`);
166
166
 
167
167
  if (data?.error) {
168
- if (allTweets.length === 0) throw new Error(`HTTP ${data.error}: Tweet not found or queryId expired`);
168
+ if (allTweets.length === 0) throw new CommandExecutionError(`HTTP ${data.error}: Tweet not found or queryId expired`);
169
169
  break;
170
170
  }
171
171
 
@@ -1,3 +1,4 @@
1
+ import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
 
3
4
  // ── Twitter GraphQL constants ──────────────────────────────────────────
@@ -194,7 +195,7 @@ cli({
194
195
  const ct0 = await page.evaluate(`() => {
195
196
  return document.cookie.split(';').map(c=>c.trim()).find(c=>c.startsWith('ct0='))?.split('=')[1] || null;
196
197
  }`);
197
- if (!ct0) throw new Error('Not logged into x.com (no ct0 cookie)');
198
+ if (!ct0) throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');
198
199
 
199
200
  // Dynamically resolve queryId for the selected endpoint
200
201
  const resolved = await page.evaluate(`async () => {
@@ -236,7 +237,7 @@ cli({
236
237
 
237
238
  if (data?.error) {
238
239
  if (allTweets.length === 0)
239
- throw new Error(`HTTP ${data.error}: Failed to fetch timeline. queryId may have expired.`);
240
+ throw new CommandExecutionError(`HTTP ${data.error}: Failed to fetch timeline. queryId may have expired.`);
240
241
  break;
241
242
  }
242
243
 
@@ -1,4 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import { AuthRequiredError, EmptyResultError } from '../../errors.js';
2
3
 
3
4
  // ── Twitter GraphQL constants ──────────────────────────────────────────
4
5
 
@@ -37,7 +38,7 @@ cli({
37
38
  const ct0 = await page.evaluate(`(() => {
38
39
  return document.cookie.split(';').map(c=>c.trim()).find(c=>c.startsWith('ct0='))?.split('=')[1] || null;
39
40
  })()`);
40
- if (!ct0) throw new Error('Not logged into x.com (no ct0 cookie)');
41
+ if (!ct0) throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');
41
42
 
42
43
  // Try legacy guide.json API first (faster than DOM scraping)
43
44
  let trends: TrendItem[] = [];
@@ -105,7 +106,7 @@ cli({
105
106
  }
106
107
 
107
108
  if (trends.length === 0) {
108
- throw new Error('No trending data found. API may have changed or login may be required.');
109
+ throw new EmptyResultError('twitter trending', 'API may have changed or login may be required.');
109
110
  }
110
111
 
111
112
  return trends.slice(0, limit);
@@ -1,3 +1,4 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  import type { IPage } from '../../types.js';
3
4
 
@@ -13,7 +14,7 @@ cli({
13
14
  ],
14
15
  columns: ['status', 'message'],
15
16
  func: async (page: IPage | null, kwargs: any) => {
16
- if (!page) throw new Error('Requires browser');
17
+ if (!page) throw new CommandExecutionError('Browser session required for twitter unblock');
17
18
  const username = kwargs.username.replace(/^@/, '');
18
19
 
19
20
  await page.goto(`https://x.com/${username}`);
@@ -1,3 +1,4 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  import type { IPage } from '../../types.js';
3
4
 
@@ -13,7 +14,7 @@ cli({
13
14
  ],
14
15
  columns: ['status', 'message'],
15
16
  func: async (page: IPage | null, kwargs: any) => {
16
- if (!page) throw new Error('Requires browser');
17
+ if (!page) throw new CommandExecutionError('Browser session required for twitter unbookmark');
17
18
 
18
19
  await page.goto(kwargs.url);
19
20
  await page.wait(5);
@@ -1,4 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ import { CommandExecutionError } from '../../errors.js';
2
3
  import type { IPage } from '../../types.js';
3
4
 
4
5
  cli({
@@ -13,7 +14,7 @@ cli({
13
14
  ],
14
15
  columns: ['status', 'message'],
15
16
  func: async (page: IPage | null, kwargs: any) => {
16
- if (!page) throw new Error('Requires browser');
17
+ if (!page) throw new CommandExecutionError('Browser session required for twitter unfollow');
17
18
  const username = kwargs.username.replace(/^@/, '');
18
19
 
19
20
  await page.goto(`https://x.com/${username}`);
@@ -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
  import type { IPage } from '../../types.js';
6
7
 
@@ -15,7 +16,7 @@ cli({
15
16
  args: [],
16
17
  columns: ['status', 'message'],
17
18
  func: async (page: IPage | null) => {
18
- if (!page) throw new Error('Browser page required');
19
+ if (!page) throw new CommandExecutionError('Browser page required');
19
20
 
20
21
  if (process.env.OPENCLI_VERBOSE) {
21
22
  console.error('[opencli:v2ex] Navigating to /mission/daily');
@@ -60,7 +61,7 @@ cli({
60
61
  console.error(`[opencli:v2ex:debug] Page Title: ${checkResult.debug_title}`);
61
62
  console.error(`[opencli:v2ex:debug] Page Body: ${checkResult.debug_body}`);
62
63
  }
63
- throw new Error(checkResult.error);
64
+ throw new CommandExecutionError(checkResult.error);
64
65
  }
65
66
 
66
67
  if (checkResult.claimed) {
@@ -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
  import type { IPage } from '../../types.js';
6
7
 
@@ -15,7 +16,7 @@ cli({
15
16
  args: [],
16
17
  columns: ['username', 'balance', 'unread_notifications', 'daily_reward_ready'],
17
18
  func: async (page: IPage | null) => {
18
- if (!page) throw new Error('Browser page required');
19
+ if (!page) throw new CommandExecutionError('Browser page required');
19
20
 
20
21
  if (process.env.OPENCLI_VERBOSE) {
21
22
  console.error('[opencli:v2ex] Navigating to /');
@@ -95,7 +96,7 @@ cli({
95
96
  console.error(`[opencli:v2ex:debug] Page Title: ${data.debug_title}`);
96
97
  console.error(`[opencli:v2ex:debug] Page Body: ${data.debug_body}`);
97
98
  }
98
- throw new Error(data.error);
99
+ throw new CommandExecutionError(data.error);
99
100
  }
100
101
 
101
102
  return [data];
@@ -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
  import type { IPage } from '../../types.js';
6
7
 
@@ -17,7 +18,7 @@ cli({
17
18
  ],
18
19
  columns: ['type', 'content', 'time'],
19
20
  func: async (page: IPage | null, kwargs) => {
20
- if (!page) throw new Error('Browser page required');
21
+ if (!page) throw new CommandExecutionError('Browser page required');
21
22
 
22
23
  if (process.env.OPENCLI_VERBOSE) {
23
24
  console.error('[opencli:v2ex] Navigating to /notifications');
@@ -67,9 +68,7 @@ cli({
67
68
  }
68
69
  `);
69
70
 
70
- if (!Array.isArray(data)) {
71
- throw new Error('Failed to parse notifications data');
72
- }
71
+ if (!Array.isArray(data)) throw new CommandExecutionError('Failed to parse notifications data');
73
72
 
74
73
  const limit = kwargs.limit || 20;
75
74
  return data.slice(0, limit);
@@ -0,0 +1,210 @@
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
+
17
+ import { cli, Strategy } from '../../registry.js';
18
+ import { downloadArticle } from '../../download/article-download.js';
19
+
20
+ cli({
21
+ site: 'web',
22
+ name: 'read',
23
+ description: 'Fetch any web page and export as Markdown',
24
+ strategy: Strategy.COOKIE,
25
+ navigateBefore: false, // we handle navigation ourselves
26
+ args: [
27
+ { name: 'url', required: true, help: 'Any web page URL' },
28
+ { name: 'output', default: './web-articles', help: 'Output directory' },
29
+ { name: 'download-images', type: 'boolean', default: true, help: 'Download images locally' },
30
+ { name: 'wait', type: 'int', default: 3, help: 'Seconds to wait after page load' },
31
+ ],
32
+ columns: ['title', 'author', 'publish_time', 'status', 'size'],
33
+ func: async (page, kwargs) => {
34
+ const url = kwargs.url;
35
+ const waitSeconds = kwargs.wait ?? 3;
36
+
37
+ // Navigate to the target URL
38
+ await page.goto(url);
39
+ await page.wait(waitSeconds);
40
+
41
+ // Extract article content using browser-side heuristics
42
+ const data = await page.evaluate(`
43
+ (() => {
44
+ const result = {
45
+ title: '',
46
+ author: '',
47
+ publishTime: '',
48
+ contentHtml: '',
49
+ imageUrls: []
50
+ };
51
+
52
+ // --- Title extraction ---
53
+ // Priority: og:title > <title> > first <h1>
54
+ const ogTitle = document.querySelector('meta[property="og:title"]');
55
+ if (ogTitle) {
56
+ result.title = ogTitle.getAttribute('content')?.trim() || '';
57
+ }
58
+ if (!result.title) {
59
+ result.title = document.title?.trim() || '';
60
+ }
61
+ if (!result.title) {
62
+ const h1 = document.querySelector('h1');
63
+ result.title = h1?.textContent?.trim() || 'untitled';
64
+ }
65
+ // Strip site suffix (e.g. " | Anthropic", " - Blog")
66
+ result.title = result.title.replace(/\\s*[|\\-–—]\\s*[^|\\-–—]{1,30}$/, '').trim();
67
+
68
+ // --- Author extraction ---
69
+ const authorMeta = document.querySelector(
70
+ 'meta[name="author"], meta[property="article:author"], meta[name="twitter:creator"]'
71
+ );
72
+ result.author = authorMeta?.getAttribute('content')?.trim() || '';
73
+
74
+ // --- Publish time extraction ---
75
+ const timeMeta = document.querySelector(
76
+ 'meta[property="article:published_time"], meta[name="date"], meta[name="publishdate"], time[datetime]'
77
+ );
78
+ if (timeMeta) {
79
+ result.publishTime = timeMeta.getAttribute('content')
80
+ || timeMeta.getAttribute('datetime')
81
+ || timeMeta.textContent?.trim()
82
+ || '';
83
+ }
84
+
85
+ // --- Content extraction ---
86
+ // Strategy: try semantic elements first, then fall back to largest text block
87
+ let contentEl = null;
88
+
89
+ // 1. <article>
90
+ const articles = document.querySelectorAll('article');
91
+ if (articles.length === 1) {
92
+ contentEl = articles[0];
93
+ } else if (articles.length > 1) {
94
+ // Pick the largest article by text length
95
+ let maxLen = 0;
96
+ articles.forEach(a => {
97
+ const len = a.textContent?.length || 0;
98
+ if (len > maxLen) { maxLen = len; contentEl = a; }
99
+ });
100
+ }
101
+
102
+ // 2. [role="main"]
103
+ if (!contentEl) {
104
+ contentEl = document.querySelector('[role="main"]');
105
+ }
106
+
107
+ // 3. <main>
108
+ if (!contentEl) {
109
+ contentEl = document.querySelector('main');
110
+ }
111
+
112
+ // 4. Largest text-dense block fallback
113
+ if (!contentEl) {
114
+ const candidates = document.querySelectorAll(
115
+ 'div[class*="content"], div[class*="article"], div[class*="post"], ' +
116
+ 'div[class*="entry"], div[class*="body"], div[id*="content"], ' +
117
+ 'div[id*="article"], div[id*="post"], section'
118
+ );
119
+ let maxLen = 0;
120
+ candidates.forEach(c => {
121
+ const len = c.textContent?.length || 0;
122
+ if (len > maxLen) { maxLen = len; contentEl = c; }
123
+ });
124
+ }
125
+
126
+ // 5. Last resort: document.body
127
+ if (!contentEl || (contentEl.textContent?.length || 0) < 200) {
128
+ contentEl = document.body;
129
+ }
130
+
131
+ // Clean up noise elements before extraction
132
+ const clone = contentEl.cloneNode(true);
133
+ const noise = 'nav, header, footer, aside, .sidebar, .nav, .menu, .footer, ' +
134
+ '.header, .comments, .comment, .ad, .ads, .advertisement, .social-share, ' +
135
+ '.related-posts, .newsletter, .cookie-banner, script, style, noscript, iframe';
136
+ clone.querySelectorAll(noise).forEach(el => el.remove());
137
+
138
+ // Deduplicate: some sites (e.g. Anthropic) render each paragraph twice
139
+ // (a visible version + a line-broken animation version with missing spaces).
140
+ // Compare by stripping ALL whitespace so "Hello world" matches "Helloworld".
141
+ const stripWS = (s) => (s || '').replace(/\\s+/g, '');
142
+ const dedup = (parent) => {
143
+ const children = Array.from(parent.children || []);
144
+ for (let i = children.length - 1; i >= 1; i--) {
145
+ const curRaw = children[i].textContent || '';
146
+ const prevRaw = children[i - 1].textContent || '';
147
+ const cur = stripWS(curRaw);
148
+ const prev = stripWS(prevRaw);
149
+ if (cur.length < 20 || prev.length < 20) continue;
150
+ // Exact match after whitespace strip, or >90% overlap
151
+ if (cur === prev) {
152
+ // Keep the one with more proper spacing (more spaces = better formatted)
153
+ const curSpaces = (curRaw.match(/ /g) || []).length;
154
+ const prevSpaces = (prevRaw.match(/ /g) || []).length;
155
+ if (curSpaces >= prevSpaces) children[i - 1].remove();
156
+ else children[i].remove();
157
+ } else if (prev.includes(cur) && cur.length / prev.length > 0.8) {
158
+ children[i].remove();
159
+ } else if (cur.includes(prev) && prev.length / cur.length > 0.8) {
160
+ children[i - 1].remove();
161
+ }
162
+ }
163
+ };
164
+ dedup(clone);
165
+ clone.querySelectorAll('section, div').forEach(el => {
166
+ if (el.children && el.children.length > 2) dedup(el);
167
+ });
168
+
169
+ result.contentHtml = clone.innerHTML;
170
+
171
+ // --- Image extraction ---
172
+ const seen = new Set();
173
+ clone.querySelectorAll('img').forEach(img => {
174
+ const src = img.getAttribute('data-src')
175
+ || img.getAttribute('data-original')
176
+ || img.getAttribute('src');
177
+ if (src && !src.startsWith('data:') && !seen.has(src)) {
178
+ seen.add(src);
179
+ result.imageUrls.push(src);
180
+ }
181
+ });
182
+
183
+ return result;
184
+ })()
185
+ `);
186
+
187
+ // Determine Referer from URL for image downloads
188
+ let referer = '';
189
+ try {
190
+ const parsed = new URL(url);
191
+ referer = parsed.origin + '/';
192
+ } catch { /* ignore */ }
193
+
194
+ return downloadArticle(
195
+ {
196
+ title: data?.title || 'untitled',
197
+ author: data?.author,
198
+ publishTime: data?.publishTime,
199
+ sourceUrl: url,
200
+ contentHtml: data?.contentHtml || '',
201
+ imageUrls: data?.imageUrls,
202
+ },
203
+ {
204
+ output: kwargs.output,
205
+ downloadImages: kwargs['download-images'],
206
+ imageHeaders: referer ? { Referer: referer } : undefined,
207
+ },
208
+ );
209
+ },
210
+ });