@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 @@
1
+ import './reel.js';
@@ -0,0 +1,167 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { ArgumentError } from '../../errors.js';
6
+ import { getRegistry } from '../../registry.js';
7
+ import './reel.js';
8
+ const tempDirs = [];
9
+ function createTempVideo(name = 'demo.mp4', bytes = Buffer.from('video')) {
10
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-instagram-reel-'));
11
+ tempDirs.push(dir);
12
+ const filePath = path.join(dir, name);
13
+ fs.writeFileSync(filePath, bytes);
14
+ return filePath;
15
+ }
16
+ function createPageMock(evaluateResults, overrides = {}) {
17
+ const evaluate = vi.fn();
18
+ for (const result of evaluateResults) {
19
+ evaluate.mockResolvedValueOnce(result);
20
+ }
21
+ return {
22
+ goto: vi.fn().mockResolvedValue(undefined),
23
+ evaluate,
24
+ getCookies: vi.fn().mockResolvedValue([]),
25
+ snapshot: vi.fn().mockResolvedValue(undefined),
26
+ click: vi.fn().mockResolvedValue(undefined),
27
+ typeText: vi.fn().mockResolvedValue(undefined),
28
+ pressKey: vi.fn().mockResolvedValue(undefined),
29
+ scrollTo: vi.fn().mockResolvedValue(undefined),
30
+ getFormState: vi.fn().mockResolvedValue({ forms: [], orphanFields: [] }),
31
+ wait: vi.fn().mockResolvedValue(undefined),
32
+ tabs: vi.fn().mockResolvedValue([]),
33
+ closeTab: vi.fn().mockResolvedValue(undefined),
34
+ newTab: vi.fn().mockResolvedValue(undefined),
35
+ selectTab: vi.fn().mockResolvedValue(undefined),
36
+ networkRequests: vi.fn().mockResolvedValue([]),
37
+ consoleMessages: vi.fn().mockResolvedValue([]),
38
+ scroll: vi.fn().mockResolvedValue(undefined),
39
+ autoScroll: vi.fn().mockResolvedValue(undefined),
40
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
41
+ getInterceptedRequests: vi.fn().mockResolvedValue([]),
42
+ waitForCapture: vi.fn().mockResolvedValue(undefined),
43
+ screenshot: vi.fn().mockResolvedValue(''),
44
+ setFileInput: vi.fn().mockResolvedValue(undefined),
45
+ insertText: vi.fn().mockResolvedValue(undefined),
46
+ getCurrentUrl: vi.fn().mockResolvedValue(null),
47
+ ...overrides,
48
+ };
49
+ }
50
+ afterAll(() => {
51
+ for (const dir of tempDirs) {
52
+ fs.rmSync(dir, { recursive: true, force: true });
53
+ }
54
+ });
55
+ describe('instagram reel registration', () => {
56
+ beforeEach(() => {
57
+ vi.restoreAllMocks();
58
+ });
59
+ afterEach(() => {
60
+ vi.restoreAllMocks();
61
+ });
62
+ it('registers the reel command with a required-value video arg', () => {
63
+ const cmd = getRegistry().get('instagram/reel');
64
+ expect(cmd).toBeDefined();
65
+ expect(cmd?.browser).toBe(true);
66
+ expect(cmd?.args.some((arg) => arg.name === 'video' && !arg.required && arg.valueRequired)).toBe(true);
67
+ expect(cmd?.args.some((arg) => arg.name === 'content' && arg.positional && !arg.required)).toBe(true);
68
+ });
69
+ it('rejects missing --video before browser work', async () => {
70
+ const page = createPageMock([]);
71
+ const cmd = getRegistry().get('instagram/reel');
72
+ await expect(cmd.func(page, { content: 'hello reel' })).rejects.toThrow(ArgumentError);
73
+ expect(page.goto).not.toHaveBeenCalled();
74
+ });
75
+ it('rejects unsupported video formats', async () => {
76
+ const videoPath = createTempVideo('demo.mov');
77
+ const page = createPageMock([]);
78
+ const cmd = getRegistry().get('instagram/reel');
79
+ await expect(cmd.func(page, { video: videoPath })).rejects.toThrow('Unsupported video format');
80
+ expect(page.goto).not.toHaveBeenCalled();
81
+ });
82
+ it('uploads a reel video without caption and shares it', async () => {
83
+ const videoPath = createTempVideo();
84
+ const page = createPageMock([
85
+ { ok: false }, // dismiss residual dialogs
86
+ { ok: true }, // ensure composer open
87
+ { ok: true }, // composer upload input ready
88
+ { ok: true, selectors: ['[data-opencli-reel-upload-index="0"]', '[data-opencli-reel-upload-index="1"]'] }, // resolve upload selector
89
+ { count: 1 }, // file bound to input
90
+ { state: 'preview', detail: 'Crop Back Next' }, // preview detected
91
+ { ok: true, label: 'OK' }, // dismiss reels nux
92
+ { ok: true, label: 'Next' }, // move from crop to edit
93
+ { state: 'edit' }, // edit stage
94
+ { ok: true, label: 'Next' }, // move from edit to composer
95
+ { state: 'composer' }, // composer stage
96
+ { ok: true, label: 'Share' }, // share
97
+ { ok: true, url: 'https://www.instagram.com/reel/REEL123/' }, // success
98
+ ]);
99
+ const cmd = getRegistry().get('instagram/reel');
100
+ const result = await cmd.func(page, { video: videoPath });
101
+ expect(page.setFileInput).toHaveBeenCalledWith([videoPath], '[data-opencli-reel-upload-index="0"]');
102
+ expect(page.insertText).not.toHaveBeenCalled();
103
+ expect(result).toEqual([
104
+ {
105
+ status: '✅ Posted',
106
+ detail: 'Single reel shared successfully',
107
+ url: 'https://www.instagram.com/reel/REEL123/',
108
+ },
109
+ ]);
110
+ });
111
+ it('copies query-style local video filenames to a safe temp upload path before setFileInput', async () => {
112
+ const videoPath = createTempVideo('demo.mp4?sign=abc&t=123video.MP4');
113
+ const page = createPageMock([
114
+ { ok: false },
115
+ { ok: true },
116
+ { ok: true },
117
+ { ok: true, selectors: ['[data-opencli-reel-upload-index="0"]'] },
118
+ { count: 1 },
119
+ { state: 'preview', detail: 'Crop Back Next' },
120
+ { ok: true, label: 'OK' },
121
+ { ok: true, label: 'Next' },
122
+ { state: 'edit' },
123
+ { ok: true, label: 'Next' },
124
+ { state: 'composer' },
125
+ { ok: true, label: 'Share' },
126
+ { ok: true, url: 'https://www.instagram.com/reel/REELSAFE123/' },
127
+ ]);
128
+ const cmd = getRegistry().get('instagram/reel');
129
+ await cmd.func(page, { video: videoPath });
130
+ const uploadPaths = page.setFileInput.mock.calls[0]?.[0] ?? [];
131
+ expect(uploadPaths).toHaveLength(1);
132
+ expect(uploadPaths[0]).not.toBe(videoPath);
133
+ expect(String(uploadPaths[0])).toContain('opencli-instagram-video-real');
134
+ expect(String(uploadPaths[0]).toLowerCase()).toContain('.mp4');
135
+ });
136
+ it('uploads a reel video with caption and shares it', async () => {
137
+ const videoPath = createTempVideo('captioned.mp4');
138
+ const page = createPageMock([
139
+ { ok: false }, // dismiss residual dialogs
140
+ { ok: true }, // ensure composer open
141
+ { ok: true }, // composer upload input ready
142
+ { ok: true, selectors: ['[data-opencli-reel-upload-index="0"]'] }, // resolve upload selector
143
+ { count: 1 }, // file bound to input
144
+ { state: 'preview', detail: 'Crop Back Next' }, // preview detected
145
+ { ok: true, label: 'OK' }, // dismiss reels nux
146
+ { ok: true, label: 'Next' }, // move from crop to edit
147
+ { state: 'edit' }, // edit stage
148
+ { ok: true, label: 'Next' }, // move from edit to composer
149
+ { state: 'composer' }, // composer stage
150
+ { ok: true }, // focus caption editor
151
+ { ok: true }, // post-insert event dispatch
152
+ { ok: true }, // caption matches
153
+ { ok: true, label: 'Share' }, // share
154
+ { ok: true, url: 'https://www.instagram.com/reel/REEL456/' }, // success
155
+ ]);
156
+ const cmd = getRegistry().get('instagram/reel');
157
+ const result = await cmd.func(page, { video: videoPath, content: 'hello reel' });
158
+ expect(page.insertText).toHaveBeenCalledWith('hello reel');
159
+ expect(result).toEqual([
160
+ {
161
+ status: '✅ Posted',
162
+ detail: 'Single reel shared successfully',
163
+ url: 'https://www.instagram.com/reel/REEL456/',
164
+ },
165
+ ]);
166
+ });
167
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,115 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { ArgumentError, CommandExecutionError } from '../../errors.js';
4
+ import { cli, Strategy } from '../../registry.js';
5
+ import { publishStoryViaPrivateApi, resolveInstagramPrivatePublishConfig, } from './_shared/private-publish.js';
6
+ import { resolveInstagramRuntimeInfo } from './_shared/runtime-info.js';
7
+ const INSTAGRAM_HOME_URL = 'https://www.instagram.com/';
8
+ const SUPPORTED_STORY_IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.webp']);
9
+ const SUPPORTED_STORY_VIDEO_EXTENSIONS = new Set(['.mp4']);
10
+ function requirePage(page) {
11
+ if (!page)
12
+ throw new CommandExecutionError('Browser session required for instagram story');
13
+ return page;
14
+ }
15
+ function validateInstagramStoryArgs(kwargs) {
16
+ if (kwargs.media === undefined) {
17
+ throw new ArgumentError('Argument "media" is required.', 'Provide --media /path/to/file.jpg or --media /path/to/file.mp4');
18
+ }
19
+ }
20
+ function normalizeStoryMediaItem(kwargs) {
21
+ const raw = String(kwargs.media ?? '').trim();
22
+ const parts = raw.split(',').map((part) => part.trim()).filter(Boolean);
23
+ if (parts.length === 0) {
24
+ throw new ArgumentError('Argument "media" is required.', 'Provide --media /path/to/file.jpg or --media /path/to/file.mp4');
25
+ }
26
+ if (parts.length > 1) {
27
+ throw new ArgumentError('Instagram story currently supports a single media item.', 'Provide one image or one video path with --media');
28
+ }
29
+ const resolved = path.resolve(parts[0]);
30
+ if (!fs.existsSync(resolved)) {
31
+ throw new ArgumentError(`Story media file not found: ${resolved}`);
32
+ }
33
+ const ext = path.extname(resolved).toLowerCase();
34
+ if (SUPPORTED_STORY_IMAGE_EXTENSIONS.has(ext)) {
35
+ return { type: 'image', filePath: resolved };
36
+ }
37
+ if (SUPPORTED_STORY_VIDEO_EXTENSIONS.has(ext)) {
38
+ return { type: 'video', filePath: resolved };
39
+ }
40
+ throw new ArgumentError(`Unsupported story media format: ${ext}`, 'Supported formats: images (.jpg, .jpeg, .png, .webp) and videos (.mp4)');
41
+ }
42
+ async function resolveCurrentUserId(page) {
43
+ const cookies = await page.getCookies({ domain: 'instagram.com' });
44
+ return cookies.find((cookie) => cookie.name === 'ds_user_id')?.value || '';
45
+ }
46
+ async function resolveCurrentUsername(page, currentUserId = '') {
47
+ if (!currentUserId)
48
+ return '';
49
+ const runtimeInfo = await resolveInstagramRuntimeInfo(page);
50
+ const apiResult = await page.evaluate(`
51
+ (async () => {
52
+ const userId = ${JSON.stringify(currentUserId)};
53
+ const appId = ${JSON.stringify(runtimeInfo.appId || '')};
54
+ try {
55
+ const res = await fetch(
56
+ 'https://www.instagram.com/api/v1/users/' + encodeURIComponent(userId) + '/info/',
57
+ {
58
+ credentials: 'include',
59
+ headers: appId ? { 'X-IG-App-ID': appId } : {},
60
+ },
61
+ );
62
+ if (!res.ok) return { ok: false };
63
+ const data = await res.json();
64
+ const username = data?.user?.username || '';
65
+ return { ok: !!username, username };
66
+ } catch {
67
+ return { ok: false };
68
+ }
69
+ })()
70
+ `);
71
+ return apiResult?.ok && apiResult.username ? apiResult.username : '';
72
+ }
73
+ function buildStorySuccessResult(mediaItem, url) {
74
+ return [{
75
+ status: '✅ Posted',
76
+ detail: mediaItem.type === 'video'
77
+ ? 'Single video story shared successfully'
78
+ : 'Single story shared successfully',
79
+ url,
80
+ }];
81
+ }
82
+ cli({
83
+ site: 'instagram',
84
+ name: 'story',
85
+ description: 'Post a single Instagram story image or video',
86
+ domain: 'www.instagram.com',
87
+ strategy: Strategy.UI,
88
+ browser: true,
89
+ timeoutSeconds: 300,
90
+ args: [
91
+ { name: 'media', required: false, valueRequired: true, help: 'Path to a single story image or video file' },
92
+ ],
93
+ columns: ['status', 'detail', 'url'],
94
+ validateArgs: validateInstagramStoryArgs,
95
+ func: async (page, kwargs) => {
96
+ const browserPage = requirePage(page);
97
+ const mediaItem = normalizeStoryMediaItem(kwargs);
98
+ const currentUserId = await resolveCurrentUserId(browserPage);
99
+ const privateConfig = await resolveInstagramPrivatePublishConfig(browserPage);
100
+ const storyResult = await publishStoryViaPrivateApi({
101
+ page: browserPage,
102
+ mediaItem,
103
+ content: '',
104
+ apiContext: privateConfig.apiContext,
105
+ jazoest: privateConfig.jazoest,
106
+ currentUserId,
107
+ });
108
+ const username = await resolveCurrentUsername(browserPage, currentUserId);
109
+ const mediaPk = storyResult.mediaPk || storyResult.uploadId;
110
+ const url = username && mediaPk
111
+ ? new URL(`/stories/${username}/${mediaPk}/`, INSTAGRAM_HOME_URL).toString()
112
+ : '';
113
+ return buildStorySuccessResult(mediaItem, url);
114
+ },
115
+ });
@@ -0,0 +1 @@
1
+ import './story.js';
@@ -0,0 +1,167 @@
1
+ import * as fs from 'node:fs';
2
+ import * as os from 'node:os';
3
+ import * as path from 'node:path';
4
+ import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { ArgumentError } from '../../errors.js';
6
+ import { getRegistry } from '../../registry.js';
7
+ import * as privatePublish from './_shared/private-publish.js';
8
+ import './story.js';
9
+ const tempDirs = [];
10
+ function createTempFile(name, bytes = Buffer.from('story-media')) {
11
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-instagram-story-'));
12
+ tempDirs.push(dir);
13
+ const filePath = path.join(dir, name);
14
+ fs.writeFileSync(filePath, bytes);
15
+ return filePath;
16
+ }
17
+ function createPageMock(evaluateResults = [], overrides = {}) {
18
+ const evaluate = vi.fn();
19
+ for (const result of evaluateResults) {
20
+ evaluate.mockResolvedValueOnce(result);
21
+ }
22
+ return {
23
+ goto: vi.fn().mockResolvedValue(undefined),
24
+ evaluate,
25
+ getCookies: vi.fn().mockResolvedValue([]),
26
+ snapshot: vi.fn().mockResolvedValue(undefined),
27
+ click: vi.fn().mockResolvedValue(undefined),
28
+ typeText: vi.fn().mockResolvedValue(undefined),
29
+ pressKey: vi.fn().mockResolvedValue(undefined),
30
+ scrollTo: vi.fn().mockResolvedValue(undefined),
31
+ getFormState: vi.fn().mockResolvedValue({ forms: [], orphanFields: [] }),
32
+ wait: vi.fn().mockResolvedValue(undefined),
33
+ tabs: vi.fn().mockResolvedValue([]),
34
+ closeTab: vi.fn().mockResolvedValue(undefined),
35
+ newTab: vi.fn().mockResolvedValue(undefined),
36
+ selectTab: vi.fn().mockResolvedValue(undefined),
37
+ networkRequests: vi.fn().mockResolvedValue([]),
38
+ consoleMessages: vi.fn().mockResolvedValue([]),
39
+ scroll: vi.fn().mockResolvedValue(undefined),
40
+ autoScroll: vi.fn().mockResolvedValue(undefined),
41
+ installInterceptor: vi.fn().mockResolvedValue(undefined),
42
+ getInterceptedRequests: vi.fn().mockResolvedValue([]),
43
+ waitForCapture: vi.fn().mockResolvedValue(undefined),
44
+ screenshot: vi.fn().mockResolvedValue(''),
45
+ setFileInput: vi.fn().mockResolvedValue(undefined),
46
+ insertText: vi.fn().mockResolvedValue(undefined),
47
+ getCurrentUrl: vi.fn().mockResolvedValue(null),
48
+ ...overrides,
49
+ };
50
+ }
51
+ afterAll(() => {
52
+ for (const dir of tempDirs) {
53
+ fs.rmSync(dir, { recursive: true, force: true });
54
+ }
55
+ });
56
+ describe('instagram story registration', () => {
57
+ beforeEach(() => {
58
+ vi.restoreAllMocks();
59
+ });
60
+ afterEach(() => {
61
+ vi.restoreAllMocks();
62
+ });
63
+ it('registers the story command with a required-value media arg', () => {
64
+ const cmd = getRegistry().get('instagram/story');
65
+ expect(cmd).toBeDefined();
66
+ expect(cmd?.browser).toBe(true);
67
+ expect(cmd?.args.some((arg) => arg.name === 'media' && !arg.required && arg.valueRequired)).toBe(true);
68
+ expect(cmd?.args.some((arg) => arg.name === 'content')).toBe(false);
69
+ });
70
+ it('rejects missing --media before browser work', async () => {
71
+ const page = createPageMock();
72
+ const cmd = getRegistry().get('instagram/story');
73
+ await expect(cmd.func(page, {})).rejects.toThrow(ArgumentError);
74
+ expect(page.goto).not.toHaveBeenCalled();
75
+ });
76
+ it('rejects multiple media inputs for a single story', async () => {
77
+ const first = createTempFile('one.jpg');
78
+ const second = createTempFile('two.mp4');
79
+ const page = createPageMock();
80
+ const cmd = getRegistry().get('instagram/story');
81
+ await expect(cmd.func(page, { media: `${first},${second}` })).rejects.toThrow('single media');
82
+ expect(page.goto).not.toHaveBeenCalled();
83
+ });
84
+ it('rejects unsupported story formats', async () => {
85
+ const filePath = createTempFile('story.mov');
86
+ const page = createPageMock();
87
+ const cmd = getRegistry().get('instagram/story');
88
+ await expect(cmd.func(page, { media: filePath })).rejects.toThrow('Unsupported story media format');
89
+ expect(page.goto).not.toHaveBeenCalled();
90
+ });
91
+ it('publishes a single image story through the private route', async () => {
92
+ const imagePath = createTempFile('story.jpg');
93
+ const page = createPageMock([
94
+ { appId: '936619743392459', csrfToken: '', instagramAjax: 'ajax' },
95
+ { ok: true, username: 'tsezi_ray' },
96
+ ], {
97
+ getCookies: vi.fn().mockResolvedValue([{ name: 'ds_user_id', value: '123', domain: 'instagram.com' }]),
98
+ });
99
+ const cmd = getRegistry().get('instagram/story');
100
+ vi.spyOn(privatePublish, 'resolveInstagramPrivatePublishConfig').mockResolvedValue({
101
+ apiContext: {
102
+ asbdId: '359341',
103
+ csrfToken: 'csrf-token',
104
+ igAppId: '936619743392459',
105
+ igWwwClaim: 'claim',
106
+ instagramAjax: 'ajax',
107
+ webSessionId: 'session',
108
+ },
109
+ jazoest: '22047',
110
+ });
111
+ vi.spyOn(privatePublish, 'publishStoryViaPrivateApi').mockResolvedValue({
112
+ mediaPk: '1234567890',
113
+ uploadId: '1234567890',
114
+ });
115
+ const result = await cmd.func(page, { media: imagePath });
116
+ expect(privatePublish.publishStoryViaPrivateApi).toHaveBeenCalledWith(expect.objectContaining({
117
+ page,
118
+ mediaItem: { type: 'image', filePath: imagePath },
119
+ content: '',
120
+ }));
121
+ expect(result).toEqual([
122
+ {
123
+ status: '✅ Posted',
124
+ detail: 'Single story shared successfully',
125
+ url: 'https://www.instagram.com/stories/tsezi_ray/1234567890/',
126
+ },
127
+ ]);
128
+ });
129
+ it('publishes a single video story through the private route', async () => {
130
+ const videoPath = createTempFile('story.mp4');
131
+ const page = createPageMock([
132
+ { appId: '936619743392459', csrfToken: '', instagramAjax: 'ajax' },
133
+ { ok: true, username: 'tsezi_ray' },
134
+ ], {
135
+ getCookies: vi.fn().mockResolvedValue([{ name: 'ds_user_id', value: '123', domain: 'instagram.com' }]),
136
+ });
137
+ const cmd = getRegistry().get('instagram/story');
138
+ vi.spyOn(privatePublish, 'resolveInstagramPrivatePublishConfig').mockResolvedValue({
139
+ apiContext: {
140
+ asbdId: '359341',
141
+ csrfToken: 'csrf-token',
142
+ igAppId: '936619743392459',
143
+ igWwwClaim: 'claim',
144
+ instagramAjax: 'ajax',
145
+ webSessionId: 'session',
146
+ },
147
+ jazoest: '22047',
148
+ });
149
+ vi.spyOn(privatePublish, 'publishStoryViaPrivateApi').mockResolvedValue({
150
+ mediaPk: '9988776655',
151
+ uploadId: '9988776655',
152
+ });
153
+ const result = await cmd.func(page, { media: videoPath });
154
+ expect(privatePublish.publishStoryViaPrivateApi).toHaveBeenCalledWith(expect.objectContaining({
155
+ page,
156
+ mediaItem: { type: 'video', filePath: videoPath },
157
+ content: '',
158
+ }));
159
+ expect(result).toEqual([
160
+ {
161
+ status: '✅ Posted',
162
+ detail: 'Single video story shared successfully',
163
+ url: 'https://www.instagram.com/stories/tsezi_ray/9988776655/',
164
+ },
165
+ ]);
166
+ });
167
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Sinafinance stock rank
3
+ */
4
+ export {};
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Sinafinance stock rank
3
+ */
4
+ import { cli, Strategy } from '../../registry.js';
5
+ cli({
6
+ site: 'sinafinance',
7
+ name: 'stock-rank',
8
+ description: '新浪财经热搜榜',
9
+ domain: 'finance.sina.cn',
10
+ strategy: Strategy.COOKIE,
11
+ navigateBefore: false,
12
+ args: [
13
+ { name: 'market', type: 'string', default: 'cn', choices: ['cn', 'hk', 'us', 'wh', 'ft'], help: 'Market: cn (A股), hk (港股), us (美股), wh (外汇), ft (期货)' },
14
+ ],
15
+ columns: ['rank', 'name', 'symbol', 'market', 'price', 'change', 'url'],
16
+ func: async (page, _args) => {
17
+ const market = _args.market || 'cn';
18
+ await page.goto('https://finance.sina.cn/');
19
+ await page.wait({ selector: '#actionSearch', timeout: 10000 });
20
+ const payload = await page.evaluate(`
21
+ (async () => {
22
+ const wait = (ms) => new Promise(r => setTimeout(r, ms));
23
+ const cleanText = (value) => (value || '').replace(/\\s+/g, ' ').trim();
24
+ const marketType = ${JSON.stringify(market)};
25
+
26
+ const searchBtn = document.querySelector('#actionSearch');
27
+ if (searchBtn) {
28
+ searchBtn.dispatchEvent(new Event('tap', { bubbles: true }));
29
+ await wait(3000);
30
+ }
31
+
32
+ const tabEl = document.querySelector('[data-type="' + marketType + '"]');
33
+ const marketName = tabEl?.textContent || marketType;
34
+ if (marketType !== 'cn' && tabEl) {
35
+ tabEl.click();
36
+ await wait(2000);
37
+ }
38
+
39
+ const results = [];
40
+ document.querySelectorAll('#stock-list .j-stock-row').forEach(el => {
41
+ const rankEl = el.querySelector('.rank');
42
+ const nameEl = el.querySelector('.j-sname');
43
+ const codeEl = el.querySelector('.stock-code');
44
+ const priceEl = el.querySelector('.j-price');
45
+ const changeEl = el.querySelector('.j-change');
46
+ const openUrl = el.getAttribute('open-url') || '';
47
+ const fullUrl = openUrl ? 'https:' + openUrl : '';
48
+ results.push({
49
+ rank: cleanText(rankEl?.textContent || ''),
50
+ name: cleanText(nameEl?.textContent || ''),
51
+ symbol: cleanText(codeEl?.textContent || ''),
52
+ market: cleanText(marketName),
53
+ price: cleanText(priceEl?.textContent || ''),
54
+ change: cleanText(changeEl?.textContent || ''),
55
+ url: fullUrl,
56
+ });
57
+ });
58
+ return results;
59
+ })()
60
+ `);
61
+ if (!Array.isArray(payload))
62
+ return [];
63
+ return payload;
64
+ },
65
+ });
@@ -12,8 +12,6 @@ function createPageMock(evaluateResult) {
12
12
  getFormState: vi.fn().mockResolvedValue({}),
13
13
  wait: vi.fn().mockResolvedValue(undefined),
14
14
  tabs: vi.fn().mockResolvedValue([]),
15
- closeTab: vi.fn().mockResolvedValue(undefined),
16
- newTab: vi.fn().mockResolvedValue(undefined),
17
15
  selectTab: vi.fn().mockResolvedValue(undefined),
18
16
  networkRequests: vi.fn().mockResolvedValue([]),
19
17
  consoleMessages: vi.fn().mockResolvedValue([]),