@jackwener/opencli 1.0.6 → 1.1.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 (400) hide show
  1. package/.agents/skills/cross-project-adapter-migration/SKILL.md +2 -2
  2. package/.github/pull_request_template.md +7 -0
  3. package/.github/workflows/doc-check.yml +36 -0
  4. package/.github/workflows/docs.yml +7 -42
  5. package/CHANGELOG.md +23 -0
  6. package/CLI-EXPLORER.md +9 -8
  7. package/README.md +51 -10
  8. package/README.zh-CN.md +29 -11
  9. package/SKILL.md +102 -33
  10. package/dist/browser/cdp.js +6 -1
  11. package/dist/browser/page.d.ts +4 -1
  12. package/dist/browser/page.js +7 -1
  13. package/dist/build-manifest.js +23 -16
  14. package/dist/cli-manifest.json +951 -296
  15. package/dist/cli.d.ts +6 -0
  16. package/dist/cli.js +225 -148
  17. package/dist/clis/antigravity/serve.js +296 -47
  18. package/dist/clis/apple-podcasts/commands.test.d.ts +2 -0
  19. package/dist/clis/apple-podcasts/commands.test.js +76 -0
  20. package/dist/clis/apple-podcasts/search.js +2 -2
  21. package/dist/clis/apple-podcasts/top.js +9 -2
  22. package/dist/clis/arxiv/paper.js +21 -0
  23. package/dist/clis/arxiv/search.js +24 -0
  24. package/dist/clis/arxiv/utils.d.ts +18 -0
  25. package/dist/clis/arxiv/utils.js +49 -0
  26. package/dist/clis/bilibili/dynamic.js +1 -1
  27. package/dist/clis/bilibili/favorite.js +1 -1
  28. package/dist/clis/bilibili/feed.js +1 -1
  29. package/dist/clis/bilibili/following.js +1 -1
  30. package/dist/clis/bilibili/history.js +1 -1
  31. package/dist/clis/bilibili/me.js +1 -1
  32. package/dist/clis/bilibili/ranking.js +1 -1
  33. package/dist/clis/bilibili/search.js +3 -3
  34. package/dist/clis/bilibili/subtitle.js +1 -1
  35. package/dist/clis/bilibili/user-videos.js +1 -1
  36. package/dist/{bilibili.d.ts → clis/bilibili/utils.d.ts} +1 -1
  37. package/dist/clis/bloomberg/businessweek.d.ts +1 -0
  38. package/dist/clis/bloomberg/businessweek.js +17 -0
  39. package/dist/clis/bloomberg/economics.d.ts +1 -0
  40. package/dist/clis/bloomberg/economics.js +17 -0
  41. package/dist/clis/bloomberg/feeds.d.ts +1 -0
  42. package/dist/clis/bloomberg/feeds.js +15 -0
  43. package/dist/clis/bloomberg/industries.d.ts +1 -0
  44. package/dist/clis/bloomberg/industries.js +17 -0
  45. package/dist/clis/bloomberg/main.d.ts +1 -0
  46. package/dist/clis/bloomberg/main.js +17 -0
  47. package/dist/clis/bloomberg/markets.d.ts +1 -0
  48. package/dist/clis/bloomberg/markets.js +17 -0
  49. package/dist/clis/bloomberg/news.d.ts +1 -0
  50. package/dist/clis/bloomberg/news.js +105 -0
  51. package/dist/clis/bloomberg/opinions.d.ts +1 -0
  52. package/dist/clis/bloomberg/opinions.js +17 -0
  53. package/dist/clis/bloomberg/politics.d.ts +1 -0
  54. package/dist/clis/bloomberg/politics.js +17 -0
  55. package/dist/clis/bloomberg/tech.d.ts +1 -0
  56. package/dist/clis/bloomberg/tech.js +17 -0
  57. package/dist/clis/bloomberg/utils.d.ts +34 -0
  58. package/dist/clis/bloomberg/utils.js +364 -0
  59. package/dist/clis/bloomberg/utils.test.d.ts +1 -0
  60. package/dist/clis/bloomberg/utils.test.js +129 -0
  61. package/dist/clis/boss/batchgreet.d.ts +1 -0
  62. package/dist/clis/boss/batchgreet.js +147 -0
  63. package/dist/clis/boss/chatlist.js +2 -2
  64. package/dist/clis/boss/detail.js +2 -2
  65. package/dist/clis/boss/exchange.d.ts +1 -0
  66. package/dist/clis/boss/exchange.js +111 -0
  67. package/dist/clis/boss/greet.d.ts +1 -0
  68. package/dist/clis/boss/greet.js +175 -0
  69. package/dist/clis/boss/invite.d.ts +1 -0
  70. package/dist/clis/boss/invite.js +158 -0
  71. package/dist/clis/boss/joblist.d.ts +1 -0
  72. package/dist/clis/boss/joblist.js +55 -0
  73. package/dist/clis/boss/mark.d.ts +1 -0
  74. package/dist/clis/boss/mark.js +141 -0
  75. package/dist/clis/boss/recommend.d.ts +1 -0
  76. package/dist/clis/boss/recommend.js +83 -0
  77. package/dist/clis/boss/search.js +1 -1
  78. package/dist/clis/boss/send.js +1 -1
  79. package/dist/clis/boss/stats.d.ts +1 -0
  80. package/dist/clis/boss/stats.js +116 -0
  81. package/dist/clis/chaoxing/assignments.js +1 -1
  82. package/dist/clis/chaoxing/exams.js +1 -1
  83. package/dist/{chaoxing.d.ts → clis/chaoxing/utils.d.ts} +1 -1
  84. package/dist/{chaoxing.js → clis/chaoxing/utils.js} +0 -2
  85. package/dist/clis/chaoxing/utils.test.d.ts +1 -0
  86. package/dist/{chaoxing.test.js → clis/chaoxing/utils.test.js} +1 -1
  87. package/dist/clis/chatgpt/read.js +1 -1
  88. package/dist/clis/chatwise/export.js +1 -1
  89. package/dist/clis/chatwise/model.js +2 -2
  90. package/dist/clis/chatwise/screenshot.js +1 -1
  91. package/dist/clis/codex/export.js +1 -1
  92. package/dist/clis/codex/model.js +2 -2
  93. package/dist/clis/codex/screenshot.js +1 -1
  94. package/dist/clis/coupang/add-to-cart.js +3 -4
  95. package/dist/clis/coupang/search.js +2 -4
  96. package/dist/clis/coupang/utils.test.d.ts +1 -0
  97. package/dist/{coupang.test.js → clis/coupang/utils.test.js} +1 -1
  98. package/dist/clis/ctrip/search.js +1 -1
  99. package/dist/clis/cursor/export.js +1 -1
  100. package/dist/clis/cursor/model.js +2 -2
  101. package/dist/clis/cursor/screenshot.js +1 -1
  102. package/dist/clis/jike/comment.js +2 -3
  103. package/dist/clis/jike/create.js +1 -2
  104. package/dist/clis/jike/feed.js +0 -1
  105. package/dist/clis/jike/like.js +1 -2
  106. package/dist/clis/jike/notifications.js +0 -1
  107. package/dist/clis/jike/post.yaml +1 -0
  108. package/dist/clis/jike/repost.js +1 -2
  109. package/dist/clis/jike/search.js +2 -3
  110. package/dist/clis/jike/topic.yaml +1 -0
  111. package/dist/clis/jike/user.yaml +1 -0
  112. package/dist/clis/jimeng/history.yaml +0 -1
  113. package/dist/clis/linkedin/search.js +7 -7
  114. package/dist/clis/linux-do/category.yaml +1 -0
  115. package/dist/clis/linux-do/search.yaml +4 -3
  116. package/dist/clis/linux-do/topic.yaml +1 -0
  117. package/dist/clis/notion/export.js +1 -1
  118. package/dist/clis/reddit/comment.js +3 -4
  119. package/dist/clis/reddit/read.js +4 -5
  120. package/dist/clis/reddit/save.js +2 -3
  121. package/dist/clis/reddit/saved.js +0 -1
  122. package/dist/clis/reddit/search.yaml +1 -0
  123. package/dist/clis/reddit/subscribe.js +0 -1
  124. package/dist/clis/reddit/upvote.js +2 -3
  125. package/dist/clis/reddit/upvoted.js +0 -1
  126. package/dist/clis/reddit/user-comments.yaml +1 -0
  127. package/dist/clis/reddit/user-posts.yaml +1 -0
  128. package/dist/clis/reddit/user.yaml +1 -0
  129. package/dist/clis/reuters/search.js +1 -1
  130. package/dist/clis/sinafinance/news.d.ts +7 -0
  131. package/dist/clis/sinafinance/news.js +61 -0
  132. package/dist/clis/smzdm/search.js +2 -3
  133. package/dist/clis/stackoverflow/search.yaml +1 -0
  134. package/dist/clis/steam/top-sellers.yaml +29 -0
  135. package/dist/clis/twitter/accept.js +2 -2
  136. package/dist/clis/twitter/article.js +2 -2
  137. package/dist/clis/twitter/block.d.ts +1 -0
  138. package/dist/clis/twitter/block.js +88 -0
  139. package/dist/clis/twitter/delete.js +1 -1
  140. package/dist/clis/twitter/hide-reply.d.ts +1 -0
  141. package/dist/clis/twitter/hide-reply.js +66 -0
  142. package/dist/clis/twitter/like.js +1 -1
  143. package/dist/clis/twitter/post.js +1 -1
  144. package/dist/clis/twitter/reply-dm.js +1 -1
  145. package/dist/clis/twitter/reply.js +2 -2
  146. package/dist/clis/twitter/search.js +1 -1
  147. package/dist/clis/twitter/thread.js +2 -2
  148. package/dist/clis/twitter/trending.d.ts +1 -0
  149. package/dist/clis/twitter/trending.js +91 -0
  150. package/dist/clis/twitter/unblock.d.ts +1 -0
  151. package/dist/clis/twitter/unblock.js +71 -0
  152. package/dist/clis/v2ex/topic.yaml +1 -0
  153. package/dist/clis/weibo/hot.js +0 -1
  154. package/dist/clis/weread/book.js +1 -1
  155. package/dist/clis/weread/highlights.js +1 -1
  156. package/dist/clis/weread/notes.js +1 -1
  157. package/dist/clis/weread/search.js +1 -1
  158. package/dist/clis/wikipedia/search.d.ts +1 -0
  159. package/dist/clis/wikipedia/search.js +30 -0
  160. package/dist/clis/wikipedia/summary.d.ts +1 -0
  161. package/dist/clis/wikipedia/summary.js +28 -0
  162. package/dist/clis/wikipedia/utils.d.ts +8 -0
  163. package/dist/clis/wikipedia/utils.js +18 -0
  164. package/dist/clis/xiaohongshu/creator-note-detail.d.ts +79 -5
  165. package/dist/clis/xiaohongshu/creator-note-detail.js +323 -70
  166. package/dist/clis/xiaohongshu/creator-note-detail.test.d.ts +1 -0
  167. package/dist/clis/xiaohongshu/creator-note-detail.test.js +258 -0
  168. package/dist/clis/xiaohongshu/creator-notes-summary.d.ts +28 -0
  169. package/dist/clis/xiaohongshu/creator-notes-summary.js +92 -0
  170. package/dist/clis/xiaohongshu/creator-notes-summary.test.d.ts +1 -0
  171. package/dist/clis/xiaohongshu/creator-notes-summary.test.js +49 -0
  172. package/dist/clis/xiaohongshu/creator-notes.d.ts +18 -5
  173. package/dist/clis/xiaohongshu/creator-notes.js +189 -71
  174. package/dist/clis/xiaohongshu/creator-notes.test.d.ts +1 -0
  175. package/dist/clis/xiaohongshu/creator-notes.test.js +191 -0
  176. package/dist/clis/xiaohongshu/creator-profile.js +0 -1
  177. package/dist/clis/xiaohongshu/creator-stats.js +0 -1
  178. package/dist/clis/xiaohongshu/download.js +2 -3
  179. package/dist/clis/xiaohongshu/feed.yaml +0 -1
  180. package/dist/clis/xiaohongshu/notifications.yaml +0 -1
  181. package/dist/clis/xiaohongshu/search.js +2 -2
  182. package/dist/clis/xiaohongshu/user.js +1 -2
  183. package/dist/clis/yahoo-finance/quote.js +0 -1
  184. package/dist/clis/youtube/search.js +1 -1
  185. package/dist/clis/youtube/transcript.js +1 -1
  186. package/dist/clis/youtube/video.js +1 -1
  187. package/dist/clis/zhihu/download.js +1 -2
  188. package/dist/clis/zhihu/question.js +1 -1
  189. package/dist/clis/zhihu/search.yaml +4 -3
  190. package/dist/commanderAdapter.d.ts +21 -0
  191. package/dist/commanderAdapter.js +111 -0
  192. package/dist/{engine.d.ts → discovery.d.ts} +0 -6
  193. package/dist/{engine.js → discovery.js} +1 -98
  194. package/dist/download/index.d.ts +2 -6
  195. package/dist/download/index.js +19 -46
  196. package/dist/engine.test.d.ts +1 -1
  197. package/dist/engine.test.js +8 -7
  198. package/dist/execution.d.ts +22 -0
  199. package/dist/execution.js +129 -0
  200. package/dist/explore.js +121 -107
  201. package/dist/external-clis.yaml +48 -0
  202. package/dist/external.d.ts +25 -0
  203. package/dist/external.js +156 -0
  204. package/dist/main.js +1 -1
  205. package/dist/pipeline/steps/browser.js +8 -2
  206. package/dist/registry.d.ts +2 -0
  207. package/dist/registry.js +2 -0
  208. package/dist/runtime.d.ts +5 -0
  209. package/dist/runtime.js +8 -0
  210. package/dist/serialization.d.ts +34 -0
  211. package/dist/serialization.js +63 -0
  212. package/dist/types.d.ts +4 -1
  213. package/docs/.vitepress/config.mts +14 -3
  214. package/docs/adapters/browser/arxiv.md +27 -0
  215. package/docs/adapters/browser/barchart.md +32 -0
  216. package/docs/adapters/browser/bloomberg.md +70 -0
  217. package/docs/adapters/browser/chaoxing.md +39 -0
  218. package/docs/adapters/browser/grok.md +35 -0
  219. package/docs/adapters/browser/hf.md +42 -0
  220. package/docs/adapters/browser/jike.md +45 -0
  221. package/docs/adapters/browser/jimeng.md +39 -0
  222. package/docs/adapters/browser/linux-do.md +45 -0
  223. package/docs/adapters/browser/sinafinance.md +35 -0
  224. package/docs/adapters/browser/stackoverflow.md +35 -0
  225. package/docs/adapters/browser/steam.md +26 -0
  226. package/docs/adapters/browser/twitter.md +3 -0
  227. package/docs/adapters/browser/weread.md +48 -0
  228. package/docs/adapters/browser/wikipedia.md +30 -0
  229. package/docs/adapters/browser/xiaohongshu.md +5 -1
  230. package/docs/adapters/desktop/chatgpt.md +3 -3
  231. package/docs/adapters/index.md +13 -0
  232. package/docs/advanced/download.md +4 -4
  233. package/docs/developer/architecture.md +17 -4
  234. package/package.json +1 -1
  235. package/scripts/check-doc-coverage.sh +69 -0
  236. package/scripts/copy-yaml.cjs +7 -0
  237. package/src/browser/cdp.ts +9 -4
  238. package/src/browser/page.ts +7 -1
  239. package/src/build-manifest.ts +25 -19
  240. package/src/cli.ts +253 -119
  241. package/src/clis/antigravity/serve.ts +323 -50
  242. package/src/clis/apple-podcasts/commands.test.ts +95 -0
  243. package/src/clis/apple-podcasts/search.ts +2 -2
  244. package/src/clis/apple-podcasts/top.ts +12 -2
  245. package/src/clis/arxiv/paper.ts +21 -0
  246. package/src/clis/arxiv/search.ts +24 -0
  247. package/src/clis/arxiv/utils.ts +63 -0
  248. package/src/clis/bilibili/dynamic.ts +1 -1
  249. package/src/clis/bilibili/favorite.ts +1 -1
  250. package/src/clis/bilibili/feed.ts +1 -1
  251. package/src/clis/bilibili/following.ts +1 -1
  252. package/src/clis/bilibili/history.ts +1 -1
  253. package/src/clis/bilibili/me.ts +1 -1
  254. package/src/clis/bilibili/ranking.ts +1 -1
  255. package/src/clis/bilibili/search.ts +3 -3
  256. package/src/clis/bilibili/subtitle.ts +1 -1
  257. package/src/clis/bilibili/user-videos.ts +1 -1
  258. package/src/{bilibili.ts → clis/bilibili/utils.ts} +1 -1
  259. package/src/clis/bloomberg/businessweek.ts +18 -0
  260. package/src/clis/bloomberg/economics.ts +18 -0
  261. package/src/clis/bloomberg/feeds.ts +16 -0
  262. package/src/clis/bloomberg/industries.ts +18 -0
  263. package/src/clis/bloomberg/main.ts +18 -0
  264. package/src/clis/bloomberg/markets.ts +18 -0
  265. package/src/clis/bloomberg/news.ts +136 -0
  266. package/src/clis/bloomberg/opinions.ts +18 -0
  267. package/src/clis/bloomberg/politics.ts +18 -0
  268. package/src/clis/bloomberg/tech.ts +18 -0
  269. package/src/clis/bloomberg/utils.test.ts +135 -0
  270. package/src/clis/bloomberg/utils.ts +429 -0
  271. package/src/clis/boss/batchgreet.ts +167 -0
  272. package/src/clis/boss/chatlist.ts +2 -2
  273. package/src/clis/boss/detail.ts +2 -2
  274. package/src/clis/boss/exchange.ts +126 -0
  275. package/src/clis/boss/greet.ts +198 -0
  276. package/src/clis/boss/invite.ts +177 -0
  277. package/src/clis/boss/joblist.ts +63 -0
  278. package/src/clis/boss/mark.ts +155 -0
  279. package/src/clis/boss/recommend.ts +94 -0
  280. package/src/clis/boss/search.ts +1 -1
  281. package/src/clis/boss/send.ts +1 -1
  282. package/src/clis/boss/stats.ts +130 -0
  283. package/src/clis/chaoxing/assignments.ts +1 -1
  284. package/src/clis/chaoxing/exams.ts +1 -1
  285. package/src/{chaoxing.test.ts → clis/chaoxing/utils.test.ts} +1 -1
  286. package/src/{chaoxing.ts → clis/chaoxing/utils.ts} +1 -3
  287. package/src/clis/chatgpt/README.zh-CN.md +3 -3
  288. package/src/clis/chatgpt/read.ts +1 -1
  289. package/src/clis/chatwise/export.ts +1 -1
  290. package/src/clis/chatwise/model.ts +2 -2
  291. package/src/clis/chatwise/screenshot.ts +1 -1
  292. package/src/clis/codex/export.ts +1 -1
  293. package/src/clis/codex/model.ts +2 -2
  294. package/src/clis/codex/screenshot.ts +1 -1
  295. package/src/clis/coupang/add-to-cart.ts +3 -4
  296. package/src/clis/coupang/search.ts +2 -4
  297. package/src/{coupang.test.ts → clis/coupang/utils.test.ts} +1 -1
  298. package/src/clis/ctrip/search.ts +1 -1
  299. package/src/clis/cursor/export.ts +1 -1
  300. package/src/clis/cursor/model.ts +2 -2
  301. package/src/clis/cursor/screenshot.ts +1 -1
  302. package/src/clis/jike/comment.ts +2 -3
  303. package/src/clis/jike/create.ts +1 -2
  304. package/src/clis/jike/feed.ts +0 -1
  305. package/src/clis/jike/like.ts +1 -2
  306. package/src/clis/jike/notifications.ts +0 -1
  307. package/src/clis/jike/post.yaml +1 -0
  308. package/src/clis/jike/repost.ts +1 -2
  309. package/src/clis/jike/search.ts +2 -3
  310. package/src/clis/jike/topic.yaml +1 -0
  311. package/src/clis/jike/user.yaml +1 -0
  312. package/src/clis/jimeng/history.yaml +0 -1
  313. package/src/clis/linkedin/search.ts +7 -7
  314. package/src/clis/linux-do/category.yaml +1 -0
  315. package/src/clis/linux-do/search.yaml +4 -3
  316. package/src/clis/linux-do/topic.yaml +1 -0
  317. package/src/clis/notion/export.ts +1 -1
  318. package/src/clis/reddit/comment.ts +3 -4
  319. package/src/clis/reddit/read.ts +4 -5
  320. package/src/clis/reddit/save.ts +2 -3
  321. package/src/clis/reddit/saved.ts +0 -1
  322. package/src/clis/reddit/search.yaml +1 -0
  323. package/src/clis/reddit/subscribe.ts +0 -1
  324. package/src/clis/reddit/upvote.ts +2 -3
  325. package/src/clis/reddit/upvoted.ts +0 -1
  326. package/src/clis/reddit/user-comments.yaml +1 -0
  327. package/src/clis/reddit/user-posts.yaml +1 -0
  328. package/src/clis/reddit/user.yaml +1 -0
  329. package/src/clis/reuters/search.ts +1 -1
  330. package/src/clis/sinafinance/news.ts +76 -0
  331. package/src/clis/smzdm/search.ts +2 -3
  332. package/src/clis/stackoverflow/search.yaml +1 -0
  333. package/src/clis/steam/top-sellers.yaml +29 -0
  334. package/src/clis/twitter/accept.ts +2 -2
  335. package/src/clis/twitter/article.ts +2 -2
  336. package/src/clis/twitter/block.ts +92 -0
  337. package/src/clis/twitter/delete.ts +1 -1
  338. package/src/clis/twitter/hide-reply.ts +70 -0
  339. package/src/clis/twitter/like.ts +1 -1
  340. package/src/clis/twitter/post.ts +1 -1
  341. package/src/clis/twitter/reply-dm.ts +1 -1
  342. package/src/clis/twitter/reply.ts +2 -2
  343. package/src/clis/twitter/search.ts +1 -1
  344. package/src/clis/twitter/thread.ts +2 -2
  345. package/src/clis/twitter/trending.ts +113 -0
  346. package/src/clis/twitter/unblock.ts +75 -0
  347. package/src/clis/v2ex/topic.yaml +1 -0
  348. package/src/clis/weibo/hot.ts +0 -1
  349. package/src/clis/weread/book.ts +1 -1
  350. package/src/clis/weread/highlights.ts +1 -1
  351. package/src/clis/weread/notes.ts +1 -1
  352. package/src/clis/weread/search.ts +1 -1
  353. package/src/clis/wikipedia/search.ts +32 -0
  354. package/src/clis/wikipedia/summary.ts +28 -0
  355. package/src/clis/wikipedia/utils.ts +20 -0
  356. package/src/clis/xiaohongshu/creator-note-detail.test.ts +272 -0
  357. package/src/clis/xiaohongshu/creator-note-detail.ts +425 -73
  358. package/src/clis/xiaohongshu/creator-notes-summary.test.ts +54 -0
  359. package/src/clis/xiaohongshu/creator-notes-summary.ts +120 -0
  360. package/src/clis/xiaohongshu/creator-notes.test.ts +211 -0
  361. package/src/clis/xiaohongshu/creator-notes.ts +254 -75
  362. package/src/clis/xiaohongshu/creator-profile.ts +0 -1
  363. package/src/clis/xiaohongshu/creator-stats.ts +0 -1
  364. package/src/clis/xiaohongshu/download.ts +2 -3
  365. package/src/clis/xiaohongshu/feed.yaml +0 -1
  366. package/src/clis/xiaohongshu/notifications.yaml +0 -1
  367. package/src/clis/xiaohongshu/search.ts +2 -2
  368. package/src/clis/xiaohongshu/user.ts +1 -2
  369. package/src/clis/yahoo-finance/quote.ts +0 -1
  370. package/src/clis/youtube/search.ts +1 -1
  371. package/src/clis/youtube/transcript.ts +1 -1
  372. package/src/clis/youtube/video.ts +1 -1
  373. package/src/clis/zhihu/download.ts +1 -2
  374. package/src/clis/zhihu/question.ts +1 -1
  375. package/src/clis/zhihu/search.yaml +4 -3
  376. package/src/commanderAdapter.ts +113 -0
  377. package/src/daemon.ts +3 -3
  378. package/src/{engine.ts → discovery.ts} +1 -108
  379. package/src/download/index.ts +21 -54
  380. package/src/engine.test.ts +8 -7
  381. package/src/execution.ts +138 -0
  382. package/src/explore.ts +135 -109
  383. package/src/external-clis.yaml +48 -0
  384. package/src/external.ts +185 -0
  385. package/src/main.ts +1 -1
  386. package/src/pipeline/steps/browser.ts +7 -2
  387. package/src/registry.ts +5 -0
  388. package/src/runtime.ts +9 -0
  389. package/src/serialization.ts +79 -0
  390. package/src/types.ts +1 -1
  391. package/tests/e2e/browser-public.test.ts +25 -0
  392. package/tests/e2e/public-commands.test.ts +55 -1
  393. package/dist/clis/twitter/trending.yaml +0 -46
  394. package/src/clis/twitter/trending.yaml +0 -46
  395. /package/dist/{chaoxing.test.d.ts → clis/arxiv/paper.d.ts} +0 -0
  396. /package/dist/{coupang.test.d.ts → clis/arxiv/search.d.ts} +0 -0
  397. /package/dist/{bilibili.js → clis/bilibili/utils.js} +0 -0
  398. /package/dist/{coupang.d.ts → clis/coupang/utils.d.ts} +0 -0
  399. /package/dist/{coupang.js → clis/coupang/utils.js} +0 -0
  400. /package/src/{coupang.ts → clis/coupang/utils.ts} +0 -0
