@jackwener/opencli 1.7.12 → 1.7.14

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 (419) hide show
  1. package/README.md +8 -7
  2. package/README.zh-CN.md +9 -8
  3. package/cli-manifest.json +12128 -6665
  4. package/clis/1point3acres/digest.js +35 -0
  5. package/clis/1point3acres/forum.js +51 -0
  6. package/clis/1point3acres/forums.js +44 -0
  7. package/clis/1point3acres/hot.js +35 -0
  8. package/clis/1point3acres/latest.js +35 -0
  9. package/clis/1point3acres/notifications.js +64 -0
  10. package/clis/1point3acres/search.js +71 -0
  11. package/clis/1point3acres/thread.js +117 -0
  12. package/clis/1point3acres/user.js +77 -0
  13. package/clis/1point3acres/utils.js +247 -0
  14. package/clis/_shared/desktop-commands.js +4 -0
  15. package/clis/aibase/news.js +110 -0
  16. package/clis/aibase/news.test.js +59 -0
  17. package/clis/amazon/discussion.test.js +1 -28
  18. package/clis/antigravity/watch.js +3 -2
  19. package/clis/arxiv/author.js +44 -0
  20. package/clis/baidu-scholar/search.js +0 -1
  21. package/clis/bbc/topic.js +57 -0
  22. package/clis/bbc/utils.js +79 -0
  23. package/clis/chaoxing/assignments.js +1 -1
  24. package/clis/chaoxing/exams.js +1 -1
  25. package/clis/chatgpt/ask.js +57 -0
  26. package/clis/chatgpt/commands.test.js +45 -0
  27. package/clis/chatgpt/detail.js +46 -0
  28. package/clis/chatgpt/history.js +39 -0
  29. package/clis/chatgpt/image.js +12 -11
  30. package/clis/chatgpt/image.test.js +23 -0
  31. package/clis/chatgpt/new.js +25 -0
  32. package/clis/chatgpt/read.js +43 -0
  33. package/clis/chatgpt/send.js +46 -0
  34. package/clis/chatgpt/status.js +29 -0
  35. package/clis/chatgpt/utils.js +294 -4
  36. package/clis/chatgpt/utils.test.js +13 -0
  37. package/clis/chatgpt-app/ask.js +6 -3
  38. package/clis/chatwise/ask.js +16 -43
  39. package/clis/chatwise/composer.test.js +186 -0
  40. package/clis/chatwise/send.js +2 -24
  41. package/clis/chatwise/utils.js +143 -0
  42. package/clis/claude/ask.js +1 -1
  43. package/clis/claude/detail.js +1 -0
  44. package/clis/claude/history.js +1 -0
  45. package/clis/claude/new.js +1 -0
  46. package/clis/claude/read.js +1 -0
  47. package/clis/claude/send.js +1 -0
  48. package/clis/claude/status.js +1 -0
  49. package/clis/codex/ask.js +15 -9
  50. package/clis/codex/history.js +16 -33
  51. package/clis/codex/projects.js +28 -0
  52. package/clis/codex/read.js +10 -4
  53. package/clis/codex/send.js +10 -3
  54. package/clis/codex/sidebar.js +356 -0
  55. package/clis/codex/sidebar.test.js +329 -0
  56. package/clis/coingecko/categories.js +75 -0
  57. package/clis/coingecko/coin.js +107 -0
  58. package/clis/coingecko/coingecko.test.js +109 -0
  59. package/clis/coingecko/derivatives.js +84 -0
  60. package/clis/coingecko/exchanges.js +74 -0
  61. package/clis/coingecko/global.js +71 -0
  62. package/clis/coingecko/top.js +64 -0
  63. package/clis/coingecko/trending.js +55 -0
  64. package/clis/coupang/add-to-cart.js +21 -13
  65. package/clis/coupang/coupang.test.js +159 -0
  66. package/clis/coupang/product.js +257 -0
  67. package/clis/coupang/search.js +38 -16
  68. package/clis/coupang/utils.js +55 -1
  69. package/clis/crates/crate.js +62 -0
  70. package/clis/crates/search.js +44 -0
  71. package/clis/crates/utils.js +72 -0
  72. package/clis/ctrip/ctrip.test.js +234 -0
  73. package/clis/ctrip/hotel-suggest.js +45 -0
  74. package/clis/ctrip/search.js +22 -68
  75. package/clis/ctrip/utils.js +175 -0
  76. package/clis/cursor/ask.js +6 -3
  77. package/clis/dblp/author.js +133 -0
  78. package/clis/dblp/venue.js +64 -0
  79. package/clis/deepseek/ask.js +12 -7
  80. package/clis/deepseek/ask.test.js +13 -13
  81. package/clis/deepseek/detail.js +38 -0
  82. package/clis/deepseek/detail.test.js +81 -0
  83. package/clis/deepseek/history.js +1 -0
  84. package/clis/deepseek/new.js +1 -0
  85. package/clis/deepseek/read.js +1 -0
  86. package/clis/deepseek/send.js +140 -0
  87. package/clis/deepseek/send.test.js +107 -0
  88. package/clis/deepseek/status.js +1 -0
  89. package/clis/deepseek/utils.js +66 -0
  90. package/clis/deepseek/utils.test.js +107 -1
  91. package/clis/defillama/defillama.test.js +99 -0
  92. package/clis/defillama/protocol.js +84 -0
  93. package/clis/defillama/protocols.js +55 -0
  94. package/clis/defillama/utils.js +99 -0
  95. package/clis/devto/latest.js +74 -0
  96. package/clis/dockerhub/image.js +52 -0
  97. package/clis/dockerhub/search.js +47 -0
  98. package/clis/dockerhub/utils.js +100 -0
  99. package/clis/doubao/ask.js +7 -3
  100. package/clis/doubao/detail.js +1 -0
  101. package/clis/doubao/history.js +1 -0
  102. package/clis/doubao/meeting-summary.js +1 -0
  103. package/clis/doubao/meeting-transcript.js +1 -0
  104. package/clis/doubao/new.js +1 -0
  105. package/clis/doubao/read.js +1 -0
  106. package/clis/doubao/send.js +1 -0
  107. package/clis/doubao/status.js +1 -0
  108. package/clis/douyin/draft.test.js +1 -30
  109. package/clis/endoflife/endoflife.test.js +51 -0
  110. package/clis/endoflife/product.js +55 -0
  111. package/clis/endoflife/utils.js +89 -0
  112. package/clis/facebook/__fixtures__/notifications-page.html +13 -0
  113. package/clis/facebook/notifications.js +326 -30
  114. package/clis/facebook/notifications.test.js +458 -0
  115. package/clis/flathub/app.js +71 -0
  116. package/clis/flathub/flathub.test.js +90 -0
  117. package/clis/flathub/search.js +80 -0
  118. package/clis/flathub/utils.js +114 -0
  119. package/clis/gemini/ask.js +7 -3
  120. package/clis/gemini/ask.test.js +2 -2
  121. package/clis/gemini/deep-research-result.js +6 -2
  122. package/clis/gemini/deep-research-result.test.js +15 -14
  123. package/clis/gemini/deep-research.js +8 -4
  124. package/clis/gemini/deep-research.test.js +15 -18
  125. package/clis/gemini/image.js +7 -2
  126. package/clis/gemini/new.js +1 -0
  127. package/clis/gemini/utils.js +0 -4
  128. package/clis/google-scholar/cite.js +0 -1
  129. package/clis/google-scholar/profile.js +0 -1
  130. package/clis/google-scholar/search.js +0 -1
  131. package/clis/goproxy/goproxy.test.js +103 -0
  132. package/clis/goproxy/module.js +47 -0
  133. package/clis/goproxy/utils.js +165 -0
  134. package/clis/goproxy/versions.js +59 -0
  135. package/clis/gov-law/recent.js +0 -1
  136. package/clis/gov-law/search.js +0 -1
  137. package/clis/gov-policy/__fixtures__/recent.html +16 -0
  138. package/clis/gov-policy/__fixtures__/search.html +41 -0
  139. package/clis/gov-policy/gov-policy.test.js +224 -0
  140. package/clis/gov-policy/recent.js +66 -24
  141. package/clis/gov-policy/search.js +65 -23
  142. package/clis/gov-policy/utils.js +54 -0
  143. package/clis/grok/ask.js +49 -265
  144. package/clis/grok/ask.test.js +21 -46
  145. package/clis/grok/detail.js +60 -0
  146. package/clis/grok/history.js +48 -0
  147. package/clis/grok/{image.ts → image.js} +56 -70
  148. package/clis/grok/image.test.ts +20 -0
  149. package/clis/grok/new.js +20 -0
  150. package/clis/grok/read.js +39 -0
  151. package/clis/grok/send.js +50 -0
  152. package/clis/grok/status.js +41 -0
  153. package/clis/grok/utils.js +326 -0
  154. package/clis/grok/utils.test.js +103 -0
  155. package/clis/hf/datasets.js +88 -0
  156. package/clis/hf/hf.test.js +16 -0
  157. package/clis/hf/models.js +91 -0
  158. package/clis/hf/paper.js +79 -0
  159. package/clis/hf/spaces.js +101 -0
  160. package/clis/hf/top.js +1 -0
  161. package/clis/homebrew/cask.js +39 -0
  162. package/clis/homebrew/formula.js +41 -0
  163. package/clis/homebrew/popular.js +54 -0
  164. package/clis/homebrew/utils.js +100 -0
  165. package/clis/hupu/__fixtures__/hot-home.html +64 -0
  166. package/clis/hupu/detail.js +0 -1
  167. package/clis/hupu/hot.js +156 -35
  168. package/clis/hupu/hot.test.js +224 -0
  169. package/clis/hupu/search.js +0 -1
  170. package/clis/instagram/note.js +1 -1
  171. package/clis/instagram/note.test.js +1 -29
  172. package/clis/instagram/post.js +1 -1
  173. package/clis/instagram/post.test.js +1 -1
  174. package/clis/instagram/reel.js +1 -1
  175. package/clis/instagram/story.js +1 -1
  176. package/clis/instagram/story.test.js +1 -34
  177. package/clis/jd/commands.test.js +1 -24
  178. package/clis/lichess/lichess.test.js +85 -0
  179. package/clis/lichess/top.js +46 -0
  180. package/clis/lichess/user.js +91 -0
  181. package/clis/lichess/utils.js +97 -0
  182. package/clis/linkedin/search.js +107 -10
  183. package/clis/linkedin/search.test.js +222 -0
  184. package/clis/linux-do/feed.js +2 -5
  185. package/clis/linux-do/feed.test.js +35 -0
  186. package/clis/lobsters/domain.js +92 -0
  187. package/clis/maven/artifact.js +49 -0
  188. package/clis/maven/search.js +51 -0
  189. package/clis/maven/utils.js +110 -0
  190. package/clis/mdn/search.js +97 -0
  191. package/clis/medium/tag.js +135 -0
  192. package/clis/npm/downloads.js +59 -0
  193. package/clis/npm/package.js +70 -0
  194. package/clis/npm/search.js +49 -0
  195. package/clis/npm/utils.js +76 -0
  196. package/clis/nuget/nuget.test.js +111 -0
  197. package/clis/nuget/package.js +101 -0
  198. package/clis/nuget/search.js +69 -0
  199. package/clis/nuget/utils.js +87 -0
  200. package/clis/nvd/cve.js +121 -0
  201. package/clis/oeis/oeis.test.js +88 -0
  202. package/clis/oeis/search.js +63 -0
  203. package/clis/oeis/sequence.js +71 -0
  204. package/clis/oeis/utils.js +88 -0
  205. package/clis/openalex/search.js +69 -0
  206. package/clis/openalex/utils.js +160 -0
  207. package/clis/openalex/work.js +65 -0
  208. package/clis/openfda/drug-label.js +74 -0
  209. package/clis/openfda/food-recall.js +65 -0
  210. package/clis/openfda/openfda.test.js +114 -0
  211. package/clis/openfda/utils.js +67 -0
  212. package/clis/osv/osv.test.js +97 -0
  213. package/clis/osv/query.js +72 -0
  214. package/clis/osv/utils.js +169 -0
  215. package/clis/osv/vulnerability.js +54 -0
  216. package/clis/packagist/package.js +49 -0
  217. package/clis/packagist/search.js +43 -0
  218. package/clis/packagist/utils.js +113 -0
  219. package/clis/paperreview/feedback.js +1 -1
  220. package/clis/paperreview/review.js +1 -1
  221. package/clis/paperreview/submit.js +1 -1
  222. package/clis/pixiv/download.test.js +1 -1
  223. package/clis/pixiv/illusts.test.js +1 -1
  224. package/clis/pixiv/search.test.js +1 -1
  225. package/clis/pubmed/article.js +50 -0
  226. package/clis/pubmed/author.js +64 -0
  227. package/clis/pubmed/citations.js +36 -0
  228. package/clis/pubmed/pubmed.test.js +276 -0
  229. package/clis/pubmed/related.js +45 -0
  230. package/clis/pubmed/search.js +75 -0
  231. package/clis/pubmed/utils.js +309 -0
  232. package/clis/pypi/downloads.js +66 -0
  233. package/clis/pypi/package.js +79 -0
  234. package/clis/pypi/utils.js +55 -0
  235. package/clis/quark/mv.js +1 -1
  236. package/clis/quark/save.js +1 -1
  237. package/clis/qwen/ask.js +85 -0
  238. package/clis/qwen/detail.js +62 -0
  239. package/clis/qwen/history.js +61 -0
  240. package/clis/qwen/image.js +179 -0
  241. package/clis/qwen/new.js +23 -0
  242. package/clis/qwen/read.js +41 -0
  243. package/clis/qwen/send.js +55 -0
  244. package/clis/qwen/status.js +37 -0
  245. package/clis/qwen/utils.js +409 -0
  246. package/clis/qwen/utils.test.js +45 -0
  247. package/clis/rest-countries/country.js +65 -0
  248. package/clis/rest-countries/region.js +64 -0
  249. package/clis/rest-countries/rest-countries.test.js +83 -0
  250. package/clis/rest-countries/utils.js +126 -0
  251. package/clis/reuters/article-detail.js +53 -0
  252. package/clis/reuters/reuters.test.js +299 -0
  253. package/clis/reuters/search.js +45 -34
  254. package/clis/reuters/utils.js +159 -0
  255. package/clis/rfc/rfc.js +52 -0
  256. package/clis/rfc/rfc.test.js +74 -0
  257. package/clis/rfc/utils.js +72 -0
  258. package/clis/rubygems/gem.js +42 -0
  259. package/clis/rubygems/search.js +47 -0
  260. package/clis/rubygems/utils.js +86 -0
  261. package/clis/stackoverflow/related.js +66 -0
  262. package/clis/stackoverflow/stackoverflow.test.js +58 -0
  263. package/clis/stackoverflow/tag.js +60 -0
  264. package/clis/stackoverflow/user.js +50 -0
  265. package/clis/stackoverflow/utils.js +118 -0
  266. package/clis/steam/app.js +67 -0
  267. package/clis/steam/search.js +58 -0
  268. package/clis/steam/steam.test.js +46 -0
  269. package/clis/steam/utils.js +107 -0
  270. package/clis/taobao/commands.test.js +1 -24
  271. package/clis/test-utils.js +61 -0
  272. package/clis/tieba/hot.js +0 -1
  273. package/clis/tiktok/comment.js +128 -41
  274. package/clis/tiktok/creator-videos.js +270 -0
  275. package/clis/tiktok/creator-videos.test.js +113 -0
  276. package/clis/tiktok/explore.js +137 -29
  277. package/clis/tiktok/follow.js +115 -33
  278. package/clis/tiktok/following.js +157 -36
  279. package/clis/tiktok/friends.js +139 -37
  280. package/clis/tiktok/live.js +137 -41
  281. package/clis/tiktok/notifications.js +141 -38
  282. package/clis/tiktok/refactor.test.js +389 -0
  283. package/clis/tiktok/unfollow.js +124 -38
  284. package/clis/tiktok/user.js +203 -29
  285. package/clis/tiktok/utils.js +505 -0
  286. package/clis/tiktok/write-refactor.test.js +370 -0
  287. package/clis/toutiao/articles.js +36 -62
  288. package/clis/toutiao/hot.js +63 -0
  289. package/clis/toutiao/toutiao.test.js +378 -0
  290. package/clis/toutiao/utils.js +161 -0
  291. package/clis/tvmaze/search.js +61 -0
  292. package/clis/tvmaze/show.js +60 -0
  293. package/clis/tvmaze/tvmaze.test.js +93 -0
  294. package/clis/tvmaze/utils.js +110 -0
  295. package/clis/twitter/accept.js +1 -1
  296. package/clis/twitter/followers.js +134 -69
  297. package/clis/twitter/quote.js +139 -0
  298. package/clis/twitter/quote.test.js +106 -0
  299. package/clis/twitter/reply-dm.js +1 -1
  300. package/clis/twitter/reply.test.js +1 -29
  301. package/clis/twitter/retweet.js +99 -0
  302. package/clis/twitter/retweet.test.js +69 -0
  303. package/clis/twitter/shared.js +38 -0
  304. package/clis/twitter/shared.test.js +28 -1
  305. package/clis/twitter/unlike.js +87 -0
  306. package/clis/twitter/unlike.test.js +72 -0
  307. package/clis/twitter/unretweet.js +99 -0
  308. package/clis/twitter/unretweet.test.js +69 -0
  309. package/clis/uisdc/news.js +105 -0
  310. package/clis/uisdc/news.test.js +66 -0
  311. package/clis/wanfang/search.js +0 -1
  312. package/clis/web/read.js +47 -17
  313. package/clis/web/read.test.js +101 -1
  314. package/clis/weixin/create-draft.js +1 -1
  315. package/clis/weixin/drafts.js +1 -1
  316. package/clis/weixin/drafts.test.js +5 -1
  317. package/clis/weixin/search.js +157 -0
  318. package/clis/weixin/search.test.js +227 -0
  319. package/clis/wikidata/entity.js +60 -0
  320. package/clis/wikidata/search.js +50 -0
  321. package/clis/wikidata/utils.js +117 -0
  322. package/clis/wikidata/wikidata.test.js +83 -0
  323. package/clis/wikipedia/page.js +95 -0
  324. package/clis/wttr/current.js +63 -0
  325. package/clis/wttr/forecast.js +71 -0
  326. package/clis/wttr/utils.js +50 -0
  327. package/clis/wttr/wttr.test.js +84 -0
  328. package/clis/xianyu/chat.js +16 -4
  329. package/clis/xianyu/chat.test.js +64 -0
  330. package/clis/xianyu/publish.js +485 -0
  331. package/clis/xianyu/publish.test.js +220 -0
  332. package/clis/xiaoe/catalog.js +105 -40
  333. package/clis/xiaoe/content.js +164 -29
  334. package/clis/xiaoe/courses.js +86 -29
  335. package/clis/xiaoe/xiaoe.test.js +486 -0
  336. package/clis/xiaohongshu/creator-notes-summary.js +1 -1
  337. package/clis/xiaohongshu/publish.js +16 -3
  338. package/clis/xiaohongshu/publish.test.js +46 -1
  339. package/clis/youtube/transcript.js +13 -19
  340. package/clis/youtube/transcript.test.js +17 -0
  341. package/clis/yuanbao/ask.js +17 -66
  342. package/clis/yuanbao/ask.test.js +5 -5
  343. package/clis/yuanbao/detail.js +65 -0
  344. package/clis/yuanbao/history.js +51 -0
  345. package/clis/yuanbao/new.js +1 -0
  346. package/clis/yuanbao/read.js +38 -0
  347. package/clis/yuanbao/send.js +57 -0
  348. package/clis/yuanbao/shared.js +297 -5
  349. package/clis/yuanbao/shared.test.js +80 -0
  350. package/clis/yuanbao/status.js +44 -0
  351. package/clis/zlibrary/commands.test.js +1 -11
  352. package/dist/src/browser/base-page.d.ts +9 -0
  353. package/dist/src/browser/base-page.js +44 -1
  354. package/dist/src/browser/base-page.test.js +66 -0
  355. package/dist/src/browser/bridge.js +47 -45
  356. package/dist/src/browser/cdp.d.ts +1 -0
  357. package/dist/src/browser/cdp.js +51 -9
  358. package/dist/src/browser/daemon-client.d.ts +4 -0
  359. package/dist/src/browser/errors.js +1 -1
  360. package/dist/src/browser/page.d.ts +1 -1
  361. package/dist/src/browser/page.js +3 -1
  362. package/dist/src/browser/page.test.js +29 -0
  363. package/dist/src/browser/target-errors.d.ts +2 -1
  364. package/dist/src/browser/target-errors.js +1 -0
  365. package/dist/src/browser/target-resolver.d.ts +25 -0
  366. package/dist/src/browser/target-resolver.js +43 -0
  367. package/dist/src/browser.test.js +18 -0
  368. package/dist/src/build-manifest.js +9 -4
  369. package/dist/src/build-manifest.test.js +2 -8
  370. package/dist/src/capabilityRouting.d.ts +16 -1
  371. package/dist/src/capabilityRouting.js +24 -1
  372. package/dist/src/capabilityRouting.test.js +19 -1
  373. package/dist/src/cli.js +76 -11
  374. package/dist/src/cli.test.js +241 -1
  375. package/dist/src/commanderAdapter.js +23 -9
  376. package/dist/src/commanderAdapter.test.js +0 -1
  377. package/dist/src/discovery.js +2 -5
  378. package/dist/src/errors.js +1 -1
  379. package/dist/src/execution.d.ts +1 -1
  380. package/dist/src/execution.js +111 -27
  381. package/dist/src/execution.test.js +326 -17
  382. package/dist/src/help.d.ts +27 -2
  383. package/dist/src/help.js +196 -23
  384. package/dist/src/help.test.d.ts +1 -0
  385. package/dist/src/help.test.js +54 -0
  386. package/dist/src/main.js +14 -1
  387. package/dist/src/manifest-types.d.ts +5 -3
  388. package/dist/src/pipeline/executor.js +1 -1
  389. package/dist/src/pipeline/executor.test.js +8 -0
  390. package/dist/src/pipeline/registry.d.ts +9 -0
  391. package/dist/src/pipeline/registry.js +13 -1
  392. package/dist/src/pipeline/steps/browser.d.ts +1 -0
  393. package/dist/src/pipeline/steps/browser.js +10 -0
  394. package/dist/src/pipeline/steps/download.test.js +1 -0
  395. package/dist/src/registry-api.d.ts +1 -1
  396. package/dist/src/registry.d.ts +12 -11
  397. package/dist/src/registry.js +16 -6
  398. package/dist/src/registry.test.js +2 -2
  399. package/dist/src/runtime.d.ts +2 -1
  400. package/dist/src/runtime.js +1 -1
  401. package/dist/src/serialization.d.ts +2 -2
  402. package/dist/src/serialization.js +4 -6
  403. package/dist/src/serialization.test.js +17 -0
  404. package/dist/src/types.d.ts +17 -0
  405. package/dist/src/validate.js +15 -11
  406. package/dist/src/validate.test.d.ts +9 -0
  407. package/dist/src/validate.test.js +90 -0
  408. package/package.json +1 -1
  409. package/scripts/fetch-adapters.js +1 -1
  410. package/scripts/typed-error-lint-baseline.json +5 -77
  411. package/clis/ctrip/search.test.js +0 -64
  412. package/clis/gov-policy/commands.test.js +0 -27
  413. package/clis/linux-do/category.js +0 -37
  414. package/clis/linux-do/hot.js +0 -26
  415. package/clis/linux-do/latest.js +0 -19
  416. package/clis/pixiv/test-utils.js +0 -23
  417. package/clis/toutiao/articles.test.js +0 -30
  418. package/dist/src/analysis.d.ts +0 -40
  419. package/dist/src/analysis.js +0 -172
