@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
@@ -9,9 +9,6 @@
9
9
  * Inspired by puppeteer-extra-plugin-stealth.
10
10
  */
11
11
 
12
- /** Guard flag set on `window` to prevent double-injection. */
13
- export const STEALTH_GUARD = '__opencli_stealth_applied';
14
-
15
12
  /**
16
13
  * Return a self-contained JS string that, when evaluated in a page context,
17
14
  * applies all stealth patches. Safe to call multiple times — the guard flag
@@ -20,16 +17,25 @@ export const STEALTH_GUARD = '__opencli_stealth_applied';
20
17
  export function generateStealthJs(): string {
21
18
  return `
22
19
  (() => {
23
- // Guard: skip if already applied
24
- if (window.${STEALTH_GUARD}) return 'skipped';
25
- // Use defineProperty so the guard flag is non-enumerable (not a detection vector).
26
- Object.defineProperty(window, '${STEALTH_GUARD}', { value: true, configurable: true });
20
+ // Guard: prevent double-injection across separate CDP evaluations.
21
+ // We cannot use a closure variable (each eval is a fresh scope), and
22
+ // window properties / Symbols are discoverable by anti-bot scripts.
23
+ // Instead, stash the flag in a non-enumerable getter on a built-in
24
+ // prototype that fingerprinters are unlikely to scan.
25
+ const _gProto = EventTarget.prototype;
26
+ const _gKey = '__lsn'; // looks like an internal listener cache
27
+ if (_gProto[_gKey]) return 'skipped';
28
+ try {
29
+ Object.defineProperty(_gProto, _gKey, { value: true, enumerable: false, configurable: true });
30
+ } catch {}
27
31
 
28
- // 1. navigator.webdriver → undefined
32
+ // 1. navigator.webdriver → false
29
33
  // Most common check; Playwright/Puppeteer/CDP set this to true.
34
+ // Real Chrome returns false (not undefined) — returning undefined is
35
+ // itself a detection signal for advanced fingerprinters.
30
36
  try {
31
37
  Object.defineProperty(navigator, 'webdriver', {
32
- get: () => undefined,
38
+ get: () => false,
33
39
  configurable: true,
34
40
  });
35
41
  } catch {}
@@ -119,9 +125,17 @@ export function generateStealthJs(): string {
119
125
  // We override the stack property getter on Error.prototype to filter them.
120
126
  // Note: Error.prepareStackTrace is V8/Node-only and not available in
121
127
  // browser page context, so we use a property descriptor approach instead.
128
+ // We use generic protocol patterns instead of product-specific names to
129
+ // also catch our own injected code frames without leaking identifiers.
122
130
  try {
123
131
  const _origDescriptor = Object.getOwnPropertyDescriptor(Error.prototype, 'stack');
124
- const _cdpPatterns = ['puppeteer_evaluation_script', 'pptr:', 'debugger://', '__opencli'];
132
+ const _cdpPatterns = [
133
+ 'puppeteer_evaluation_script',
134
+ 'pptr:',
135
+ 'debugger://',
136
+ '__playwright',
137
+ '__puppeteer',
138
+ ];
125
139
  if (_origDescriptor && _origDescriptor.get) {
126
140
  Object.defineProperty(Error.prototype, 'stack', {
127
141
  get: function () {
@@ -1,6 +1,5 @@
1
1
  import { afterEach, describe, it, expect, vi } from 'vitest';
2
2
  import { BrowserBridge, __test__, generateStealthJs } from './browser/index.js';
3
- import { STEALTH_GUARD } from './browser/stealth.js';
4
3
  import * as daemonClient from './browser/daemon-client.js';
5
4
 
6
5
  describe('browser helpers', () => {
@@ -170,7 +169,8 @@ describe('stealth anti-detection', () => {
170
169
 
171
170
  it('includes guard flag to prevent double-injection', () => {
172
171
  const js = generateStealthJs();
173
- expect(js).toContain(STEALTH_GUARD);
172
+ // Guard uses a non-enumerable property on a built-in prototype
173
+ expect(js).toContain("EventTarget.prototype");
174
174
  // Guard should check early and return 'skipped'
175
175
  expect(js).toContain("return 'skipped'");
176
176
  // Normal path returns 'applied'
@@ -129,4 +129,18 @@ describe('manifest helper rules', () => {
129
129
 
130
130
  expect(scanTs(file, 'demo')).toBeNull();
131
131
  });
132
+
133
+ it('keeps literal domain and navigateBefore for TS adapters', () => {
134
+ const file = path.join(process.cwd(), 'src', 'clis', 'xueqiu', 'fund-holdings.ts');
135
+ const entry = scanTs(file, 'xueqiu');
136
+
137
+ expect(entry).toMatchObject({
138
+ site: 'xueqiu',
139
+ name: 'fund-holdings',
140
+ domain: 'danjuanfunds.com',
141
+ navigateBefore: 'https://danjuanfunds.com/my-money',
142
+ type: 'ts',
143
+ modulePath: 'xueqiu/fund-holdings.js',
144
+ });
145
+ });
132
146
  });
@@ -46,33 +46,9 @@ export interface ManifestEntry {
46
46
  navigateBefore?: boolean | string;
47
47
  }
48
48
 
49
- interface YamlArgDefinition {
50
- type?: string;
51
- default?: unknown;
52
- required?: boolean;
53
- positional?: boolean;
54
- description?: string;
55
- help?: string;
56
- choices?: string[];
57
- }
58
-
59
- interface YamlCliDefinition {
60
- site?: string;
61
- name?: string;
62
- description?: string;
63
- domain?: string;
64
- strategy?: string;
65
- browser?: boolean;
66
- args?: Record<string, YamlArgDefinition>;
67
- columns?: string[];
68
- pipeline?: Record<string, unknown>[];
69
- timeout?: number;
70
- navigateBefore?: boolean | string;
71
- }
49
+ import type { YamlCliDefinition } from './yaml-schema.js';
72
50
 
73
- function isRecord(value: unknown): value is Record<string, unknown> {
74
- return typeof value === 'object' && value !== null && !Array.isArray(value);
75
- }
51
+ import { isRecord } from './utils.js';
76
52
 
77
53
 
78
54
  function extractBalancedBlock(
@@ -179,7 +155,8 @@ export function parseTsArgsBlock(argsBlock: string): ManifestEntry['args'] {
179
155
  choices: parseInlineChoices(body),
180
156
  });
181
157
 
182
- cursor = objectStart + body.length + 2;
158
+ cursor = objectStart + body.length;
159
+ if (cursor <= objectStart) break; // safety: prevent infinite loop
183
160
  }
184
161
 
185
162
  return args;
@@ -283,9 +260,14 @@ export function scanTs(filePath: string, site: string): ManifestEntry | null {
283
260
  entry.args = parseTsArgsBlock(argsBlock);
284
261
  }
285
262
 
286
- // Extract navigateBefore: false
287
- const navMatch = src.match(/navigateBefore\s*:\s*(true|false)/);
288
- if (navMatch) entry.navigateBefore = navMatch[1] === 'true' ? true : false;
263
+ // Extract navigateBefore: false / true / 'https://...'
264
+ const navBoolMatch = src.match(/navigateBefore\s*:\s*(true|false)/);
265
+ if (navBoolMatch) {
266
+ entry.navigateBefore = navBoolMatch[1] === 'true';
267
+ } else {
268
+ const navStringMatch = src.match(/navigateBefore\s*:\s*['"`]([^'"`]+)['"`]/);
269
+ if (navStringMatch) entry.navigateBefore = navStringMatch[1];
270
+ }
289
271
 
290
272
  return entry;
291
273
  } catch (err) {
@@ -300,7 +282,7 @@ export function scanTs(filePath: string, site: string): ManifestEntry | null {
300
282
  * prefer the TS version (it self-registers and typically has richer logic).
301
283
  */
