@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,514 @@
1
+ import { ArgumentError, AuthRequiredError, CommandExecutionError } from '../../errors.js';
2
+ export const SITE = '1688';
3
+ export const HOME_URL = 'https://www.1688.com/';
4
+ export const SEARCH_URL_PREFIX = 'https://s.1688.com/selloffer/offer_search.htm?charset=utf8&keywords=';
5
+ export const DETAIL_URL_PREFIX = 'https://detail.1688.com/offer/';
6
+ export const STORE_MOBILE_URL_PREFIX = 'https://winport.m.1688.com/page/index.html?memberId=';
7
+ export const STRATEGY = 'cookie';
8
+ export const SEARCH_LIMIT_DEFAULT = 20;
9
+ export const SEARCH_LIMIT_MAX = 100;
10
+ const STORE_GENERIC_HOSTS = new Set(['www', 'detail', 's', 'winport', 'work', 'air', 'dj']);
11
+ const TRACKING_QUERY_KEYS = new Set([
12
+ 'spm',
13
+ 'tracelog',
14
+ 'clickid',
15
+ 'source',
16
+ 'scene',
17
+ 'from',
18
+ 'src',
19
+ 'ns',
20
+ 'cna',
21
+ 'pvid',
22
+ ]);
23
+ const CAPTCHA_URL_MARKER = '/_____tmd_____/punish';
24
+ const CAPTCHA_TEXT_PATTERNS = [
25
+ '请拖动下方滑块完成验证',
26
+ '请按住滑块,拖动到最右边',
27
+ '通过验证以确保正常访问',
28
+ '验证码拦截',
29
+ '访问验证',
30
+ '滑动验证',
31
+ ];
32
+ const LOGIN_TEXT_PATTERNS = [
33
+ '请登录',
34
+ '登录后',
35
+ '账号登录',
36
+ '手机登录',
37
+ '立即登录',
38
+ '扫码登录',
39
+ '请先完成登录',
40
+ '请先登录后查看',
41
+ ];
42
+ const LOGIN_URL_PATTERNS = ['/member/login', 'passport', 'login.taobao.com', 'account.1688.com'];
43
+ export const FACTORY_BADGE_PATTERNS = [
44
+ '源头工厂',
45
+ '深度验厂',
46
+ '实力工厂',
47
+ '工厂档案',
48
+ '加工专区',
49
+ '验厂报告',
50
+ '厂家直销',
51
+ '生产厂家',
52
+ '工厂直供',
53
+ ];
54
+ export const SERVICE_BADGE_PATTERNS = [
55
+ '延期必赔',
56
+ '品质保障',
57
+ '破损包赔',
58
+ '退货包运费',
59
+ '晚发必赔',
60
+ '7*24小时响应',
61
+ '48小时发货',
62
+ '72小时发货',
63
+ '后天达',
64
+ '包邮',
65
+ '闪电拿样',
66
+ ];
67
+ const CHINA_LOCATIONS = [
68
+ '北京',
69
+ '天津',
70
+ '上海',
71
+ '重庆',
72
+ '河北',
73
+ '山西',
74
+ '辽宁',
75
+ '吉林',
76
+ '黑龙江',
77
+ '江苏',
78
+ '浙江',
79
+ '安徽',
80
+ '福建',
81
+ '江西',
82
+ '山东',
83
+ '河南',
84
+ '湖北',
85
+ '湖南',
86
+ '广东',
87
+ '海南',
88
+ '四川',
89
+ '贵州',
90
+ '云南',
91
+ '陕西',
92
+ '甘肃',
93
+ '青海',
94
+ '台湾',
95
+ '内蒙古',
96
+ '广西',
97
+ '西藏',
98
+ '宁夏',
99
+ '新疆',
100
+ '香港',
101
+ '澳门',
102
+ ];
103
+ export function cleanText(value) {
104
+ return typeof value === 'string'
105
+ ? value.replace(/\u00a0/g, ' ').replace(/\s+/g, ' ').trim()
106
+ : '';
107
+ }
108
+ export function cleanMultilineText(value) {
109
+ return typeof value === 'string'
110
+ ? value
111
+ .replace(/\u00a0/g, ' ')
112
+ .split('\n')
113
+ .map((line) => line.replace(/\s+/g, ' ').trim())
114
+ .filter(Boolean)
115
+ .join('\n')
116
+ : '';
117
+ }
118
+ export function uniqueNonEmpty(values) {
119
+ return [...new Set(values.map((value) => cleanText(value)).filter(Boolean))];
120
+ }
121
+ export function parseSearchLimit(input) {
122
+ const parsed = Number.parseInt(String(input ?? SEARCH_LIMIT_DEFAULT), 10);
123
+ if (!Number.isFinite(parsed) || parsed < 1) {
124
+ throw new ArgumentError('1688 search --limit must be a positive integer', 'Example: opencli 1688 search "桌面置物架" --limit 20');
125
+ }
126
+ return Math.min(SEARCH_LIMIT_MAX, parsed);
127
+ }
128
+ export function buildSearchUrl(query) {
129
+ const normalized = cleanText(query);
130
+ if (!normalized) {
131
+ throw new ArgumentError('1688 search query cannot be empty', 'Example: opencli 1688 search "桌面置物架" --limit 20');
132
+ }
133
+ return `${SEARCH_URL_PREFIX}${encodeURIComponent(normalized)}`;
134
+ }
135
+ export function buildDetailUrl(input) {
136
+ const offerId = extractOfferId(input);
137
+ if (!offerId) {
138
+ throw new ArgumentError('1688 item expects an offer URL or offer ID', 'Example: opencli 1688 item 887904326744');
139
+ }
140
+ return `${DETAIL_URL_PREFIX}${offerId}.html`;
141
+ }
142
+ export function resolveStoreUrl(input) {
143
+ const normalized = cleanText(input);
144
+ if (!normalized) {
145
+ throw new ArgumentError('1688 store expects a store URL or member ID', 'Example: opencli 1688 store https://yinuoweierfushi.1688.com/');
146
+ }
147
+ const memberId = extractMemberId(normalized);
148
+ if (memberId) {
149
+ return `${STORE_MOBILE_URL_PREFIX}${memberId}`;
150
+ }
151
+ if (/^https?:\/\//i.test(normalized)) {
152
+ return canonicalizeStoreUrl(normalized);
153
+ }
154
+ if (normalized.endsWith('.1688.com')) {
155
+ return canonicalizeStoreUrl(`https://${normalized}`);
156
+ }
157
+ if (/^[a-z0-9-]+$/i.test(normalized)) {
158
+ return canonicalizeStoreUrl(`https://${normalized}.1688.com`);
159
+ }
160
+ throw new ArgumentError('1688 store expects a store URL or member ID', 'Example: opencli 1688 store b2b-22154705262941f196');
161
+ }
162
+ export function canonicalizeStoreUrl(input) {
163
+ const url = parse1688Url(input);
164
+ const memberId = extractMemberId(url.toString());
165
+ if (memberId) {
166
+ return `${STORE_MOBILE_URL_PREFIX}${memberId}`;
167
+ }
168
+ const host = normalizeStoreHost(url.hostname);
169
+ if (!host) {
170
+ throw new ArgumentError('Invalid 1688 store URL', 'Example: opencli 1688 store https://yinuoweierfushi.1688.com/');
171
+ }
172
+ return `https://${host}`;
173
+ }
174
+ export function canonicalizeItemUrl(input) {
175
+ const offerId = extractOfferId(input);
176
+ if (offerId) {
177
+ return `${DETAIL_URL_PREFIX}${offerId}.html`;
178
+ }
179
+ const url = parse1688UrlOrNull(input);
180
+ if (!url)
181
+ return null;
182
+ stripTrackingParams(url);
183
+ url.hash = '';
184
+ return url.toString();
185
+ }
186
+ export function canonicalizeSellerUrl(input) {
187
+ const memberId = extractMemberId(input);
188
+ if (memberId) {
189
+ return `${STORE_MOBILE_URL_PREFIX}${memberId}`;
190
+ }
191
+ const url = parse1688UrlOrNull(input);
192
+ if (!url)
193
+ return null;
194
+ const host = normalizeStoreHost(url.hostname);
195
+ if (!host)
196
+ return null;
197
+ return `https://${host}`;
198
+ }
199
+ export function extractOfferId(input) {
200
+ const normalized = cleanText(input);
201
+ if (!normalized)
202
+ return null;
203
+ const directId = normalized.match(/^\d{6,}$/)?.[0];
204
+ if (directId)
205
+ return directId;
206
+ const detailMatch = normalized.match(/\/offer\/(\d{6,})\.html/i);
207
+ if (detailMatch)
208
+ return detailMatch[1];
209
+ const queryMatch = normalized.match(/[?&]offerId=(\d{6,})/i);
210
+ if (queryMatch)
211
+ return queryMatch[1];
212
+ return null;
213
+ }
214
+ export function extractMemberId(input) {
215
+ const normalized = cleanText(input);
216
+ if (!normalized)
217
+ return null;
218
+ const direct = normalized.match(/\bb2b-[a-z0-9]+\b/i)?.[0];
219
+ if (direct)
220
+ return direct;
221
+ const queryMatch = normalized.match(/[?&]memberId=(b2b-[a-z0-9]+)/i);
222
+ if (queryMatch)
223
+ return queryMatch[1];
224
+ const mobileMatch = normalized.match(/\/winport\/(b2b-[a-z0-9]+)\.html/i);
225
+ if (mobileMatch)
226
+ return mobileMatch[1];
227
+ return null;
228
+ }
229
+ export function extractShopId(input) {
230
+ const normalized = cleanText(input);
231
+ if (!normalized)
232
+ return null;
233
+ try {
234
+ const url = new URL(/^https?:\/\//i.test(normalized) ? normalized : `https://${normalized}`);
235
+ const host = normalizeStoreHost(url.hostname);
236
+ if (!host)
237
+ return null;
238
+ return host.split('.')[0] ?? null;
239
+ }
240
+ catch {
241
+ return /^[a-z0-9-]+$/i.test(normalized) ? normalized : null;
242
+ }
243
+ }
244
+ export function buildProvenance(sourceUrl) {
245
+ return {
246
+ source_url: sourceUrl,
247
+ fetched_at: new Date().toISOString(),
248
+ strategy: STRATEGY,
249
+ };
250
+ }
251
+ export function parsePriceText(text) {
252
+ const normalized = normalizeNumericText(cleanText(text));
253
+ const matches = normalized.match(/\d+(?:,\d{3})*(?:\.\d+)?/g) ?? [];
254
+ const values = matches
255
+ .map((value) => Number.parseFloat(value.replace(/,/g, '')))
256
+ .filter((value) => Number.isFinite(value));
257
+ if (values.length === 0) {
258
+ return {
259
+ price_text: normalized,
260
+ price_min: null,
261
+ price_max: null,
262
+ currency: null,
263
+ };
264
+ }
265
+ return {
266
+ price_text: normalized,
267
+ price_min: values[0] ?? null,
268
+ price_max: values[values.length - 1] ?? values[0] ?? null,
269
+ currency: normalized.includes('¥') || normalized.includes('元') ? 'CNY' : null,
270
+ };
271
+ }
272
+ export function normalizePriceTiers(rawTiers, unit) {
273
+ return rawTiers
274
+ .map((tier) => {
275
+ const quantityMin = toNumber(tier.beginAmount);
276
+ const priceText = cleanText(tier.price);
277
+ const price = toNumber(tier.price);
278
+ return {
279
+ quantity_text: quantityMin !== null ? `${quantityMin}${unit ?? ''}` : '',
280
+ quantity_min: quantityMin,
281
+ price_text: priceText,
282
+ price,
283
+ currency: priceText ? 'CNY' : null,
284
+ };
285
+ })
286
+ .filter((tier) => tier.price_text);
287
+ }
288
+ export function parseMoqText(text) {
289
+ const normalized = normalizeNumericText(cleanText(text));
290
+ const match = normalized.match(/(\d+(?:\.\d+)?)\s*(件|个|套|箱|包|双|台|把|只|pcs|piece|pieces)?\s*起批/i)
291
+ ?? normalized.match(/≥\s*(\d+(?:\.\d+)?)/);
292
+ const rangeMatch = normalized.match(/(\d+(?:\.\d+)?)\s*(?:~|-|至|到)\s*\d+(?:\.\d+)?\s*(件|个|套|箱|包|双|台|把|只|pcs|piece|pieces)/i);
293
+ if (!match && !rangeMatch) {
294
+ return {
295
+ moq_text: normalized,
296
+ moq_value: null,
297
+ };
298
+ }
299
+ return {
300
+ moq_text: normalized,
301
+ moq_value: Number.parseFloat((match ?? rangeMatch)[1]),
302
+ };
303
+ }
304
+ export function extractLocation(text) {
305
+ const normalized = cleanMultilineText(text);
306
+ const primaryRegion = normalized.split(/送至|发往/)[0] ?? normalized;
307
+ const lines = primaryRegion.split('\n');
308
+ for (const line of lines) {
309
+ const compact = cleanText(line);
310
+ if (!compact || compact.length > 16)
311
+ continue;
312
+ if (CHINA_LOCATIONS.some((location) => compact.startsWith(location))) {
313
+ return compact;
314
+ }
315
+ }
316
+ const locationPattern = new RegExp(`(${CHINA_LOCATIONS.join('|')})[\\u4e00-\\u9fa5]{0,8}`);
317
+ return primaryRegion.match(locationPattern)?.[0] ?? null;
318
+ }
319
+ export function extractAddress(text) {
320
+ const normalized = cleanMultilineText(text);
321
+ const lineMatch = normalized.match(/地址[::]\s*([^\n]+)/);
322
+ if (lineMatch)
323
+ return cleanText(lineMatch[1]);
324
+ return normalized
325
+ .split('\n')
326
+ .map((line) => cleanText(line))
327
+ .find((line) => line.includes('省') || line.includes('市') || line.includes('区') || line.includes('县'))
328
+ ?? null;
329
+ }
330
+ export function extractMetric(text, label) {
331
+ const normalized = cleanMultilineText(text);
332
+ const direct = normalized.match(new RegExp(`(?:^|\\n)\\s*${escapeForRegex(label)}[::]?\\s*([^\\n]+)`));
333
+ if (direct)
334
+ return cleanText(direct[1]);
335
+ const lineBased = normalized.match(new RegExp(`(?:^|\\n)\\s*${escapeForRegex(label)}\\n([^\\n]+)`));
336
+ return lineBased ? cleanText(lineBased[1]) : null;
337
+ }
338
+ export function extractYearsOnPlatform(text) {
339
+ return text.match(/入驻\d+年/)?.[0] ?? null;
340
+ }
341
+ export function extractMainBusiness(text) {
342
+ const value = extractMetric(text, '主营');
343
+ return value ? value.replace(/^:/, '').trim() : null;
344
+ }
345
+ export function extractBadges(text, candidates) {
346
+ return uniqueNonEmpty(candidates.filter((candidate) => cleanMultilineText(text).includes(candidate)));
347
+ }
348
+ export function guessTopCategories(text) {
349
+ const mainBusiness = extractMainBusiness(text);
350
+ if (!mainBusiness)
351
+ return [];
352
+ return uniqueNonEmpty(mainBusiness.split(/[、,/|]/).map((value) => value.trim()));
353
+ }
354
+ export function isCaptchaState(state) {
355
+ const href = cleanText(state.href).toLowerCase();
356
+ const title = cleanText(state.title);
357
+ const bodyText = cleanMultilineText(state.body_text);
358
+ if (href.includes(CAPTCHA_URL_MARKER))
359
+ return true;
360
+ return CAPTCHA_TEXT_PATTERNS.some((pattern) => title.includes(pattern) || bodyText.includes(pattern));
361
+ }
362
+ export function isLoginState(state) {
363
+ const href = cleanText(state.href).toLowerCase();
364
+ const title = cleanText(state.title);
365
+ const bodyText = cleanMultilineText(state.body_text);
366
+ if (LOGIN_URL_PATTERNS.some((pattern) => href.includes(pattern)))
367
+ return true;
368
+ return LOGIN_TEXT_PATTERNS.some((pattern) => title.includes(pattern) || bodyText.includes(pattern));
369
+ }
370
+ export function buildCaptchaHint(action) {
371
+ return [
372
+ `Open a clean 1688 ${action} page in the shared Chrome profile and finish any slider challenge first.`,
373
+ 'If you run opencli via CDP, set OPENCLI_CDP_TARGET=1688.com or a more specific 1688 host before retrying.',
374
+ ].join(' ');
375
+ }
376
+ export async function readPageState(page) {
377
+ const result = await page.evaluate(`
378
+ (() => ({
379
+ href: window.location.href,
380
+ title: document.title || '',
381
+ body_text: document.body ? document.body.innerText || '' : '',
382
+ }))()
383
+ `);
384
+ return {
385
+ href: cleanText(result.href),
386
+ title: cleanText(result.title),
387
+ body_text: cleanMultilineText(result.body_text),
388
+ };
389
+ }
390
+ export async function gotoAndReadState(page, url, settleMs = 2500, action = 'page') {
391
+ try {
392
+ await page.goto(url, { settleMs });
393
+ await page.wait(1.5);
394
+ return readPageState(page);
395
+ }
396
+ catch (error) {
397
+ const message = error instanceof Error ? error.message : String(error);
398
+ if (message.includes('Inspected target navigated or closed')
399
+ || message.includes('Cannot find context with specified id')
400
+ || message.includes('Target closed')) {
401
+ throw new CommandExecutionError(`1688 ${action} navigation lost the current browser target`, `${buildCaptchaHint(action)} If CDP is attached to a stale or blocked tab, open a fresh 1688 tab and point OPENCLI_CDP_TARGET at that tab.`);
402
+ }
403
+ throw error;
404
+ }
405
+ }
406
+ export async function ensure1688Session(page) {
407
+ const state = await gotoAndReadState(page, HOME_URL, 1500, 'homepage');
408
+ assertAuthenticatedState(state, 'homepage');
409
+ }
410
+ export function assertAuthenticatedState(state, action) {
411
+ if (!isCaptchaState(state) && !isLoginState(state))
412
+ return;
413
+ throw new AuthRequiredError('1688.com', `请先在共享 Chrome 完成 1688 登录/验证,再重试(${action})`);
414
+ }
415
+ export function assertNotCaptcha(state, action) {
416
+ assertAuthenticatedState(state, action);
417
+ }
418
+ export function toNumber(value) {
419
+ if (typeof value === 'number' && Number.isFinite(value)) {
420
+ return value;
421
+ }
422
+ if (typeof value === 'string') {
423
+ const normalized = value.replace(/,/g, '').trim();
424
+ if (!normalized)
425
+ return null;
426
+ const parsed = Number.parseFloat(normalized);
427
+ return Number.isFinite(parsed) ? parsed : null;
428
+ }
429
+ return null;
430
+ }
431
+ export function limitCandidates(values, limit) {
432
+ const normalizedLimit = Math.max(1, Math.trunc(limit) || 1);
433
+ return values.slice(0, normalizedLimit);
434
+ }
435
+ function normalizeNumericText(value) {
436
+ return value
437
+ .replace(/([¥$€])\s+(?=\d)/g, '$1')
438
+ .replace(/(\d)\s*\.\s*(\d)/g, '$1.$2')
439
+ .replace(/\s*([~-])\s*/g, '$1')
440
+ .trim();
441
+ }
442
+ function escapeForRegex(value) {
443
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
444
+ }
445
+ function parse1688Url(input) {
446
+ const normalized = cleanText(input);
447
+ try {
448
+ const url = new URL(normalized);
449
+ if (!url.hostname.endsWith('.1688.com') && url.hostname !== '1688.com' && url.hostname !== 'www.1688.com') {
450
+ throw new Error('invalid-host');
451
+ }
452
+ stripTrackingParams(url);
453
+ url.hash = '';
454
+ return url;
455
+ }
456
+ catch {
457
+ throw new ArgumentError('Invalid 1688 URL', 'Use a URL under 1688.com (for example: https://detail.1688.com/offer/887904326744.html)');
458
+ }
459
+ }
460
+ function parse1688UrlOrNull(input) {
461
+ try {
462
+ return parse1688Url(input);
463
+ }
464
+ catch {
465
+ return null;
466
+ }
467
+ }
468
+ function normalizeStoreHost(hostname) {
469
+ const lower = cleanText(hostname).toLowerCase();
470
+ if (!lower.endsWith('.1688.com'))
471
+ return null;
472
+ const [subdomain] = lower.split('.');
473
+ if (!subdomain || STORE_GENERIC_HOSTS.has(subdomain))
474
+ return null;
475
+ return lower;
476
+ }
477
+ function stripTrackingParams(url) {
478
+ const keys = [...url.searchParams.keys()];
479
+ for (const key of keys) {
480
+ if (TRACKING_QUERY_KEYS.has(key) || key.toLowerCase().startsWith('utm_')) {
481
+ url.searchParams.delete(key);
482
+ }
483
+ }
484
+ }
485
+ export const __test__ = {
486
+ SEARCH_LIMIT_DEFAULT,
487
+ SEARCH_LIMIT_MAX,
488
+ parseSearchLimit,
489
+ buildSearchUrl,
490
+ buildDetailUrl,
491
+ resolveStoreUrl,
492
+ canonicalizeStoreUrl,
493
+ canonicalizeItemUrl,
494
+ canonicalizeSellerUrl,
495
+ extractOfferId,
496
+ extractMemberId,
497
+ extractShopId,
498
+ parsePriceText,
499
+ normalizePriceTiers,
500
+ parseMoqText,
501
+ extractLocation,
502
+ extractAddress,
503
+ extractMetric,
504
+ extractYearsOnPlatform,
505
+ extractMainBusiness,
506
+ extractBadges,
507
+ guessTopCategories,
508
+ isCaptchaState,
509
+ isLoginState,
510
+ cleanText,
511
+ cleanMultilineText,
512
+ uniqueNonEmpty,
513
+ limitCandidates,
514
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { __test__ } from './shared.js';
3
+ describe('1688 shared helpers', () => {
4
+ it('builds encoded search URLs and validates limit', () => {
5
+ expect(__test__.buildSearchUrl('置物架')).toBe('https://s.1688.com/selloffer/offer_search.htm?charset=utf8&keywords=%E7%BD%AE%E7%89%A9%E6%9E%B6');
6
+ expect(() => __test__.buildSearchUrl(' ')).toThrowError(/cannot be empty/i);
7
+ expect(__test__.parseSearchLimit(3)).toBe(3);
8
+ expect(__test__.parseSearchLimit('1000')).toBe(__test__.SEARCH_LIMIT_MAX);
9
+ expect(() => __test__.parseSearchLimit('0')).toThrowError(/positive integer/i);
10
+ });
11
+ it('extracts IDs and canonicalizes urls', () => {
12
+ expect(__test__.extractOfferId('887904326744')).toBe('887904326744');
13
+ expect(__test__.extractOfferId('https://detail.1688.com/offer/887904326744.html')).toBe('887904326744');
14
+ expect(__test__.extractMemberId('https://winport.m.1688.com/page/index.html?memberId=b2b-1641351767')).toBe('b2b-1641351767');
15
+ expect(__test__.extractMemberId('b2b-22154705262941f196')).toBe('b2b-22154705262941f196');
16
+ expect(__test__.resolveStoreUrl('b2b-22154705262941f196')).toBe('https://winport.m.1688.com/page/index.html?memberId=b2b-22154705262941f196');
17
+ expect(__test__.canonicalizeStoreUrl('https://yinuoweierfushi.1688.com/page/index.html?spm=foo')).toBe('https://yinuoweierfushi.1688.com');
18
+ expect(__test__.canonicalizeItemUrl('http://detail.m.1688.com/page/index.html?offerId=910933345396&spm=x')).toBe('https://detail.1688.com/offer/910933345396.html');
19
+ expect(__test__.canonicalizeSellerUrl('https://yinuoweierfushi.1688.com/page/contactinfo.html?tracelog=1')).toBe('https://yinuoweierfushi.1688.com');
20
+ expect(__test__.extractShopId('https://yinuoweierfushi.1688.com/page/index.html')).toBe('yinuoweierfushi');
21
+ });
22
+ it('parses price ranges and moq text', () => {
23
+ expect(__test__.parsePriceText('¥96.00-98.00')).toEqual({
24
+ price_text: '¥96.00-98.00',
25
+ price_min: 96,
26
+ price_max: 98,
27
+ currency: 'CNY',
28
+ });
29
+ expect(__test__.parsePriceText('¥ 14 .28')).toEqual({
30
+ price_text: '¥14.28',
31
+ price_min: 14.28,
32
+ price_max: 14.28,
33
+ currency: 'CNY',
34
+ });
35
+ expect(__test__.parseMoqText('3套起批')).toEqual({
36
+ moq_text: '3套起批',
37
+ moq_value: 3,
38
+ });
39
+ expect(__test__.parseMoqText('2~999个')).toEqual({
40
+ moq_text: '2~999个',
41
+ moq_value: 2,
42
+ });
43
+ });
44
+ it('detects captcha and login states', () => {
45
+ expect(__test__.extractLocation('山东青岛 送至 江苏苏州')).toBe('山东青岛');
46
+ expect(__test__.isCaptchaState({
47
+ href: 'https://s.1688.com/_____tmd_____/punish',
48
+ title: '验证码拦截',
49
+ body_text: '请拖动下方滑块完成验证',
50
+ })).toBe(true);
51
+ expect(__test__.isLoginState({
52
+ href: 'https://login.taobao.com/member/login.jhtml',
53
+ title: '账号登录',
54
+ body_text: '请登录后继续',
55
+ })).toBe(true);
56
+ });
57
+ });
@@ -0,0 +1,45 @@
1
+ interface StoreBrowserPayload {
2
+ href?: string;
3
+ title?: string;
4
+ bodyText?: string;
5
+ offerLinks?: string[];
6
+ contactLinks?: string[];
7
+ }
8
+ interface StoreItemSeed {
9
+ href?: string;
10
+ bodyText?: string;
11
+ seller?: {
12
+ companyName?: string;
13
+ memberId?: string;
14
+ winportUrl?: string;
15
+ sellerWinportUrlMap?: Record<string, string>;
16
+ };
17
+ services?: Array<{
18
+ serviceName?: string;
19
+ }>;
20
+ }
21
+ declare function normalizeStorePayload(input: {
22
+ resolvedUrl: string;
23
+ storePayload: StoreBrowserPayload | null;
24
+ contactPayload: StoreBrowserPayload | null;
25
+ seed: StoreItemSeed | null;
26
+ explicitMemberId: string | null;
27
+ }): Record<string, unknown>;
28
+ declare function safeCanonicalStoreUrl(url: string): string | null;
29
+ declare function buildContactUrl(storeUrl: string): string | null;
30
+ declare function firstNamedLine(text: string): string | null;
31
+ declare function firstMetric(text: string, labels: string[]): string | null;
32
+ declare function extractReturnRate(text: string): string | null;
33
+ declare function firstOfferId(links: string[]): string | null;
34
+ declare function firstContactUrl(links: string[]): string | null;
35
+ export declare const __test__: {
36
+ normalizeStorePayload: typeof normalizeStorePayload;
37
+ safeCanonicalStoreUrl: typeof safeCanonicalStoreUrl;
38
+ buildContactUrl: typeof buildContactUrl;
39
+ firstNamedLine: typeof firstNamedLine;
40
+ firstMetric: typeof firstMetric;
41
+ extractReturnRate: typeof extractReturnRate;
42
+ firstOfferId: typeof firstOfferId;
43
+ firstContactUrl: typeof firstContactUrl;
44
+ };
45
+ export {};