@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,114 @@
1
+ import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import type { CliCommand } from '../../registry.js';
3
+ import { getRegistry } from '../../registry.js';
4
+ import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
5
+ import { createPageMock } from './test-utils.js';
6
+
7
+ // Mock download dependencies before importing the adapter
8
+ const { mockHttpDownload, mockMkdirSync } = vi.hoisted(() => ({
9
+ mockHttpDownload: vi.fn(),
10
+ mockMkdirSync: vi.fn(),
11
+ }));
12
+
13
+ vi.mock('../../download/index.js', () => ({
14
+ formatCookieHeader: vi.fn().mockReturnValue('cookie=value'),
15
+ httpDownload: mockHttpDownload,
16
+ }));
17
+
18
+ vi.mock('node:fs', () => ({
19
+ mkdirSync: mockMkdirSync,
20
+ }));
21
+
22
+ // Now import the adapter (after mocks are set up)
23
+ await import('./download.js');
24
+
25
+ let cmd: CliCommand;
26
+
27
+ beforeAll(() => {
28
+ cmd = getRegistry().get('pixiv/download')!;
29
+ expect(cmd?.func).toBeTypeOf('function');
30
+ });
31
+
32
+ describe('pixiv download', () => {
33
+ beforeEach(() => {
34
+ mockHttpDownload.mockReset();
35
+ mockMkdirSync.mockReset();
36
+ });
37
+
38
+ it('throws CommandExecutionError on invalid illust ID', async () => {
39
+ const page = createPageMock([]);
40
+
41
+ await expect(cmd.func!(page, { 'illust-id': 'abc', output: '/tmp/test' })).rejects.toThrow(CommandExecutionError);
42
+ });
43
+
44
+ it('throws AuthRequiredError on 403', async () => {
45
+ const page = createPageMock([{ __httpError: 403 }]);
46
+
47
+ await expect(cmd.func!(page, { 'illust-id': '12345', output: '/tmp/test' })).rejects.toThrow(AuthRequiredError);
48
+ });
49
+
50
+ it('throws CommandExecutionError on 404', async () => {
51
+ const page = createPageMock([{ __httpError: 404 }]);
52
+
53
+ await expect(cmd.func!(page, { 'illust-id': '12345', output: '/tmp/test' })).rejects.toThrow(CommandExecutionError);
54
+ });
55
+
56
+ it('throws CommandExecutionError on non-auth HTTP failure', async () => {
57
+ const page = createPageMock([{ __httpError: 500 }]);
58
+
59
+ await expect(cmd.func!(page, { 'illust-id': '12345', output: '/tmp/test' })).rejects.toThrow(CommandExecutionError);
60
+ });
61
+
62
+ it('returns failure when no images found', async () => {
63
+ const page = createPageMock([{ body: [] }]);
64
+
65
+ const result = (await cmd.func!(page, { 'illust-id': '12345', output: '/tmp/test' })) as any[];
66
+ expect(result).toEqual([{ index: 0, type: '-', status: 'failed', size: 'No images found' }]);
67
+ });
68
+
69
+ it('downloads images with Referer header', async () => {
70
+ mockHttpDownload.mockResolvedValue({ success: true, size: 1024000 });
71
+
72
+ const page = createPageMock([
73
+ {
74
+ body: [
75
+ { urls: { original: 'https://i.pximg.net/img-original/img/2025/01/01/00/00/00/12345_p0.png' } },
76
+ { urls: { original: 'https://i.pximg.net/img-original/img/2025/01/01/00/00/00/12345_p1.jpg' } },
77
+ ],
78
+ },
79
+ ]);
80
+
81
+ const result = (await cmd.func!(page, { 'illust-id': '12345', output: '/tmp/test' })) as any[];
82
+
83
+ expect(result).toHaveLength(2);
84
+ expect(result[0]).toMatchObject({ index: 1, type: 'image', status: 'success' });
85
+ expect(result[1]).toMatchObject({ index: 2, type: 'image', status: 'success' });
86
+
87
+ // Verify Referer header was passed
88
+ expect(mockHttpDownload).toHaveBeenCalledTimes(2);
89
+ const firstCallOpts = mockHttpDownload.mock.calls[0][2];
90
+ expect(firstCallOpts.headers).toEqual({ Referer: 'https://www.pixiv.net/' });
91
+ });
92
+
93
+ it('handles individual download failures gracefully', async () => {
94
+ mockHttpDownload
95
+ .mockResolvedValueOnce({ success: true, size: 512000 })
96
+ .mockRejectedValueOnce(new Error('Connection timeout'));
97
+
98
+ const page = createPageMock([
99
+ {
100
+ body: [
101
+ { urls: { original: 'https://i.pximg.net/img/12345_p0.png' } },
102
+ { urls: { original: 'https://i.pximg.net/img/12345_p1.png' } },
103
+ ],
104
+ },
105
+ ]);
106
+
107
+ const result = (await cmd.func!(page, { 'illust-id': '12345', output: '/tmp/test' })) as any[];
108
+
109
+ expect(result).toHaveLength(2);
110
+ expect(result[0].status).toBe('success');
111
+ expect(result[1].status).toBe('failed');
112
+ expect(result[1].size).toBe('Connection timeout');
113
+ });
114
+ });
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Pixiv download — download all images from an illustration.
3
+ *
4
+ * Pixiv's CDN (i.pximg.net) requires Referer: https://www.pixiv.net/ header.
5
+ * Uses the /ajax/illust/{id}/pages API to get original-quality image URLs.
6
+ */
7
+
8
+ import * as fs from 'node:fs';
9
+ import * as path from 'node:path';
10
+ import { cli, Strategy } from '../../registry.js';
11
+ import { formatCookieHeader, httpDownload } from '../../download/index.js';
12
+ import { formatBytes } from '../../download/progress.js';
13
+ import { CommandExecutionError, getErrorMessage } from '../../errors.js';
14
+ import { pixivFetch } from './utils.js';
15
+
16
+ cli({
17
+ site: 'pixiv',
18
+ name: 'download',
19
+ description: 'Download illustration images from Pixiv',
20
+ domain: 'www.pixiv.net',
21
+ strategy: Strategy.COOKIE,
22
+ args: [
23
+ { name: 'illust-id', positional: true, required: true, help: 'Illustration ID' },
24
+ { name: 'output', default: './pixiv-downloads', help: 'Output directory' },
25
+ ],
26
+ columns: ['index', 'type', 'status', 'size'],
27
+
28
+ func: async (page, kwargs) => {
29
+ const illustId = String(kwargs['illust-id'] ?? '');
30
+ const output = String(kwargs.output ?? './pixiv-downloads');
31
+
32
+ if (!/^\d+$/.test(illustId)) {
33
+ throw new CommandExecutionError(`Invalid illustration ID: ${illustId}`);
34
+ }
35
+
36
+ // pixivFetch handles navigate + error checking; returns the response body directly
37
+ const pages: any[] = await pixivFetch(page, `/ajax/illust/${illustId}/pages`, {
38
+ notFoundMsg: `Illustration not found: ${illustId}`,
39
+ }) || [];
40
+
41
+ if (pages.length === 0) {
42
+ return [{ index: 0, type: '-', status: 'failed', size: 'No images found' }];
43
+ }
44
+
45
+ // Extract cookies for authenticated downloads
46
+ const cookies = formatCookieHeader(await page.getCookies({ domain: 'pixiv.net' }));
47
+
48
+ // Create output directory
49
+ const outputDir = path.join(output, illustId);
50
+ fs.mkdirSync(outputDir, { recursive: true });
51
+
52
+ const results = [];
53
+
54
+ for (let i = 0; i < pages.length; i++) {
55
+ const p = pages[i];
56
+ const url = p.urls?.original || p.urls?.regular || '';
57
+ if (!url) {
58
+ results.push({ index: i + 1, type: 'image', status: 'failed', size: 'No URL' });
59
+ continue;
60
+ }
61
+
62
+ try {
63
+ const ext = path.extname(new URL(url).pathname) || '.jpg';
64
+ const filename = `${illustId}_p${i}${ext}`;
65
+ const destPath = path.join(outputDir, filename);
66
+
67
+ const result = await httpDownload(url, destPath, {
68
+ cookies,
69
+ headers: { Referer: 'https://www.pixiv.net/' },
70
+ timeout: 60000,
71
+ });
72
+
73
+ results.push({
74
+ index: i + 1,
75
+ type: 'image',
76
+ status: result.success ? 'success' : 'failed',
77
+ size: result.success ? formatBytes(result.size) : (result.error || 'unknown error'),
78
+ });
79
+ } catch (err) {
80
+ results.push({
81
+ index: i + 1,
82
+ type: 'image',
83
+ status: 'failed',
84
+ size: getErrorMessage(err),
85
+ });
86
+ }
87
+ }
88
+
89
+ return results;
90
+ },
91
+ });
@@ -0,0 +1,115 @@
1
+ import { beforeAll, describe, expect, it } from 'vitest';
2
+ import type { CliCommand } from '../../registry.js';
3
+ import { getRegistry } from '../../registry.js';
4
+ import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
5
+ import { createPageMock } from './test-utils.js';
6
+ import './illusts.js';
7
+
8
+ let cmd: CliCommand;
9
+
10
+ beforeAll(() => {
11
+ cmd = getRegistry().get('pixiv/illusts')!;
12
+ expect(cmd?.func).toBeTypeOf('function');
13
+ });
14
+
15
+ describe('pixiv illusts', () => {
16
+ it('throws CommandExecutionError on invalid user ID', async () => {
17
+ const page = createPageMock([]);
18
+
19
+ await expect(cmd.func!(page, { 'user-id': 'abc', limit: 5 })).rejects.toThrow(CommandExecutionError);
20
+ });
21
+
22
+ it('throws AuthRequiredError on 401', async () => {
23
+ const page = createPageMock([{ __httpError: 401 }]);
24
+
25
+ await expect(cmd.func!(page, { 'user-id': '11', limit: 5 })).rejects.toThrow(AuthRequiredError);
26
+ });
27
+
28
+ it('throws generic error on non-auth HTTP failure', async () => {
29
+ const page = createPageMock([{ __httpError: 500 }]);
30
+
31
+ await expect(cmd.func!(page, { 'user-id': '11', limit: 5 })).rejects.toThrow(CommandExecutionError);
32
+ });
33
+
34
+ it('returns empty array when user has no illusts', async () => {
35
+ const page = createPageMock([
36
+ { body: { illusts: {} } },
37
+ ]);
38
+
39
+ const result = await cmd.func!(page, { 'user-id': '11', limit: 5 });
40
+ expect(result).toEqual([]);
41
+ });
42
+
43
+ it('fetches illust IDs then batch-fetches details', async () => {
44
+ const page = createPageMock([
45
+ // Step 1: profile/all returns illust IDs
46
+ {
47
+ body: {
48
+ illusts: { '99999': null, '88888': null, '77777': null },
49
+ },
50
+ },
51
+ // Step 2: batch detail response
52
+ {
53
+ body: {
54
+ works: {
55
+ '99999': {
56
+ id: '99999',
57
+ title: 'Latest Work',
58
+ pageCount: 2,
59
+ bookmarkCount: 300,
60
+ tags: ['original', 'fantasy'],
61
+ createDate: '2025-01-15T12:00:00+09:00',
62
+ },
63
+ '88888': {
64
+ id: '88888',
65
+ title: 'Older Work',
66
+ pageCount: 1,
67
+ bookmarkCount: 150,
68
+ tags: ['landscape'],
69
+ createDate: '2024-12-01T10:00:00+09:00',
70
+ },
71
+ },
72
+ },
73
+ },
74
+ ]);
75
+
76
+ const result = (await cmd.func!(page, { 'user-id': '11', limit: 3 })) as any[];
77
+
78
+ // Should be sorted newest first (99999 > 88888 > 77777)
79
+ expect(result).toHaveLength(2); // 77777 has no detail data, filtered out
80
+ expect(result[0]).toMatchObject({
81
+ rank: 1,
82
+ title: 'Latest Work',
83
+ illust_id: '99999',
84
+ pages: 2,
85
+ bookmarks: 300,
86
+ created: '2025-01-15',
87
+ });
88
+ expect(result[1]).toMatchObject({
89
+ rank: 2,
90
+ title: 'Older Work',
91
+ illust_id: '88888',
92
+ });
93
+ });
94
+
95
+ it('respects the limit on illust IDs fetched', async () => {
96
+ const page = createPageMock([
97
+ {
98
+ body: {
99
+ illusts: { '100': null, '200': null, '300': null, '400': null, '500': null },
100
+ },
101
+ },
102
+ {
103
+ body: {
104
+ works: {
105
+ '500': { id: '500', title: 'W5', pageCount: 1, bookmarkCount: 0, tags: [], createDate: '' },
106
+ '400': { id: '400', title: 'W4', pageCount: 1, bookmarkCount: 0, tags: [], createDate: '' },
107
+ },
108
+ },
109
+ },
110
+ ]);
111
+
112
+ const result = (await cmd.func!(page, { 'user-id': '11', limit: 2 })) as any[];
113
+ expect(result).toHaveLength(2);
114
+ });
115
+ });
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Pixiv illusts — list illustrations by an artist.
3
+ *
4
+ * Two-step process:
5
+ * 1. Fetch all illust IDs from the user's profile
6
+ * 2. Batch-fetch details for the most recent ones (max 48 IDs per request)
7
+ */
8
+
9
+ import { cli, Strategy } from '../../registry.js';
10
+ import { CommandExecutionError } from '../../errors.js';
11
+ import { pixivFetch, BATCH_SIZE } from './utils.js';
12
+
13
+ cli({
14
+ site: 'pixiv',
15
+ name: 'illusts',
16
+ description: "List a Pixiv artist's illustrations",
17
+ domain: 'www.pixiv.net',
18
+ strategy: Strategy.COOKIE,
19
+ args: [
20
+ { name: 'user-id', positional: true, required: true, help: 'Pixiv user ID' },
21
+ { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
22
+ ],
23
+ columns: ['rank', 'title', 'illust_id', 'pages', 'bookmarks', 'tags', 'created'],
24
+
25
+ func: async (page, kwargs) => {
26
+ const userId = String(kwargs['user-id'] ?? '');
27
+ const limit = Number(kwargs.limit) || 20;
28
+
29
+ if (!/^\d+$/.test(userId)) {
30
+ throw new CommandExecutionError(`Invalid user ID: ${userId}`);
31
+ }
32
+
33
+ // Step 1: get all illust IDs
34
+ const profileBody = await pixivFetch(page, `/ajax/user/${userId}/profile/all`, {
35
+ notFoundMsg: `User not found: ${userId}`,
36
+ });
37
+
38
+ const allIds = Object.keys(profileBody?.illusts || {})
39
+ .sort((a, b) => Number(b) - Number(a))
40
+ .slice(0, limit);
41
+
42
+ if (allIds.length === 0) return [];
43
+
44
+ // Step 2: batch fetch details (Pixiv supports up to ~48 IDs per request)
45
+ const allWorks: Record<string, any> = {};
46
+
47
+ for (let offset = 0; offset < allIds.length; offset += BATCH_SIZE) {
48
+ const batch = allIds.slice(offset, offset + BATCH_SIZE);
49
+ const idsParam = batch.map(id => `ids[]=${id}`).join('&');
50
+
51
+ // pixivFetch navigates on each call; for subsequent batches we re-navigate,
52
+ // which is fine — the cookie is already attached.
53
+ const detailBody = await pixivFetch(
54
+ page,
55
+ `/ajax/user/${userId}/profile/illusts?${idsParam}&work_category=illustManga&is_first_page=${offset === 0 ? 1 : 0}`,
56
+ );
57
+
58
+ Object.assign(allWorks, detailBody?.works || {});
59
+ }
60
+
61
+ return allIds
62
+ .map((id, i) => {
63
+ const w = allWorks[id];
64
+ if (!w) return null;
65
+ return {
66
+ rank: i + 1,
67
+ title: w.title || '',
68
+ illust_id: w.id,
69
+ pages: w.pageCount || 1,
70
+ bookmarks: w.bookmarkCount || 0,
71
+ tags: (w.tags || []).slice(0, 5).join(', '),
72
+ created: (w.createDate || '').split('T')[0],
73
+ url: 'https://www.pixiv.net/artworks/' + w.id,
74
+ };
75
+ })
76
+ .filter(Boolean);
77
+ },
78
+ });
@@ -0,0 +1,53 @@
1
+ site: pixiv
2
+ name: ranking
3
+ description: Pixiv illustration rankings (daily/weekly/monthly)
4
+ domain: www.pixiv.net
5
+ strategy: cookie
6
+ browser: true
7
+
8
+ args:
9
+ mode:
10
+ type: str
11
+ default: daily
12
+ description: Ranking mode
13
+ choices: [daily, weekly, monthly, rookie, original, male, female, daily_r18, weekly_r18]
14
+ page:
15
+ type: int
16
+ default: 1
17
+ description: Page number
18
+ limit:
19
+ type: int
20
+ default: 20
21
+ description: Number of results
22
+
23
+ pipeline:
24
+ - navigate: https://www.pixiv.net
25
+
26
+ - evaluate: |
27
+ (async () => {
28
+ const mode = ${{ args.mode | json }};
29
+ const page = ${{ args.page | json }};
30
+ const limit = ${{ args.limit | json }};
31
+ const res = await fetch(
32
+ 'https://www.pixiv.net/ranking.php?mode=' + mode + '&p=' + page + '&format=json',
33
+ { credentials: 'include' }
34
+ );
35
+ if (!res.ok) {
36
+ if (res.status === 401 || res.status === 403) throw new Error('Authentication required — please log in to Pixiv in Chrome');
37
+ throw new Error('Pixiv request failed (HTTP ' + res.status + ')');
38
+ }
39
+ const data = await res.json();
40
+ const items = (data?.contents || []).slice(0, limit);
41
+ return items.map((item, i) => ({
42
+ rank: item.rank,
43
+ title: item.title,
44
+ author: item.user_name,
45
+ user_id: item.user_id,
46
+ illust_id: item.illust_id,
47
+ pages: item.illust_page_count,
48
+ bookmarks: item.illust_bookmark_count,
49
+ url: 'https://www.pixiv.net/artworks/' + item.illust_id
50
+ }));
51
+ })()
52
+
53
+ columns: [rank, title, author, illust_id, pages, bookmarks]
@@ -0,0 +1,97 @@
1
+ import { beforeAll, describe, expect, it } from 'vitest';
2
+ import type { CliCommand } from '../../registry.js';
3
+ import { getRegistry } from '../../registry.js';
4
+ import { AuthRequiredError, CommandExecutionError } from '../../errors.js';
5
+ import { createPageMock } from './test-utils.js';
6
+ import './search.js';
7
+
8
+ let cmd: CliCommand;
9
+
10
+ beforeAll(() => {
11
+ cmd = getRegistry().get('pixiv/search')!;
12
+ expect(cmd?.func).toBeTypeOf('function');
13
+ });
14
+
15
+ describe('pixiv search', () => {
16
+ it('throws AuthRequiredError on 401', async () => {
17
+ const page = createPageMock([{ __httpError: 401 }]);
18
+
19
+ await expect(cmd.func!(page, { query: '初音ミク', limit: 5 })).rejects.toThrow(AuthRequiredError);
20
+ });
21
+
22
+ it('throws generic error on non-auth HTTP failure', async () => {
23
+ const page = createPageMock([{ __httpError: 500 }]);
24
+
25
+ await expect(cmd.func!(page, { query: 'test', limit: 5 })).rejects.toThrow(CommandExecutionError);
26
+ });
27
+
28
+ it('returns ranked results with correct fields', async () => {
29
+ const page = createPageMock([
30
+ {
31
+ body: {
32
+ illust: {
33
+ data: [
34
+ {
35
+ id: '12345',
36
+ title: 'Miku Illustration',
37
+ userName: 'artist1',
38
+ userId: '100',
39
+ pageCount: 3,
40
+ bookmarkCount: 500,
41
+ tags: ['初音ミク', 'VOCALOID', 'ミク'],
42
+ },
43
+ {
44
+ id: '67890',
45
+ title: 'Another Art',
46
+ userName: 'artist2',
47
+ userId: '200',
48
+ pageCount: 1,
49
+ bookmarkCount: 100,
50
+ tags: ['オリジナル'],
51
+ },
52
+ ],
53
+ },
54
+ },
55
+ },
56
+ ]);
57
+
58
+ const result = (await cmd.func!(page, { query: '初音ミク', limit: 10 })) as any[];
59
+
60
+ expect(result).toHaveLength(2);
61
+ expect(result[0]).toMatchObject({
62
+ rank: 1,
63
+ title: 'Miku Illustration',
64
+ author: 'artist1',
65
+ illust_id: '12345',
66
+ pages: 3,
67
+ bookmarks: 500,
68
+ });
69
+ expect(result[1]).toMatchObject({ rank: 2, illust_id: '67890' });
70
+ });
71
+
72
+ it('respects the limit parameter', async () => {
73
+ const page = createPageMock([
74
+ {
75
+ body: {
76
+ illust: {
77
+ data: [
78
+ { id: '1', title: 'A', userName: 'u1', userId: '1', pageCount: 1, bookmarkCount: 0, tags: [] },
79
+ { id: '2', title: 'B', userName: 'u2', userId: '2', pageCount: 1, bookmarkCount: 0, tags: [] },
80
+ { id: '3', title: 'C', userName: 'u3', userId: '3', pageCount: 1, bookmarkCount: 0, tags: [] },
81
+ ],
82
+ },
83
+ },
84
+ },
85
+ ]);
86
+
87
+ const result = (await cmd.func!(page, { query: 'test', limit: 2 })) as any[];
88
+ expect(result).toHaveLength(2);
89
+ });
90
+
91
+ it('returns empty array when no results', async () => {
92
+ const page = createPageMock([{ body: { illust: { data: [] } } }]);
93
+
94
+ const result = await cmd.func!(page, { query: 'nonexistent', limit: 10 });
95
+ expect(result).toEqual([]);
96
+ });
97
+ });
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Pixiv search — search illustrations by keyword/tag.
3
+ *
4
+ * Uses the internal Ajax search API with browser cookies for authentication.
5
+ */
6
+
7
+ import { cli, Strategy } from '../../registry.js';
8
+ import { pixivFetch } from './utils.js';
9
+
10
+ cli({
11
+ site: 'pixiv',
12
+ name: 'search',
13
+ description: 'Search Pixiv illustrations by keyword',
14
+ domain: 'www.pixiv.net',
15
+ strategy: Strategy.COOKIE,
16
+ args: [
17
+ { name: 'query', positional: true, required: true, help: 'Search keyword or tag' },
18
+ { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
19
+ { name: 'order', type: 'str', default: 'date_d', help: 'Sort order', choices: ['date_d', 'date', 'popular_d', 'popular_male_d', 'popular_female_d'] },
20
+ { name: 'mode', type: 'str', default: 'all', help: 'Search mode', choices: ['all', 'safe', 'r18'] },
21
+ { name: 'page', type: 'int', default: 1, help: 'Page number' },
22
+ ],
23
+ columns: ['rank', 'title', 'author', 'illust_id', 'pages', 'bookmarks', 'tags'],
24
+
25
+ func: async (page, kwargs) => {
26
+ const { query, limit = 20, order = 'date_d', mode = 'all', page: pageNum = 1 } = kwargs;
27
+ const encoded = encodeURIComponent(query);
28
+
29
+ // Pixiv search API requires the keyword in BOTH the URL path and the `word` query param.
30
+ const body = await pixivFetch(
31
+ page,
32
+ `/ajax/search/illustrations/${encoded}`,
33
+ { params: { word: query, order, mode, p: pageNum, s_mode: 's_tag_full', type: 'illust_and_ugoira' } },
34
+ );
35
+
36
+ const items: any[] = body?.illust?.data || [];
37
+
38
+ return items
39
+ .filter((item: any) => item.id)
40
+ .slice(0, Number(limit))
41
+ .map((item: any, i: number) => ({
42
+ rank: i + 1,
43
+ title: item.title || '',
44
+ author: item.userName || '',
45
+ user_id: item.userId || '',
46
+ illust_id: item.id,
47
+ pages: item.pageCount || 1,
48
+ bookmarks: item.bookmarkCount || 0,
49
+ tags: (item.tags || []).slice(0, 5).join(', '),
50
+ url: 'https://www.pixiv.net/artworks/' + item.id,
51
+ }));
52
+ },
53
+ });
@@ -0,0 +1,29 @@
1
+ import { vi } from 'vitest';
2
+ import type { IPage } from '../../types.js';
3
+
4
+ /**
5
+ * Create a minimal page mock with only the methods commonly used by Pixiv adapters.
6
+ *
7
+ * Since all TS adapters now go through `pixivFetch` which calls `page.evaluate`,
8
+ * the evaluate results should match the raw Pixiv Ajax response format:
9
+ * - Success: `{ body: ... }` (pixivFetch returns the `body` field)
10
+ * - HTTP error: `{ __httpError: <status> }` (pixivFetch detects and throws)
11
+ *
12
+ * Additional methods can be overridden via the overrides parameter.
13
+ */
14
+ export function createPageMock(
15
+ evaluateResults: any[],
16
+ overrides?: Partial<IPage>,
17
+ ): IPage {
18
+ const evaluate = vi.fn();
19
+ for (const result of evaluateResults) {
20
+ evaluate.mockResolvedValueOnce(result);
21
+ }
22
+
23
+ return {
24
+ goto: vi.fn().mockResolvedValue(undefined),
25
+ evaluate,
26
+ getCookies: vi.fn().mockResolvedValue([]),
27
+ ...overrides,
28
+ } as unknown as IPage;
29
+ }
@@ -0,0 +1,46 @@
1
+ site: pixiv
2
+ name: user
3
+ description: View Pixiv artist profile
4
+ domain: www.pixiv.net
5
+ strategy: cookie
6
+ browser: true
7
+
8
+ args:
9
+ uid:
10
+ type: str
11
+ required: true
12
+ positional: true
13
+ description: Pixiv user ID
14
+
15
+ pipeline:
16
+ - navigate: https://www.pixiv.net
17
+
18
+ - evaluate: |
19
+ (async () => {
20
+ const uid = ${{ args.uid | json }};
21
+ const res = await fetch(
22
+ 'https://www.pixiv.net/ajax/user/' + uid + '?full=1',
23
+ { credentials: 'include' }
24
+ );
25
+ if (!res.ok) {
26
+ if (res.status === 401 || res.status === 403) throw new Error('Authentication required — please log in to Pixiv in Chrome');
27
+ if (res.status === 404) throw new Error('User not found: ' + uid);
28
+ throw new Error('Pixiv request failed (HTTP ' + res.status + ')');
29
+ }
30
+ const data = await res.json();
31
+ const b = data?.body;
32
+ if (!b) throw new Error('User not found');
33
+ return [{
34
+ user_id: uid,
35
+ name: b.name,
36
+ premium: b.premium ? 'Yes' : 'No',
37
+ following: b.following,
38
+ illusts: typeof b.illusts === 'object' ? Object.keys(b.illusts).length : (b.illusts || 0),
39
+ manga: typeof b.manga === 'object' ? Object.keys(b.manga).length : (b.manga || 0),
40
+ novels: typeof b.novels === 'object' ? Object.keys(b.novels).length : (b.novels || 0),
41
+ comment: (b.comment || '').slice(0, 80),
42
+ url: 'https://www.pixiv.net/users/' + uid
43
+ }];
44
+ })()
45
+
46
+ columns: [user_id, name, premium, following, illusts, manga, novels, comment]