@jackwener/opencli 1.4.1 → 1.5.0

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 (322) 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/discover.d.ts +4 -1
  8. package/dist/browser/discover.js +6 -2
  9. package/dist/browser/errors.d.ts +2 -2
  10. package/dist/browser/errors.js +4 -12
  11. package/dist/browser/mcp.d.ts +2 -1
  12. package/dist/build-manifest.d.ts +2 -0
  13. package/dist/build-manifest.js +39 -14
  14. package/dist/build-manifest.test.js +21 -0
  15. package/dist/capabilityRouting.d.ts +2 -0
  16. package/dist/capabilityRouting.js +2 -1
  17. package/dist/cli-manifest.json +1111 -112
  18. package/dist/cli.js +34 -3
  19. package/dist/clis/36kr/article.d.ts +1 -0
  20. package/dist/clis/36kr/article.js +62 -0
  21. package/dist/clis/36kr/hot.d.ts +3 -0
  22. package/dist/clis/36kr/hot.js +80 -0
  23. package/dist/clis/36kr/hot.test.d.ts +1 -0
  24. package/dist/clis/36kr/hot.test.js +15 -0
  25. package/dist/clis/36kr/news.d.ts +1 -0
  26. package/dist/clis/36kr/news.js +51 -0
  27. package/dist/clis/36kr/news.test.d.ts +1 -0
  28. package/dist/clis/36kr/news.test.js +85 -0
  29. package/dist/clis/36kr/search.d.ts +1 -0
  30. package/dist/clis/36kr/search.js +72 -0
  31. package/dist/clis/bilibili/comments.d.ts +5 -0
  32. package/dist/clis/bilibili/comments.js +40 -0
  33. package/dist/clis/bilibili/comments.test.d.ts +1 -0
  34. package/dist/clis/bilibili/comments.test.js +82 -0
  35. package/dist/clis/chatgpt/ask.js +29 -14
  36. package/dist/clis/chatgpt/ax.d.ts +6 -0
  37. package/dist/clis/chatgpt/ax.js +172 -1
  38. package/dist/clis/chatgpt/model.d.ts +1 -0
  39. package/dist/clis/chatgpt/model.js +24 -0
  40. package/dist/clis/chatgpt/send.js +12 -3
  41. package/dist/clis/douban/download.d.ts +1 -0
  42. package/dist/clis/douban/download.js +67 -0
  43. package/dist/clis/douban/download.test.d.ts +1 -0
  44. package/dist/clis/douban/download.test.js +170 -0
  45. package/dist/clis/douban/photos.d.ts +1 -0
  46. package/dist/clis/douban/photos.js +34 -0
  47. package/dist/clis/douban/utils.d.ts +25 -0
  48. package/dist/clis/douban/utils.js +190 -1
  49. package/dist/clis/douban/utils.test.d.ts +1 -0
  50. package/dist/clis/douban/utils.test.js +64 -0
  51. package/dist/clis/imdb/person.d.ts +1 -0
  52. package/dist/clis/imdb/person.js +203 -0
  53. package/dist/clis/imdb/reviews.d.ts +1 -0
  54. package/dist/clis/imdb/reviews.js +88 -0
  55. package/dist/clis/imdb/search.d.ts +1 -0
  56. package/dist/clis/imdb/search.js +161 -0
  57. package/dist/clis/imdb/title.d.ts +1 -0
  58. package/dist/clis/imdb/title.js +93 -0
  59. package/dist/clis/imdb/top.d.ts +1 -0
  60. package/dist/clis/imdb/top.js +53 -0
  61. package/dist/clis/imdb/trending.d.ts +1 -0
  62. package/dist/clis/imdb/trending.js +52 -0
  63. package/dist/clis/imdb/utils.d.ts +46 -0
  64. package/dist/clis/imdb/utils.js +285 -0
  65. package/dist/clis/imdb/utils.test.d.ts +1 -0
  66. package/dist/clis/imdb/utils.test.js +88 -0
  67. package/dist/clis/jd/item.d.ts +4 -0
  68. package/dist/clis/jd/item.js +16 -15
  69. package/dist/clis/jd/item.test.js +16 -1
  70. package/dist/clis/linux-do/categories.yaml +38 -9
  71. package/dist/clis/linux-do/category.d.ts +1 -0
  72. package/dist/clis/linux-do/category.js +36 -0
  73. package/dist/clis/linux-do/feed.d.ts +45 -0
  74. package/dist/clis/linux-do/feed.js +397 -0
  75. package/dist/clis/linux-do/feed.test.d.ts +1 -0
  76. package/dist/clis/linux-do/feed.test.js +118 -0
  77. package/dist/clis/linux-do/hot.d.ts +1 -0
  78. package/dist/clis/linux-do/hot.js +25 -0
  79. package/dist/clis/linux-do/latest.d.ts +1 -0
  80. package/dist/clis/linux-do/latest.js +18 -0
  81. package/dist/clis/linux-do/tags.yaml +41 -0
  82. package/dist/clis/linux-do/topic.yaml +41 -3
  83. package/dist/clis/linux-do/user-posts.yaml +67 -0
  84. package/dist/clis/linux-do/user-topics.yaml +54 -0
  85. package/dist/clis/paperreview/commands.test.d.ts +3 -0
  86. package/dist/clis/paperreview/commands.test.js +243 -0
  87. package/dist/clis/paperreview/feedback.d.ts +1 -0
  88. package/dist/clis/paperreview/feedback.js +52 -0
  89. package/dist/clis/paperreview/review.d.ts +1 -0
  90. package/dist/clis/paperreview/review.js +37 -0
  91. package/dist/clis/paperreview/submit.d.ts +1 -0
  92. package/dist/clis/paperreview/submit.js +85 -0
  93. package/dist/clis/paperreview/utils.d.ts +46 -0
  94. package/dist/clis/paperreview/utils.js +197 -0
  95. package/dist/clis/paperreview/utils.test.d.ts +1 -0
  96. package/dist/clis/paperreview/utils.test.js +49 -0
  97. package/dist/clis/producthunt/browse.d.ts +1 -0
  98. package/dist/clis/producthunt/browse.js +99 -0
  99. package/dist/clis/producthunt/hot.d.ts +1 -0
  100. package/dist/clis/producthunt/hot.js +110 -0
  101. package/dist/clis/producthunt/posts.d.ts +1 -0
  102. package/dist/clis/producthunt/posts.js +28 -0
  103. package/dist/clis/producthunt/today.d.ts +1 -0
  104. package/dist/clis/producthunt/today.js +35 -0
  105. package/dist/clis/producthunt/utils.d.ts +29 -0
  106. package/dist/clis/producthunt/utils.js +99 -0
  107. package/dist/clis/producthunt/utils.test.d.ts +1 -0
  108. package/dist/clis/producthunt/utils.test.js +64 -0
  109. package/dist/clis/twitter/article.js +4 -28
  110. package/dist/clis/twitter/likes.d.ts +24 -0
  111. package/dist/clis/twitter/likes.js +217 -0
  112. package/dist/clis/twitter/likes.test.d.ts +1 -0
  113. package/dist/clis/twitter/likes.test.js +85 -0
  114. package/dist/clis/twitter/profile.js +4 -28
  115. package/dist/clis/twitter/search.js +2 -1
  116. package/dist/clis/twitter/search.test.js +2 -0
  117. package/dist/clis/twitter/shared.d.ts +6 -0
  118. package/dist/clis/twitter/shared.js +35 -0
  119. package/dist/clis/twitter/timeline.js +2 -13
  120. package/dist/clis/weixin/download.d.ts +17 -0
  121. package/dist/clis/weixin/download.js +88 -20
  122. package/dist/clis/weread/book.js +2 -2
  123. package/dist/clis/weread/commands.test.d.ts +3 -0
  124. package/dist/clis/weread/commands.test.js +43 -0
  125. package/dist/clis/weread/highlights.js +2 -2
  126. package/dist/clis/weread/notebooks.js +2 -2
  127. package/dist/clis/weread/notes.js +3 -3
  128. package/dist/clis/weread/shelf.js +2 -2
  129. package/dist/clis/weread/utils.d.ts +4 -4
  130. package/dist/clis/weread/utils.js +32 -14
  131. package/dist/clis/weread/utils.test.js +1 -28
  132. package/dist/clis/xiaohongshu/comments.d.ts +5 -0
  133. package/dist/clis/xiaohongshu/comments.js +74 -0
  134. package/dist/clis/xiaohongshu/comments.test.d.ts +1 -0
  135. package/dist/clis/xiaohongshu/comments.test.js +79 -0
  136. package/dist/clis/xiaohongshu/publish.js +114 -18
  137. package/dist/clis/xiaohongshu/publish.test.d.ts +1 -0
  138. package/dist/clis/xiaohongshu/publish.test.js +119 -0
  139. package/dist/commanderAdapter.d.ts +1 -0
  140. package/dist/commanderAdapter.js +176 -29
  141. package/dist/commanderAdapter.test.d.ts +1 -0
  142. package/dist/commanderAdapter.test.js +62 -0
  143. package/dist/daemon.js +17 -1
  144. package/dist/discovery.js +8 -14
  145. package/dist/doctor.d.ts +1 -0
  146. package/dist/doctor.js +9 -2
  147. package/dist/download/index.js +63 -51
  148. package/dist/download/index.test.js +17 -4
  149. package/dist/errors.d.ts +3 -1
  150. package/dist/errors.js +15 -32
  151. package/dist/execution.d.ts +1 -3
  152. package/dist/execution.js +21 -1
  153. package/dist/hooks.js +2 -0
  154. package/dist/main.js +5 -0
  155. package/dist/output.js +5 -1
  156. package/dist/pipeline/executor.js +3 -4
  157. package/dist/plugin-manifest.d.ts +70 -0
  158. package/dist/plugin-manifest.js +160 -0
  159. package/dist/plugin-manifest.test.d.ts +4 -0
  160. package/dist/plugin-manifest.test.js +179 -0
  161. package/dist/plugin.d.ts +38 -5
  162. package/dist/plugin.js +267 -33
  163. package/dist/plugin.test.js +220 -3
  164. package/dist/registry.d.ts +4 -0
  165. package/dist/registry.js +2 -0
  166. package/dist/runtime-detect.d.ts +21 -0
  167. package/dist/runtime-detect.js +32 -0
  168. package/dist/runtime-detect.test.d.ts +1 -0
  169. package/dist/runtime-detect.test.js +27 -0
  170. package/dist/runtime.js +1 -1
  171. package/dist/serialization.d.ts +2 -0
  172. package/dist/serialization.js +6 -0
  173. package/dist/types.d.ts +1 -0
  174. package/dist/update-check.d.ts +22 -0
  175. package/dist/update-check.js +112 -0
  176. package/dist/weixin-download.test.d.ts +1 -0
  177. package/dist/weixin-download.test.js +30 -0
  178. package/dist/weread-private-api-regression.test.d.ts +1 -0
  179. package/dist/weread-private-api-regression.test.js +122 -0
  180. package/dist/yaml-schema.d.ts +3 -0
  181. package/dist/yaml-schema.js +18 -1
  182. package/docs/.vitepress/config.mts +4 -0
  183. package/docs/adapters/browser/36kr.md +47 -0
  184. package/docs/adapters/browser/douban.md +14 -0
  185. package/docs/adapters/browser/imdb.md +47 -0
  186. package/docs/adapters/browser/jd.md +2 -2
  187. package/docs/adapters/browser/linux-do.md +181 -20
  188. package/docs/adapters/browser/paperreview.md +43 -0
  189. package/docs/adapters/browser/producthunt.md +49 -0
  190. package/docs/adapters/desktop/chatgpt.md +5 -0
  191. package/docs/adapters/index.md +6 -2
  192. package/docs/advanced/download.md +4 -0
  193. package/docs/advanced/rate-limiter-plugin.md +99 -0
  194. package/docs/guide/electron-app-cli.md +200 -0
  195. package/docs/guide/getting-started.md +1 -0
  196. package/docs/guide/plugins.md +87 -0
  197. package/docs/zh/guide/electron-app-cli.md +188 -0
  198. package/docs/zh/guide/getting-started.md +1 -0
  199. package/docs/zh/guide/plugins.md +65 -0
  200. package/extension/package.json +1 -0
  201. package/extension/scripts/package-release.mjs +179 -0
  202. package/extension/src/background.ts +2 -0
  203. package/package.json +4 -1
  204. package/scripts/postinstall.js +10 -0
  205. package/src/browser/cdp.ts +2 -1
  206. package/src/browser/discover.ts +8 -3
  207. package/src/browser/errors.ts +13 -14
  208. package/src/browser/mcp.ts +2 -1
  209. package/src/build-manifest.test.ts +23 -0
  210. package/src/build-manifest.ts +40 -15
  211. package/src/capabilityRouting.ts +2 -1
  212. package/src/cli.ts +35 -3
  213. package/src/clis/36kr/article.ts +69 -0
  214. package/src/clis/36kr/hot.test.ts +19 -0
  215. package/src/clis/36kr/hot.ts +100 -0
  216. package/src/clis/36kr/news.test.ts +90 -0
  217. package/src/clis/36kr/news.ts +54 -0
  218. package/src/clis/36kr/search.ts +78 -0
  219. package/src/clis/bilibili/comments.test.ts +102 -0
  220. package/src/clis/bilibili/comments.ts +44 -0
  221. package/src/clis/chatgpt/ask.ts +28 -14
  222. package/src/clis/chatgpt/ax.ts +180 -1
  223. package/src/clis/chatgpt/model.ts +27 -0
  224. package/src/clis/chatgpt/send.ts +16 -6
  225. package/src/clis/douban/download.test.ts +196 -0
  226. package/src/clis/douban/download.ts +78 -0
  227. package/src/clis/douban/photos.ts +36 -0
  228. package/src/clis/douban/utils.test.ts +97 -0
  229. package/src/clis/douban/utils.ts +232 -1
  230. package/src/clis/imdb/person.ts +232 -0
  231. package/src/clis/imdb/reviews.ts +111 -0
  232. package/src/clis/imdb/search.ts +179 -0
  233. package/src/clis/imdb/title.ts +121 -0
  234. package/src/clis/imdb/top.ts +67 -0
  235. package/src/clis/imdb/trending.ts +66 -0
  236. package/src/clis/imdb/utils.test.ts +117 -0
  237. package/src/clis/imdb/utils.ts +305 -0
  238. package/src/clis/jd/item.test.ts +18 -1
  239. package/src/clis/jd/item.ts +18 -15
  240. package/src/clis/linux-do/categories.yaml +38 -9
  241. package/src/clis/linux-do/category.ts +37 -0
  242. package/src/clis/linux-do/feed.test.ts +132 -0
  243. package/src/clis/linux-do/feed.ts +501 -0
  244. package/src/clis/linux-do/hot.ts +26 -0
  245. package/src/clis/linux-do/latest.ts +19 -0
  246. package/src/clis/linux-do/tags.yaml +41 -0
  247. package/src/clis/linux-do/topic.yaml +41 -3
  248. package/src/clis/linux-do/user-posts.yaml +67 -0
  249. package/src/clis/linux-do/user-topics.yaml +54 -0
  250. package/src/clis/paperreview/commands.test.ts +283 -0
  251. package/src/clis/paperreview/feedback.ts +64 -0
  252. package/src/clis/paperreview/review.ts +47 -0
  253. package/src/clis/paperreview/submit.ts +119 -0
  254. package/src/clis/paperreview/utils.test.ts +68 -0
  255. package/src/clis/paperreview/utils.ts +276 -0
  256. package/src/clis/producthunt/browse.ts +109 -0
  257. package/src/clis/producthunt/hot.ts +127 -0
  258. package/src/clis/producthunt/posts.ts +29 -0
  259. package/src/clis/producthunt/today.ts +37 -0
  260. package/src/clis/producthunt/utils.test.ts +72 -0
  261. package/src/clis/producthunt/utils.ts +122 -0
  262. package/src/clis/twitter/article.ts +5 -28
  263. package/src/clis/twitter/likes.test.ts +91 -0
  264. package/src/clis/twitter/likes.ts +256 -0
  265. package/src/clis/twitter/profile.ts +5 -28
  266. package/src/clis/twitter/search.test.ts +2 -0
  267. package/src/clis/twitter/search.ts +3 -1
  268. package/src/clis/twitter/shared.ts +45 -0
  269. package/src/clis/twitter/timeline.ts +2 -13
  270. package/src/clis/weixin/download.ts +114 -20
  271. package/src/clis/weread/book.ts +2 -2
  272. package/src/clis/weread/commands.test.ts +57 -0
  273. package/src/clis/weread/highlights.ts +2 -2
  274. package/src/clis/weread/notebooks.ts +2 -2
  275. package/src/clis/weread/notes.ts +3 -3
  276. package/src/clis/weread/shelf.ts +2 -2
  277. package/src/clis/weread/utils.test.ts +1 -32
  278. package/src/clis/weread/utils.ts +41 -16
  279. package/src/clis/xiaohongshu/comments.test.ts +96 -0
  280. package/src/clis/xiaohongshu/comments.ts +81 -0
  281. package/src/clis/xiaohongshu/publish.test.ts +137 -0
  282. package/src/clis/xiaohongshu/publish.ts +129 -18
  283. package/src/commanderAdapter.test.ts +78 -0
  284. package/src/commanderAdapter.ts +188 -24
  285. package/src/daemon.ts +19 -1
  286. package/src/discovery.ts +8 -15
  287. package/src/doctor.ts +13 -2
  288. package/src/download/index.test.ts +14 -4
  289. package/src/download/index.ts +67 -55
  290. package/src/errors.ts +25 -66
  291. package/src/execution.ts +28 -3
  292. package/src/hooks.ts +1 -0
  293. package/src/main.ts +6 -0
  294. package/src/output.ts +3 -1
  295. package/src/pipeline/executor.ts +4 -6
  296. package/src/plugin-manifest.test.ts +223 -0
  297. package/src/plugin-manifest.ts +206 -0
  298. package/src/plugin.test.ts +246 -2
  299. package/src/plugin.ts +338 -36
  300. package/src/registry.ts +6 -1
  301. package/src/runtime-detect.test.ts +30 -0
  302. package/src/runtime-detect.ts +36 -0
  303. package/src/runtime.ts +1 -1
  304. package/src/serialization.ts +4 -0
  305. package/src/types.ts +1 -0
  306. package/src/update-check.ts +114 -0
  307. package/src/weixin-download.test.ts +64 -0
  308. package/src/weread-private-api-regression.test.ts +150 -0
  309. package/src/yaml-schema.ts +20 -0
  310. package/tests/e2e/browser-auth.test.ts +13 -9
  311. package/tests/e2e/browser-public-extended.test.ts +1 -1
  312. package/tests/e2e/browser-public.test.ts +62 -4
  313. package/tests/e2e/helpers.ts +2 -1
  314. package/tests/e2e/public-commands.test.ts +37 -3
  315. package/tests/smoke/api-health.test.ts +1 -1
  316. package/vitest.config.ts +10 -0
  317. package/dist/clis/linux-do/category.yaml +0 -51
  318. package/dist/clis/linux-do/hot.yaml +0 -50
  319. package/dist/clis/linux-do/latest.yaml +0 -40
  320. package/src/clis/linux-do/category.yaml +0 -51
  321. package/src/clis/linux-do/hot.yaml +0 -50
  322. 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.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];
