@jackwener/opencli 1.4.1 → 1.5.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 (369) hide show
  1. package/.github/workflows/build-extension.yml +2 -6
  2. package/.github/workflows/ci.yml +21 -1
  3. package/README.md +35 -6
  4. package/README.zh-CN.md +12 -5
  5. package/SKILL.md +2 -0
  6. package/dist/browser/cdp.d.ts +2 -1
  7. package/dist/browser/cdp.js +5 -0
  8. package/dist/browser/discover.d.ts +4 -1
  9. package/dist/browser/discover.js +6 -2
  10. package/dist/browser/errors.d.ts +2 -2
  11. package/dist/browser/errors.js +4 -12
  12. package/dist/browser/mcp.d.ts +2 -1
  13. package/dist/browser/page.d.ts +3 -0
  14. package/dist/browser/page.js +24 -1
  15. package/dist/build-manifest.d.ts +2 -0
  16. package/dist/build-manifest.js +39 -14
  17. package/dist/build-manifest.test.js +21 -0
  18. package/dist/capabilityRouting.d.ts +2 -0
  19. package/dist/capabilityRouting.js +2 -1
  20. package/dist/cli-manifest.json +1567 -108
  21. package/dist/cli.js +68 -6
  22. package/dist/clis/36kr/article.d.ts +1 -0
  23. package/dist/clis/36kr/article.js +62 -0
  24. package/dist/clis/36kr/hot.d.ts +3 -0
  25. package/dist/clis/36kr/hot.js +80 -0
  26. package/dist/clis/36kr/hot.test.d.ts +1 -0
  27. package/dist/clis/36kr/hot.test.js +15 -0
  28. package/dist/clis/36kr/news.d.ts +1 -0
  29. package/dist/clis/36kr/news.js +51 -0
  30. package/dist/clis/36kr/news.test.d.ts +1 -0
  31. package/dist/clis/36kr/news.test.js +85 -0
  32. package/dist/clis/36kr/search.d.ts +1 -0
  33. package/dist/clis/36kr/search.js +72 -0
  34. package/dist/clis/bilibili/comments.d.ts +5 -0
  35. package/dist/clis/bilibili/comments.js +40 -0
  36. package/dist/clis/bilibili/comments.test.d.ts +1 -0
  37. package/dist/clis/bilibili/comments.test.js +82 -0
  38. package/dist/clis/bluesky/feeds.yaml +29 -0
  39. package/dist/clis/bluesky/followers.yaml +33 -0
  40. package/dist/clis/bluesky/following.yaml +33 -0
  41. package/dist/clis/bluesky/profile.yaml +27 -0
  42. package/dist/clis/bluesky/search.yaml +34 -0
  43. package/dist/clis/bluesky/starter-packs.yaml +34 -0
  44. package/dist/clis/bluesky/thread.yaml +32 -0
  45. package/dist/clis/bluesky/trending.yaml +27 -0
  46. package/dist/clis/bluesky/user.yaml +34 -0
  47. package/dist/clis/chatgpt/ask.js +29 -14
  48. package/dist/clis/chatgpt/ax.d.ts +6 -0
  49. package/dist/clis/chatgpt/ax.js +172 -1
  50. package/dist/clis/chatgpt/model.d.ts +1 -0
  51. package/dist/clis/chatgpt/model.js +24 -0
  52. package/dist/clis/chatgpt/send.js +12 -3
  53. package/dist/clis/douban/download.d.ts +1 -0
  54. package/dist/clis/douban/download.js +67 -0
  55. package/dist/clis/douban/download.test.d.ts +1 -0
  56. package/dist/clis/douban/download.test.js +170 -0
  57. package/dist/clis/douban/photos.d.ts +1 -0
  58. package/dist/clis/douban/photos.js +34 -0
  59. package/dist/clis/douban/utils.d.ts +25 -0
  60. package/dist/clis/douban/utils.js +190 -1
  61. package/dist/clis/douban/utils.test.d.ts +1 -0
  62. package/dist/clis/douban/utils.test.js +64 -0
  63. package/dist/clis/imdb/person.d.ts +1 -0
  64. package/dist/clis/imdb/person.js +203 -0
  65. package/dist/clis/imdb/reviews.d.ts +1 -0
  66. package/dist/clis/imdb/reviews.js +88 -0
  67. package/dist/clis/imdb/search.d.ts +1 -0
  68. package/dist/clis/imdb/search.js +161 -0
  69. package/dist/clis/imdb/title.d.ts +1 -0
  70. package/dist/clis/imdb/title.js +93 -0
  71. package/dist/clis/imdb/top.d.ts +1 -0
  72. package/dist/clis/imdb/top.js +53 -0
  73. package/dist/clis/imdb/trending.d.ts +1 -0
  74. package/dist/clis/imdb/trending.js +52 -0
  75. package/dist/clis/imdb/utils.d.ts +46 -0
  76. package/dist/clis/imdb/utils.js +285 -0
  77. package/dist/clis/imdb/utils.test.d.ts +1 -0
  78. package/dist/clis/imdb/utils.test.js +88 -0
  79. package/dist/clis/jd/item.d.ts +4 -0
  80. package/dist/clis/jd/item.js +16 -15
  81. package/dist/clis/jd/item.test.js +16 -1
  82. package/dist/clis/linux-do/categories.yaml +38 -9
  83. package/dist/clis/linux-do/category.d.ts +1 -0
  84. package/dist/clis/linux-do/category.js +36 -0
  85. package/dist/clis/linux-do/feed.d.ts +45 -0
  86. package/dist/clis/linux-do/feed.js +397 -0
  87. package/dist/clis/linux-do/feed.test.d.ts +1 -0
  88. package/dist/clis/linux-do/feed.test.js +118 -0
  89. package/dist/clis/linux-do/hot.d.ts +1 -0
  90. package/dist/clis/linux-do/hot.js +25 -0
  91. package/dist/clis/linux-do/latest.d.ts +1 -0
  92. package/dist/clis/linux-do/latest.js +18 -0
  93. package/dist/clis/linux-do/tags.yaml +41 -0
  94. package/dist/clis/linux-do/topic.yaml +41 -3
  95. package/dist/clis/linux-do/user-posts.yaml +67 -0
  96. package/dist/clis/linux-do/user-topics.yaml +54 -0
  97. package/dist/clis/paperreview/commands.test.d.ts +3 -0
  98. package/dist/clis/paperreview/commands.test.js +243 -0
  99. package/dist/clis/paperreview/feedback.d.ts +1 -0
  100. package/dist/clis/paperreview/feedback.js +52 -0
  101. package/dist/clis/paperreview/review.d.ts +1 -0
  102. package/dist/clis/paperreview/review.js +37 -0
  103. package/dist/clis/paperreview/submit.d.ts +1 -0
  104. package/dist/clis/paperreview/submit.js +85 -0
  105. package/dist/clis/paperreview/utils.d.ts +46 -0
  106. package/dist/clis/paperreview/utils.js +197 -0
  107. package/dist/clis/paperreview/utils.test.d.ts +1 -0
  108. package/dist/clis/paperreview/utils.test.js +49 -0
  109. package/dist/clis/producthunt/browse.d.ts +1 -0
  110. package/dist/clis/producthunt/browse.js +99 -0
  111. package/dist/clis/producthunt/hot.d.ts +1 -0
  112. package/dist/clis/producthunt/hot.js +110 -0
  113. package/dist/clis/producthunt/posts.d.ts +1 -0
  114. package/dist/clis/producthunt/posts.js +28 -0
  115. package/dist/clis/producthunt/today.d.ts +1 -0
  116. package/dist/clis/producthunt/today.js +35 -0
  117. package/dist/clis/producthunt/utils.d.ts +29 -0
  118. package/dist/clis/producthunt/utils.js +99 -0
  119. package/dist/clis/producthunt/utils.test.d.ts +1 -0
  120. package/dist/clis/producthunt/utils.test.js +64 -0
  121. package/dist/clis/twitter/article.js +4 -28
  122. package/dist/clis/twitter/likes.d.ts +24 -0
  123. package/dist/clis/twitter/likes.js +217 -0
  124. package/dist/clis/twitter/likes.test.d.ts +1 -0
  125. package/dist/clis/twitter/likes.test.js +85 -0
  126. package/dist/clis/twitter/profile.js +4 -28
  127. package/dist/clis/twitter/search.js +2 -1
  128. package/dist/clis/twitter/search.test.js +2 -0
  129. package/dist/clis/twitter/shared.d.ts +6 -0
  130. package/dist/clis/twitter/shared.js +35 -0
  131. package/dist/clis/twitter/timeline.js +2 -13
  132. package/dist/clis/twitter/trending.js +29 -61
  133. package/dist/clis/v2ex/hot.yaml +17 -3
  134. package/dist/clis/weixin/download.d.ts +17 -0
  135. package/dist/clis/weixin/download.js +88 -20
  136. package/dist/clis/weread/book.js +2 -2
  137. package/dist/clis/weread/commands.test.d.ts +3 -0
  138. package/dist/clis/weread/commands.test.js +43 -0
  139. package/dist/clis/weread/highlights.js +2 -2
  140. package/dist/clis/weread/notebooks.js +2 -2
  141. package/dist/clis/weread/notes.js +3 -3
  142. package/dist/clis/weread/shelf.js +2 -2
  143. package/dist/clis/weread/utils.d.ts +4 -4
  144. package/dist/clis/weread/utils.js +32 -14
  145. package/dist/clis/weread/utils.test.js +1 -28
  146. package/dist/clis/xiaohongshu/comments.d.ts +5 -0
  147. package/dist/clis/xiaohongshu/comments.js +74 -0
  148. package/dist/clis/xiaohongshu/comments.test.d.ts +1 -0
  149. package/dist/clis/xiaohongshu/comments.test.js +79 -0
  150. package/dist/clis/xiaohongshu/publish.js +179 -47
  151. package/dist/clis/xiaohongshu/publish.test.d.ts +1 -0
  152. package/dist/clis/xiaohongshu/publish.test.js +131 -0
  153. package/dist/clis/xiaohongshu/search.d.ts +8 -1
  154. package/dist/clis/xiaohongshu/search.js +20 -1
  155. package/dist/clis/xiaohongshu/search.test.d.ts +1 -1
  156. package/dist/clis/xiaohongshu/search.test.js +32 -1
  157. package/dist/commanderAdapter.d.ts +1 -0
  158. package/dist/commanderAdapter.js +176 -29
  159. package/dist/commanderAdapter.test.d.ts +1 -0
  160. package/dist/commanderAdapter.test.js +62 -0
  161. package/dist/daemon.js +17 -1
  162. package/dist/discovery.js +48 -42
  163. package/dist/doctor.d.ts +2 -2
  164. package/dist/doctor.js +11 -4
  165. package/dist/download/index.js +63 -51
  166. package/dist/download/index.test.js +17 -4
  167. package/dist/engine.test.js +42 -0
  168. package/dist/errors.d.ts +4 -2
  169. package/dist/errors.js +17 -34
  170. package/dist/execution.d.ts +1 -3
  171. package/dist/execution.js +66 -8
  172. package/dist/execution.test.d.ts +1 -0
  173. package/dist/execution.test.js +40 -0
  174. package/dist/external.js +6 -1
  175. package/dist/hooks.js +2 -0
  176. package/dist/main.js +6 -0
  177. package/dist/output.js +5 -1
  178. package/dist/pipeline/executor.js +3 -4
  179. package/dist/plugin-manifest.d.ts +70 -0
  180. package/dist/plugin-manifest.js +160 -0
  181. package/dist/plugin-manifest.test.d.ts +4 -0
  182. package/dist/plugin-manifest.test.js +179 -0
  183. package/dist/plugin-scaffold.d.ts +28 -0
  184. package/dist/plugin-scaffold.js +142 -0
  185. package/dist/plugin-scaffold.test.d.ts +4 -0
  186. package/dist/plugin-scaffold.test.js +83 -0
  187. package/dist/plugin.d.ts +82 -11
  188. package/dist/plugin.js +870 -84
  189. package/dist/plugin.test.js +1032 -17
  190. package/dist/registry.d.ts +4 -0
  191. package/dist/registry.js +2 -0
  192. package/dist/runtime-detect.d.ts +21 -0
  193. package/dist/runtime-detect.js +32 -0
  194. package/dist/runtime-detect.test.d.ts +1 -0
  195. package/dist/runtime-detect.test.js +27 -0
  196. package/dist/runtime.d.ts +1 -0
  197. package/dist/runtime.js +2 -2
  198. package/dist/serialization.d.ts +2 -0
  199. package/dist/serialization.js +6 -0
  200. package/dist/types.d.ts +3 -0
  201. package/dist/update-check.d.ts +22 -0
  202. package/dist/update-check.js +112 -0
  203. package/dist/weixin-download.test.d.ts +1 -0
  204. package/dist/weixin-download.test.js +30 -0
  205. package/dist/weread-private-api-regression.test.d.ts +1 -0
  206. package/dist/weread-private-api-regression.test.js +122 -0
  207. package/dist/yaml-schema.d.ts +3 -0
  208. package/dist/yaml-schema.js +18 -1
  209. package/docs/.vitepress/config.mts +4 -0
  210. package/docs/adapters/browser/36kr.md +47 -0
  211. package/docs/adapters/browser/bluesky.md +53 -0
  212. package/docs/adapters/browser/douban.md +14 -0
  213. package/docs/adapters/browser/imdb.md +47 -0
  214. package/docs/adapters/browser/jd.md +2 -2
  215. package/docs/adapters/browser/linux-do.md +181 -20
  216. package/docs/adapters/browser/paperreview.md +43 -0
  217. package/docs/adapters/browser/producthunt.md +49 -0
  218. package/docs/adapters/desktop/chatgpt.md +5 -0
  219. package/docs/adapters/index.md +6 -2
  220. package/docs/advanced/download.md +4 -0
  221. package/docs/advanced/rate-limiter-plugin.md +99 -0
  222. package/docs/guide/electron-app-cli.md +200 -0
  223. package/docs/guide/getting-started.md +1 -0
  224. package/docs/guide/plugins.md +97 -0
  225. package/docs/zh/guide/electron-app-cli.md +188 -0
  226. package/docs/zh/guide/getting-started.md +1 -0
  227. package/docs/zh/guide/plugins.md +65 -0
  228. package/extension/package.json +1 -0
  229. package/extension/scripts/package-release.mjs +179 -0
  230. package/extension/src/background.ts +2 -0
  231. package/package.json +4 -1
  232. package/scripts/postinstall.js +10 -0
  233. package/src/browser/cdp.ts +8 -1
  234. package/src/browser/discover.ts +8 -3
  235. package/src/browser/errors.ts +13 -14
  236. package/src/browser/mcp.ts +2 -1
  237. package/src/browser/page.ts +24 -1
  238. package/src/build-manifest.test.ts +23 -0
  239. package/src/build-manifest.ts +40 -15
  240. package/src/capabilityRouting.ts +2 -1
  241. package/src/cli.ts +69 -6
  242. package/src/clis/36kr/article.ts +69 -0
  243. package/src/clis/36kr/hot.test.ts +19 -0
  244. package/src/clis/36kr/hot.ts +100 -0
  245. package/src/clis/36kr/news.test.ts +90 -0
  246. package/src/clis/36kr/news.ts +54 -0
  247. package/src/clis/36kr/search.ts +78 -0
  248. package/src/clis/bilibili/comments.test.ts +102 -0
  249. package/src/clis/bilibili/comments.ts +44 -0
  250. package/src/clis/bluesky/feeds.yaml +29 -0
  251. package/src/clis/bluesky/followers.yaml +33 -0
  252. package/src/clis/bluesky/following.yaml +33 -0
  253. package/src/clis/bluesky/profile.yaml +27 -0
  254. package/src/clis/bluesky/search.yaml +34 -0
  255. package/src/clis/bluesky/starter-packs.yaml +34 -0
  256. package/src/clis/bluesky/thread.yaml +32 -0
  257. package/src/clis/bluesky/trending.yaml +27 -0
  258. package/src/clis/bluesky/user.yaml +34 -0
  259. package/src/clis/chatgpt/ask.ts +28 -14
  260. package/src/clis/chatgpt/ax.ts +180 -1
  261. package/src/clis/chatgpt/model.ts +27 -0
  262. package/src/clis/chatgpt/send.ts +16 -6
  263. package/src/clis/douban/download.test.ts +196 -0
  264. package/src/clis/douban/download.ts +78 -0
  265. package/src/clis/douban/photos.ts +36 -0
  266. package/src/clis/douban/utils.test.ts +97 -0
  267. package/src/clis/douban/utils.ts +232 -1
  268. package/src/clis/imdb/person.ts +232 -0
  269. package/src/clis/imdb/reviews.ts +111 -0
  270. package/src/clis/imdb/search.ts +179 -0
  271. package/src/clis/imdb/title.ts +121 -0
  272. package/src/clis/imdb/top.ts +67 -0
  273. package/src/clis/imdb/trending.ts +66 -0
  274. package/src/clis/imdb/utils.test.ts +117 -0
  275. package/src/clis/imdb/utils.ts +305 -0
  276. package/src/clis/jd/item.test.ts +18 -1
  277. package/src/clis/jd/item.ts +18 -15
  278. package/src/clis/linux-do/categories.yaml +38 -9
  279. package/src/clis/linux-do/category.ts +37 -0
  280. package/src/clis/linux-do/feed.test.ts +132 -0
  281. package/src/clis/linux-do/feed.ts +501 -0
  282. package/src/clis/linux-do/hot.ts +26 -0
  283. package/src/clis/linux-do/latest.ts +19 -0
  284. package/src/clis/linux-do/tags.yaml +41 -0
  285. package/src/clis/linux-do/topic.yaml +41 -3
  286. package/src/clis/linux-do/user-posts.yaml +67 -0
  287. package/src/clis/linux-do/user-topics.yaml +54 -0
  288. package/src/clis/paperreview/commands.test.ts +283 -0
  289. package/src/clis/paperreview/feedback.ts +64 -0
  290. package/src/clis/paperreview/review.ts +47 -0
  291. package/src/clis/paperreview/submit.ts +119 -0
  292. package/src/clis/paperreview/utils.test.ts +68 -0
  293. package/src/clis/paperreview/utils.ts +276 -0
  294. package/src/clis/producthunt/browse.ts +109 -0
  295. package/src/clis/producthunt/hot.ts +127 -0
  296. package/src/clis/producthunt/posts.ts +29 -0
  297. package/src/clis/producthunt/today.ts +37 -0
  298. package/src/clis/producthunt/utils.test.ts +72 -0
  299. package/src/clis/producthunt/utils.ts +122 -0
  300. package/src/clis/twitter/article.ts +5 -28
  301. package/src/clis/twitter/likes.test.ts +91 -0
  302. package/src/clis/twitter/likes.ts +256 -0
  303. package/src/clis/twitter/profile.ts +5 -28
  304. package/src/clis/twitter/search.test.ts +2 -0
  305. package/src/clis/twitter/search.ts +3 -1
  306. package/src/clis/twitter/shared.ts +45 -0
  307. package/src/clis/twitter/timeline.ts +2 -13
  308. package/src/clis/twitter/trending.ts +29 -77
  309. package/src/clis/v2ex/hot.yaml +17 -3
  310. package/src/clis/weixin/download.ts +114 -20
  311. package/src/clis/weread/book.ts +2 -2
  312. package/src/clis/weread/commands.test.ts +57 -0
  313. package/src/clis/weread/highlights.ts +2 -2
  314. package/src/clis/weread/notebooks.ts +2 -2
  315. package/src/clis/weread/notes.ts +3 -3
  316. package/src/clis/weread/shelf.ts +2 -2
  317. package/src/clis/weread/utils.test.ts +1 -32
  318. package/src/clis/weread/utils.ts +41 -16
  319. package/src/clis/xiaohongshu/comments.test.ts +96 -0
  320. package/src/clis/xiaohongshu/comments.ts +81 -0
  321. package/src/clis/xiaohongshu/publish.test.ts +151 -0
  322. package/src/clis/xiaohongshu/publish.ts +206 -54
  323. package/src/clis/xiaohongshu/search.test.ts +39 -1
  324. package/src/clis/xiaohongshu/search.ts +19 -1
  325. package/src/commanderAdapter.test.ts +78 -0
  326. package/src/commanderAdapter.ts +188 -24
  327. package/src/daemon.ts +19 -1
  328. package/src/discovery.ts +49 -48
  329. package/src/doctor.ts +15 -5
  330. package/src/download/index.test.ts +14 -4
  331. package/src/download/index.ts +67 -55
  332. package/src/engine.test.ts +38 -0
  333. package/src/errors.ts +26 -63
  334. package/src/execution.test.ts +47 -0
  335. package/src/execution.ts +67 -9
  336. package/src/external.ts +6 -1
  337. package/src/hooks.ts +1 -0
  338. package/src/main.ts +7 -0
  339. package/src/output.ts +3 -1
  340. package/src/pipeline/executor.ts +4 -6
  341. package/src/plugin-manifest.test.ts +223 -0
  342. package/src/plugin-manifest.ts +206 -0
  343. package/src/plugin-scaffold.test.ts +98 -0
  344. package/src/plugin-scaffold.ts +170 -0
  345. package/src/plugin.test.ts +1104 -17
  346. package/src/plugin.ts +1101 -86
  347. package/src/registry.ts +6 -1
  348. package/src/runtime-detect.test.ts +30 -0
  349. package/src/runtime-detect.ts +36 -0
  350. package/src/runtime.ts +3 -3
  351. package/src/serialization.ts +4 -0
  352. package/src/types.ts +3 -0
  353. package/src/update-check.ts +114 -0
  354. package/src/weixin-download.test.ts +64 -0
  355. package/src/weread-private-api-regression.test.ts +150 -0
  356. package/src/yaml-schema.ts +20 -0
  357. package/tests/e2e/browser-auth.test.ts +13 -9
  358. package/tests/e2e/browser-public-extended.test.ts +1 -1
  359. package/tests/e2e/browser-public.test.ts +62 -4
  360. package/tests/e2e/helpers.ts +2 -1
  361. package/tests/e2e/public-commands.test.ts +37 -3
  362. package/tests/smoke/api-health.test.ts +1 -1
  363. package/vitest.config.ts +10 -0
  364. package/dist/clis/linux-do/category.yaml +0 -51
  365. package/dist/clis/linux-do/hot.yaml +0 -50
  366. package/dist/clis/linux-do/latest.yaml +0 -40
  367. package/src/clis/linux-do/category.yaml +0 -51
  368. package/src/clis/linux-do/hot.yaml +0 -50
  369. package/src/clis/linux-do/latest.yaml +0 -40