302
284
  export function shouldReplaceManifestEntry(current: ManifestEntry, next: ManifestEntry): boolean {
303
- if (current.type === next.type) return true;
285
+ if (current.type === next.type) return false;
304
286
  return current.type === 'yaml' && next.type === 'ts';
305
287
  }
306
288
 
package/src/cascade.ts CHANGED
@@ -12,6 +12,7 @@
12
12
 
13
13
  import { Strategy } from './registry.js';
14
14
  import type { IPage } from './types.js';
15
+ import { getErrorMessage } from './errors.js';
15
16
 
16
17
  /** Strategy cascade order (simplest → most complex) */
17
18
  const CASCADE_ORDER: Strategy[] = [
@@ -128,9 +129,9 @@ export async function probeEndpoint(
128
129
  result.error = `Strategy ${strategy} requires site-specific implementation`;
129
130
  break;
130
131
  }
131
- } catch (err: any) {
132
+ } catch (err) {
132
133
  result.success = false;
133
- result.error = err.message ?? String(err);
134
+ result.error = getErrorMessage(err);
134
135
  }
135
136
 
136
137
  return result;
@@ -145,9 +146,10 @@ export async function cascadeProbe(
145
146
  url: string,
146
147
  opts: { maxStrategy?: Strategy; timeout?: number } = {},
147
148
  ): Promise<CascadeResult> {
148
- const maxIdx = opts.maxStrategy
149
+ const rawIdx = opts.maxStrategy
149
150
  ? CASCADE_ORDER.indexOf(opts.maxStrategy)
150
151
  : CASCADE_ORDER.indexOf(Strategy.HEADER); // Don't auto-try INTERCEPT/UI
152
+ const maxIdx = rawIdx === -1 ? CASCADE_ORDER.indexOf(Strategy.HEADER) : rawIdx;
151
153
 
152
154
  const probes: ProbeResult[] = [];
153
155
 
package/src/cli.ts CHANGED
@@ -15,6 +15,7 @@ import { PKG_VERSION } from './version.js';
15
15
  import { printCompletionScript } from './completion.js';
16
16
  import { loadExternalClis, executeExternalCli, installExternalCli, registerExternalCli, isBinaryInstalled } from './external.js';
17
17
  import { registerAllCommands } from './commanderAdapter.js';
18
+ import { getErrorMessage } from './errors.js';
18
19
 
19
20
  export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
20
21
  const program = new Command();
@@ -173,8 +174,6 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
173
174
  const r = await generateCliFromUrl({
174
175
  url,
175
176
  BrowserFactory: getBrowserFactory(),
176
- builtinClis: BUILTIN_CLIS,
177
- userClis: USER_CLIS,
178
177
  goal: opts.goal,
179
178
  site: opts.site,
180
179
  workspace,
@@ -262,8 +261,8 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
262
261
  const name = installPlugin(source);
263
262
  await discoverPlugins();
264
263
  console.log(chalk.green(`✅ Plugin "${name}" installed successfully. Commands are ready to use.`));
265
- } catch (err: any) {
266
- console.error(chalk.red(`Error: ${err.message}`));
264
+ } catch (err) {
265
+ console.error(chalk.red(`Error: ${getErrorMessage(err)}`));
267
266
  process.exitCode = 1;
268
267
  }
269
268
  });
