@jackwener/opencli 1.3.3 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (496) hide show
  1. package/.github/pull_request_template.md +3 -1
  2. package/.github/workflows/build-extension.yml +7 -1
  3. package/.github/workflows/ci.yml +29 -3
  4. package/.github/workflows/docs.yml +1 -1
  5. package/.github/workflows/e2e-headed.yml +20 -0
  6. package/.github/workflows/release.yml +1 -1
  7. package/.github/workflows/security.yml +0 -3
  8. package/CHANGELOG.md +55 -0
  9. package/CONTRIBUTING.md +6 -3
  10. package/README.md +30 -3
  11. package/README.zh-CN.md +30 -3
  12. package/SKILL.md +7 -1
  13. package/TESTING.md +1 -0
  14. package/chatwise-opencli.ps1 +82 -0
  15. package/dist/analysis.d.ts +38 -0
  16. package/dist/analysis.js +166 -0
  17. package/dist/browser/cdp.d.ts +0 -4
  18. package/dist/browser/cdp.js +53 -41
  19. package/dist/browser/cdp.test.d.ts +1 -0
  20. package/dist/browser/cdp.test.js +52 -0
  21. package/dist/browser/dom-snapshot.d.ts +2 -2
  22. package/dist/browser/dom-snapshot.js +54 -1
  23. package/dist/browser/dom-snapshot.test.js +36 -0
  24. package/dist/browser/index.d.ts +2 -2
  25. package/dist/browser/index.js +1 -1
  26. package/dist/browser/mcp.d.ts +0 -2
  27. package/dist/browser/mcp.js +2 -3
  28. package/dist/browser/page.d.ts +4 -3
  29. package/dist/browser/page.js +34 -37
  30. package/dist/browser/stealth.d.ts +0 -2
  31. package/dist/browser/stealth.js +24 -9
  32. package/dist/browser.test.js +2 -2
  33. package/dist/build-manifest.js +15 -9
  34. package/dist/build-manifest.test.js +12 -0
  35. package/dist/cascade.js +4 -2
  36. package/dist/cli-manifest.json +639 -258
  37. package/dist/cli.js +57 -29
  38. package/dist/clis/_shared/desktop-commands.d.ts +22 -0
  39. package/dist/clis/_shared/desktop-commands.js +108 -0
  40. package/dist/clis/antigravity/serve.js +5 -2
  41. package/dist/clis/arxiv/search.js +1 -1
  42. package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
  43. package/dist/clis/bilibili/dynamic.test.js +68 -0
  44. package/dist/clis/bilibili/favorite.js +4 -2
  45. package/dist/clis/bilibili/following.js +3 -2
  46. package/dist/clis/bilibili/subtitle.js +8 -7
  47. package/dist/clis/bilibili/utils.js +2 -2
  48. package/dist/clis/boss/batchgreet.js +1 -1
  49. package/dist/clis/boss/chatlist.js +1 -1
  50. package/dist/clis/boss/chatmsg.js +1 -1
  51. package/dist/clis/boss/detail.js +1 -1
  52. package/dist/clis/boss/exchange.js +1 -1
  53. package/dist/clis/boss/greet.js +1 -1
  54. package/dist/clis/boss/invite.js +1 -1
  55. package/dist/clis/boss/joblist.js +1 -1
  56. package/dist/clis/boss/mark.js +4 -3
  57. package/dist/clis/boss/recommend.js +1 -1
  58. package/dist/clis/boss/resume.js +1 -1
  59. package/dist/clis/boss/search.js +1 -1
  60. package/dist/clis/boss/send.js +5 -4
  61. package/dist/clis/boss/stats.js +1 -1
  62. package/dist/clis/chatgpt/ask.js +4 -0
  63. package/dist/clis/chatgpt/new.js +5 -1
  64. package/dist/clis/chatgpt/read.js +5 -1
  65. package/dist/clis/chatgpt/send.js +2 -1
  66. package/dist/clis/chatgpt/status.js +5 -1
  67. package/dist/clis/chatwise/ask.js +8 -2
  68. package/dist/clis/chatwise/export.js +2 -0
  69. package/dist/clis/chatwise/history.js +2 -0
  70. package/dist/clis/chatwise/model.js +8 -3
  71. package/dist/clis/chatwise/new.js +3 -18
  72. package/dist/clis/chatwise/read.js +2 -0
  73. package/dist/clis/chatwise/screenshot.js +3 -27
  74. package/dist/clis/chatwise/send.js +8 -2
  75. package/dist/clis/chatwise/shared.d.ts +2 -0
  76. package/dist/clis/chatwise/shared.js +6 -0
  77. package/dist/clis/chatwise/status.js +3 -22
  78. package/dist/clis/codex/ask.js +6 -2
  79. package/dist/clis/codex/dump.js +2 -25
  80. package/dist/clis/codex/new.js +2 -25
  81. package/dist/clis/codex/screenshot.js +2 -27
  82. package/dist/clis/codex/send.js +6 -4
  83. package/dist/clis/codex/status.js +2 -22
  84. package/dist/clis/cursor/ask.js +2 -1
  85. package/dist/clis/cursor/composer.js +2 -1
  86. package/dist/clis/cursor/dump.js +2 -25
  87. package/dist/clis/cursor/new.js +2 -18
  88. package/dist/clis/cursor/read.js +2 -1
  89. package/dist/clis/cursor/screenshot.js +1 -30
  90. package/dist/clis/cursor/send.js +2 -1
  91. package/dist/clis/cursor/status.js +2 -21
  92. package/dist/clis/dictionary/examples.yaml +25 -0
  93. package/dist/clis/dictionary/search.yaml +27 -0
  94. package/dist/clis/dictionary/synonyms.yaml +25 -0
  95. package/dist/clis/douban/book-hot.js +1 -1
  96. package/dist/clis/douban/movie-hot.js +1 -1
  97. package/dist/clis/douban/search.js +1 -1
  98. package/dist/clis/douban/utils.d.ts +4 -1
  99. package/dist/clis/douban/utils.js +156 -1
  100. package/dist/clis/doubao/ask.js +1 -1
  101. package/dist/clis/doubao/new.js +1 -1
  102. package/dist/clis/doubao/read.js +1 -1
  103. package/dist/clis/doubao/send.js +1 -1
  104. package/dist/clis/doubao/status.js +1 -1
  105. package/dist/clis/doubao-app/ask.js +1 -1
  106. package/dist/clis/doubao-app/new.js +1 -1
  107. package/dist/clis/doubao-app/read.js +1 -1
  108. package/dist/clis/doubao-app/send.js +1 -1
  109. package/dist/clis/grok/ask.d.ts +4 -0
  110. package/dist/clis/grok/ask.js +28 -10
  111. package/dist/clis/grok/ask.test.js +18 -0
  112. package/dist/clis/jd/item.d.ts +1 -0
  113. package/dist/clis/jd/item.js +96 -0
  114. package/dist/clis/jd/item.test.d.ts +1 -0
  115. package/dist/clis/jd/item.test.js +28 -0
  116. package/dist/clis/jike/feed.js +1 -1
  117. package/dist/clis/jike/search.js +1 -1
  118. package/dist/clis/linkedin/search.js +5 -4
  119. package/dist/clis/linkedin/timeline.d.ts +21 -0
  120. package/dist/clis/linkedin/timeline.js +503 -0
  121. package/dist/clis/linkedin/timeline.test.d.ts +1 -0
  122. package/dist/clis/linkedin/timeline.test.js +81 -0
  123. package/dist/clis/medium/feed.js +1 -1
  124. package/dist/clis/medium/search.js +1 -1
  125. package/dist/clis/medium/user.js +1 -1
  126. package/dist/clis/medium/{shared.js → utils.js} +2 -1
  127. package/dist/clis/pixiv/detail.yaml +49 -0
  128. package/dist/clis/pixiv/download.d.ts +7 -0
  129. package/dist/clis/pixiv/download.js +78 -0
  130. package/dist/clis/pixiv/download.test.d.ts +1 -0
  131. package/dist/clis/pixiv/download.test.js +87 -0
  132. package/dist/clis/pixiv/illusts.d.ts +8 -0
  133. package/dist/clis/pixiv/illusts.js +65 -0
  134. package/dist/clis/pixiv/illusts.test.d.ts +1 -0
  135. package/dist/clis/pixiv/illusts.test.js +99 -0
  136. package/dist/clis/pixiv/ranking.yaml +53 -0
  137. package/dist/clis/pixiv/search.d.ts +6 -0
  138. package/dist/clis/pixiv/search.js +43 -0
  139. package/dist/clis/pixiv/search.test.d.ts +1 -0
  140. package/dist/clis/pixiv/search.test.js +83 -0
  141. package/dist/clis/pixiv/test-utils.d.ts +12 -0
  142. package/dist/clis/pixiv/test-utils.js +23 -0
  143. package/dist/clis/pixiv/user.yaml +46 -0
  144. package/dist/clis/pixiv/utils.d.ts +27 -0
  145. package/dist/clis/pixiv/utils.js +49 -0
  146. package/dist/clis/reddit/comment.js +2 -1
  147. package/dist/clis/reddit/read.js +4 -3
  148. package/dist/clis/reddit/read.test.d.ts +1 -0
  149. package/dist/clis/reddit/read.test.js +28 -0
  150. package/dist/clis/reddit/save.js +2 -1
  151. package/dist/clis/reddit/saved.js +7 -3
  152. package/dist/clis/reddit/subscribe.js +2 -1
  153. package/dist/clis/reddit/upvote.js +2 -1
  154. package/dist/clis/reddit/upvoted.js +7 -3
  155. package/dist/clis/sinablog/article.js +1 -1
  156. package/dist/clis/sinablog/hot.js +1 -1
  157. package/dist/clis/sinablog/user.js +1 -1
  158. package/dist/clis/substack/feed.js +1 -1
  159. package/dist/clis/substack/publication.js +1 -1
  160. package/dist/clis/substack/search.js +3 -2
  161. package/dist/clis/substack/{shared.js → utils.js} +3 -2
  162. package/dist/clis/tiktok/search.yaml +2 -1
  163. package/dist/clis/twitter/accept.js +2 -1
  164. package/dist/clis/twitter/article.js +4 -1
  165. package/dist/clis/twitter/block.js +2 -1
  166. package/dist/clis/twitter/bookmark.js +2 -1
  167. package/dist/clis/twitter/bookmarks.js +3 -2
  168. package/dist/clis/twitter/delete.js +2 -1
  169. package/dist/clis/twitter/follow.js +2 -1
  170. package/dist/clis/twitter/followers.js +3 -2
  171. package/dist/clis/twitter/following.js +3 -2
  172. package/dist/clis/twitter/hide-reply.js +2 -1
  173. package/dist/clis/twitter/like.js +2 -1
  174. package/dist/clis/twitter/notifications.js +2 -1
  175. package/dist/clis/twitter/post.js +2 -1
  176. package/dist/clis/twitter/profile.js +5 -2
  177. package/dist/clis/twitter/reply-dm.js +2 -1
  178. package/dist/clis/twitter/reply.js +2 -1
  179. package/dist/clis/twitter/search.js +30 -13
  180. package/dist/clis/twitter/search.test.d.ts +1 -0
  181. package/dist/clis/twitter/search.test.js +104 -0
  182. package/dist/clis/twitter/thread.js +2 -2
  183. package/dist/clis/twitter/timeline.js +3 -2
  184. package/dist/clis/twitter/trending.js +3 -2
  185. package/dist/clis/twitter/unblock.js +2 -1
  186. package/dist/clis/twitter/unbookmark.js +2 -1
  187. package/dist/clis/twitter/unfollow.js +2 -1
  188. package/dist/clis/v2ex/daily.js +3 -2
  189. package/dist/clis/v2ex/me.js +3 -2
  190. package/dist/clis/v2ex/notifications.js +4 -4
  191. package/dist/clis/web/read.d.ts +16 -0
  192. package/dist/clis/web/read.js +202 -0
  193. package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
  194. package/dist/clis/xueqiu/danjuan-utils.js +126 -0
  195. package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
  196. package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
  197. package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
  198. package/dist/clis/xueqiu/fund-holdings.js +28 -0
  199. package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
  200. package/dist/clis/xueqiu/fund-snapshot.js +25 -0
  201. package/dist/clis/youtube/transcript.js +5 -4
  202. package/dist/clis/youtube/video.js +3 -2
  203. package/dist/daemon.js +7 -3
  204. package/dist/discovery.js +11 -10
  205. package/dist/doctor.js +2 -1
  206. package/dist/download/index.d.ts +4 -12
  207. package/dist/download/index.js +33 -12
  208. package/dist/download/index.test.js +79 -2
  209. package/dist/download/media-download.js +4 -2
  210. package/dist/engine.test.js +76 -4
  211. package/dist/execution.d.ts +1 -9
  212. package/dist/execution.js +56 -46
  213. package/dist/explore.js +12 -111
  214. package/dist/external-clis.yaml +0 -8
  215. package/dist/external.js +7 -5
  216. package/dist/external.test.js +4 -0
  217. package/dist/generate.d.ts +0 -9
  218. package/dist/generate.js +4 -20
  219. package/dist/hooks.d.ts +46 -0
  220. package/dist/hooks.js +56 -0
  221. package/dist/hooks.test.d.ts +4 -0
  222. package/dist/hooks.test.js +92 -0
  223. package/dist/interceptor.js +70 -23
  224. package/dist/main.js +2 -0
  225. package/dist/output.js +12 -6
  226. package/dist/pipeline/executor.js +1 -1
  227. package/dist/pipeline/steps/browser.js +1 -3
  228. package/dist/pipeline/steps/download.js +42 -26
  229. package/dist/pipeline/steps/download.test.d.ts +1 -0
  230. package/dist/pipeline/steps/download.test.js +101 -0
  231. package/dist/pipeline/steps/fetch.js +40 -22
  232. package/dist/pipeline/steps/fetch.test.d.ts +1 -0
  233. package/dist/pipeline/steps/fetch.test.js +123 -0
  234. package/dist/pipeline/steps/transform.js +2 -6
  235. package/dist/pipeline/template.js +66 -52
  236. package/dist/pipeline/template.test.js +28 -0
  237. package/dist/pipeline/transform.test.js +18 -0
  238. package/dist/plugin.d.ts +40 -1
  239. package/dist/plugin.js +214 -17
  240. package/dist/plugin.test.d.ts +1 -1
  241. package/dist/plugin.test.js +219 -3
  242. package/dist/record.js +6 -98
  243. package/dist/registry-api.d.ts +2 -0
  244. package/dist/registry-api.js +1 -0
  245. package/dist/registry.d.ts +5 -2
  246. package/dist/registry.js +1 -2
  247. package/dist/runtime.d.ts +0 -1
  248. package/dist/runtime.js +14 -4
  249. package/dist/snapshotFormatter.d.ts +7 -14
  250. package/dist/snapshotFormatter.js +38 -78
  251. package/dist/utils.d.ts +9 -0
  252. package/dist/utils.js +29 -0
  253. package/dist/validate.js +3 -5
  254. package/dist/yaml-schema.d.ts +26 -0
  255. package/dist/yaml-schema.js +5 -0
  256. package/docs/.vitepress/config.mts +3 -0
  257. package/docs/adapters/browser/dictionary.md +27 -0
  258. package/docs/adapters/browser/jd.md +27 -0
  259. package/docs/adapters/browser/linkedin.md +6 -0
  260. package/docs/adapters/browser/pixiv.md +92 -0
  261. package/docs/adapters/browser/web.md +30 -0
  262. package/docs/adapters/browser/xueqiu.md +27 -9
  263. package/docs/adapters/index.md +3 -1
  264. package/docs/comparison.md +125 -0
  265. package/docs/developer/contributing.md +21 -2
  266. package/docs/developer/testing.md +14 -8
  267. package/docs/developer/ts-adapter.md +18 -0
  268. package/docs/developer/yaml-adapter.md +16 -0
  269. package/docs/guide/plugins.md +10 -0
  270. package/docs/zh/guide/plugins.md +10 -0
  271. package/extension/dist/background.js +519 -444
  272. package/extension/manifest.json +1 -1
  273. package/extension/package.json +1 -1
  274. package/extension/src/background.test.ts +46 -1
  275. package/extension/src/background.ts +108 -33
  276. package/extension/src/cdp.ts +9 -9
  277. package/package.json +3 -2
  278. package/scripts/check-doc-coverage.sh +2 -0
  279. package/src/analysis.ts +170 -0
  280. package/src/browser/cdp.test.ts +66 -0
  281. package/src/browser/cdp.ts +59 -44
  282. package/src/browser/dom-snapshot.test.ts +42 -0
  283. package/src/browser/dom-snapshot.ts +56 -3
  284. package/src/browser/index.ts +2 -2
  285. package/src/browser/mcp.ts +2 -4
  286. package/src/browser/page.ts +34 -37
  287. package/src/browser/stealth.ts +24 -10
  288. package/src/browser.test.ts +2 -2
  289. package/src/build-manifest.test.ts +14 -0
  290. package/src/build-manifest.ts +13 -31
  291. package/src/cascade.ts +5 -3
  292. package/src/cli.ts +66 -34
  293. package/src/clis/_shared/desktop-commands.ts +121 -0
  294. package/src/clis/antigravity/serve.ts +6 -3
  295. package/src/clis/arxiv/search.ts +1 -1
  296. package/src/clis/bilibili/dynamic.test.ts +79 -0
  297. package/src/clis/bilibili/favorite.ts +5 -2
  298. package/src/clis/bilibili/following.ts +3 -2
  299. package/src/clis/bilibili/subtitle.ts +8 -7
  300. package/src/clis/bilibili/utils.ts +2 -2
  301. package/src/clis/boss/batchgreet.ts +1 -1
  302. package/src/clis/boss/chatlist.ts +1 -1
  303. package/src/clis/boss/chatmsg.ts +1 -1
  304. package/src/clis/boss/detail.ts +1 -1
  305. package/src/clis/boss/exchange.ts +1 -1
  306. package/src/clis/boss/greet.ts +1 -1
  307. package/src/clis/boss/invite.ts +1 -1
  308. package/src/clis/boss/joblist.ts +1 -1
  309. package/src/clis/boss/mark.ts +4 -3
  310. package/src/clis/boss/recommend.ts +1 -1
  311. package/src/clis/boss/resume.ts +1 -1
  312. package/src/clis/boss/search.ts +1 -1
  313. package/src/clis/boss/send.ts +5 -4
  314. package/src/clis/boss/stats.ts +1 -1
  315. package/src/clis/chatgpt/ask.ts +5 -0
  316. package/src/clis/chatgpt/new.ts +7 -2
  317. package/src/clis/chatgpt/read.ts +7 -2
  318. package/src/clis/chatgpt/send.ts +3 -2
  319. package/src/clis/chatgpt/status.ts +6 -1
  320. package/src/clis/chatwise/ask.ts +7 -2
  321. package/src/clis/chatwise/export.ts +2 -0
  322. package/src/clis/chatwise/history.ts +2 -0
  323. package/src/clis/chatwise/model.ts +7 -3
  324. package/src/clis/chatwise/new.ts +3 -20
  325. package/src/clis/chatwise/read.ts +2 -0
  326. package/src/clis/chatwise/screenshot.ts +3 -32
  327. package/src/clis/chatwise/send.ts +7 -2
  328. package/src/clis/chatwise/shared.ts +8 -0
  329. package/src/clis/chatwise/status.ts +3 -24
  330. package/src/clis/codex/ask.ts +5 -2
  331. package/src/clis/codex/dump.ts +2 -27
  332. package/src/clis/codex/new.ts +2 -28
  333. package/src/clis/codex/screenshot.ts +2 -32
  334. package/src/clis/codex/send.ts +5 -4
  335. package/src/clis/codex/status.ts +2 -24
  336. package/src/clis/cursor/ask.ts +2 -1
  337. package/src/clis/cursor/composer.ts +2 -1
  338. package/src/clis/cursor/dump.ts +2 -27
  339. package/src/clis/cursor/new.ts +2 -20
  340. package/src/clis/cursor/read.ts +2 -1
  341. package/src/clis/cursor/screenshot.ts +1 -36
  342. package/src/clis/cursor/send.ts +2 -1
  343. package/src/clis/cursor/status.ts +2 -22
  344. package/src/clis/dictionary/examples.yaml +25 -0
  345. package/src/clis/dictionary/search.yaml +27 -0
  346. package/src/clis/dictionary/synonyms.yaml +25 -0
  347. package/src/clis/douban/book-hot.ts +1 -1
  348. package/src/clis/douban/movie-hot.ts +1 -1
  349. package/src/clis/douban/search.ts +1 -1
  350. package/src/clis/douban/utils.ts +165 -1
  351. package/src/clis/doubao/ask.ts +1 -1
  352. package/src/clis/doubao/new.ts +1 -1
  353. package/src/clis/doubao/read.ts +1 -1
  354. package/src/clis/doubao/send.ts +1 -1
  355. package/src/clis/doubao/status.ts +1 -1
  356. package/src/clis/doubao-app/ask.ts +1 -1
  357. package/src/clis/doubao-app/new.ts +1 -1
  358. package/src/clis/doubao-app/read.ts +1 -1
  359. package/src/clis/doubao-app/send.ts +1 -1
  360. package/src/clis/grok/ask.test.ts +25 -0
  361. package/src/clis/grok/ask.ts +25 -12
  362. package/src/clis/jd/item.test.ts +35 -0
  363. package/src/clis/jd/item.ts +101 -0
  364. package/src/clis/jike/feed.ts +1 -1
  365. package/src/clis/jike/search.ts +1 -1
  366. package/src/clis/linkedin/search.ts +5 -4
  367. package/src/clis/linkedin/timeline.test.ts +99 -0
  368. package/src/clis/linkedin/timeline.ts +532 -0
  369. package/src/clis/medium/feed.ts +1 -1
  370. package/src/clis/medium/search.ts +1 -1
  371. package/src/clis/medium/user.ts +1 -1
  372. package/src/clis/medium/{shared.ts → utils.ts} +2 -1
  373. package/src/clis/pixiv/detail.yaml +49 -0
  374. package/src/clis/pixiv/download.test.ts +114 -0
  375. package/src/clis/pixiv/download.ts +91 -0
  376. package/src/clis/pixiv/illusts.test.ts +115 -0
  377. package/src/clis/pixiv/illusts.ts +78 -0
  378. package/src/clis/pixiv/ranking.yaml +53 -0
  379. package/src/clis/pixiv/search.test.ts +97 -0
  380. package/src/clis/pixiv/search.ts +53 -0
  381. package/src/clis/pixiv/test-utils.ts +29 -0
  382. package/src/clis/pixiv/user.yaml +46 -0
  383. package/src/clis/pixiv/utils.ts +62 -0
  384. package/src/clis/reddit/comment.ts +2 -1
  385. package/src/clis/reddit/read.test.ts +34 -0
  386. package/src/clis/reddit/read.ts +4 -3
  387. package/src/clis/reddit/save.ts +2 -1
  388. package/src/clis/reddit/saved.ts +6 -2
  389. package/src/clis/reddit/subscribe.ts +2 -1
  390. package/src/clis/reddit/upvote.ts +2 -1
  391. package/src/clis/reddit/upvoted.ts +6 -2
  392. package/src/clis/sinablog/article.ts +1 -1
  393. package/src/clis/sinablog/hot.ts +1 -1
  394. package/src/clis/sinablog/user.ts +1 -1
  395. package/src/clis/substack/feed.ts +1 -1
  396. package/src/clis/substack/publication.ts +1 -1
  397. package/src/clis/substack/search.ts +3 -2
  398. package/src/clis/substack/{shared.ts → utils.ts} +3 -2
  399. package/src/clis/tiktok/search.yaml +2 -1
  400. package/src/clis/twitter/accept.ts +2 -1
  401. package/src/clis/twitter/article.ts +3 -1
  402. package/src/clis/twitter/block.ts +2 -1
  403. package/src/clis/twitter/bookmark.ts +2 -1
  404. package/src/clis/twitter/bookmarks.ts +3 -2
  405. package/src/clis/twitter/delete.ts +2 -1
  406. package/src/clis/twitter/follow.ts +2 -1
  407. package/src/clis/twitter/followers.ts +3 -2
  408. package/src/clis/twitter/following.ts +3 -2
  409. package/src/clis/twitter/hide-reply.ts +2 -1
  410. package/src/clis/twitter/like.ts +2 -1
  411. package/src/clis/twitter/notifications.ts +2 -1
  412. package/src/clis/twitter/post.ts +2 -1
  413. package/src/clis/twitter/profile.ts +4 -2
  414. package/src/clis/twitter/reply-dm.ts +2 -1
  415. package/src/clis/twitter/reply.ts +2 -1
  416. package/src/clis/twitter/search.test.ts +113 -0
  417. package/src/clis/twitter/search.ts +38 -14
  418. package/src/clis/twitter/thread.ts +2 -2
  419. package/src/clis/twitter/timeline.ts +3 -2
  420. package/src/clis/twitter/trending.ts +3 -2
  421. package/src/clis/twitter/unblock.ts +2 -1
  422. package/src/clis/twitter/unbookmark.ts +2 -1
  423. package/src/clis/twitter/unfollow.ts +2 -1
  424. package/src/clis/v2ex/daily.ts +3 -2
  425. package/src/clis/v2ex/me.ts +3 -2
  426. package/src/clis/v2ex/notifications.ts +3 -4
  427. package/src/clis/web/read.ts +210 -0
  428. package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
  429. package/src/clis/xueqiu/danjuan-utils.ts +176 -0
  430. package/src/clis/xueqiu/fund-holdings.ts +32 -0
  431. package/src/clis/xueqiu/fund-snapshot.ts +27 -0
  432. package/src/clis/youtube/transcript.ts +5 -4
  433. package/src/clis/youtube/video.ts +3 -2
  434. package/src/daemon.ts +5 -4
  435. package/src/discovery.ts +12 -34
  436. package/src/doctor.ts +3 -2
  437. package/src/download/index.test.ts +93 -2
  438. package/src/download/index.ts +44 -23
  439. package/src/download/media-download.ts +5 -3
  440. package/src/engine.test.ts +84 -3
  441. package/src/execution.ts +62 -46
  442. package/src/explore.ts +21 -90
  443. package/src/external-clis.yaml +0 -8
  444. package/src/external.test.ts +9 -0
  445. package/src/external.ts +12 -10
  446. package/src/generate.ts +4 -41
  447. package/src/hooks.test.ts +126 -0
  448. package/src/hooks.ts +90 -0
  449. package/src/interceptor.ts +73 -23
  450. package/src/main.ts +2 -0
  451. package/src/output.ts +14 -6
  452. package/src/pipeline/executor.ts +1 -1
  453. package/src/pipeline/steps/browser.ts +1 -3
  454. package/src/pipeline/steps/download.test.ts +136 -0
  455. package/src/pipeline/steps/download.ts +47 -34
  456. package/src/pipeline/steps/fetch.test.ts +179 -0
  457. package/src/pipeline/steps/fetch.ts +39 -23
  458. package/src/pipeline/steps/transform.ts +2 -6
  459. package/src/pipeline/template.test.ts +28 -0
  460. package/src/pipeline/template.ts +67 -79
  461. package/src/pipeline/transform.test.ts +20 -0
  462. package/src/plugin.test.ts +251 -3
  463. package/src/plugin.ts +265 -21
  464. package/src/record.ts +12 -84
  465. package/src/registry-api.ts +2 -0
  466. package/src/registry.ts +7 -4
  467. package/src/runtime.ts +14 -4
  468. package/src/snapshotFormatter.ts +43 -121
  469. package/src/utils.ts +39 -0
  470. package/src/validate.ts +3 -5
  471. package/src/yaml-schema.ts +28 -0
  472. package/tests/e2e/browser-auth.test.ts +25 -0
  473. package/tests/e2e/plugin-management.test.ts +137 -0
  474. package/tests/e2e/public-commands.test.ts +34 -1
  475. package/vitest.config.ts +19 -1
  476. package/.github/workflows/pkg-pr-new.yml +0 -30
  477. package/dist/clis/douban/shared.d.ts +0 -4
  478. package/dist/clis/douban/shared.js +0 -155
  479. package/src/clis/douban/shared.ts +0 -165
  480. /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
  481. /package/dist/clis/boss/{common.js → utils.js} +0 -0
  482. /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
  483. /package/dist/clis/doubao/{common.js → utils.js} +0 -0
  484. /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
  485. /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
  486. /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
  487. /package/dist/clis/jike/{shared.js → utils.js} +0 -0
  488. /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
  489. /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
  490. /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
  491. /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
  492. /package/src/clis/boss/{common.ts → utils.ts} +0 -0
  493. /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
  494. /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
  495. /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
  496. /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Pixiv shared helpers: authenticated Ajax fetch with standard error handling.