@@ -39,6 +39,10 @@ export interface CliCommand {
39
39
  source?: string;
40
40
  footerExtra?: (kwargs: CommandArgs) => string | undefined;
41
41
  requiredEnv?: RequiredEnv[];
42
+ /** Deprecation note shown in help / execution warnings. */
43
+ deprecated?: boolean | string;
44
+ /** Preferred replacement command, if any. */
45
+ replacedBy?: string;
42
46
  /**
43
47
  * Control pre-navigation for cookie/header context before command execution.
44
48
  *
package/dist/registry.js CHANGED
@@ -27,6 +27,8 @@ export function cli(opts) {
27
27
  timeoutSeconds: opts.timeoutSeconds,
28
28
  footerExtra: opts.footerExtra,
29
29
  requiredEnv: opts.requiredEnv,
30
+ deprecated: opts.deprecated,
31
+ replacedBy: opts.replacedBy,
30
32
  navigateBefore: opts.navigateBefore,
31
33
  };
32
34
  const key = fullName(cmd);
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Runtime detection — identify whether opencli is running under Node.js or Bun.
3
+ *
4
+ * Bun injects `globalThis.Bun` at startup, making detection trivial.
5
+ * This module centralises the check so other code can adapt behaviour
6
+ * (e.g. logging, diagnostics) without littering runtime sniffing everywhere.
7
+ */
8
+ export type Runtime = 'bun' | 'node';
9
+ /**
10
+ * Detect the current JavaScript runtime.
11
+ */
12
+ export declare function detectRuntime(): Runtime;
13
+ /**
14
+ * Return a human-readable version string for the current runtime.
15
+ * Examples: "v22.13.0" (Node), "1.1.42" (Bun)
16
+ */
17
+ export declare function getRuntimeVersion(): string;
18
+ /**
19
+ * Return a combined label like "node v22.13.0" or "bun 1.1.42".
20
+ */
21
+ export declare function getRuntimeLabel(): string;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Runtime detection — identify whether opencli is running under Node.js or Bun.
3
+ *
4
+ * Bun injects `globalThis.Bun` at startup, making detection trivial.
5
+ * This module centralises the check so other code can adapt behaviour
6
+ * (e.g. logging, diagnostics) without littering runtime sniffing everywhere.
7
+ */
8
+ /**
9
+ * Detect the current JavaScript runtime.
10
+ */
11
+ export function detectRuntime() {
12
+ // Bun always exposes globalThis.Bun (including Bun.version)
13
+ if (typeof globalThis.Bun !== 'undefined')
14
+ return 'bun';
15
+ return 'node';
16
+ }
17
+ /**
18
+ * Return a human-readable version string for the current runtime.
19
+ * Examples: "v22.13.0" (Node), "1.1.42" (Bun)
20
+ */
21
+ export function getRuntimeVersion() {
22
+ if (detectRuntime() === 'bun') {
23
+ return globalThis.Bun.version;
24
+ }
25
+ return process.version; // e.g. "v22.13.0"
26
+ }
27
+ /**
28
+ * Return a combined label like "node v22.13.0" or "bun 1.1.42".
29
+ */
30
+ export function getRuntimeLabel() {
31
+ return `${detectRuntime()} ${getRuntimeVersion()}`;
32
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { detectRuntime, getRuntimeVersion, getRuntimeLabel } from './runtime-detect.js';
3
+ describe('runtime-detect', () => {
4
+ it('detectRuntime returns a valid runtime string', () => {
5
+ const rt = detectRuntime();
6
+ expect(['bun', 'node']).toContain(rt);
7
+ });
8
+ it('getRuntimeVersion returns a non-empty version string', () => {
9
+ const ver = getRuntimeVersion();
10
+ expect(typeof ver).toBe('string');
11
+ expect(ver.length).toBeGreaterThan(0);
12
+ });
13
+ it('getRuntimeLabel returns "<runtime> <version>" format', () => {
14
+ const label = getRuntimeLabel();
15
+ expect(label).toMatch(/^(bun|node) .+$/);
16
+ });
17
+ it('detects the current environment correctly', () => {
18
+ const isBun = typeof globalThis.Bun !== 'undefined';
19
+ const rt = detectRuntime();
20
+ if (isBun) {
21
+ expect(rt).toBe('bun');
22
+ }
23
+ else {
24
+ expect(rt).toBe('node');
25
+ }
26
+ });
27
+ });
package/dist/runtime.d.ts CHANGED
@@ -13,6 +13,7 @@ export declare const DEFAULT_BROWSER_EXPLORE_TIMEOUT: number;
13
13
  export declare function runWithTimeout<T>(promise: Promise<T>, opts: {
14
14
  timeout: number;
15
15
  label?: string;
16
+ hint?: string;
16
17
  }): Promise<T>;
17
18
  /**
18
19
  * Timeout with milliseconds unit. Used for low-level internal timeouts.
package/dist/runtime.js CHANGED
@@ -5,7 +5,7 @@ import { TimeoutError } from './errors.js';
5
5
  * Uses CDPBridge when OPENCLI_CDP_ENDPOINT is set, otherwise BrowserBridge.
6
6
  */
7
7
  export function getBrowserFactory() {
8
- return (process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge);
8
+ return process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge;
9
9
  }
10
10
  function parseEnvTimeout(envVar, fallback) {
11
11
  const raw = process.env[envVar];
@@ -26,7 +26,7 @@ export const DEFAULT_BROWSER_EXPLORE_TIMEOUT = parseEnvTimeout('OPENCLI_BROWSER_
26
26
  */
27
27
  export async function runWithTimeout(promise, opts) {
28
28
  const label = opts.label ?? 'Operation';
29
- return withTimeoutMs(promise, opts.timeout * 1000, () => new TimeoutError(label, opts.timeout));
29
+ return withTimeoutMs(promise, opts.timeout * 1000, () => new TimeoutError(label, opts.timeout, opts.hint));
30
30
  }
31
31
  /**
32
32
  * Timeout with milliseconds unit. Used for low-level internal timeouts.
@@ -27,6 +27,8 @@ export declare function serializeCommand(cmd: CliCommand): {
27
27
  args: SerializedArg[];
28
28
  columns: string[];
29
29
  domain: string | null;
30
+ deprecated: string | boolean | null;
31
+ replacedBy: string | null;
30
32
  };
31
33
  /** Human-readable arg summary: `<required> [optional]` style. */
32
34
  export declare function formatArgSummary(args: Arg[]): string;
@@ -29,6 +29,8 @@ export function serializeCommand(cmd) {
29
29
  args: cmd.args.map(serializeArg),
30
30
  columns: cmd.columns ?? [],
31
31
  domain: cmd.domain ?? null,
32
+ deprecated: cmd.deprecated ?? null,
33
+ replacedBy: cmd.replacedBy ?? null,
32
34
  };
33
35
  }
34
36
  // ── Formatting ──────────────────────────────────────────────────────────────
@@ -56,6 +58,10 @@ export function formatRegistryHelpText(cmd) {
56
58
  meta.push(`Browser: ${cmd.browser ? 'yes' : 'no'}`);
57
59
  if (cmd.domain)
58
60
  meta.push(`Domain: ${cmd.domain}`);
61
+ if (cmd.deprecated)
62
+ meta.push(`Deprecated: ${typeof cmd.deprecated === 'string' ? cmd.deprecated : 'yes'}`);
63
+ if (cmd.replacedBy)
64
+ meta.push(`Use instead: ${cmd.replacedBy}`);
59
65
  lines.push(meta.join(' | '));
60
66
  if (cmd.columns?.length)
61
67
  lines.push(`Output columns: ${cmd.columns.join(', ')}`);
package/dist/types.d.ts CHANGED
@@ -68,4 +68,7 @@ export interface IPage {
68
68
  installInterceptor(pattern: string): Promise<void>;
69
69
  getInterceptedRequests(): Promise<any[]>;
70
70
  screenshot(options?: ScreenshotOptions): Promise<string>;
71
+ closeWindow?(): Promise<void>;
72
+ /** Returns the current page URL, or null if unavailable. */
73
+ getCurrentUrl?(): Promise<string | null>;
71
74
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Non-blocking update checker.
3
+ *
4
+ * Pattern: register exit-hook + kick-off-background-fetch
5
+ * - On startup: kick off background fetch (non-blocking)
6
+ * - On process exit: read cache, print notice if newer version exists
7
+ * - Check interval: 24 hours
8
+ * - Notice appears AFTER command output, not before (same as npm/gh/yarn)
9
+ * - Never delays or blocks the CLI command
10
+ */
11
+ /**
12
+ * Register a process exit hook that prints an update notice if a newer
13
+ * version was found on the last background check.
14
+ * Notice appears after command output — same pattern as npm/gh/yarn.
15
+ * Skipped during --get-completions to avoid polluting shell completion output.
16
+ */
17
+ export declare function registerUpdateNoticeOnExit(): void;
18
+ /**
19
+ * Kick off a background fetch to npm registry. Writes to cache for next run.
20
+ * Fully non-blocking — never awaited.
21
+ */
22
+ export declare function checkForUpdateBackground(): void;
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Non-blocking update checker.
3
+ *
4
+ * Pattern: register exit-hook + kick-off-background-fetch
5
+ * - On startup: kick off background fetch (non-blocking)
6
+ * - On process exit: read cache, print notice if newer version exists
7
+ * - Check interval: 24 hours
8
+ * - Notice appears AFTER command output, not before (same as npm/gh/yarn)
9
+ * - Never delays or blocks the CLI command
10
+ */
11
+ import * as fs from 'node:fs';
12
+ import * as path from 'node:path';
13
+ import * as os from 'node:os';
14
+ import chalk from 'chalk';
15
+ import { PKG_VERSION } from './version.js';
16
+ const CACHE_DIR = path.join(os.homedir(), '.opencli');
17
+ const CACHE_FILE = path.join(CACHE_DIR, 'update-check.json');
18
+ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h
19
+ const NPM_REGISTRY_URL = 'https://registry.npmjs.org/@jackwener/opencli/latest';
20
+ // Read cache once at module load — shared by both exported functions
21
+ const _cache = (() => {
22
+ try {
23
+ return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ })();
29
+ function writeCache(latestVersion) {
30
+ try {
31
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
32
+ fs.writeFileSync(CACHE_FILE, JSON.stringify({ lastCheck: Date.now(), latestVersion }), 'utf-8');
33
+ }
34
+ catch {
35
+ // Best-effort; never fail
36
+ }
37
+ }
38
+ /** Compare semver strings. Returns true if `a` is strictly newer than `b`. */
39
+ function isNewer(a, b) {
40
+ const parse = (v) => v.replace(/^v/, '').split('-')[0].split('.').map(Number);
41
+ const pa = parse(a);
42
+ const pb = parse(b);
43
+ if (pa.some(isNaN) || pb.some(isNaN))
44
+ return false;
45
+ const [aMaj, aMin, aPat] = pa;
46
+ const [bMaj, bMin, bPat] = pb;
47
+ if (aMaj !== bMaj)
48
+ return aMaj > bMaj;
49
+ if (aMin !== bMin)
50
+ return aMin > bMin;
51
+ return aPat > bPat;
52
+ }
53
+ function isCI() {
54
+ return !!(process.env.CI || process.env.CONTINUOUS_INTEGRATION);
55
+ }
56
+ /**
57
+ * Register a process exit hook that prints an update notice if a newer
58
+ * version was found on the last background check.
59
+ * Notice appears after command output — same pattern as npm/gh/yarn.
60
+ * Skipped during --get-completions to avoid polluting shell completion output.
61
+ */
62
+ export function registerUpdateNoticeOnExit() {
63
+ if (isCI())
64
+ return;
65
+ if (process.argv.includes('--get-completions'))
66
+ return;
67
+ process.on('exit', (code) => {
68
+ if (code !== 0)
69
+ return; // Don't show update notice on error exit
70
+ if (!_cache)
71
+ return;
72
+ if (!isNewer(_cache.latestVersion, PKG_VERSION))
73
+ return;
74
+ try {
75
+ process.stderr.write(chalk.yellow(`\n Update available: v${PKG_VERSION} → v${_cache.latestVersion}\n`) +
76
+ chalk.dim(` Run: npm install -g @jackwener/opencli\n\n`));
77
+ }
78
+ catch {
79
+ // Ignore broken pipe (stderr closed before process exits)
80
+ }
81
+ });
82
+ }
83
+ /**
84
+ * Kick off a background fetch to npm registry. Writes to cache for next run.
85
+ * Fully non-blocking — never awaited.
86
+ */
87
+ export function checkForUpdateBackground() {
88
+ if (isCI())
89
+ return;
90
+ if (_cache && Date.now() - _cache.lastCheck < CHECK_INTERVAL_MS)
91
+ return;
92
+ void (async () => {
93
+ try {
94
+ const controller = new AbortController();
95
+ const timer = setTimeout(() => controller.abort(), 3000);
96
+ const res = await fetch(NPM_REGISTRY_URL, {
97
+ signal: controller.signal,
98
+ headers: { 'User-Agent': `opencli/${PKG_VERSION}` },
99
+ });
100
+ clearTimeout(timer);
101
+ if (!res.ok)
102
+ return;
103
+ const data = await res.json();
104
+ if (typeof data.version === 'string') {
105
+ writeCache(data.version);
106
+ }
107
+ }
108
+ catch {
109
+ // Network error: silently skip, try again next run
110
+ }
111
+ })();
112
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ async function loadModule() {
3
+ return import('./clis/weixin/download.js');
4
+ }
5
+ describe('weixin publish time extraction', () => {
6
+ it('prefers publish_time text over create_time-like date strings', async () => {
7
+ const mod = await loadModule();
8
+ expect(mod.extractWechatPublishTime('2026年3月24日 22:38', 'var create_time = "2026年3月24日 22:38";')).toBe('2026年3月24日 22:38');
9
+ });
10
+ it('falls back to unix timestamp create_time values', async () => {
11
+ const mod = await loadModule();
12
+ expect(mod.extractWechatPublishTime('', 'var create_time = "1711291080";')).toBe('2024-03-24 22:38:00');
13
+ });
14
+ it('rejects malformed create_time values', async () => {
15
+ const mod = await loadModule();
16
+ expect(mod.extractWechatPublishTime('', 'var create_time = "2026年3月24日 22:38";')).toBe('');
17
+ expect(mod.extractWechatPublishTime('', 'var create_time = "1711291080abc";')).toBe('');
18
+ expect(mod.extractWechatPublishTime('', 'var create_time = "17112910800";')).toBe('');
19
+ });
20
+ it('builds a self-contained browser helper that matches fallback behavior', async () => {
21
+ const mod = await loadModule();
22
+ const extractInPage = eval(mod.buildExtractWechatPublishTimeJs());
23
+ expect(extractInPage('', 'var create_time = "1711291080";')).toBe('2024-03-24 22:38:00');
24
+ });
25
+ it('browser helper still prefers DOM publish_time text', async () => {
26
+ const mod = await loadModule();
27
+ const extractInPage = eval(mod.buildExtractWechatPublishTimeJs());
28
+ expect(extractInPage('2026年3月24日 22:38', 'var create_time = "1711291080";')).toBe('2026年3月24日 22:38');
29
+ });
30
+ });
@@ -0,0 +1 @@
1
+ import './clis/weread/shelf.js';
@@ -0,0 +1,122 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+ import { getRegistry } from './registry.js';
3
+ import { fetchPrivateApi } from './clis/weread/utils.js';
4
+ import './clis/weread/shelf.js';
5
+ describe('weread private API regression', () => {
6
+ beforeEach(() => {
7
+ vi.restoreAllMocks();
8
+ });
9
+ it('uses browser cookies and Node fetch for private API requests', async () => {
10
+ const mockPage = {
11
+ getCookies: vi.fn()
12
+ .mockResolvedValueOnce([
13
+ { name: 'wr_name', value: 'alice', domain: 'weread.qq.com' },
14
+ { name: 'wr_vid', value: 'vid123', domain: 'i.weread.qq.com' },
15
+ ]),
16
+ evaluate: vi.fn(),
17
+ };
18
+ const fetchMock = vi.fn().mockResolvedValue({
19
+ ok: true,
20
+ status: 200,
21
+ json: () => Promise.resolve({ title: 'Test Book', errcode: 0 }),
22
+ });
23
+ vi.stubGlobal('fetch', fetchMock);
24
+ const result = await fetchPrivateApi(mockPage, '/book/info', { bookId: '123' });
25
+ expect(result.title).toBe('Test Book');
26
+ expect(mockPage.getCookies).toHaveBeenCalledTimes(1);
27
+ expect(mockPage.getCookies).toHaveBeenCalledWith({ url: 'https://i.weread.qq.com/book/info?bookId=123' });
28
+ expect(mockPage.evaluate).not.toHaveBeenCalled();
29
+ expect(fetchMock).toHaveBeenCalledWith('https://i.weread.qq.com/book/info?bookId=123', expect.objectContaining({
30
+ headers: expect.objectContaining({
31
+ Cookie: 'wr_name=alice; wr_vid=vid123',
32
+ }),
33
+ }));
34
+ });
35
+ it('maps unauthenticated private API responses to AUTH_REQUIRED', async () => {
36
+ const mockPage = {
37
+ getCookies: vi.fn().mockResolvedValue([]),
38
+ evaluate: vi.fn(),
39
+ };
40
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
41
+ ok: false,
42
+ status: 401,
43
+ json: () => Promise.resolve({ errcode: -2010, errmsg: '用户不存在' }),
44
+ }));
45
+ await expect(fetchPrivateApi(mockPage, '/book/info')).rejects.toThrow('Not logged in');
46
+ });
47
+ it('maps non-auth API errors to API_ERROR', async () => {
48
+ const mockPage = {
49
+ getCookies: vi.fn().mockResolvedValue([]),
50
+ evaluate: vi.fn(),
51
+ };
52
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
53
+ ok: true,
54
+ status: 200,
55
+ json: () => Promise.resolve({ errcode: -1, errmsg: 'unknown error' }),
56
+ }));
57
+ await expect(fetchPrivateApi(mockPage, '/book/info')).rejects.toThrow('unknown error');
58
+ });
59
+ it('maps non-401 HTTP failures to FETCH_ERROR', async () => {
60
+ const mockPage = {
61
+ getCookies: vi.fn().mockResolvedValue([]),
62
+ evaluate: vi.fn(),
63
+ };
64
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
65
+ ok: false,
66
+ status: 403,
67
+ json: () => Promise.resolve({ errmsg: 'forbidden' }),
68
+ }));
69
+ await expect(fetchPrivateApi(mockPage, '/book/info')).rejects.toThrow('HTTP 403');
70
+ });
71
+ it('maps invalid JSON to PARSE_ERROR', async () => {
72
+ const mockPage = {
73
+ getCookies: vi.fn().mockResolvedValue([]),
74
+ evaluate: vi.fn(),
75
+ };
76
+ vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
77
+ ok: true,
78
+ status: 200,
79
+ json: () => Promise.reject(new SyntaxError('Unexpected token <')),
80
+ }));
81
+ await expect(fetchPrivateApi(mockPage, '/book/info')).rejects.toThrow('Invalid JSON');
82
+ });
83
+ it('routes weread shelf through the private API helper path', async () => {
84
+ const command = getRegistry().get('weread/shelf');
85
+ expect(command?.func).toBeTypeOf('function');
86
+ const mockPage = {
87
+ getCookies: vi.fn()
88
+ .mockResolvedValueOnce([
89
+ { name: 'wr_name', value: 'alice', domain: 'weread.qq.com' },
90
+ { name: 'wr_vid', value: 'vid123', domain: 'i.weread.qq.com' },
91
+ ]),
92
+ evaluate: vi.fn(),
93
+ };
94
+ const fetchMock = vi.fn().mockResolvedValue({
95
+ ok: true,
96
+ status: 200,
97
+ json: () => Promise.resolve({
98
+ books: [{
99
+ title: 'Deep Work',
100
+ author: 'Cal Newport',
101
+ readingProgress: 42,
102
+ bookId: 'abc123',
103
+ }],
104
+ }),
105
+ });
106
+ vi.stubGlobal('fetch', fetchMock);
107
+ const result = await command.func(mockPage, { limit: 1 });
108
+ expect(mockPage.evaluate).not.toHaveBeenCalled();
109
+ expect(fetchMock).toHaveBeenCalledWith('https://i.weread.qq.com/shelf/sync?synckey=0&lectureSynckey=0', expect.any(Object));
110
+ expect(mockPage.getCookies).toHaveBeenCalledWith({
111
+ url: 'https://i.weread.qq.com/shelf/sync?synckey=0&lectureSynckey=0',
112
+ });
113
+ expect(result).toEqual([
114
+ {
115
+ title: 'Deep Work',
116
+ author: 'Cal Newport',
117
+ progress: '42%',
118
+ bookId: 'abc123',
119
+ },
120
+ ]);
121
+ });
122
+ });
@@ -24,3 +24,6 @@ export interface YamlCliDefinition {
24
24
  timeout?: number;
25
25
  navigateBefore?: boolean | string;
26
26
  }