@@ -0,0 +1,111 @@
1
+ /**
2
+ * BOSS直聘 exchange — request phone/wechat exchange with a candidate.
3
+ *
4
+ * Uses POST /wapi/zpchat/exchange/request to send an exchange request.
5
+ */
6
+ import { cli, Strategy } from '../../registry.js';
7
+ cli({
8
+ site: 'boss',
9
+ name: 'exchange',
10
+ description: 'BOSS直聘交换联系方式(请求手机/微信)',
11
+ domain: 'www.zhipin.com',
12
+ strategy: Strategy.COOKIE,
13
+ browser: true,
14
+ args: [
15
+ { name: 'uid', required: true, help: 'Encrypted UID of the candidate' },
16
+ { name: 'type', default: 'phone', choices: ['phone', 'wechat'], help: 'Exchange type: phone or wechat' },
17
+ ],
18
+ columns: ['status', 'detail'],
19
+ func: async (page, kwargs) => {
20
+ if (!page)
21
+ throw new Error('Browser page required');
22
+ const uid = kwargs.uid;
23
+ 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) {
69
+ throw new Error('未找到该候选人');
70
+ }
71
+ const numericUid = friend.uid;
72
+ const friendName = friend.name || '候选人';
73
+ const securityId = friend.securityId || '';
74
+ // type mapping from JS source: 1=phone, 2=wechat, 4=resume
75
+ const typeId = exchangeType === 'wechat' ? 2 : 1;
76
+ // Params from JS: {type, securityId, uniqueId, name}
77
+ const params = new URLSearchParams({
78
+ type: String(typeId),
79
+ securityId: securityId,
80
+ uniqueId: String(numericUid),
81
+ name: friendName,
82
+ });
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())});
96
+ });
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
+ const typeLabel = exchangeType === 'wechat' ? '微信' : '手机号';
106
+ return [{
107
+ status: '✅ 交换请求已发送',
108
+ detail: `已向 ${friendName} 发送${typeLabel}交换请求`,
109
+ }];
110
+ },
111
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,175 @@
1
+ /**
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
+ */
11
+ import { cli, Strategy } from '../../registry.js';
12
+ cli({
13
+ site: 'boss',
14
+ name: 'greet',
15
+ description: 'BOSS直聘向新候选人发送招呼(开始聊天)',
16
+ domain: 'www.zhipin.com',
17
+ strategy: Strategy.COOKIE,
18
+ browser: true,
19
+ args: [
20
+ { name: 'uid', required: true, help: 'Encrypted UID of the candidate (from recommend)' },
21
+ { name: 'security-id', required: true, help: 'Security ID of the candidate' },
22
+ { name: 'job-id', required: true, help: 'Encrypted job ID' },
23
+ { name: 'text', default: '', help: 'Custom greeting message (uses default template if empty)' },
24
+ ],
25
+ columns: ['status', 'detail'],
26
+ 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();
51
+ });
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) {
96
+ throw new Error('未找到该候选人,请确认 uid 是否正确(可从 recommend 命令获取)');
97
+ }
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) {
117
+ throw new Error('无法在聊天列表中找到该用户,候选人可能不在当前列表中');
118
+ }
119
+ 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) {
153
+ throw new Error('找不到消息输入框');
154
+ }
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
+ await page.wait({ time: 1 });
173
+ return [{ status: '✅ 招呼已发送', detail: `已向 ${friendName} 发送: ${msgText}` }];
174
+ },
175
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,158 @@
1
+ /**
2
+ * BOSS直聘 invite — send interview invitation to a candidate.
3
+ *
4
+ * Uses POST /wapi/zpinterview/boss/interview/invite to send interview invitations.
5
+ * Address and contact info come from the boss's saved settings.
6
+ */
7
+ import { cli, Strategy } from '../../registry.js';
8
+ cli({
9
+ site: 'boss',
10
+ name: 'invite',
11
+ description: 'BOSS直聘发送面试邀请',
12
+ domain: 'www.zhipin.com',
13
+ strategy: Strategy.COOKIE,
14
+ browser: true,
15
+ args: [
16
+ { name: 'uid', required: true, help: 'Encrypted UID of the candidate' },
17
+ { name: 'time', required: true, help: 'Interview time (e.g. 2025-04-01 14:00)' },
18
+ { name: 'address', default: '', help: 'Interview address (uses saved address if empty)' },
19
+ { name: 'contact', default: '', help: 'Contact person name (uses saved contact if empty)' },
20
+ ],
21
+ columns: ['status', 'detail'],
22
+ func: async (page, kwargs) => {
23
+ if (!page)
24
+ throw new Error('Browser page required');
25
+ const uid = kwargs.uid;
26
+ const timeStr = kwargs.time;
27
+ const address = kwargs.address;
28
+ const contact = kwargs.contact;
29
+ if (process.env.OPENCLI_VERBOSE) {
30
+ console.error(`[opencli:boss] Sending interview invitation to ${uid}...`);
31
+ }
32
+ await page.goto('https://www.zhipin.com/web/chat/index');
33
+ await page.wait({ time: 2 });
34
+ // Get candidate info
35
+ let friend = null;
36
+ // Check greet list first
37
+ const greetData = await page.evaluate(`
38
+ async () => {
39
+ return new Promise((resolve, reject) => {
40
+ const xhr = new XMLHttpRequest();
41
+ xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/greetRecSortList', true);
42
+ xhr.withCredentials = true;
43
+ xhr.timeout = 15000;
44
+ xhr.setRequestHeader('Accept', 'application/json');
45
+ xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
46
+ xhr.onerror = () => reject(new Error('Network Error'));
47
+ xhr.send();
48
+ });
49
+ }
50
+ `);
51
+ if (greetData.code === 0) {
52
+ friend = (greetData.zpData?.friendList || []).find((f) => f.encryptUid === uid);
53
+ }
54
+ if (!friend) {
55
+ const friendData = await page.evaluate(`
56
+ async () => {
57
+ return new Promise((resolve, reject) => {
58
+ const xhr = new XMLHttpRequest();
59
+ xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0', true);
60
+ xhr.withCredentials = true;
61
+ xhr.timeout = 15000;
62
+ xhr.setRequestHeader('Accept', 'application/json');
63
+ xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
64
+ xhr.onerror = () => reject(new Error('Network Error'));
65
+ xhr.send();
66
+ });
67
+ }
68
+ `);
69
+ if (friendData.code === 0) {
70
+ friend = (friendData.zpData?.friendList || []).find((f) => f.encryptUid === uid);
71
+ }
72
+ }
73
+ if (!friend) {
74
+ throw new Error('未找到该候选人');
75
+ }
76
+ const numericUid = friend.uid;
77
+ const friendName = friend.name || '候选人';
78
+ const securityId = friend.securityId || '';
79
+ const encJobId = friend.encryptJobId || '';
80
+ // Get saved contact info
81
+ const contactData = await page.evaluate(`
82
+ async () => {
83
+ return new Promise((resolve, reject) => {
84
+ const xhr = new XMLHttpRequest();
85
+ xhr.open('GET', 'https://www.zhipin.com/wapi/zpinterview/boss/interview/contactInit', true);
86
+ xhr.withCredentials = true;
87
+ xhr.timeout = 10000;
88
+ xhr.setRequestHeader('Accept', 'application/json');
89
+ xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
90
+ xhr.onerror = () => reject(new Error('Network Error'));
91
+ xhr.send();
92
+ });
93
+ }
94
+ `);
95
+ const contactId = contactData.zpData?.contactId || '';
96
+ const contactName = contact || contactData.zpData?.contactName || '';
97
+ const contactPhone = contactData.zpData?.contactPhone || '';
98
+ // Get saved address
99
+ const addressData = await page.evaluate(`
100
+ async () => {
101
+ return new Promise((resolve, reject) => {
102
+ const xhr = new XMLHttpRequest();
103
+ xhr.open('GET', 'https://www.zhipin.com/wapi/zpinterview/boss/interview/listAddress', true);
104
+ xhr.withCredentials = true;
105
+ xhr.timeout = 10000;
106
+ xhr.setRequestHeader('Accept', 'application/json');
107
+ xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
108
+ xhr.onerror = () => reject(new Error('Network Error'));
109
+ xhr.send();
110
+ });
111
+ }
112
+ `);
113
+ const savedAddress = addressData.zpData?.list?.[0] || {};
114
+ const addressText = address || savedAddress.cityAddressText || savedAddress.addressText || '';
115
+ // Parse interview time
116
+ const interviewTime = new Date(timeStr).getTime();
117
+ if (isNaN(interviewTime)) {
118
+ throw new Error(`时间格式错误: ${timeStr},请使用格式如 2025-04-01 14:00`);
119
+ }
120
+ // Send interview invitation
121
+ const params = new URLSearchParams({
122
+ uid: String(numericUid),
123
+ securityId: securityId,
124
+ encryptJobId: encJobId,
125
+ interviewTime: String(interviewTime),
126
+ contactId: contactId,
127
+ contactName: contactName,
128
+ contactPhone: contactPhone,
129
+ address: addressText,
130
+ interviewType: '1', // 1 = onsite
131
+ });
132
+ const data = await page.evaluate(`
133
+ async () => {
134
+ return new Promise((resolve, reject) => {
135
+ const xhr = new XMLHttpRequest();
136
+ xhr.open('POST', 'https://www.zhipin.com/wapi/zpinterview/boss/interview/invite.json', true);
137
+ xhr.withCredentials = true;
138
+ xhr.timeout = 15000;
139
+ xhr.setRequestHeader('Accept', 'application/json');
140
+ xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
141
+ xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(new Error('JSON parse failed')); } };
142
+ xhr.onerror = () => reject(new Error('Network Error'));
143
+ xhr.send(${JSON.stringify(params.toString())});
144
+ });
145
+ }
146
+ `);
147
+ if (data.code !== 0) {
148
+ if (data.code === 7 || data.code === 37) {
149
+ throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
150
+ }
151
+ throw new Error(`面试邀请发送失败: ${data.message} (code=${data.code})`);
152
+ }
153
+ return [{
154
+ status: '✅ 面试邀请已发送',
155
+ detail: `已向 ${friendName} 发送面试邀请\n时间: ${timeStr}\n地点: ${addressText}\n联系人: ${contactName}`,
156
+ }];
157
+ },
158
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,55 @@
1
+ /**
2
+ * BOSS直聘 job list — list my published jobs via boss API.
3
+ *
4
+ * Uses /wapi/zpjob/job/chatted/jobList to get job list with status info.
5
+ */
6
+ import { cli, Strategy } from '../../registry.js';
7
+ cli({
8
+ site: 'boss',
9
+ name: 'joblist',
10
+ description: 'BOSS直聘查看我发布的职位列表',
11
+ domain: 'www.zhipin.com',
12
+ strategy: Strategy.COOKIE,
13
+ browser: true,
14
+ args: [],
15
+ columns: ['job_name', 'salary', 'city', 'status', 'encrypt_job_id'],
16
+ func: async (page, kwargs) => {
17
+ if (!page)
18
+ throw new Error('Browser page required');
19
+ if (process.env.OPENCLI_VERBOSE) {
20
+ console.error('[opencli:boss] Fetching job list...');
21
+ }
22
+ await page.goto('https://www.zhipin.com/web/chat/index');
23
+ await page.wait({ time: 2 });
24
+ const targetUrl = 'https://www.zhipin.com/wapi/zpjob/job/chatted/jobList';
25
+ const data = 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.ontimeout = () => reject(new Error('Timeout'));
36
+ xhr.send();
37
+ });
38
+ }
39
+ `);
40
+ if (data.code !== 0) {
41
+ if (data.code === 7 || data.code === 37) {
42
+ throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
43
+ }
44
+ throw new Error(`API error: ${data.message} (code=${data.code})`);
45
+ }
46
+ const jobs = data.zpData || [];
47
+ return jobs.map((j) => ({
48
+ job_name: j.jobName || '',
49
+ salary: j.salaryDesc || '',
50
+ city: j.address || '',
51
+ status: j.jobOnlineStatus === 1 ? '在线' : '已关闭',
52
+ encrypt_job_id: j.encryptJobId || '',
53
+ }));
54
+ },
55
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,141 @@
1
+ /**
2
+ * BOSS直聘 mark — label/mark a candidate.
3
+ *
4
+ * Uses /wapi/zprelation/friend/label/addMark to add a label to a candidate,
5
+ * and /wapi/zprelation/friend/label/deleteMark to remove one.
6
+ *
7
+ * Available labels (from /wapi/zprelation/friend/label/get):
8
+ * 1=新招呼, 2=沟通中, 3=已约面, 4=已获取简历, 5=已交换电话,
9
+ * 6=已交换微信, 7=不合适, 8=牛人发起, 11=收藏
10
+ */
11
+ import { cli, Strategy } from '../../registry.js';
12
+ const LABEL_MAP = {
13
+ '新招呼': 1, '沟通中': 2, '已约面': 3, '已获取简历': 4,
14
+ '已交换电话': 5, '已交换微信': 6, '不合适': 7, '牛人发起': 8, '收藏': 11,
15
+ };
16
+ cli({
17
+ site: 'boss',
18
+ name: 'mark',
19
+ description: 'BOSS直聘给候选人添加标签',
20
+ domain: 'www.zhipin.com',
21
+ strategy: Strategy.COOKIE,
22
+ browser: true,
23
+ args: [
24
+ { name: 'uid', required: true, help: 'Encrypted UID of the candidate' },
25
+ { name: 'label', required: true, help: 'Label name (新招呼/沟通中/已约面/已获取简历/已交换电话/已交换微信/不合适/收藏) or label ID' },
26
+ { name: 'remove', type: 'boolean', default: false, help: 'Remove the label instead of adding' },
27
+ ],
28
+ columns: ['status', 'detail'],
29
+ func: async (page, kwargs) => {
30
+ if (!page)
31
+ throw new Error('Browser page required');
32
+ const uid = kwargs.uid;
33
+ const labelInput = kwargs.label;
34
+ const remove = kwargs.remove || false;
35
+ // Resolve label to ID
36
+ let labelId;
37
+ if (LABEL_MAP[labelInput]) {
38
+ labelId = LABEL_MAP[labelInput];
39
+ }
40
+ else if (!isNaN(Number(labelInput))) {
41
+ labelId = Number(labelInput);
42
+ }
43
+ else {
44
+ // Try partial match
45
+ const entry = Object.entries(LABEL_MAP).find(([k]) => k.includes(labelInput));
46
+ if (entry) {
47
+ labelId = entry[1];
48
+ }
49
+ else {
50
+ throw new Error(`未知标签: ${labelInput}。可用标签: ${Object.keys(LABEL_MAP).join(', ')}`);
51
+ }
52
+ }
53
+ if (process.env.OPENCLI_VERBOSE) {
54
+ console.error(`[opencli:boss] ${remove ? 'Removing' : 'Adding'} label ${labelId} for ${uid}...`);
55
+ }
56
+ await page.goto('https://www.zhipin.com/web/chat/index');
57
+ await page.wait({ time: 2 });
58
+ // First get numeric UID from friend list
59
+ const friendData = await page.evaluate(`
60
+ async () => {
61
+ return new Promise((resolve, reject) => {
62
+ const xhr = new XMLHttpRequest();
63
+ xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/getBossFriendListV2.json?page=1&status=0&jobId=0', true);
64
+ xhr.withCredentials = true;
65
+ xhr.timeout = 15000;
66
+ xhr.setRequestHeader('Accept', 'application/json');
67
+ xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
68
+ xhr.onerror = () => reject(new Error('Network Error'));
69
+ xhr.send();
70
+ });
71
+ }
72
+ `);
73
+ if (friendData.code !== 0) {
74
+ if (friendData.code === 7 || friendData.code === 37) {
75
+ throw new Error('Cookie 已过期!请在当前 Chrome 浏览器中重新登录 BOSS 直聘。');
76
+ }
77
+ throw new Error(`获取好友列表失败: ${friendData.message}`);
78
+ }
79
+ // Find in friend list (check multiple pages)
80
+ let friend = null;
81
+ let allFriends = friendData.zpData?.friendList || [];
82
+ friend = allFriends.find((f) => f.encryptUid === uid);
83
+ if (!friend) {
84
+ // Also check greetRecSortList
85
+ const greetData = await page.evaluate(`
86
+ async () => {
87
+ return new Promise((resolve, reject) => {
88
+ const xhr = new XMLHttpRequest();
89
+ xhr.open('GET', 'https://www.zhipin.com/wapi/zprelation/friend/greetRecSortList', true);
90
+ xhr.withCredentials = true;
91
+ xhr.timeout = 15000;
92
+ xhr.setRequestHeader('Accept', 'application/json');
93
+ xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(e); } };
94
+ xhr.onerror = () => reject(new Error('Network Error'));
95
+ xhr.send();
96
+ });
97
+ }
98
+ `);
99
+ if (greetData.code === 0) {
100
+ friend = (greetData.zpData?.friendList || []).find((f) => f.encryptUid === uid);
101
+ }
102
+ }
103
+ if (!friend) {
104
+ throw new Error('未找到该候选人');
105
+ }
106
+ const numericUid = friend.uid;
107
+ const friendName = friend.name || '候选人';
108
+ const friendSource = friend.friendSource ?? 0;
109
+ const action = remove ? 'deleteMark' : 'addMark';
110
+ const targetUrl = `https://www.zhipin.com/wapi/zprelation/friend/label/${action}`;
111
+ // The API uses friendId + friendSource + labelId (discovered from JS bundles)
112
+ const params = new URLSearchParams({
113
+ friendId: String(numericUid),
114
+ friendSource: String(friendSource),
115
+ labelId: String(labelId),
116
+ });
117
+ // Try GET first (the N() wrapper in boss JS uses GET with query params)
118
+ const data = await page.evaluate(`
119
+ async () => {
120
+ return new Promise((resolve, reject) => {
121
+ const xhr = new XMLHttpRequest();
122
+ xhr.open('GET', '${targetUrl}?${params.toString()}', true);
123
+ xhr.withCredentials = true;
124
+ xhr.timeout = 15000;
125
+ xhr.setRequestHeader('Accept', 'application/json');
126
+ xhr.onload = () => { try { resolve(JSON.parse(xhr.responseText)); } catch(e) { reject(new Error('JSON parse failed')); } };
127
+ xhr.onerror = () => reject(new Error('Network Error'));
128
+ xhr.send();
129
+ });
130
+ }
131
+ `);
132
+ if (data.code !== 0) {
133
+ throw new Error(`标签操作失败: ${data.message} (code=${data.code})`);
134
+ }
135
+ const labelName = Object.entries(LABEL_MAP).find(([, v]) => v === labelId)?.[0] || String(labelId);
136
+ return [{
137
+ status: remove ? '✅ 标签已移除' : '✅ 标签已添加',
138
+ detail: `${friendName}: ${remove ? '移除' : '添加'}标签「${labelName}」`,
139
+ }];
140
+ },
141
+ });
@@ -0,0 +1 @@
1
+ export {};