@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
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * BOSS直聘 batchgreet — batch greet recommended candidates.
3
- *
4
- * Combines recommend (greetRecSortList) + greet (UI automation).
5
- * Sends greeting messages to multiple candidates sequentially.
6
3
  */
7
4
  import { cli, Strategy } from '../../registry.js';
8
- import type { IPage } from '../../types.js';
5
+ import {
6
+ requirePage, navigateToChat, fetchRecommendList,
7
+ clickCandidateInList, typeAndSendMessage, verbose,
8
+ } from './common.js';
9
9
 
10
10
  cli({
11
11
  site: 'boss',
@@ -13,6 +13,7 @@ cli({
13
13
  description: 'BOSS直聘批量向推荐候选人发送招呼',
14
14
  domain: 'www.zhipin.com',
15
15
  strategy: Strategy.COOKIE,
16
+ navigateBefore: false,
16
17
  browser: true,
17
18
  args: [
18
19
  { name: 'job-id', default: '', help: 'Filter by encrypted job ID (greet all jobs if empty)' },
@@ -20,44 +21,18 @@ cli({
20
21
  { name: 'text', default: '', help: 'Custom greeting message (uses default if empty)' },
21
22
  ],
22
23
  columns: ['name', 'status', 'detail'],
23
- func: async (page: IPage | null, kwargs) => {
24
- if (!page) throw new Error('Browser page required');
24
+ func: async (page, kwargs) => {
25
+ requirePage(page);
25
26
 
26
27
  const filterJobId = kwargs['job-id'] || '';
27
28
  const limit = kwargs.limit || 5;
28
29
  const text = kwargs.text || '你好,请问您对这个职位感兴趣吗?';
29
30
 
30
- if (process.env.OPENCLI_VERBOSE) {
31
- console.error(`[opencli:boss] Batch greeting up to ${limit} candidates...`);
32
- }
33
-
34
- await page.goto('https://www.zhipin.com/web/chat/index');
35
- await page.wait({ time: 3 });
31
+ verbose(`Batch greeting up to ${limit} candidates...`);
36
32
 
37
- // Get recommended candidates
38
- const listData: any = 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/greetRecSortList', 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
- `);
33
+ await navigateToChat(page, 3);
52
34
 
53
- if (listData.code !== 0) {
54
- if (listData.code === 7 || listData.code === 37) {
55
- throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
56
- }
57
- throw new Error(`获取推荐列表失败: ${listData.message}`);
58
- }
59
-
60
- let candidates = listData.zpData?.friendList || [];
35
+ let candidates = await fetchRecommendList(page);
61
36
  if (filterJobId) {
62
37
  candidates = candidates.filter((f: any) => f.encryptJobId === filterJobId);
63
38
  }
@@ -74,88 +49,21 @@ cli({
74
49
  const friendName = candidate.name || '候选人';
75
50
 
76
51
  try {
77
- // Click on candidate
78
- const clicked: any = await page.evaluate(`
79
- async () => {
80
- const item = document.querySelector('#_${numericUid}-0') || document.querySelector('[id^="_${numericUid}"]');
81
- if (item) {
82
- item.click();
83
- return { clicked: true };
84
- }
85
- const items = document.querySelectorAll('.geek-item');
86
- for (const el of items) {
87
- if (el.id && el.id.startsWith('_${numericUid}')) {
88
- el.click();
89
- return { clicked: true };
90
- }
91
- }
92
- return { clicked: false };
93
- }
94
- `);
95
-
96
- if (!clicked.clicked) {
52
+ const clicked = await clickCandidateInList(page, numericUid);
53
+ if (!clicked) {
97
54
  results.push({ name: friendName, status: '❌ 跳过', detail: '在聊天列表中未找到' });
98
55
  continue;
99
56
  }
100
57
 
101
58
  await page.wait({ time: 2 });
102
59
 
103
- // Type message
104
- const typed: any = await page.evaluate(`
105
- async () => {
106
- const selectors = [
107
- '.chat-editor [contenteditable="true"]',
108
- '.chat-input [contenteditable="true"]',
109
- '[contenteditable="true"]',
110
- 'textarea',
111
- ];
112
- for (const sel of selectors) {
113
- const el = document.querySelector(sel);
114
- if (el && el.offsetParent !== null) {
115
- el.focus();
116
- if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
117
- el.value = ${JSON.stringify(text)};
118
- el.dispatchEvent(new Event('input', { bubbles: true }));
119
- } else {
120
- el.textContent = '';
121
- el.focus();
122
- document.execCommand('insertText', false, ${JSON.stringify(text)});
123
- el.dispatchEvent(new Event('input', { bubbles: true }));
124
- }
125
- return { found: true };
126
- }
127
- }
128
- return { found: false };
129
- }
130
- `);
131
-
132
- if (!typed.found) {
60
+ const sent = await typeAndSendMessage(page, text);
61
+ if (!sent) {
133
62
  results.push({ name: friendName, status: '❌ 失败', detail: '找不到消息输入框' });
134
63
  continue;
135
64
  }
136
65
 
137
- await page.wait({ time: 0.5 });
138
-
139
- // Click send
140
- const sent: any = await page.evaluate(`
141
- async () => {
142
- const btn = document.querySelector('.conversation-editor .submit')
143
- || document.querySelector('.submit-content .submit')
144
- || document.querySelector('.conversation-operate .submit');
145
- if (btn) {
146
- btn.click();
147
- return { clicked: true };
148
- }
149
- return { clicked: false };
150
- }
151
- `);
152
-
153
- if (!sent.clicked) {
154
- await page.pressKey('Enter');
155
- }
156
-
157
66
  await page.wait({ time: 1.5 });
158
-
159
67
  results.push({ name: friendName, status: '✅ 已发送', detail: text });
160
68
  } catch (e: any) {
161
69
  results.push({ name: friendName, status: '❌ 失败', detail: e.message?.substring(0, 80) || '未知错误' });
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import type { IPage } from '../../types.js';
2
+ import { requirePage, navigateToChat, fetchFriendList } from './common.js';
3
3
 
4
4
  cli({
5
5
  site: 'boss',
@@ -7,6 +7,7 @@ cli({
7
7
  description: 'BOSS直聘查看聊天列表(招聘端)',
8
8
  domain: 'www.zhipin.com',
9
9
  strategy: Strategy.COOKIE,
10
+ navigateBefore: false,
10
11
  browser: true,
11
12
  args: [
12
13
  { name: 'page', type: 'int', default: 1, help: 'Page number' },
@@ -14,31 +15,16 @@ cli({
14
15
  { name: 'job-id', default: '0', help: 'Filter by job ID (0=all)' },
15
16
  ],
16
17
  columns: ['name', 'job', 'last_msg', 'last_time', 'uid', 'security_id'],
17
- func: async (page: IPage | null, kwargs) => {
18
- if (!page) throw new Error('Browser page required');
19
- await page.goto('https://www.zhipin.com/web/chat/index');
20
- await page.wait({ time: 2 });
21
- const jobId = kwargs['job-id'] || '0';
22
- const pageNum = kwargs.page || 1;
23
- const limit = kwargs.limit || 20;
24
- const targetUrl = `https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=${pageNum}&status=0&jobId=${jobId}`;
25
- const data: any = await page.evaluate(`
26
- async () => {
27
- return new Promise((resolve, reject) => {
28
- const xhr = new XMLHttpRequest();
29
- xhr.open('GET', '${targetUrl}', true);
30
- xhr.withCredentials = true;
31
- xhr.timeout = 15000;
32
- xhr.setRequestHeader('Accept', 'application/json');
33
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(new Error('JSON parse failed')); } };
34
- xhr.onerror = () => reject(new Error('Network Error'));
35
- xhr.send();
36
- });
37
- }
38
- `);
39
- if (data.code !== 0) throw new Error(`API error: ${data.message} (code=${data.code})`);
40
- const friends = (data.zpData?.friendList || []).slice(0, limit);
41
- return friends.map((f: any) => ({
18
+ func: async (page, kwargs) => {
19
+ requirePage(page);
20
+ await navigateToChat(page);
21
+
22
+ const friends = await fetchFriendList(page, {
23
+ pageNum: kwargs.page || 1,
24
+ jobId: kwargs['job-id'] || '0',
25
+ });
26
+
27
+ return friends.slice(0, kwargs.limit || 20).map((f: any) => ({
42
28
  name: f.name || '',
43
29
  job: f.jobName || '',
44
30
  last_msg: f.lastMessageInfo?.text || '',
@@ -1,5 +1,5 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
- import type { IPage } from '../../types.js';
2
+ import { requirePage, navigateToChat, bossFetch, findFriendByUid } from './common.js';
3
3
 
4
4
  cli({
5
5
  site: 'boss',
@@ -7,54 +7,30 @@ cli({
7
7
  description: 'BOSS直聘查看与候选人的聊天消息',
8
8
  domain: 'www.zhipin.com',
9
9
  strategy: Strategy.COOKIE,
10
+ navigateBefore: false,
10
11
  browser: true,
11
12
  args: [
12
- { name: 'uid', required: true, help: 'Encrypted UID (from chatlist)' },
13
+ { name: 'uid', required: true, positional: true, help: 'Encrypted UID (from chatlist)' },
13
14
  { name: 'page', type: 'int', default: 1, help: 'Page number' },
14
15
  ],
15
16
  columns: ['from', 'type', 'text', 'time'],
16
- func: async (page: IPage | null, kwargs) => {
17
- if (!page) throw new Error('Browser page required');
18
- await page.goto('https://www.zhipin.com/web/chat/index');
19
- await page.wait({ time: 2 });
20
- const uid = kwargs.uid;
21
- const friendData: any = await page.evaluate(`
22
- async () => {
23
- return new Promise((resolve, reject) => {
24
- const xhr = new XMLHttpRequest();
25
- xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0', true);
26
- xhr.withCredentials = true;
27
- xhr.timeout = 15000;
28
- xhr.setRequestHeader('Accept', 'application/json');
29
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
30
- xhr.onerror = () => reject(new Error('Network Error'));
31
- xhr.send();
32
- });
33
- }
34
- `);
35
- if (friendData.code !== 0) throw new Error('获取好友列表失败');
36
- const friend = (friendData.zpData?.friendList || []).find((f: any) => f.encryptUid === uid);
17
+ func: async (page, kwargs) => {
18
+ requirePage(page);
19
+ await navigateToChat(page);
20
+
21
+ const friend = await findFriendByUid(page, kwargs.uid);
37
22
  if (!friend) throw new Error('未找到该候选人');
23
+
38
24
  const gid = friend.uid;
39
25
  const securityId = encodeURIComponent(friend.securityId);
40
26
  const msgUrl = `https://www.zhipin.com/wapi/zpchat/boss/historyMsg?gid=${gid}&securityId=${securityId}&page=${kwargs.page}&c=20&src=0`;
41
- const msgData: any = await page.evaluate(`
42
- async () => {
43
- return new Promise((resolve, reject) => {
44
- const xhr = new XMLHttpRequest();
45
- xhr.open('GET', '${msgUrl}', true);
46
- xhr.withCredentials = true;
47
- xhr.timeout = 15000;
48
- xhr.setRequestHeader('Accept', 'application/json');
49
- xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { resolve({raw: xhr.responseText.substring(0,500)}); } };
50
- xhr.onerror = () => reject(new Error('Network Error'));
51
- xhr.send();
52
- });
53
- }
54
- `);
55
- if (msgData.raw) throw new Error('Non-JSON: ' + msgData.raw);
56
- if (msgData.code !== 0) throw new Error('API error: ' + (msgData.message || msgData.code));
57
- const TYPE_MAP: Record<number, string> = {1: '文本', 2: '图片', 3: '招呼', 4: '简历', 5: '系统', 6: '名片', 7: '语音', 8: '视频', 9: '表情'};
27
+
28
+ const msgData = await bossFetch(page, msgUrl);
29
+
30
+ const TYPE_MAP: Record<number, string> = {
31
+ 1: '文本', 2: '图片', 3: '招呼', 4: '简历', 5: '系统',
32
+ 6: '名片', 7: '语音', 8: '视频', 9: '表情',
33
+ };
58
34
  const messages = msgData.zpData?.messages || msgData.zpData?.historyMsgList || [];
59
35
  return messages.map((m: any) => {
60
36
  const fromObj = m.from || {};
@@ -0,0 +1,287 @@
1
+ /**
2
+ * BOSS直聘 common utilities — shared logic for all boss adapters.
3
+ *
4
+ * Consolidates:
5
+ * - Page navigation with cookie context
6
+ * - XHR-based API calls (GET/POST) with automatic login state detection
7
+ * - Cookie expiry error codes (code 7, 37)
8
+ * - Verbose logging
9
+ */
10
+ import type { IPage } from '../../types.js';
11
+
12
+ // ── Constants ───────────────────────────────────────────────────────────────
13
+
14
+ const BOSS_DOMAIN = 'www.zhipin.com';
15
+ const CHAT_URL = `https://${BOSS_DOMAIN}/web/chat/index`;
16
+ const COOKIE_EXPIRED_CODES = new Set([7, 37]);
17
+ const COOKIE_EXPIRED_MSG = 'Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。';
18
+ const DEFAULT_TIMEOUT = 15_000;
19
+
20
+ // ── Types ───────────────────────────────────────────────────────────────────
21
+
22
+ export interface BossApiResponse {
23
+ code: number;
24
+ message?: string;
25
+ zpData?: any;
26
+ [key: string]: any;
27
+ }
28
+
29
+ export interface FetchOptions {
30
+ /** HTTP method, defaults to 'GET' */
31
+ method?: 'GET' | 'POST';
32
+ /** POST body (will be sent as application/x-www-form-urlencoded) */
33
+ body?: string;
34
+ /** XHR timeout in ms, defaults to 15000 */
35
+ timeout?: number;
36
+ /** If true, don't throw on non-zero code — return the raw response */
37
+ allowNonZero?: boolean;
38
+ }
39
+
40
+ // ── Core helpers ────────────────────────────────────────────────────────────
41
+
42
+ /**
43
+ * Assert that page is available (non-null).
44
+ */
45
+ export function requirePage(page: IPage | null): asserts page is IPage {
46
+ if (!page) throw new Error('Browser page required');
47
+ }
48
+
49
+ /**
50
+ * Navigate to BOSS chat page and wait for it to settle.
51
+ * This establishes the cookie context needed for subsequent API calls.
52
+ */
53
+ export async function navigateToChat(page: IPage, waitSeconds = 2): Promise<void> {
54
+ await page.goto(CHAT_URL);
55
+ await page.wait({ time: waitSeconds });
56
+ }
57
+
58
+ /**
59
+ * Navigate to a custom BOSS page (for search/detail that use different pages).
60
+ */
61
+ export async function navigateTo(page: IPage, url: string, waitSeconds = 1): Promise<void> {
62
+ await page.goto(url);
63
+ await page.wait({ time: waitSeconds });
64
+ }
65
+
66
+ /**
67
+ * Check if an API response indicates cookie expiry and throw a clear error.
68
+ * Call this after every BOSS API response with a non-zero code.
69
+ */
70
+ export function checkAuth(data: BossApiResponse): void {
71
+ if (COOKIE_EXPIRED_CODES.has(data.code)) {
72
+ throw new Error(COOKIE_EXPIRED_MSG);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Throw if the API response is not code 0.
78
+ * Checks for cookie expiry first, then throws with the provided message.
79
+ */
80
+ export function assertOk(data: BossApiResponse, errorPrefix?: string): void {
81
+ if (data.code === 0) return;
82
+ checkAuth(data);
83
+ const prefix = errorPrefix ? `${errorPrefix}: ` : '';
84
+ throw new Error(`${prefix}${data.message || 'Unknown error'} (code=${data.code})`);
85
+ }
86
+
87
+ /**
88
+ * Make a credentialed XHR request via page.evaluate().
89
+ *
90
+ * This is the single XHR template — no more copy-pasting the same 15-line
91
+ * XMLHttpRequest boilerplate across every adapter.
92
+ *
93
+ * @returns Parsed JSON response
94
+ * @throws On network error, timeout, JSON parse failure, or cookie expiry
95
+ */
96
+ export async function bossFetch(
97
+ page: IPage,
98
+ url: string,
99
+ opts: FetchOptions = {},
100
+ ): Promise<BossApiResponse> {
101
+ const method = opts.method ?? 'GET';
102
+ const timeout = opts.timeout ?? DEFAULT_TIMEOUT;
103
+ const body = opts.body ?? null;
104
+
105
+ // Build the evaluate script. We use JSON.stringify for safe interpolation.
106
+ const script = `
107
+ async () => {
108
+ return new Promise((resolve, reject) => {
109
+ const xhr = new XMLHttpRequest();
110
+ xhr.open(${JSON.stringify(method)}, ${JSON.stringify(url)}, true);
111
+ xhr.withCredentials = true;
112
+ xhr.timeout = ${timeout};
113
+ xhr.setRequestHeader('Accept', 'application/json');
114
+ ${method === 'POST' ? `xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');` : ''}
115
+ xhr.onload = () => {
116
+ try { resolve(JSON.parse(xhr.responseText)); }
117
+ catch(e) { reject(new Error('JSON parse failed: ' + xhr.responseText.substring(0, 200))); }
118
+ };
119
+ xhr.onerror = () => reject(new Error('Network Error'));
120
+ xhr.ontimeout = () => reject(new Error('Timeout'));
121
+ xhr.send(${body ? JSON.stringify(body) : 'null'});
122
+ });
123
+ }
124
+ `;
125
+
126
+ const data = await page.evaluate(script) as BossApiResponse;
127
+
128
+ // Auto-check auth unless caller opts out
129
+ if (!opts.allowNonZero && data.code !== 0) {
130
+ assertOk(data);
131
+ }
132
+
133
+ return data;
134
+ }
135
+
136
+ // ── Convenience helpers ─────────────────────────────────────────────────────
137
+
138
+ /**
139
+ * Fetch the boss friend (chat) list.
140
+ */
141
+ export async function fetchFriendList(
142
+ page: IPage,
143
+ opts: { pageNum?: number; jobId?: string } = {},
144
+ ): Promise<any[]> {
145
+ const pageNum = opts.pageNum ?? 1;
146
+ const jobId = opts.jobId ?? '0';
147
+ const url = `https://${BOSS_DOMAIN}/wapi/zprelation/friend/getBossFriendListV2.json?page=${pageNum}&status=0&jobId=${jobId}`;
148
+ const data = await bossFetch(page, url);
149
+ return data.zpData?.friendList || [];
150
+ }
151
+
152
+ /**
153
+ * Fetch the recommended candidates (greetRecSortList).
154
+ */
155
+ export async function fetchRecommendList(page: IPage): Promise<any[]> {
156
+ const url = `https://${BOSS_DOMAIN}/wapi/zprelation/friend/greetRecSortList`;
157
+ const data = await bossFetch(page, url);
158
+ return data.zpData?.friendList || [];
159
+ }
160
+
161
+ /**
162
+ * Find a friend by encryptUid, searching through friend list and optionally greet list.
163
+ * Returns null if not found.
164
+ */
165
+ export async function findFriendByUid(
166
+ page: IPage,
167
+ encryptUid: string,
168
+ opts: { maxPages?: number; checkGreetList?: boolean } = {},
169
+ ): Promise<any | null> {
170
+ const maxPages = opts.maxPages ?? 1;
171
+ const checkGreetList = opts.checkGreetList ?? false;
172
+
173
+ // Search friend list pages
174
+ for (let p = 1; p <= maxPages; p++) {
175
+ const friends = await fetchFriendList(page, { pageNum: p });
176
+ const found = friends.find((f: any) => f.encryptUid === encryptUid);
177
+ if (found) return found;
178
+ if (friends.length === 0) break;
179
+ }
180
+
181
+ // Optionally check greet list
182
+ if (checkGreetList) {
183
+ const greetList = await fetchRecommendList(page);
184
+ const found = greetList.find((f: any) => f.encryptUid === encryptUid);
185
+ if (found) return found;
186
+ }
187
+
188
+ return null;
189
+ }
190
+
191
+ // ── UI automation helpers ───────────────────────────────────────────────────
192
+
193
+ /**
194
+ * Click on a candidate in the chat list by their numeric UID.
195
+ * @returns true if clicked, false if not found
196
+ */
197
+ export async function clickCandidateInList(page: IPage, numericUid: string | number): Promise<boolean> {
198
+ const uid = String(numericUid).replace(/[^0-9]/g, ''); // sanitize to digits only
199
+ const result: any = await page.evaluate(`
200
+ async () => {
201
+ const uid = ${JSON.stringify(uid)};
202
+ const item = document.querySelector('#_' + uid + '-0') || document.querySelector('[id^="_' + uid + '"]');
203
+ if (item) {
204
+ item.click();
205
+ return { clicked: true };
206
+ }
207
+ const items = document.querySelectorAll('.geek-item');
208
+ for (const el of items) {
209
+ if (el.id && el.id.startsWith('_' + uid)) {
210
+ el.click();
211
+ return { clicked: true };
212
+ }
213
+ }
214
+ return { clicked: false };
215
+ }
216
+ `);
217
+ return result.clicked;
218
+ }
219
+
220
+ /**
221
+ * Type a message into the chat editor and send it.
222
+ * @returns true if sent successfully
223
+ */
224
+ export async function typeAndSendMessage(page: IPage, text: string): Promise<boolean> {
225
+ const typed: any = await page.evaluate(`
226
+ async () => {
227
+ const selectors = [
228
+ '.chat-editor [contenteditable="true"]',
229
+ '.chat-input [contenteditable="true"]',
230
+ '.message-editor [contenteditable="true"]',
231
+ '.chat-conversation [contenteditable="true"]',
232
+ '[contenteditable="true"]',
233
+ 'textarea',
234
+ ];
235
+ for (const sel of selectors) {
236
+ const el = document.querySelector(sel);
237
+ if (el && el.offsetParent !== null) {
238
+ el.focus();
239
+ if (el.tagName === 'TEXTAREA' || el.tagName === 'INPUT') {
240
+ el.value = ${JSON.stringify(text)};
241
+ el.dispatchEvent(new Event('input', { bubbles: true }));
242
+ } else {
243
+ el.textContent = '';
244
+ el.focus();
245
+ document.execCommand('insertText', false, ${JSON.stringify(text)});
246
+ el.dispatchEvent(new Event('input', { bubbles: true }));
247
+ }
248
+ return { found: true };
249
+ }
250
+ }
251
+ return { found: false };
252
+ }
253
+ `);
254
+
255
+ if (!typed.found) return false;
256
+
257
+ await page.wait({ time: 0.5 });
258
+
259
+ // Click send button
260
+ const sent: any = await page.evaluate(`
261
+ async () => {
262
+ const btn = document.querySelector('.conversation-editor .submit')
263
+ || document.querySelector('.submit-content .submit')
264
+ || document.querySelector('.conversation-operate .submit');
265
+ if (btn) {
266
+ btn.click();
267
+ return { clicked: true };
268
+ }
269
+ return { clicked: false };
270
+ }
271
+ `);
272
+
273
+ if (!sent.clicked) {
274
+ await page.pressKey('Enter');
275
+ }
276
+
277
+ return true;
278
+ }
279
+
280
+ /**
281
+ * Verbose log helper — prints when OPENCLI_VERBOSE or DEBUG=opencli is set.
282
+ */
283
+ export function verbose(msg: string): void {
284
+ if (process.env.OPENCLI_VERBOSE || process.env.DEBUG?.includes('opencli')) {
285
+ console.error(`[opencli:boss] ${msg}`);
286
+ }
287
+ }
@@ -1,11 +1,8 @@
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';
8
- import type { IPage } from '../../types.js';
5
+ import { requirePage, navigateTo, bossFetch, verbose } from './common.js';
9
6
 
10
7
  cli({
11
8
  site: 'boss',
@@ -13,10 +10,10 @@ cli({
13
10
  description: 'BOSS直聘查看职位详情',
14
11
  domain: 'www.zhipin.com',
15
12
  strategy: Strategy.COOKIE,
16
-
13
+ navigateBefore: false,
17
14
  browser: true,
18
15
  args: [
19
- { name: 'security-id', required: true, help: 'Security ID from search results (securityId field)' },
16
+ { name: 'security-id', positional: true, required: true, help: 'Security ID from search results (securityId field)' },
20
17
  ],
21
18
  columns: [
22
19
  'name', 'salary', 'experience', 'degree', 'city', 'district',
@@ -25,60 +22,17 @@ cli({
25
22
  'company', 'industry', 'scale', 'stage',
26
23
  'address', 'url',
27
24
  ],
28
- func: async (page: IPage | null, kwargs) => {
29
- if (!page) throw new Error('Browser page required');
25
+ func: async (page, kwargs) => {
26
+ requirePage(page);
30
27
 
31
28
  const securityId = kwargs['security-id'];
29
+ verbose('Fetching job detail...');
32
30
 
33
31
  // Navigate to zhipin.com first to establish cookie context (referrer + cookies)
34
- await page.goto('https://www.zhipin.com/web/geek/job');
35
- await page.wait({ time: 1 });
32
+ await navigateTo(page, 'https://www.zhipin.com/web/geek/job');
36
33
 
37
34
  const targetUrl = `https://www.zhipin.com/wapi/zpgeek/job/detail.json?securityId=${encodeURIComponent(securityId)}`;
38
-
39
- if (process.env.OPENCLI_VERBOSE || process.env.DEBUG?.includes('opencli')) {
40
- console.error(`[opencli:boss] Fetching job detail...`);
41
- }
42
-
43
- const evaluateScript = `
44
- async () => {
45
- return new Promise((resolve, reject) => {
46
- const xhr = new window.XMLHttpRequest();
47
- xhr.open('GET', ${JSON.stringify(targetUrl)}, true);
48
- xhr.withCredentials = true;
49
- xhr.timeout = 15000;
50
- xhr.setRequestHeader('Accept', 'application/json, text/plain, */*');
51
- xhr.onload = () => {
52
- if (xhr.status >= 200 && xhr.status < 300) {
53
- try {
54
- resolve(JSON.parse(xhr.responseText));
55
- } catch (e) {
56
- reject(new Error('Failed to parse JSON. Raw (200 chars): ' + xhr.responseText.substring(0, 200)));
57
- }
58
- } else {
59
- reject(new Error('XHR HTTP Status: ' + xhr.status));
60
- }
61
- };
62
- xhr.onerror = () => reject(new Error('XHR Network Error'));
63
- xhr.ontimeout = () => reject(new Error('XHR Timeout'));
64
- xhr.send();
65
- });
66
- }
67
- `;
68
-
69
- let data: any;
70
- try {
71
- data = await page.evaluate(evaluateScript);
72
- } catch (e: any) {
73
- throw new Error('API evaluate failed: ' + e.message);
74
- }
75
-
76
- if (data.code !== 0) {
77
- if (data.code === 37) {
78
- throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
79
- }
80
- throw new Error(`BOSS API error: ${data.message || 'Unknown'} (code=${data.code})`);
81
- }
35
+ const data = await bossFetch(page, targetUrl);
82
36
 
83
37
  const zpData = data.zpData || {};
84
38
  const jobInfo = zpData.jobInfo || {};