@jackwener/opencli 1.7.8 → 1.7.10

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 (281) hide show
  1. package/README.md +49 -14
  2. package/README.zh-CN.md +30 -10
  3. package/cli-manifest.json +646 -30
  4. package/clis/36kr/news.js +1 -1
  5. package/clis/apple-podcasts/commands.test.js +4 -4
  6. package/clis/apple-podcasts/episodes.js +1 -1
  7. package/clis/apple-podcasts/search.js +1 -1
  8. package/clis/apple-podcasts/top.js +1 -1
  9. package/clis/arxiv/paper.js +1 -1
  10. package/clis/arxiv/search.js +1 -1
  11. package/clis/band/mentions.js +3 -3
  12. package/clis/bbc/news.js +1 -1
  13. package/clis/bilibili/subtitle.js +2 -2
  14. package/clis/bloomberg/businessweek.js +1 -1
  15. package/clis/bloomberg/economics.js +1 -1
  16. package/clis/bloomberg/industries.js +1 -1
  17. package/clis/bloomberg/main.js +1 -1
  18. package/clis/bloomberg/markets.js +1 -1
  19. package/clis/bloomberg/opinions.js +1 -1
  20. package/clis/bloomberg/politics.js +1 -1
  21. package/clis/bloomberg/tech.js +1 -1
  22. package/clis/boss/search.js +49 -8
  23. package/clis/boss/search.test.js +78 -0
  24. package/clis/boss/send.js +3 -3
  25. package/clis/chatgpt/image.js +37 -8
  26. package/clis/chatgpt/image.test.js +92 -0
  27. package/clis/chatgpt/utils.js +39 -6
  28. package/clis/chatgpt/utils.test.js +63 -0
  29. package/clis/chatgpt-app/ask.js +1 -1
  30. package/clis/chatgpt-app/ax.js +4 -2
  31. package/clis/chatgpt-app/ax.test.js +12 -0
  32. package/clis/chatgpt-app/model.js +1 -1
  33. package/clis/chatgpt-app/new.js +1 -1
  34. package/clis/chatgpt-app/read.js +1 -1
  35. package/clis/chatgpt-app/send.js +1 -1
  36. package/clis/chatgpt-app/status.js +1 -1
  37. package/clis/chatwise/ask.js +2 -2
  38. package/clis/chatwise/model.js +2 -2
  39. package/clis/chatwise/send.js +2 -2
  40. package/clis/claude/ask.js +128 -0
  41. package/clis/claude/ask.test.js +338 -0
  42. package/clis/claude/commands.test.js +118 -0
  43. package/clis/claude/detail.js +29 -0
  44. package/clis/claude/history.js +31 -0
  45. package/clis/claude/new.js +21 -0
  46. package/clis/claude/read.js +24 -0
  47. package/clis/claude/send.js +41 -0
  48. package/clis/claude/status.js +24 -0
  49. package/clis/claude/utils.js +440 -0
  50. package/clis/claude/utils.test.js +148 -0
  51. package/clis/codex/ask.js +2 -2
  52. package/clis/codex/send.js +2 -2
  53. package/clis/ctrip/search.js +1 -1
  54. package/clis/ctrip/search.test.js +4 -4
  55. package/clis/cursor/ask.js +2 -2
  56. package/clis/cursor/composer.js +2 -2
  57. package/clis/cursor/send.js +2 -2
  58. package/clis/deepseek/ask.js +17 -4
  59. package/clis/deepseek/ask.test.js +46 -0
  60. package/clis/deepseek/utils.js +55 -16
  61. package/clis/deepseek/utils.test.js +124 -5
  62. package/clis/doubao/utils.js +53 -11
  63. package/clis/doubao/utils.test.js +22 -2
  64. package/clis/eastmoney/announcement.js +1 -1
  65. package/clis/eastmoney/convertible.js +1 -1
  66. package/clis/eastmoney/etf.js +1 -1
  67. package/clis/eastmoney/holders.js +1 -1
  68. package/clis/eastmoney/index-board.js +1 -1
  69. package/clis/eastmoney/kline.js +1 -1
  70. package/clis/eastmoney/kuaixun.js +1 -1
  71. package/clis/eastmoney/longhu.js +1 -1
  72. package/clis/eastmoney/money-flow.js +1 -1
  73. package/clis/eastmoney/northbound.js +1 -1
  74. package/clis/eastmoney/quote.js +1 -1
  75. package/clis/eastmoney/rank.js +1 -1
  76. package/clis/eastmoney/sectors.js +1 -1
  77. package/clis/facebook/marketplace-inbox.js +83 -0
  78. package/clis/facebook/marketplace-listings.js +83 -0
  79. package/clis/facebook/marketplace.test.js +91 -0
  80. package/clis/google/news.js +1 -1
  81. package/clis/google/suggest.js +1 -1
  82. package/clis/google/trends.js +1 -1
  83. package/clis/google-scholar/cite.js +74 -0
  84. package/clis/google-scholar/cite.test.js +47 -0
  85. package/clis/google-scholar/profile.js +92 -0
  86. package/clis/google-scholar/profile.test.js +49 -0
  87. package/clis/google-scholar/search.js +1 -1
  88. package/clis/google-scholar/search.test.js +15 -0
  89. package/clis/hf/top.js +1 -1
  90. package/clis/instagram/collection-create.js +57 -0
  91. package/clis/instagram/saved.js +21 -7
  92. package/clis/jd/item.js +679 -47
  93. package/clis/jd/item.test.js +318 -7
  94. package/clis/jd/item.test.ts +517 -0
  95. package/clis/lesswrong/comments.js +1 -1
  96. package/clis/lesswrong/curated.js +1 -1
  97. package/clis/lesswrong/frontpage.js +1 -1
  98. package/clis/lesswrong/new.js +1 -1
  99. package/clis/lesswrong/read.js +1 -1
  100. package/clis/lesswrong/sequences.js +1 -1
  101. package/clis/lesswrong/shortform.js +1 -1
  102. package/clis/lesswrong/tag.js +1 -1
  103. package/clis/lesswrong/tags.js +1 -1
  104. package/clis/lesswrong/top-month.js +1 -1
  105. package/clis/lesswrong/top-week.js +1 -1
  106. package/clis/lesswrong/top-year.js +1 -1
  107. package/clis/lesswrong/top.js +1 -1
  108. package/clis/lesswrong/user-posts.js +1 -1
  109. package/clis/lesswrong/user.js +1 -1
  110. package/clis/paperreview/commands.test.js +6 -6
  111. package/clis/paperreview/feedback.js +1 -1
  112. package/clis/paperreview/review.js +1 -1
  113. package/clis/paperreview/submit.js +1 -1
  114. package/clis/producthunt/posts.js +1 -1
  115. package/clis/producthunt/today.js +1 -1
  116. package/clis/sinablog/search.js +1 -1
  117. package/clis/sinafinance/news.js +1 -1
  118. package/clis/sinafinance/stock.js +1 -1
  119. package/clis/sinafinance/stock.test.js +2 -2
  120. package/clis/spotify/spotify.js +6 -6
  121. package/clis/substack/search.js +1 -1
  122. package/clis/toutiao/articles.js +5 -6
  123. package/clis/toutiao/articles.test.js +22 -15
  124. package/clis/twitter/followers.js +2 -2
  125. package/clis/twitter/following.js +224 -73
  126. package/clis/twitter/following.test.js +277 -0
  127. package/clis/twitter/post.js +184 -47
  128. package/clis/twitter/post.test.js +114 -34
  129. package/clis/uiverse/_shared.js +63 -4
  130. package/clis/uiverse/_shared.test.js +7 -0
  131. package/clis/uiverse/code.js +1 -0
  132. package/clis/uiverse/navigation.test.js +12 -0
  133. package/clis/uiverse/preview.js +1 -0
  134. package/clis/web/read.js +319 -81
  135. package/clis/web/read.test.js +221 -5
  136. package/clis/weibo/favorites.js +169 -0
  137. package/clis/weibo/favorites.test.js +114 -0
  138. package/clis/weibo/publish.js +282 -0
  139. package/clis/weibo/publish.test.js +183 -0
  140. package/clis/weread/ranking.js +1 -1
  141. package/clis/weread/search-regression.test.js +8 -8
  142. package/clis/weread/search.js +1 -1
  143. package/clis/wikipedia/random.js +1 -1
  144. package/clis/wikipedia/search.js +1 -1
  145. package/clis/wikipedia/summary.js +1 -1
  146. package/clis/wikipedia/trending.js +1 -1
  147. package/clis/xianyu/chat.js +3 -3
  148. package/clis/xianyu/item.js +2 -2
  149. package/clis/xianyu/item.test.js +3 -3
  150. package/clis/xiaohongshu/search.js +17 -2
  151. package/clis/xiaohongshu/search.test.js +37 -1
  152. package/clis/xiaoyuzhou/download.js +1 -1
  153. package/clis/xiaoyuzhou/download.test.js +3 -3
  154. package/clis/xiaoyuzhou/episode.js +1 -1
  155. package/clis/xiaoyuzhou/podcast-episodes.js +1 -1
  156. package/clis/xiaoyuzhou/podcast-episodes.test.js +2 -2
  157. package/clis/xiaoyuzhou/podcast.js +1 -1
  158. package/clis/xiaoyuzhou/transcript.js +1 -1
  159. package/clis/xiaoyuzhou/transcript.test.js +5 -5
  160. package/clis/yollomi/models.js +1 -1
  161. package/clis/youtube/channel.js +24 -1
  162. package/clis/youtube/channel.test.js +59 -0
  163. package/clis/zhihu/answer.js +21 -162
  164. package/clis/zhihu/answer.test.js +26 -53
  165. package/clis/zhihu/collection.js +197 -0
  166. package/clis/zhihu/collection.test.js +290 -0
  167. package/clis/zhihu/collections.js +127 -0
  168. package/clis/zhihu/collections.test.js +182 -0
  169. package/clis/zhihu/comment.js +24 -305
  170. package/clis/zhihu/comment.test.js +31 -35
  171. package/clis/zhihu/favorite.js +44 -182
  172. package/clis/zhihu/favorite.test.js +30 -167
  173. package/clis/zhihu/follow.js +25 -56
  174. package/clis/zhihu/follow.test.js +20 -23
  175. package/clis/zhihu/like.js +22 -67
  176. package/clis/zhihu/like.test.js +19 -42
  177. package/clis/zhihu/search.js +3 -2
  178. package/clis/zhihu/write-shared.js +8 -1
  179. package/clis/zhihu/write-shared.test.js +1 -0
  180. package/clis/zlibrary/commands.test.js +75 -0
  181. package/clis/zlibrary/info.js +47 -0
  182. package/clis/zlibrary/search.js +46 -0
  183. package/clis/zlibrary/utils.js +136 -0
  184. package/dist/src/adapter-source.d.ts +11 -0
  185. package/dist/src/adapter-source.js +24 -0
  186. package/dist/src/adapter-source.test.js +29 -0
  187. package/dist/src/browser/base-page.d.ts +3 -1
  188. package/dist/src/browser/base-page.js +76 -1
  189. package/dist/src/browser/base-page.test.d.ts +1 -0
  190. package/dist/src/browser/base-page.test.js +74 -0
  191. package/dist/src/browser/bridge.d.ts +1 -2
  192. package/dist/src/browser/bridge.js +40 -41
  193. package/dist/src/browser/cdp.d.ts +1 -0
  194. package/dist/src/browser/cdp.js +3 -3
  195. package/dist/src/browser/daemon-client.d.ts +38 -4
  196. package/dist/src/browser/daemon-client.js +24 -7
  197. package/dist/src/browser/daemon-client.test.js +49 -0
  198. package/dist/src/browser/daemon-lifecycle.d.ts +23 -0
  199. package/dist/src/browser/daemon-lifecycle.js +67 -0
  200. package/dist/src/browser/daemon-version.d.ts +4 -0
  201. package/dist/src/browser/daemon-version.js +12 -0
  202. package/dist/src/browser/errors.js +3 -0
  203. package/dist/src/browser/errors.test.js +3 -0
  204. package/dist/src/browser/network-cache.d.ts +1 -0
  205. package/dist/src/browser/page.d.ts +3 -1
  206. package/dist/src/browser/page.js +10 -2
  207. package/dist/src/browser/profile.d.ts +14 -0
  208. package/dist/src/browser/profile.js +85 -0
  209. package/dist/src/build-manifest.d.ts +2 -0
  210. package/dist/src/build-manifest.js +13 -3
  211. package/dist/src/build-manifest.test.js +20 -2
  212. package/dist/src/cli.d.ts +6 -0
  213. package/dist/src/cli.js +477 -35
  214. package/dist/src/cli.test.js +303 -2
  215. package/dist/src/commanderAdapter.js +17 -9
  216. package/dist/src/commanderAdapter.test.js +67 -2
  217. package/dist/src/commands/daemon.d.ts +2 -0
  218. package/dist/src/commands/daemon.js +42 -1
  219. package/dist/src/commands/daemon.test.js +103 -2
  220. package/dist/src/completion-shared.js +1 -2
  221. package/dist/src/completion.test.js +3 -2
  222. package/dist/src/daemon.js +125 -41
  223. package/dist/src/doctor.d.ts +5 -6
  224. package/dist/src/doctor.js +77 -19
  225. package/dist/src/doctor.test.js +117 -0
  226. package/dist/src/engine.test.js +6 -5
  227. package/dist/src/errors.d.ts +14 -8
  228. package/dist/src/errors.js +36 -30
  229. package/dist/src/errors.test.js +5 -5
  230. package/dist/src/execution.d.ts +4 -0
  231. package/dist/src/execution.js +173 -25
  232. package/dist/src/execution.test.js +171 -1
  233. package/dist/src/main.js +10 -0
  234. package/dist/src/observation/artifact.d.ts +16 -0
  235. package/dist/src/observation/artifact.js +260 -0
  236. package/dist/src/observation/artifact.test.d.ts +1 -0
  237. package/dist/src/observation/artifact.test.js +121 -0
  238. package/dist/src/observation/events.d.ts +89 -0
  239. package/dist/src/observation/events.js +1 -0
  240. package/dist/src/observation/index.d.ts +7 -0
  241. package/dist/src/observation/index.js +7 -0
  242. package/dist/src/observation/manager.d.ts +9 -0
  243. package/dist/src/observation/manager.js +27 -0
  244. package/dist/src/observation/manager.test.d.ts +1 -0
  245. package/dist/src/observation/manager.test.js +13 -0
  246. package/dist/src/observation/redaction.d.ts +11 -0
  247. package/dist/src/observation/redaction.js +81 -0
  248. package/dist/src/observation/redaction.test.d.ts +1 -0
  249. package/dist/src/observation/redaction.test.js +32 -0
  250. package/dist/src/observation/retention.d.ts +32 -0
  251. package/dist/src/observation/retention.js +160 -0
  252. package/dist/src/observation/retention.test.d.ts +1 -0
  253. package/dist/src/observation/retention.test.js +118 -0
  254. package/dist/src/observation/ring-buffer.d.ts +22 -0
  255. package/dist/src/observation/ring-buffer.js +45 -0
  256. package/dist/src/observation/ring-buffer.test.d.ts +1 -0
  257. package/dist/src/observation/ring-buffer.test.js +22 -0
  258. package/dist/src/observation/session.d.ts +25 -0
  259. package/dist/src/observation/session.js +50 -0
  260. package/dist/src/pipeline/executor.test.js +1 -0
  261. package/dist/src/pipeline/steps/download.test.js +1 -0
  262. package/dist/src/pipeline/steps/fetch.js +1 -21
  263. package/dist/src/pipeline/steps/fetch.test.js +6 -12
  264. package/dist/src/plugin-scaffold.js +1 -1
  265. package/dist/src/plugin-scaffold.test.js +1 -1
  266. package/dist/src/registry.d.ts +40 -9
  267. package/dist/src/registry.js +3 -1
  268. package/dist/src/runtime-detect.d.ts +10 -0
  269. package/dist/src/runtime-detect.js +19 -0
  270. package/dist/src/runtime-detect.test.js +12 -1
  271. package/dist/src/runtime.d.ts +2 -0
  272. package/dist/src/runtime.js +1 -0
  273. package/dist/src/types.d.ts +22 -0
  274. package/dist/src/update-check.d.ts +31 -1
  275. package/dist/src/update-check.js +62 -16
  276. package/dist/src/update-check.test.js +86 -1
  277. package/package.json +1 -1
  278. package/dist/src/diagnostic.d.ts +0 -63
  279. package/dist/src/diagnostic.js +0 -292
  280. package/dist/src/diagnostic.test.js +0 -302
  281. /package/dist/src/{diagnostic.test.d.ts → adapter-source.test.d.ts} +0 -0
