@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
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Preset: Zhihu Command Reliability
3
+ *
4
+ * Optimizes opencli operate commands against the Zhihu test suite.
5
+ * 60 tasks across 8 difficulty layers (atomic → complex long chain).
6
+ * Zhihu is a React SPA with lazy loading, making it harder than V2EX.
7
+ */
8
+
9
+ import type { AutoResearchConfig } from '../config.js';
10
+
11
+ export const zhihuReliability: AutoResearchConfig = {
12
+ goal: 'Increase Zhihu operate command pass rate to 60/60 (100%)',
13
+ scope: [
14
+ 'src/browser/dom-snapshot.ts',
15
+ 'src/browser/dom-helpers.ts',
16
+ 'src/browser/base-page.ts',
17
+ 'src/browser/page.ts',
18
+ 'src/cli.ts',
19
+ ],
20
+ metric: 'pass_count',
21
+ direction: 'higher',
22
+ verify: 'npx tsx autoresearch/eval-zhihu.ts 2>&1 | tail -1',
23
+ guard: 'npm run build',
24
+ minDelta: 1,
25
+ };
@@ -0,0 +1,345 @@
1
+ [
2
+ {
3
+ "name": "twitter-fill-compose",
4
+ "platform": "twitter",
5
+ "type": "fill-only",
6
+ "description": "Navigate to tweet composer, fill in content (no publish)",
7
+ "steps": [
8
+ "opencli operate open https://x.com/compose/tweet",
9
+ "opencli operate wait time 3",
10
+ "opencli operate eval \"document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]') ? 'composer-ready' : 'not-found'\"",
11
+ "opencli operate eval \"(() => { const box = document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]'); if (!box) return 'no-box'; box.focus(); const dt = new DataTransfer(); dt.setData('text/plain', '[AutoTest] OpenCLI publish eval - fill only test ' + Date.now()); box.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); return 'filled'; })()\"",
12
+ "opencli operate wait time 1",
13
+ "opencli operate eval \"document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]')?.textContent || ''\""
14
+ ],
15
+ "judge": {
16
+ "type": "contains",
17
+ "value": "AutoTest"
18
+ },
19
+ "note": "3-step: open compose → paste text via ClipboardEvent → verify text in composer"
20
+ },
21
+ {
22
+ "name": "twitter-post-and-delete",
23
+ "platform": "twitter",
24
+ "type": "publish",
25
+ "description": "Post a tweet, verify success, then delete it",
26
+ "steps": [
27
+ "opencli operate open https://x.com/compose/tweet",
28
+ "opencli operate wait time 3",
29
+ "opencli operate eval \"(() => { const box = document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]'); if (!box) return 'no-box'; box.focus(); const dt = new DataTransfer(); dt.setData('text/plain', '[AutoTest] OpenCLI publish eval ' + Date.now()); box.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); return 'filled'; })()\"",
30
+ "opencli operate wait time 1",
31
+ "opencli operate eval \"(() => { const btn = document.querySelector('[data-testid=\\\"tweetButton\\\"]') || document.querySelector('[data-testid=\\\"tweetButtonInline\\\"]'); if (btn && !btn.disabled) { btn.click(); return 'clicked'; } return 'btn-not-ready'; })()\"",
32
+ "opencli operate wait time 4",
33
+ "opencli operate eval \"document.querySelector('[data-testid=\\\"toast\\\"]')?.textContent || document.title\""
34
+ ],
35
+ "judge": {
36
+ "type": "matchesPattern",
37
+ "pattern": "post|sent|Your post|X"
38
+ },
39
+ "cleanup": [
40
+ "opencli operate open https://x.com/home",
41
+ "opencli operate wait time 3",
42
+ "opencli operate eval \"(() => { const tweets = document.querySelectorAll('[data-testid=\\\"tweet\\\"]'); for (const t of tweets) { if (t.textContent?.includes('[AutoTest]')) { const more = t.querySelector('[data-testid=\\\"caret\\\"]'); if (more) { more.click(); return 'found-menu'; } } } return 'no-autotest-tweet'; })()\"",
43
+ "opencli operate wait time 1",
44
+ "opencli operate eval \"(() => { const items = document.querySelectorAll('[role=\\\"menuitem\\\"]'); for (const item of items) { if (item.textContent?.includes('Delete')) { item.click(); return 'clicked-delete'; } } return 'no-delete-option'; })()\"",
45
+ "opencli operate wait time 1",
46
+ "opencli operate eval \"(() => { const confirm = document.querySelector('[data-testid=\\\"confirmationSheetConfirm\\\"]'); if (confirm) { confirm.click(); return 'deleted'; } return 'no-confirm'; })()\""
47
+ ],
48
+ "note": "6-step chain: open compose → paste text → click post → wait → verify toast → cleanup: find tweet → menu → delete → confirm"
49
+ },
50
+ {
51
+ "name": "twitter-read-hn-then-post",
52
+ "platform": "twitter",
53
+ "type": "publish",
54
+ "description": "Read HN top story title, compose a tweet about it, post, then delete",
55
+ "steps": [
56
+ "opencli operate open https://news.ycombinator.com",
57
+ "opencli operate wait time 2",
58
+ "opencli operate eval \"document.querySelector('.titleline a')?.textContent?.trim() || 'no-title'\"",
59
+ "opencli operate open https://x.com/compose/tweet",
60
+ "opencli operate wait time 3",
61
+ "opencli operate eval \"(() => { const box = document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]'); if (!box) return 'no-box'; box.focus(); const title = document.title || 'HN Story'; const dt = new DataTransfer(); dt.setData('text/plain', '[AutoTest] Interesting from HN: ' + Date.now()); box.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); return 'filled'; })()\"",
62
+ "opencli operate wait time 1",
63
+ "opencli operate eval \"(() => { const btn = document.querySelector('[data-testid=\\\"tweetButton\\\"]') || document.querySelector('[data-testid=\\\"tweetButtonInline\\\"]'); if (btn && !btn.disabled) { btn.click(); return 'clicked'; } return 'btn-not-ready'; })()\"",
64
+ "opencli operate wait time 4",
65
+ "opencli operate eval \"document.querySelector('[data-testid=\\\"toast\\\"]')?.textContent || document.title\""
66
+ ],
67
+ "judge": {
68
+ "type": "matchesPattern",
69
+ "pattern": "post|sent|Your post|X"
70
+ },
71
+ "cleanup": [
72
+ "opencli operate open https://x.com/home",
73
+ "opencli operate wait time 3",
74
+ "opencli operate eval \"(() => { const tweets = document.querySelectorAll('[data-testid=\\\"tweet\\\"]'); for (const t of tweets) { if (t.textContent?.includes('[AutoTest]')) { const more = t.querySelector('[data-testid=\\\"caret\\\"]'); if (more) { more.click(); return 'found-menu'; } } } return 'no-autotest-tweet'; })()\"",
75
+ "opencli operate wait time 1",
76
+ "opencli operate eval \"(() => { const items = document.querySelectorAll('[role=\\\"menuitem\\\"]'); for (const item of items) { if (item.textContent?.includes('Delete')) { item.click(); return 'clicked-delete'; } } return 'no-delete-option'; })()\"",
77
+ "opencli operate wait time 1",
78
+ "opencli operate eval \"(() => { const confirm = document.querySelector('[data-testid=\\\"confirmationSheetConfirm\\\"]'); if (confirm) { confirm.click(); return 'deleted'; } return 'no-confirm'; })()\""
79
+ ],
80
+ "note": "9-step cross-site chain: read HN title → navigate to twitter compose → paste content → post → verify → cleanup delete"
81
+ },
82
+ {
83
+ "name": "twitter-reply-to-own-tweet",
84
+ "platform": "twitter",
85
+ "type": "fill-only",
86
+ "description": "Navigate to own profile, find latest tweet, open reply box, fill reply text",
87
+ "steps": [
88
+ "opencli operate open https://x.com/home",
89
+ "opencli operate wait time 3",
90
+ "opencli operate eval \"(() => { const tweet = document.querySelector('[data-testid=\\\"tweet\\\"]'); if (!tweet) return 'no-tweet'; const reply = tweet.querySelector('[data-testid=\\\"reply\\\"]'); if (reply) { reply.click(); return 'reply-clicked'; } return 'no-reply-btn'; })()\"",
91
+ "opencli operate wait time 2",
92
+ "opencli operate eval \"(() => { const box = document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]'); if (!box) return 'no-box'; box.focus(); const dt = new DataTransfer(); dt.setData('text/plain', '[AutoTest] Reply test ' + Date.now()); box.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); return 'filled'; })()\"",
93
+ "opencli operate wait time 1",
94
+ "opencli operate eval \"document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]')?.textContent || ''\""
95
+ ],
96
+ "judge": {
97
+ "type": "contains",
98
+ "value": "AutoTest"
99
+ },
100
+ "note": "5-step: home → find first tweet → click reply → fill reply text → verify content"
101
+ },
102
+ {
103
+ "name": "zhihu-fill-answer",
104
+ "platform": "zhihu",
105
+ "type": "fill-only",
106
+ "description": "Navigate to a popular question, open answer editor, fill in answer content (no publish)",
107
+ "steps": [
108
+ "opencli operate open https://www.zhihu.com/question/19550225",
109
+ "opencli operate wait time 3",
110
+ "opencli operate eval \"(() => { const btn = document.querySelector('[data-zop-retarget=\\\"answer\\\"]') || Array.from(document.querySelectorAll('button')).find(b => b.textContent?.includes('写回答')); if (btn) { btn.click(); return 'editor-opened'; } return 'no-answer-btn'; })()\"",
111
+ "opencli operate wait time 2",
112
+ "opencli operate eval \"(() => { const editor = document.querySelector('.ql-editor') || document.querySelector('[contenteditable=\\\"true\\\"]'); if (!editor) return 'no-editor'; editor.focus(); editor.innerHTML = '<p>[AutoTest] 这是一个 OpenCLI 发文测试,时间戳: ' + Date.now() + '</p><p>这段内容用于验证 operate 命令链的完整性。</p>'; editor.dispatchEvent(new Event('input', { bubbles: true })); return 'filled'; })()\"",
113
+ "opencli operate wait time 1",
114
+ "opencli operate eval \"(document.querySelector('.ql-editor') || document.querySelector('[contenteditable=\\\"true\\\"]'))?.textContent || ''\""
115
+ ],
116
+ "judge": {
117
+ "type": "contains",
118
+ "value": "AutoTest"
119
+ },
120
+ "note": "5-step: navigate to question → click '写回答' → find editor → fill rich content (title + body) → verify"
121
+ },
122
+ {
123
+ "name": "zhihu-fill-article",
124
+ "platform": "zhihu",
125
+ "type": "fill-only",
126
+ "description": "Navigate to zhihu article editor (zhuanlan), fill title + body (no publish)",
127
+ "steps": [
128
+ "opencli operate open https://zhuanlan.zhihu.com/write",
129
+ "opencli operate wait time 3",
130
+ "opencli operate eval \"(() => { const ta = document.querySelector('.WriteIndex-titleInput textarea') || document.querySelector('textarea[placeholder]'); if (!ta) return 'no-title-input'; ta.focus(); var nativeSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; nativeSetter.call(ta, '[AutoTest] OpenCLI 发文能力验证 ' + Date.now()); ta.dispatchEvent(new Event('input', { bubbles: true })); return 'title-filled'; })()\"",
131
+ "opencli operate wait time 1",
132
+ "opencli operate eval \"(() => { const editor = document.querySelector('[contenteditable=true]'); if (!editor) return 'no-editor'; editor.focus(); editor.innerHTML = '<p>这是 OpenCLI autoresearch 发文测试集的一部分。</p><p>测试链路:导航 → 填写标题 → 填写正文 → 验证内容。</p><p>时间戳: ' + Date.now() + '</p>'; editor.dispatchEvent(new Event('input', { bubbles: true })); return 'body-filled'; })()\"",
133
+ "opencli operate wait time 1",
134
+ "opencli operate eval \"(() => { const title = (document.querySelector('.WriteIndex-titleInput textarea') || document.querySelector('textarea[placeholder]'))?.value || ''; const body = document.querySelector('[contenteditable=true]')?.textContent || ''; return JSON.stringify({ title: title.slice(0, 50), body: body.slice(0, 50) }); })()\""
135
+ ],
136
+ "judge": {
137
+ "type": "contains",
138
+ "value": "AutoTest"
139
+ },
140
+ "note": "5-step: navigate to zhuanlan editor → fill title textarea → fill rich text body → verify both title and body content"
141
+ },
142
+ {
143
+ "name": "zhihu-read-hn-fill-answer",
144
+ "platform": "zhihu",
145
+ "type": "fill-only",
146
+ "description": "Read HN top story, then navigate to zhihu question and fill an answer about it",
147
+ "steps": [
148
+ "opencli operate open https://news.ycombinator.com",
149
+ "opencli operate wait time 2",
150
+ "opencli operate eval \"(() => { const a = document.querySelector('.titleline a'); return a ? a.textContent?.trim() : 'no-title'; })()\"",
151
+ "opencli operate open https://www.zhihu.com/question/19550225",
152
+ "opencli operate wait time 3",
153
+ "opencli operate eval \"(() => { const btn = Array.from(document.querySelectorAll('button')).find(b => b.textContent?.includes('写回答')) || document.querySelector('[data-zop-retarget=\\\"answer\\\"]'); if (btn) { btn.click(); return 'editor-opened'; } return 'no-answer-btn'; })()\"",
154
+ "opencli operate wait time 2",
155
+ "opencli operate eval \"(() => { const editor = document.querySelector('.ql-editor') || document.querySelector('[contenteditable=true]'); if (!editor) return 'no-editor'; editor.focus(); editor.innerHTML = '<p>[AutoTest] 分享一个来自 Hacker News 的有趣内容</p><p>这是一个跨平台内容搬运测试,时间戳: ' + Date.now() + '</p>'; editor.dispatchEvent(new Event('input', { bubbles: true })); return 'filled'; })()\"",
156
+ "opencli operate wait time 1",
157
+ "opencli operate eval \"(document.querySelector('.ql-editor') || document.querySelector('[contenteditable=true]'))?.textContent || ''\""
158
+ ],
159
+ "judge": {
160
+ "type": "contains",
161
+ "value": "AutoTest"
162
+ },
163
+ "note": "8-step cross-site chain: read HN title → navigate zhihu question → click 写回答 → fill answer with HN content → verify"
164
+ },
165
+ {
166
+ "name": "twitter-thread-compose",
167
+ "platform": "twitter",
168
+ "type": "fill-only",
169
+ "description": "Navigate to compose, type first tweet, add thread tweet, type second tweet, verify both",
170
+ "steps": [
171
+ "opencli operate open https://x.com/compose/tweet",
172
+ "opencli operate wait time 3",
173
+ "opencli operate eval \"document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]') ? 'composer-ready' : 'not-found'\"",
174
+ "opencli operate eval \"(() => { const box = document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]'); if (!box) return 'no-box'; box.focus(); const dt = new DataTransfer(); dt.setData('text/plain', '[AutoTest] Thread tweet 1 - ' + Date.now()); box.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); return 'first-filled'; })()\"",
175
+ "opencli operate wait time 1",
176
+ "opencli operate eval \"(() => { const addBtn = document.querySelector('[data-testid=\\\"addButton\\\"]') || document.querySelector('[aria-label=\\\"Add post\\\"]'); if (addBtn) { addBtn.click(); return 'thread-added'; } return 'no-add-btn'; })()\"",
177
+ "opencli operate wait time 2",
178
+ "opencli operate eval \"(() => { const boxes = document.querySelectorAll('[data-testid=\\\"tweetTextarea_0\\\"]'); const box = boxes[boxes.length - 1]; if (!box) return 'no-second-box'; box.focus(); const dt = new DataTransfer(); dt.setData('text/plain', '[AutoTest] Thread tweet 2 - continuation'); box.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); return 'second-filled'; })()\"",
179
+ "opencli operate wait time 1",
180
+ "opencli operate eval \"(() => { const boxes = document.querySelectorAll('[data-testid=\\\"tweetTextarea_0\\\"]'); const t1 = boxes[0]?.textContent || ''; const t2 = boxes[boxes.length - 1]?.textContent || ''; return JSON.stringify({ tweet1: t1, tweet2: t2 }); })()\""
181
+ ],
182
+ "judge": {
183
+ "type": "contains",
184
+ "value": "Thread tweet 2"
185
+ },
186
+ "note": "10-step thread compose: open composer → fill tweet 1 → click add thread → fill tweet 2 → verify both tweets present"
187
+ },
188
+ {
189
+ "name": "twitter-quote-retweet-fill",
190
+ "platform": "twitter",
191
+ "type": "fill-only",
192
+ "description": "Navigate to home, find first tweet, open retweet menu, select Quote, fill quote text, verify",
193
+ "steps": [
194
+ "opencli operate open https://x.com/home",
195
+ "opencli operate wait time 3",
196
+ "opencli operate eval \"(() => { const tweet = document.querySelector('[data-testid=\\\"tweet\\\"]'); if (!tweet) return 'no-tweet'; const retweet = tweet.querySelector('[data-testid=\\\"retweet\\\"]'); if (retweet) { retweet.click(); return 'retweet-menu-opened'; } return 'no-retweet-btn'; })()\"",
197
+ "opencli operate wait time 1",
198
+ "opencli operate eval \"(() => { const items = document.querySelectorAll('[role=\\\"menuitem\\\"]'); for (const item of items) { if (item.textContent?.includes('Quote') || item.textContent?.includes('引用')) { item.click(); return 'quote-selected'; } } return 'no-quote-option'; })()\"",
199
+ "opencli operate wait time 2",
200
+ "opencli operate eval \"(() => { const box = document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]'); if (!box) return 'no-box'; box.focus(); const dt = new DataTransfer(); dt.setData('text/plain', '[AutoTest] Quote retweet test ' + Date.now()); box.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); return 'quote-filled'; })()\"",
201
+ "opencli operate wait time 1",
202
+ "opencli operate eval \"document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]')?.textContent || ''\""
203
+ ],
204
+ "judge": {
205
+ "type": "contains",
206
+ "value": "Quote retweet test"
207
+ },
208
+ "note": "8-step quote retweet: home → find tweet → click retweet → select Quote → fill quote text → verify"
209
+ },
210
+ {
211
+ "name": "twitter-search-then-reply-fill",
212
+ "platform": "twitter",
213
+ "type": "fill-only",
214
+ "description": "Search 'opencli' on twitter, find first result, click reply, fill reply text, verify",
215
+ "steps": [
216
+ "opencli operate open https://x.com/search?q=opencli&src=typed_query&f=live",
217
+ "opencli operate wait time 4",
218
+ "opencli operate eval \"(() => { const tweets = document.querySelectorAll('[data-testid=\\\"tweet\\\"]'); if (tweets.length === 0) return 'no-results'; return 'found-' + tweets.length + '-results'; })()\"",
219
+ "opencli operate eval \"(() => { const tweet = document.querySelector('[data-testid=\\\"tweet\\\"]'); if (!tweet) return 'no-tweet'; const reply = tweet.querySelector('[data-testid=\\\"reply\\\"]'); if (reply) { reply.click(); return 'reply-clicked'; } return 'no-reply-btn'; })()\"",
220
+ "opencli operate wait time 2",
221
+ "opencli operate eval \"(() => { const box = document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]'); if (!box) return 'no-box'; box.focus(); const dt = new DataTransfer(); dt.setData('text/plain', '[AutoTest] Reply from search result ' + Date.now()); box.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); return 'reply-filled'; })()\"",
222
+ "opencli operate wait time 1",
223
+ "opencli operate eval \"document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]')?.textContent || ''\""
224
+ ],
225
+ "judge": {
226
+ "type": "contains",
227
+ "value": "Reply from search"
228
+ },
229
+ "note": "8-step search-then-reply: navigate to search URL → verify results → click reply on first → fill reply → verify"
230
+ },
231
+ {
232
+ "name": "zhihu-search-then-fill-answer",
233
+ "platform": "zhihu",
234
+ "type": "fill-only",
235
+ "description": "Search 'AI agent' on zhihu, click first question result, click 写回答, fill answer, verify",
236
+ "steps": [
237
+ "opencli operate open https://www.zhihu.com/search?type=content&q=AI%20agent",
238
+ "opencli operate wait time 4",
239
+ "opencli operate eval \"(() => { const links = document.querySelectorAll('a[href*=\\\"/question/\\\"]'); if (links.length === 0) return 'no-question-links'; const link = links[0]; const href = link.getAttribute('href'); return 'found: ' + href; })()\"",
240
+ "opencli operate eval \"(() => { const links = document.querySelectorAll('a[href*=\\\"/question/\\\"]'); if (links.length === 0) return 'no-links'; const link = links[0]; const href = link.getAttribute('href'); const match = href.match(/\\\\/question\\\\/(\\\\d+)/); if (match) { window.location.href = 'https://www.zhihu.com/question/' + match[1]; return 'navigating-to-question'; } link.click(); return 'clicked-link'; })()\"",
241
+ "opencli operate wait time 4",
242
+ "opencli operate eval \"(() => { const btn = document.querySelector('[data-zop-retarget=\\\"answer\\\"]') || Array.from(document.querySelectorAll('button')).find(b => b.textContent?.includes('写回答')) || Array.from(document.querySelectorAll('a')).find(a => a.textContent?.includes('写回答')); if (btn) { btn.click(); return 'editor-opened'; } return 'no-answer-btn'; })()\"",
243
+ "opencli operate wait time 2",
244
+ "opencli operate eval \"(() => { const editor = document.querySelector('.ql-editor') || document.querySelector('[contenteditable=\\\"true\\\"]'); if (!editor) return 'no-editor'; editor.focus(); editor.innerHTML = '<p>[AutoTest] AI agent 搜索后回答测试 ' + Date.now() + '</p><p>这是通过搜索 → 进入问题 → 填写回答的完整链路测试。</p>'; editor.dispatchEvent(new Event('input', { bubbles: true })); return 'filled'; })()\"",
245
+ "opencli operate wait time 1",
246
+ "opencli operate eval \"(document.querySelector('.ql-editor') || document.querySelector('[contenteditable=\\\"true\\\"]'))?.textContent || ''\""
247
+ ],
248
+ "judge": {
249
+ "type": "contains",
250
+ "value": "AutoTest"
251
+ },
252
+ "note": "9-step search-then-answer: search zhihu → find question link → navigate → click 写回答 → fill answer → verify"
253
+ },
254
+ {
255
+ "name": "zhihu-read-question-fill-comment",
256
+ "platform": "zhihu",
257
+ "type": "fill-only",
258
+ "description": "Navigate to question page, scroll to first answer, click comment, fill comment text, verify",
259
+ "steps": [
260
+ "opencli operate open https://www.zhihu.com/question/19550225",
261
+ "opencli operate wait time 3",
262
+ "opencli operate eval \"(() => { const answer = document.querySelector('[data-testid=\\\"answer\\\"]') || document.querySelector('.AnswerItem') || document.querySelector('.List-item'); if (answer) { answer.scrollIntoView({ behavior: 'smooth', block: 'center' }); return 'answer-scrolled'; } return 'no-answer'; })()\"",
263
+ "opencli operate wait time 2",
264
+ "opencli operate eval \"(() => { const commentBtns = document.querySelectorAll('button'); for (const btn of commentBtns) { if (btn.textContent?.match(/评论|条评论|comment/i)) { btn.click(); return 'comment-opened: ' + btn.textContent.trim(); } } const commentIcons = document.querySelectorAll('[data-testid=\\\"comment\\\"]') || []; for (const icon of commentIcons) { icon.click(); return 'comment-icon-clicked'; } return 'no-comment-btn'; })()\"",
265
+ "opencli operate wait time 2",
266
+ "opencli operate eval \"(() => { const editor = document.querySelector('.CommentEditor textarea') || document.querySelector('[placeholder*=\\\"评论\\\"]') || document.querySelector('[placeholder*=\\\"comment\\\"]') || document.querySelector('.ql-editor') || document.querySelector('[contenteditable=\\\"true\\\"]'); if (!editor) return 'no-comment-editor'; editor.focus(); if (editor.tagName === 'TEXTAREA' || editor.tagName === 'INPUT') { editor.value = '[AutoTest] 评论测试 ' + Date.now(); editor.dispatchEvent(new Event('input', { bubbles: true })); } else { editor.innerHTML = '<p>[AutoTest] 评论测试 ' + Date.now() + '</p>'; editor.dispatchEvent(new Event('input', { bubbles: true })); } return 'comment-filled'; })()\"",
267
+ "opencli operate wait time 1",
268
+ "opencli operate eval \"(() => { const editor = document.querySelector('.CommentEditor textarea') || document.querySelector('[placeholder*=\\\"评论\\\"]') || document.querySelector('[placeholder*=\\\"comment\\\"]') || document.querySelector('.ql-editor') || document.querySelector('[contenteditable=\\\"true\\\"]'); if (!editor) return 'no-editor'; return editor.value || editor.textContent || ''; })()\""
269
+ ],
270
+ "judge": {
271
+ "type": "contains",
272
+ "value": "AutoTest"
273
+ },
274
+ "note": "8-step comment chain: navigate question → scroll to answer → click comment → fill comment text → verify"
275
+ },
276
+ {
277
+ "name": "zhihu-article-with-formatting",
278
+ "platform": "zhihu",
279
+ "type": "fill-only",
280
+ "description": "Navigate to zhuanlan editor, fill title, fill body with multiple paragraphs and bold text, verify",
281
+ "steps": [
282
+ "opencli operate open https://zhuanlan.zhihu.com/write",
283
+ "opencli operate wait time 3",
284
+ "opencli operate eval \"(() => { const titleInput = document.querySelector('.WriteIndex-titleInput textarea') || document.querySelector('textarea[placeholder*=\\\"标题\\\"]'); if (!titleInput) return 'no-title-input'; titleInput.focus(); titleInput.value = '[AutoTest] 格式化文章测试 ' + Date.now(); titleInput.dispatchEvent(new Event('input', { bubbles: true })); return 'title-filled'; })()\"",
285
+ "opencli operate wait time 1",
286
+ "opencli operate eval \"(() => { const editor = document.querySelector('.ql-editor') || document.querySelector('[contenteditable=\\\"true\\\"]'); if (!editor) return 'no-editor'; editor.focus(); editor.innerHTML = '<p>这是第一段:OpenCLI 格式化发文测试。</p><p><strong>[AutoTest-Bold] 这是加粗的第二段,用于验证富文本格式。</strong></p><p>这是第三段,包含普通文本内容,时间戳: ' + Date.now() + '。</p><p>这是第四段,测试多段落填充能力。</p>'; editor.dispatchEvent(new Event('input', { bubbles: true })); return 'body-filled-with-formatting'; })()\"",
287
+ "opencli operate wait time 1",
288
+ "opencli operate eval \"(() => { const editor = document.querySelector('.ql-editor') || document.querySelector('[contenteditable=\\\"true\\\"]'); if (!editor) return 'no-editor'; const hasBold = editor.querySelector('strong') || editor.querySelector('b'); const paragraphs = editor.querySelectorAll('p'); return JSON.stringify({ paragraphCount: paragraphs.length, hasBold: !!hasBold, preview: editor.textContent?.slice(0, 80) }); })()\"",
289
+ "opencli operate wait time 1",
290
+ "opencli operate eval \"(() => { const title = (document.querySelector('.WriteIndex-titleInput textarea') || document.querySelector('textarea[placeholder*=\\\"标题\\\"]'))?.value || ''; const body = (document.querySelector('.ql-editor') || document.querySelector('[contenteditable=\\\"true\\\"]'))?.textContent || ''; return JSON.stringify({ title: title.slice(0, 60), bodyHasBold: body.includes('AutoTest-Bold'), bodyLength: body.length }); })()\""
291
+ ],
292
+ "judge": {
293
+ "type": "contains",
294
+ "value": "AutoTest"
295
+ },
296
+ "note": "8-step formatted article: navigate editor → fill title → fill body with <strong> bold + 4 paragraphs → verify formatting + content"
297
+ },
298
+ {
299
+ "name": "cross-zhihu-to-twitter",
300
+ "platform": "cross",
301
+ "type": "fill-only",
302
+ "description": "Read zhihu hot topic title, navigate to twitter compose, fill tweet with zhihu content, verify",
303
+ "steps": [
304
+ "opencli operate open https://www.zhihu.com/hot",
305
+ "opencli operate wait time 3",
306
+ "opencli operate eval \"(() => { const hotItem = document.querySelector('.HotItem-content a') || document.querySelector('.HotList-item a') || document.querySelector('[data-testid=\\\"hot-item\\\"] a') || document.querySelector('.HotItem a'); if (hotItem) return hotItem.textContent?.trim()?.slice(0, 60) || 'no-text'; const titles = document.querySelectorAll('h2'); for (const t of titles) { if (t.textContent?.trim().length > 5) return t.textContent.trim().slice(0, 60); } return 'no-hot-topic'; })()\"",
307
+ "opencli operate state save zhihu_hot_title",
308
+ "opencli operate open https://x.com/compose/tweet",
309
+ "opencli operate wait time 3",
310
+ "opencli operate eval \"document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]') ? 'composer-ready' : 'not-found'\"",
311
+ "opencli operate eval \"(() => { const box = document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]'); if (!box) return 'no-box'; box.focus(); const dt = new DataTransfer(); dt.setData('text/plain', '[AutoTest] Zhihu热榜话题搬运: 知乎上正在热议的话题 - ' + Date.now()); box.dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true })); return 'tweet-filled-with-zhihu'; })()\"",
312
+ "opencli operate wait time 1",
313
+ "opencli operate eval \"document.querySelector('[data-testid=\\\"tweetTextarea_0\\\"]')?.textContent || ''\""
314
+ ],
315
+ "judge": {
316
+ "type": "contains",
317
+ "value": "Zhihu热榜话题搬运"
318
+ },
319
+ "note": "10-step cross-platform: read zhihu hot → save state → navigate twitter compose → fill tweet with zhihu content → verify"
320
+ },
321
+ {
322
+ "name": "cross-twitter-to-zhihu",
323
+ "platform": "cross",
324
+ "type": "fill-only",
325
+ "description": "Read twitter trending/explore topic, navigate to zhihu zhuanlan editor, fill title and body, verify",
326
+ "steps": [
327
+ "opencli operate open https://x.com/explore/tabs/trending",
328
+ "opencli operate wait time 3",
329
+ "opencli operate eval \"(() => { const trends = document.querySelectorAll('[data-testid=\\\"trend\\\"]'); if (trends.length > 0) { const first = trends[0]; return first.textContent?.trim()?.slice(0, 80) || 'no-text'; } const spans = document.querySelectorAll('span'); for (const s of spans) { if (s.textContent?.startsWith('#') || s.textContent?.includes('Trending')) { return s.textContent.trim().slice(0, 80); } } return 'no-trending-topic'; })()\"",
330
+ "opencli operate state save twitter_trending",
331
+ "opencli operate open https://zhuanlan.zhihu.com/write",
332
+ "opencli operate wait time 3",
333
+ "opencli operate eval \"(() => { const titleInput = document.querySelector('.WriteIndex-titleInput textarea') || document.querySelector('textarea[placeholder*=\\\"标题\\\"]'); if (!titleInput) return 'no-title-input'; titleInput.focus(); titleInput.value = '[AutoTest] Twitter热点搬运: 来自推特的热门话题 ' + Date.now(); titleInput.dispatchEvent(new Event('input', { bubbles: true })); return 'title-filled'; })()\"",
334
+ "opencli operate wait time 1",
335
+ "opencli operate eval \"(() => { const editor = document.querySelector('.ql-editor') || document.querySelector('[contenteditable=\\\"true\\\"]'); if (!editor) return 'no-editor'; editor.focus(); editor.innerHTML = '<p>[AutoTest] 这篇文章搬运自 Twitter 热门话题。</p><p>Twitter 上正在讨论的热门话题为大家带来了新的视角和思考。</p><p>时间戳: ' + Date.now() + '</p>'; editor.dispatchEvent(new Event('input', { bubbles: true })); return 'body-filled'; })()\"",
336
+ "opencli operate wait time 1",
337
+ "opencli operate eval \"(() => { const title = (document.querySelector('.WriteIndex-titleInput textarea') || document.querySelector('textarea[placeholder*=\\\"标题\\\"]'))?.value || ''; const body = (document.querySelector('.ql-editor') || document.querySelector('[contenteditable=\\\"true\\\"]'))?.textContent || ''; return JSON.stringify({ title: title.slice(0, 60), body: body.slice(0, 60) }); })()\""
338
+ ],
339
+ "judge": {
340
+ "type": "contains",
341
+ "value": "Twitter热点搬运"
342
+ },
343
+ "note": "10-step cross-platform reverse: read twitter trending → save state → navigate zhihu editor → fill title + body → verify"
344
+ }
345
+ ]
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env bash
2
+ # Layer 4: Save as CLI — test the full save pipeline
3
+ # Tests: operate init → write adapter → operate verify
4
+ set -euo pipefail
5
+ cd "$(dirname "$0")/.."
6
+
7
+ echo "=== Layer 4: Save as CLI ==="
8
+ echo "Testing: init → write → verify pipeline"
9
+ echo ""
10
+
11
+ npx tsx autoresearch/eval-save.ts "$@"
@@ -0,0 +1,64 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+
3
+ cli({
4
+ site: 'test-xhs',
5
+ name: 'explore-deep',
6
+ description: '小红书探索页深度提取 + 去重 + 按互动排序',
7
+ domain: 'www.xiaohongshu.com',
8
+ strategy: Strategy.COOKIE,
9
+ browser: true,
10
+ args: [
11
+ { name: 'limit', type: 'int', default: 15, help: 'Number of items' },
12
+ ],
13
+ columns: ['rank', 'title', 'author', 'likes', 'url'],
14
+ func: async (page, kwargs) => {
15
+ const limit = kwargs.limit ?? 15;
16
+ // Step 1: Navigate to explore page
17
+ await page.goto('https://www.xiaohongshu.com/explore');
18
+ // Step 2: Wait for initial content via MutationObserver
19
+ await page.evaluate(`new Promise(function(resolve) {
20
+ var check = function() { return document.querySelectorAll('section.note-item').length > 0; };
21
+ if (check()) return resolve(true);
22
+ var observer = new MutationObserver(function(m, obs) { if (check()) { obs.disconnect(); resolve(true); } });
23
+ observer.observe(document.body, { childList: true, subtree: true });
24
+ setTimeout(function() { observer.disconnect(); resolve(false); }, 8000);
25
+ })`);
26
+ // Step 3: Multi-round adaptive scroll (early stop when no new content)
27
+ let prevCount = 0;
28
+ for (let round = 0; round < 5; round++) {
29
+ await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
30
+ await page.wait(1.5);
31
+ const count = await page.evaluate('document.querySelectorAll("section.note-item").length') as number;
32
+ if (count >= limit * 2 || count === prevCount) break;
33
+ prevCount = count;
34
+ }
35
+ // Step 4: Extract with noteId deduplication + parse likes as integers
36
+ const result = await page.evaluate(`(function() {
37
+ var seen = {};
38
+ var items = [];
39
+ document.querySelectorAll('section.note-item').forEach(function(el) {
40
+ var linkEl = el.querySelector('a[href]');
41
+ var href = linkEl ? linkEl.getAttribute('href') || '' : '';
42
+ var m = href.match(/explore\\/([a-f0-9]+)/);
43
+ var noteId = m ? m[1] : '';
44
+ if (!noteId || seen[noteId]) return;
45
+ seen[noteId] = true;
46
+ var titleEl = el.querySelector('.title span') || el.querySelector('a.title');
47
+ var authorEl = el.querySelector('.author-wrapper .name') || el.querySelector('.author .name');
48
+ var likesEl = el.querySelector('.like-wrapper .count') || el.querySelector('.interact-container .count');
49
+ var title = (titleEl ? titleEl.textContent || '' : '').trim();
50
+ var author = (authorEl ? authorEl.textContent || '' : '').trim();
51
+ var likesRaw = (likesEl ? likesEl.textContent || '0' : '0').trim();
52
+ var likes = parseInt(likesRaw.replace(/[^0-9]/g, '')) || 0;
53
+ items.push({ title: title, author: author, likes: likes, url: 'https://www.xiaohongshu.com/explore/' + noteId });
54
+ });
55
+ return items;
56
+ })()`);
57
+ // Step 5: Sort by likes descending
58
+ const sorted = (result as any[] || []).sort((a: any, b: any) => b.likes - a.likes);
59
+ // Step 6: Slice and format
60
+ return sorted.slice(0, limit).map((item: any, i: number) => ({
61
+ rank: i + 1, title: item.title, author: item.author, likes: String(item.likes), url: item.url,
62
+ }));
63
+ },
64
+ });
@@ -0,0 +1,61 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+
3
+ cli({
4
+ site: 'test-xhs',
5
+ name: 'note-comments',
6
+ description: '小红书笔记详情 + 评论(多步合并输出)',
7
+ domain: 'www.xiaohongshu.com',
8
+ strategy: Strategy.COOKIE,
9
+ browser: true,
10
+ args: [
11
+ { name: 'id', type: 'string', default: '6745a82f000000000800b6ed', positional: true, help: 'Note ID' },
12
+ { name: 'limit', type: 'int', default: 5, help: 'Max comments' },
13
+ ],
14
+ columns: ['section', 'title', 'author', 'likes', 'text'],
15
+ func: async (page, kwargs) => {
16
+ const noteId = kwargs.id ?? '6745a82f000000000800b6ed';
17
+ const commentLimit = kwargs.limit ?? 5;
18
+ // Step 1: Navigate to note detail page
19
+ await page.goto('https://www.xiaohongshu.com/explore/' + noteId);
20
+ await page.wait(3);
21
+ // Step 2: Extract note metadata (title, author, likes)
22
+ const meta = await page.evaluate(`(function() {
23
+ return {
24
+ title: (document.querySelector('#detail-title') || document.querySelector('.title') || {}).textContent?.trim() || '',
25
+ author: (document.querySelector('.author-container .username') || document.querySelector('.user-nickname') || {}).textContent?.trim() || '',
26
+ likes: (document.querySelector('[data-type="like"] .count') || document.querySelector('.like-wrapper .count') || {}).textContent?.trim() || '0',
27
+ };
28
+ })()`) as any;
29
+ // Step 3: Scroll the note container to trigger comment loading
30
+ for (let i = 0; i < 3; i++) {
31
+ await page.evaluate(`(function() {
32
+ var scroller = document.querySelector('.note-scroller') || document.querySelector('.container');
33
+ if (scroller && scroller.scrollTo) { scroller.scrollTo(0, 99999); } else { window.scrollTo(0, document.body.scrollHeight); }
34
+ })()`);
35
+ await page.wait(1);
36
+ }
37
+ // Step 4: Extract comments from DOM
38
+ const comments = await page.evaluate(`(function() {
39
+ var results = [];
40
+ var commentEls = document.querySelectorAll('.parent-comment, .comment-item-root');
41
+ commentEls.forEach(function(el) {
42
+ var item = el.querySelector('.comment-item') || el.querySelector('.comment-inner');
43
+ if (!item) return;
44
+ var authorEl = item.querySelector('.author-wrapper .name') || item.querySelector('.user-name');
45
+ var textEl = item.querySelector('.content') || item.querySelector('.note-text');
46
+ var likesEl = item.querySelector('.count');
47
+ var author = (authorEl ? authorEl.textContent || '' : '').trim();
48
+ var text = (textEl ? textEl.textContent || '' : '').replace(/\\s+/g, ' ').trim();
49
+ var likes = (likesEl ? likesEl.textContent || '0' : '0').trim();
50
+ if (text) results.push({ author: author, text: text.slice(0, 80), likes: likes });
51
+ });
52
+ return results;
53
+ })()`) as any[];
54
+ // Step 5: Merge note meta + comments into unified output
55
+ const rows: any[] = [{ section: 'note', title: meta.title, author: meta.author, likes: meta.likes, text: '' }];
56
+ for (const c of (comments || []).slice(0, commentLimit)) {
57
+ rows.push({ section: 'comment', title: '', author: c.author, likes: c.likes, text: c.text });
58
+ }
59
+ return rows;
60
+ },
61
+ });
@@ -0,0 +1,62 @@
1
+ import { cli, Strategy } from '@jackwener/opencli/registry';
2
+
3
+ cli({
4
+ site: 'test-xhs',
5
+ name: 'search-full',
6
+ description: '小红书搜索 + 滚动加载 + 去重',
7
+ domain: 'www.xiaohongshu.com',
8
+ strategy: Strategy.COOKIE,
9
+ browser: true,
10
+ args: [
11
+ { name: 'query', type: 'string', default: '咖啡', positional: true, help: 'Search query' },
12
+ { name: 'limit', type: 'int', default: 10, help: 'Number of results' },
13
+ ],
14
+ columns: ['rank', 'title', 'author', 'likes', 'url'],
15
+ func: async (page, kwargs) => {
16
+ const query = encodeURIComponent(kwargs.query ?? '咖啡');
17
+ const limit = kwargs.limit ?? 10;
18
+ // Step 1: Navigate to search page
19
+ await page.goto('https://www.xiaohongshu.com/search_result?keyword=' + query + '&source=web_search_result_notes');
20
+ // Step 2: Wait for async render via MutationObserver
21
+ await page.evaluate(`new Promise(function(resolve) {
22
+ var check = function() { return document.querySelectorAll('section.note-item').length > 0; };
23
+ if (check()) return resolve(true);
24
+ var observer = new MutationObserver(function(m, obs) { if (check()) { obs.disconnect(); resolve(true); } });
25
+ observer.observe(document.body, { childList: true, subtree: true });
26
+ setTimeout(function() { observer.disconnect(); resolve(false); }, 8000);
27
+ })`);
28
+ // Step 3: Scroll 3x to load more content
29
+ for (let i = 0; i < 3; i++) {
30
+ await page.evaluate('window.scrollTo(0, document.body.scrollHeight)');
31
+ await page.wait(1);
32
+ }
33
+ // Step 4: Extract from DOM with deduplication
34
+ const result = await page.evaluate(`(function() {
35
+ var seen = {};
36
+ var items = [];
37
+ document.querySelectorAll('section.note-item').forEach(function(el) {
38
+ var linkEl = el.querySelector('a[href]');
39
+ var href = linkEl ? linkEl.getAttribute('href') || '' : '';
40
+ var m = href.match(/explore\\/([a-f0-9]+)/);
41
+ var noteId = m ? m[1] : href;
42
+ if (!noteId || seen[noteId]) return;
43
+ seen[noteId] = true;
44
+ var titleEl = el.querySelector('.title span') || el.querySelector('a.title');
45
+ var authorEl = el.querySelector('.author-wrapper .name') || el.querySelector('.author .name');
46
+ var likesEl = el.querySelector('.like-wrapper .count') || el.querySelector('.interact-container .count');
47
+ if (titleEl) {
48
+ items.push({
49
+ title: (titleEl.textContent || '').trim(),
50
+ author: (authorEl ? authorEl.textContent || '' : '').trim(),
51
+ likes: (likesEl ? likesEl.textContent || '0' : '0').trim(),
52
+ url: 'https://www.xiaohongshu.com' + href,
53
+ });
54
+ }
55
+ });
56
+ return items;
57
+ })()`);
58
+ return (result as any[]).slice(0, limit).map((item: any, i: number) => ({
59
+ rank: i + 1, title: item.title, author: item.author, likes: item.likes, url: item.url,
60
+ }));
61
+ },
62
+ });