3
+ *
4
+ * All Pixiv Ajax APIs return `{ error: false, body: ... }` on success.
5
+ * On failure the HTTP status code is used to distinguish auth (401/403),
6
+ * not-found (404), and other errors.
7
+ */
8
+ import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
9
+ const PIXIV_DOMAIN = 'www.pixiv.net';
10
+ /**
11
+ * Navigate to Pixiv (to attach cookies) then fetch a Pixiv Ajax API endpoint.
12
+ *
13
+ * Handles the common navigate → evaluate(fetch) → error-check pattern used
14
+ * by every Pixiv TS adapter.
15
+ *
16
+ * @param page - Browser page instance
17
+ * @param path - API path, e.g. '/ajax/illust/12345'
18
+ * @param opts - Optional query params
19
+ * @returns - The parsed `body` from the JSON response
20
+ * @throws AuthRequiredError on 401/403
21
+ * @throws CommandExecutionError on 404 or other HTTP errors
22
+ */
23
+ export async function pixivFetch(page, path, opts = {}) {
24
+ await page.goto(`https://${PIXIV_DOMAIN}`);
25
+ const qs = opts.params
26
+ ? '?' + Object.entries(opts.params).map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join('&')
27
+ : '';
28
+ const url = `https://${PIXIV_DOMAIN}${path}${qs}`;
29
+ const data = await page.evaluate(`
30
+ (async () => {
31
+ const res = await fetch(${JSON.stringify(url)}, { credentials: 'include' });
32
+ if (!res.ok) return { __httpError: res.status };
33
+ return await res.json();
34
+ })()
35
+ `);
36
+ if (data?.__httpError) {
37
+ const status = data.__httpError;
38
+ if (status === 401 || status === 403) {
39
+ throw new AuthRequiredError(PIXIV_DOMAIN, 'Authentication required — please log in to Pixiv in Chrome');
40
+ }
41
+ if (status === 404) {
42
+ throw new CommandExecutionError(opts.notFoundMsg || `Pixiv resource not found (HTTP 404)`);
43
+ }
44
+ throw new CommandExecutionError(`Pixiv request failed (HTTP ${status})`);
45
+ }
46
+ return data?.body;
47
+ }
48
+ /** Maximum number of illust IDs per batch detail request (Pixiv server limit). */
49
+ export const BATCH_SIZE = 48;
@@ -1,3 +1,4 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  cli({
3
4
  site: 'reddit',
@@ -13,7 +14,7 @@ cli({
13
14
  columns: ['status', 'message'],
14
15
  func: async (page, kwargs) => {
15
16
  if (!page)
16
- throw new Error('Requires browser');
17
+ throw new CommandExecutionError('Browser session required');
17
18
  await page.goto('https://www.reddit.com');
18
19
  const result = await page.evaluate(`(async () => {
19
20
  try {
@@ -7,6 +7,7 @@
7
7
  * - Indented output showing conversation threads
8
8
  */
9
9
  import { cli, Strategy } from '../../registry.js';
10
+ import { CommandExecutionError } from '../../errors.js';
10
11
  cli({
11
12
  site: 'reddit',
12
13
  name: 'read',
@@ -173,11 +174,11 @@ cli({
173
174
  })()
174
175
  `);
175
176
  if (!data || typeof data !== 'object')
176
- throw new Error('Failed to fetch post data');
177
+ throw new CommandExecutionError('Failed to fetch post data');
177
178
  if (!Array.isArray(data) && data.error)
178
- throw new Error(data.error);
179
+ throw new CommandExecutionError(data.error);
179
180
  if (!Array.isArray(data))
180
- throw new Error('Unexpected response');
181
+ throw new CommandExecutionError('Unexpected response');
181
182
  return data;
182
183
  },
183
184
  });
@@ -0,0 +1 @@
1
+ import './read.js';
@@ -0,0 +1,28 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { getRegistry } from '../../registry.js';
3
+ import './read.js';
4
+ describe('reddit read adapter', () => {
5
+ const command = getRegistry().get('reddit/read');
6
+ it('returns threaded rows from the browser-evaluated payload', async () => {
7
+ const page = {
8
+ goto: vi.fn().mockResolvedValue(undefined),
9
+ evaluate: vi.fn().mockResolvedValue([
10
+ { type: 'POST', author: 'alice', score: 10, text: 'Title' },
11
+ { type: 'L0', author: 'bob', score: 5, text: 'Comment' },
12
+ ]),
13
+ };
14
+ const result = await command.func(page, { 'post-id': 'abc123', limit: 5 });
15
+ expect(page.goto).toHaveBeenCalledWith('https://www.reddit.com');
16
+ expect(result).toEqual([
17
+ { type: 'POST', author: 'alice', score: 10, text: 'Title' },
18
+ { type: 'L0', author: 'bob', score: 5, text: 'Comment' },
19
+ ]);
20
+ });
21
+ it('surfaces adapter-level API errors clearly', async () => {
22
+ const page = {
23
+ goto: vi.fn().mockResolvedValue(undefined),
24
+ evaluate: vi.fn().mockResolvedValue({ error: 'Reddit API returned HTTP 403' }),
25
+ };
26
+ await expect(command.func(page, { 'post-id': 'abc123' })).rejects.toThrow('Reddit API returned HTTP 403');
27
+ });
28
+ });
@@ -1,3 +1,4 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  cli({
3
4
  site: 'reddit',
@@ -13,7 +14,7 @@ cli({
13
14
  columns: ['status', 'message'],
14
15
  func: async (page, kwargs) => {
15
16
  if (!page)
16
- throw new Error('Requires browser');
17
+ throw new CommandExecutionError('Browser session required');
17
18
  await page.goto('https://www.reddit.com');
18
19
  const result = await page.evaluate(`(async () => {
19
20
  try {
@@ -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: 'reddit',
@@ -12,7 +13,7 @@ cli({
12
13
  columns: ['title', 'subreddit', 'score', 'comments', 'url'],
13
14
  func: async (page, kwargs) => {
14
15
  if (!page)
15
- throw new Error('Requires browser');
16
+ throw new CommandExecutionError('Browser session required');
16
17
  await page.goto('https://www.reddit.com');
17
18
  const result = await page.evaluate(`(async () => {
18
19
  try {
@@ -38,8 +39,11 @@ cli({
38
39
  return { error: e.toString() };
39
40
  }
40
41
  })()`);
41
- if (result?.error)
42
- throw new Error(result.error);
42
+ if (result?.error) {
43
+ if (String(result.error).includes('Not logged in'))
44
+ throw new AuthRequiredError('reddit.com', result.error);
45
+ throw new CommandExecutionError(result.error);
46
+ }
43
47
  return (result || []).slice(0, kwargs.limit);
44
48
  }
45
49
  });
@@ -1,3 +1,4 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  cli({
3
4
  site: 'reddit',
@@ -13,7 +14,7 @@ cli({
13
14
  columns: ['status', 'message'],
14
15
  func: async (page, kwargs) => {
15
16
  if (!page)
16
- throw new Error('Requires browser');
17
+ throw new CommandExecutionError('Browser session required');
17
18
  await page.goto('https://www.reddit.com');
18
19
  const result = await page.evaluate(`(async () => {
19
20
  try {
@@ -1,3 +1,4 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  cli({
3
4
  site: 'reddit',
@@ -13,7 +14,7 @@ cli({
13
14
  columns: ['status', 'message'],
14
15
  func: async (page, kwargs) => {
15
16
  if (!page)
16
- throw new Error('Requires browser');
17
+ throw new CommandExecutionError('Browser session required');
17
18
  await page.goto('https://www.reddit.com');
18
19
  const result = await page.evaluate(`(async () => {
19
20
  try {
@@ -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: 'reddit',
@@ -12,7 +13,7 @@ cli({
12
13
  columns: ['title', 'subreddit', 'score', 'comments', 'url'],
13
14
  func: async (page, kwargs) => {
14
15
  if (!page)
15
- throw new Error('Requires browser');
16
+ throw new CommandExecutionError('Browser session required');
16
17
  await page.goto('https://www.reddit.com');
17
18
  const result = await page.evaluate(`(async () => {
18
19
  try {
@@ -38,8 +39,11 @@ cli({
38
39
  return { error: e.toString() };
39
40
  }
40
41
  })()`);
41
- if (result?.error)
42
- throw new Error(result.error);
42
+ if (result?.error) {
43
+ if (String(result.error).includes('Not logged in'))
44
+ throw new AuthRequiredError('reddit.com', result.error);
45
+ throw new CommandExecutionError(result.error);
46
+ }
43
47
  return (result || []).slice(0, kwargs.limit);
44
48
  }
45
49
  });
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { loadSinaBlogArticle } from './shared.js';
2
+ import { loadSinaBlogArticle } from './utils.js';
3
3
  cli({
4
4
  site: 'sinablog',
5
5
  name: 'article',
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { loadSinaBlogHot } from './shared.js';
2
+ import { loadSinaBlogHot } from './utils.js';
3
3
  cli({
4
4
  site: 'sinablog',
5
5
  name: 'hot',
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { loadSinaBlogUser } from './shared.js';
2
+ import { loadSinaBlogUser } from './utils.js';
3
3
  cli({
4
4
  site: 'sinablog',
5
5
  name: 'user',
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { buildSubstackBrowseUrl, loadSubstackFeed } from './shared.js';
2
+ import { buildSubstackBrowseUrl, loadSubstackFeed } from './utils.js';
3
3
  cli({
4
4
  site: 'substack',
5
5
  name: 'feed',
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { loadSubstackArchive } from './shared.js';
2
+ import { loadSubstackArchive } from './utils.js';
3
3
  cli({
4
4
  site: 'substack',
5
5
  name: 'publication',
@@ -1,3 +1,4 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  function headers() {
3
4
  return {
@@ -22,7 +23,7 @@ async function searchPosts(keyword, limit) {
22
23
  url.searchParams.set('includePlatformResults', 'true');
23
24
  const resp = await fetch(url, { headers: headers() });
24
25
  if (!resp.ok)
25
- throw new Error(`Substack post search failed: HTTP ${resp.status}`);
26
+ throw new CommandExecutionError(`Substack post search failed: HTTP ${resp.status}`);
26
27
  const data = await resp.json();
27
28
  const results = Array.isArray(data?.results) ? data.results : [];
28
29
  return results.slice(0, limit).map((item, index) => ({
@@ -40,7 +41,7 @@ async function searchPublications(keyword, limit) {
40
41
  url.searchParams.set('page', '0');
41
42
  const resp = await fetch(url, { headers: headers() });
42
43
  if (!resp.ok)
43
- throw new Error(`Substack publication search failed: HTTP ${resp.status}`);
44
+ throw new CommandExecutionError(`Substack publication search failed: HTTP ${resp.status}`);
44
45
  const data = await resp.json();
45
46
  const results = Array.isArray(data?.results) ? data.results : [];
46
47
  return results.slice(0, limit).map((item, index) => {
@@ -1,3 +1,4 @@
1
+ import { CommandExecutionError } from '../../errors.js';
1
2
  export function buildSubstackBrowseUrl(category) {
2
3
  if (!category || category === 'all')
3
4
  return 'https://substack.com/';
@@ -6,7 +7,7 @@ export function buildSubstackBrowseUrl(category) {
6
7
  }
7
8
  export async function loadSubstackFeed(page, url, limit) {
8
9
  if (!page)
9
- throw new Error('Requires browser session');
10
+ throw new CommandExecutionError('Browser session required for substack feed');
10
11
  await page.goto(url);
11
12
  await page.wait(5);
12
13
  const data = await page.evaluate(`
@@ -74,7 +75,7 @@ export async function loadSubstackFeed(page, url, limit) {
74
75
  }
75
76
  export async function loadSubstackArchive(page, baseUrl, limit) {
76
77
  if (!page)
77
- throw new Error('Requires browser session');
78
+ throw new CommandExecutionError('Browser session required for substack archive');
78
79
  await page.goto(`${baseUrl}/archive`);
79
80
  await page.wait(5);
80
81
  const data = await page.evaluate(`
@@ -35,6 +35,7 @@ pipeline:
35
35
  rank: idx + 1,
36
36
  desc: (v.desc || '').replace(/\n/g, ' ').substring(0, 100),
37
37
  author: a.uniqueId || '',
38
+ url: (a.uniqueId && v.id) ? 'https://www.tiktok.com/@' + a.uniqueId + '/video/' + v.id : '',
38
39
  plays: s.playCount || 0,
39
40
  likes: s.diggCount || 0,
40
41
  comments: s.commentCount || 0,
@@ -43,4 +44,4 @@ pipeline:
43
44
  });
44
45
  })()
45
46
 
46
- columns: [rank, desc, author, plays, likes, comments, shares]
47
+ columns: [rank, desc, author, url, plays, likes, comments, shares]
@@ -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',
@@ -14,7 +15,7 @@ cli({
14
15
  columns: ['index', 'status', 'user', 'message'],
15
16
  func: async (page, kwargs) => {
16
17
  if (!page)
17
- throw new Error('Requires browser');
18
+ throw new CommandExecutionError('Browser session required for twitter accept');
18
19
  const keywords = kwargs.query.split(',').map((k) => k.trim()).filter(Boolean);
19
20
  const maxAccepts = kwargs.max ?? 20;
20
21
  const results = [];
@@ -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',
@@ -150,7 +151,9 @@ cli({
150
151
  }
151
152
  `);
152
153
  if (result?.error) {
153
- throw new Error(result.error + (result.hint ? ` (${result.hint})` : ''));
154
+ if (String(result.error).includes('No ct0 cookie'))
155
+ throw new AuthRequiredError('x.com', result.error);
156
+ throw new CommandExecutionError(result.error + (result.hint ? ` (${result.hint})` : ''));
154
157
  }
155
158
  return result || [];
156
159
  }
@@ -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 block');
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 bookmark');
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 { AuthRequiredError, CommandExecutionError } from '../../errors.js';
2
3
  const BEARER_TOKEN = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
3
4
  const BOOKMARKS_QUERY_ID = 'Fy0QMy4q_aZCpkO0PnyLYw';
4
5
  const FEATURES = {
@@ -114,7 +115,7 @@ cli({
114
115
  return document.cookie.split(';').map(c => c.trim()).find(c => c.startsWith('ct0='))?.split('=')[1] || null;
115
116
  }`);
116
117
  if (!ct0)
117
- throw new Error('Not logged into x.com (no ct0 cookie)');
118
+ throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');
118
119
  const queryId = await page.evaluate(`async () => {
119
120
  try {
120
121
  const ghResp = await fetch('https://raw.githubusercontent.com/fa0311/twitter-openapi/refs/heads/main/src/config/placeholder.json');
@@ -157,7 +158,7 @@ cli({
157
158
  }`);
158
159
  if (data?.error) {
159
160
  if (allTweets.length === 0)
160
- throw new Error(`HTTP ${data.error}: Failed to fetch bookmarks. queryId may have expired.`);
161
+ throw new CommandExecutionError(`HTTP ${data.error}: Failed to fetch bookmarks. queryId may have expired.`);
161
162
  break;
162
163
  }
163
164
  const { tweets, nextCursor } = parseBookmarks(data, seen);
@@ -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: 'delete',
@@ -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 delete');
16
17
  await page.goto(kwargs.url);
17
18
  await page.wait(5); // Wait for tweet to load completely
18
19
  const result = await page.evaluate(`(async () => {
@@ -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 follow');
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 { AuthRequiredError, SelectorError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  cli({
3
4
  site: 'twitter',
@@ -22,7 +23,7 @@ cli({
22
23
  return link ? link.getAttribute('href') : null;
23
24
  }`);
24
25
  if (!href) {
25
- throw new Error('Could not find logged-in user profile link. Are you logged in?');
26
+ throw new AuthRequiredError('x.com', 'Could not find logged-in user profile link. Are you logged in?');
26
27
  }
27
28
  targetUser = href.replace('/', '');
28
29
  }
@@ -48,7 +49,7 @@ cli({
48
49
  return false;
49
50
  }`);
50
51
  if (!clicked) {
51
- throw new Error('Could not find followers link on profile page. Twitter may have changed the layout.');
52
+ throw new SelectorError('Twitter followers link', 'Twitter may have changed the layout.');
52
53
  }
53
54
  await page.wait(5);
54
55
  // 4. Scroll to trigger pagination API calls
@@ -1,3 +1,4 @@
1
+ import { AuthRequiredError, SelectorError } from '../../errors.js';
1
2
  import { cli, Strategy } from '../../registry.js';
2
3
  cli({
3
4
  site: 'twitter',
@@ -22,7 +23,7 @@ cli({
22
23
  return link ? link.getAttribute('href') : null;
23
24
  }`);
24
25
  if (!href) {
25
- throw new Error('Could not find logged-in user profile link. Are you logged in?');
26
+ throw new AuthRequiredError('x.com', 'Could not find logged-in user profile link. Are you logged in?');
26
27
  }
27
28
  targetUser = href.replace('/', '');
28
29
  }
@@ -41,7 +42,7 @@ cli({
41
42
  return false;
42
43
  }`);
43
44
  if (!clicked) {
44
- throw new Error('Could not find following link on profile page. Twitter may have changed the layout.');
45
+ throw new SelectorError('Twitter following link', 'Twitter may have changed the layout.');
45
46
  }
46
47
  await page.wait(5);
47
48
  // 4. Scroll to trigger pagination API calls
@@ -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 hide-reply');
16
17
  await page.goto(kwargs.url);
17
18
  await page.wait(5);
18
19
  const result = await page.evaluate(`(async () => {
@@ -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 like');
16
17
  await page.goto(kwargs.url);
17
18
  await page.wait(5); // Wait for tweet to load completely
18
19
  const result = await page.evaluate(`(async () => {
@@ -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',
@@ -25,7 +26,7 @@ cli({
25
26
  // Verify SPA navigation succeeded
26
27
  const currentUrl = await page.evaluate('() => window.location.pathname');
27
28
  if (currentUrl !== '/notifications') {
28
- throw new Error('SPA navigation to notifications failed. Twitter may have changed its routing.');
29
+ throw new CommandExecutionError('SPA navigation to notifications failed. Twitter may have changed its routing.');
29
30
  }
30
31
  // 4. Scroll to trigger pagination
31
32
  await page.autoScroll({ times: 2, delayMs: 2000 });
@@ -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: 'post',
@@ -12,7 +13,7 @@ cli({
12
13
  columns: ['status', 'message', 'text'],
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 post');
16
17
  // 1. Navigate directly to the compose tweet modal
17
18
  await page.goto('https://x.com/compose/tweet');
18
19
  await page.wait(3); // Wait for the modal and React app to hydrate
@@ -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