@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
@@ -0,0 +1,223 @@
1
+ // ── Constants ───────────────────────────────────────────────────────────────
2
+ const BOSS_DOMAIN = 'www.zhipin.com';
3
+ const CHAT_URL = `https://${BOSS_DOMAIN}/web/chat/index`;
4
+ const COOKIE_EXPIRED_CODES = new Set([7, 37]);
5
+ const COOKIE_EXPIRED_MSG = 'Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。';
6
+ const DEFAULT_TIMEOUT = 15_000;
7
+ // ── Core helpers ────────────────────────────────────────────────────────────
8
+ /**
9
+ * Assert that page is available (non-null).
10
+ */
11
+ export function requirePage(page) {
12
+ if (!page)
13
+ throw new Error('Browser page required');
14
+ }
15
+ /**
16
+ * Navigate to BOSS chat page and wait for it to settle.
17
+ * This establishes the cookie context needed for subsequent API calls.
18
+ */
19
+ export async function navigateToChat(page, waitSeconds = 2) {
20
+ await page.goto(CHAT_URL);
21
+ await page.wait({ time: waitSeconds });
22
+ }
23
+ /**
24
+ * Navigate to a custom BOSS page (for search/detail that use different pages).
25
+ */
26
+ export async function navigateTo(page, url, waitSeconds = 1) {
27
+ await page.goto(url);
28
+ await page.wait({ time: waitSeconds });
29
+ }
30
+ /**
31
+ * Check if an API response indicates cookie expiry and throw a clear error.
32
+ * Call this after every BOSS API response with a non-zero code.
33
+ */
34
+ export function checkAuth(data) {
35
+ if (COOKIE_EXPIRED_CODES.has(data.code)) {
36
+ throw new Error(COOKIE_EXPIRED_MSG);
37
+ }
38
+ }
39
+ /**
40
+ * Throw if the API response is not code 0.
41
+ * Checks for cookie expiry first, then throws with the provided message.
42
+ */
43
+ export function assertOk(data, errorPrefix) {
44
+ if (data.code === 0)
45
+ return;
46
+ checkAuth(data);
47
+ const prefix = errorPrefix ? `${errorPrefix}: ` : '';
48
+ throw new Error(`${prefix}${data.message || 'Unknown error'} (code=${data.code})`);
49
+ }
50
+ /**
51
+ * Make a credentialed XHR request via page.evaluate().
52
+ *
53
+ * This is the single XHR template — no more copy-pasting the same 15-line
54
+ * XMLHttpRequest boilerplate across every adapter.
55
+ *
56
+ * @returns Parsed JSON response
57
+ * @throws On network error, timeout, JSON parse failure, or cookie expiry
58
+ */
59
+ export async function bossFetch(page, url, opts = {}) {
60
+ const method = opts.method ?? 'GET';
61
+ const timeout = opts.timeout ?? DEFAULT_TIMEOUT;
62
+ const body = opts.body ?? null;
63
+ // Build the evaluate script. We use JSON.stringify for safe interpolation.
64
+ const script = `
65
+ async () => {
66
+ return new Promise((resolve, reject) => {
67
+ const xhr = new XMLHttpRequest();
68
+ xhr.open(${JSON.stringify(method)}, ${JSON.stringify(url)}, true);
69
+ xhr.withCredentials = true;
70
+ xhr.timeout = ${timeout};
71
+ xhr.setRequestHeader('Accept', 'application/json');
72
+ ${method === 'POST' ? `xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');` : ''}
73
+ xhr.onload = () => {
74
+ try { resolve(JSON.parse(xhr.responseText)); }
75
+ catch(e) { reject(new Error('JSON parse failed: ' + xhr.responseText.substring(0, 200))); }
76
+ };
77
+ xhr.onerror = () => reject(new Error('Network Error'));
78
+ xhr.ontimeout = () => reject(new Error('Timeout'));
79
+ xhr.send(${body ? JSON.stringify(body) : 'null'});
80
+ });
81
+ }
82
+ `;
83
+ const data = await page.evaluate(script);
84
+ // Auto-check auth unless caller opts out
85
+ if (!opts.allowNonZero && data.code !== 0) {
86
+ assertOk(data);
87
+ }
88
+ return data;
89
+ }
90
+ // ── Convenience helpers ─────────────────────────────────────────────────────
91
+ /**
92
+ * Fetch the boss friend (chat) list.
93
+ */
94
+ export async function fetchFriendList(page, opts = {}) {
95
+ const pageNum = opts.pageNum ?? 1;
96
+ const jobId = opts.jobId ?? '0';
97
+ const url = `https://${BOSS_DOMAIN}/wapi/zprelation/friend/getBossFriendListV2.json?page=${pageNum}&status=0&jobId=${jobId}`;
98
+ const data = await bossFetch(page, url);
99
+ return data.zpData?.friendList || [];
100
+ }
101
+ /**
102
+ * Fetch the recommended candidates (greetRecSortList).
103
+ */
104
+ export async function fetchRecommendList(page) {
105
+ const url = `https://${BOSS_DOMAIN}/wapi/zprelation/friend/greetRecSortList`;
106
+ const data = await bossFetch(page, url);
107
+ return data.zpData?.friendList || [];
108
+ }
109
+ /**
110
+ * Find a friend by encryptUid, searching through friend list and optionally greet list.
111
+ * Returns null if not found.
112
+ */
113
+ export async function findFriendByUid(page, encryptUid, opts = {}) {
114
+ const maxPages = opts.maxPages ?? 1;
115
+ const checkGreetList = opts.checkGreetList ?? false;
116
+ // Search friend list pages
117
+ for (let p = 1; p <= maxPages; p++) {
118
+ const friends = await fetchFriendList(page, { pageNum: p });
119
+ const found = friends.find((f) => f.encryptUid === encryptUid);
120
+ if (found)
121
+ return found;
122
+ if (friends.length === 0)
123
+ break;
124
+ }
125
+ // Optionally check greet list
126
+ if (checkGreetList) {
127
+ const greetList = await fetchRecommendList(page);
128
+ const found = greetList.find((f) => f.encryptUid === encryptUid);
129
+ if (found)
130
+ return found;
131
+ }
132
+ return null;
133
+ }
134
+ // ── UI automation helpers ───────────────────────────────────────────────────
135
+ /**
136
+ * Click on a candidate in the chat list by their numeric UID.
137
+ * @returns true if clicked, false if not found
138
+ */
139
+ export async function clickCandidateInList(page, numericUid) {
140
+ const uid = String(numericUid).replace(/[^0-9]/g, ''); // sanitize to digits only
141
+ const result = await page.evaluate(`
142
+ async () => {
143
+ const uid = ${JSON.stringify(uid)};
144
+ const item = document.querySelector('#_' + uid + '-0') || document.querySelector('[id^="_' + uid + '"]');
145
+ if (item) {
146
+ item.click();
147
+ return { clicked: true };
148
+ }
149
+ const items = document.querySelectorAll('.geek-item');
150
+ for (const el of items) {
151
+ if (el.id && el.id.startsWith('_' + uid)) {
152
+ el.click();
153
+ return { clicked: true };
154
+ }
155
+ }
156
+ return { clicked: false };
157
+ }
158
+ `);
159
+ return result.clicked;
160
+ }
161
+ /**
162
+ * Type a message into the chat editor and send it.
163
+ * @returns true if sent successfully
164
+ */
165
+ export async function typeAndSendMessage(page, text) {
166
+ const typed = await page.evaluate(`
167
+ async () => {
168
+ const selectors = [
169
+ '.chat-editor [contenteditable="true"]',
170
+ '.chat-input [contenteditable="true"]',
171
+ '.message-editor [contenteditable="true"]',
172
+ '.chat-conversation [contenteditable="true"]',
173
+ '[contenteditable="true"]',
174
+ 'textarea',
175
+ ];
176
+ for (const sel of selectors) {
177
+ const el = document.querySelector(sel);
178
+ if (el && el.offsetParent !== null) {
179
+ el.focus();
180
+ if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
181
+ el.value = ${JSON.stringify(text)};
182
+ el.dispatchEvent(new Event('input', { bubbles: true }));
183
+ } else {
184
+ el.textContent = '';
185
+ el.focus();
186
+ document.execCommand('insertText', false, ${JSON.stringify(text)});
187
+ el.dispatchEvent(new Event('input', { bubbles: true }));
188
+ }
189
+ return { found: true };
190
+ }
191
+ }
192
+ return { found: false };
193
+ }
194
+ `);
195
+ if (!typed.found)
196
+ return false;
197
+ await page.wait({ time: 0.5 });
198
+ // Click send button
199
+ const sent = await page.evaluate(`
200
+ async () => {
201
+ const btn = document.querySelector('.conversation-editor .submit')
202
+ || document.querySelector('.submit-content .submit')
203
+ || document.querySelector('.conversation-operate .submit');
204
+ if (btn) {
205
+ btn.click();
206
+ return { clicked: true };
207
+ }
208
+ return { clicked: false };
209
+ }
210
+ `);
211
+ if (!sent.clicked) {
212
+ await page.pressKey('Enter');
213
+ }
214
+ return true;
215
+ }
216
+ /**
217
+ * Verbose log helper — prints when OPENCLI_VERBOSE or DEBUG=opencli is set.
218
+ */
219
+ export function verbose(msg) {
220
+ if (process.env.OPENCLI_VERBOSE || process.env.DEBUG?.includes('opencli')) {
221
+ console.error(`[opencli:boss] ${msg}`);
222
+ }
223
+ }
@@ -1,19 +1,18 @@
1
1
  /**
2
2
  * BOSS直聘 job detail — fetch full job posting details via browser cookie API.
3
- *
4
- * Uses securityId from search results to call the detail API.
5
- * Returns: job description, skills, welfare, boss info, company info, address.
6
3
  */