27
+ import type { Arg } from './registry.js';
28
+ /** Convert YAML args definition to the internal Arg[] format. */
29
+ export declare function parseYamlArgs(args: Record<string, YamlArgDefinition> | undefined): Arg[];
@@ -2,4 +2,21 @@
2
2
  * Shared YAML CLI definition types.
3
3
  * Used by both discovery.ts (runtime) and build-manifest.ts (build-time).
4
4
  */
5
- export {};
5
+ /** Convert YAML args definition to the internal Arg[] format. */
6
+ export function parseYamlArgs(args) {
7
+ if (!args || typeof args !== 'object')
8
+ return [];
9
+ const result = [];
10
+ for (const [argName, argDef] of Object.entries(args)) {
11
+ result.push({
12
+ name: argName,
13
+ type: argDef?.type ?? 'str',
14
+ default: argDef?.default,
15
+ required: argDef?.required ?? false,
16
+ positional: argDef?.positional ?? false,
17
+ help: argDef?.description ?? argDef?.help ?? '',
18
+ choices: argDef?.choices,
19
+ });
20
+ }
21
+ return result;
22
+ }
@@ -32,6 +32,7 @@ export default defineConfig({
32
32
  { text: 'Comparison', link: '/comparison' },
33
33
  { text: 'Browser Bridge', link: '/guide/browser-bridge' },
34
34
  { text: 'Troubleshooting', link: '/guide/troubleshooting' },
35
+ { text: 'Add an Electron App CLI', link: '/guide/electron-app-cli' },
35
36
  { text: 'Plugins', link: '/guide/plugins' },
36
37
  ],
37
38
  },