@@ -25,17 +25,17 @@ export interface RequiredEnv {
25
25
  help?: string;
26
26
  }
27
27
  export type CommandArgs = Record<string, any>;
28
- export interface CliCommand {
28
+ export type BrowserCommandFunc = (page: IPage, kwargs: CommandArgs, debug?: boolean) => Promise<unknown>;
29
+ export type NonBrowserCommandFunc = (kwargs: CommandArgs, debug?: boolean) => Promise<unknown>;
30
+ interface BaseCliCommand {
29
31
  site: string;
30
32
  name: string;
31
33
  aliases?: string[];
32
34
  description: string;
33
35
  domain?: string;
34
36
  strategy?: Strategy;
35
- browser?: boolean;
36
37
  args: Arg[];
37
38
  columns?: string[];
38
- func?: (page: IPage, kwargs: CommandArgs, debug?: boolean) => Promise<unknown>;
39
39
  pipeline?: Record<string, unknown>[];
40
40
  timeoutSeconds?: number;
41
41
  /** Origin of this command: 'yaml', 'ts', or plugin name. */
@@ -66,22 +66,53 @@ export interface CliCommand {
66
66
  /** Override the default CLI output format when the user does not pass -f/--format. */
67
67
  defaultFormat?: 'table' | 'plain' | 'json' | 'yaml' | 'yml' | 'md' | 'markdown' | 'csv';
68
68
  }
69
+ export interface BrowserCliCommand extends BaseCliCommand {
70
+ /** Browser commands receive an IPage. Omitted means true after normalization. */
71
+ browser?: true;
72
+ func?: BrowserCommandFunc;
73
+ }
74
+ export interface NonBrowserCliCommand extends BaseCliCommand {
75
+ /** Non-browser commands do not receive a page argument. */
76
+ browser: false;
77
+ func?: NonBrowserCommandFunc;
78
+ }
79
+ export type CliCommand = BrowserCliCommand | NonBrowserCliCommand;
80
+ type RawCliCommand = BaseCliCommand & {
81
+ browser?: boolean;
82
+ func?: BrowserCommandFunc | NonBrowserCommandFunc;
83
+ };
69
84
  /** Internal extension for lazy-loaded TS modules (not exposed in public API) */
70
- export interface InternalCliCommand extends CliCommand {
85
+ export type InternalCliCommand = CliCommand & {
71
86
  _lazy?: boolean;
72
87
  _modulePath?: string;
73
- }
74
- export interface CliOptions extends Partial<Omit<CliCommand, 'args' | 'description'>> {
88
+ };
89
+ type RequiredCliOptions = {
75
90
  site: string;
76
91
  name: string;
77
92
  description?: string;
78
93
  args?: Arg[];
79
- }
94
+ };
95
+ type BrowserStrategy = Exclude<Strategy, Strategy.PUBLIC | Strategy.LOCAL>;
96
+ type BrowserCliOptions = Partial<Omit<BrowserCliCommand, 'args' | 'description' | 'browser' | 'strategy'>> & RequiredCliOptions & ({
97
+ browser: true;
98
+ strategy?: Strategy;
99
+ } | {
100
+ browser?: true;
101
+ strategy?: BrowserStrategy;
102
+ });
103
+ type NonBrowserCliOptions = Partial<Omit<NonBrowserCliCommand, 'args' | 'description'>> & RequiredCliOptions & ({
104
+ browser: false;
105
+ } | {
106
+ strategy: Strategy.PUBLIC | Strategy.LOCAL;
107
+ browser?: false;
108
+ });
109
+ export type CliOptions = BrowserCliOptions | NonBrowserCliOptions;
80
110
  declare global {
81
111
  var __opencli_registry__: Map<string, CliCommand> | undefined;
82
112
  }
83
113
  export declare function cli(opts: CliOptions): CliCommand;
84
114
  export declare function getRegistry(): Map<string, CliCommand>;
85
- export declare function fullName(cmd: CliCommand): string;
115
+ export declare function fullName(cmd: Pick<BaseCliCommand, 'site' | 'name'>): string;
86
116
  export declare function strategyLabel(cmd: CliCommand): string;
87
- export declare function registerCommand(cmd: CliCommand): void;
117
+ export declare function registerCommand(cmd: RawCliCommand): void;
118
+ export {};
@@ -72,7 +72,9 @@ function normalizeCommand(cmd) {
72
72
  navigateBefore = true;
73
73
  }
74
74
  }
75
- return { ...cmd, strategy, browser, navigateBefore };
75
+ return browser
76
+ ? { ...cmd, strategy, browser: true, navigateBefore }
77
+ : { ...cmd, strategy, browser: false, navigateBefore };
76
78
  }