7
4
  import { cli, Strategy } from '../../registry.js';
5
+ import { requirePage, navigateTo, bossFetch, verbose } from './common.js';
8
6
  cli({
9
7
  site: 'boss',
10
8
  name: 'detail',
11
9
  description: 'BOSS直聘查看职位详情',
12
10
  domain: 'www.zhipin.com',
13
11
  strategy: Strategy.COOKIE,
12
+ navigateBefore: false,
14
13
  browser: true,
15
14
  args: [
16
- { name: 'security-id', required: true, help: 'Security ID from search results (securityId field)' },
15
+ { name: 'security-id', positional: true, required: true, help: 'Security ID from search results (securityId field)' },
17
16
  ],
18
17
  columns: [
19
18
  'name', 'salary', 'experience', 'degree', 'city', 'district',
@@ -23,54 +22,13 @@ cli({
23
22
  'address', 'url',
24
23
  ],
25
24
  func: async (page, kwargs) => {
26
- if (!page)
27
- throw new Error('Browser page required');
25
+ requirePage(page);
28
26
  const securityId = kwargs['security-id'];
27
+ verbose('Fetching job detail...');
29
28
  // Navigate to zhipin.com first to establish cookie context (referrer + cookies)
30
- await page.goto('https://www.zhipin.com/web/geek/job');
31
- await page.wait({ time: 1 });
29
+ await navigateTo(page, 'https://www.zhipin.com/web/geek/job');
32
30
  const targetUrl = `https://www.zhipin.com/wapi/zpgeek/job/detail.json?securityId=${encodeURIComponent(securityId)}`;
33
- if (process.env.OPENCLI_VERBOSE || process.env.DEBUG?.includes('opencli')) {
34
- console.error(`[opencli:boss] Fetching job detail...`);
35
- }
36
- const evaluateScript = `
37
- async () => {
38
- return new Promise((resolve, reject) => {
39
- const xhr = new window.XMLHttpRequest();
40
- xhr.open('GET', ${JSON.stringify(targetUrl)}, true);
41
- xhr.withCredentials = true;
42
- xhr.timeout = 15000;
43
- xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
44
- xhr.onload = () => {
45
- if (xhr.status >= 200 && xhr.status < 300) {
46
- try {
47
- resolve(JSON.parse(xhr.responseText));
48
- } catch (e) {
49
- reject(new Error('Failed to parse JSON. Raw (200 chars): ' + xhr.responseText.substring(0, 200)));
50
- }
51
- } else {
52
- reject(new Error('XHR HTTP Status: ' + xhr.status));
53
- }
54
- };
55
- xhr.onerror = () => reject(new Error('XHR Network Error'));
56
- xhr.ontimeout = () => reject(new Error('XHR Timeout'));
57
- xhr.send();
58
- });
59
- }
60
- `;
61
- let data;
62
- try {
63
- data = await page.evaluate(evaluateScript);
64
- }
65
- catch (e) {
66
- throw new Error('API evaluate failed: ' + e.message);
67
- }
68
- if (data.code !== 0) {
69
- if (data.code === 37) {
70
- throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
71
- }
72
- throw new Error(`BOSS API error: ${data.message || 'Unknown'} (code=${data.code})`);
73
- }
31
+ const data = await bossFetch(page, targetUrl);
74
32
  const zpData = data.zpData || {};
75
33
  const jobInfo = zpData.jobInfo || {};
76
34
  const bossInfo = zpData.bossInfo || {};
@@ -1,107 +1,41 @@
1
1
  /**
2
2
  * BOSS直聘 exchange — request phone/wechat exchange with a candidate.
3
- *
4
- * Uses POST /wapi/zpchat/exchange/request to send an exchange request.
5
3
  */
6
4
  import { cli, Strategy } from '../../registry.js';
5
+ import { requirePage, navigateToChat, bossFetch, findFriendByUid, verbose } from './common.js';
7
6
  cli({
8
7
  site: 'boss',
9
8
  name: 'exchange',
10
9
  description: 'BOSS直聘交换联系方式(请求手机/微信)',
11
10
  domain: 'www.zhipin.com',
12
11
  strategy: Strategy.COOKIE,
12
+ navigateBefore: false,
13
13
  browser: true,
14
14
  args: [
15
- { name: 'uid', required: true, help: 'Encrypted UID of the candidate' },
15
+ { name: 'uid', required: true, positional: true, help: 'Encrypted UID of the candidate' },
16
16
  { name: 'type', default: 'phone', choices: ['phone', 'wechat'], help: 'Exchange type: phone or wechat' },
17
17
  ],
18
18
  columns: ['status', 'detail'],
19
19
  func: async (page, kwargs) => {
20
- if (!page)
21
- throw new Error('Browser page required');
22
- const uid = kwargs.uid;
20
+ requirePage(page);
23
21
  const exchangeType = kwargs.type || 'phone';
24
- if (process.env.OPENCLI_VERBOSE) {
25
- console.error(`[opencli:boss] Requesting ${exchangeType} exchange for ${uid}...`);
26
- }
27
- await page.goto('https://www.zhipin.com/web/chat/index');
28
- await page.wait({ time: 2 });
29
- // Find candidate
30
- let friend = null;
31
- // Check greet list
32
- const greetData = await page.evaluate(`
33
- async () => {
34
- return new Promise((resolve, reject) => {
35
- const xhr = new XMLHttpRequest();
36
- xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/greetRecSortList', true);
37
- xhr.withCredentials = true;
38
- xhr.timeout = 15000;
39
- xhr.setRequestHeader('Accept', 'application/json');
40
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
41
- xhr.onerror = () => reject(new Error('Network Error'));
42
- xhr.send();
43
- });
44
- }
45
- `);
46
- if (greetData.code === 0) {
47
- friend = (greetData.zpData?.friendList || []).find((f) => f.encryptUid === uid);
48
- }
49
- if (!friend) {
50
- const friendData = await page.evaluate(`
51
- async () => {
52
- return new Promise((resolve, reject) => {
53
- const xhr = new XMLHttpRequest();
54
- xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0', true);
55
- xhr.withCredentials = true;
56
- xhr.timeout = 15000;
57
- xhr.setRequestHeader('Accept', 'application/json');
58
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
59
- xhr.onerror = () => reject(new Error('Network Error'));
60
- xhr.send();
61
- });
62
- }
63
- `);
64
- if (friendData.code === 0) {
65
- friend = (friendData.zpData?.friendList || []).find((f) => f.encryptUid === uid);
66
- }
67
- }
68
- if (!friend) {
22
+ verbose(`Requesting ${exchangeType} exchange for ${kwargs.uid}...`);
23
+ await navigateToChat(page);
24
+ const friend = await findFriendByUid(page, kwargs.uid, { checkGreetList: true });
25
+ if (!friend)
69
26
  throw new Error('未找到该候选人');
70
- }
71
- const numericUid = friend.uid;
72
27
  const friendName = friend.name || '候选人';
73
- const securityId = friend.securityId || '';
74
- // type mapping from JS source: 1=phone, 2=wechat, 4=resume
75
28
  const typeId = exchangeType === 'wechat' ? 2 : 1;
76
- // Params from JS: {type, securityId, uniqueId, name}
77
29
  const params = new URLSearchParams({
78
30
  type: String(typeId),
79
- securityId: securityId,
80
- uniqueId: String(numericUid),
31
+ securityId: friend.securityId || '',
32
+ uniqueId: String(friend.uid),
81
33
  name: friendName,
82
34
  });
83
- // POST with form-urlencoded (discovered from 336.js bundle)
84
- const data = await page.evaluate(`
85
- async () => {
86
- return new Promise((resolve, reject) => {
87
- const xhr = new XMLHttpRequest();
88
- xhr.open('POST', 'https://www.zhipin.com/wapi/zpchat/exchange/request', true);
89
- xhr.withCredentials = true;
90
- xhr.timeout = 15000;
91
- xhr.setRequestHeader('Accept', 'application/json');
92
- xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
93
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(new Error('JSON parse failed')); } };
94
- xhr.onerror = () => reject(new Error('Network Error'));
95
- xhr.send(${JSON.stringify(params.toString())});
35
+ await bossFetch(page, 'https://www.zhipin.com/wapi/zpchat/exchange/request', {
36
+ method: 'POST',
37
+ body: params.toString(),
96
38
  });
97
- }
98
- `);
99
- if (data.code !== 0) {
100
- if (data.code === 7 || data.code === 37) {
101
- throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
102
- }
103
- throw new Error(`交换请求失败: ${data.message} (code=${data.code})`);
104
- }
105
39
  const typeLabel = exchangeType === 'wechat' ? '微信' : '手机号';
106
40
  return [{
107
41
  status: '✅ 交换请求已发送',
@@ -1,174 +1,47 @@
1
1
  /**
2
2
  * BOSS直聘 greet — send greeting to a new candidate (initiate chat).
3
- *
4
- * This is different from send.ts which messages existing contacts.
5
- * For new candidates (from recommend list), we navigate to their chat page
6
- * and use UI automation to send the greeting message.
7
- *
8
- * The greetRecSortList provides candidates who have applied or been recommended.
9
- * We click on them in the list and send a greeting.
10
3
  */
11
4
  import { cli, Strategy } from '../../registry.js';
5
+ import { requirePage, navigateToChat, findFriendByUid, clickCandidateInList, typeAndSendMessage, verbose, } from './common.js';
12
6
  cli({
13
7
  site: 'boss',
14
8
  name: 'greet',
15
9
  description: 'BOSS直聘向新候选人发送招呼(开始聊天)',
16
10
  domain: 'www.zhipin.com',
17
11
  strategy: Strategy.COOKIE,
12
+ navigateBefore: false,
18
13
  browser: true,
19
14
  args: [
20
- { name: 'uid', required: true, help: 'Encrypted UID of the candidate (from recommend)' },
15
+ { name: 'uid', positional: true, required: true, help: 'Encrypted UID of the candidate (from recommend)' },
21
16
  { name: 'security-id', required: true, help: 'Security ID of the candidate' },
22
17
  { name: 'job-id', required: true, help: 'Encrypted job ID' },
23
18
  { name: 'text', default: '', help: 'Custom greeting message (uses default template if empty)' },
24
19
  ],
25
20
  columns: ['status', 'detail'],
26
21
  func: async (page, kwargs) => {
27
- if (!page)
28
- throw new Error('Browser page required');
29
- const uid = kwargs.uid;
30
- const securityId = kwargs['security-id'];
31
- const jobId = kwargs['job-id'];
32
- const text = kwargs.text;
33
- if (process.env.OPENCLI_VERBOSE) {
34
- console.error(`[opencli:boss] Greeting candidate ${uid}...`);
35
- }
36
- // Navigate to chat page
37
- await page.goto('https://www.zhipin.com/web/chat/index');
38
- await page.wait({ time: 3 });
39
- // Find the candidate in the greet list by encryptUid
40
- const listData = await page.evaluate(`
41
- async () => {
42
- return new Promise((resolve, reject) => {
43
- const xhr = new XMLHttpRequest();
44
- xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/greetRecSortList', true);
45
- xhr.withCredentials = true;
46
- xhr.timeout = 15000;
47
- xhr.setRequestHeader('Accept', 'application/json');
48
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
49
- xhr.onerror = () => reject(new Error('Network Error'));
50
- xhr.send();
22
+ requirePage(page);
23
+ verbose(`Greeting candidate ${kwargs.uid}...`);
24
+ await navigateToChat(page, 3);
25
+ // Find candidate in greet list or friend list
26
+ const friend = await findFriendByUid(page, kwargs.uid, {
27
+ maxPages: 1,
28
+ checkGreetList: true,
51
29
  });
52
- }
53
- `);
54
- if (listData.code !== 0) {
55
- if (listData.code === 7 || listData.code === 37) {
56
- throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
57
- }
58
- throw new Error(`获取候选人列表失败: ${listData.message}`);
59
- }
60
- // Also check the regular friend list
61
- let target = null;
62
- const greetList = listData.zpData?.friendList || [];
63
- target = greetList.find((f) => f.encryptUid === uid);
64
- let numericUid = null;
65
- let friendName = '候选人';
66
- if (target) {
67
- numericUid = target.uid;
68
- friendName = target.name || friendName;
69
- }
70
- if (!numericUid) {
71
- // Try to find in friend list
72
- const friendData = await page.evaluate(`
73
- async () => {
74
- return new Promise((resolve, reject) => {
75
- const xhr = new XMLHttpRequest();
76
- xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0', 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 (friendData.code === 0) {
87
- const allFriends = friendData.zpData?.friendList || [];
88
- const found = allFriends.find((f) => f.encryptUid === uid);
89
- if (found) {
90
- numericUid = found.uid;
91
- friendName = found.name || friendName;
92
- }
93
- }
94
- }
95
- if (!numericUid) {
30
+ if (!friend) {
96
31
  throw new Error('未找到该候选人,请确认 uid 是否正确(可从 recommend 命令获取)');
97
32
  }
98
- // Click on the candidate in the chat list
99
- const clicked = await page.evaluate(`
100
- async () => {
101
- const item = document.querySelector('#_${numericUid}-0') || document.querySelector('[id^="_${numericUid}"]');
102
- if (item) {
103
- item.click();
104
- return { clicked: true, id: item.id };
105
- }
106
- const items = document.querySelectorAll('.geek-item');
107
- for (const el of items) {
108
- if (el.id && el.id.startsWith('_${numericUid}')) {
109
- el.click();
110
- return { clicked: true, id: el.id };
111
- }
112
- }
113
- return { clicked: false };
114
- }
115
- `);
116
- if (!clicked.clicked) {
33
+ const numericUid = friend.uid;
34
+ const friendName = friend.name || '候选人';
35
+ const clicked = await clickCandidateInList(page, numericUid);
36
+ if (!clicked) {
117
37
  throw new Error('无法在聊天列表中找到该用户,候选人可能不在当前列表中');
118
38
  }
119
39
  await page.wait({ time: 2 });
120
- // Type the message
121
- const msgText = text || '你好,请问您对这个职位感兴趣吗?';
122
- const typed = await page.evaluate(`
123
- async () => {
124
- const selectors = [
125
- '.chat-editor [contenteditable="true"]',
126
- '.chat-input [contenteditable="true"]',
127
- '.message-editor [contenteditable="true"]',
128
- '.chat-conversation [contenteditable="true"]',
129
- '[contenteditable="true"]',
130
- 'textarea',
131
- ];
132
-
133
- for (const sel of selectors) {
134
- const el = document.querySelector(sel);
135
- if (el && el.offsetParent !== null) {
136
- el.focus();
137
- if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
138
- el.value = ${JSON.stringify(msgText)};
139
- el.dispatchEvent(new Event('input', { bubbles: true }));
140
- } else {
141
- el.textContent = '';
142
- el.focus();
143
- document.execCommand('insertText', false, ${JSON.stringify(msgText)});
144
- el.dispatchEvent(new Event('input', { bubbles: true }));
145
- }
146
- return { found: true, selector: sel };
147
- }
148
- }
149
- return { found: false };
150
- }
151
- `);
152
- if (!typed.found) {
40
+ const msgText = kwargs.text || '你好,请问您对这个职位感兴趣吗?';
41
+ const sent = await typeAndSendMessage(page, msgText);
42
+ if (!sent) {
153
43
  throw new Error('找不到消息输入框');
154
44
  }
155
- await page.wait({ time: 0.5 });
156
- // Click send button
157
- const sent = await page.evaluate(`
158
- async () => {
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
- await page.pressKey('Enter');
171
- }
172
45
  await page.wait({ time: 1 });
173
46
  return [{ status: '✅ 招呼已发送', detail: `已向 ${friendName} 发送: ${msgText}` }];
174
47
  },