@jackwener/opencli 1.6.0 → 1.6.2

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 (390) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +27 -45
  4. package/README.zh-CN.md +32 -34
  5. package/autoresearch/browse-tasks.json +18 -20
  6. package/autoresearch/commands/debug.ts +163 -0
  7. package/autoresearch/commands/fix.ts +145 -0
  8. package/autoresearch/commands/plan.ts +88 -0
  9. package/autoresearch/commands/run.ts +138 -0
  10. package/autoresearch/config.ts +82 -0
  11. package/autoresearch/engine.ts +359 -0
  12. package/autoresearch/eval-all.ts +127 -0
  13. package/autoresearch/eval-browse.ts +1 -1
  14. package/autoresearch/eval-publish.ts +238 -0
  15. package/autoresearch/eval-save.ts +249 -0
  16. package/autoresearch/eval-skill.ts +14 -8
  17. package/autoresearch/eval-v2ex.ts +220 -0
  18. package/autoresearch/eval-zhihu.ts +230 -0
  19. package/autoresearch/logger.ts +69 -0
  20. package/autoresearch/presets/combined-reliability.ts +27 -0
  21. package/autoresearch/presets/index.ts +23 -0
  22. package/autoresearch/presets/operate-reliability.ts +24 -0
  23. package/autoresearch/presets/save-reliability.ts +26 -0
  24. package/autoresearch/presets/skill-quality.ts +20 -0
  25. package/autoresearch/presets/v2ex-reliability.ts +24 -0
  26. package/autoresearch/presets/zhihu-reliability.ts +25 -0
  27. package/autoresearch/publish-tasks.json +345 -0
  28. package/autoresearch/run-save.sh +11 -0
  29. package/autoresearch/save-adapters/xhs-explore-deep.ts +64 -0
  30. package/autoresearch/save-adapters/xhs-note-comments.ts +61 -0
  31. package/autoresearch/save-adapters/xhs-search-full.ts +62 -0
  32. package/autoresearch/save-adapters/zhihu-hot-detail.ts +52 -0
  33. package/autoresearch/save-adapters/zhihu-question-full.ts +57 -0
  34. package/autoresearch/save-adapters/zhihu-search-detail.ts +53 -0
  35. package/autoresearch/save-tasks.json +281 -0
  36. package/autoresearch/v2ex-tasks.json +899 -0
  37. package/autoresearch/zhihu-tasks.json +848 -0
  38. package/bun.lock +615 -0
  39. package/dist/browser/base-page.d.ts +4 -2
  40. package/dist/browser/base-page.js +37 -4
  41. package/dist/browser/bridge.js +10 -8
  42. package/dist/browser/cdp.js +2 -6
  43. package/dist/browser/daemon-client.d.ts +11 -1
  44. package/dist/browser/daemon-client.js +3 -0
  45. package/dist/browser/dom-helpers.d.ts +4 -2
  46. package/dist/browser/dom-helpers.js +42 -31
  47. package/dist/browser/dom-snapshot.js +23 -1
  48. package/dist/browser/page.d.ts +7 -2
  49. package/dist/browser/page.js +112 -30
  50. package/dist/browser.test.js +1 -1
  51. package/dist/build-manifest.d.ts +1 -0
  52. package/dist/build-manifest.js +1 -0
  53. package/dist/cli-manifest.json +1133 -182
  54. package/dist/cli.d.ts +2 -0
  55. package/dist/cli.js +48 -7
  56. package/dist/cli.test.d.ts +1 -0
  57. package/dist/cli.test.js +88 -0
  58. package/dist/clis/1688/item.d.ts +70 -0
  59. package/dist/clis/1688/item.js +187 -0
  60. package/dist/clis/1688/item.test.d.ts +1 -0
  61. package/dist/clis/1688/item.test.js +67 -0
  62. package/dist/clis/1688/search.d.ts +56 -0
  63. package/dist/clis/1688/search.js +309 -0
  64. package/dist/clis/1688/search.test.d.ts +1 -0
  65. package/dist/clis/1688/search.test.js +75 -0
  66. package/dist/clis/1688/shared.d.ts +112 -0
  67. package/dist/clis/1688/shared.js +514 -0
  68. package/dist/clis/1688/shared.test.d.ts +1 -0
  69. package/dist/clis/1688/shared.test.js +57 -0
  70. package/dist/clis/1688/store.d.ts +45 -0
  71. package/dist/clis/1688/store.js +226 -0
  72. package/dist/clis/1688/store.test.d.ts +1 -0
  73. package/dist/clis/1688/store.test.js +62 -0
  74. package/dist/clis/amazon/bestsellers.d.ts +0 -20
  75. package/dist/clis/amazon/bestsellers.js +6 -129
  76. package/dist/clis/amazon/bestsellers.test.js +12 -3
  77. package/dist/clis/amazon/movers-shakers.d.ts +1 -0
  78. package/dist/clis/amazon/movers-shakers.js +7 -0
  79. package/dist/clis/amazon/new-releases.d.ts +1 -0
  80. package/dist/clis/amazon/new-releases.js +7 -0
  81. package/dist/clis/amazon/rankings.d.ts +59 -0
  82. package/dist/clis/amazon/rankings.js +226 -0
  83. package/dist/clis/amazon/rankings.test.d.ts +1 -0
  84. package/dist/clis/amazon/rankings.test.js +41 -0
  85. package/dist/clis/amazon/shared.d.ts +11 -0
  86. package/dist/clis/amazon/shared.js +121 -11
  87. package/dist/clis/amazon/shared.test.js +11 -0
  88. package/dist/clis/bilibili/comments.js +2 -2
  89. package/dist/clis/bilibili/comments.test.js +3 -2
  90. package/dist/clis/bilibili/download.js +2 -1
  91. package/dist/clis/bilibili/subtitle.js +4 -3
  92. package/dist/clis/bilibili/subtitle.test.js +2 -1
  93. package/dist/clis/bilibili/utils.d.ts +5 -0
  94. package/dist/clis/bilibili/utils.js +30 -0
  95. package/dist/clis/bilibili/utils.test.d.ts +1 -0
  96. package/dist/clis/bilibili/utils.test.js +17 -0
  97. package/dist/clis/douban/marks.js +1 -1
  98. package/dist/clis/douban/subject.yaml +50 -19
  99. package/dist/clis/doubao/utils.js +32 -12
  100. package/dist/clis/douyin/_shared/browser-fetch.test.js +0 -1
  101. package/dist/clis/douyin/_shared/transcode.test.js +0 -2
  102. package/dist/clis/douyin/draft.test.js +0 -2
  103. package/dist/clis/facebook/search.test.js +0 -2
  104. package/dist/clis/gemini/ask.js +9 -3
  105. package/dist/clis/gemini/ask.test.d.ts +1 -0
  106. package/dist/clis/gemini/ask.test.js +100 -0
  107. package/dist/clis/gemini/reply-state.test.d.ts +1 -0
  108. package/dist/clis/gemini/reply-state.test.js +641 -0
  109. package/dist/clis/gemini/utils.d.ts +44 -1
  110. package/dist/clis/gemini/utils.js +528 -61
  111. package/dist/clis/gemini/utils.test.js +149 -2
  112. package/dist/clis/hupu/detail.d.ts +1 -0
  113. package/dist/clis/hupu/detail.js +72 -0
  114. package/dist/clis/hupu/hot.yaml +43 -0
  115. package/dist/clis/hupu/like.d.ts +1 -0
  116. package/dist/clis/hupu/like.js +75 -0
  117. package/dist/clis/hupu/reply.d.ts +1 -0
  118. package/dist/clis/hupu/reply.js +71 -0
  119. package/dist/clis/hupu/search.d.ts +1 -0
  120. package/dist/clis/hupu/search.js +59 -0
  121. package/dist/clis/hupu/unlike.d.ts +1 -0
  122. package/dist/clis/hupu/unlike.js +75 -0
  123. package/dist/clis/hupu/utils.d.ts +20 -0
  124. package/dist/clis/hupu/utils.js +319 -0
  125. package/dist/clis/instagram/_shared/private-publish.d.ts +138 -0
  126. package/dist/clis/instagram/_shared/private-publish.js +1030 -0
  127. package/dist/clis/instagram/_shared/private-publish.test.d.ts +1 -0
  128. package/dist/clis/instagram/_shared/private-publish.test.js +705 -0
  129. package/dist/clis/instagram/_shared/protocol-capture.d.ts +26 -0
  130. package/dist/clis/instagram/_shared/protocol-capture.js +282 -0
  131. package/dist/clis/instagram/_shared/protocol-capture.test.d.ts +1 -0
  132. package/dist/clis/instagram/_shared/protocol-capture.test.js +114 -0
  133. package/dist/clis/instagram/_shared/runtime-info.d.ts +9 -0
  134. package/dist/clis/instagram/_shared/runtime-info.js +81 -0
  135. package/dist/clis/instagram/note.d.ts +1 -0
  136. package/dist/clis/instagram/note.js +222 -0
  137. package/dist/clis/instagram/note.test.d.ts +1 -0
  138. package/dist/clis/instagram/note.test.js +81 -0
  139. package/dist/clis/instagram/post.d.ts +4 -0
  140. package/dist/clis/instagram/post.js +1496 -0
  141. package/dist/clis/instagram/post.test.d.ts +1 -0
  142. package/dist/clis/instagram/post.test.js +1647 -0
  143. package/dist/clis/instagram/reel.d.ts +1 -0
  144. package/dist/clis/instagram/reel.js +826 -0
  145. package/dist/clis/instagram/reel.test.d.ts +1 -0
  146. package/dist/clis/instagram/reel.test.js +167 -0
  147. package/dist/clis/instagram/story.d.ts +1 -0
  148. package/dist/clis/instagram/story.js +115 -0
  149. package/dist/clis/instagram/story.test.d.ts +1 -0
  150. package/dist/clis/instagram/story.test.js +167 -0
  151. package/dist/clis/sinafinance/stock-rank.d.ts +4 -0
  152. package/dist/clis/sinafinance/stock-rank.js +65 -0
  153. package/dist/clis/substack/utils.test.js +0 -2
  154. package/dist/clis/twitter/post.js +72 -45
  155. package/dist/clis/twitter/post.test.d.ts +1 -0
  156. package/dist/clis/twitter/post.test.js +116 -0
  157. package/dist/clis/twitter/reply.d.ts +12 -0
  158. package/dist/clis/twitter/reply.js +257 -35
  159. package/dist/clis/twitter/reply.test.d.ts +1 -0
  160. package/dist/clis/twitter/reply.test.js +151 -0
  161. package/dist/clis/twitter/search.js +67 -5
  162. package/dist/clis/twitter/search.test.js +83 -5
  163. package/dist/clis/xianyu/chat.d.ts +7 -0
  164. package/dist/clis/xianyu/chat.js +146 -0
  165. package/dist/clis/xianyu/chat.test.d.ts +1 -0
  166. package/dist/clis/xianyu/chat.test.js +15 -0
  167. package/dist/clis/xianyu/item.d.ts +7 -0
  168. package/dist/clis/xianyu/item.js +152 -0
  169. package/dist/clis/xianyu/item.test.d.ts +1 -0
  170. package/dist/clis/xianyu/item.test.js +56 -0
  171. package/dist/clis/xianyu/search.d.ts +10 -0
  172. package/dist/clis/xianyu/search.js +134 -0
  173. package/dist/clis/xianyu/search.test.d.ts +1 -0
  174. package/dist/clis/xianyu/search.test.js +17 -0
  175. package/dist/clis/xianyu/utils.d.ts +1 -0
  176. package/dist/clis/xianyu/utils.js +8 -0
  177. package/dist/clis/xiaoe/catalog.yaml +129 -0
  178. package/dist/clis/xiaoe/content.yaml +43 -0
  179. package/dist/clis/xiaoe/courses.yaml +73 -0
  180. package/dist/clis/xiaoe/detail.yaml +39 -0
  181. package/dist/clis/xiaoe/play-url.yaml +124 -0
  182. package/dist/clis/xiaohongshu/comments.test.js +0 -2
  183. package/dist/clis/xiaohongshu/creator-note-detail.test.js +0 -2
  184. package/dist/clis/xiaohongshu/creator-notes.test.js +0 -2
  185. package/dist/clis/xiaohongshu/download.test.js +0 -2
  186. package/dist/clis/xiaohongshu/note.test.js +0 -2
  187. package/dist/clis/xiaohongshu/publish.test.js +0 -2
  188. package/dist/clis/xiaohongshu/search.js +29 -20
  189. package/dist/clis/xiaohongshu/search.test.js +56 -48
  190. package/dist/clis/yuanbao/ask.d.ts +21 -0
  191. package/dist/clis/yuanbao/ask.js +427 -0
  192. package/dist/clis/yuanbao/ask.test.d.ts +1 -0
  193. package/dist/clis/yuanbao/ask.test.js +124 -0
  194. package/dist/clis/yuanbao/new.d.ts +1 -0
  195. package/dist/clis/yuanbao/new.js +70 -0
  196. package/dist/clis/yuanbao/new.test.d.ts +1 -0
  197. package/dist/clis/yuanbao/new.test.js +30 -0
  198. package/dist/clis/yuanbao/shared.d.ts +13 -0
  199. package/dist/clis/yuanbao/shared.js +49 -0
  200. package/dist/clis/zhihu/question.js +30 -19
  201. package/dist/clis/zhihu/question.test.js +34 -16
  202. package/dist/commanderAdapter.js +8 -4
  203. package/dist/commanderAdapter.test.js +42 -0
  204. package/dist/completion.js +3 -1
  205. package/dist/completion.test.d.ts +1 -0
  206. package/dist/completion.test.js +23 -0
  207. package/dist/doctor.js +1 -1
  208. package/dist/electron-apps.d.ts +2 -0
  209. package/dist/electron-apps.js +7 -1
  210. package/dist/errors.js +1 -1
  211. package/dist/execution.js +25 -35
  212. package/dist/explore.js +1 -1
  213. package/dist/launcher.d.ts +4 -0
  214. package/dist/launcher.js +64 -8
  215. package/dist/launcher.test.js +88 -7
  216. package/dist/output.d.ts +2 -0
  217. package/dist/output.js +10 -1
  218. package/dist/output.test.d.ts +0 -3
  219. package/dist/output.test.js +59 -92
  220. package/dist/pipeline/executor.test.js +0 -2
  221. package/dist/pipeline/steps/download.test.js +0 -2
  222. package/dist/registry.d.ts +2 -0
  223. package/dist/serialization.d.ts +1 -0
  224. package/dist/serialization.js +1 -0
  225. package/dist/types.d.ts +9 -2
  226. package/docs/.vitepress/config.mts +4 -0
  227. package/docs/adapters/browser/1688.md +52 -0
  228. package/docs/adapters/browser/36kr.md +2 -1
  229. package/docs/adapters/browser/doubao.md +5 -1
  230. package/docs/adapters/browser/hupu.md +53 -0
  231. package/docs/adapters/browser/sinafinance.md +32 -2
  232. package/docs/adapters/browser/weibo.md +6 -1
  233. package/docs/adapters/browser/wikipedia.md +2 -0
  234. package/docs/adapters/browser/xianyu.md +42 -0
  235. package/docs/adapters/browser/xiaoe.md +44 -0
  236. package/docs/adapters/browser/yuanbao.md +64 -0
  237. package/docs/adapters/index.md +14 -5
  238. package/docs/comparison.md +1 -1
  239. package/docs/developer/ai-workflow.md +2 -2
  240. package/docs/developer/contributing.md +1 -1
  241. package/docs/developer/testing.md +2 -0
  242. package/docs/guide/plugins.md +1 -0
  243. package/docs/guide/troubleshooting.md +11 -0
  244. package/docs/superpowers/specs/2026-04-03-v2ex-autoresearch-design.md +41 -0
  245. package/docs/zh/guide/plugins.md +1 -0
  246. package/extension/dist/background.js +1127 -0
  247. package/extension/src/background.test.ts +39 -0
  248. package/extension/src/background.ts +223 -34
  249. package/extension/src/cdp.ts +194 -4
  250. package/extension/src/protocol.ts +22 -1
  251. package/package.json +3 -2
  252. package/scripts/postinstall.js +1 -1
  253. package/skills/opencli-explorer/SKILL.md +1 -1
  254. package/skills/opencli-oneshot/SKILL.md +2 -2
  255. package/skills/opencli-operate/SKILL.md +120 -27
  256. package/skills/opencli-usage/SKILL.md +31 -20
  257. package/skills/opencli-usage/browser.md +114 -16
  258. package/skills/opencli-usage/public-api.md +32 -3
  259. package/skills/smart-search/SKILL.md +156 -0
  260. package/skills/smart-search/references/sources-ai.md +74 -0
  261. package/skills/smart-search/references/sources-info.md +43 -0
  262. package/skills/smart-search/references/sources-media.md +50 -0
  263. package/skills/smart-search/references/sources-other.md +42 -0
  264. package/skills/smart-search/references/sources-shopping.md +31 -0
  265. package/skills/smart-search/references/sources-social.md +51 -0
  266. package/skills/smart-search/references/sources-tech.md +42 -0
  267. package/skills/smart-search/references/sources-travel.md +20 -0
  268. package/src/browser/base-page.ts +41 -6
  269. package/src/browser/bridge.ts +11 -8
  270. package/src/browser/cdp.ts +1 -8
  271. package/src/browser/daemon-client.ts +11 -1
  272. package/src/browser/dom-helpers.ts +43 -31
  273. package/src/browser/dom-snapshot.ts +23 -1
  274. package/src/browser/page.ts +115 -31
  275. package/src/browser.test.ts +1 -1
  276. package/src/build-manifest.ts +2 -0
  277. package/src/cli.test.ts +133 -0
  278. package/src/cli.ts +73 -11
  279. package/src/clis/1688/item.test.ts +69 -0
  280. package/src/clis/1688/item.ts +282 -0
  281. package/src/clis/1688/search.test.ts +81 -0
  282. package/src/clis/1688/search.ts +402 -0
  283. package/src/clis/1688/shared.test.ts +75 -0
  284. package/src/clis/1688/shared.ts +623 -0
  285. package/src/clis/1688/store.test.ts +69 -0
  286. package/src/clis/1688/store.ts +300 -0
  287. package/src/clis/amazon/bestsellers.test.ts +12 -3
  288. package/src/clis/amazon/bestsellers.ts +6 -178
  289. package/src/clis/amazon/movers-shakers.ts +8 -0
  290. package/src/clis/amazon/new-releases.ts +8 -0
  291. package/src/clis/amazon/rankings.test.ts +47 -0
  292. package/src/clis/amazon/rankings.ts +312 -0
  293. package/src/clis/amazon/shared.test.ts +16 -0
  294. package/src/clis/amazon/shared.ts +134 -12
  295. package/src/clis/bilibili/comments.test.ts +4 -3
  296. package/src/clis/bilibili/comments.ts +2 -2
  297. package/src/clis/bilibili/download.ts +2 -1
  298. package/src/clis/bilibili/subtitle.test.ts +2 -1
  299. package/src/clis/bilibili/subtitle.ts +4 -3
  300. package/src/clis/bilibili/utils.test.ts +21 -0
  301. package/src/clis/bilibili/utils.ts +27 -0
  302. package/src/clis/douban/marks.ts +1 -1
  303. package/src/clis/douban/subject.yaml +50 -19
  304. package/src/clis/doubao/utils.ts +32 -12
  305. package/src/clis/douyin/_shared/browser-fetch.test.ts +0 -1
  306. package/src/clis/douyin/_shared/transcode.test.ts +0 -2
  307. package/src/clis/douyin/draft.test.ts +0 -2
  308. package/src/clis/facebook/search.test.ts +0 -2
  309. package/src/clis/gemini/ask.test.ts +116 -0
  310. package/src/clis/gemini/ask.ts +10 -3
  311. package/src/clis/gemini/reply-state.test.ts +708 -0
  312. package/src/clis/gemini/utils.test.ts +184 -2
  313. package/src/clis/gemini/utils.ts +588 -60
  314. package/src/clis/hupu/detail.ts +126 -0
  315. package/src/clis/hupu/hot.yaml +43 -0
  316. package/src/clis/hupu/like.ts +76 -0
  317. package/src/clis/hupu/reply.ts +76 -0
  318. package/src/clis/hupu/search.ts +95 -0
  319. package/src/clis/hupu/unlike.ts +76 -0
  320. package/src/clis/hupu/utils.ts +381 -0
  321. package/src/clis/instagram/_shared/private-publish.test.ts +827 -0
  322. package/src/clis/instagram/_shared/private-publish.ts +1303 -0
  323. package/src/clis/instagram/_shared/protocol-capture.test.ts +148 -0
  324. package/src/clis/instagram/_shared/protocol-capture.ts +321 -0
  325. package/src/clis/instagram/_shared/runtime-info.ts +91 -0
  326. package/src/clis/instagram/note.test.ts +96 -0
  327. package/src/clis/instagram/note.ts +254 -0
  328. package/src/clis/instagram/post.test.ts +1716 -0
  329. package/src/clis/instagram/post.ts +1620 -0
  330. package/src/clis/instagram/reel.test.ts +191 -0
  331. package/src/clis/instagram/reel.ts +886 -0
  332. package/src/clis/instagram/story.test.ts +191 -0
  333. package/src/clis/instagram/story.ts +151 -0
  334. package/src/clis/sinafinance/stock-rank.ts +68 -0
  335. package/src/clis/substack/utils.test.ts +0 -2
  336. package/src/clis/twitter/post.test.ts +157 -0
  337. package/src/clis/twitter/post.ts +82 -48
  338. package/src/clis/twitter/reply.test.ts +177 -0
  339. package/src/clis/twitter/reply.ts +285 -39
  340. package/src/clis/twitter/search.test.ts +88 -5
  341. package/src/clis/twitter/search.ts +68 -5
  342. package/src/clis/xianyu/chat.test.ts +20 -0
  343. package/src/clis/xianyu/chat.ts +175 -0
  344. package/src/clis/xianyu/item.test.ts +67 -0
  345. package/src/clis/xianyu/item.ts +172 -0
  346. package/src/clis/xianyu/search.test.ts +22 -0
  347. package/src/clis/xianyu/search.ts +151 -0
  348. package/src/clis/xianyu/utils.ts +9 -0
  349. package/src/clis/xiaoe/catalog.yaml +129 -0
  350. package/src/clis/xiaoe/content.yaml +43 -0
  351. package/src/clis/xiaoe/courses.yaml +73 -0
  352. package/src/clis/xiaoe/detail.yaml +39 -0
  353. package/src/clis/xiaoe/play-url.yaml +124 -0
  354. package/src/clis/xiaohongshu/comments.test.ts +0 -2
  355. package/src/clis/xiaohongshu/creator-note-detail.test.ts +0 -2
  356. package/src/clis/xiaohongshu/creator-notes.test.ts +0 -2
  357. package/src/clis/xiaohongshu/download.test.ts +0 -2
  358. package/src/clis/xiaohongshu/note.test.ts +0 -2
  359. package/src/clis/xiaohongshu/publish.test.ts +0 -2
  360. package/src/clis/xiaohongshu/search.test.ts +59 -48
  361. package/src/clis/xiaohongshu/search.ts +31 -21
  362. package/src/clis/yuanbao/ask.test.ts +156 -0
  363. package/src/clis/yuanbao/ask.ts +522 -0
  364. package/src/clis/yuanbao/new.test.ts +36 -0
  365. package/src/clis/yuanbao/new.ts +81 -0
  366. package/src/clis/yuanbao/shared.ts +57 -0
  367. package/src/clis/zhihu/question.test.ts +42 -17
  368. package/src/clis/zhihu/question.ts +31 -26
  369. package/src/commanderAdapter.test.ts +51 -0
  370. package/src/commanderAdapter.ts +8 -4
  371. package/src/completion.test.ts +30 -0
  372. package/src/completion.ts +3 -1
  373. package/src/doctor.ts +1 -1
  374. package/src/electron-apps.ts +9 -1
  375. package/src/errors.ts +1 -1
  376. package/src/execution.ts +26 -30
  377. package/src/explore.ts +1 -1
  378. package/src/launcher.test.ts +121 -7
  379. package/src/launcher.ts +87 -9
  380. package/src/output.test.ts +50 -90
  381. package/src/output.ts +10 -1
  382. package/src/pipeline/executor.test.ts +0 -2
  383. package/src/pipeline/steps/download.test.ts +0 -2
  384. package/src/registry.ts +2 -0
  385. package/src/serialization.ts +2 -0
  386. package/src/types.ts +9 -2
  387. package/tests/e2e/browser-auth.test.ts +9 -0
  388. package/CLI-EXPLORER.md +0 -724
  389. package/CLI-ONESHOT.md +0 -216
  390. package/SKILL.md +0 -59
