@jackwener/opencli 1.6.1 → 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 (384) hide show
  1. package/CONTRIBUTING.md +1 -1
  2. package/README.md +27 -45
  3. package/README.zh-CN.md +32 -34
  4. package/autoresearch/browse-tasks.json +18 -20
  5. package/autoresearch/commands/debug.ts +163 -0
  6. package/autoresearch/commands/fix.ts +145 -0
  7. package/autoresearch/commands/plan.ts +88 -0
  8. package/autoresearch/commands/run.ts +138 -0
  9. package/autoresearch/config.ts +82 -0
  10. package/autoresearch/engine.ts +359 -0
  11. package/autoresearch/eval-all.ts +127 -0
  12. package/autoresearch/eval-browse.ts +1 -1
  13. package/autoresearch/eval-publish.ts +238 -0
  14. package/autoresearch/eval-save.ts +249 -0
  15. package/autoresearch/eval-skill.ts +14 -8
  16. package/autoresearch/eval-v2ex.ts +220 -0
  17. package/autoresearch/eval-zhihu.ts +230 -0
  18. package/autoresearch/logger.ts +69 -0
  19. package/autoresearch/presets/combined-reliability.ts +27 -0
  20. package/autoresearch/presets/index.ts +23 -0
  21. package/autoresearch/presets/operate-reliability.ts +24 -0
  22. package/autoresearch/presets/save-reliability.ts +26 -0
  23. package/autoresearch/presets/skill-quality.ts +20 -0
  24. package/autoresearch/presets/v2ex-reliability.ts +24 -0
  25. package/autoresearch/presets/zhihu-reliability.ts +25 -0
  26. package/autoresearch/publish-tasks.json +345 -0
  27. package/autoresearch/run-save.sh +11 -0
  28. package/autoresearch/save-adapters/xhs-explore-deep.ts +64 -0
  29. package/autoresearch/save-adapters/xhs-note-comments.ts +61 -0
  30. package/autoresearch/save-adapters/xhs-search-full.ts +62 -0
  31. package/autoresearch/save-adapters/zhihu-hot-detail.ts +52 -0
  32. package/autoresearch/save-adapters/zhihu-question-full.ts +57 -0
  33. package/autoresearch/save-adapters/zhihu-search-detail.ts +53 -0
  34. package/autoresearch/save-tasks.json +281 -0
  35. package/autoresearch/v2ex-tasks.json +899 -0
  36. package/autoresearch/zhihu-tasks.json +848 -0
  37. package/dist/browser/base-page.d.ts +4 -2
  38. package/dist/browser/base-page.js +37 -4
  39. package/dist/browser/bridge.js +10 -8
  40. package/dist/browser/cdp.js +2 -6
  41. package/dist/browser/daemon-client.d.ts +11 -1
  42. package/dist/browser/daemon-client.js +3 -0
  43. package/dist/browser/dom-helpers.d.ts +4 -2
  44. package/dist/browser/dom-helpers.js +42 -31
  45. package/dist/browser/dom-snapshot.js +23 -1
  46. package/dist/browser/page.d.ts +7 -2
  47. package/dist/browser/page.js +112 -30
  48. package/dist/browser.test.js +1 -1
  49. package/dist/build-manifest.d.ts +1 -0
  50. package/dist/build-manifest.js +1 -0
  51. package/dist/cli-manifest.json +1135 -184
  52. package/dist/cli.d.ts +2 -0
  53. package/dist/cli.js +48 -7
  54. package/dist/cli.test.d.ts +1 -0
  55. package/dist/cli.test.js +88 -0
  56. package/dist/clis/1688/item.d.ts +70 -0
  57. package/dist/clis/1688/item.js +187 -0
  58. package/dist/clis/1688/item.test.d.ts +1 -0
  59. package/dist/clis/1688/item.test.js +67 -0
  60. package/dist/clis/1688/search.d.ts +56 -0
  61. package/dist/clis/1688/search.js +309 -0
  62. package/dist/clis/1688/search.test.d.ts +1 -0
  63. package/dist/clis/1688/search.test.js +75 -0
  64. package/dist/clis/1688/shared.d.ts +112 -0
  65. package/dist/clis/1688/shared.js +514 -0
  66. package/dist/clis/1688/shared.test.d.ts +1 -0
  67. package/dist/clis/1688/shared.test.js +57 -0
  68. package/dist/clis/1688/store.d.ts +45 -0
  69. package/dist/clis/1688/store.js +226 -0
  70. package/dist/clis/1688/store.test.d.ts +1 -0
  71. package/dist/clis/1688/store.test.js +62 -0
  72. package/dist/clis/amazon/bestsellers.d.ts +0 -20
  73. package/dist/clis/amazon/bestsellers.js +6 -129
  74. package/dist/clis/amazon/bestsellers.test.js +12 -3
  75. package/dist/clis/amazon/movers-shakers.d.ts +1 -0
  76. package/dist/clis/amazon/movers-shakers.js +7 -0
  77. package/dist/clis/amazon/new-releases.d.ts +1 -0
  78. package/dist/clis/amazon/new-releases.js +7 -0
  79. package/dist/clis/amazon/rankings.d.ts +59 -0
  80. package/dist/clis/amazon/rankings.js +226 -0
  81. package/dist/clis/amazon/rankings.test.d.ts +1 -0
  82. package/dist/clis/amazon/rankings.test.js +41 -0
  83. package/dist/clis/amazon/shared.d.ts +11 -0
  84. package/dist/clis/amazon/shared.js +121 -11
  85. package/dist/clis/amazon/shared.test.js +11 -0
  86. package/dist/clis/bilibili/comments.js +2 -2
  87. package/dist/clis/bilibili/comments.test.js +3 -2
  88. package/dist/clis/bilibili/download.js +2 -1
  89. package/dist/clis/bilibili/subtitle.js +4 -3
  90. package/dist/clis/bilibili/subtitle.test.js +2 -1
  91. package/dist/clis/bilibili/utils.d.ts +5 -0
  92. package/dist/clis/bilibili/utils.js +30 -0
  93. package/dist/clis/bilibili/utils.test.d.ts +1 -0
  94. package/dist/clis/bilibili/utils.test.js +17 -0
  95. package/dist/clis/douban/marks.js +1 -1
  96. package/dist/clis/douban/subject.yaml +50 -19
  97. package/dist/clis/doubao/utils.js +32 -12
  98. package/dist/clis/douyin/_shared/browser-fetch.test.js +0 -1
  99. package/dist/clis/douyin/_shared/transcode.test.js +0 -2
  100. package/dist/clis/douyin/draft.test.js +0 -2
  101. package/dist/clis/facebook/search.test.js +0 -2
  102. package/dist/clis/gemini/ask.js +9 -3
  103. package/dist/clis/gemini/ask.test.d.ts +1 -0
  104. package/dist/clis/gemini/ask.test.js +100 -0
  105. package/dist/clis/gemini/reply-state.test.d.ts +1 -0
  106. package/dist/clis/gemini/reply-state.test.js +641 -0
  107. package/dist/clis/gemini/utils.d.ts +44 -1
  108. package/dist/clis/gemini/utils.js +528 -61
  109. package/dist/clis/gemini/utils.test.js +149 -2
  110. package/dist/clis/hupu/detail.d.ts +1 -0
  111. package/dist/clis/hupu/detail.js +72 -0
  112. package/dist/clis/hupu/hot.yaml +43 -0
  113. package/dist/clis/hupu/like.d.ts +1 -0
  114. package/dist/clis/hupu/like.js +75 -0
  115. package/dist/clis/hupu/reply.d.ts +1 -0
  116. package/dist/clis/hupu/reply.js +71 -0
  117. package/dist/clis/hupu/search.d.ts +1 -0
  118. package/dist/clis/hupu/search.js +59 -0
  119. package/dist/clis/hupu/unlike.d.ts +1 -0
  120. package/dist/clis/hupu/unlike.js +75 -0
  121. package/dist/clis/hupu/utils.d.ts +20 -0
  122. package/dist/clis/hupu/utils.js +319 -0
  123. package/dist/clis/instagram/_shared/private-publish.d.ts +138 -0
  124. package/dist/clis/instagram/_shared/private-publish.js +1030 -0
  125. package/dist/clis/instagram/_shared/private-publish.test.d.ts +1 -0
  126. package/dist/clis/instagram/_shared/private-publish.test.js +705 -0
  127. package/dist/clis/instagram/_shared/protocol-capture.d.ts +26 -0
  128. package/dist/clis/instagram/_shared/protocol-capture.js +282 -0
  129. package/dist/clis/instagram/_shared/protocol-capture.test.d.ts +1 -0
  130. package/dist/clis/instagram/_shared/protocol-capture.test.js +114 -0
  131. package/dist/clis/instagram/_shared/runtime-info.d.ts +9 -0
  132. package/dist/clis/instagram/_shared/runtime-info.js +81 -0
  133. package/dist/clis/instagram/note.d.ts +1 -0
  134. package/dist/clis/instagram/note.js +222 -0
  135. package/dist/clis/instagram/note.test.d.ts +1 -0
  136. package/dist/clis/instagram/note.test.js +81 -0
  137. package/dist/clis/instagram/post.d.ts +4 -0
  138. package/dist/clis/instagram/post.js +1496 -0
  139. package/dist/clis/instagram/post.test.d.ts +1 -0
  140. package/dist/clis/instagram/post.test.js +1647 -0
  141. package/dist/clis/instagram/reel.d.ts +1 -0
  142. package/dist/clis/instagram/reel.js +826 -0
  143. package/dist/clis/instagram/reel.test.d.ts +1 -0
  144. package/dist/clis/instagram/reel.test.js +167 -0
  145. package/dist/clis/instagram/story.d.ts +1 -0
  146. package/dist/clis/instagram/story.js +115 -0
  147. package/dist/clis/instagram/story.test.d.ts +1 -0
  148. package/dist/clis/instagram/story.test.js +167 -0
  149. package/dist/clis/sinafinance/stock-rank.d.ts +4 -0
  150. package/dist/clis/sinafinance/stock-rank.js +65 -0
  151. package/dist/clis/substack/utils.test.js +0 -2
  152. package/dist/clis/twitter/post.js +72 -45
  153. package/dist/clis/twitter/post.test.d.ts +1 -0
  154. package/dist/clis/twitter/post.test.js +116 -0
  155. package/dist/clis/twitter/reply.d.ts +12 -0
  156. package/dist/clis/twitter/reply.js +257 -35
  157. package/dist/clis/twitter/reply.test.d.ts +1 -0
  158. package/dist/clis/twitter/reply.test.js +151 -0
  159. package/dist/clis/xianyu/chat.d.ts +7 -0
  160. package/dist/clis/xianyu/chat.js +146 -0
  161. package/dist/clis/xianyu/chat.test.d.ts +1 -0
  162. package/dist/clis/xianyu/chat.test.js +15 -0
  163. package/dist/clis/xianyu/item.d.ts +7 -0
  164. package/dist/clis/xianyu/item.js +152 -0
  165. package/dist/clis/xianyu/item.test.d.ts +1 -0
  166. package/dist/clis/xianyu/item.test.js +56 -0
  167. package/dist/clis/xianyu/search.d.ts +10 -0
  168. package/dist/clis/xianyu/search.js +134 -0
  169. package/dist/clis/xianyu/search.test.d.ts +1 -0
  170. package/dist/clis/xianyu/search.test.js +17 -0
  171. package/dist/clis/xianyu/utils.d.ts +1 -0
  172. package/dist/clis/xianyu/utils.js +8 -0
  173. package/dist/clis/xiaoe/catalog.yaml +129 -0
  174. package/dist/clis/xiaoe/content.yaml +43 -0
  175. package/dist/clis/xiaoe/courses.yaml +73 -0
  176. package/dist/clis/xiaoe/detail.yaml +39 -0
  177. package/dist/clis/xiaoe/play-url.yaml +124 -0
  178. package/dist/clis/xiaohongshu/comments.test.js +0 -2
  179. package/dist/clis/xiaohongshu/creator-note-detail.test.js +0 -2
  180. package/dist/clis/xiaohongshu/creator-notes.test.js +0 -2
  181. package/dist/clis/xiaohongshu/download.test.js +0 -2
  182. package/dist/clis/xiaohongshu/note.test.js +0 -2
  183. package/dist/clis/xiaohongshu/publish.test.js +0 -2
  184. package/dist/clis/xiaohongshu/search.js +29 -20
  185. package/dist/clis/xiaohongshu/search.test.js +56 -48
  186. package/dist/clis/yuanbao/ask.d.ts +21 -0
  187. package/dist/clis/yuanbao/ask.js +427 -0
  188. package/dist/clis/yuanbao/ask.test.d.ts +1 -0
  189. package/dist/clis/yuanbao/ask.test.js +124 -0
  190. package/dist/clis/yuanbao/new.d.ts +1 -0
  191. package/dist/clis/yuanbao/new.js +70 -0
  192. package/dist/clis/yuanbao/new.test.d.ts +1 -0
  193. package/dist/clis/yuanbao/new.test.js +30 -0
  194. package/dist/clis/yuanbao/shared.d.ts +13 -0
  195. package/dist/clis/yuanbao/shared.js +49 -0
  196. package/dist/clis/zhihu/question.js +30 -19
  197. package/dist/clis/zhihu/question.test.js +34 -16
  198. package/dist/commanderAdapter.js +8 -4
  199. package/dist/commanderAdapter.test.js +42 -0
  200. package/dist/completion.js +3 -1
  201. package/dist/completion.test.d.ts +1 -0
  202. package/dist/completion.test.js +23 -0
  203. package/dist/doctor.js +1 -1
  204. package/dist/electron-apps.d.ts +2 -0
  205. package/dist/electron-apps.js +7 -1
  206. package/dist/errors.js +1 -1
  207. package/dist/execution.js +25 -35
  208. package/dist/explore.js +1 -1
  209. package/dist/launcher.d.ts +4 -0
  210. package/dist/launcher.js +64 -8
  211. package/dist/launcher.test.js +88 -7
  212. package/dist/output.d.ts +2 -0
  213. package/dist/output.js +10 -1
  214. package/dist/output.test.d.ts +0 -3
  215. package/dist/output.test.js +59 -92
  216. package/dist/pipeline/executor.test.js +0 -2
  217. package/dist/pipeline/steps/download.test.js +0 -2
  218. package/dist/registry.d.ts +2 -0
  219. package/dist/serialization.d.ts +1 -0
  220. package/dist/serialization.js +1 -0
  221. package/dist/types.d.ts +9 -2
  222. package/docs/.vitepress/config.mts +4 -0
  223. package/docs/adapters/browser/1688.md +52 -0
  224. package/docs/adapters/browser/36kr.md +2 -1
  225. package/docs/adapters/browser/doubao.md +5 -1
  226. package/docs/adapters/browser/hupu.md +53 -0
  227. package/docs/adapters/browser/sinafinance.md +32 -2
  228. package/docs/adapters/browser/weibo.md +6 -1
  229. package/docs/adapters/browser/wikipedia.md +2 -0
  230. package/docs/adapters/browser/xianyu.md +42 -0
  231. package/docs/adapters/browser/xiaoe.md +44 -0
  232. package/docs/adapters/browser/yuanbao.md +64 -0
  233. package/docs/adapters/index.md +14 -5
  234. package/docs/comparison.md +1 -1
  235. package/docs/developer/ai-workflow.md +2 -2
  236. package/docs/developer/contributing.md +1 -1
  237. package/docs/developer/testing.md +2 -0
  238. package/docs/guide/plugins.md +1 -0
  239. package/docs/guide/troubleshooting.md +11 -0
  240. package/docs/superpowers/specs/2026-04-03-v2ex-autoresearch-design.md +41 -0
  241. package/docs/zh/guide/plugins.md +1 -0
  242. package/extension/dist/background.js +1127 -0
  243. package/extension/src/background.test.ts +39 -0
  244. package/extension/src/background.ts +223 -34
  245. package/extension/src/cdp.ts +194 -4
  246. package/extension/src/protocol.ts +22 -1
  247. package/package.json +3 -2
  248. package/scripts/postinstall.js +1 -1
  249. package/skills/opencli-explorer/SKILL.md +1 -1
  250. package/skills/opencli-oneshot/SKILL.md +2 -2
  251. package/skills/opencli-operate/SKILL.md +120 -27
  252. package/skills/opencli-usage/SKILL.md +31 -20
  253. package/skills/opencli-usage/browser.md +114 -16
  254. package/skills/opencli-usage/public-api.md +32 -3
  255. package/skills/smart-search/SKILL.md +156 -0
  256. package/skills/smart-search/references/sources-ai.md +74 -0
  257. package/skills/smart-search/references/sources-info.md +43 -0
  258. package/skills/smart-search/references/sources-media.md +50 -0
  259. package/skills/smart-search/references/sources-other.md +42 -0
  260. package/skills/smart-search/references/sources-shopping.md +31 -0
  261. package/skills/smart-search/references/sources-social.md +51 -0
  262. package/skills/smart-search/references/sources-tech.md +42 -0
  263. package/skills/smart-search/references/sources-travel.md +20 -0
  264. package/src/browser/base-page.ts +41 -6
  265. package/src/browser/bridge.ts +11 -8
  266. package/src/browser/cdp.ts +1 -8
  267. package/src/browser/daemon-client.ts +11 -1
  268. package/src/browser/dom-helpers.ts +43 -31
  269. package/src/browser/dom-snapshot.ts +23 -1
  270. package/src/browser/page.ts +115 -31
  271. package/src/browser.test.ts +1 -1
  272. package/src/build-manifest.ts +2 -0
  273. package/src/cli.test.ts +133 -0
  274. package/src/cli.ts +73 -11
  275. package/src/clis/1688/item.test.ts +69 -0
  276. package/src/clis/1688/item.ts +282 -0
  277. package/src/clis/1688/search.test.ts +81 -0
  278. package/src/clis/1688/search.ts +402 -0
  279. package/src/clis/1688/shared.test.ts +75 -0
  280. package/src/clis/1688/shared.ts +623 -0
  281. package/src/clis/1688/store.test.ts +69 -0
  282. package/src/clis/1688/store.ts +300 -0
  283. package/src/clis/amazon/bestsellers.test.ts +12 -3
  284. package/src/clis/amazon/bestsellers.ts +6 -178
  285. package/src/clis/amazon/movers-shakers.ts +8 -0
  286. package/src/clis/amazon/new-releases.ts +8 -0
  287. package/src/clis/amazon/rankings.test.ts +47 -0
  288. package/src/clis/amazon/rankings.ts +312 -0
  289. package/src/clis/amazon/shared.test.ts +16 -0
  290. package/src/clis/amazon/shared.ts +134 -12
  291. package/src/clis/bilibili/comments.test.ts +4 -3
  292. package/src/clis/bilibili/comments.ts +2 -2
  293. package/src/clis/bilibili/download.ts +2 -1
  294. package/src/clis/bilibili/subtitle.test.ts +2 -1
  295. package/src/clis/bilibili/subtitle.ts +4 -3
  296. package/src/clis/bilibili/utils.test.ts +21 -0
  297. package/src/clis/bilibili/utils.ts +27 -0
  298. package/src/clis/douban/marks.ts +1 -1
  299. package/src/clis/douban/subject.yaml +50 -19
  300. package/src/clis/doubao/utils.ts +32 -12
  301. package/src/clis/douyin/_shared/browser-fetch.test.ts +0 -1
  302. package/src/clis/douyin/_shared/transcode.test.ts +0 -2
  303. package/src/clis/douyin/draft.test.ts +0 -2
  304. package/src/clis/facebook/search.test.ts +0 -2
  305. package/src/clis/gemini/ask.test.ts +116 -0
  306. package/src/clis/gemini/ask.ts +10 -3
  307. package/src/clis/gemini/reply-state.test.ts +708 -0
  308. package/src/clis/gemini/utils.test.ts +184 -2
  309. package/src/clis/gemini/utils.ts +588 -60
  310. package/src/clis/hupu/detail.ts +126 -0
  311. package/src/clis/hupu/hot.yaml +43 -0
  312. package/src/clis/hupu/like.ts +76 -0
  313. package/src/clis/hupu/reply.ts +76 -0
  314. package/src/clis/hupu/search.ts +95 -0
  315. package/src/clis/hupu/unlike.ts +76 -0
  316. package/src/clis/hupu/utils.ts +381 -0
  317. package/src/clis/instagram/_shared/private-publish.test.ts +827 -0
  318. package/src/clis/instagram/_shared/private-publish.ts +1303 -0
  319. package/src/clis/instagram/_shared/protocol-capture.test.ts +148 -0
  320. package/src/clis/instagram/_shared/protocol-capture.ts +321 -0
  321. package/src/clis/instagram/_shared/runtime-info.ts +91 -0
  322. package/src/clis/instagram/note.test.ts +96 -0
  323. package/src/clis/instagram/note.ts +254 -0
  324. package/src/clis/instagram/post.test.ts +1716 -0
  325. package/src/clis/instagram/post.ts +1620 -0
  326. package/src/clis/instagram/reel.test.ts +191 -0
  327. package/src/clis/instagram/reel.ts +886 -0
  328. package/src/clis/instagram/story.test.ts +191 -0
  329. package/src/clis/instagram/story.ts +151 -0
  330. package/src/clis/sinafinance/stock-rank.ts +68 -0
  331. package/src/clis/substack/utils.test.ts +0 -2
  332. package/src/clis/twitter/post.test.ts +157 -0
  333. package/src/clis/twitter/post.ts +82 -48
  334. package/src/clis/twitter/reply.test.ts +177 -0
  335. package/src/clis/twitter/reply.ts +285 -39
  336. package/src/clis/xianyu/chat.test.ts +20 -0
  337. package/src/clis/xianyu/chat.ts +175 -0
  338. package/src/clis/xianyu/item.test.ts +67 -0
  339. package/src/clis/xianyu/item.ts +172 -0
  340. package/src/clis/xianyu/search.test.ts +22 -0
  341. package/src/clis/xianyu/search.ts +151 -0
  342. package/src/clis/xianyu/utils.ts +9 -0
  343. package/src/clis/xiaoe/catalog.yaml +129 -0
  344. package/src/clis/xiaoe/content.yaml +43 -0
  345. package/src/clis/xiaoe/courses.yaml +73 -0
  346. package/src/clis/xiaoe/detail.yaml +39 -0
  347. package/src/clis/xiaoe/play-url.yaml +124 -0
  348. package/src/clis/xiaohongshu/comments.test.ts +0 -2
  349. package/src/clis/xiaohongshu/creator-note-detail.test.ts +0 -2
  350. package/src/clis/xiaohongshu/creator-notes.test.ts +0 -2
  351. package/src/clis/xiaohongshu/download.test.ts +0 -2
  352. package/src/clis/xiaohongshu/note.test.ts +0 -2
  353. package/src/clis/xiaohongshu/publish.test.ts +0 -2
  354. package/src/clis/xiaohongshu/search.test.ts +59 -48
  355. package/src/clis/xiaohongshu/search.ts +31 -21
  356. package/src/clis/yuanbao/ask.test.ts +156 -0
  357. package/src/clis/yuanbao/ask.ts +522 -0
  358. package/src/clis/yuanbao/new.test.ts +36 -0
  359. package/src/clis/yuanbao/new.ts +81 -0
  360. package/src/clis/yuanbao/shared.ts +57 -0
  361. package/src/clis/zhihu/question.test.ts +42 -17
  362. package/src/clis/zhihu/question.ts +31 -26
  363. package/src/commanderAdapter.test.ts +51 -0
  364. package/src/commanderAdapter.ts +8 -4
  365. package/src/completion.test.ts +30 -0
  366. package/src/completion.ts +3 -1
  367. package/src/doctor.ts +1 -1
  368. package/src/electron-apps.ts +9 -1
  369. package/src/errors.ts +1 -1
  370. package/src/execution.ts +26 -30
  371. package/src/explore.ts +1 -1
  372. package/src/launcher.test.ts +121 -7
  373. package/src/launcher.ts +87 -9
  374. package/src/output.test.ts +50 -90
  375. package/src/output.ts +10 -1
  376. package/src/pipeline/executor.test.ts +0 -2
  377. package/src/pipeline/steps/download.test.ts +0 -2
  378. package/src/registry.ts +2 -0
  379. package/src/serialization.ts +2 -0
  380. package/src/types.ts +9 -2
  381. package/tests/e2e/browser-auth.test.ts +9 -0
  382. package/CLI-EXPLORER.md +0 -724
  383. package/CLI-ONESHOT.md +0 -216
  384. package/SKILL.md +0 -59