@@ -277,25 +276,69 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
277
276
  try {
278
277
  uninstallPlugin(name);
279
278
  console.log(chalk.green(`✅ Plugin "${name}" uninstalled.`));
280
- } catch (err: any) {
281
- console.error(chalk.red(`Error: ${err.message}`));
279
+ } catch (err) {
280
+ console.error(chalk.red(`Error: ${getErrorMessage(err)}`));
282
281
  process.exitCode = 1;
283
282
  }
284
283
  });
285
284
 
286
285
  pluginCmd
287
286
  .command('update')
288
- .description('Update a plugin to the latest version')
289
- .argument('<name>', 'Plugin name')
290
- .action(async (name: string) => {
291
- const { updatePlugin } = await import('./plugin.js');
287
+ .description('Update a plugin (or all plugins) to the latest version')
288
+ .argument('[name]', 'Plugin name (required unless --all is passed)')
289
+ .option('--all', 'Update all installed plugins')
290
+ .action(async (name: string | undefined, opts: { all?: boolean }) => {
291
+ if (!name && !opts.all) {
292
+ console.error(chalk.red('Error: Please specify a plugin name or use the --all flag.'));
293
+ process.exitCode = 1;
294
+ return;
295
+ }
296
+ if (name && opts.all) {
297
+ console.error(chalk.red('Error: Cannot specify both a plugin name and --all.'));
298
+ process.exitCode = 1;
299
+ return;
300
+ }
301
+
302
+ const { updatePlugin, updateAllPlugins } = await import('./plugin.js');
292
303
  const { discoverPlugins } = await import('./discovery.js');
304
+ if (opts.all) {
305
+ const results = updateAllPlugins();
306
+ if (results.length > 0) {
307
+ await discoverPlugins();
308
+ }
309
+
310
+ let hasErrors = false;
311
+ console.log(chalk.bold(' Update Results:'));
312
+ for (const result of results) {
313
+ if (result.success) {
314
+ console.log(` ${chalk.green('✓')} ${result.name}`);
315
+ continue;
316
+ }
317
+ hasErrors = true;
318
+ console.log(` ${chalk.red('✗')} ${result.name} — ${chalk.dim(result.error)}`);
319
+ }
320
+
321
+ if (results.length === 0) {
322
+ console.log(chalk.dim(' No plugins installed.'));
323
+ return;
324
+ }
325
+
326
+ console.log();
327
+ if (hasErrors) {
328
+ console.error(chalk.red('Completed with some errors.'));
329
+ process.exitCode = 1;
330
+ } else {
331
+ console.log(chalk.green('✅ All plugins updated successfully.'));
332
+ }
333
+ return;
334
+ }
335
+
293
336
  try {
294
- updatePlugin(name);
337
+ updatePlugin(name!);
295
338
  await discoverPlugins();
296
339
  console.log(chalk.green(`✅ Plugin "${name}" updated successfully.`));
297
- } catch (err: any) {
298
- console.error(chalk.red(`Error: ${err.message}`));
340
+ } catch (err) {
341
+ console.error(chalk.red(`Error: ${getErrorMessage(err)}`));
299
342
  process.exitCode = 1;
300
343
  }
301
344
  });
@@ -326,9 +369,10 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
326
369
  console.log(chalk.bold(' Installed plugins'));
327
370
  console.log();
328
371
  for (const p of plugins) {
372
+ const version = p.version ? chalk.green(` @${p.version}`) : '';
329
373
  const cmds = p.commands.length > 0 ? chalk.dim(` (${p.commands.join(', ')})`) : '';
330
374
  const src = p.source ? chalk.dim(` ← ${p.source}`) : '';
331
- console.log(` ${chalk.cyan(p.name)}${cmds}${src}`);
375
+ console.log(` ${chalk.cyan(p.name)}${version}${cmds}${src}`);
332
376
  }
333
377
  console.log();
334
378
  console.log(chalk.dim(` ${plugins.length} plugin(s) installed`));
@@ -371,8 +415,8 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
371
415
  })();
