@jackwener/opencli 1.1.1 → 1.2.0

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 (509) hide show
  1. package/CONTRIBUTING.md +39 -1
  2. package/README.md +9 -10
  3. package/README.zh-CN.md +39 -17
  4. package/SKILL.md +10 -5
  5. package/dist/browser/cdp.d.ts +4 -4
  6. package/dist/browser/cdp.js +39 -16
  7. package/dist/browser/daemon-client.d.ts +2 -1
  8. package/dist/browser/dom-helpers.js +38 -7
  9. package/dist/browser/dom-snapshot.d.ts +86 -0
  10. package/dist/browser/dom-snapshot.js +729 -0
  11. package/dist/browser/dom-snapshot.test.d.ts +11 -0
  12. package/dist/browser/dom-snapshot.test.js +212 -0
  13. package/dist/browser/index.d.ts +2 -0
  14. package/dist/browser/index.js +1 -0
  15. package/dist/browser/page.d.ts +14 -24
  16. package/dist/browser/page.js +37 -4
  17. package/dist/build-manifest.d.ts +11 -4
  18. package/dist/build-manifest.js +59 -21
  19. package/dist/build-manifest.test.js +58 -2
  20. package/dist/cli-manifest.json +3856 -1509
  21. package/dist/cli.js +66 -0
  22. package/dist/clis/barchart/greeks.js +1 -1
  23. package/dist/clis/barchart/options.js +1 -1
  24. package/dist/clis/barchart/quote.js +1 -1
  25. package/dist/clis/bilibili/download.js +1 -1
  26. package/dist/clis/bilibili/following.js +1 -1
  27. package/dist/clis/bilibili/subtitle.js +1 -1
  28. package/dist/clis/bilibili/user-videos.js +1 -1
  29. package/dist/clis/boss/batchgreet.js +10 -97
  30. package/dist/clis/boss/chatlist.js +8 -25
  31. package/dist/clis/boss/chatmsg.js +11 -42
  32. package/dist/clis/boss/common.d.ts +92 -0
  33. package/dist/clis/boss/common.js +223 -0
  34. package/dist/clis/boss/detail.js +7 -49
  35. package/dist/clis/boss/exchange.js +13 -79
  36. package/dist/clis/boss/greet.js +18 -145
  37. package/dist/clis/boss/invite.js +26 -121
  38. package/dist/clis/boss/joblist.js +6 -31
  39. package/dist/clis/boss/mark.js +12 -85
  40. package/dist/clis/boss/recommend.js +10 -49
  41. package/dist/clis/boss/resume.js +18 -118
  42. package/dist/clis/boss/search.js +12 -60
  43. package/dist/clis/boss/send.js +17 -151
  44. package/dist/clis/boss/stats.js +18 -69
  45. package/dist/clis/coupang/add-to-cart.js +1 -1
  46. package/dist/clis/devto/tag.yaml +34 -0
  47. package/dist/clis/devto/top.yaml +29 -0
  48. package/dist/clis/devto/user.yaml +33 -0
  49. package/dist/clis/douban/book-hot.d.ts +1 -0
  50. package/dist/clis/douban/book-hot.js +14 -0
  51. package/dist/clis/douban/marks.d.ts +1 -0
  52. package/dist/clis/douban/marks.js +115 -0
  53. package/dist/clis/douban/movie-hot.d.ts +1 -0
  54. package/dist/clis/douban/movie-hot.js +14 -0
  55. package/dist/clis/douban/reviews.d.ts +1 -0
  56. package/dist/clis/douban/reviews.js +106 -0
  57. package/dist/clis/douban/search.d.ts +1 -0
  58. package/dist/clis/douban/search.js +16 -0
  59. package/dist/clis/douban/shared.d.ts +4 -0
  60. package/dist/clis/douban/shared.js +155 -0
  61. package/dist/clis/douban/subject.yaml +76 -0
  62. package/dist/clis/douban/top250.yaml +70 -0
  63. package/dist/clis/douban/utils.d.ts +35 -0
  64. package/dist/clis/douban/utils.js +48 -0
  65. package/dist/clis/facebook/add-friend.yaml +43 -0
  66. package/dist/clis/facebook/events.yaml +44 -0
  67. package/dist/clis/facebook/feed.yaml +63 -0
  68. package/dist/clis/facebook/friends.yaml +42 -0
  69. package/dist/clis/facebook/groups.yaml +50 -0
  70. package/dist/clis/facebook/join-group.yaml +44 -0
  71. package/dist/clis/facebook/memories.yaml +39 -0
  72. package/dist/clis/facebook/notifications.yaml +40 -0
  73. package/dist/clis/facebook/profile.yaml +37 -0
  74. package/dist/clis/facebook/search.yaml +46 -0
  75. package/dist/clis/google/news.d.ts +5 -0
  76. package/dist/clis/google/news.js +58 -0
  77. package/dist/clis/google/search.d.ts +10 -0
  78. package/dist/clis/google/search.js +127 -0
  79. package/dist/clis/google/suggest.d.ts +5 -0
  80. package/dist/clis/google/suggest.js +34 -0
  81. package/dist/clis/google/trends.d.ts +5 -0
  82. package/dist/clis/google/trends.js +38 -0
  83. package/dist/clis/google/utils.d.ts +9 -0
  84. package/dist/clis/google/utils.js +23 -0
  85. package/dist/clis/google/utils.test.d.ts +1 -0
  86. package/dist/clis/google/utils.test.js +75 -0
  87. package/dist/clis/grok/ask.d.ts +14 -0
  88. package/dist/clis/grok/ask.js +257 -65
  89. package/dist/clis/grok/ask.test.d.ts +1 -0
  90. package/dist/clis/grok/ask.test.js +36 -0
  91. package/dist/clis/instagram/comment.yaml +52 -0
  92. package/dist/clis/instagram/explore.yaml +43 -0
  93. package/dist/clis/instagram/follow.yaml +41 -0
  94. package/dist/clis/instagram/followers.yaml +51 -0
  95. package/dist/clis/instagram/following.yaml +51 -0
  96. package/dist/clis/instagram/like.yaml +46 -0
  97. package/dist/clis/instagram/profile.yaml +42 -0
  98. package/dist/clis/instagram/save.yaml +46 -0
  99. package/dist/clis/instagram/saved.yaml +40 -0
  100. package/dist/clis/instagram/search.yaml +43 -0
  101. package/dist/clis/instagram/unfollow.yaml +38 -0
  102. package/dist/clis/instagram/unlike.yaml +46 -0
  103. package/dist/clis/instagram/unsave.yaml +46 -0
  104. package/dist/clis/instagram/user.yaml +54 -0
  105. package/dist/clis/jike/repost.js +1 -1
  106. package/dist/clis/jimeng/generate.yaml +1 -0
  107. package/dist/clis/linux-do/category.yaml +1 -0
  108. package/dist/clis/lobsters/active.yaml +29 -0
  109. package/dist/clis/lobsters/hot.yaml +29 -0
  110. package/dist/clis/lobsters/newest.yaml +29 -0
  111. package/dist/clis/lobsters/tag.yaml +34 -0
  112. package/dist/clis/medium/feed.d.ts +1 -0
  113. package/dist/clis/medium/feed.js +15 -0
  114. package/dist/clis/medium/search.d.ts +1 -0
  115. package/dist/clis/medium/search.js +15 -0
  116. package/dist/clis/medium/shared.d.ts +5 -0
  117. package/dist/clis/medium/shared.js +78 -0
  118. package/dist/clis/medium/user.d.ts +1 -0
  119. package/dist/clis/medium/user.js +15 -0
  120. package/dist/clis/reddit/comment.js +1 -1
  121. package/dist/clis/reddit/read.js +1 -1
  122. package/dist/clis/reddit/save.js +1 -1
  123. package/dist/clis/reddit/subreddit.yaml +1 -0
  124. package/dist/clis/reddit/subscribe.js +1 -1
  125. package/dist/clis/reddit/upvote.js +1 -1
  126. package/dist/clis/sinablog/article.d.ts +1 -0
  127. package/dist/clis/sinablog/article.js +14 -0
  128. package/dist/clis/sinablog/hot.d.ts +1 -0
  129. package/dist/clis/sinablog/hot.js +14 -0
  130. package/dist/clis/sinablog/search.d.ts +1 -0
  131. package/dist/clis/sinablog/search.js +51 -0
  132. package/dist/clis/sinablog/shared.d.ts +7 -0
  133. package/dist/clis/sinablog/shared.js +187 -0
  134. package/dist/clis/sinablog/user.d.ts +1 -0
  135. package/dist/clis/sinablog/user.js +15 -0
  136. package/dist/clis/substack/feed.d.ts +1 -0
  137. package/dist/clis/substack/feed.js +15 -0
  138. package/dist/clis/substack/publication.d.ts +1 -0
  139. package/dist/clis/substack/publication.js +15 -0
  140. package/dist/clis/substack/search.d.ts +1 -0
  141. package/dist/clis/substack/search.js +77 -0
  142. package/dist/clis/substack/shared.d.ts +4 -0
  143. package/dist/clis/substack/shared.js +129 -0
  144. package/dist/clis/tiktok/comment.yaml +66 -0
  145. package/dist/clis/tiktok/explore.yaml +39 -0
  146. package/dist/clis/tiktok/follow.yaml +39 -0
  147. package/dist/clis/tiktok/following.yaml +46 -0
  148. package/dist/clis/tiktok/friends.yaml +47 -0
  149. package/dist/clis/tiktok/like.yaml +38 -0
  150. package/dist/clis/tiktok/live.yaml +51 -0
  151. package/dist/clis/tiktok/notifications.yaml +52 -0
  152. package/dist/clis/tiktok/profile.yaml +45 -0
  153. package/dist/clis/tiktok/save.yaml +34 -0
  154. package/dist/clis/tiktok/search.yaml +46 -0
  155. package/dist/clis/tiktok/unfollow.yaml +44 -0
  156. package/dist/clis/tiktok/unlike.yaml +38 -0
  157. package/dist/clis/tiktok/unsave.yaml +36 -0
  158. package/dist/clis/tiktok/user.yaml +44 -0
  159. package/dist/clis/twitter/download.d.ts +1 -1
  160. package/dist/clis/twitter/download.js +3 -3
  161. package/dist/clis/twitter/followers.js +1 -1
  162. package/dist/clis/twitter/following.js +1 -1
  163. package/dist/clis/twitter/thread.js +1 -1
  164. package/dist/clis/twitter/timeline.d.ts +23 -0
  165. package/dist/clis/twitter/timeline.js +42 -14
  166. package/dist/clis/twitter/timeline.test.d.ts +1 -0
  167. package/dist/clis/twitter/timeline.test.js +102 -0
  168. package/dist/clis/wikipedia/random.d.ts +1 -0
  169. package/dist/clis/wikipedia/random.js +19 -0
  170. package/dist/clis/wikipedia/search.js +3 -3
  171. package/dist/clis/wikipedia/summary.js +4 -9
  172. package/dist/clis/wikipedia/trending.d.ts +1 -0
  173. package/dist/clis/wikipedia/trending.js +35 -0
  174. package/dist/clis/wikipedia/utils.d.ts +28 -0
  175. package/dist/clis/wikipedia/utils.js +13 -0
  176. package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
  177. package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
  178. package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
  179. package/dist/clis/xiaohongshu/download.js +1 -1
  180. package/dist/clis/xueqiu/earnings-date.yaml +69 -0
  181. package/dist/clis/xueqiu/search.yaml +2 -1
  182. package/dist/clis/xueqiu/stock.yaml +2 -0
  183. package/dist/clis/yahoo-finance/quote.js +1 -1
  184. package/dist/commanderAdapter.js +13 -7
  185. package/dist/discovery.d.ts +8 -0
  186. package/dist/discovery.js +105 -19
  187. package/dist/doctor.js +3 -1
  188. package/dist/doctor.test.js +46 -2
  189. package/dist/engine.test.d.ts +0 -3
  190. package/dist/engine.test.js +74 -6
  191. package/dist/execution.d.ts +4 -2
  192. package/dist/execution.js +31 -7
  193. package/dist/explore.d.ts +76 -3
  194. package/dist/explore.js +11 -4
  195. package/dist/generate.d.ts +41 -2
  196. package/dist/generate.js +5 -4
  197. package/dist/main.js +2 -1
  198. package/dist/pipeline/executor.d.ts +2 -2
  199. package/dist/pipeline/executor.js +2 -2
  200. package/dist/pipeline/executor.test.js +33 -6
  201. package/dist/pipeline/registry.d.ts +1 -1
  202. package/dist/pipeline/steps/browser.d.ts +7 -7
  203. package/dist/pipeline/steps/browser.js +15 -7
  204. package/dist/pipeline/steps/fetch.d.ts +1 -1
  205. package/dist/pipeline/steps/fetch.js +11 -7
  206. package/dist/pipeline/steps/transform.d.ts +6 -5
  207. package/dist/pipeline/steps/transform.js +30 -9
  208. package/dist/pipeline/template.d.ts +6 -6
  209. package/dist/pipeline/template.js +43 -5
  210. package/dist/pipeline/template.test.js +18 -0
  211. package/dist/pipeline/transform.test.js +11 -0
  212. package/dist/plugin.d.ts +31 -0
  213. package/dist/plugin.js +216 -0
  214. package/dist/plugin.test.d.ts +4 -0
  215. package/dist/plugin.test.js +76 -0
  216. package/dist/registry-api.d.ts +11 -0
  217. package/dist/registry-api.js +9 -0
  218. package/dist/registry.d.ts +11 -0
  219. package/dist/registry.js +6 -1
  220. package/dist/synthesize.d.ts +94 -4
  221. package/dist/synthesize.js +5 -4
  222. package/dist/types.d.ts +39 -26
  223. package/dist/validate.js +8 -2
  224. package/docs/.vitepress/config.mts +6 -4
  225. package/docs/adapters/browser/barchart.md +6 -5
  226. package/docs/adapters/browser/bilibili.md +9 -0
  227. package/docs/adapters/browser/devto.md +35 -0
  228. package/docs/adapters/browser/douban.md +38 -0
  229. package/docs/adapters/browser/facebook.md +36 -0
  230. package/docs/adapters/browser/google.md +62 -0
  231. package/docs/adapters/browser/grok.md +26 -8
  232. package/docs/adapters/browser/instagram.md +46 -0
  233. package/docs/adapters/browser/lobsters.md +32 -0
  234. package/docs/adapters/browser/medium.md +32 -0
  235. package/docs/adapters/browser/reddit.md +9 -0
  236. package/docs/adapters/browser/sinablog.md +36 -0
  237. package/docs/adapters/browser/substack.md +38 -0
  238. package/docs/adapters/browser/tiktok.md +68 -0
  239. package/docs/adapters/browser/wikipedia.md +11 -2
  240. package/docs/adapters/browser/xueqiu.md +10 -0
  241. package/docs/adapters/browser/yahoo-finance.md +6 -5
  242. package/docs/adapters/desktop/antigravity.md +6 -0
  243. package/docs/adapters/desktop/chatgpt.md +2 -1
  244. package/docs/adapters/desktop/codex.md +5 -1
  245. package/docs/adapters/desktop/cursor.md +4 -0
  246. package/docs/adapters/desktop/discord.md +7 -7
  247. package/docs/adapters/index.md +1 -4
  248. package/docs/guide/getting-started.md +1 -0
  249. package/docs/guide/plugins.md +153 -0
  250. package/docs/zh/guide/plugins.md +107 -0
  251. package/extension/src/background.ts +18 -11
  252. package/package.json +10 -5
  253. package/scripts/clean-dist.cjs +13 -0
  254. package/src/browser/cdp.ts +71 -31
  255. package/src/browser/daemon-client.ts +2 -1
  256. package/src/browser/dom-helpers.ts +38 -7
  257. package/src/browser/dom-snapshot.test.ts +249 -0
  258. package/src/browser/dom-snapshot.ts +770 -0
  259. package/src/browser/index.ts +2 -0
  260. package/src/browser/page.ts +50 -19
  261. package/src/build-manifest.test.ts +70 -2
  262. package/src/build-manifest.ts +94 -26
  263. package/src/cli.ts +71 -2
  264. package/src/clis/barchart/greeks.ts +1 -1
  265. package/src/clis/barchart/options.ts +1 -1
  266. package/src/clis/barchart/quote.ts +1 -1
  267. package/src/clis/bilibili/download.ts +1 -1
  268. package/src/clis/bilibili/following.ts +1 -1
  269. package/src/clis/bilibili/subtitle.ts +1 -1
  270. package/src/clis/bilibili/user-videos.ts +1 -1
  271. package/src/clis/boss/batchgreet.ts +14 -106
  272. package/src/clis/boss/chatlist.ts +12 -26
  273. package/src/clis/boss/chatmsg.ts +16 -40
  274. package/src/clis/boss/common.ts +287 -0
  275. package/src/clis/boss/detail.ts +8 -54
  276. package/src/clis/boss/exchange.ts +15 -89
  277. package/src/clis/boss/greet.ts +23 -160
  278. package/src/clis/boss/invite.ts +36 -133
  279. package/src/clis/boss/joblist.ts +7 -36
  280. package/src/clis/boss/mark.ts +13 -94
  281. package/src/clis/boss/recommend.ts +12 -57
  282. package/src/clis/boss/resume.ts +19 -124
  283. package/src/clis/boss/search.ts +13 -66
  284. package/src/clis/boss/send.ts +21 -161
  285. package/src/clis/boss/stats.ts +19 -74
  286. package/src/clis/coupang/add-to-cart.ts +1 -1
  287. package/src/clis/devto/tag.yaml +34 -0
  288. package/src/clis/devto/top.yaml +29 -0
  289. package/src/clis/devto/user.yaml +33 -0
  290. package/src/clis/douban/book-hot.ts +15 -0
  291. package/src/clis/douban/marks.ts +135 -0
  292. package/src/clis/douban/movie-hot.ts +15 -0
  293. package/src/clis/douban/reviews.ts +127 -0
  294. package/src/clis/douban/search.ts +17 -0
  295. package/src/clis/douban/shared.ts +165 -0
  296. package/src/clis/douban/subject.yaml +76 -0
  297. package/src/clis/douban/top250.yaml +70 -0
  298. package/src/clis/douban/utils.ts +81 -0
  299. package/src/clis/facebook/add-friend.yaml +43 -0
  300. package/src/clis/facebook/events.yaml +44 -0
  301. package/src/clis/facebook/feed.yaml +63 -0
  302. package/src/clis/facebook/friends.yaml +42 -0
  303. package/src/clis/facebook/groups.yaml +50 -0
  304. package/src/clis/facebook/join-group.yaml +44 -0
  305. package/src/clis/facebook/memories.yaml +39 -0
  306. package/src/clis/facebook/notifications.yaml +40 -0
  307. package/src/clis/facebook/profile.yaml +37 -0
  308. package/src/clis/facebook/search.yaml +46 -0
  309. package/src/clis/google/news.ts +66 -0
  310. package/src/clis/google/search.ts +133 -0
  311. package/src/clis/google/suggest.ts +40 -0
  312. package/src/clis/google/trends.ts +44 -0
  313. package/src/clis/google/utils.test.ts +82 -0
  314. package/src/clis/google/utils.ts +24 -0
  315. package/src/clis/grok/ask.test.ts +53 -0
  316. package/src/clis/grok/ask.ts +300 -69
  317. package/src/clis/instagram/comment.yaml +52 -0
  318. package/src/clis/instagram/explore.yaml +43 -0
  319. package/src/clis/instagram/follow.yaml +41 -0
  320. package/src/clis/instagram/followers.yaml +51 -0
  321. package/src/clis/instagram/following.yaml +51 -0
  322. package/src/clis/instagram/like.yaml +46 -0
  323. package/src/clis/instagram/profile.yaml +42 -0
  324. package/src/clis/instagram/save.yaml +46 -0
  325. package/src/clis/instagram/saved.yaml +40 -0
  326. package/src/clis/instagram/search.yaml +43 -0
  327. package/src/clis/instagram/unfollow.yaml +38 -0
  328. package/src/clis/instagram/unlike.yaml +46 -0
  329. package/src/clis/instagram/unsave.yaml +46 -0
  330. package/src/clis/instagram/user.yaml +54 -0
  331. package/src/clis/jike/repost.ts +1 -1
  332. package/src/clis/jimeng/generate.yaml +1 -0
  333. package/src/clis/linux-do/category.yaml +1 -0
  334. package/src/clis/lobsters/active.yaml +29 -0
  335. package/src/clis/lobsters/hot.yaml +29 -0
  336. package/src/clis/lobsters/newest.yaml +29 -0
  337. package/src/clis/lobsters/tag.yaml +34 -0
  338. package/src/clis/medium/feed.ts +16 -0
  339. package/src/clis/medium/search.ts +16 -0
  340. package/src/clis/medium/shared.ts +83 -0
  341. package/src/clis/medium/user.ts +16 -0
  342. package/src/clis/reddit/comment.ts +1 -1
  343. package/src/clis/reddit/read.ts +1 -1
  344. package/src/clis/reddit/save.ts +1 -1
  345. package/src/clis/reddit/subreddit.yaml +1 -0
  346. package/src/clis/reddit/subscribe.ts +1 -1
  347. package/src/clis/reddit/upvote.ts +1 -1
  348. package/src/clis/sinablog/article.ts +15 -0
  349. package/src/clis/sinablog/hot.ts +15 -0
  350. package/src/clis/sinablog/search.ts +56 -0
  351. package/src/clis/sinablog/shared.ts +198 -0
  352. package/src/clis/sinablog/user.ts +16 -0
  353. package/src/clis/substack/feed.ts +16 -0
  354. package/src/clis/substack/publication.ts +16 -0
  355. package/src/clis/substack/search.ts +91 -0
  356. package/src/clis/substack/shared.ts +132 -0
  357. package/src/clis/tiktok/comment.yaml +66 -0
  358. package/src/clis/tiktok/explore.yaml +39 -0
  359. package/src/clis/tiktok/follow.yaml +39 -0
  360. package/src/clis/tiktok/following.yaml +46 -0
  361. package/src/clis/tiktok/friends.yaml +47 -0
  362. package/src/clis/tiktok/like.yaml +38 -0
  363. package/src/clis/tiktok/live.yaml +51 -0
  364. package/src/clis/tiktok/notifications.yaml +52 -0
  365. package/src/clis/tiktok/profile.yaml +45 -0
  366. package/src/clis/tiktok/save.yaml +34 -0
  367. package/src/clis/tiktok/search.yaml +46 -0
  368. package/src/clis/tiktok/unfollow.yaml +44 -0
  369. package/src/clis/tiktok/unlike.yaml +38 -0
  370. package/src/clis/tiktok/unsave.yaml +36 -0
  371. package/src/clis/tiktok/user.yaml +44 -0
  372. package/src/clis/twitter/download.ts +3 -3
  373. package/src/clis/twitter/followers.ts +1 -1
  374. package/src/clis/twitter/following.ts +1 -1
  375. package/src/clis/twitter/thread.ts +1 -1
  376. package/src/clis/twitter/timeline.test.ts +109 -0
  377. package/src/clis/twitter/timeline.ts +59 -19
  378. package/src/clis/wikipedia/random.ts +19 -0
  379. package/src/clis/wikipedia/search.ts +10 -4
  380. package/src/clis/wikipedia/summary.ts +4 -9
  381. package/src/clis/wikipedia/trending.ts +41 -0
  382. package/src/clis/wikipedia/utils.ts +31 -0
  383. package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
  384. package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
  385. package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
  386. package/src/clis/xiaohongshu/download.ts +1 -1
  387. package/src/clis/xueqiu/earnings-date.yaml +69 -0
  388. package/src/clis/xueqiu/search.yaml +2 -1
  389. package/src/clis/xueqiu/stock.yaml +2 -0
  390. package/src/clis/yahoo-finance/quote.ts +1 -1
  391. package/src/commanderAdapter.ts +17 -10
  392. package/src/discovery.ts +134 -24
  393. package/src/doctor.test.ts +59 -2
  394. package/src/doctor.ts +4 -2
  395. package/src/engine.test.ts +79 -6
  396. package/src/execution.ts +42 -16
  397. package/src/explore.ts +77 -9
  398. package/src/generate.ts +58 -9
  399. package/src/main.ts +2 -1
  400. package/src/pipeline/executor.test.ts +35 -6
  401. package/src/pipeline/executor.ts +11 -7
  402. package/src/pipeline/registry.ts +3 -3
  403. package/src/pipeline/steps/browser.ts +24 -15
  404. package/src/pipeline/steps/fetch.ts +18 -13
  405. package/src/pipeline/steps/transform.ts +40 -15
  406. package/src/pipeline/template.test.ts +18 -0
  407. package/src/pipeline/template.ts +86 -13
  408. package/src/pipeline/transform.test.ts +15 -2
  409. package/src/plugin.test.ts +86 -0
  410. package/src/plugin.ts +254 -0
  411. package/src/registry-api.ts +12 -0
  412. package/src/registry.ts +19 -1
  413. package/src/synthesize.ts +102 -21
  414. package/src/types.ts +44 -12
  415. package/src/validate.ts +19 -4
  416. package/tests/e2e/browser-public.test.ts +11 -0
  417. package/tests/e2e/public-commands.test.ts +64 -0
  418. package/dist/clis/feishu/new.d.ts +0 -1
  419. package/dist/clis/feishu/new.js +0 -27
  420. package/dist/clis/feishu/read.d.ts +0 -1
  421. package/dist/clis/feishu/read.js +0 -40
  422. package/dist/clis/feishu/search.d.ts +0 -1
  423. package/dist/clis/feishu/search.js +0 -30
  424. package/dist/clis/feishu/send.d.ts +0 -1
  425. package/dist/clis/feishu/send.js +0 -39
  426. package/dist/clis/feishu/status.d.ts +0 -1
  427. package/dist/clis/feishu/status.js +0 -28
  428. package/dist/clis/neteasemusic/like.d.ts +0 -1
  429. package/dist/clis/neteasemusic/like.js +0 -25
  430. package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
  431. package/dist/clis/neteasemusic/lyrics.js +0 -47
  432. package/dist/clis/neteasemusic/next.d.ts +0 -1
  433. package/dist/clis/neteasemusic/next.js +0 -26
  434. package/dist/clis/neteasemusic/play.d.ts +0 -1
  435. package/dist/clis/neteasemusic/play.js +0 -26
  436. package/dist/clis/neteasemusic/playing.d.ts +0 -1
  437. package/dist/clis/neteasemusic/playing.js +0 -59
  438. package/dist/clis/neteasemusic/playlist.d.ts +0 -1
  439. package/dist/clis/neteasemusic/playlist.js +0 -46
  440. package/dist/clis/neteasemusic/prev.d.ts +0 -1
  441. package/dist/clis/neteasemusic/prev.js +0 -25
  442. package/dist/clis/neteasemusic/search.d.ts +0 -1
  443. package/dist/clis/neteasemusic/search.js +0 -52
  444. package/dist/clis/neteasemusic/status.d.ts +0 -1
  445. package/dist/clis/neteasemusic/status.js +0 -16
  446. package/dist/clis/neteasemusic/volume.d.ts +0 -1
  447. package/dist/clis/neteasemusic/volume.js +0 -54
  448. package/dist/clis/wechat/chats.d.ts +0 -1
  449. package/dist/clis/wechat/chats.js +0 -28
  450. package/dist/clis/wechat/contacts.d.ts +0 -1
  451. package/dist/clis/wechat/contacts.js +0 -28
  452. package/dist/clis/wechat/read.d.ts +0 -1
  453. package/dist/clis/wechat/read.js +0 -58
  454. package/dist/clis/wechat/search.d.ts +0 -1
  455. package/dist/clis/wechat/search.js +0 -31
  456. package/dist/clis/wechat/send.d.ts +0 -1
  457. package/dist/clis/wechat/send.js +0 -42
  458. package/dist/clis/wechat/status.d.ts +0 -1
  459. package/dist/clis/wechat/status.js +0 -29
  460. package/dist/pipeline.d.ts +0 -7
  461. package/dist/pipeline.js +0 -7
  462. package/docs/adapters/browser/github.md +0 -26
  463. package/docs/adapters/desktop/feishu.md +0 -20
  464. package/docs/adapters/desktop/neteasemusic.md +0 -31
  465. package/docs/adapters/desktop/wechat.md +0 -28
  466. package/src/clis/antigravity/README.md +0 -5
  467. package/src/clis/antigravity/README.zh-CN.md +0 -51
  468. package/src/clis/chaoxing/README.md +0 -14
  469. package/src/clis/chaoxing/README.zh-CN.md +0 -35
  470. package/src/clis/chatgpt/README.md +0 -5
  471. package/src/clis/chatgpt/README.zh-CN.md +0 -44
  472. package/src/clis/chatwise/README.md +0 -5
  473. package/src/clis/chatwise/README.zh-CN.md +0 -38
  474. package/src/clis/codex/README.md +0 -5
  475. package/src/clis/codex/README.zh-CN.md +0 -33
  476. package/src/clis/cursor/README.md +0 -5
  477. package/src/clis/cursor/README.zh-CN.md +0 -33
  478. package/src/clis/discord-app/README.md +0 -5
  479. package/src/clis/discord-app/README.zh-CN.md +0 -28
  480. package/src/clis/feishu/README.md +0 -5
  481. package/src/clis/feishu/README.zh-CN.md +0 -20
  482. package/src/clis/feishu/new.ts +0 -32
  483. package/src/clis/feishu/read.ts +0 -48
  484. package/src/clis/feishu/search.ts +0 -35
  485. package/src/clis/feishu/send.ts +0 -46
  486. package/src/clis/feishu/status.ts +0 -34
  487. package/src/clis/neteasemusic/README.md +0 -5
  488. package/src/clis/neteasemusic/README.zh-CN.md +0 -31
  489. package/src/clis/neteasemusic/like.ts +0 -28
  490. package/src/clis/neteasemusic/lyrics.ts +0 -53
  491. package/src/clis/neteasemusic/next.ts +0 -30
  492. package/src/clis/neteasemusic/play.ts +0 -30
  493. package/src/clis/neteasemusic/playing.ts +0 -62
  494. package/src/clis/neteasemusic/playlist.ts +0 -51
  495. package/src/clis/neteasemusic/prev.ts +0 -29
  496. package/src/clis/neteasemusic/search.ts +0 -58
  497. package/src/clis/neteasemusic/status.ts +0 -18
  498. package/src/clis/neteasemusic/volume.ts +0 -61
  499. package/src/clis/notion/README.md +0 -5
  500. package/src/clis/notion/README.zh-CN.md +0 -29
  501. package/src/clis/wechat/README.md +0 -5
  502. package/src/clis/wechat/README.zh-CN.md +0 -28
  503. package/src/clis/wechat/chats.ts +0 -33
  504. package/src/clis/wechat/contacts.ts +0 -33
  505. package/src/clis/wechat/read.ts +0 -72
  506. package/src/clis/wechat/search.ts +0 -36
  507. package/src/clis/wechat/send.ts +0 -49
  508. package/src/clis/wechat/status.ts +0 -35
  509. package/src/pipeline.ts +0 -8