@@ -78,6 +79,7 @@ export default defineConfig({
78
79
  { text: 'Doubao', link: '/adapters/browser/doubao' },
79
80
  { text: 'Facebook', link: '/adapters/browser/facebook' },
80
81
  { text: 'Google', link: '/adapters/browser/google' },
82
+ { text: 'IMDb', link: '/adapters/browser/imdb' },
81
83
  { text: 'Instagram', link: '/adapters/browser/instagram' },
82
84
  { text: 'JD.com', link: '/adapters/browser/jd' },
83
85
  { text: 'Medium', link: '/adapters/browser/medium' },
@@ -98,6 +100,7 @@ export default defineConfig({
98
100
  { text: 'Xiaoyuzhou', link: '/adapters/browser/xiaoyuzhou' },
99
101
  { text: 'Yahoo Finance', link: '/adapters/browser/yahoo-finance' },
100
102
  { text: 'arXiv', link: '/adapters/browser/arxiv' },
103
+ { text: 'paperreview.ai', link: '/adapters/browser/paperreview' },
101
104
  { text: 'Barchart', link: '/adapters/browser/barchart' },
102
105
  { text: 'Hugging Face', link: '/adapters/browser/hf' },
103
106
  { text: 'Sina Finance', link: '/adapters/browser/sinafinance' },
@@ -168,6 +171,7 @@ export default defineConfig({
168
171
  { text: '快速开始', link: '/zh/guide/getting-started' },
169
172
  { text: '安装', link: '/zh/guide/installation' },
170
173
  { text: 'Browser Bridge', link: '/zh/guide/browser-bridge' },
174
+ { text: '给新 Electron 应用生成 CLI', link: '/zh/guide/electron-app-cli' },
171
175
  { text: '插件', link: '/zh/guide/plugins' },
172
176
  ],
173
177
  },
@@ -0,0 +1,47 @@
1
+ # 36kr (36氪)
2
+
3
+ **Mode**: 🌐 Public / 🔐 Browser · **Domain**: `36kr.com`
4
+
5
+ ## Commands
6
+
7
+ | Command | Description |
8
+ |---------|-------------|
9
+ | `opencli 36kr hot` | 36氪热榜 — trending articles |
10
+ | `opencli 36kr news` | Latest tech/startup news from 36kr |
11
+ | `opencli 36kr search <query>` | Search 36kr articles |
12
+ | `opencli 36kr article <id-or-url>` | Read full article content |
13
+
14
+ ## Usage Examples
15
+
16
+ ```bash
17
+ # Trending articles
18
+ opencli 36kr hot --limit 10
19
+
20
+ # Hot by type
21
+ opencli 36kr hot --type renqi --limit 10
22
+ opencli 36kr hot --type zonghe --limit 10
23
+
24
+ # Latest news
25
+ opencli 36kr news --limit 20
26
+
27
+ # Search articles
28
+ opencli 36kr search "AI" --limit 10
29
+ opencli 36kr search "OpenAI" --limit 5
30
+
31
+ # Read full article (by ID or URL)
32
+ opencli 36kr article 3000000123456
33
+ opencli 36kr article https://36kr.com/p/3000000123456
34
+
35
+ # JSON output
36
+ opencli 36kr hot -f json
37
+ ```
38
+
39
+ ## Notes
40
+
41
+ - `news` uses the public RSS feed and works without Browser Bridge.
42
+ - `hot`, `search`, and `article` use Browser Bridge and are best run with Chrome open.
43
+ - `hot --type` accepts `catalog`, `renqi`, `zonghe`, and `shoucang`.
44
+
45
+ ## Prerequisites
46
+
47
+ - No browser required — uses public API
@@ -0,0 +1,53 @@
1
+ # Bluesky
2
+
3
+ **Mode**: 🌐 Public · **Domain**: `bsky.app`
4
+
5
+ ## Commands
6
+
7
+ | Command | Description |
8
+ |---------|-------------|
9
+ | `opencli bluesky profile` | User profile info |
10
+ | `opencli bluesky user` | Recent posts from a user |
11
+ | `opencli bluesky trending` | Trending topics |
12
+ | `opencli bluesky search` | Search users |
13
+ | `opencli bluesky feeds` | Popular feed generators |
14
+ | `opencli bluesky followers` | User's followers |
15
+ | `opencli bluesky following` | Accounts a user follows |
16
+ | `opencli bluesky thread` | Post thread with replies |
17
+ | `opencli bluesky starter-packs` | User's starter packs |
18
+
19
+ ## Usage Examples
20
+
21
+ ```bash
22
+ # User profile
23
+ opencli bluesky profile --handle bsky.app
24
+
25
+ # Recent posts
26
+ opencli bluesky user --handle bsky.app --limit 10
27
+
28
+ # Trending topics
29
+ opencli bluesky trending --limit 10
30
+
31
+ # Search users
32
+ opencli bluesky search --query "AI" --limit 10
33
+
34
+ # Popular feeds
35
+ opencli bluesky feeds --limit 10
36
+
37
+ # Followers / following
38
+ opencli bluesky followers --handle bsky.app --limit 10
39
+ opencli bluesky following --handle bsky.app
40
+
41
+ # Post thread with replies
42
+ opencli bluesky thread --uri "at://did:.../app.bsky.feed.post/..."
43
+
44
+ # Starter packs
45
+ opencli bluesky starter-packs --handle bsky.app
46
+
47
+ # JSON output
48
+ opencli bluesky profile --handle bsky.app -f json
49
+ ```
50
+
51
+ ## Prerequisites
52
+
53
+ None — all commands use the public Bluesky AT Protocol API, no browser or login required.
@@ -9,6 +9,8 @@
9
9
  | `opencli douban search` | 搜索豆瓣电影、图书或音乐 |
10
10
  | `opencli douban top250` | 豆瓣电影 Top 250 |
11
11
  | `opencli douban subject` | 条目详情 |
12
+ | `opencli douban photos` | 获取电影海报/剧照图片列表 |
13
+ | `opencli douban download` | 下载电影海报/剧照图片 |
12
14
  | `opencli douban marks` | 我的标记 |
13
15
  | `opencli douban reviews` | 我的短评 |
14
16
  | `opencli douban movie-hot` | 豆瓣电影热门榜单 |
@@ -32,6 +34,18 @@ opencli douban top250 --limit 10
32
34
  # 条目详情
33
35
  opencli douban subject 1292052
34
36
 
37
+ # 获取海报直链(默认 type=Rb)
38
+ opencli douban photos 30382501 --limit 20
39
+
40
+ # 下载海报到本地目录
41
+ opencli douban download 30382501 --output ./douban
42
+
43
+ # 只下载指定 photo_id 的一张图
44
+ opencli douban download 30382501 --photo-id 2913621075 --output ./douban
45
+
46
+ # 返回 JSON,便于上层界面直接渲染图片并右键取图
47
+ opencli douban photos 30382501 -f json
48
+
35
49
  # 电影热门
36
50
  opencli douban movie-hot --limit 10
37
51