372
416
  try {
373
417
  executeExternalCli(name, args, externalClis);
374
- } catch (err: any) {
375
- console.error(chalk.red(`Error: ${err.message}`));
418
+ } catch (err) {
419
+ console.error(chalk.red(`Error: ${getErrorMessage(err)}`));
376
420
  process.exitCode = 1;
377
421
  }
378
422
  }
@@ -408,29 +452,17 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void {
408
452
  registerAllCommands(program, siteGroups);
409
453
 
410
454
  // ── Unknown command fallback ──────────────────────────────────────────────
411
-
412
- const DENY_LIST = new Set([
413
- 'rm', 'sudo', 'dd', 'mkfs', 'fdisk', 'shutdown', 'reboot',
414
- 'kill', 'killall', 'chmod', 'chown', 'passwd', 'su', 'mount',
415
- 'umount', 'format', 'diskutil',
416
- ]);
455
+ // Security: do NOT auto-discover and register arbitrary system binaries.
456
+ // Only explicitly registered external CLIs (via `opencli register`) are allowed.
417
457
 
418
458
  program.on('command:*', (operands: string[]) => {
419
459
  const binary = operands[0];
420
- if (DENY_LIST.has(binary)) {
421
- console.error(chalk.red(`Refusing to register system command '${binary}'.`));
422
- process.exitCode = 1;
423
- return;
424
- }
460
+ console.error(chalk.red(`error: unknown command '${binary}'`));
425
461
  if (isBinaryInstalled(binary)) {
426
- console.log(chalk.cyan(`🔹 Auto-discovered local CLI '${binary}'. Registering...`));
427
- registerExternalCli(binary);
428
- passthroughExternal(binary);
429
- } else {
430
- console.error(chalk.red(`error: unknown command '${binary}'`));
431
- program.outputHelp();
432
- process.exitCode = 1;
462
+ console.error(chalk.dim(` Tip: '${binary}' exists on your PATH. Use 'opencli register ${binary}' to add it as an external CLI.`));
433
463
  }
464
+ program.outputHelp();
465
+ process.exitCode = 1;
434
466
  });
435
467
 
436
468
  program.parse();
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Shared command factories for Electron/desktop app adapters.
3
+ * Eliminates duplicate screenshot/status/new/dump implementations
4
+ * across cursor, codex, chatwise, etc.
5
+ */
6
+
7
+ import * as fs from 'node:fs';
8
+ import { cli, Strategy } from '../../registry.js';
9
+ import type { IPage } from '../../types.js';
10
+ import type { CliOptions } from '../../registry.js';
11
+
12
+ /**
13
+ * Factory: capture DOM HTML + accessibility snapshot.
14
+ */
15
+ export function makeScreenshotCommand(site: string, displayName?: string, extra: Partial<CliOptions> = {}) {
16
+ const label = displayName ?? site;
17
+ return cli({
18
+ ...extra,
19
+ site,
20
+ name: 'screenshot',
21
+ description: `Capture a snapshot of the current ${label} window (DOM + Accessibility tree)`,
22
+ domain: 'localhost',
23
+ strategy: Strategy.UI,
24
+ browser: true,
25
+ args: [
26
+ { name: 'output', required: false, help: `Output file path (default: /tmp/${site}-snapshot.txt)` },
27
+ ],
28
+ columns: ['Status', 'File'],
29
+ func: async (page: IPage, kwargs: any) => {
30
+ const outputPath = (kwargs.output as string) || `/tmp/${site}-snapshot.txt`;
31
+
32
+ const snap = await page.snapshot({ compact: true });
33
+ const html = await page.evaluate('document.documentElement.outerHTML');
34
+
35
+ const htmlPath = outputPath.replace(/\.\w+$/, '') + '-dom.html';
36
+ const snapPath = outputPath.replace(/\.\w+$/, '') + '-a11y.txt';
37
+
38
+ fs.writeFileSync(htmlPath, html);
39
+ fs.writeFileSync(snapPath, typeof snap === 'string' ? snap : JSON.stringify(snap, null, 2));
40
+
41
+ return [
42
+ { Status: 'Success', File: htmlPath },
43
+ { Status: 'Success', File: snapPath },
44
+ ];
45
+ },
46
+ });
47
+ }
48
+
49
+ /**
50
+ * Factory: check CDP connection status.
51
+ */
52
+ export function makeStatusCommand(site: string, displayName?: string, extra: Partial<CliOptions> = {}) {
53
+ const label = displayName ?? site;
54
+ return cli({
55
+ ...extra,
56
+ site,
57
+ name: 'status',
58
+ description: `Check active CDP connection to ${label}`,
59
+ domain: 'localhost',
60
+ strategy: Strategy.UI,
61
+ browser: true,
62
+ columns: ['Status', 'Url', 'Title'],
63
+ func: async (page: IPage) => {
64
+ const url = await page.evaluate('window.location.href');
65
+ const title = await page.evaluate('document.title');
66
+ return [{ Status: 'Connected', Url: url, Title: title }];
67
+ },
68
+ });
69
+ }
70
+
71
+ /**
72
+ * Factory: start a new session via Cmd/Ctrl+N.
73
+ */
74
+ export function makeNewCommand(site: string, displayName?: string, extra: Partial<CliOptions> = {}) {
75
+ const label = displayName ?? site;
76
+ return cli({
77
+ ...extra,
78
+ site,
79
+ name: 'new',
80
+ description: `Start a new ${label} session`,
81
+ domain: 'localhost',
82
+ strategy: Strategy.UI,
83
+ browser: true,
84
+ columns: ['Status'],
85
+ func: async (page: IPage) => {
86
+ const isMac = process.platform === 'darwin';
87
+ await page.pressKey(isMac ? 'Meta+N' : 'Control+N');
88
+ await page.wait(1);
89
+ return [{ Status: 'Success' }];
90
+ },
91
+ });
92
+ }
93
+
94
+ /**
95
+ * Factory: dump DOM + snapshot for reverse-engineering.
96
+ */
97
+ export function makeDumpCommand(site: string) {
98
+ return cli({
99
+ site,
100
+ name: 'dump',
101
+ description: `Dump the DOM and Accessibility tree of ${site} for reverse-engineering`,
102
+ domain: 'localhost',
103
+ strategy: Strategy.UI,
104
+ browser: true,
105
+ columns: ['action', 'files'],
106
+ func: async (page: IPage) => {
107
+ const dom = await page.evaluate('document.body.innerHTML');
108
+ fs.writeFileSync(`/tmp/${site}-dom.html`, dom);
109
+
110
+ const snap = await page.snapshot({ interactive: false });
111
+ fs.writeFileSync(`/tmp/${site}-snapshot.json`, JSON.stringify(snap, null, 2));
112
+
113
+ return [
114
+ {
115
+ action: 'Dom extraction finished',
116
+ files: `/tmp/${site}-dom.html, /tmp/${site}-snapshot.json`,
117
+ },
118
+ ];
119
+ },
120
+ });
121
+ }
@@ -13,6 +13,7 @@
13
13
  import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
14
14
  import { CDPBridge } from '../../browser/cdp.js';
15
15
  import type { IPage } from '../../types.js';
16
+ import { getErrorMessage } from '../../errors.js';
16
17
 
17
18
  // ─── Types ───────────────────────────────────────────────────────────
18
19
 
@@ -461,15 +462,17 @@ export async function startServe(opts: { port?: number } = {}): Promise<void> {
461
462
  cdp = new CDPBridge();
462
463
  try {
463
464
  page = await cdp.connect({ timeout: 15_000 });
464
- } catch (err: any) {
465
+ } catch (err: unknown) {
465
466
  cdp = null;
466
- const isRefused = err?.cause?.code === 'ECONNREFUSED' || err?.message?.includes('ECONNREFUSED');
467
+ const errMsg = getErrorMessage(err);
468
+ const cause = err instanceof Error ? (err.cause as Record<string, unknown> | undefined) : undefined;
469
+ const isRefused = cause?.code === 'ECONNREFUSED' || errMsg.includes('ECONNREFUSED');
467
470
  throw new Error(
468
471
  isRefused
469
472
  ? `Cannot connect to Antigravity at ${endpoint}.\n` +
470
473
  ' 1. Make sure Antigravity is running\n' +
471
474
  ' 2. Launch with: --remote-debugging-port=9224'
472
- : `CDP connection failed: ${err.message}`
475
+ : `CDP connection failed: ${errMsg}`
473
476
  );
474
477
  }
475
478
 
@@ -15,7 +15,7 @@ cli({
15
15
  columns: ['id', 'title', 'authors', 'published'],
16
16
  func: async (_page, args) => {
17
17
  const limit = Math.max(1, Math.min(Number(args.limit), 25));
18
- const query = encodeURIComponent(`all:${args.keyword}`);
18
+ const query = encodeURIComponent(`all:${args.query}`);
19
19
  const xml = await arxivFetch(`search_query=${query}&max_results=${limit}&sortBy=relevance`);
20
20
  const entries = parseEntries(xml);
21
21
  if (!entries.length) throw new CliError('NOT_FOUND', 'No papers found', 'Try a different keyword');
@@ -0,0 +1,79 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const { mockApiGet } = vi.hoisted(() => ({
4
+ mockApiGet: vi.fn(),
5
+ }));
6
+
7
+ vi.mock('./utils.js', () => ({
8
+ apiGet: mockApiGet,
9
+ }));
10
+
11
+ import { getRegistry } from '../../registry.js';
12
+ import './dynamic.js';
13
+
14
+ describe('bilibili dynamic adapter', () => {
15
+ const command = getRegistry().get('bilibili/dynamic');
16
+
17
+ beforeEach(() => {
18
+ mockApiGet.mockReset();
19
+ });
20
+
21
+ it('maps desc text rows from the dynamic feed payload', async () => {
22
+ mockApiGet.mockResolvedValue({
23
+ data: {
24
+ items: [
25
+ {
26
+ id_str: '123',
27
+ modules: {
28
+ module_author: { name: 'Alice' },
29
+ module_dynamic: { desc: { text: 'hello world' } },
30
+ module_stat: { like: { count: 9 } },
31
+ },
32
+ },
33
+ ],
34
+ },
35
+ });
36
+
37
+ const result = await command!.func!({} as any, { limit: 5 });
38
+
39
+ expect(mockApiGet).toHaveBeenCalledWith({}, '/x/polymer/web-dynamic/v1/feed/all', { params: {}, signed: false });
40
+ expect(result).toEqual([
41
+ {
42
+ id: '123',
43
+ author: 'Alice',
44
+ text: 'hello world',
45
+ likes: 9,
46
+ url: 'https://t.bilibili.com/123',
47
+ },
48
+ ]);
49
+ });
50
+
51
+ it('falls back to archive title when desc text is absent', async () => {
52
+ mockApiGet.mockResolvedValue({
53
+ data: {
54
+ items: [
55
+ {
56
+ id_str: '456',
57
+ modules: {
58
+ module_author: { name: 'Bob' },
59
+ module_dynamic: { major: { archive: { title: 'Video title' } } },
60
+ module_stat: { like: { count: 3 } },
61
+ },
62
+ },
63
+ ],
64
+ },
65
+ });
66
+
67
+ const result = await command!.func!({} as any, { limit: 5 });
68
+
69
+ expect(result).toEqual([
70
+ {
71
+ id: '456',
72
+ author: 'Bob',
73
+ text: 'Video title',
74
+ likes: 3,
75
+ url: 'https://t.bilibili.com/456',
76
+ },
77
+ ]);
78
+ });
79
+ });
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import { apiGet, payloadData } from './utils.js';
2
+ import { apiGet, payloadData, getSelfUid } from './utils.js';
3
3
 
4
4
  cli({
5
5
  site: 'bilibili',
@@ -15,9 +15,12 @@ cli({
15
15
  func: async (page, kwargs) => {
16
16
  const { limit = 20, page: pageNum = 1 } = kwargs;
17
17
 
18
+ // Get current user's UID
19
+ const uid = await getSelfUid(page);
20
+
18
21
  // Get default favorite folder ID
19
22
  const foldersPayload = await apiGet(page, '/x/v3/fav/folder/created/list-all', {
20
- params: { up_mid: 0 },
23
+ params: { up_mid: uid },
21
24
  signed: true,
22
25
  });
23
26
  const folders = payloadData(foldersPayload)?.list ?? [];
@@ -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
  import { fetchJson, getSelfUid, resolveUid } from './utils.js';
4
5
 
@@ -14,7 +15,7 @@ cli({
14
15
  ],
15
16
  columns: ['mid', 'name', 'sign', 'following', 'fans'],
16
17
  func: async (page: IPage | null, kwargs: any) => {
17
- if (!page) throw new Error('Requires browser');
18
+ if (!page) throw new CommandExecutionError('Browser session required for bilibili following');
18
19
 
19
20
  // 1. Resolve UID (default to self)
20
21
  const uid = kwargs.uid
@@ -30,7 +31,7 @@ cli({
30
31
  );
31
32
 
32
33
  if (payload.code !== 0) {
33
- throw new Error(`获取关注列表失败: ${payload.message} (${payload.code})`);
34
+ throw new CommandExecutionError(`获取关注列表失败: ${payload.message} (${payload.code})`);
34
35
  }
35
36
 
36
37
  const list = payload.data?.list || [];