77
79
  export function registerCommand(cmd) {
78
80
  const normalized = normalizeCommand(cmd);
@@ -6,6 +6,7 @@
6
6
  * (e.g. logging, diagnostics) without littering runtime sniffing everywhere.
7
7
  */
8
8
  export type Runtime = 'bun' | 'node';
9
+ export declare const MIN_SUPPORTED_NODE_MAJOR = 21;
9
10
  /**
10
11
  * Detect the current JavaScript runtime.
11
12
  */
@@ -19,3 +20,12 @@ export declare function getRuntimeVersion(): string;
19
20
  * Return a combined label like "node v22.13.0" or "bun 1.1.42".
20
21
  */
21
22
  export declare function getRuntimeLabel(): string;
23
+ /**
24
+ * Parse a Node.js version string like "v22.13.0" and return its major version.
25
+ * Returns null for non-Node or malformed inputs.
26
+ */
27
+ export declare function parseNodeMajor(version: string): number | null;
28
+ /**
29
+ * Whether the given Node.js version satisfies the current minimum support policy.
30
+ */
31
+ export declare function isSupportedNodeVersion(version?: string): boolean;
@@ -5,6 +5,7 @@
5
5
  * This module centralises the check so other code can adapt behaviour
6
6
  * (e.g. logging, diagnostics) without littering runtime sniffing everywhere.
7
7
  */
8
+ export const MIN_SUPPORTED_NODE_MAJOR = 21;
8
9
  /**
9
10
  * Detect the current JavaScript runtime.
10
11
  */
@@ -26,3 +27,21 @@ export function getRuntimeVersion() {
26
27
  export function getRuntimeLabel() {
27
28
  return `${detectRuntime()} ${getRuntimeVersion()}`;
28
29
  }
30
+ /**
31
+ * Parse a Node.js version string like "v22.13.0" and return its major version.
32
+ * Returns null for non-Node or malformed inputs.
33
+ */
34
+ export function parseNodeMajor(version) {
35
+ const match = /^v?(\d+)\./.exec(String(version).trim());
36
+ if (!match)
37
+ return null;
38
+ const major = Number(match[1]);
39
+ return Number.isInteger(major) ? major : null;
40
+ }
41
+ /**
42
+ * Whether the given Node.js version satisfies the current minimum support policy.
43
+ */
44
+ export function isSupportedNodeVersion(version = process.version) {
45
+ const major = parseNodeMajor(version);
46
+ return major !== null && major >= MIN_SUPPORTED_NODE_MAJOR;
47
+ }
@@ -1,5 +1,5 @@
1
1
  import { describe, it, expect } from 'vitest';
2
- import { detectRuntime, getRuntimeVersion, getRuntimeLabel } from './runtime-detect.js';
2
+ import { detectRuntime, getRuntimeVersion, getRuntimeLabel, parseNodeMajor, isSupportedNodeVersion, MIN_SUPPORTED_NODE_MAJOR } from './runtime-detect.js';
3
3
  describe('runtime-detect', () => {
4
4
  it('detectRuntime returns a valid runtime string', () => {
5
5
  const rt = detectRuntime();
@@ -24,4 +24,15 @@ describe('runtime-detect', () => {
24
24
  expect(rt).toBe('node');
25
25
  }
26
26
  });
27
+ it('parses Node major versions from standard version strings', () => {
28
+ expect(parseNodeMajor('v21.0.0')).toBe(21);
29
+ expect(parseNodeMajor('22.13.1')).toBe(22);
30
+ expect(parseNodeMajor('bun-1.2.0')).toBeNull();
31
+ });
32
+ it('checks the current minimum supported Node major version', () => {
33
+ expect(MIN_SUPPORTED_NODE_MAJOR).toBe(21);
34
+ expect(isSupportedNodeVersion('v20.18.0')).toBe(false);
35
+ expect(isSupportedNodeVersion('v21.0.0')).toBe(true);
36
+ expect(isSupportedNodeVersion('v25.0.0')).toBe(true);
37
+ });
27
38
  });
@@ -27,10 +27,12 @@ export interface IBrowserFactory {
27
27
  timeout?: number;
28
28
  workspace?: string;
29
29
  cdpEndpoint?: string;
30
+ contextId?: string;
30
31
  }): Promise<IPage>;
31
32
  close(): Promise<void>;
32
33
  }
33
34
  export declare function browserSession<T>(BrowserFactory: new () => IBrowserFactory, fn: (page: IPage) => Promise<T>, opts?: {
34
35
  workspace?: string;
35
36
  cdpEndpoint?: string;
37
+ contextId?: string;
36
38
  }): Promise<T>;
@@ -53,6 +53,7 @@ export async function browserSession(BrowserFactory, fn, opts = {}) {
53
53
  timeout: DEFAULT_BROWSER_CONNECT_TIMEOUT,
54
54
  workspace: opts.workspace,
55
55
  cdpEndpoint: opts.cdpEndpoint,
56
+ contextId: opts.contextId,
56
57
  });
