@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,522 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+ import TurndownService from 'turndown';
4
+ import { CommandExecutionError, TimeoutError } from '../../errors.js';
5
+ import { YUANBAO_DOMAIN, YUANBAO_URL, IS_VISIBLE_JS, authRequired, isOnYuanbao, ensureYuanbaoPage, hasLoginGate } from './shared.js';
6
+
7
+ const YUANBAO_RESPONSE_POLL_INTERVAL_SECONDS = 2;
8
+ const YUANBAO_MIN_WAIT_MS = 8_000;
9
+ const YUANBAO_STABLE_POLLS_REQUIRED = 3;
10
+
11
+ type YuanbaoSendResult = {
12
+ ok?: boolean;
13
+ action?: string;
14
+ reason?: string;
15
+ detail?: string;
16
+ };
17
+
18
+ type YuanbaoToggleState = {
19
+ enabled: boolean;
20
+ found: boolean;
21
+ };
22
+
23
+ function sendFailure(reason?: string, detail?: string) {
24
+ const suffix = detail ? ` Detail: ${detail}` : '';
25
+ return new CommandExecutionError(
26
+ `${reason || 'Unknown Yuanbao send failure.'}${suffix}`,
27
+ 'Make sure the Yuanbao chat composer is visible and ready before retrying.',
28
+ );
29
+ }
30
+
31
+ function normalizeText(value: unknown): string {
32
+ return typeof value === 'string' ? value.trim() : '';
33
+ }
34
+
35
+ function normalizeBooleanFlag(value: unknown, fallback: boolean): boolean {
36
+ if (typeof value === 'boolean') return value;
37
+ if (value == null || value === '') return fallback;
38
+
39
+ const normalized = String(value).trim().toLowerCase();
40
+ return normalized === 'true' || normalized === '1' || normalized === 'yes' || normalized === 'on';
41
+ }
42
+
43
+ function createYuanbaoTurndown(): TurndownService {
44
+ const td = new TurndownService({
45
+ headingStyle: 'atx',
46
+ codeBlockStyle: 'fenced',
47
+ bulletListMarker: '-',
48
+ });
49
+
50
+ td.addRule('linebreak', {
51
+ filter: 'br',
52
+ replacement: () => '\n',
53
+ });
54
+
55
+ td.addRule('table', {
56
+ filter: 'table',
57
+ replacement: (content) => `\n\n${content}\n\n`,
58
+ });
59
+
60
+ td.addRule('tableSection', {
61
+ filter: ['thead', 'tbody', 'tfoot'],
62
+ replacement: (content) => content,
63
+ });
64
+
65
+ td.addRule('tableRow', {
66
+ filter: 'tr',
67
+ replacement: (content, node) => {
68
+ const element = node as Element;
69
+ const cells = Array.from(element.children);
70
+ const isHeaderRow = element.parentElement?.tagName === 'THEAD'
71
+ || (cells.length > 0 && cells.every((cell) => cell.tagName === 'TH'));
72
+
73
+ const row = `${content}\n`;
74
+ if (!isHeaderRow) return row;
75
+
76
+ const separator = `| ${cells.map(() => '---').join(' | ')} |\n`;
77
+ return `${row}${separator}`;
78
+ },
79
+ });
80
+
81
+ td.addRule('tableCell', {
82
+ filter: ['th', 'td'],
83
+ replacement: (content, node) => {
84
+ const element = node as Element;
85
+ const index = element.parentElement ? Array.from(element.parentElement.children).indexOf(element) : 0;
86
+ const prefix = index === 0 ? '| ' : ' ';
87
+ return `${prefix}${content.trim()} |`;
88
+ },
89
+ });
90
+
91
+ return td;
92
+ }
93
+
94
+ const yuanbaoTurndown = createYuanbaoTurndown();
95
+
96
+ export function convertYuanbaoHtmlToMarkdown(value: string): string {
97
+ const markdown = yuanbaoTurndown.turndown(value || '');
98
+ return markdown
99
+ .replace(/\u00a0/g, ' ')
100
+ .replace(/\n{4,}/g, '\n\n\n')
101
+ .replace(/[ \t]+$/gm, '')
102
+ .trim();
103
+ }
104
+
105
+ export function sanitizeYuanbaoResponseText(value: string, promptText: string): string {
106
+ let sanitized = value
107
+ .replace(/内容由AI生成,仅供参考/gi, '')
108
+ .replace(/重新回答/gi, '')
109
+ .trim();
110
+
111
+ if (/^(正在搜索资料|搜索资料中|正在思考|思考中)[.。…]*$/u.test(sanitized)) {
112
+ return '';
113
+ }
114
+
115
+ const prompt = promptText.trim();
116
+ if (!prompt) return sanitized;
117
+ if (sanitized === prompt) return '';
118
+
119
+ for (const separator of ['\n\n', '\n', '\r\n\r\n', '\r\n', ' ']) {
120
+ const prefix = `${prompt}${separator}`;
121
+ if (sanitized.startsWith(prefix)) {
122
+ sanitized = sanitized.slice(prefix.length).trim();
123
+ break;
124
+ }
125
+ }
126
+
127
+ return sanitized;
128
+ }
129
+
130
+ export function collectYuanbaoTranscriptAdditions(
131
+ beforeLines: string[],
132
+ currentLines: string[],
133
+ promptText: string,
134
+ ): string {
135
+ const beforeSet = new Set(beforeLines);
136
+ const additions = currentLines
137
+ .filter((line) => !beforeSet.has(line))
138
+ .map((line) => sanitizeYuanbaoResponseText(line, promptText))
139
+ .filter((line) => line && line !== promptText);
140
+
141
+ return additions.join('\n').trim();
142
+ }
143
+
144
+ export function pickLatestYuanbaoAssistantCandidate(
145
+ messages: string[],
146
+ baselineCount: number,
147
+ promptText: string,
148
+ ): string {
149
+ const freshMessages = messages
150
+ .slice(Math.max(0, baselineCount))
151
+ .map((message) => sanitizeYuanbaoResponseText(message, promptText))
152
+ .filter(Boolean);
153
+
154
+ for (let i = freshMessages.length - 1; i >= 0; i -= 1) {
155
+ if (freshMessages[i] !== promptText.trim()) return freshMessages[i];
156
+ }
157
+
158
+ return '';
159
+ }
160
+
161
+ export function updateStableState(previousText: string, stableCount: number, nextText: string) {
162
+ if (!nextText) return { previousText: '', stableCount: 0 };
163
+ if (nextText === previousText) return { previousText, stableCount: stableCount + 1 };
164
+ return { previousText: nextText, stableCount: 0 };
165
+ }
166
+
167
+
168
+ function getTranscriptLinesScript(): string {
169
+ return `
170
+ (() => {
171
+ const clean = (value) => (value || '')
172
+ .replace(/\\u00a0/g, ' ')
173
+ .replace(/\\n{3,}/g, '\\n\\n')
174
+ .trim();
175
+
176
+ const root = (
177
+ document.querySelector('.agent-dialogue__content--common')
178
+ || document.querySelector('.agent-dialogue__content')
179
+ || document.querySelector('.agent-dialogue')
180
+ || document.body
181
+ ).cloneNode(true);
182
+
183
+ const removableSelectors = [
184
+ '.agent-dialogue__content--common__input',
185
+ '.agent-dialogue__tool',
186
+ '.agent-dialogue__content-copyright',
187
+ '.index_chatLandingBox__G7hAT',
188
+ '.index_chatLandingBoxMobile__J8i8v',
189
+ '.index_chatLandingHintList__M69Lr',
190
+ '.yb-nav',
191
+ '.agent-dialogue__content--common__input .ql-toolbar',
192
+ '.agent-dialogue__content--common__input .ql-container',
193
+ '.agent-dialogue__content--common__input .ql-editor',
194
+ '[role="dialog"]',
195
+ 'iframe',
196
+ 'button',
197
+ 'script',
198
+ 'style',
199
+ 'noscript',
200
+ ];
201
+
202
+ for (const selector of removableSelectors) {
203
+ root.querySelectorAll(selector).forEach((node) => node.remove());
204
+ }
205
+
206
+ const stopLines = new Set([
207
+ '元宝',
208
+ 'DeepSeek',
209
+ '深度思考',
210
+ '联网搜索',
211
+ '工具',
212
+ '登录',
213
+ '安装电脑版',
214
+ '内容由AI生成,仅供参考',
215
+ '有问题,尽管问,shift+enter换行',
216
+ '立即创建团队',
217
+ '微信',
218
+ '手机',
219
+ 'QQ',
220
+ '微信扫码登录',
221
+ '扫码默认已阅读并同意',
222
+ '用户服务协议',
223
+ '隐私协议',
224
+ ]);
225
+
226
+ const noisyPatterns = [
227
+ /^支持文件格式[::]/,
228
+ /^文件拖动到此处即可上传/,
229
+ /^下载元宝电脑版/,
230
+ ];
231
+
232
+ return clean(root.innerText || root.textContent || '')
233
+ .split('\\n')
234
+ .map((line) => clean(line))
235
+ .filter((line) => line
236
+ && line.length <= 4000
237
+ && !stopLines.has(line)
238
+ && !noisyPatterns.some((pattern) => pattern.test(line)));
239
+ })()
240
+ `;
241
+ }
242
+
243
+ async function getYuanbaoTranscriptLines(page: IPage): Promise<string[]> {
244
+ const result = await page.evaluate(getTranscriptLinesScript());
245
+ return Array.isArray(result) ? result.map(normalizeText).filter(Boolean) : [];
246
+ }
247
+
248
+ async function getYuanbaoAssistantMessages(page: IPage): Promise<string[]> {
249
+ const result = await page.evaluate(`(() => {
250
+ ${IS_VISIBLE_JS}
251
+
252
+ const roots = Array.from(document.querySelectorAll('.agent-chat__list__item--ai'))
253
+ .filter((node) => isVisible(node));
254
+
255
+ return roots.map((root) => {
256
+ const doneContent = root.querySelector('.hyc-content-md-done');
257
+ const markdownContent = doneContent || root.querySelector('.hyc-content-md');
258
+ const speechContent = root.querySelector('.agent-chat__speech-text');
259
+ const bubbleContent = root.querySelector('.agent-chat__bubble__content');
260
+ const content = markdownContent || speechContent || bubbleContent;
261
+
262
+ if (content instanceof HTMLElement) {
263
+ return content.innerHTML || content.textContent || '';
264
+ }
265
+
266
+ return root instanceof HTMLElement ? (root.innerHTML || root.textContent || '') : '';
267
+ }).filter(Boolean);
268
+ })()`);
269
+
270
+ return Array.isArray(result)
271
+ ? result
272
+ .map((value) => convertYuanbaoHtmlToMarkdown(typeof value === 'string' ? value : ''))
273
+ .map(normalizeText)
274
+ .filter(Boolean)
275
+ : [];
276
+ }
277
+
278
+ async function getYuanbaoInternetSearchState(page: IPage): Promise<YuanbaoToggleState> {
279
+ const result = await page.evaluate(`(() => {
280
+ ${IS_VISIBLE_JS}
281
+
282
+ const button = Array.from(document.querySelectorAll('[dt-button-id="internet_search"]'))
283
+ .find((node) => isVisible(node));
284
+
285
+ if (!(button instanceof HTMLElement)) return { found: false, enabled: false };
286
+
287
+ const attr = button.getAttribute('dt-internet-search') || '';
288
+ const className = button.className || '';
289
+ return {
290
+ found: true,
291
+ enabled: attr === 'openInternetSearch' || className.includes('index_v2_active__'),
292
+ };
293
+ })()`);
294
+
295
+ return result as YuanbaoToggleState;
296
+ }
297
+
298
+ async function setYuanbaoInternetSearch(page: IPage, enabled: boolean): Promise<void> {
299
+ const current = await getYuanbaoInternetSearchState(page);
300
+ if (!current.found || current.enabled === enabled) return;
301
+
302
+ await page.evaluate(`(() => {
303
+ ${IS_VISIBLE_JS}
304
+
305
+ const button = Array.from(document.querySelectorAll('[dt-button-id="internet_search"]'))
306
+ .find((node) => isVisible(node));
307
+
308
+ if (button instanceof HTMLElement) button.click();
309
+ })()`);
310
+
311
+ await page.wait(0.5);
312
+ }
313
+
314
+ async function getYuanbaoDeepThinkState(page: IPage): Promise<YuanbaoToggleState> {
315
+ const result = await page.evaluate(`(() => {
316
+ ${IS_VISIBLE_JS}
317
+
318
+ const button = Array.from(document.querySelectorAll('[dt-button-id="deep_think"]'))
319
+ .find((node) => isVisible(node));
320
+
321
+ if (!(button instanceof HTMLElement)) return { found: false, enabled: false };
322
+
323
+ const className = button.className || '';
324
+ return {
325
+ found: true,
326
+ enabled: className.includes('ThinkSelector_selected__'),
327
+ };
328
+ })()`);
329
+
330
+ return result as YuanbaoToggleState;
331
+ }
332
+
333
+ async function setYuanbaoDeepThink(page: IPage, enabled: boolean): Promise<void> {
334
+ const current = await getYuanbaoDeepThinkState(page);
335
+ if (!current.found || current.enabled === enabled) return;
336
+
337
+ await page.evaluate(`(() => {
338
+ ${IS_VISIBLE_JS}
339
+
340
+ const button = Array.from(document.querySelectorAll('[dt-button-id="deep_think"]'))
341
+ .find((node) => isVisible(node));
342
+
343
+ if (button instanceof HTMLElement) button.click();
344
+ })()`);
345
+
346
+ await page.wait(0.5);
347
+ }
348
+
349
+ async function sendYuanbaoMessage(page: IPage, prompt: string): Promise<YuanbaoSendResult> {
350
+ return await page.evaluate(`(async () => {
351
+ const waitFor = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
352
+ ${IS_VISIBLE_JS}
353
+
354
+ const composer = Array.from(document.querySelectorAll('.ql-editor[contenteditable="true"], .ql-editor, [contenteditable="true"]'))
355
+ .find(isVisible);
356
+
357
+ if (!(composer instanceof HTMLElement)) {
358
+ return {
359
+ ok: false,
360
+ reason: 'Yuanbao composer was not found.',
361
+ };
362
+ }
363
+
364
+ try {
365
+ composer.focus();
366
+ const selection = window.getSelection();
367
+ const range = document.createRange();
368
+ range.selectNodeContents(composer);
369
+ range.collapse(false);
370
+ selection?.removeAllRanges();
371
+ selection?.addRange(range);
372
+ composer.textContent = '';
373
+ document.execCommand('insertText', false, ${JSON.stringify(prompt)});
374
+ composer.dispatchEvent(new InputEvent('input', { bubbles: true, data: ${JSON.stringify(prompt)}, inputType: 'insertText' }));
375
+ await waitFor(200);
376
+ } catch (error) {
377
+ return {
378
+ ok: false,
379
+ reason: 'Failed to insert the prompt into the Yuanbao composer.',
380
+ detail: error instanceof Error ? error.message : String(error),
381
+ };
382
+ }
383
+
384
+ const submit = Array.from(document.querySelectorAll('a[class*="send-btn"], button[class*="send-btn"]'))
385
+ .find((node) => {
386
+ if (!(node instanceof HTMLElement) || !isVisible(node)) return false;
387
+ const className = node.className || '';
388
+ if (typeof className === 'string' && className.includes('disabled')) return false;
389
+ return true;
390
+ });
391
+
392
+ if (submit instanceof HTMLElement) {
393
+ submit.click();
394
+ return { ok: true, action: 'click' };
395
+ }
396
+
397
+ composer.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
398
+ composer.dispatchEvent(new KeyboardEvent('keyup', { key: 'Enter', code: 'Enter', keyCode: 13, bubbles: true }));
399
+ return { ok: true, action: 'enter' };
400
+ })()`) as YuanbaoSendResult;
401
+ }
402
+
403
+ async function waitForYuanbaoResponse(
404
+ page: IPage,
405
+ baselineAssistantCount: number,
406
+ beforeLines: string[],
407
+ prompt: string,
408
+ timeoutSeconds: number,
409
+ ): Promise<string | null | 'blocked'> {
410
+ const startTime = Date.now();
411
+ let previousText = '';
412
+ let stableCount = 0;
413
+ let latestCandidate = '';
414
+
415
+ while (Date.now() - startTime < timeoutSeconds * 1000) {
416
+ await page.wait(YUANBAO_RESPONSE_POLL_INTERVAL_SECONDS);
417
+
418
+ if (await hasLoginGate(page)) return 'blocked';
419
+
420
+ const assistantMessages = await getYuanbaoAssistantMessages(page);
421
+ const assistantCandidate = pickLatestYuanbaoAssistantCandidate(
422
+ assistantMessages,
423
+ baselineAssistantCount,
424
+ prompt,
425
+ );
426
+
427
+ const candidate = assistantCandidate || collectYuanbaoTranscriptAdditions(
428
+ beforeLines,
429
+ await getYuanbaoTranscriptLines(page),
430
+ prompt,
431
+ );
432
+
433
+ if (!candidate) continue;
434
+
435
+ latestCandidate = candidate;
436
+ const nextState = updateStableState(previousText, stableCount, candidate);
437
+ previousText = nextState.previousText;
438
+ stableCount = nextState.stableCount;
439
+
440
+ const waitedLongEnough = Date.now() - startTime >= YUANBAO_MIN_WAIT_MS;
441
+ if (waitedLongEnough && stableCount >= YUANBAO_STABLE_POLLS_REQUIRED) return candidate;
442
+ }
443
+
444
+ return latestCandidate || null;
445
+ }
446
+
447
+ export const askCommand = cli({
448
+ site: 'yuanbao',
449
+ name: 'ask',
450
+ description: 'Send a prompt to Yuanbao web chat and wait for the assistant response',
451
+ domain: YUANBAO_DOMAIN,
452
+ strategy: Strategy.COOKIE,
453
+ browser: true,
454
+ navigateBefore: false,
455
+ defaultFormat: 'plain',
456
+ timeoutSeconds: 180,
457
+ args: [
458
+ { name: 'prompt', required: true, positional: true, help: 'Prompt to send' },
459
+ { name: 'timeout', required: false, help: 'Max seconds to wait (default: 60)', default: '60' },
460
+ { name: 'search', type: 'boolean', required: false, help: 'Enable Yuanbao internet search (default: true)', default: true },
461
+ { name: 'think', type: 'boolean', required: false, help: 'Enable Yuanbao deep thinking (default: false)', default: false },
462
+ ],
463
+ columns: ['Role', 'Text'],
464
+ func: async (page: IPage, kwargs: any) => {
465
+ const prompt = kwargs.prompt as string;
466
+ const timeout = parseInt(kwargs.timeout as string, 10) || 60;
467
+ const useSearch = normalizeBooleanFlag(kwargs.search, true);
468
+ const useThink = normalizeBooleanFlag(kwargs.think, false);
469
+
470
+ await ensureYuanbaoPage(page);
471
+ if (await hasLoginGate(page)) {
472
+ throw authRequired('Yuanbao opened a login gate before sending the prompt.');
473
+ }
474
+ await setYuanbaoInternetSearch(page, useSearch);
475
+ await setYuanbaoDeepThink(page, useThink);
476
+ const beforeAssistantMessages = await getYuanbaoAssistantMessages(page);
477
+ const beforeLines = await getYuanbaoTranscriptLines(page);
478
+ const sendResult = await sendYuanbaoMessage(page, prompt);
479
+
480
+ if (!sendResult?.ok) {
481
+ if (await hasLoginGate(page)) {
482
+ throw authRequired('Yuanbao opened a login gate instead of accepting the prompt.');
483
+ }
484
+ throw sendFailure(sendResult?.reason, sendResult?.detail);
485
+ }
486
+
487
+ const response = await waitForYuanbaoResponse(
488
+ page,
489
+ beforeAssistantMessages.length,
490
+ beforeLines,
491
+ prompt,
492
+ timeout,
493
+ );
494
+
495
+ if (response === 'blocked') {
496
+ throw authRequired('Yuanbao opened a login gate instead of returning a chat response.');
497
+ }
498
+
499
+ if (!response) {
500
+ throw new TimeoutError(
501
+ 'yuanbao ask',
502
+ timeout,
503
+ 'No Yuanbao response was observed before the timeout. Retry with --timeout, and verify the current browser session is still interactive.',
504
+ );
505
+ }
506
+
507
+ return [
508
+ { Role: 'User', Text: prompt },
509
+ { Role: 'Assistant', Text: response },
510
+ ];
511
+ },
512
+ });
513
+
514
+ export const __test__ = {
515
+ collectYuanbaoTranscriptAdditions,
516
+ convertYuanbaoHtmlToMarkdown,
517
+ isOnYuanbao,
518
+ normalizeBooleanFlag,
519
+ pickLatestYuanbaoAssistantCandidate,
520
+ sanitizeYuanbaoResponseText,
521
+ updateStableState,
522
+ };
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import type { IPage } from '../../types.js';
3
+ import { AuthRequiredError } from '../../errors.js';
4
+ import { newCommand } from './new.js';
5
+
6
+ function createNewPageMock(overrides: {
7
+ currentUrl?: string;
8
+ triggerAction?: 'clicked' | 'navigate';
9
+ hasLoginGate?: boolean;
10
+ composerText?: string;
11
+ } = {}): IPage {
12
+ const currentUrl = overrides.currentUrl ?? 'https://yuanbao.tencent.com/';
13
+ const triggerAction = overrides.triggerAction ?? 'clicked';
14
+ const hasLoginGate = overrides.hasLoginGate ?? false;
15
+ const composerText = overrides.composerText ?? '';
16
+
17
+ return {
18
+ goto: vi.fn().mockResolvedValue(undefined),
19
+ wait: vi.fn().mockResolvedValue(undefined),
20
+ evaluate: vi.fn().mockImplementation(async (script: string) => {
21
+ if (script === 'window.location.href') return currentUrl;
22
+ if (script.includes('微信扫码登录')) return hasLoginGate;
23
+ if (script.includes('.ql-editor, [contenteditable="true"]')) return composerText;
24
+ if (script.includes('const trigger = Array.from(document.querySelectorAll')) return triggerAction;
25
+ throw new Error(`Unexpected evaluate script in test: ${script.slice(0, 80)}`);
26
+ }),
27
+ } as unknown as IPage;
28
+ }
29
+
30
+ describe('yuanbao new command', () => {
31
+ it('throws AuthRequiredError when Yuanbao shows a login gate', async () => {
32
+ const page = createNewPageMock({ hasLoginGate: true });
33
+
34
+ await expect(newCommand.func!(page, {})).rejects.toBeInstanceOf(AuthRequiredError);
35
+ });
36
+ });
@@ -0,0 +1,81 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import type { IPage } from '../../types.js';
3
+ import { YUANBAO_DOMAIN, YUANBAO_URL, IS_VISIBLE_JS, authRequired, ensureYuanbaoPage, hasLoginGate } from './shared.js';
4
+
5
+ async function getCurrentUrl(page: IPage): Promise<string> {
6
+ const result = await page.evaluate('window.location.href').catch(() => '');
7
+ return typeof result === 'string' ? result : '';
8
+ }
9
+
10
+ async function getComposerText(page: IPage): Promise<string> {
11
+ const result = await page.evaluate(`(() => {
12
+ const composer = document.querySelector('.ql-editor, [contenteditable="true"]');
13
+ return composer ? (composer.textContent || '').trim() : '';
14
+ })()`);
15
+
16
+ return typeof result === 'string' ? result.trim() : '';
17
+ }
18
+
19
+ async function startNewYuanbaoChat(page: IPage): Promise<'clicked' | 'navigate' | 'blocked'> {
20
+ await ensureYuanbaoPage(page);
21
+
22
+ if (await hasLoginGate(page)) return 'blocked';
23
+
24
+ const beforeUrl = await getCurrentUrl(page);
25
+ const action = await page.evaluate(`(() => {
26
+ ${IS_VISIBLE_JS}
27
+
28
+ const trigger = Array.from(document.querySelectorAll('.yb-common-nav__trigger[data-desc="new-chat"]'))
29
+ .find((node) => isVisible(node));
30
+
31
+ if (trigger instanceof HTMLElement) {
32
+ trigger.click();
33
+ return 'clicked';
34
+ }
35
+
36
+ return 'navigate';
37
+ })()`) as 'clicked' | 'navigate';
38
+
39
+ if (action === 'navigate') {
40
+ await page.goto(YUANBAO_URL, { waitUntil: 'load', settleMs: 2500 });
41
+ await page.wait(1);
42
+ if (await hasLoginGate(page)) return 'blocked';
43
+ return 'navigate';
44
+ }
45
+
46
+ await page.wait(1);
47
+
48
+ if (await hasLoginGate(page)) return 'blocked';
49
+
50
+ const afterUrl = await getCurrentUrl(page);
51
+ const composerText = await getComposerText(page);
52
+ if (afterUrl !== beforeUrl || !composerText) return 'clicked';
53
+
54
+ await page.goto(YUANBAO_URL, { waitUntil: 'load', settleMs: 2500 });
55
+ await page.wait(1);
56
+ return 'navigate';
57
+ }
58
+
59
+ export const newCommand = cli({
60
+ site: 'yuanbao',
61
+ name: 'new',
62
+ description: 'Start a new conversation in Yuanbao web chat',
63
+ domain: YUANBAO_DOMAIN,
64
+ strategy: Strategy.COOKIE,
65
+ browser: true,
66
+ navigateBefore: false,
67
+ args: [],
68
+ columns: ['Status', 'Action'],
69
+ func: async (page: IPage) => {
70
+ const action = await startNewYuanbaoChat(page);
71
+
72
+ if (action === 'blocked') {
73
+ throw authRequired('Yuanbao opened a login gate instead of starting a new chat.');
74
+ }
75
+
76
+ return [{
77
+ Status: 'Success',
78
+ Action: action === 'navigate' ? 'Reloaded Yuanbao homepage as fallback' : 'Clicked New chat',
79
+ }];
80
+ },
81
+ });
@@ -0,0 +1,57 @@
1
+ import type { IPage } from '../../types.js';
2
+ import { AuthRequiredError } from '../../errors.js';
3
+
4
+ export const YUANBAO_DOMAIN = 'yuanbao.tencent.com';
5
+ export const YUANBAO_URL = 'https://yuanbao.tencent.com/';
6
+
7
+ const SESSION_HINT = 'Likely login/auth/challenge/session issue in the existing yuanbao.tencent.com browser session.';
8
+
9
+ /**
10
+ * Reusable visibility check for injected browser scripts.
11
+ * Embed in page.evaluate strings via `${IS_VISIBLE_JS}`.
12
+ */
13
+ export const IS_VISIBLE_JS = `const isVisible = (node) => {
14
+ if (!(node instanceof HTMLElement)) return false;
15
+ const rect = node.getBoundingClientRect();
16
+ const style = window.getComputedStyle(node);
17
+ return rect.width > 0
18
+ && rect.height > 0
19
+ && style.display !== 'none'
20
+ && style.visibility !== 'hidden';
21
+ };`;
22
+
23
+ export function authRequired(message: string) {
24
+ return new AuthRequiredError(YUANBAO_DOMAIN, `${message} ${SESSION_HINT}`);
25
+ }
26
+
27
+ export async function isOnYuanbao(page: IPage): Promise<boolean> {
28
+ const url = await page.evaluate('window.location.href').catch(() => '');
29
+ if (typeof url !== 'string' || !url) return false;
30
+
31
+ try {
32
+ const hostname = new URL(url).hostname;
33
+ return hostname === YUANBAO_DOMAIN || hostname.endsWith(`.${YUANBAO_DOMAIN}`);
34
+ } catch {
35
+ return false;
36
+ }
37
+ }
38
+
39
+ export async function ensureYuanbaoPage(page: IPage): Promise<void> {
40
+ if (!(await isOnYuanbao(page))) {
41
+ await page.goto(YUANBAO_URL, { waitUntil: 'load', settleMs: 2500 });
42
+ await page.wait(1);
43
+ }
44
+ }
45
+
46
+ export async function hasLoginGate(page: IPage): Promise<boolean> {
47
+ const result = await page.evaluate(`(() => {
48
+ const bodyText = document.body.innerText || '';
49
+ const hasWechatLoginText = bodyText.includes('微信扫码登录');
50
+ const hasWechatIframe = Array.from(document.querySelectorAll('iframe'))
51
+ .some((frame) => (frame.getAttribute('src') || '').includes('open.weixin.qq.com/connect/qrconnect'));
52
+
53
+ return hasWechatLoginText || hasWechatIframe;
54
+ })()`);
55
+
56
+ return Boolean(result);
57
+ }