@@ -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,5 @@ 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>;
71
72
  }
@@ -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
@@ -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
 
@@ -0,0 +1,47 @@
1
+ # IMDb
2
+
3
+ **Mode**: 🌐 Public (Browser) · **Domain**: `www.imdb.com`
4
+
5
+ ## Commands
6
+
7
+ | Command | Description |
8
+ |---------|-------------|
9
+ | `opencli imdb search` | Search movies, TV shows, and people |
10
+ | `opencli imdb title` | Get movie or TV show details |
11
+ | `opencli imdb top` | IMDb Top 250 Movies |
12
+ | `opencli imdb trending` | IMDb Most Popular Movies |
13
+ | `opencli imdb person` | Get actor or director info |
14
+ | `opencli imdb reviews` | Get user reviews for a title |
15
+
16
+ ## Usage Examples
17
+
18
+ ```bash
19
+ # Search for a movie
20
+ opencli imdb search "inception" --limit 10
21
+
22
+ # Get movie details
23
+ opencli imdb title tt1375666
24
+
25
+ # Get TV series details (also accepts full URL)
26
+ opencli imdb title "https://www.imdb.com/title/tt0903747/"
27
+
28
+ # Top 250 movies
29
+ opencli imdb top --limit 20
30
+
31
+ # Currently trending movies
32
+ opencli imdb trending --limit 10
33
+
34
+ # Actor/director info with filmography
35
+ opencli imdb person nm0634240 --limit 5
36
+
37
+ # User reviews
38
+ opencli imdb reviews tt1375666 --limit 5
39
+
40
+ # JSON output
41
+ opencli imdb top --limit 5 -f json
42
+ ```
43
+
44
+ ## Prerequisites
45
+
46
+ - Chrome with Browser Bridge extension installed
47
+ - No login required (all data is public)
@@ -6,7 +6,7 @@
6
6
 
7
7
  | Command | Description |
8
8
  |---------|-------------|
9
- | `opencli jd item <sku>` | Fetch product details (price, images, specs) |
9
+ | `opencli jd item <sku>` | Fetch product details (price, shop, specs, AVIF images) |
10
10
 
11
11
  ## Usage Examples
12
12
 
@@ -14,7 +14,7 @@
14
14
  # Get product details by SKU
15
15
  opencli jd item 100291143898
16
16
 
17
- # Limit detail images
17
+ # Limit returned AVIF images
18
18
  opencli jd item 100291143898 --images 5
19
19
 
20
20
  # JSON output