@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
package/dist/cli.d.ts CHANGED
@@ -4,4 +4,6 @@
4
4
  * Built-in commands are registered inline here (list, validate, explore, etc.).
5
5
  * Dynamic adapter commands are registered via commanderAdapter.ts.
6
6
  */
7
+ import { Command } from 'commander';
8
+ export declare function createProgram(BUILTIN_CLIS: string, USER_CLIS: string): Command;
7
9
  export declare function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void;
package/dist/cli.js CHANGED
@@ -22,7 +22,11 @@ async function getOperatePage() {
22
22
  const bridge = new BrowserBridge();
23
23
  return bridge.connect({ timeout: 30, workspace: 'operate:default' });
24
24
  }
25
- export function runCli(BUILTIN_CLIS, USER_CLIS) {
25
+ function applyVerbose(opts) {
26
+ if (opts.verbose)
27
+ process.env.OPENCLI_VERBOSE = '1';
28
+ }
29
+ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
26
30
  const program = new Command();
27
31
  // enablePositionalOptions: prevents parent from consuming flags meant for subcommands;
28
32
  // prerequisite for passThroughOptions to forward --help/--version to external binaries
@@ -130,7 +134,9 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
130
134
  .option('--wait <s>', '', '3')
131
135
  .option('--auto', 'Enable interactive fuzzing')
132
136
  .option('--click <labels>', 'Comma-separated labels to click before fuzzing')
137
+ .option('-v, --verbose', 'Debug output')
133
138
  .action(async (url, opts) => {
139
+ applyVerbose(opts);
134
140
  const { exploreUrl, renderExploreSummary } = await import('./explore.js');
135
141
  const clickLabels = opts.click
136
142
  ? opts.click.split(',').map((s) => s.trim())
@@ -152,7 +158,9 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
152
158
  .description('Synthesize CLIs from explore')
153
159
  .argument('<target>')
154
160
  .option('--top <n>', '', '3')
161
+ .option('-v, --verbose', 'Debug output')
155
162
  .action(async (target, opts) => {
163
+ applyVerbose(opts);
156
164
  const { synthesizeFromExplore, renderSynthesizeSummary } = await import('./synthesize.js');
157
165
  console.log(renderSynthesizeSummary(synthesizeFromExplore(target, { top: parseInt(opts.top) })));
158
166
  });
@@ -162,7 +170,9 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
162
170
  .argument('<url>')
163
171
  .option('--goal <text>')
164
172
  .option('--site <name>')
173
+ .option('-v, --verbose', 'Debug output')
165
174
  .action(async (url, opts) => {
175
+ applyVerbose(opts);
166
176
  const { generateCliFromUrl, renderGenerateSummary } = await import('./generate.js');
167
177
  const workspace = `generate:${inferHost(url, opts.site)}`;
168
178
  const r = await generateCliFromUrl({
@@ -184,7 +194,9 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
184
194
  .option('--out <dir>', 'Output directory for candidates')
185
195
  .option('--poll <ms>', 'Poll interval in milliseconds', '2000')
186
196
  .option('--timeout <ms>', 'Auto-stop after N milliseconds (default: 60000)', '60000')
197
+ .option('-v, --verbose', 'Debug output')
187
198
  .action(async (url, opts) => {
199
+ applyVerbose(opts);
188
200
  const { recordSession, renderRecordSummary } = await import('./record.js');
189
201
  const result = await recordSession({
190
202
  BrowserFactory: getBrowserFactory(),
@@ -202,7 +214,9 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
202
214
  .description('Strategy cascade: find simplest working strategy')
203
215
  .argument('<url>')
204
216
  .option('--site <name>')
217
+ .option('-v, --verbose', 'Debug output')
205
218
  .action(async (url, opts) => {
219
+ applyVerbose(opts);
206
220
  const { cascadeProbe, renderCascadeResult } = await import('./cascade.js');
207
221
  const workspace = `cascade:${inferHost(url, opts.site)}`;
208
222
  const result = await browserSession(getBrowserFactory(), async (page) => {
@@ -279,7 +293,7 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
279
293
  // ── Inspect ──
280
294
  operate.command('state').description('Page state: URL, title, interactive elements with [N] indices')
281
295
  .action(operateAction(async (page) => {
282
- const snapshot = await page.snapshot({ viewportExpand: 800 });
296
+ const snapshot = await page.snapshot({ viewportExpand: 2000 });
283
297
  const url = await page.getCurrentUrl?.() ?? '';
284
298
  console.log(`URL: ${url}\n`);
285
299
  console.log(typeof snapshot === 'string' ? snapshot : JSON.stringify(snapshot, null, 2));
@@ -338,7 +352,24 @@ export function runCli(BUILTIN_CLIS, USER_CLIS) {
338
352
  await page.click(index);
339
353
  await page.wait(0.3);
340
354
  await page.typeText(index, text);
341
- console.log(`Typed "${text}" into element [${index}]`);
355
+ // Detect autocomplete/combobox fields and wait for dropdown suggestions
356
+ const isAutocomplete = await page.evaluate(`
357
+ (() => {
358
+ const el = document.querySelector('[data-opencli-ref="${index}"]');
359
+ if (!el) return false;
360
+ const role = el.getAttribute('role');
361
+ const ac = el.getAttribute('aria-autocomplete');
362
+ const list = el.getAttribute('list');
363
+ return role === 'combobox' || ac === 'list' || ac === 'both' || !!list;
364
+ })()
365
+ `);
366
+ if (isAutocomplete) {
367
+ await page.wait(0.4);
368
+ console.log(`Typed "${text}" into autocomplete [${index}] — use state to see suggestions`);
369
+ }
370
+ else {
371
+ console.log(`Typed "${text}" into element [${index}]`);
372
+ }
342
373
  }));
343
374
  operate.command('select').argument('<index>', 'Element index of <select>').argument('<option>', 'Option text')
344
375
  .description('Select dropdown option')
@@ -569,20 +600,25 @@ cli({
569
600
  }
570
601
  console.log(`🔍 Verifying ${name}...\n`);
571
602
  console.log(` Loading: ${filePath}`);
603
+ // Read adapter to check if it defines a 'limit' arg
604
+ const adapterSrc = fs.readFileSync(filePath, 'utf-8');
605
+ const hasLimitArg = /['"]limit['"]/.test(adapterSrc);
606
+ const limitFlag = hasLimitArg ? ' --limit 3' : '';
607
+ const verifyCmd = `node dist/main.js ${site} ${command}${limitFlag}`;
572
608
  try {
573
- const output = execSync(`node dist/main.js ${site} ${command} --limit 3`, {
609
+ const output = execSync(verifyCmd, {
574
610
  cwd: path.join(path.dirname(import.meta.url.replace('file://', '')), '..'),
575
611
  timeout: 30000,
576
612
  encoding: 'utf-8',
577
613
  env: process.env,
578
614
  stdio: ['pipe', 'pipe', 'pipe'],
579
615
  });
580
- console.log(` Executing: opencli ${site} ${command} --limit 3\n`);
616
+ console.log(` Executing: opencli ${site} ${command}${limitFlag}\n`);
581
617
  console.log(output);
582
618
  console.log(`\n ✓ Adapter works!`);
583
619
  }
584
620
  catch (err) {
585
- console.log(` Executing: opencli ${site} ${command} --limit 3\n`);
621
+ console.log(` Executing: opencli ${site} ${command}${limitFlag}\n`);
586
622
  if (err.stdout)
587
623
  console.log(err.stdout);
588
624
  if (err.stderr)
@@ -608,7 +644,9 @@ cli({
608
644
  .description('Diagnose opencli browser bridge connectivity')
609
645
  .option('--no-live', 'Skip live browser connectivity test')
610
646
  .option('--sessions', 'Show active automation sessions', false)
647
+ .option('-v, --verbose', 'Debug output')
611
648
  .action(async (opts) => {
649
+ applyVerbose(opts);
612
650
  const { runBrowserDoctor, renderBrowserDoctorReport } = await import('./doctor.js');
613
651
  const report = await runBrowserDoctor({ live: opts.live, sessions: opts.sessions, cliVersion: PKG_VERSION });
614
652
  console.log(renderBrowserDoctorReport(report));
@@ -896,7 +934,10 @@ cli({
896
934
  program.outputHelp();
897
935
  process.exitCode = EXIT_CODES.USAGE_ERROR;
898
936
  });
899
- program.parse();
937
+ return program;
938
+ }
939
+ export function runCli(BUILTIN_CLIS, USER_CLIS) {
940
+ createProgram(BUILTIN_CLIS, USER_CLIS).parse();
900
941
  }
901
942
  // ── Helpers ─────────────────────────────────────────────────────────────────
902
943
  /** Infer a workspace-friendly hostname from a URL, with site override. */
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,88 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ const { mockExploreUrl, mockRenderExploreSummary, mockGenerateCliFromUrl, mockRenderGenerateSummary, mockRecordSession, mockRenderRecordSummary, mockCascadeProbe, mockRenderCascadeResult, mockGetBrowserFactory, mockBrowserSession, } = vi.hoisted(() => ({
3
+ mockExploreUrl: vi.fn(),
4
+ mockRenderExploreSummary: vi.fn(),
5
+ mockGenerateCliFromUrl: vi.fn(),
6
+ mockRenderGenerateSummary: vi.fn(),
7
+ mockRecordSession: vi.fn(),
8
+ mockRenderRecordSummary: vi.fn(),
9
+ mockCascadeProbe: vi.fn(),
10
+ mockRenderCascadeResult: vi.fn(),
11
+ mockGetBrowserFactory: vi.fn(() => ({ name: 'BrowserFactory' })),
12
+ mockBrowserSession: vi.fn(),
13
+ }));
14
+ vi.mock('./explore.js', () => ({
15
+ exploreUrl: mockExploreUrl,
16
+ renderExploreSummary: mockRenderExploreSummary,
17
+ }));
18
+ vi.mock('./generate.js', () => ({
19
+ generateCliFromUrl: mockGenerateCliFromUrl,
20
+ renderGenerateSummary: mockRenderGenerateSummary,
21
+ }));
22
+ vi.mock('./record.js', () => ({
23
+ recordSession: mockRecordSession,
24
+ renderRecordSummary: mockRenderRecordSummary,
25
+ }));
26
+ vi.mock('./cascade.js', () => ({
27
+ cascadeProbe: mockCascadeProbe,
28
+ renderCascadeResult: mockRenderCascadeResult,
29
+ }));
30
+ vi.mock('./runtime.js', () => ({
31
+ getBrowserFactory: mockGetBrowserFactory,
32
+ browserSession: mockBrowserSession,
33
+ }));
34
+ import { createProgram } from './cli.js';
35
+ describe('built-in browser commands verbose wiring', () => {
36
+ const consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
37
+ beforeEach(() => {
38
+ delete process.env.OPENCLI_VERBOSE;
39
+ process.exitCode = undefined;
40
+ mockExploreUrl.mockReset().mockResolvedValue({ ok: true });
41
+ mockRenderExploreSummary.mockReset().mockReturnValue('explore-summary');
42
+ mockGenerateCliFromUrl.mockReset().mockResolvedValue({ ok: true });
43
+ mockRenderGenerateSummary.mockReset().mockReturnValue('generate-summary');
44
+ mockRecordSession.mockReset().mockResolvedValue({ candidateCount: 1 });
45
+ mockRenderRecordSummary.mockReset().mockReturnValue('record-summary');
46
+ mockCascadeProbe.mockReset().mockResolvedValue({ ok: true });
47
+ mockRenderCascadeResult.mockReset().mockReturnValue('cascade-summary');
48
+ mockGetBrowserFactory.mockClear();
49
+ mockBrowserSession.mockReset().mockImplementation(async (_factory, fn) => {
50
+ const page = {
51
+ goto: vi.fn(),
52
+ wait: vi.fn(),
53
+ };
54
+ return fn(page);
55
+ });
56
+ });
57
+ it('enables OPENCLI_VERBOSE for explore via the real CLI command', async () => {
58
+ const program = createProgram('', '');
59
+ await program.parseAsync(['node', 'opencli', 'explore', 'https://example.com', '-v']);
60
+ expect(process.env.OPENCLI_VERBOSE).toBe('1');
61
+ expect(mockExploreUrl).toHaveBeenCalledWith('https://example.com', expect.objectContaining({ workspace: 'explore:example.com' }));
62
+ });
63
+ it('enables OPENCLI_VERBOSE for generate via the real CLI command', async () => {
64
+ const program = createProgram('', '');
65
+ await program.parseAsync(['node', 'opencli', 'generate', 'https://example.com', '-v']);
66
+ expect(process.env.OPENCLI_VERBOSE).toBe('1');
67
+ expect(mockGenerateCliFromUrl).toHaveBeenCalledWith(expect.objectContaining({ url: 'https://example.com', workspace: 'generate:example.com' }));
68
+ });
69
+ it('enables OPENCLI_VERBOSE for record via the real CLI command', async () => {
70
+ const program = createProgram('', '');
71
+ await program.parseAsync(['node', 'opencli', 'record', 'https://example.com', '-v']);
72
+ expect(process.env.OPENCLI_VERBOSE).toBe('1');
73
+ expect(mockRecordSession).toHaveBeenCalledWith(expect.objectContaining({ url: 'https://example.com' }));
74
+ });
75
+ it('enables OPENCLI_VERBOSE for cascade via the real CLI command', async () => {
76
+ const program = createProgram('', '');
77
+ await program.parseAsync(['node', 'opencli', 'cascade', 'https://example.com', '-v']);
78
+ expect(process.env.OPENCLI_VERBOSE).toBe('1');
79
+ expect(mockBrowserSession).toHaveBeenCalled();
80
+ expect(mockCascadeProbe).toHaveBeenCalledWith(expect.any(Object), 'https://example.com');
81
+ });
82
+ it('leaves OPENCLI_VERBOSE unset when verbose is omitted', async () => {
83
+ const program = createProgram('', '');
84
+ await program.parseAsync(['node', 'opencli', 'explore', 'https://example.com']);
85
+ expect(process.env.OPENCLI_VERBOSE).toBeUndefined();
86
+ });
87
+ consoleLogSpy.mockClear();
88
+ });
@@ -0,0 +1,70 @@
1
+ interface BuyerProtectionModel {
2
+ serviceName?: string;
3
+ shortBuyerDesc?: string;
4
+ packageBuyerDesc?: string;
5
+ textDesc?: string;
6
+ agreeDeliveryHours?: number;
7
+ }
8
+ interface ItemBrowserPayload {
9
+ href?: string;
10
+ title?: string;
11
+ bodyText?: string;
12
+ offerTitle?: string;
13
+ offerId?: string | number;
14
+ seller?: {
15
+ companyName?: string;
16
+ memberId?: string;
17
+ winportUrl?: string;
18
+ sellerWinportUrlMap?: Record<string, string>;
19
+ };
20
+ trade?: {
21
+ beginAmount?: string | number;
22
+ priceDisplay?: string;
23
+ unit?: string;
24
+ saleCount?: string | number;
25
+ offerIDatacenterSellInfo?: Record<string, unknown>;
26
+ offerPriceModel?: {
27
+ currentPrices?: Array<{
28
+ beginAmount?: string | number;
29
+ price?: string | number;
30
+ }>;
31
+ };
32
+ };
33
+ gallery?: {
34
+ mainImage?: string[];
35
+ offerImgList?: string[];
36
+ wlImageInfos?: Array<{
37
+ fullPathImageURI?: string;
38
+ }>;
39
+ };
40
+ shipping?: {
41
+ deliveryLimitText?: string;
42
+ logisticsText?: string;
43
+ protectionInfos?: BuyerProtectionModel[];
44
+ buyerProtectionModel?: BuyerProtectionModel[];
45
+ };
46
+ services?: BuyerProtectionModel[];
47
+ }
48
+ interface VisibleAttribute {
49
+ key: string;
50
+ value: string;
51
+ }
52
+ declare function normalizeItemPayload(payload: ItemBrowserPayload): Record<string, unknown>;
53
+ declare function normalizeVisibleAttributes(raw: unknown): VisibleAttribute[];
54
+ declare function stripAlibabaSuffix(title: string | undefined): string;
55
+ declare function extractMoqText(bodyText: string, beginAmount: string | number | undefined, unit: string): string;
56
+ declare function extractDeliveryDaysText(bodyText: string, services: BuyerProtectionModel[], shipping: ItemBrowserPayload['shipping']): string | null;
57
+ declare function extractKeywordLine(bodyText: string, keywords: string[]): string | null;
58
+ declare function extractSalesText(bodyText: string): string | null;
59
+ declare function extractStockQuantity(bodyText: string): number | null;
60
+ export declare const __test__: {
61
+ normalizeItemPayload: typeof normalizeItemPayload;
62
+ normalizeVisibleAttributes: typeof normalizeVisibleAttributes;
63
+ stripAlibabaSuffix: typeof stripAlibabaSuffix;
64
+ extractMoqText: typeof extractMoqText;
65
+ extractDeliveryDaysText: typeof extractDeliveryDaysText;
66
+ extractKeywordLine: typeof extractKeywordLine;
67
+ extractSalesText: typeof extractSalesText;
68
+ extractStockQuantity: typeof extractStockQuantity;
69
+ };
70
+ export {};
@@ -0,0 +1,187 @@
1
+ import { CommandExecutionError } from '../../errors.js';
2
+ import { cli, Strategy } from '../../registry.js';
3
+ import { isRecord } from '../../utils.js';
4
+ import { assertAuthenticatedState, buildDetailUrl, buildProvenance, canonicalizeSellerUrl, cleanMultilineText, cleanText, extractLocation, extractMemberId, extractOfferId, extractShopId, gotoAndReadState, normalizePriceTiers, parseMoqText, parsePriceText, toNumber, uniqueNonEmpty, } from './shared.js';
5
+ function normalizeItemPayload(payload) {
6
+ const href = cleanText(payload.href);
7
+ const bodyText = cleanMultilineText(payload.bodyText);
8
+ const sellerName = cleanText(payload.seller?.companyName);
9
+ const sellerUrlRaw = cleanText(payload.seller?.winportUrl
10
+ ?? payload.seller?.sellerWinportUrlMap?.defaultUrl
11
+ ?? payload.seller?.sellerWinportUrlMap?.indexUrl);
12
+ const sellerUrl = canonicalizeSellerUrl(sellerUrlRaw);
13
+ const offerId = cleanText(String(payload.offerId ?? '')) || extractOfferId(href) || null;
14
+ const memberId = cleanText(payload.seller?.memberId) || extractMemberId(sellerUrlRaw || href) || null;
15
+ const shopId = extractShopId(sellerUrl ?? href);
16
+ const unit = cleanText(payload.trade?.unit);
17
+ const priceDisplay = cleanText(payload.trade?.priceDisplay);
18
+ const priceRange = parsePriceText(priceDisplay ? `¥${priceDisplay}` : bodyText);
19
+ const moqText = extractMoqText(bodyText, payload.trade?.beginAmount, unit);
20
+ const moq = parseMoqText(moqText);
21
+ const services = uniqueServices(payload);
22
+ const serviceBadges = uniqueNonEmpty(services.map((service) => cleanText(service.serviceName)));
23
+ const attributes = normalizeVisibleAttributes(payload.trade?.offerIDatacenterSellInfo);
24
+ const priceTiers = normalizePriceTiers(payload.trade?.offerPriceModel?.currentPrices ?? [], unit || null);
25
+ const images = uniqueNonEmpty([
26
+ ...(payload.gallery?.mainImage ?? []),
27
+ ...(payload.gallery?.offerImgList ?? []),
28
+ ...((payload.gallery?.wlImageInfos ?? []).map((item) => item.fullPathImageURI ?? '')),
29
+ ]);
30
+ const detailUrl = offerId ? buildDetailUrl(offerId) : href;
31
+ const provenance = buildProvenance(href || detailUrl);
32
+ return {
33
+ offer_id: offerId,
34
+ member_id: memberId,
35
+ shop_id: shopId,
36
+ title: cleanText(payload.offerTitle) || stripAlibabaSuffix(payload.title) || firstNonEmptyLine(bodyText) || null,
37
+ item_url: detailUrl,
38
+ main_images: images,
39
+ price_text: priceRange.price_text || null,
40
+ price_tiers: priceTiers,
41
+ currency: priceRange.currency,
42
+ moq_text: moq.moq_text || null,
43
+ moq_value: moq.moq_value,
44
+ seller_name: sellerName || null,
45
+ seller_url: sellerUrl,
46
+ shop_name: sellerName || null,
47
+ origin_place: extractLocation(bodyText),
48
+ delivery_days_text: extractDeliveryDaysText(bodyText, services, payload.shipping),
49
+ customization_text: extractKeywordLine(bodyText, ['来样定制', '来图定制', '支持定制', '可定制', '定制']),
50
+ private_label_text: extractKeywordLine(bodyText, ['贴牌', '贴标', '定制logo', '打logo', 'OEM', 'ODM']),
51
+ visible_attributes: attributes,
52
+ sales_text: extractSalesText(bodyText),
53
+ service_badges: serviceBadges,
54
+ stock_quantity: extractStockQuantity(bodyText),
55
+ ...provenance,
56
+ };
57
+ }
58
+ function normalizeVisibleAttributes(raw) {
59
+ if (!isRecord(raw))
60
+ return [];
61
+ return Object.entries(raw)
62
+ .filter(([key, value]) => key !== 'sellPointModel' && cleanText(key) && cleanText(String(value)))
63
+ .map(([key, value]) => ({ key: cleanText(key), value: cleanText(String(value)) }));
64
+ }
65
+ function uniqueServices(payload) {
66
+ const combined = [
67
+ ...(Array.isArray(payload.services) ? payload.services : []),
68
+ ...(Array.isArray(payload.shipping?.protectionInfos) ? payload.shipping.protectionInfos : []),
69
+ ...(Array.isArray(payload.shipping?.buyerProtectionModel) ? payload.shipping.buyerProtectionModel : []),
70
+ ];
71
+ const seen = new Set();
72
+ const result = [];
73
+ for (const service of combined) {
74
+ const key = cleanText(service.serviceName);
75
+ if (!key || seen.has(key))
76
+ continue;
77
+ seen.add(key);
78
+ result.push(service);
79
+ }
80
+ return result;
81
+ }
82
+ function stripAlibabaSuffix(title) {
83
+ return cleanText(title).replace(/\s*-\s*阿里巴巴$/, '').trim();
84
+ }
85
+ function firstNonEmptyLine(text) {
86
+ return text.split('\n').map((line) => cleanText(line)).find(Boolean) ?? '';
87
+ }
88
+ function extractMoqText(bodyText, beginAmount, unit) {
89
+ const lineMatch = bodyText.match(/\d+(?:\.\d+)?\s*(件|个|套|箱|包|双|台|把|只)\s*起批/);
90
+ if (lineMatch)
91
+ return lineMatch[0];
92
+ const moqValue = toNumber(beginAmount);
93
+ if (moqValue !== null) {
94
+ return `${moqValue}${unit || ''}起批`;
95
+ }
96
+ return '';
97
+ }
98
+ function extractDeliveryDaysText(bodyText, services, shipping) {
99
+ const shippingText = cleanText(shipping?.deliveryLimitText) || cleanText(shipping?.logisticsText);
100
+ if (shippingText)
101
+ return shippingText;
102
+ const textMatch = bodyText.match(/\d+\s*(?:小时|天)(?:内)?发货/);
103
+ if (textMatch)
104
+ return textMatch[0];
105
+ const hourMatch = services.find((service) => typeof service.agreeDeliveryHours === 'number');
106
+ if (hourMatch && typeof hourMatch.agreeDeliveryHours === 'number') {
107
+ return `${hourMatch.agreeDeliveryHours}小时内发货`;
108
+ }
109
+ return null;
110
+ }
111
+ function extractKeywordLine(bodyText, keywords) {
112
+ const lines = bodyText.split('\n').map((line) => cleanText(line)).filter(Boolean);
113
+ for (const line of lines) {
114
+ if (keywords.some((keyword) => line.includes(keyword))) {
115
+ return line;
116
+ }
117
+ }
118
+ return null;
119
+ }
120
+ function extractSalesText(bodyText) {
121
+ const match = bodyText.match(/(?:全网销量|已售)\s*\d+(?:\.\d+)?\+?\s*[件套个单]?/);
122
+ return match ? cleanText(match[0]) : null;
123
+ }
124
+ function extractStockQuantity(bodyText) {
125
+ const match = bodyText.match(/库存\s*(\d+)/);
126
+ return match ? Number.parseInt(match[1], 10) : null;
127
+ }
128
+ async function readItemPayload(page, itemUrl) {
129
+ const state = await gotoAndReadState(page, itemUrl, 2500, 'item');
130
+ assertAuthenticatedState(state, 'item');
131
+ const payload = await page.evaluate(`
132
+ (() => {
133
+ const root = window.context ?? {};
134
+ const model = root.result?.global?.globalData?.model ?? null;
135
+ const toJson = (value) => JSON.parse(JSON.stringify(value ?? null));
136
+ return {
137
+ href: window.location.href,
138
+ title: document.title || '',
139
+ bodyText: document.body ? document.body.innerText || '' : '',
140
+ offerTitle: model?.offerTitleModel?.subject ?? '',
141
+ offerId: model?.tradeModel?.offerId ?? '',
142
+ seller: toJson(model?.sellerModel),
143
+ trade: toJson(model?.tradeModel),
144
+ gallery: toJson(root.result?.data?.gallery?.fields ?? null),
145
+ shipping: toJson(root.result?.data?.shippingServices?.fields ?? null),
146
+ services: toJson(root.result?.data?.shippingServices?.fields?.protectionInfos ?? []),
147
+ };
148
+ })()
149
+ `);
150
+ const resolvedOfferId = cleanText(String(payload.offerId ?? '')) || extractOfferId(cleanText(payload.href));
151
+ if (!resolvedOfferId) {
152
+ throw new CommandExecutionError('1688 item page did not expose product context', '当前 tab 非商品详情上下文,请切到 detail.1688.com 商品页并重试');
153
+ }
154
+ return payload;
155
+ }
156
+ cli({
157
+ site: '1688',
158
+ name: 'item',
159
+ description: '1688 商品详情(公开商品字段、价格阶梯、卖家基础信息)',
160
+ domain: 'www.1688.com',
161
+ strategy: Strategy.COOKIE,
162
+ navigateBefore: false,
163
+ args: [
164
+ {
165
+ name: 'input',
166
+ required: true,
167
+ positional: true,
168
+ help: '1688 商品 URL 或 offer ID(如 887904326744)',
169
+ },
170
+ ],
171
+ columns: ['offer_id', 'title', 'price_text', 'moq_text', 'seller_name', 'origin_place'],
172
+ func: async (page, kwargs) => {
173
+ const itemUrl = buildDetailUrl(String(kwargs.input ?? ''));
174
+ const payload = await readItemPayload(page, itemUrl);
175
+ return [normalizeItemPayload(payload)];
176
+ },
177
+ });
178
+ export const __test__ = {
179
+ normalizeItemPayload,
180
+ normalizeVisibleAttributes,
181
+ stripAlibabaSuffix,
182
+ extractMoqText,
183
+ extractDeliveryDaysText,
184
+ extractKeywordLine,
185
+ extractSalesText,
186
+ extractStockQuantity,
187
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,67 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { __test__ } from './item.js';
3
+ describe('1688 item normalization', () => {
4
+ it('normalizes public item payload into contract fields', () => {
5
+ const result = __test__.normalizeItemPayload({
6
+ href: 'https://detail.1688.com/offer/887904326744.html',
7
+ title: '法式春季长袖开衫连衣裙女新款大码女装碎花吊带裙套装142077 - 阿里巴巴',
8
+ bodyText: `
9
+ 青岛沁澜衣品服装有限公司
10
+ 入驻13年
11
+ 主营:大码女装
12
+ 店铺回头率
13
+ 87%
14
+ 山东青岛
15
+ 3套起批
16
+ 已售1600+套
17
+ 支持定制logo
18
+ `,
19
+ offerTitle: '法式春季长袖开衫连衣裙女新款大码女装碎花吊带裙套装142077',
20
+ offerId: 887904326744,
21
+ seller: {
22
+ companyName: '青岛沁澜衣品服装有限公司',
23
+ memberId: 'b2b-1641351767',
24
+ winportUrl: 'https://yinuoweierfushi.1688.com/page/index.html?spm=a1',
25
+ },
26
+ trade: {
27
+ beginAmount: 3,
28
+ priceDisplay: '96.00-98.00',
29
+ unit: '套',
30
+ saleCount: 1655,
31
+ offerIDatacenterSellInfo: {
32
+ 面料名称: '莫代尔',
33
+ 主面料成分: '莫代尔纤维',
34
+ sellPointModel: '{"ignore":true}',
35
+ },
36
+ offerPriceModel: {
37
+ currentPrices: [
38
+ { beginAmount: 3, price: '98.00' },
39
+ { beginAmount: 50, price: '97.00' },
40
+ ],
41
+ },
42
+ },
43
+ gallery: {
44
+ mainImage: ['https://example.com/1.jpg'],
45
+ offerImgList: ['https://example.com/2.jpg'],
46
+ wlImageInfos: [{ fullPathImageURI: 'https://example.com/3.jpg' }],
47
+ },
48
+ services: [
49
+ { serviceName: '延期必赔', agreeDeliveryHours: 360 },
50
+ { serviceName: '品质保障' },
51
+ ],
52
+ });
53
+ expect(result.offer_id).toBe('887904326744');
54
+ expect(result.member_id).toBe('b2b-1641351767');
55
+ expect(result.shop_id).toBe('yinuoweierfushi');
56
+ expect(result.seller_url).toBe('https://yinuoweierfushi.1688.com');
57
+ expect(result.price_text).toBe('¥96.00-98.00');
58
+ expect(result.moq_text).toBe('3套起批');
59
+ expect(result.origin_place).toBe('山东青岛');
60
+ expect(result.delivery_days_text).toBe('360小时内发货');
61
+ expect(result.private_label_text).toBe('支持定制logo');
62
+ expect(result.visible_attributes).toEqual([
63
+ { key: '面料名称', value: '莫代尔' },
64
+ { key: '主面料成分', value: '莫代尔纤维' },
65
+ ]);
66
+ });
67
+ });
@@ -0,0 +1,56 @@
1
+ interface SearchPayload {
2
+ href?: string;
3
+ title?: string;
4
+ bodyText?: string;
5
+ next_url?: string;
6
+ candidates?: Array<{
7
+ item_url?: string;
8
+ title?: string;
9
+ container_text?: string;
10
+ desc_rows?: string[];
11
+ price_text?: string | null;
12
+ sales_text?: string | null;
13
+ hover_price_text?: string | null;
14
+ moq_text?: string | null;
15
+ tag_items?: string[];
16
+ hover_items?: string[];
17
+ seller_name?: string | null;
18
+ seller_url?: string | null;
19
+ }>;
20
+ }
21
+ interface SearchRow {
22
+ rank: number;
23
+ offer_id: string | null;
24
+ member_id: string | null;
25
+ shop_id: string | null;
26
+ title: string | null;
27
+ item_url: string | null;
28
+ seller_name: string | null;
29
+ seller_url: string | null;
30
+ price_text: string | null;
31
+ price_min: number | null;
32
+ price_max: number | null;
33
+ currency: string | null;
34
+ moq_text: string | null;
35
+ moq_value: number | null;
36
+ location: string | null;
37
+ badges: string[];
38
+ sales_text: string | null;
39
+ return_rate_text: string | null;
40
+ source_url: string;
41
+ fetched_at: string;
42
+ strategy: string;
43
+ }
44
+ declare function normalizeSearchCandidate(candidate: NonNullable<SearchPayload['candidates']>[number], sourceUrl: string): SearchRow;
45
+ declare function extractMoqText(text: string | null | undefined): string;
46
+ declare function extractSalesText(text: string | null | undefined): string;
47
+ declare function firstWord(text: string): string;
48
+ declare function buildDedupeKey(row: Pick<SearchRow, 'offer_id' | 'item_url'>): string | null;
49
+ export declare const __test__: {
50
+ normalizeSearchCandidate: typeof normalizeSearchCandidate;
51
+ extractMoqText: typeof extractMoqText;
52
+ extractSalesText: typeof extractSalesText;
53
+ firstWord: typeof firstWord;
54
+ buildDedupeKey: typeof buildDedupeKey;
55
+ };
56
+ export {};