57
58
  return await fn(page);
58
59
  }
@@ -33,19 +33,41 @@ export interface ScreenshotOptions {
33
33
  fullPage?: boolean;
34
34
  path?: string;
35
35
  }
36
+ export interface FetchJsonOptions {
37
+ method?: string;
38
+ headers?: Record<string, string>;
39
+ body?: unknown;
40
+ timeoutMs?: number;
41
+ }
36
42
  export interface BrowserSessionInfo {
37
43
  workspace?: string;
38
44
  connected?: boolean;
45
+ windowId?: number;
46
+ preferredTabId?: number | null;
47
+ owned?: boolean;
48
+ ownership?: 'owned' | 'borrowed';
49
+ lifecycle?: 'ephemeral' | 'persistent' | 'pinned';
50
+ surface?: 'dedicated-container' | 'borrowed-user-tab';
51
+ contextId?: string;
52
+ tabCount?: number;
53
+ idleMsRemaining?: number | null;
39
54
  [key: string]: unknown;
40
55
  }
41
56
  export interface IPage {
42
57
  goto(url: string, options?: {
43
58
  waitUntil?: 'load' | 'none';
44
59
  settleMs?: number;
60
+ allowBoundNavigation?: boolean;
45
61
  }): Promise<void>;
46
62
  evaluate(js: string): Promise<any>;
47
63
  /** Safely evaluate JS with pre-serialized arguments — prevents injection. */
48
64
  evaluateWithArgs?(js: string, args: Record<string, unknown>): Promise<any>;
65
+ /**
66
+ * Fetch JSON from inside the browser context, carrying the page's cookies.
67
+ * This is intentionally narrow: browser-context JSON fetch, not a generic
68
+ * HTTP client.
69
+ */
70
+ fetchJson(url: string, opts?: FetchJsonOptions): Promise<unknown>;
49
71
  getCookies(opts?: {
50
72
  domain?: string;
51
73
  url?: string;
@@ -7,7 +7,20 @@
7
7
  * - Check interval: 24 hours
8
8
  * - Notice appears AFTER command output, not before (same as npm/gh/yarn)
9
9
  * - Never delays or blocks the CLI command
10
+ *
11
+ * Cache is shared between the CLI process (writes latestVersion / latestExtensionVersion
12
+ * via background fetch) and the daemon process (writes currentExtensionVersion /
13
+ * extensionLastSeenAt via `recordExtensionVersion` on each hello). Writes use a
14
+ * read-merge-write pattern so neither side clobbers the other.
10
15
  */
16
+ declare const EXTENSION_STALE_MS: number;
17
+ interface UpdateCache {
18
+ lastCheck?: number;
19
+ latestVersion?: string;
20
+ latestExtensionVersion?: string;
21
+ currentExtensionVersion?: string;
22
+ extensionLastSeenAt?: number;
23
+ }
11
24
  interface GitHubReleaseAsset {
12
25
  name: string;
13
26
  }
@@ -15,6 +28,17 @@ interface GitHubRelease {
15
28
  tag_name: string;
16
29
  assets?: GitHubReleaseAsset[];
17
30
  }
31
+ interface NoticeInputs {
32
+ cliVersion: string;
33
+ cache: UpdateCache | null;
34
+ now: number;
35
+ }
36
+ interface NoticeLines {
37
+ cli?: string;
38
+ extension?: string;
39
+ }
40
+ /** Pure function: derive notice text from cache state. Exported for tests. */
41
+ declare function buildUpdateNotices({ cliVersion, cache, now }: NoticeInputs): NoticeLines;
18
42
  /**
19
43
  * Register a process exit hook that prints an update notice if a newer
20
44
  * version was found on the last background check.
@@ -28,9 +52,15 @@ declare function extractLatestExtensionVersionFromReleases(releases: GitHubRelea
28
52
  * Fully non-blocking — never awaited.
29
53
  */
30
54
  export declare function checkForUpdateBackground(): void;
55
+ /**
56
+ * Stash the current extension version into the shared cache. Called by the
57
+ * daemon on each hello handshake. Lets the next CLI process compare against
58
+ * the latest known release and print an exit notice without any extra I/O.
59
+ */
60
+ export declare function recordExtensionVersion(version: string): void;
31
61
  /**
32
62
  * Get the cached latest extension version (if available).
33
63
  * Used by `opencli doctor` to report extension updates.
34
64
  */
35
65
  export declare function getCachedLatestExtensionVersion(): string | undefined;
36
- export { extractLatestExtensionVersionFromReleases as _extractLatestExtensionVersionFromReleases, };
66
+ export { extractLatestExtensionVersionFromReleases as _extractLatestExtensionVersionFromReleases, buildUpdateNotices as _buildUpdateNotices, EXTENSION_STALE_MS as _EXTENSION_STALE_MS, };
@@ -7,6 +7,11 @@
7
7
  * - Check interval: 24 hours
8
8
  * - Notice appears AFTER command output, not before (same as npm/gh/yarn)
9
9
  * - Never delays or blocks the CLI command
10
+ *
11
+ * Cache is shared between the CLI process (writes latestVersion / latestExtensionVersion
12
+ * via background fetch) and the daemon process (writes currentExtensionVersion /
13
+ * extensionLastSeenAt via `recordExtensionVersion` on each hello). Writes use a
14
+ * read-merge-write pattern so neither side clobbers the other.
10
15
  */
11
16
  import * as fs from 'node:fs';
12
17
  import * as path from 'node:path';
@@ -16,24 +21,25 @@ import { PKG_VERSION } from './version.js';
16
21
  const CACHE_DIR = path.join(os.homedir(), '.opencli');
17
22
  const CACHE_FILE = path.join(CACHE_DIR, 'update-check.json');
18
23
  const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24h
24
+ const EXTENSION_STALE_MS = 7 * 24 * 60 * 60 * 1000; // 7d
19
25
  const NPM_REGISTRY_URL = 'https://registry.npmjs.org/@jackwener/opencli/latest';
20
26
  const GITHUB_RELEASES_URL = 'https://api.github.com/repos/jackwener/OpenCLI/releases?per_page=20';
21
- // Read cache once at module load — shared by both exported functions
22
- const _cache = (() => {
27
+ function readCacheSync() {
23
28
  try {
24
29
  return JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
25
30
  }
26
31
  catch {
27
32
  return null;
28
33
  }
29
- })();
30
- function writeCache(latestVersion, latestExtensionVersion) {
34
+ }
35
+ // Read cache once at module load — shared by both exported functions
36
+ const _cache = readCacheSync();
37
+ function writeCacheMerge(updates) {
31
38
  try {
32
39
  fs.mkdirSync(CACHE_DIR, { recursive: true });
33
- const data = { lastCheck: Date.now(), latestVersion };
34
- if (latestExtensionVersion)
35
- data.latestExtensionVersion = latestExtensionVersion;
36
- fs.writeFileSync(CACHE_FILE, JSON.stringify(data), 'utf-8');
40
+ const existing = readCacheSync() ?? {};
41
+ const merged = { ...existing, ...updates };
42
+ fs.writeFileSync(CACHE_FILE, JSON.stringify(merged), 'utf-8');
37
43
  }
38
44
  catch {
39
45
  // Best-effort; never fail
@@ -57,6 +63,28 @@ function isNewer(a, b) {
57
63
  function isCI() {
58
64
  return !!(process.env.CI || process.env.CONTINUOUS_INTEGRATION);
59
65
  }
66
+ /** Pure function: derive notice text from cache state. Exported for tests. */
67
+ function buildUpdateNotices({ cliVersion, cache, now }) {
68
+ if (!cache)
69
+ return {};
70
+ const lines = {};
71
+ if (cache.latestVersion && isNewer(cache.latestVersion, cliVersion)) {
72
+ lines.cli =
73
+ styleText('yellow', `\n Update available: v${cliVersion} → v${cache.latestVersion}\n`) +
74
+ styleText('dim', ` Run: npm install -g @jackwener/opencli\n`);
75
+ }
76
+ const { currentExtensionVersion, latestExtensionVersion, extensionLastSeenAt } = cache;
77
+ if (currentExtensionVersion &&
78
+ latestExtensionVersion &&
79
+ extensionLastSeenAt &&
80
+ now - extensionLastSeenAt < EXTENSION_STALE_MS &&
81
+ isNewer(latestExtensionVersion, currentExtensionVersion)) {
82
+ lines.extension =
83
+ styleText('yellow', `\n Extension update available: v${currentExtensionVersion} → v${latestExtensionVersion}\n`) +
84
+ styleText('dim', ` Download: https://github.com/jackwener/opencli/releases\n`);
85
+ }
86
+ return lines;
87
+ }
60
88
  /**
61
89
  * Register a process exit hook that prints an update notice if a newer
62
90
  * version was found on the last background check.
@@ -71,13 +99,15 @@ export function registerUpdateNoticeOnExit() {
71
99
  process.on('exit', (code) => {
72
100
  if (code !== 0)
73
101
  return; // Don't show update notice on error exit
74
- if (!_cache)
75
- return;
76
- if (!isNewer(_cache.latestVersion, PKG_VERSION))
102
+ const { cli, extension } = buildUpdateNotices({
103
+ cliVersion: PKG_VERSION,
104
+ cache: _cache,
105
+ now: Date.now(),
106
+ });
107
+ if (!cli && !extension)
77
108
  return;
78
109
  try {
79
- process.stderr.write(styleText('yellow', `\n Update available: v${PKG_VERSION} v${_cache.latestVersion}\n`) +
80
- styleText('dim', ` Run: npm install -g @jackwener/opencli\n\n`));
110
+ process.stderr.write(`${cli ?? ''}${extension ?? ''}\n`);
81
111
  }
82
112
  catch {
83
113
  // Ignore broken pipe (stderr closed before process exits)
@@ -123,7 +153,7 @@ async function fetchLatestExtensionVersion() {
123
153
  export function checkForUpdateBackground() {
124
154
  if (isCI())
125
155
  return;
126
- if (_cache && Date.now() - _cache.lastCheck < CHECK_INTERVAL_MS)
156
+ if (_cache?.lastCheck && Date.now() - _cache.lastCheck < CHECK_INTERVAL_MS)
127
157
  return;
128
158
  void (async () => {
129
159
  try {
@@ -139,7 +169,10 @@ export function checkForUpdateBackground() {
139
169
  const data = await res.json();
140
170
  if (typeof data.version === 'string') {
141
171
  const extVersion = await fetchLatestExtensionVersion();
142
- writeCache(data.version, extVersion);
172
+ const updates = { lastCheck: Date.now(), latestVersion: data.version };
173
+ if (extVersion)
174
+ updates.latestExtensionVersion = extVersion;
175
+ writeCacheMerge(updates);
143
176
  }
144
177
  }
145
178
  catch {
@@ -147,6 +180,19 @@ export function checkForUpdateBackground() {
147
180
  }
148
181
  })();
149
182
  }
183
+ /**
184
+ * Stash the current extension version into the shared cache. Called by the
185
+ * daemon on each hello handshake. Lets the next CLI process compare against
186
+ * the latest known release and print an exit notice without any extra I/O.
187
+ */
188
+ export function recordExtensionVersion(version) {
189
+ if (typeof version !== 'string' || !version.trim())
190
+ return;
191
+ writeCacheMerge({
192
+ currentExtensionVersion: version.trim(),
193
+ extensionLastSeenAt: Date.now(),
194
+ });
195
+ }
150
196
  /**
151
197
  * Get the cached latest extension version (if available).
152
198
  * Used by `opencli doctor` to report extension updates.
@@ -154,4 +200,4 @@ export function checkForUpdateBackground() {
154
200
  export function getCachedLatestExtensionVersion() {
155
201
  return _cache?.latestExtensionVersion;
156
202
  }
157
- export { extractLatestExtensionVersionFromReleases as _extractLatestExtensionVersionFromReleases, };
203
+ export { extractLatestExtensionVersionFromReleases as _extractLatestExtensionVersionFromReleases, buildUpdateNotices as _buildUpdateNotices, EXTENSION_STALE_MS as _EXTENSION_STALE_MS, };
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from 'vitest';
2
- import { _extractLatestExtensionVersionFromReleases as extractLatestExtensionVersionFromReleases } from './update-check.js';
2
+ import { _extractLatestExtensionVersionFromReleases as extractLatestExtensionVersionFromReleases, _buildUpdateNotices as buildUpdateNotices, _EXTENSION_STALE_MS as EXTENSION_STALE_MS, } from './update-check.js';
3
3
  describe('extractLatestExtensionVersionFromReleases', () => {
4
4
  it('reads the extension version from a versioned asset on a normal CLI release', () => {
5
5
  expect(extractLatestExtensionVersionFromReleases([
@@ -29,3 +29,88 @@ describe('extractLatestExtensionVersionFromReleases', () => {
29
29
  ])).toBeUndefined();
30
30
  });
31
31
  });
32
+ describe('buildUpdateNotices', () => {
33
+ const now = 1_700_000_000_000;
34
+ it('returns nothing when cache is empty', () => {
35
+ expect(buildUpdateNotices({ cliVersion: '1.0.0', cache: null, now })).toEqual({});
36
+ });
37
+ it('emits a CLI notice when registry version is newer', () => {
38
+ const lines = buildUpdateNotices({
39
+ cliVersion: '1.0.0',
40
+ cache: { lastCheck: now, latestVersion: '1.0.1' },
41
+ now,
42
+ });
43
+ expect(lines.cli).toContain('v1.0.0 → v1.0.1');
44
+ expect(lines.extension).toBeUndefined();
45
+ });
46
+ it('emits an extension notice when current ext is older and cache is fresh', () => {
47
+ const lines = buildUpdateNotices({
48
+ cliVersion: '1.0.0',
49
+ cache: {
50
+ lastCheck: now,
51
+ latestVersion: '1.0.0',
52
+ latestExtensionVersion: '2.1.0',
53
+ currentExtensionVersion: '2.0.0',
54
+ extensionLastSeenAt: now - 60_000,
55
+ },
56
+ now,
57
+ });
58
+ expect(lines.cli).toBeUndefined();
59
+ expect(lines.extension).toContain('v2.0.0 → v2.1.0');
60
+ });
61
+ it('skips the extension notice when lastSeenAt is older than the stale window', () => {
62
+ const lines = buildUpdateNotices({
63
+ cliVersion: '1.0.0',
64
+ cache: {
65
+ lastCheck: now,
66
+ latestVersion: '1.0.0',
67
+ latestExtensionVersion: '2.1.0',
68
+ currentExtensionVersion: '2.0.0',
69
+ extensionLastSeenAt: now - EXTENSION_STALE_MS - 1,
70
+ },
71
+ now,
72
+ });
73
+ expect(lines.extension).toBeUndefined();
74
+ });
75
+ it('skips the extension notice when current and latest are equal', () => {
76
+ const lines = buildUpdateNotices({
77
+ cliVersion: '1.0.0',
78
+ cache: {
79
+ lastCheck: now,
80
+ latestVersion: '1.0.0',
81
+ latestExtensionVersion: '2.0.0',
82
+ currentExtensionVersion: '2.0.0',
83
+ extensionLastSeenAt: now,
84
+ },
85
+ now,
86
+ });
87
+ expect(lines.extension).toBeUndefined();
88
+ });
89
+ it('does not throw when cache has only daemon-written fields and no latestVersion', () => {
90
+ const lines = buildUpdateNotices({
91
+ cliVersion: '1.0.0',
92
+ cache: {
93
+ currentExtensionVersion: '2.0.0',
94
+ extensionLastSeenAt: now,
95
+ },
96
+ now,
97
+ });
98
+ expect(lines.cli).toBeUndefined();
99
+ expect(lines.extension).toBeUndefined();
100
+ });
101
+ it('emits both notices when both are out of date', () => {
102
+ const lines = buildUpdateNotices({
103
+ cliVersion: '1.0.0',
104
+ cache: {
105
+ lastCheck: now,
106
+ latestVersion: '1.1.0',
107
+ latestExtensionVersion: '2.1.0',
108
+ currentExtensionVersion: '2.0.0',
109
+ extensionLastSeenAt: now,
110
+ },
111
+ now,
112
+ });
113
+ expect(lines.cli).toContain('v1.0.0 → v1.1.0');
114
+ expect(lines.extension).toContain('v2.0.0 → v2.1.0');
115
+ });
116
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackwener/opencli",
3
- "version": "1.7.8",
3
+ "version": "1.7.10",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -1,63 +0,0 @@
1
- /**
2
- * Structured diagnostic output for AI-driven adapter repair.
3
- *
4
- * When OPENCLI_DIAGNOSTIC=1, failed commands emit a JSON RepairContext to stderr
5
- * containing the error, adapter source, and browser state (DOM snapshot, network
6
- * requests, console errors). AI Agents consume this to diagnose and fix adapters.
7
- *
8
- * Safety boundaries:
9
- * - Sensitive headers/cookies are redacted before emission
10
- * - Individual fields are capped to prevent unbounded output
11
- * - Network response bodies from authenticated requests are stripped
12
- * - Total output is capped to MAX_DIAGNOSTIC_BYTES
13
- */
14
- import type { IPage } from './types.js';
15
- import type { InternalCliCommand } from './registry.js';
16
- /** Maximum bytes for the entire diagnostic JSON output. */
17
- export declare const MAX_DIAGNOSTIC_BYTES: number;
18
- export interface RepairContext {
19
- error: {
20
- code: string;
21
- message: string;
22
- hint?: string;
23
- stack?: string;
24
- };
25
- adapter: {
26
- site: string;
27
- command: string;
28
- sourcePath?: string;
29
- source?: string;
30
- };
31
- page?: {
32
- url: string;
33
- snapshot: string;
34
- networkRequests: unknown[];
35
- capturedPayloads?: unknown[];
36
- consoleErrors: unknown[];
37
- };
38
- timestamp: string;
39
- }
40
- /** Truncate a string to maxLen, appending a truncation marker. */
41
- export declare function truncate(str: string, maxLen: number): string;
42
- /** Redact sensitive query parameters from a URL. */
43
- export declare function redactUrl(url: string): string;
44
- /** Redact inline secrets from free-text strings (error messages, stack traces, console output, DOM). */
45
- export declare function redactText(text: string): string;
46
- /**
47
- * Resolve the editable source file path for an adapter.
48
- *
49
- * Priority:
50
- * 1. cmd.source (set for FS-scanned JS and manifest lazy-loaded JS)
51
- * 2. cmd._modulePath (set for manifest lazy-loaded JS)
52
- *
53
- * Skip manifest: prefixed pseudo-paths (YAML commands inlined in manifest).
54
- */
55
- export declare function resolveAdapterSourcePath(cmd: InternalCliCommand): string | undefined;
56
- /** Whether diagnostic mode is enabled. */
57
- export declare function isDiagnosticEnabled(): boolean;
58
- /** Build a RepairContext from an error, command metadata, and optional page state. */
59
- export declare function buildRepairContext(err: unknown, cmd: InternalCliCommand, pageState?: RepairContext['page']): RepairContext;
60
- /** Collect full diagnostic context including page state (with timeout). */
61
- export declare function collectDiagnostic(err: unknown, cmd: InternalCliCommand, page: IPage | null): Promise<RepairContext>;
62
- /** Emit diagnostic JSON to stderr, enforcing total size cap. */
63
- export declare function emitDiagnostic(ctx: RepairContext): void;