@@ -0,0 +1,62 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { EmptyResultError } from '@jackwener/opencli/errors';
3
+ import {
4
+ QIANWEN_DOMAIN,
5
+ bubbleHtmlToMarkdown,
6
+ dismissLoginModal,
7
+ getMessageBubbles,
8
+ normalizeBooleanFlag,
9
+ parseQianwenSessionId,
10
+ } from './utils.js';
11
+
12
+ cli({
13
+ site: 'qwen',
14
+ name: 'detail',
15
+ access: 'read',
16
+ description: 'Open a Qianwen conversation by ID and read its messages',
17
+ domain: QIANWEN_DOMAIN,
18
+ strategy: Strategy.COOKIE,
19
+ browser: true,
20
+ navigateBefore: false,
21
+ browserSession: { reuse: 'site' },
22
+ args: [
23
+ { name: 'id', positional: true, required: true, help: 'Session ID (32-char hex) or full https://www.qianwen.com/chat/<id> URL' },
24
+ { name: 'markdown', type: 'boolean', default: false, help: 'Emit assistant replies as markdown' },
25
+ ],
26
+ columns: ['Role', 'Text'],
27
+ func: async (page, kwargs) => {
28
+ const sessionId = parseQianwenSessionId(kwargs.id);
29
+ const wantMarkdown = normalizeBooleanFlag(kwargs.markdown, false);
30
+
31
+ await page.goto(`https://www.qianwen.com/chat/${sessionId}`);
32
+ await page.wait(2);
33
+ await dismissLoginModal(page);
34
+
35
+ // Poll for the conversation transcript to load. Qianwen renders the chat
36
+ // page shell before fetching message history, so a fixed wait can race the
37
+ // initial empty render. Cap at ~20s to surface real "no data" cases without
38
+ // hanging on broken IDs.
39
+ let bubbles = [];
40
+ const POLL_DEADLINE_MS = 20_000;
41
+ const POLL_INTERVAL_S = 1;
42
+ const startedAt = Date.now();
43
+ while (Date.now() - startedAt < POLL_DEADLINE_MS) {
44
+ bubbles = await getMessageBubbles(page);
45
+ if (bubbles.length > 0) break;
46
+ await page.wait(POLL_INTERVAL_S);
47
+ }
48
+
49
+ if (!bubbles.length) {
50
+ throw new EmptyResultError(
51
+ 'qwen detail',
52
+ `No visible messages found for conversation ${sessionId}. Verify the ID is correct and that the session belongs to the current device's b-user-id (or that you are logged in for cross-device sync).`,
53
+ );
54
+ }
55
+ return bubbles.map((b) => ({
56
+ Role: b.role,
57
+ Text: wantMarkdown && b.role === 'Assistant' && b.html
58
+ ? (bubbleHtmlToMarkdown(b.html) || b.text)
59
+ : b.text,
60
+ }));
61
+ },
62
+ });
@@ -0,0 +1,61 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
3
+ import {
4
+ QIANWEN_DOMAIN,
5
+ authRequired,
6
+ dismissLoginModal,
7
+ ensureOnQianwen,
8
+ getSessionListFromApi,
9
+ } from './utils.js';
10
+
11
+ function formatDate(ms) {
12
+ if (!ms) return '';
13
+ const d = new Date(ms);
14
+ if (Number.isNaN(d.getTime())) return '';
15
+ const pad = (n) => String(n).padStart(2, '0');
16
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`;
17
+ }
18
+
19
+ cli({
20
+ site: 'qwen',
21
+ name: 'history',
22
+ access: 'read',
23
+ description: 'List recent Qianwen conversations (requires login)',
24
+ domain: QIANWEN_DOMAIN,
25
+ strategy: Strategy.COOKIE,
26
+ browser: true,
27
+ browserSession: { reuse: 'site' },
28
+ navigateBefore: false,
29
+ args: [
30
+ { name: 'limit', type: 'int', default: 20, help: 'Max conversations to show (default 20, max 100)' },
31
+ ],
32
+ columns: ['Index', 'Title', 'Updated', 'Url'],
33
+ func: async (page, kwargs) => {
34
+ const limit = Number(kwargs.limit ?? 20);
35
+ if (!Number.isInteger(limit) || limit <= 0) {
36
+ throw new ArgumentError('limit must be a positive integer');
37
+ }
38
+ if (limit > 100) {
39
+ throw new ArgumentError('limit must be <= 100');
40
+ }
41
+ await ensureOnQianwen(page);
42
+ await dismissLoginModal(page);
43
+ await page.wait(1);
44
+ const result = await getSessionListFromApi(page, limit);
45
+ if (!result.ok) {
46
+ if (result.status === 401 || result.status === 403) throw authRequired();
47
+ if (!result.sessions.length) {
48
+ throw new CommandExecutionError(`Qianwen history API failed (status=${result.status}) ${result.error || ''}`.trim());
49
+ }
50
+ }
51
+ if (!result.sessions.length) {
52
+ throw new EmptyResultError('qwen history', 'No Qianwen conversations found.');
53
+ }
54
+ return result.sessions.slice(0, limit).map((s, i) => ({
55
+ Index: i + 1,
56
+ Title: s.title || '(untitled)',
57
+ Updated: formatDate(s.updated_at),
58
+ Url: `https://www.qianwen.com/chat/${s.id}`,
59
+ }));
60
+ },
61
+ });
@@ -0,0 +1,179 @@
1
+ import * as os from 'node:os';
2
+ import * as path from 'node:path';
3
+ import { cli, Strategy } from '@jackwener/opencli/registry';
4
+ import { saveBase64ToFile } from '@jackwener/opencli/utils';
5
+ import { ArgumentError, CommandExecutionError, EmptyResultError, TimeoutError } from '@jackwener/opencli/errors';
6
+ import {
7
+ QIANWEN_DOMAIN,
8
+ authRequired,
9
+ dismissLoginModal,
10
+ ensureOnQianwen,
11
+ getMessageBubbles,
12
+ hasLoginGate,
13
+ normalizeBooleanFlag,
14
+ sendMessage,
15
+ setFeatureToggle,
16
+ startNewChat,
17
+ } from './utils.js';
18
+
19
+ function displayPath(filePath) {
20
+ const home = os.homedir();
21
+ return filePath.startsWith(home) ? `~${filePath.slice(home.length)}` : filePath;
22
+ }
23
+
24
+ function extFromMime(mime) {
25
+ if (!mime) return '.jpg';
26
+ if (mime.includes('png')) return '.png';
27
+ if (mime.includes('webp')) return '.webp';
28
+ if (mime.includes('gif')) return '.gif';
29
+ return '.jpg';
30
+ }
31
+
32
+ async function collectImageUrls(page, sinceAssistantId) {
33
+ return await page.evaluate(`(() => {
34
+ const scope = ${JSON.stringify(sinceAssistantId || '')};
35
+ const bubbles = Array.from(document.querySelectorAll('[data-msgid$="-answer"]'));
36
+ const target = scope
37
+ ? bubbles.find((b) => b.getAttribute('data-msgid') === scope)
38
+ : bubbles[bubbles.length - 1];
39
+ if (!target) return [];
40
+ const imgs = Array.from(target.querySelectorAll('img'))
41
+ .map((node) => node.getAttribute('src') || '')
42
+ .filter((src) => src
43
+ && !src.startsWith('data:')
44
+ && !/\\.(svg)$/i.test(src)
45
+ && !src.includes('alicdn.com/imgextra'));
46
+ return Array.from(new Set(imgs));
47
+ })()`);
48
+ }
49
+
50
+ async function waitForImageUrls(page, sinceAssistantId, timeoutSeconds) {
51
+ const startTime = Date.now();
52
+ let lastUrls = [];
53
+ while (Date.now() - startTime < timeoutSeconds * 1000) {
54
+ await page.wait(2);
55
+ if (await hasLoginGate(page)) return { status: 'auth_required', urls: [] };
56
+ const urls = await collectImageUrls(page, sinceAssistantId);
57
+ if (urls.length && urls.length === lastUrls.length && urls.every((u, i) => u === lastUrls[i])) {
58
+ return { status: 'ok', urls };
59
+ }
60
+ if (urls.length) {
61
+ await page.wait(2);
62
+ const urls2 = await collectImageUrls(page, sinceAssistantId);
63
+ if (urls2.length === urls.length && urls2.every((u, i) => u === urls[i])) {
64
+ return { status: 'ok', urls: urls2 };
65
+ }
66
+ lastUrls = urls2;
67
+ continue;
68
+ }
69
+ lastUrls = urls;
70
+ }
71
+ return lastUrls.length ? { status: 'partial', urls: lastUrls } : { status: 'timeout', urls: [] };
72
+ }
73
+
74
+ async function fetchImageAsset(page, url) {
75
+ return await page.evaluate(`(async () => {
76
+ try {
77
+ const res = await fetch(${JSON.stringify(url)}, { credentials: 'include' });
78
+ if (!res.ok) return { ok: false, status: res.status };
79
+ const mime = res.headers.get('content-type') || '';
80
+ const buf = await res.arrayBuffer();
81
+ const bytes = new Uint8Array(buf);
82
+ let binary = '';
83
+ const chunk = 0x8000;
84
+ for (let i = 0; i < bytes.length; i += chunk) {
85
+ binary += String.fromCharCode.apply(null, bytes.subarray(i, i + chunk));
86
+ }
87
+ return { ok: true, mime, base64: btoa(binary) };
88
+ } catch (error) {
89
+ return { ok: false, status: 0, error: String(error?.message || error) };
90
+ }
91
+ })()`);
92
+ }
93
+
94
+ cli({
95
+ site: 'qwen',
96
+ name: 'image',
97
+ access: 'write',
98
+ description: 'Generate images with Qianwen (AI生图) and save them locally',
99
+ domain: QIANWEN_DOMAIN,
100
+ strategy: Strategy.COOKIE,
101
+ browser: true,
102
+ browserSession: { reuse: 'site' },
103
+ navigateBefore: false,
104
+ defaultFormat: 'plain',
105
+ args: [
106
+ { name: 'prompt', required: true, positional: true, help: 'Image prompt to send' },
107
+ { name: 'op', default: '~/Pictures/qianwen', help: 'Output directory' },
108
+ { name: 'new', type: 'boolean', default: true, help: 'Start a new chat before generating (default: true)' },
109
+ { name: 'sd', type: 'boolean', default: false, help: 'Skip download; only show the Qianwen link' },
110
+ { name: 'timeout', type: 'int', default: 180, help: 'Max seconds to wait for the image response' },
111
+ ],
112
+ columns: ['Status', 'File', 'Link'],
113
+ func: async (page, kwargs) => {
114
+ const prompt = String(kwargs.prompt || '').trim();
115
+ if (!prompt) throw new ArgumentError('prompt is required');
116
+ const outputDir = String(kwargs.op || '~/Pictures/qianwen').replace(/^~\//, `${os.homedir()}/`);
117
+ const startFresh = normalizeBooleanFlag(kwargs.new, true);
118
+ const skipDownload = normalizeBooleanFlag(kwargs.sd, false);
119
+ const timeout = Number(kwargs.timeout ?? 180);
120
+ if (!Number.isInteger(timeout) || timeout <= 0) {
121
+ throw new ArgumentError('timeout must be a positive integer');
122
+ }
123
+
124
+ await ensureOnQianwen(page);
125
+ await dismissLoginModal(page);
126
+ if (startFresh) {
127
+ await startNewChat(page);
128
+ await dismissLoginModal(page);
129
+ }
130
+ await setFeatureToggle(page, 'image', true);
131
+ await page.wait(0.5);
132
+
133
+ const send = await sendMessage(page, prompt);
134
+ if (!send?.ok) {
135
+ if (await hasLoginGate(page)) throw authRequired();
136
+ throw new CommandExecutionError(send?.reason || 'Failed to send Qianwen image prompt');
137
+ }
138
+
139
+ // Grab the newest assistant bubble id after send by polling briefly
140
+ let targetId = '';
141
+ for (let i = 0; i < 5; i += 1) {
142
+ await page.wait(1);
143
+ const bubbles = await getMessageBubbles(page);
144
+ const lastAnswer = [...bubbles].reverse().find((b) => b.role === 'Assistant');
145
+ if (lastAnswer) { targetId = lastAnswer.id; break; }
146
+ }
147
+
148
+ const waitResult = await waitForImageUrls(page, targetId, timeout);
149
+ const link = await page.evaluate('window.location.href').catch(() => 'https://www.qianwen.com/');
150
+ if (waitResult.status === 'auth_required') throw authRequired();
151
+ if (waitResult.status === 'timeout') {
152
+ throw new TimeoutError('qianwen image', timeout, 'No generated images observed before timeout.');
153
+ }
154
+
155
+ const urls = waitResult.urls;
156
+ if (skipDownload) {
157
+ return [{ Status: '🎨 generated', File: null, Link: link }];
158
+ }
159
+
160
+ const stamp = Date.now();
161
+ const results = [];
162
+ for (let i = 0; i < urls.length; i += 1) {
163
+ const url = urls[i];
164
+ const asset = await fetchImageAsset(page, url);
165
+ if (!asset?.ok) {
166
+ throw new CommandExecutionError(`Failed to fetch generated Qianwen image ${i + 1}: status=${asset?.status || '?'}`);
167
+ }
168
+ const suffix = urls.length > 1 ? `_${i + 1}` : '';
169
+ const ext = extFromMime(asset.mime);
170
+ const filePath = path.join(outputDir, `qianwen_${stamp}${suffix}${ext}`);
171
+ await saveBase64ToFile(asset.base64, filePath);
172
+ results.push({ Status: '✅ saved', File: displayPath(filePath), Link: link });
173
+ }
174
+ if (!results.length) {
175
+ throw new EmptyResultError('qwen image', 'No generated images were available to download.');
176
+ }
177
+ return results;
178
+ },
179
+ });
@@ -0,0 +1,23 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { QIANWEN_DOMAIN, QIANWEN_URL, startNewChat, dismissLoginModal } from './utils.js';
3
+
4
+ cli({
5
+ site: 'qwen',
6
+ name: 'new',
7
+ access: 'write',
8
+ description: 'Start a new conversation in Qianwen',
9
+ domain: QIANWEN_DOMAIN,
10
+ strategy: Strategy.COOKIE,
11
+ browser: true,
12
+ browserSession: { reuse: 'site' },
13
+ navigateBefore: false,
14
+ args: [],
15
+ columns: ['Status'],
16
+ func: async (page) => {
17
+ await page.goto(QIANWEN_URL);
18
+ await page.wait(2);
19
+ await dismissLoginModal(page);
20
+ await startNewChat(page);
21
+ return [{ Status: 'New chat started' }];
22
+ },
23
+ });
@@ -0,0 +1,41 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import {
3
+ QIANWEN_DOMAIN,
4
+ bubbleHtmlToMarkdown,
5
+ dismissLoginModal,
6
+ ensureOnQianwen,
7
+ getMessageBubbles,
8
+ normalizeBooleanFlag,
9
+ } from './utils.js';
10
+
11
+ cli({
12
+ site: 'qwen',
13
+ name: 'read',
14
+ access: 'read',
15
+ description: 'Read messages in the current Qianwen conversation',
16
+ domain: QIANWEN_DOMAIN,
17
+ strategy: Strategy.COOKIE,
18
+ browser: true,
19
+ browserSession: { reuse: 'site' },
20
+ navigateBefore: false,
21
+ args: [
22
+ { name: 'markdown', type: 'boolean', default: false, help: 'Emit assistant replies as markdown' },
23
+ ],
24
+ columns: ['Role', 'Text'],
25
+ func: async (page, kwargs) => {
26
+ const wantMarkdown = normalizeBooleanFlag(kwargs.markdown, false);
27
+ await ensureOnQianwen(page);
28
+ await dismissLoginModal(page);
29
+ await page.wait(2);
30
+ const bubbles = await getMessageBubbles(page);
31
+ if (!bubbles.length) {
32
+ return [{ Role: 'system', Text: 'No visible messages in the current conversation.' }];
33
+ }
34
+ return bubbles.map((b) => ({
35
+ Role: b.role,
36
+ Text: wantMarkdown && b.role === 'Assistant' && b.html
37
+ ? (bubbleHtmlToMarkdown(b.html) || b.text)
38
+ : b.text,
39
+ }));
40
+ },
41
+ });
@@ -0,0 +1,55 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { ArgumentError, CommandExecutionError } from '@jackwener/opencli/errors';
3
+ import {
4
+ QIANWEN_DOMAIN,
5
+ authRequired,
6
+ dismissLoginModal,
7
+ ensureOnQianwen,
8
+ hasLoginGate,
9
+ normalizeBooleanFlag,
10
+ sendMessage,
11
+ setFeatureToggle,
12
+ startNewChat,
13
+ } from './utils.js';
14
+
15
+ cli({
16
+ site: 'qwen',
17
+ name: 'send',
18
+ access: 'write',
19
+ description: 'Fire-and-forget: send a prompt to Qianwen without waiting for the reply',
20
+ domain: QIANWEN_DOMAIN,
21
+ strategy: Strategy.COOKIE,
22
+ browser: true,
23
+ browserSession: { reuse: 'site' },
24
+ navigateBefore: false,
25
+ args: [
26
+ { name: 'prompt', required: true, positional: true, help: 'Prompt to send to Qianwen' },
27
+ { name: 'new', type: 'boolean', default: false, help: 'Start a new chat before sending' },
28
+ { name: 'think', type: 'boolean', default: false, help: 'Enable 深度思考 (DeepThink)' },
29
+ { name: 'research', type: 'boolean', default: false, help: 'Enable 深度研究 (DeepResearch)' },
30
+ ],
31
+ columns: ['Status', 'Prompt'],
32
+ func: async (page, kwargs) => {
33
+ const prompt = String(kwargs.prompt || '').trim();
34
+ if (!prompt) throw new ArgumentError('prompt is required');
35
+ const startFresh = normalizeBooleanFlag(kwargs.new, false);
36
+ const useThink = normalizeBooleanFlag(kwargs.think, false);
37
+ const useResearch = normalizeBooleanFlag(kwargs.research, false);
38
+
39
+ await ensureOnQianwen(page);
40
+ await dismissLoginModal(page);
41
+ if (startFresh) {
42
+ await startNewChat(page);
43
+ await dismissLoginModal(page);
44
+ }
45
+ if (useThink) await setFeatureToggle(page, 'think', true);
46
+ if (useResearch) await setFeatureToggle(page, 'research', true);
47
+
48
+ const send = await sendMessage(page, prompt);
49
+ if (!send?.ok) {
50
+ if (await hasLoginGate(page)) throw authRequired();
51
+ throw new CommandExecutionError(send?.reason || 'Failed to send Qianwen prompt');
52
+ }
53
+ return [{ Status: 'sent', Prompt: prompt }];
54
+ },
55
+ });
@@ -0,0 +1,37 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+ import { QIANWEN_DOMAIN, ensureOnQianwen, isLoggedIn, getCurrentSessionId, getModelLabel } from './utils.js';
3
+
4
+ cli({
5
+ site: 'qwen',
6
+ name: 'status',
7
+ access: 'read',
8
+ description: 'Check Qianwen page availability, login state, current session and model',
9
+ domain: QIANWEN_DOMAIN,
10
+ strategy: Strategy.COOKIE,
11
+ browser: true,
12
+ browserSession: { reuse: 'site' },
13
+ navigateBefore: false,
14
+ args: [],
15
+ columns: ['Status', 'Login', 'Model', 'SessionId', 'Url'],
16
+ func: async (page) => {
17
+ await ensureOnQianwen(page);
18
+ await page.wait(2);
19
+ const [loggedIn, sessionId, model, url] = await Promise.all([
20
+ isLoggedIn(page),
21
+ getCurrentSessionId(page),
22
+ getModelLabel(page),
23
+ page.evaluate('window.location.href').catch(() => ''),
24
+ ]);
25
+ // Model / SessionId may be unknown when the page is loading or the user
26
+ // is in guest mode. Surface that as `null` (typed unknown) instead of a
27
+ // string sentinel like '-' — agents can branch on null cleanly, and a
28
+ // sentinel string would silently get treated as a real model name.
29
+ return [{
30
+ Status: 'Connected',
31
+ Login: loggedIn ? 'Yes' : 'No (guest mode)',
32
+ Model: model ? model : null,
33
+ SessionId: sessionId ? sessionId : null,
34
+ Url: typeof url === 'string' ? url : '',
35
+ }];
36
+ },
37
+ });