@@ -9,6 +9,29 @@
9
9
  import { cli, Strategy } from '../../registry.js';
10
10
  import { AuthRequiredError } from '../../errors.js';
11
11
 
12
+ /**
13
+ * Wait for search results or login wall using MutationObserver (max 5s).
14
+ * Returns 'content' if note items appeared, 'login_wall' if login gate
15
+ * detected, or 'timeout' if neither appeared within the deadline.
16
+ */
17
+ const WAIT_FOR_CONTENT_JS = `
18
+ new Promise((resolve) => {
19
+ const detect = () => {
20
+ if (document.querySelector('section.note-item')) return 'content';
21
+ if (/登录后查看搜索结果/.test(document.body?.innerText || '')) return 'login_wall';
22
+ return null;
23
+ };
24
+ const found = detect();
25
+ if (found) return resolve(found);
26
+ const observer = new MutationObserver(() => {
27
+ const result = detect();
28
+ if (result) { observer.disconnect(); resolve(result); }
29
+ });
30
+ observer.observe(document.body, { childList: true, subtree: true });
31
+ setTimeout(() => { observer.disconnect(); resolve('timeout'); }, 5000);
32
+ })
33
+ `;
34
+
12
35
  /**
13
36
  * Extract approximate publish date from a Xiaohongshu note URL.
14
37
  * XHS note IDs follow MongoDB ObjectID format where the first 8 hex
@@ -42,15 +65,13 @@ cli({
42
65
  await page.goto(
43
66
  `https://www.xiaohongshu.com/search_result?keyword=${keyword}&source=web_search_result_notes`
44
67
  );
45
- await page.wait(3);
46
68
 
47
- // Early login-wall detection: XHS may show a login gate instead of
48
- // results. Check *before* autoScroll to avoid crashing on a page
49
- // that has no meaningful content to scroll through.
50
- const loginCheck = await page.evaluate(`
51
- (() => /登录后查看搜索结果/.test(document.body?.innerText || ''))()
52
- `);
53
- if (loginCheck) {
69
+ // Wait for search results to render (or login wall to appear).
70
+ // Uses MutationObserver to resolve as soon as content appears,
71
+ // instead of a fixed delay + blind retry.
72
+ const waitResult = await page.evaluate(WAIT_FOR_CONTENT_JS);
73
+
74
+ if (waitResult === 'login_wall') {
54
75
  throw new AuthRequiredError(
55
76
  'www.xiaohongshu.com',
56
77
  'Xiaohongshu search results are blocked behind a login wall',
@@ -62,8 +83,6 @@ cli({
62
83
 
63
84
  const payload = await page.evaluate(`
64
85
  (() => {
65
- const loginWall = /登录后查看搜索结果/.test(document.body.innerText || '');
66
-
67
86
  const normalizeUrl = (href) => {
68
87
  if (!href) return '';
69
88
  if (href.startsWith('http://') || href.startsWith('https://')) return href;
@@ -107,20 +126,11 @@ cli({
107
126
  });
108
127
  });
109
128
 
110
- return {
111
- loginWall,
112
- results,
113
- };
129
+ return results;
114
130
  })()
115
131
  `);
116
132
 
117
- if (!payload || typeof payload !== 'object') return [];
118
-
119
- if ((payload as any).loginWall) {
120
- throw new AuthRequiredError('www.xiaohongshu.com', 'Xiaohongshu search results are blocked behind a login wall');
121
- }
122
-
123
- const data: any[] = Array.isArray((payload as any).results) ? (payload as any).results : [];
133
+ const data: any[] = Array.isArray(payload) ? (payload as any[]) : [];
124
134
  return data
125
135
  .filter((item: any) => item.title)
126
136
  .slice(0, kwargs.limit)
@@ -0,0 +1,156 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import type { IPage } from '../../types.js';
3
+ import { AuthRequiredError, CommandExecutionError, TimeoutError } from '../../errors.js';
4
+ import { __test__ } from './ask.js';
5
+ import { askCommand } from './ask.js';
6
+
7
+ describe('yuanbao ask helpers', () => {
8
+ describe('isOnYuanbao', () => {
9
+ const fakePage = (url: string | Error): IPage =>
10
+ ({ evaluate: () => url instanceof Error ? Promise.reject(url) : Promise.resolve(url) }) as unknown as IPage;
11
+
12
+ it('returns true for yuanbao.tencent.com URLs', async () => {
13
+ expect(await __test__.isOnYuanbao(fakePage('https://yuanbao.tencent.com/'))).toBe(true);
14
+ expect(await __test__.isOnYuanbao(fakePage('https://yuanbao.tencent.com/chat/abc'))).toBe(true);
15
+ });
16
+
17
+ it('returns false for non-yuanbao domains', async () => {
18
+ expect(await __test__.isOnYuanbao(fakePage('https://example.com/?next=yuanbao.tencent.com'))).toBe(false);
19
+ expect(await __test__.isOnYuanbao(fakePage('about:blank'))).toBe(false);
20
+ });
21
+
22
+ it('returns false when evaluate throws', async () => {
23
+ expect(await __test__.isOnYuanbao(fakePage(new Error('detached')))).toBe(false);
24
+ });
25
+ });
26
+
27
+ it('removes echoed prompt prefixes from transcript additions', () => {
28
+ expect(__test__.sanitizeYuanbaoResponseText('你好\n你好,我是元宝。', '你好')).toBe('你好,我是元宝。');
29
+ });
30
+
31
+ it('filters transient in-progress assistant placeholders', () => {
32
+ expect(__test__.sanitizeYuanbaoResponseText('正在搜索资料', '张雪机车相关的股票有哪些?')).toBe('');
33
+ });
34
+
35
+ it('normalizes boolean flags with explicit defaults', () => {
36
+ expect(__test__.normalizeBooleanFlag(undefined, true)).toBe(true);
37
+ expect(__test__.normalizeBooleanFlag(undefined, false)).toBe(false);
38
+ expect(__test__.normalizeBooleanFlag('true', false)).toBe(true);
39
+ expect(__test__.normalizeBooleanFlag('1', false)).toBe(true);
40
+ expect(__test__.normalizeBooleanFlag('yes', false)).toBe(true);
41
+ expect(__test__.normalizeBooleanFlag('false', true)).toBe(false);
42
+ });
43
+
44
+ it('ignores baseline lines and echoed prompts when collecting additions', () => {
45
+ const response = __test__.collectYuanbaoTranscriptAdditions(
46
+ ['旧消息'],
47
+ ['旧消息', '你好', '你好\n你好,我是元宝。'],
48
+ '你好',
49
+ );
50
+
51
+ expect(response).toBe('你好,我是元宝。');
52
+ });
53
+
54
+ it('prefers fresh assistant messages over echoed prompts and older messages', () => {
55
+ const response = __test__.pickLatestYuanbaoAssistantCandidate(
56
+ ['旧回复', '你好', '你好!我是元宝,由腾讯推出的AI助手。'],
57
+ 1,
58
+ '你好',
59
+ );
60
+
61
+ expect(response).toBe('你好!我是元宝,由腾讯推出的AI助手。');
62
+ });
63
+
64
+ it('converts assistant html tables to markdown tables via turndown', () => {
65
+ const markdown = __test__.convertYuanbaoHtmlToMarkdown(`
66
+ <h3>核心产业链概念股一览</h3>
67
+ <table>
68
+ <thead>
69
+ <tr><th>细分赛道</th><th>核心标的</th></tr>
70
+ </thead>
71
+ <tbody>
72
+ <tr><td>光模块</td><td>中际旭创</td></tr>
73
+ </tbody>
74
+ </table>
75
+ `);
76
+
77
+ expect(markdown).toContain('### 核心产业链概念股一览');
78
+ expect(markdown).toContain('| 细分赛道 | 核心标的 |');
79
+ expect(markdown).toContain('| --- | --- |');
80
+ expect(markdown).toContain('| 光模块 | 中际旭创 |');
81
+ });
82
+
83
+ it('tracks stabilization by incrementing repeats and resetting on changes', () => {
84
+ expect(__test__.updateStableState('', 0, '第一段')).toEqual({
85
+ previousText: '第一段',
86
+ stableCount: 0,
87
+ });
88
+
89
+ expect(__test__.updateStableState('第一段', 0, '第一段')).toEqual({
90
+ previousText: '第一段',
91
+ stableCount: 1,
92
+ });
93
+
94
+ expect(__test__.updateStableState('第一段', 1, '第二段')).toEqual({
95
+ previousText: '第二段',
96
+ stableCount: 0,
97
+ });
98
+ });
99
+ });
100
+
101
+ function createAskPageMock(overrides: {
102
+ currentUrl?: string;
103
+ hasLoginGate?: boolean;
104
+ sendResult?: { ok?: boolean; reason?: string; detail?: string; action?: string };
105
+ } = {}): IPage {
106
+ const currentUrl = overrides.currentUrl ?? 'https://yuanbao.tencent.com/';
107
+ const hasLoginGate = overrides.hasLoginGate ?? false;
108
+ const sendResult = overrides.sendResult;
109
+
110
+ return {
111
+ goto: vi.fn().mockResolvedValue(undefined),
112
+ wait: vi.fn().mockResolvedValue(undefined),
113
+ evaluate: vi.fn().mockImplementation(async (script: string) => {
114
+ if (script === 'window.location.href') return currentUrl;
115
+ if (script.includes('微信扫码登录')) return hasLoginGate;
116
+ if (script.includes('[dt-button-id="internet_search"]')) return { found: false, enabled: false };
117
+ if (script.includes('[dt-button-id="deep_think"]')) return { found: false, enabled: false };
118
+ if (script.includes('.agent-chat__list__item--ai')) return [];
119
+ if (script.includes('const stopLines = new Set([')) return [];
120
+ if (script.includes('Failed to insert the prompt into the Yuanbao composer.')) {
121
+ return sendResult ?? { ok: true, action: 'click' };
122
+ }
123
+ throw new Error(`Unexpected evaluate script in test: ${script.slice(0, 80)}`);
124
+ }),
125
+ } as unknown as IPage;
126
+ }
127
+
128
+ describe('yuanbao ask command', () => {
129
+ it('throws AuthRequiredError when Yuanbao shows a login gate before sending', async () => {
130
+ const page = createAskPageMock({ hasLoginGate: true });
131
+
132
+ await expect(askCommand.func!(page, { prompt: '你好', timeout: '60', search: true, think: false }))
133
+ .rejects.toBeInstanceOf(AuthRequiredError);
134
+ });
135
+
136
+ it('throws CommandExecutionError when the prompt cannot be sent', async () => {
137
+ const page = createAskPageMock({
138
+ sendResult: {
139
+ ok: false,
140
+ reason: 'Yuanbao composer was not found.',
141
+ },
142
+ });
143
+
144
+ await expect(askCommand.func!(page, { prompt: '你好', timeout: '60', search: true, think: false }))
145
+ .rejects.toBeInstanceOf(CommandExecutionError);
146
+ });
147
+
148
+ it('throws TimeoutError when no response arrives before timeout', async () => {
149
+ const page = createAskPageMock({
150
+ sendResult: { ok: true, action: 'click' },
151
+ });
152
+
153
+ await expect(askCommand.func!(page, { prompt: '你好', timeout: '-1', search: true, think: false }))
154
+ .rejects.toBeInstanceOf(TimeoutError);
155
+ });
156
+ });