@jackwener/opencli 1.1.1 → 1.2.1

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