@@ -1,15 +1,12 @@
1
1
  /**
2
2
  * BOSS直聘 mark — label/mark a candidate.
3
3
  *
4
- * Uses /wapi/zprelation/friend/label/addMark to add a label to a candidate,
5
- * and /wapi/zprelation/friend/label/deleteMark to remove one.
6
- *
7
- * Available labels (from /wapi/zprelation/friend/label/get):
4
+ * Available labels:
8
5
  * 1=新招呼, 2=沟通中, 3=已约面, 4=已获取简历, 5=已交换电话,
9
6
  * 6=已交换微信, 7=不合适, 8=牛人发起, 11=收藏
10
7
  */
11
8
  import { cli, Strategy } from '../../registry.js';
12
- import type { IPage } from '../../types.js';
9
+ import { requirePage, navigateToChat, bossFetch, findFriendByUid, verbose } from './common.js';
13
10
 
14
11
  const LABEL_MAP: Record<string, number> = {
15
12
  '新招呼': 1, '沟通中': 2, '已约面': 3, '已获取简历': 4,
@@ -22,17 +19,17 @@ cli({
22
19
  description: 'BOSS直聘给候选人添加标签',
23
20
  domain: 'www.zhipin.com',
24
21
  strategy: Strategy.COOKIE,
22
+ navigateBefore: false,
25
23
  browser: true,
26
24
  args: [
27
- { name: 'uid', required: true, help: 'Encrypted UID of the candidate' },
25
+ { name: 'uid', positional: true, required: true, help: 'Encrypted UID of the candidate' },
28
26
  { name: 'label', required: true, help: 'Label name (新招呼/沟通中/已约面/已获取简历/已交换电话/已交换微信/不合适/收藏) or label ID' },
29
27
  { name: 'remove', type: 'boolean', default: false, help: 'Remove the label instead of adding' },
30
28
  ],
31
29
  columns: ['status', 'detail'],
32
- func: async (page: IPage | null, kwargs) => {
33
- if (!page) throw new Error('Browser page required');
30
+ func: async (page, kwargs) => {
31
+ requirePage(page);
34
32
 
35
- const uid = kwargs.uid;
36
33
  const labelInput = kwargs.label;
37
34
  const remove = kwargs.remove || false;
38
35
 
@@ -43,7 +40,6 @@ cli({
43
40
  } else if (!isNaN(Number(labelInput))) {
44
41
  labelId = Number(labelInput);
45
42
  } else {
46
- // Try partial match
47
43
  const entry = Object.entries(LABEL_MAP).find(([k]) => k.includes(labelInput));
48
44
  if (entry) {
49
45
  labelId = entry[1];
@@ -52,99 +48,22 @@ cli({
52
48
  }
53
49
  }
54
50
 
55
- if (process.env.OPENCLI_VERBOSE) {
56
- console.error(`[opencli:boss] ${remove ? 'Removing' : 'Adding'} label ${labelId} for ${uid}...`);
57
- }
51
+ verbose(`${remove ? 'Removing' : 'Adding'} label ${labelId} for ${kwargs.uid}...`);
58
52
 
59
- await page.goto('https://www.zhipin.com/web/chat/index');
60
- await page.wait({ time: 2 });
53
+ await navigateToChat(page);
61
54
 
62
- // First get numeric UID from friend list
63
- const friendData: any = await page.evaluate(`
64
- async () => {
65
- return new Promise((resolve, reject) => {
66
- const xhr = new XMLHttpRequest();
67
- xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0', true);
68
- xhr.withCredentials = true;
69
- xhr.timeout = 15000;
70
- xhr.setRequestHeader('Accept', 'application/json');
71
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
72
- xhr.onerror = () => reject(new Error('Network Error'));
73
- xhr.send();
74
- });
75
- }
76
- `);
55
+ const friend = await findFriendByUid(page, kwargs.uid, { checkGreetList: true });
56
+ if (!friend) throw new Error('未找到该候选人');
77
57
 
78
- if (friendData.code !== 0) {
79
- if (friendData.code === 7 || friendData.code === 37) {
80
- throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
81
- }
82
- throw new Error(`获取好友列表失败: ${friendData.message}`);
83
- }
84
-
85
- // Find in friend list (check multiple pages)
86
- let friend: any = null;
87
- let allFriends = friendData.zpData?.friendList || [];
88
- friend = allFriends.find((f: any) => f.encryptUid === uid);
89
-
90
- if (!friend) {
91
- // Also check greetRecSortList
92
- const greetData: any = await page.evaluate(`
93
- async () => {
94
- return new Promise((resolve, reject) => {
95
- const xhr = new XMLHttpRequest();
96
- xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/greetRecSortList', true);
97
- xhr.withCredentials = true;
98
- xhr.timeout = 15000;
99
- xhr.setRequestHeader('Accept', 'application/json');
100
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
101
- xhr.onerror = () => reject(new Error('Network Error'));
102
- xhr.send();
103
- });
104
- }
105
- `);
106
- if (greetData.code === 0) {
107
- friend = (greetData.zpData?.friendList || []).find((f: any) => f.encryptUid === uid);
108
- }
109
- }
110
-
111
- if (!friend) {
112
- throw new Error('未找到该候选人');
113
- }
114
-
115
- const numericUid = friend.uid;
116
58
  const friendName = friend.name || '候选人';
117
- const friendSource = friend.friendSource ?? 0;
118
-
119
59
  const action = remove ? 'deleteMark' : 'addMark';
120
- const targetUrl = `https://www.zhipin.com/wapi/zprelation/friend/label/${action}`;
121
-
122
- // The API uses friendId + friendSource + labelId (discovered from JS bundles)
123
60
  const params = new URLSearchParams({
124
- friendId: String(numericUid),
125
- friendSource: String(friendSource),
61
+ friendId: String(friend.uid),
62
+ friendSource: String(friend.friendSource ?? 0),
126
63
  labelId: String(labelId),
127
64
  });
128
65
 
129
- // Try GET first (the N() wrapper in boss JS uses GET with query params)
130
- const data: any = await page.evaluate(`
131
- async () => {
132
- return new Promise((resolve, reject) => {
133
- const xhr = new XMLHttpRequest();
134
- xhr.open('GET', '${targetUrl}?${params.toString()}', true);
135
- xhr.withCredentials = true;
136
- xhr.timeout = 15000;
137
- xhr.setRequestHeader('Accept', 'application/json');
138
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(new Error('JSON parse failed')); } };
139
- xhr.onerror = () => reject(new Error('Network Error'));
140
- xhr.send();
141
- });
142
- }
143
- `);
144
-
145
- if (data.code !== 0) {
146
- throw new Error(`标签操作失败: ${data.message} (code=${data.code})`);
147
- }
66
+ await bossFetch(page, `https://www.zhipin.com/wapi/zprelation/friend/label/${action}?${params.toString()}`);
148
67
 
149
68
  const labelName = Object.entries(LABEL_MAP).find(([, v]) => v === labelId)?.[0] || String(labelId);
150
69
  return [{
@@ -1,11 +1,8 @@
1
1
  /**
2
2
  * BOSS直聘 recommend — view recommended candidates (新招呼/greet sort list).
3
- *
4
- * Uses /wapi/zprelation/friend/greetRecSortList to get system-recommended candidates.
5
- * These are candidates who have greeted or been recommended by the system.
6
3
  */
7
4
  import { cli, Strategy } from '../../registry.js';
8
- import type { IPage } from '../../types.js';
5
+ import { requirePage, navigateToChat, bossFetch, fetchRecommendList, verbose } from './common.js';
9
6
 
10
7
  cli({
11
8
  site: 'boss',
@@ -13,39 +10,22 @@ cli({
13
10
  description: 'BOSS直聘查看推荐候选人(新招呼列表)',
14
11
  domain: 'www.zhipin.com',
15
12
  strategy: Strategy.COOKIE,
13
+ navigateBefore: false,
16
14
  browser: true,
17
15
  args: [
18
16
  { name: 'limit', type: 'int', default: 20, help: 'Number of results to return' },
19
17
  ],
20
18
  columns: ['name', 'job_name', 'last_time', 'labels', 'encrypt_uid', 'security_id', 'encrypt_job_id'],
21
- func: async (page: IPage | null, kwargs) => {
22
- if (!page) throw new Error('Browser page required');
19
+ func: async (page, kwargs) => {
20
+ requirePage(page);
21
+ verbose('Fetching recommended candidates...');
23
22
 
24
- const limit = kwargs.limit || 20;
25
-
26
- if (process.env.OPENCLI_VERBOSE) {
27
- console.error('[opencli:boss] Fetching recommended candidates...');
28
- }
29
-
30
- await page.goto('https://www.zhipin.com/web/chat/index');
31
- await page.wait({ time: 2 });
23
+ await navigateToChat(page);
32
24
 
33
25
  // Get label definitions for mapping
34
- const labelData: any = await page.evaluate(`
35
- async () => {
36
- return new Promise((resolve, reject) => {
37
- const xhr = new XMLHttpRequest();
38
- xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/label/get', true);
39
- xhr.withCredentials = true;
40
- xhr.timeout = 10000;
41
- xhr.setRequestHeader('Accept', 'application/json');
42
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { resolve({}); } };
43
- xhr.onerror = () => resolve({});
44
- xhr.send();
45
- });
46
- }
47
- `);
48
-
26
+ const labelData = await bossFetch(page, 'https://www.zhipin.com/wapi/zprelation/friend/label/get', {
27
+ allowNonZero: true,
28
+ });
49
29
  const labelMap: Record<number, string> = {};
50
30
  if (labelData.code === 0 && labelData.zpData?.labels) {
51
31
  for (const l of labelData.zpData.labels) {
@@ -53,35 +33,10 @@ cli({
53
33
  }
54
34
  }
55
35
 
56
- // Get recommended candidates
57
- const targetUrl = 'https://www.zhipin.com/wapi/zprelation/friend/greetRecSortList';
58
-
59
- const data: any = await page.evaluate(`
60
- async () => {
61
- return new Promise((resolve, reject) => {
62
- const xhr = new XMLHttpRequest();
63
- xhr.open('GET', '${targetUrl}', true);
64
- xhr.withCredentials = true;
65
- xhr.timeout = 15000;
66
- xhr.setRequestHeader('Accept', 'application/json');
67
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(new Error('JSON parse failed')); } };
68
- xhr.onerror = () => reject(new Error('Network Error'));
69
- xhr.ontimeout = () => reject(new Error('Timeout'));
70
- xhr.send();
71
- });
72
- }
73
- `);
74
-
75
- if (data.code !== 0) {
76
- if (data.code === 7 || data.code === 37) {
77
- throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
78
- }
79
- throw new Error(`API error: ${data.message} (code=${data.code})`);
80
- }
81
-
82
- const friends = (data.zpData?.friendList || []).slice(0, limit);
36
+ const friends = await fetchRecommendList(page);
37
+ const limit = kwargs.limit || 20;
83
38
 
84
- return friends.map((f: any) => ({
39
+ return friends.slice(0, limit).map((f: any) => ({
85
40
  name: f.name || '',
86
41
  job_name: f.jobName || '',
87
42
  last_time: f.lastTime || '',
@@ -2,17 +2,15 @@
2
2
  * BOSS直聘 resume — view candidate resume/profile via chat page UI scraping (boss side).
3
3
  *
4
4
  * Flow: navigate to chat page → click on candidate → scrape the right panel info.
5
- * The chat page loads candidate basic info, work experience, and education
6
- * in the right panel when a candidate is selected.
7
5
  *
8
- * HTML structure (right panel):
6
+ * Right panel HTML structure:
9
7
  * .base-info-single-detial → name, gender, age, experience, degree
10
8
  * .experience-content.time-list → time ranges (icon-base-info-work / icon-base-info-edu)
11
9
  * .experience-content.detail-list → details (company·position / school·major·degree)
12
10
  * .position-content → job being discussed + expectation
13
11
  */
14
12
  import { cli, Strategy } from '../../registry.js';
15
- import type { IPage } from '../../types.js';
13
+ import { requirePage, navigateToChat, findFriendByUid, clickCandidateInList } from './common.js';
16
14
 
17
15
  cli({
18
16
  site: 'boss',
@@ -20,119 +18,42 @@ cli({
20
18
  description: 'BOSS直聘查看候选人简历(招聘端)',
21
19
  domain: 'www.zhipin.com',
22
20
  strategy: Strategy.COOKIE,
23
-
21
+ navigateBefore: false,
24
22
  browser: true,
25
23
  args: [
26
- { name: 'uid', required: true, help: 'Encrypted UID of the candidate (from chatlist)' },
24
+ { name: 'uid', required: true, positional: true, help: 'Encrypted UID of the candidate (from chatlist)' },
27
25
  ],
28
26
  columns: [
29
27
  'name', 'gender', 'age', 'experience', 'degree', 'active_time',
30
28
  'work_history', 'education',
31
29
  'job_chatting', 'expect',
32
30
  ],
33
- func: async (page: IPage | null, kwargs) => {
34
- if (!page) throw new Error('Browser page required');
35
-
36
- const uid = kwargs.uid;
37
-
38
- // Step 1: Navigate to chat page
39
- await page.goto('https://www.zhipin.com/web/chat/index');
40
- await page.wait({ time: 3 });
41
-
42
- // Step 2: Get friend list to find candidate's numeric uid
43
- const friendData: any = await page.evaluate(`
44
- async () => {
45
- return new Promise((resolve, reject) => {
46
- const xhr = new XMLHttpRequest();
47
- xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0', true);
48
- xhr.withCredentials = true;
49
- xhr.timeout = 15000;
50
- xhr.setRequestHeader('Accept', 'application/json');
51
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
52
- xhr.onerror = () => reject(new Error('Network Error'));
53
- xhr.send();
54
- });
55
- }
56
- `);
31
+ func: async (page, kwargs) => {
32
+ requirePage(page);
57
33
 
58
- if (friendData.code !== 0) {
59
- if (friendData.code === 7 || friendData.code === 37) {
60
- throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
61
- }
62
- throw new Error('获取好友列表失败: ' + (friendData.message || friendData.code));
63
- }
64
-
65
- let friend: any = null;
66
- const allFriends = friendData.zpData?.friendList || [];
67
- friend = allFriends.find((f: any) => f.encryptUid === uid);
68
-
69
- if (!friend) {
70
- for (let p = 2; p <= 5; p++) {
71
- const moreUrl = `https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=${p}&status=0&jobId=0`;
72
- const moreData: any = await page.evaluate(`
73
- async () => {
74
- return new Promise((resolve, reject) => {
75
- const xhr = new XMLHttpRequest();
76
- xhr.open('GET', '${moreUrl}', true);
77
- xhr.withCredentials = true;
78
- xhr.timeout = 15000;
79
- xhr.setRequestHeader('Accept', 'application/json');
80
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
81
- xhr.onerror = () => reject(new Error('Network Error'));
82
- xhr.send();
83
- });
84
- }
85
- `);
86
- if (moreData.code === 0) {
87
- const list = moreData.zpData?.friendList || [];
88
- friend = list.find((f: any) => f.encryptUid === uid);
89
- if (friend) break;
90
- if (list.length === 0) break;
91
- }
92
- }
93
- }
34
+ await navigateToChat(page, 3);
94
35
 
36
+ const friend = await findFriendByUid(page, kwargs.uid, { maxPages: 5 });
95
37
  if (!friend) throw new Error('未找到该候选人,请确认 uid 是否正确');
96
38
 
97
39
  const numericUid = friend.uid;
98
40
 
99
- // Step 3: Click on candidate in chat list
100
- const clicked: any = await page.evaluate(`
101
- async () => {
102
- const item = document.querySelector('#_${numericUid}-0') || document.querySelector('[id^="_${numericUid}"]');
103
- if (item) {
104
- item.click();
105
- return { clicked: true };
106
- }
107
- const items = document.querySelectorAll('.geek-item');
108
- for (const el of items) {
109
- if (el.id && el.id.startsWith('_${numericUid}')) {
110
- el.click();
111
- return { clicked: true };
112
- }
113
- }
114
- return { clicked: false };
115
- }
116
- `);
117
-
118
- if (!clicked.clicked) {
41
+ const clicked = await clickCandidateInList(page, numericUid);
42
+ if (!clicked) {
119
43
  throw new Error('无法在聊天列表中找到该用户,请确认聊天列表中有此人');
120
44
  }
121
45
 
122
- // Step 4: Wait for right panel to load
123
46
  await page.wait({ time: 2 });
124
47
 
125
- // Step 5: Scrape the right panel
48
+ // Scrape the right panel
126
49
  const resumeInfo: any = await page.evaluate(`
127
50
  (() => {
128
51
  const container = document.querySelector('.base-info-single-container') || document.querySelector('.base-info-content');
129
52
  if (!container) return { error: 'no container found' };
130
53
 
131
- // === Basic Info ===
132
54
  const nameEl = container.querySelector('.base-name');
133
55
  const name = nameEl ? nameEl.textContent.trim() : '';
134
56
 
135
- // Gender
136
57
  let gender = '';
137
58
  const detailDiv = container.querySelector('.base-info-single-detial');
138
59
  if (detailDiv) {
@@ -144,11 +65,9 @@ cli({
144
65
  }
145
66
  }
146
67
 
147
- // Active time
148
68
  const activeEl = container.querySelector('.active-time');
149
69
  const activeTime = activeEl ? activeEl.textContent.trim() : '';
150
70
 
151
- // Age, experience, degree — direct child divs of .base-info-single-detial
152
71
  let age = '', experience = '', degree = '';
153
72
  if (detailDiv) {
154
73
  for (const el of detailDiv.children) {
@@ -163,50 +82,32 @@ cli({
163
82
  }
164
83
  }
165
84
 
166
- // === Work & Education ===
167
- // Structure: two .experience-content divs
168
- // 1. .time-list → <li> items with icon (work/edu) and time span
169
- // 2. .detail-list → <li> items with icon (work/edu) and detail text
170
- // Each <li> has a <use> with xlink:href "#icon-base-info-work" or "#icon-base-info-edu"
171
-
172
- const workTimes = [];
173
- const eduTimes = [];
174
- const workDetails = [];
175
- const eduDetails = [];
85
+ const workTimes = [], eduTimes = [], workDetails = [], eduDetails = [];
176
86
 
177
87
  const timeList = container.querySelector('.experience-content.time-list');
178
88
  if (timeList) {
179
- const lis = timeList.querySelectorAll('li');
180
- for (const li of lis) {
89
+ for (const li of timeList.querySelectorAll('li')) {
181
90
  const useEl = li.querySelector('use');
182
91
  const href = useEl ? (useEl.getAttribute('xlink:href') || useEl.getAttribute('href') || '') : '';
183
92
  const timeSpan = li.querySelector('.time');
184
93
  const timeText = timeSpan ? timeSpan.textContent.trim() : li.textContent.trim();
185
- if (href.includes('base-info-edu')) {
186
- eduTimes.push(timeText);
187
- } else {
188
- workTimes.push(timeText);
189
- }
94
+ if (href.includes('base-info-edu')) eduTimes.push(timeText);
95
+ else workTimes.push(timeText);
190
96
  }
191
97
  }
192
98
 
193
99
  const detailList = container.querySelector('.experience-content.detail-list');
194
100
  if (detailList) {
195
- const lis = detailList.querySelectorAll('li');
196
- for (const li of lis) {
101
+ for (const li of detailList.querySelectorAll('li')) {
197
102
  const useEl = li.querySelector('use');
198
103
  const href = useEl ? (useEl.getAttribute('xlink:href') || useEl.getAttribute('href') || '') : '';
199
104
  const valueSpan = li.querySelector('.value');
200
105
  const valueText = valueSpan ? valueSpan.textContent.trim() : li.textContent.trim();
201
- if (href.includes('base-info-edu')) {
202
- eduDetails.push(valueText);
203
- } else {
204
- workDetails.push(valueText);
205
- }
106
+ if (href.includes('base-info-edu')) eduDetails.push(valueText);
107
+ else workDetails.push(valueText);
206
108
  }
207
109
  }
208
110
 
209
- // Combine times and details
210
111
  const workHistory = [];
211
112
  for (let i = 0; i < Math.max(workTimes.length, workDetails.length); i++) {
212
113
  const parts = [];
@@ -223,22 +124,16 @@ cli({
223
124
  if (parts.length) education.push(parts.join(' '));
224
125
  }
225
126
 
226
- // === Job Chatting & Expect ===
227
127
  const positionContent = container.querySelector('.position-content');
228
128
  let jobChatting = '', expect = '';
229
129
  if (positionContent) {
230
130
  const posNameEl = positionContent.querySelector('.position-name');
231
131
  if (posNameEl) jobChatting = posNameEl.textContent.trim();
232
-
233
132
  const expectEl = positionContent.querySelector('.position-item.expect .value');
234
133
  if (expectEl) expect = expectEl.textContent.trim();
235
134
  }
236
135
 
237
- return {
238
- name, gender, age, experience, degree, activeTime,
239
- workHistory, education,
240
- jobChatting, expect,
241
- };
136
+ return { name, gender, age, experience, degree, activeTime, workHistory, education, jobChatting, expect };
242
137
  })()
243
138
  `);
244
139
 
@@ -2,7 +2,7 @@
2
2
  * BOSS直聘 job search — browser cookie API.
3
3
  */
4
4
  import { cli, Strategy } from '../../registry.js';
5
- import type { IPage } from '../../types.js';
5
+ import { requirePage, navigateTo, bossFetch, assertOk, verbose } from './common.js';
6
6
 
7
7
  /** City name → BOSS Zhipin city code mapping */
8
8
  const CITY_CODES: Record<string, string> = {
@@ -69,7 +69,7 @@ cli({
69
69
  description: 'BOSS直聘搜索职位',
70
70
  domain: 'www.zhipin.com',
71
71
  strategy: Strategy.COOKIE,
72
-
72
+ navigateBefore: false,
73
73
  browser: true,
74
74
  args: [
75
75
  { name: 'query', required: true, positional: true, help: 'Search keyword (e.g. AI agent, 前端)' },
@@ -82,19 +82,13 @@ cli({
82
82
  { name: 'limit', type: 'int', default: 15, help: 'Number of results' },
83
83
  ],
84
84
  columns: ['name', 'salary', 'company', 'area', 'experience', 'degree', 'skills', 'boss', 'security_id', 'url'],
85
- func: async (page: IPage | null, kwargs) => {
86
- if (!page) throw new Error('Browser page required');
85
+ func: async (page, kwargs) => {
86
+ requirePage(page);
87
87
 
88
88
  const cityCode = resolveCity(kwargs.city);
89
-
90
- if (process.env.OPENCLI_VERBOSE || process.env.DEBUG?.includes('opencli')) {
91
- console.error(`[opencli:boss] Navigating to set referrer context...`);
92
- }
93
- // Navigate to the Web search view first to establish proper referrer context
94
- // This is a lesson learned from boss-cli: referrer is important
95
- await page.goto(`https://www.zhipin.com/web/geek/job?query=${encodeURIComponent(kwargs.query)}&city=${cityCode}`);
96
-
97
- // Give the page a tiny bit of time to settle to avoid immediate 403s
89
+ verbose('Navigating to set referrer context...');
90
+
91
+ await navigateTo(page, `https://www.zhipin.com/web/geek/job?query=${encodeURIComponent(kwargs.query)}&city=${cityCode}`);
98
92
  await new Promise(r => setTimeout(r, 1000));
99
93
 
100
94
  const expVal = resolveMap(kwargs.experience, EXP_MAP);
@@ -109,7 +103,6 @@ cli({
109
103
 
110
104
  while (allJobs.length < limit) {
111
105
  if (allJobs.length > 0) {
112
- // Human-like pause between page fetches (1-3 seconds)
113
106
  await new Promise(r => setTimeout(r, 1000 + Math.random() * 2000));
114
107
  }
115
108
 
@@ -126,62 +119,19 @@ cli({
126
119
  if (industryVal) qs.set('industry', industryVal);
127
120
 
128
121
  const targetUrl = `https://www.zhipin.com/wapi/zpgeek/search/joblist.json?${qs.toString()}`;
122
+ verbose(`Fetching page ${currentPage}... (current jobs: ${allJobs.length})`);
129
123
 
130
- if (process.env.OPENCLI_VERBOSE || process.env.DEBUG?.includes('opencli')) {
131
- console.error(`[opencli:boss] Fetching page ${currentPage}... (current jobs: ${allJobs.length})`);
132
- }
133
-
134
- const evaluateScript = `
135
- async () => {
136
- return new Promise((resolve, reject) => {
137
- const xhr = new window.XMLHttpRequest();
138
- xhr.open('GET', '${targetUrl}', true);
139
- xhr.withCredentials = true;
140
- xhr.timeout = 15000; // 15s timeout
141
- xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
142
- xhr.onload = () => {
143
- if (xhr.status >= 200 && xhr.status < 300) {
144
- try {
145
- resolve(JSON.parse(xhr.responseText));
146
- } catch (e) {
147
- reject(new Error('Failed to parse JSON. Raw (200 chars): ' + xhr.responseText.substring(0, 200)));
148
- }
149
- } else {
150
- reject(new Error('XHR HTTP Status: ' + xhr.status));
151
- }
152
- };
153
- xhr.onerror = () => reject(new Error('XHR Network Error'));
154
- xhr.ontimeout = () => reject(new Error('XHR Timeout'));
155
- xhr.send();
156
- });
157
- }
158
- `;
159
-
160
- let data: any;
161
- try {
162
- data = await page.evaluate(evaluateScript);
163
- } catch (e: any) {
164
- throw new Error('API evaluate failed: ' + e.message);
165
- }
166
-
167
- if (data.code !== 0) {
168
- if (data.code === 37) {
169
- throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
170
- }
171
- throw new Error(`BOSS API error: ${data.message || 'Unknown'} (code=${data.code})\nRaw data: ${JSON.stringify(data)}`);
172
- }
124
+ const data = await bossFetch(page, targetUrl);
173
125
 
174
126
  const zpData = data.zpData || {};
175
127
  const batch = zpData.jobList || [];
176
- if (batch.length === 0) {
177
- break; // No more results
178
- }
128
+ if (batch.length === 0) break;
179
129
 
180
130
  let addedInBatch = 0;
181
131
  for (const j of batch) {
182
132
  if (!j.encryptJobId || seenIds.has(j.encryptJobId)) continue;
183
133
  seenIds.add(j.encryptJobId);
184
-
134
+
185
135
  allJobs.push({
186
136
  name: j.jobName,
187
137
  salary: j.salaryDesc,
@@ -199,14 +149,11 @@ cli({
199
149
  }
200
150
 
201
151
  if (addedInBatch === 0) {
202
- // Boss API is repeating identical pages, we've hit the pagination limit
203
- if (process.env.OPENCLI_VERBOSE) console.error(`[opencli:boss] API returned duplicate page, stopping pagination at ${allJobs.length} items`);
152
+ verbose(`API returned duplicate page, stopping pagination at ${allJobs.length} items`);
204
153
  break;
205
154
  }
206
155
 
207
- if (!zpData.hasMore) {
208
- break; // API says no more pages
209
- }
156
+ if (!zpData.hasMore) break;
210
157
  currentPage++;
211
158
  }
212
159