@@ -0,0 +1,427 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import TurndownService from 'turndown';
3
+ import { CommandExecutionError, TimeoutError } from '../../errors.js';
4
+ import { YUANBAO_DOMAIN, IS_VISIBLE_JS, authRequired, isOnYuanbao, ensureYuanbaoPage, hasLoginGate } from './shared.js';
5
+ const YUANBAO_RESPONSE_POLL_INTERVAL_SECONDS = 2;
6
+ const YUANBAO_MIN_WAIT_MS = 8_000;
7
+ const YUANBAO_STABLE_POLLS_REQUIRED = 3;
8
+ function sendFailure(reason, detail) {
9
+ const suffix = detail ? ` Detail: ${detail}` : '';
10
+ return new CommandExecutionError(`${reason || 'Unknown Yuanbao send failure.'}${suffix}`, 'Make sure the Yuanbao chat composer is visible and ready before retrying.');
11
+ }
12
+ function normalizeText(value) {
13
+ return typeof value === 'string' ? value.trim() : '';
14
+ }
15
+ function normalizeBooleanFlag(value, fallback) {
16
+ if (typeof value === 'boolean')
17
+ return value;
18
+ if (value == null || value === '')
19
+ return fallback;
20
+ const normalized = String(value).trim().toLowerCase();
21
+ return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
22
+ }
23
+ function createYuanbaoTurndown() {
24
+ const td = new TurndownService({
25
+ headingStyle: 'atx',
26
+ codeBlockStyle: 'fenced',
27
+ bulletListMarker: '-',
28
+ });
29
+ td.addRule('linebreak', {
30
+ filter: 'br',
31
+ replacement: () => '\n',
32
+ });
33
+ td.addRule('table', {
34
+ filter: 'table',
35
+ replacement: (content) => `\n\n${content}\n\n`,
36
+ });
37
+ td.addRule('tableSection', {
38
+ filter: ['thead', 'tbody', 'tfoot'],
39
+ replacement: (content) => content,
40
+ });
41
+ td.addRule('tableRow', {
42
+ filter: 'tr',
43
+ replacement: (content, node) => {
44
+ const element = node;
45
+ const cells = Array.from(element.children);
46
+ const isHeaderRow = element.parentElement?.tagName === 'THEAD'
47
+ || (cells.length > 0 && cells.every((cell) => cell.tagName === 'TH'));
48
+ const row = `${content}\n`;
49
+ if (!isHeaderRow)
50
+ return row;
51
+ const separator = `| ${cells.map(() => '---').join(' | ')} |\n`;
52
+ return `${row}${separator}`;
53
+ },
54
+ });
55
+ td.addRule('tableCell', {
56
+ filter: ['th', 'td'],
57
+ replacement: (content, node) => {
58
+ const element = node;
59
+ const index = element.parentElement ? Array.from(element.parentElement.children).indexOf(element) : 0;
60
+ const prefix = index === 0 ? '| ' : ' ';
61
+ return `${prefix}${content.trim()} |`;
62
+ },
63
+ });
64
+ return td;
65
+ }
66
+ const yuanbaoTurndown = createYuanbaoTurndown();
67
+ export function convertYuanbaoHtmlToMarkdown(value) {
68
+ const markdown = yuanbaoTurndown.turndown(value || '');
69
+ return markdown
70
+ .replace(/\u00a0/g, ' ')
71
+ .replace(/\n{4,}/g, '\n\n\n')
72
+ .replace(/[ \t]+$/gm, '')
73
+ .trim();
74
+ }
75
+ export function sanitizeYuanbaoResponseText(value, promptText) {
76
+ let sanitized = value
77
+ .replace(/内容由AI生成,仅供参考/gi, '')
78
+ .replace(/重新回答/gi, '')
79
+ .trim();
80
+ if (/^(正在搜索资料|搜索资料中|正在思考|思考中)[.。…]*$/u.test(sanitized)) {
81
+ return '';
82
+ }
83
+ const prompt = promptText.trim();
84
+ if (!prompt)
85
+ return sanitized;
86
+ if (sanitized === prompt)
87
+ return '';
88
+ for (const separator of ['\n\n', '\n', '\r\n\r\n', '\r\n', ' ']) {
89
+ const prefix = `${prompt}${separator}`;
90
+ if (sanitized.startsWith(prefix)) {
91
+ sanitized = sanitized.slice(prefix.length).trim();
92
+ break;
93
+ }
94
+ }
95
+ return sanitized;
96
+ }
97
+ export function collectYuanbaoTranscriptAdditions(beforeLines, currentLines, promptText) {
98
+ const beforeSet = new Set(beforeLines);
99
+ const additions = currentLines
100
+ .filter((line) => !beforeSet.has(line))
101
+ .map((line) => sanitizeYuanbaoResponseText(line, promptText))
102
+ .filter((line) => line && line !== promptText);
103
+ return additions.join('\n').trim();
104
+ }
105
+ export function pickLatestYuanbaoAssistantCandidate(messages, baselineCount, promptText) {
106
+ const freshMessages = messages
107
+ .slice(Math.max(0, baselineCount))
108
+ .map((message) => sanitizeYuanbaoResponseText(message, promptText))
109
+ .filter(Boolean);
110
+ for (let i = freshMessages.length - 1; i >= 0; i -= 1) {
111
+ if (freshMessages[i] !== promptText.trim())
112
+ return freshMessages[i];
113
+ }
114
+ return '';
115
+ }
116
+ export function updateStableState(previousText, stableCount, nextText) {
117
+ if (!nextText)
118
+ return { previousText: '', stableCount: 0 };
119
+ if (nextText === previousText)
120
+ return { previousText, stableCount: stableCount + 1 };
121
+ return { previousText: nextText, stableCount: 0 };
122
+ }
123
+ function getTranscriptLinesScript() {
124
+ return `
125
+ (() => {
126
+ const clean = (value) => (value || '')
127
+ .replace(/\\u00a0/g, ' ')
128
+ .replace(/\\n{3,}/g, '\\n\\n')
129
+ .trim();
130
+
131
+ const root = (
132
+ document.querySelector('.agent-dialogue__content--common')
133
+ || document.querySelector('.agent-dialogue__content')
134
+ || document.querySelector('.agent-dialogue')
135
+ || document.body
136
+ ).cloneNode(true);
137
+
138
+ const removableSelectors = [
139
+ '.agent-dialogue__content--common__input',
140
+ '.agent-dialogue__tool',
141
+ '.agent-dialogue__content-copyright',
142
+ '.index_chatLandingBox__G7hAT',
143
+ '.index_chatLandingBoxMobile__J8i8v',
144
+ '.index_chatLandingHintList__M69Lr',
145
+ '.yb-nav',
146
+ '.agent-dialogue__content--common__input .ql-toolbar',
147
+ '.agent-dialogue__content--common__input .ql-container',
148
+ '.agent-dialogue__content--common__input .ql-editor',
149
+ '[role="dialog"]',
150
+ 'iframe',
151
+ 'button',
152
+ 'script',
153
+ 'style',
154
+ 'noscript',
155
+ ];
156
+
157
+ for (const selector of removableSelectors) {
158
+ root.querySelectorAll(selector).forEach((node) => node.remove());
159
+ }
160
+
161
+ const stopLines = new Set([
162
+ '元宝',
163
+ 'DeepSeek',
164
+ '深度思考',
165
+ '联网搜索',
166
+ '工具',
167
+ '登录',
168
+ '安装电脑版',
169
+ '内容由AI生成,仅供参考',
170
+ '有问题,尽管问,shift+enter换行',
171
+ '立即创建团队',
172
+ '微信',
173
+ '手机',
174
+ 'QQ',
175
+ '微信扫码登录',
176
+ '扫码默认已阅读并同意',
177
+ '用户服务协议',
178
+ '隐私协议',
179
+ ]);
180
+
181
+ const noisyPatterns = [
182
+ /^支持文件格式[::]/,
183
+ /^文件拖动到此处即可上传/,
184
+ /^下载元宝电脑版/,
185
+ ];
186
+
187
+ return clean(root.innerText || root.textContent || '')
188
+ .split('\\n')
189
+ .map((line) => clean(line))
190
+ .filter((line) => line
191
+ && line.length <= 4000
192
+ && !stopLines.has(line)
193
+ && !noisyPatterns.some((pattern) => pattern.test(line)));
194
+ })()
195
+ `;
196
+ }
197
+ async function getYuanbaoTranscriptLines(page) {
198
+ const result = await page.evaluate(getTranscriptLinesScript());
199
+ return Array.isArray(result) ? result.map(normalizeText).filter(Boolean) : [];
200
+ }
201
+ async function getYuanbaoAssistantMessages(page) {
202
+ const result = await page.evaluate(`(() => {
203
+ ${IS_VISIBLE_JS}
204
+
205
+ const roots = Array.from(document.querySelectorAll('.agent-chat__list__item--ai'))
206
+ .filter((node) => isVisible(node));
207
+
208
+ return roots.map((root) => {
209
+ const doneContent = root.querySelector('.hyc-content-md-done');
210
+ const markdownContent = doneContent || root.querySelector('.hyc-content-md');
211
+ const speechContent = root.querySelector('.agent-chat__speech-text');
212
+ const bubbleContent = root.querySelector('.agent-chat__bubble__content');
213
+ const content = markdownContent || speechContent || bubbleContent;
214
+
215
+ if (content instanceof HTMLElement) {
216
+ return content.innerHTML || content.textContent || '';
217
+ }
218
+
219
+ return root instanceof HTMLElement ? (root.innerHTML || root.textContent || '') : '';
220
+ }).filter(Boolean);
221
+ })()`);
222
+ return Array.isArray(result)
223
+ ? result
224
+ .map((value) => convertYuanbaoHtmlToMarkdown(typeof value === 'string' ? value : ''))
225
+ .map(normalizeText)
226
+ .filter(Boolean)
227
+ : [];
228
+ }
229
+ async function getYuanbaoInternetSearchState(page) {
230
+ const result = await page.evaluate(`(() => {
231
+ ${IS_VISIBLE_JS}
232
+
233
+ const button = Array.from(document.querySelectorAll('[dt-button-id="internet_search"]'))
234
+ .find((node) => isVisible(node));
235
+
236
+ if (!(button instanceof HTMLElement)) return { found: false, enabled: false };
237
+
238
+ const attr = button.getAttribute('dt-internet-search') || '';
239
+ const className = button.className || '';
240
+ return {
241
+ found: true,
242
+ enabled: attr === 'openInternetSearch' || className.includes('index_v2_active__'),
243
+ };
244
+ })()`);
245
+ return result;
246
+ }
247
+ async function setYuanbaoInternetSearch(page, enabled) {
248
+ const current = await getYuanbaoInternetSearchState(page);
249
+ if (!current.found || current.enabled === enabled)
250
+ return;
251
+ await page.evaluate(`(() => {
252
+ ${IS_VISIBLE_JS}
253
+
254
+ const button = Array.from(document.querySelectorAll('[dt-button-id="internet_search"]'))
255
+ .find((node) => isVisible(node));
256
+
257
+ if (button instanceof HTMLElement) button.click();
258
+ })()`);
259
+ await page.wait(0.5);
260
+ }
261
+ async function getYuanbaoDeepThinkState(page) {
262
+ const result = await page.evaluate(`(() => {
263
+ ${IS_VISIBLE_JS}
264
+
265
+ const button = Array.from(document.querySelectorAll('[dt-button-id="deep_think"]'))
266
+ .find((node) => isVisible(node));
267
+
268
+ if (!(button instanceof HTMLElement)) return { found: false, enabled: false };
269
+
270
+ const className = button.className || '';
271
+ return {
272
+ found: true,
273
+ enabled: className.includes('ThinkSelector_selected__'),
274
+ };
275
+ })()`);
276
+ return result;
277
+ }
278
+ async function setYuanbaoDeepThink(page, enabled) {
279
+ const current = await getYuanbaoDeepThinkState(page);
280
+ if (!current.found || current.enabled === enabled)
281
+ return;
282
+ await page.evaluate(`(() => {
283
+ ${IS_VISIBLE_JS}
284
+
285
+ const button = Array.from(document.querySelectorAll('[dt-button-id="deep_think"]'))
286
+ .find((node) => isVisible(node));
287
+
288
+ if (button instanceof HTMLElement) button.click();
289
+ })()`);
290
+ await page.wait(0.5);
291
+ }
292
+ async function sendYuanbaoMessage(page, prompt) {
293
+ return await page.evaluate(`(async () => {
294
+ const waitFor = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
295
+ ${IS_VISIBLE_JS}
296
+
297
+ const composer = Array.from(document.querySelectorAll('.ql-editor[contenteditable="true"], .ql-editor, [contenteditable="true"]'))
298
+ .find(isVisible);
299
+
300
+ if (!(composer instanceof HTMLElement)) {
301
+ return {
302
+ ok: false,
303
+ reason: 'Yuanbao composer was not found.',
304
+ };
305
+ }
306
+
307
+ try {
308
+ composer.focus();
309
+ const selection = window.getSelection();
310
+ const range = document.createRange();
311
+ range.selectNodeContents(composer);
312
+ range.collapse(false);
313
+ selection?.removeAllRanges();
314
+ selection?.addRange(range);
315
+ composer.textContent = '';
316
+ document.execCommand('insertText', false, ${JSON.stringify(prompt)});
317
+ composer.dispatchEvent(new InputEvent('input', { bubbles: true, data: ${JSON.stringify(prompt)}, inputType: 'insertText' }));
318
+ await waitFor(200);
319
+ } catch (error) {
320
+ return {
321
+ ok: false,
322
+ reason: 'Failed to insert the prompt into the Yuanbao composer.',
323
+ detail: error instanceof Error ? error.message : String(error),
324
+ };
325
+ }
326
+
327
+ const submit = Array.from(document.querySelectorAll('a[class*="send-btn"], button[class*="send-btn"]'))
328
+ .find((node) => {
329
+ if (!(node instanceof HTMLElement) || !isVisible(node)) return false;
330
+ const className = node.className || '';
331
+ if (typeof className === 'string' && className.includes('disabled')) return false;
332
+ return true;
333
+ });
334
+
335
+ if (submit instanceof HTMLElement) {
336
+ submit.click();
337
+ return { ok: true, action: 'click' };
338
+ }
339
+
340
+ composer.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
341
+ composer.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
342
+ return { ok: true, action: 'enter' };
343
+ })()`);
344
+ }
345
+ async function waitForYuanbaoResponse(page, baselineAssistantCount, beforeLines, prompt, timeoutSeconds) {
346
+ const startTime = Date.now();
347
+ let previousText = '';
348
+ let stableCount = 0;
349
+ let latestCandidate = '';
350
+ while (Date.now() - startTime < timeoutSeconds * 1000) {
351
+ await page.wait(YUANBAO_RESPONSE_POLL_INTERVAL_SECONDS);
352
+ if (await hasLoginGate(page))
353
+ return 'blocked';
354
+ const assistantMessages = await getYuanbaoAssistantMessages(page);
355
+ const assistantCandidate = pickLatestYuanbaoAssistantCandidate(assistantMessages, baselineAssistantCount, prompt);
356
+ const candidate = assistantCandidate || collectYuanbaoTranscriptAdditions(beforeLines, await getYuanbaoTranscriptLines(page), prompt);
357
+ if (!candidate)
358
+ continue;
359
+ latestCandidate = candidate;
360
+ const nextState = updateStableState(previousText, stableCount, candidate);
361
+ previousText = nextState.previousText;
362
+ stableCount = nextState.stableCount;
363
+ const waitedLongEnough = Date.now() - startTime >= YUANBAO_MIN_WAIT_MS;
364
+ if (waitedLongEnough && stableCount >= YUANBAO_STABLE_POLLS_REQUIRED)
365
+ return candidate;
366
+ }
367
+ return latestCandidate || null;
368
+ }
369
+ export const askCommand = cli({
370
+ site: 'yuanbao',
371
+ name: 'ask',
372
+ description: 'Send a prompt to Yuanbao web chat and wait for the assistant response',
373
+ domain: YUANBAO_DOMAIN,
374
+ strategy: Strategy.COOKIE,
375
+ browser: true,
376
+ navigateBefore: false,
377
+ defaultFormat: 'plain',
378
+ timeoutSeconds: 180,
379
+ args: [
380
+ { name: 'prompt', required: true, positional: true, help: 'Prompt to send' },
381
+ { name: 'timeout', required: false, help: 'Max seconds to wait (default: 60)', default: '60' },
382
+ { name: 'search', type: 'boolean', required: false, help: 'Enable Yuanbao internet search (default: true)', default: true },
383
+ { name: 'think', type: 'boolean', required: false, help: 'Enable Yuanbao deep thinking (default: false)', default: false },
384
+ ],
385
+ columns: ['Role', 'Text'],
386
+ func: async (page, kwargs) => {
387
+ const prompt = kwargs.prompt;
388
+ const timeout = parseInt(kwargs.timeout, 10) || 60;
389
+ const useSearch = normalizeBooleanFlag(kwargs.search, true);
390
+ const useThink = normalizeBooleanFlag(kwargs.think, false);
391
+ await ensureYuanbaoPage(page);
392
+ if (await hasLoginGate(page)) {
393
+ throw authRequired('Yuanbao opened a login gate before sending the prompt.');
394
+ }
395
+ await setYuanbaoInternetSearch(page, useSearch);
396
+ await setYuanbaoDeepThink(page, useThink);
397
+ const beforeAssistantMessages = await getYuanbaoAssistantMessages(page);
398
+ const beforeLines = await getYuanbaoTranscriptLines(page);
399
+ const sendResult = await sendYuanbaoMessage(page, prompt);
400
+ if (!sendResult?.ok) {
401
+ if (await hasLoginGate(page)) {
402
+ throw authRequired('Yuanbao opened a login gate instead of accepting the prompt.');
403
+ }
404
+ throw sendFailure(sendResult?.reason, sendResult?.detail);
405
+ }
406
+ const response = await waitForYuanbaoResponse(page, beforeAssistantMessages.length, beforeLines, prompt, timeout);
407
+ if (response === 'blocked') {
408
+ throw authRequired('Yuanbao opened a login gate instead of returning a chat response.');
409
+ }
410
+ if (!response) {
411
+ throw new TimeoutError('yuanbao ask', timeout, 'No Yuanbao response was observed before the timeout. Retry with --timeout, and verify the current browser session is still interactive.');
412
+ }
413
+ return [
414
+ { Role: 'User', Text: prompt },
415
+ { Role: 'Assistant', Text: response },
416
+ ];
417
+ },
418
+ });
419
+ export const __test__ = {
420
+ collectYuanbaoTranscriptAdditions,
421
+ convertYuanbaoHtmlToMarkdown,
422
+ isOnYuanbao,
423
+ normalizeBooleanFlag,
424
+ pickLatestYuanbaoAssistantCandidate,
425
+ sanitizeYuanbaoResponseText,
426
+ updateStableState,
427
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,124 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { AuthRequiredError, CommandExecutionError, TimeoutError } from '../../errors.js';
3
+ import { __test__ } from './ask.js';
4
+ import { askCommand } from './ask.js';
5
+ describe('yuanbao ask helpers', () => {
6
+ describe('isOnYuanbao', () => {
7
+ const fakePage = (url) => ({ evaluate: () => url instanceof Error ? Promise.reject(url) : Promise.resolve(url) });
8
+ it('returns true for yuanbao.tencent.com URLs', async () => {
9
+ expect(await __test__.isOnYuanbao(fakePage('https://yuanbao.tencent.com/'))).toBe(true);
10
+ expect(await __test__.isOnYuanbao(fakePage('https://yuanbao.tencent.com/chat/abc'))).toBe(true);
11
+ });
12
+ it('returns false for non-yuanbao domains', async () => {
13
+ expect(await __test__.isOnYuanbao(fakePage('https://example.com/?next=yuanbao.tencent.com'))).toBe(false);
14
+ expect(await __test__.isOnYuanbao(fakePage('about:blank'))).toBe(false);
15
+ });
16
+ it('returns false when evaluate throws', async () => {
17
+ expect(await __test__.isOnYuanbao(fakePage(new Error('detached')))).toBe(false);
18
+ });
19
+ });
20
+ it('removes echoed prompt prefixes from transcript additions', () => {
21
+ expect(__test__.sanitizeYuanbaoResponseText('你好\n你好,我是元宝。', '你好')).toBe('你好,我是元宝。');
22
+ });
23
+ it('filters transient in-progress assistant placeholders', () => {
24
+ expect(__test__.sanitizeYuanbaoResponseText('正在搜索资料', '张雪机车相关的股票有哪些?')).toBe('');
25
+ });
26
+ it('normalizes boolean flags with explicit defaults', () => {
27
+ expect(__test__.normalizeBooleanFlag(undefined, true)).toBe(true);
28
+ expect(__test__.normalizeBooleanFlag(undefined, false)).toBe(false);
29
+ expect(__test__.normalizeBooleanFlag('true', false)).toBe(true);
30
+ expect(__test__.normalizeBooleanFlag('1', false)).toBe(true);
31
+ expect(__test__.normalizeBooleanFlag('yes', false)).toBe(true);
32
+ expect(__test__.normalizeBooleanFlag('false', true)).toBe(false);
33
+ });
34
+ it('ignores baseline lines and echoed prompts when collecting additions', () => {
35
+ const response = __test__.collectYuanbaoTranscriptAdditions(['旧消息'], ['旧消息', '你好', '你好\n你好,我是元宝。'], '你好');
36
+ expect(response).toBe('你好,我是元宝。');
37
+ });
38
+ it('prefers fresh assistant messages over echoed prompts and older messages', () => {
39
+ const response = __test__.pickLatestYuanbaoAssistantCandidate(['旧回复', '你好', '你好!我是元宝,由腾讯推出的AI助手。'], 1, '你好');
40
+ expect(response).toBe('你好!我是元宝,由腾讯推出的AI助手。');
41
+ });
42
+ it('converts assistant html tables to markdown tables via turndown', () => {
43
+ const markdown = __test__.convertYuanbaoHtmlToMarkdown(`
44
+ <h3>核心产业链概念股一览</h3>
45
+ <table>
46
+ <thead>
47
+ <tr><th>细分赛道</th><th>核心标的</th></tr>
48
+ </thead>
49
+ <tbody>
50
+ <tr><td>光模块</td><td>中际旭创</td></tr>
51
+ </tbody>
52
+ </table>
53
+ `);
54
+ expect(markdown).toContain('### 核心产业链概念股一览');
55
+ expect(markdown).toContain('| 细分赛道 | 核心标的 |');
56
+ expect(markdown).toContain('| --- | --- |');
57
+ expect(markdown).toContain('| 光模块 | 中际旭创 |');
58
+ });
59
+ it('tracks stabilization by incrementing repeats and resetting on changes', () => {
60
+ expect(__test__.updateStableState('', 0, '第一段')).toEqual({
61
+ previousText: '第一段',
62
+ stableCount: 0,
63
+ });
64
+ expect(__test__.updateStableState('第一段', 0, '第一段')).toEqual({
65
+ previousText: '第一段',
66
+ stableCount: 1,
67
+ });
68
+ expect(__test__.updateStableState('第一段', 1, '第二段')).toEqual({
69
+ previousText: '第二段',
70
+ stableCount: 0,
71
+ });
72
+ });
73
+ });
74
+ function createAskPageMock(overrides = {}) {
75
+ const currentUrl = overrides.currentUrl ?? 'https://yuanbao.tencent.com/';
76
+ const hasLoginGate = overrides.hasLoginGate ?? false;
77
+ const sendResult = overrides.sendResult;
78
+ return {
79
+ goto: vi.fn().mockResolvedValue(undefined),
80
+ wait: vi.fn().mockResolvedValue(undefined),
81
+ evaluate: vi.fn().mockImplementation(async (script) => {
82
+ if (script === 'window.location.href')
83
+ return currentUrl;
84
+ if (script.includes('微信扫码登录'))
85
+ return hasLoginGate;
86
+ if (script.includes('[dt-button-id="internet_search"]'))
87
+ return { found: false, enabled: false };
88
+ if (script.includes('[dt-button-id="deep_think"]'))
89
+ return { found: false, enabled: false };
90
+ if (script.includes('.agent-chat__list__item--ai'))
91
+ return [];
92
+ if (script.includes('const stopLines = new Set(['))
93
+ return [];
94
+ if (script.includes('Failed to insert the prompt into the Yuanbao composer.')) {
95
+ return sendResult ?? { ok: true, action: 'click' };
96
+ }
97
+ throw new Error(`Unexpected evaluate script in test: ${script.slice(0, 80)}`);
98
+ }),
99
+ };
100
+ }
101
+ describe('yuanbao ask command', () => {
102
+ it('throws AuthRequiredError when Yuanbao shows a login gate before sending', async () => {
103
+ const page = createAskPageMock({ hasLoginGate: true });
104
+ await expect(askCommand.func(page, { prompt: '你好', timeout: '60', search: true, think: false }))
105
+ .rejects.toBeInstanceOf(AuthRequiredError);
106
+ });
107
+ it('throws CommandExecutionError when the prompt cannot be sent', async () => {
108
+ const page = createAskPageMock({
109
+ sendResult: {
110
+ ok: false,
111
+ reason: 'Yuanbao composer was not found.',
112
+ },
113
+ });
114
+ await expect(askCommand.func(page, { prompt: '你好', timeout: '60', search: true, think: false }))
115
+ .rejects.toBeInstanceOf(CommandExecutionError);
116
+ });
117
+ it('throws TimeoutError when no response arrives before timeout', async () => {
118
+ const page = createAskPageMock({
119
+ sendResult: { ok: true, action: 'click' },
120
+ });
121
+ await expect(askCommand.func(page, { prompt: '你好', timeout: '-1', search: true, think: false }))
122
+ .rejects.toBeInstanceOf(TimeoutError);
123
+ });
124
+ });
@@ -0,0 +1 @@
1
+ export declare const newCommand: import("../../registry.js").CliCommand;
@@ -0,0 +1,70 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { YUANBAO_DOMAIN, YUANBAO_URL, IS_VISIBLE_JS, authRequired, ensureYuanbaoPage, hasLoginGate } from './shared.js';
3
+ async function getCurrentUrl(page) {
4
+ const result = await page.evaluate('window.location.href').catch(() => '');
5
+ return typeof result === 'string' ? result : '';
6
+ }
7
+ async function getComposerText(page) {
8
+ const result = await page.evaluate(`(() => {
9
+ const composer = document.querySelector('.ql-editor, [contenteditable="true"]');
10
+ return composer ? (composer.textContent || '').trim() : '';
11
+ })()`);
12
+ return typeof result === 'string' ? result.trim() : '';
13
+ }
14
+ async function startNewYuanbaoChat(page) {
15
+ await ensureYuanbaoPage(page);
16
+ if (await hasLoginGate(page))
17
+ return 'blocked';
18
+ const beforeUrl = await getCurrentUrl(page);
19
+ const action = await page.evaluate(`(() => {
20
+ ${IS_VISIBLE_JS}
21
+
22
+ const trigger = Array.from(document.querySelectorAll('.yb-common-nav__trigger[data-desc="new-chat"]'))
23
+ .find((node) => isVisible(node));
24
+
25
+ if (trigger instanceof HTMLElement) {
26
+ trigger.click();
27
+ return 'clicked';
28
+ }
29
+
30
+ return 'navigate';
31
+ })()`);
32
+ if (action === 'navigate') {
33
+ await page.goto(YUANBAO_URL, { waitUntil: 'load', settleMs: 2500 });
34
+ await page.wait(1);
35
+ if (await hasLoginGate(page))
36
+ return 'blocked';
37
+ return 'navigate';
38
+ }
39
+ await page.wait(1);
40
+ if (await hasLoginGate(page))
41
+ return 'blocked';
42
+ const afterUrl = await getCurrentUrl(page);
43
+ const composerText = await getComposerText(page);
44
+ if (afterUrl !== beforeUrl || !composerText)
45
+ return 'clicked';
46
+ await page.goto(YUANBAO_URL, { waitUntil: 'load', settleMs: 2500 });
47
+ await page.wait(1);
48
+ return 'navigate';
49
+ }
50
+ export const newCommand = cli({
51
+ site: 'yuanbao',
52
+ name: 'new',
53
+ description: 'Start a new conversation in Yuanbao web chat',
54
+ domain: YUANBAO_DOMAIN,
55
+ strategy: Strategy.COOKIE,
56
+ browser: true,
57
+ navigateBefore: false,
58
+ args: [],
59
+ columns: ['Status', 'Action'],
60
+ func: async (page) => {
61
+ const action = await startNewYuanbaoChat(page);
62
+ if (action === 'blocked') {
63
+ throw authRequired('Yuanbao opened a login gate instead of starting a new chat.');
64
+ }
65
+ return [{
66
+ Status: 'Success',
67
+ Action: action === 'navigate' ? 'Reloaded Yuanbao homepage as fallback' : 'Clicked New chat',
68
+ }];
69
+ },
70
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { AuthRequiredError } from '../../errors.js';
3
+ import { newCommand } from './new.js';
4
+ function createNewPageMock(overrides = {}) {
5
+ const currentUrl = overrides.currentUrl ?? 'https://yuanbao.tencent.com/';
6
+ const triggerAction = overrides.triggerAction ?? 'clicked';
7
+ const hasLoginGate = overrides.hasLoginGate ?? false;
8
+ const composerText = overrides.composerText ?? '';
9
+ return {
10
+ goto: vi.fn().mockResolvedValue(undefined),
11
+ wait: vi.fn().mockResolvedValue(undefined),
12
+ evaluate: vi.fn().mockImplementation(async (script) => {
13
+ if (script === 'window.location.href')
14
+ return currentUrl;
15
+ if (script.includes('微信扫码登录'))
16
+ return hasLoginGate;
17
+ if (script.includes('.ql-editor, [contenteditable="true"]'))
18
+ return composerText;
19
+ if (script.includes('const trigger = Array.from(document.querySelectorAll'))
20
+ return triggerAction;
21
+ throw new Error(`Unexpected evaluate script in test: ${script.slice(0, 80)}`);
22
+ }),
23
+ };
24
+ }
25
+ describe('yuanbao new command', () => {
26
+ it('throws AuthRequiredError when Yuanbao shows a login gate', async () => {
27
+ const page = createNewPageMock({ hasLoginGate: true });
28
+ await expect(newCommand.func(page, {})).rejects.toBeInstanceOf(AuthRequiredError);
29
+ });
30
+ });
@@ -0,0 +1,13 @@
1
+ import type { IPage } from '../../types.js';
2
+ import { AuthRequiredError } from '../../errors.js';
3
+ export declare const YUANBAO_DOMAIN = "yuanbao.tencent.com";
4
+ export declare const YUANBAO_URL = "https://yuanbao.tencent.com/";
5
+ /**
6
+ * Reusable visibility check for injected browser scripts.
7
+ * Embed in page.evaluate strings via `${IS_VISIBLE_JS}`.
8
+ */
9
+ export declare const IS_VISIBLE_JS = "const isVisible = (node) => {\n if (!(node instanceof HTMLElement)) return false;\n const rect = node.getBoundingClientRect();\n const style = window.getComputedStyle(node);\n return rect.width > 0\n && rect.height > 0\n && style.display !== 'none'\n && style.visibility !== 'hidden';\n};";
10
+ export declare function authRequired(message: string): AuthRequiredError;
11
+ export declare function isOnYuanbao(page: IPage): Promise<boolean>;
12
+ export declare function ensureYuanbaoPage(page: IPage): Promise<void>;
13
+ export declare function hasLoginGate(page: IPage): Promise<boolean>;