@jackwener/opencli 1.7.12 → 1.7.13

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 (407) hide show
  1. package/README.md +8 -7
  2. package/README.zh-CN.md +9 -8
  3. package/cli-manifest.json +12194 -6843
  4. package/clis/1point3acres/digest.js +35 -0
  5. package/clis/1point3acres/forum.js +51 -0
  6. package/clis/1point3acres/forums.js +44 -0
  7. package/clis/1point3acres/hot.js +35 -0
  8. package/clis/1point3acres/latest.js +35 -0
  9. package/clis/1point3acres/notifications.js +64 -0
  10. package/clis/1point3acres/search.js +71 -0
  11. package/clis/1point3acres/thread.js +117 -0
  12. package/clis/1point3acres/user.js +77 -0
  13. package/clis/1point3acres/utils.js +247 -0
  14. package/clis/_shared/desktop-commands.js +4 -0
  15. package/clis/aibase/news.js +110 -0
  16. package/clis/aibase/news.test.js +59 -0
  17. package/clis/amazon/discussion.test.js +1 -28
  18. package/clis/antigravity/watch.js +3 -2
  19. package/clis/arxiv/author.js +44 -0
  20. package/clis/baidu-scholar/search.js +0 -1
  21. package/clis/bbc/topic.js +57 -0
  22. package/clis/bbc/utils.js +79 -0
  23. package/clis/chaoxing/assignments.js +1 -1
  24. package/clis/chaoxing/exams.js +1 -1
  25. package/clis/chatgpt/ask.js +57 -0
  26. package/clis/chatgpt/commands.test.js +45 -0
  27. package/clis/chatgpt/detail.js +46 -0
  28. package/clis/chatgpt/history.js +39 -0
  29. package/clis/chatgpt/image.js +12 -11
  30. package/clis/chatgpt/image.test.js +23 -0
  31. package/clis/chatgpt/new.js +25 -0
  32. package/clis/chatgpt/read.js +43 -0
  33. package/clis/chatgpt/send.js +46 -0
  34. package/clis/chatgpt/status.js +29 -0
  35. package/clis/chatgpt/utils.js +294 -4
  36. package/clis/chatgpt/utils.test.js +13 -0
  37. package/clis/chatgpt-app/ask.js +6 -3
  38. package/clis/chatwise/ask.js +16 -43
  39. package/clis/chatwise/composer.test.js +186 -0
  40. package/clis/chatwise/send.js +2 -24
  41. package/clis/chatwise/utils.js +143 -0
  42. package/clis/claude/ask.js +1 -1
  43. package/clis/claude/detail.js +1 -0
  44. package/clis/claude/history.js +1 -0
  45. package/clis/claude/new.js +1 -0
  46. package/clis/claude/read.js +1 -0
  47. package/clis/claude/send.js +1 -0
  48. package/clis/claude/status.js +1 -0
  49. package/clis/codex/ask.js +15 -9
  50. package/clis/codex/history.js +16 -33
  51. package/clis/codex/projects.js +28 -0
  52. package/clis/codex/read.js +10 -4
  53. package/clis/codex/send.js +10 -3
  54. package/clis/codex/sidebar.js +356 -0
  55. package/clis/codex/sidebar.test.js +329 -0
  56. package/clis/coingecko/categories.js +75 -0
  57. package/clis/coingecko/coin.js +107 -0
  58. package/clis/coingecko/coingecko.test.js +109 -0
  59. package/clis/coingecko/derivatives.js +84 -0
  60. package/clis/coingecko/exchanges.js +74 -0
  61. package/clis/coingecko/global.js +71 -0
  62. package/clis/coingecko/top.js +64 -0
  63. package/clis/coingecko/trending.js +55 -0
  64. package/clis/coupang/add-to-cart.js +21 -13
  65. package/clis/coupang/coupang.test.js +159 -0
  66. package/clis/coupang/product.js +257 -0
  67. package/clis/coupang/search.js +38 -16
  68. package/clis/coupang/utils.js +55 -1
  69. package/clis/crates/crate.js +62 -0
  70. package/clis/crates/search.js +44 -0
  71. package/clis/crates/utils.js +72 -0
  72. package/clis/ctrip/ctrip.test.js +234 -0
  73. package/clis/ctrip/hotel-suggest.js +45 -0
  74. package/clis/ctrip/search.js +22 -68
  75. package/clis/ctrip/utils.js +175 -0
  76. package/clis/cursor/ask.js +6 -3
  77. package/clis/dblp/author.js +133 -0
  78. package/clis/dblp/venue.js +64 -0
  79. package/clis/deepseek/ask.js +12 -7
  80. package/clis/deepseek/ask.test.js +13 -13
  81. package/clis/deepseek/detail.js +38 -0
  82. package/clis/deepseek/detail.test.js +81 -0
  83. package/clis/deepseek/history.js +1 -0
  84. package/clis/deepseek/new.js +1 -0
  85. package/clis/deepseek/read.js +1 -0
  86. package/clis/deepseek/send.js +140 -0
  87. package/clis/deepseek/send.test.js +107 -0
  88. package/clis/deepseek/status.js +1 -0
  89. package/clis/deepseek/utils.js +66 -0
  90. package/clis/deepseek/utils.test.js +107 -1
  91. package/clis/defillama/defillama.test.js +99 -0
  92. package/clis/defillama/protocol.js +84 -0
  93. package/clis/defillama/protocols.js +55 -0
  94. package/clis/defillama/utils.js +99 -0
  95. package/clis/devto/latest.js +74 -0
  96. package/clis/dockerhub/image.js +52 -0
  97. package/clis/dockerhub/search.js +47 -0
  98. package/clis/dockerhub/utils.js +100 -0
  99. package/clis/doubao/ask.js +7 -3
  100. package/clis/doubao/detail.js +1 -0
  101. package/clis/doubao/history.js +1 -0
  102. package/clis/doubao/meeting-summary.js +1 -0
  103. package/clis/doubao/meeting-transcript.js +1 -0
  104. package/clis/doubao/new.js +1 -0
  105. package/clis/doubao/read.js +1 -0
  106. package/clis/doubao/send.js +1 -0
  107. package/clis/doubao/status.js +1 -0
  108. package/clis/douyin/draft.test.js +1 -30
  109. package/clis/endoflife/endoflife.test.js +51 -0
  110. package/clis/endoflife/product.js +55 -0
  111. package/clis/endoflife/utils.js +89 -0
  112. package/clis/facebook/__fixtures__/notifications-page.html +13 -0
  113. package/clis/facebook/notifications.js +326 -30
  114. package/clis/facebook/notifications.test.js +458 -0
  115. package/clis/flathub/app.js +71 -0
  116. package/clis/flathub/flathub.test.js +90 -0
  117. package/clis/flathub/search.js +80 -0
  118. package/clis/flathub/utils.js +114 -0
  119. package/clis/gemini/ask.js +7 -3
  120. package/clis/gemini/ask.test.js +2 -2
  121. package/clis/gemini/deep-research-result.js +6 -2
  122. package/clis/gemini/deep-research-result.test.js +15 -14
  123. package/clis/gemini/deep-research.js +8 -4
  124. package/clis/gemini/deep-research.test.js +15 -18
  125. package/clis/gemini/image.js +7 -2
  126. package/clis/gemini/new.js +1 -0
  127. package/clis/gemini/utils.js +0 -4
  128. package/clis/google-scholar/cite.js +0 -1
  129. package/clis/google-scholar/profile.js +0 -1
  130. package/clis/google-scholar/search.js +0 -1
  131. package/clis/goproxy/goproxy.test.js +103 -0
  132. package/clis/goproxy/module.js +47 -0
  133. package/clis/goproxy/utils.js +165 -0
  134. package/clis/goproxy/versions.js +59 -0
  135. package/clis/gov-law/recent.js +0 -1
  136. package/clis/gov-law/search.js +0 -1
  137. package/clis/gov-policy/__fixtures__/recent.html +16 -0
  138. package/clis/gov-policy/__fixtures__/search.html +41 -0
  139. package/clis/gov-policy/gov-policy.test.js +224 -0
  140. package/clis/gov-policy/recent.js +66 -24
  141. package/clis/gov-policy/search.js +65 -23
  142. package/clis/gov-policy/utils.js +54 -0
  143. package/clis/grok/ask.js +49 -265
  144. package/clis/grok/ask.test.js +21 -46
  145. package/clis/grok/detail.js +60 -0
  146. package/clis/grok/history.js +48 -0
  147. package/clis/grok/{image.ts → image.js} +56 -70
  148. package/clis/grok/image.test.ts +20 -0
  149. package/clis/grok/new.js +20 -0
  150. package/clis/grok/read.js +39 -0
  151. package/clis/grok/send.js +50 -0
  152. package/clis/grok/status.js +41 -0
  153. package/clis/grok/utils.js +326 -0
  154. package/clis/grok/utils.test.js +103 -0
  155. package/clis/hf/datasets.js +88 -0
  156. package/clis/hf/hf.test.js +16 -0
  157. package/clis/hf/models.js +91 -0
  158. package/clis/hf/paper.js +79 -0
  159. package/clis/hf/spaces.js +101 -0
  160. package/clis/hf/top.js +1 -0
  161. package/clis/homebrew/cask.js +39 -0
  162. package/clis/homebrew/formula.js +41 -0
  163. package/clis/homebrew/popular.js +54 -0
  164. package/clis/homebrew/utils.js +100 -0
  165. package/clis/hupu/__fixtures__/hot-home.html +64 -0
  166. package/clis/hupu/detail.js +0 -1
  167. package/clis/hupu/hot.js +156 -35
  168. package/clis/hupu/hot.test.js +224 -0
  169. package/clis/hupu/search.js +0 -1
  170. package/clis/instagram/note.js +1 -1
  171. package/clis/instagram/note.test.js +1 -29
  172. package/clis/instagram/post.js +1 -1
  173. package/clis/instagram/post.test.js +1 -1
  174. package/clis/instagram/reel.js +1 -1
  175. package/clis/instagram/story.js +1 -1
  176. package/clis/instagram/story.test.js +1 -34
  177. package/clis/jd/commands.test.js +1 -24
  178. package/clis/lichess/lichess.test.js +85 -0
  179. package/clis/lichess/top.js +46 -0
  180. package/clis/lichess/user.js +91 -0
  181. package/clis/lichess/utils.js +97 -0
  182. package/clis/linkedin/search.js +107 -10
  183. package/clis/linkedin/search.test.js +222 -0
  184. package/clis/linux-do/feed.js +2 -5
  185. package/clis/linux-do/feed.test.js +35 -0
  186. package/clis/lobsters/domain.js +92 -0
  187. package/clis/maven/artifact.js +49 -0
  188. package/clis/maven/search.js +51 -0
  189. package/clis/maven/utils.js +110 -0
  190. package/clis/mdn/search.js +97 -0
  191. package/clis/medium/tag.js +135 -0
  192. package/clis/npm/downloads.js +59 -0
  193. package/clis/npm/package.js +70 -0
  194. package/clis/npm/search.js +49 -0
  195. package/clis/npm/utils.js +76 -0
  196. package/clis/nuget/nuget.test.js +111 -0
  197. package/clis/nuget/package.js +101 -0
  198. package/clis/nuget/search.js +69 -0
  199. package/clis/nuget/utils.js +87 -0
  200. package/clis/nvd/cve.js +121 -0
  201. package/clis/oeis/oeis.test.js +88 -0
  202. package/clis/oeis/search.js +63 -0
  203. package/clis/oeis/sequence.js +71 -0
  204. package/clis/oeis/utils.js +88 -0
  205. package/clis/openalex/search.js +69 -0
  206. package/clis/openalex/utils.js +160 -0
  207. package/clis/openalex/work.js +65 -0
  208. package/clis/openfda/drug-label.js +74 -0
  209. package/clis/openfda/food-recall.js +65 -0
  210. package/clis/openfda/openfda.test.js +114 -0
  211. package/clis/openfda/utils.js +67 -0
  212. package/clis/osv/osv.test.js +97 -0
  213. package/clis/osv/query.js +72 -0
  214. package/clis/osv/utils.js +169 -0
  215. package/clis/osv/vulnerability.js +54 -0
  216. package/clis/packagist/package.js +49 -0
  217. package/clis/packagist/search.js +43 -0
  218. package/clis/packagist/utils.js +113 -0
  219. package/clis/paperreview/feedback.js +1 -1
  220. package/clis/paperreview/review.js +1 -1
  221. package/clis/paperreview/submit.js +1 -1
  222. package/clis/pixiv/download.test.js +1 -1
  223. package/clis/pixiv/illusts.test.js +1 -1
  224. package/clis/pixiv/search.test.js +1 -1
  225. package/clis/pubmed/article.js +50 -0
  226. package/clis/pubmed/author.js +64 -0
  227. package/clis/pubmed/citations.js +36 -0
  228. package/clis/pubmed/pubmed.test.js +276 -0
  229. package/clis/pubmed/related.js +45 -0
  230. package/clis/pubmed/search.js +75 -0
  231. package/clis/pubmed/utils.js +309 -0
  232. package/clis/pypi/downloads.js +66 -0
  233. package/clis/pypi/package.js +79 -0
  234. package/clis/pypi/utils.js +55 -0
  235. package/clis/quark/mv.js +1 -1
  236. package/clis/quark/save.js +1 -1
  237. package/clis/qwen/ask.js +85 -0
  238. package/clis/qwen/detail.js +62 -0
  239. package/clis/qwen/history.js +61 -0
  240. package/clis/qwen/image.js +179 -0
  241. package/clis/qwen/new.js +23 -0
  242. package/clis/qwen/read.js +41 -0
  243. package/clis/qwen/send.js +55 -0
  244. package/clis/qwen/status.js +37 -0
  245. package/clis/qwen/utils.js +409 -0
  246. package/clis/qwen/utils.test.js +45 -0
  247. package/clis/rest-countries/country.js +65 -0
  248. package/clis/rest-countries/region.js +64 -0
  249. package/clis/rest-countries/rest-countries.test.js +83 -0
  250. package/clis/rest-countries/utils.js +126 -0
  251. package/clis/reuters/article-detail.js +53 -0
  252. package/clis/reuters/reuters.test.js +299 -0
  253. package/clis/reuters/search.js +45 -34
  254. package/clis/reuters/utils.js +159 -0
  255. package/clis/rfc/rfc.js +52 -0
  256. package/clis/rfc/rfc.test.js +74 -0
  257. package/clis/rfc/utils.js +72 -0
  258. package/clis/rubygems/gem.js +42 -0
  259. package/clis/rubygems/search.js +47 -0
  260. package/clis/rubygems/utils.js +86 -0
  261. package/clis/stackoverflow/related.js +66 -0
  262. package/clis/stackoverflow/stackoverflow.test.js +58 -0
  263. package/clis/stackoverflow/tag.js +60 -0
  264. package/clis/stackoverflow/user.js +50 -0
  265. package/clis/stackoverflow/utils.js +118 -0
  266. package/clis/steam/app.js +67 -0
  267. package/clis/steam/search.js +58 -0
  268. package/clis/steam/steam.test.js +46 -0
  269. package/clis/steam/utils.js +107 -0
  270. package/clis/taobao/commands.test.js +1 -24
  271. package/clis/test-utils.js +61 -0
  272. package/clis/tieba/hot.js +0 -1
  273. package/clis/tiktok/comment.js +128 -41
  274. package/clis/tiktok/creator-videos.js +270 -0
  275. package/clis/tiktok/creator-videos.test.js +113 -0
  276. package/clis/tiktok/explore.js +137 -29
  277. package/clis/tiktok/follow.js +115 -33
  278. package/clis/tiktok/following.js +157 -36
  279. package/clis/tiktok/friends.js +139 -37
  280. package/clis/tiktok/live.js +137 -41
  281. package/clis/tiktok/notifications.js +141 -38
  282. package/clis/tiktok/refactor.test.js +389 -0
  283. package/clis/tiktok/unfollow.js +124 -38
  284. package/clis/tiktok/user.js +203 -29
  285. package/clis/tiktok/utils.js +505 -0
  286. package/clis/tiktok/write-refactor.test.js +370 -0
  287. package/clis/toutiao/articles.js +36 -62
  288. package/clis/toutiao/hot.js +63 -0
  289. package/clis/toutiao/toutiao.test.js +378 -0
  290. package/clis/toutiao/utils.js +161 -0
  291. package/clis/tvmaze/search.js +61 -0
  292. package/clis/tvmaze/show.js +60 -0
  293. package/clis/tvmaze/tvmaze.test.js +93 -0
  294. package/clis/tvmaze/utils.js +110 -0
  295. package/clis/twitter/accept.js +1 -1
  296. package/clis/twitter/followers.js +134 -69
  297. package/clis/twitter/reply-dm.js +1 -1
  298. package/clis/twitter/reply.test.js +1 -29
  299. package/clis/uisdc/news.js +105 -0
  300. package/clis/uisdc/news.test.js +66 -0
  301. package/clis/wanfang/search.js +0 -1
  302. package/clis/web/read.js +47 -17
  303. package/clis/web/read.test.js +101 -1
  304. package/clis/weixin/create-draft.js +1 -1
  305. package/clis/weixin/drafts.js +1 -1
  306. package/clis/weixin/drafts.test.js +5 -1
  307. package/clis/weixin/search.js +157 -0
  308. package/clis/weixin/search.test.js +227 -0
  309. package/clis/wikidata/entity.js +60 -0
  310. package/clis/wikidata/search.js +50 -0
  311. package/clis/wikidata/utils.js +117 -0
  312. package/clis/wikidata/wikidata.test.js +83 -0
  313. package/clis/wikipedia/page.js +95 -0
  314. package/clis/wttr/current.js +63 -0
  315. package/clis/wttr/forecast.js +71 -0
  316. package/clis/wttr/utils.js +50 -0
  317. package/clis/wttr/wttr.test.js +84 -0
  318. package/clis/xianyu/chat.js +16 -4
  319. package/clis/xianyu/chat.test.js +64 -0
  320. package/clis/xianyu/publish.js +485 -0
  321. package/clis/xianyu/publish.test.js +220 -0
  322. package/clis/xiaoe/catalog.js +105 -40
  323. package/clis/xiaoe/content.js +164 -29
  324. package/clis/xiaoe/courses.js +86 -29
  325. package/clis/xiaoe/xiaoe.test.js +486 -0
  326. package/clis/xiaohongshu/creator-notes-summary.js +1 -1
  327. package/clis/xiaohongshu/publish.js +16 -3
  328. package/clis/xiaohongshu/publish.test.js +46 -1
  329. package/clis/youtube/transcript.js +13 -19
  330. package/clis/youtube/transcript.test.js +17 -0
  331. package/clis/yuanbao/ask.js +17 -66
  332. package/clis/yuanbao/ask.test.js +5 -5
  333. package/clis/yuanbao/detail.js +65 -0
  334. package/clis/yuanbao/history.js +51 -0
  335. package/clis/yuanbao/new.js +1 -0
  336. package/clis/yuanbao/read.js +38 -0
  337. package/clis/yuanbao/send.js +57 -0
  338. package/clis/yuanbao/shared.js +297 -5
  339. package/clis/yuanbao/shared.test.js +80 -0
  340. package/clis/yuanbao/status.js +44 -0
  341. package/clis/zlibrary/commands.test.js +1 -11
  342. package/dist/src/browser/base-page.d.ts +9 -0
  343. package/dist/src/browser/base-page.js +44 -1
  344. package/dist/src/browser/base-page.test.js +66 -0
  345. package/dist/src/browser/cdp.d.ts +1 -0
  346. package/dist/src/browser/cdp.js +51 -9
  347. package/dist/src/browser/daemon-client.d.ts +4 -0
  348. package/dist/src/browser/errors.js +1 -1
  349. package/dist/src/browser/page.d.ts +1 -1
  350. package/dist/src/browser/page.js +3 -1
  351. package/dist/src/browser/page.test.js +29 -0
  352. package/dist/src/browser/target-errors.d.ts +2 -1
  353. package/dist/src/browser/target-errors.js +1 -0
  354. package/dist/src/browser/target-resolver.d.ts +25 -0
  355. package/dist/src/browser/target-resolver.js +43 -0
  356. package/dist/src/build-manifest.js +9 -4
  357. package/dist/src/build-manifest.test.js +2 -8
  358. package/dist/src/capabilityRouting.d.ts +16 -1
  359. package/dist/src/capabilityRouting.js +24 -1
  360. package/dist/src/capabilityRouting.test.js +19 -1
  361. package/dist/src/cli.js +76 -11
  362. package/dist/src/cli.test.js +150 -0
  363. package/dist/src/commanderAdapter.js +0 -5
  364. package/dist/src/commanderAdapter.test.js +0 -1
  365. package/dist/src/discovery.js +2 -5
  366. package/dist/src/errors.js +1 -1
  367. package/dist/src/execution.d.ts +1 -1
  368. package/dist/src/execution.js +111 -27
  369. package/dist/src/execution.test.js +326 -17
  370. package/dist/src/help.d.ts +23 -2
  371. package/dist/src/help.js +41 -19
  372. package/dist/src/help.test.d.ts +1 -0
  373. package/dist/src/help.test.js +54 -0
  374. package/dist/src/main.js +14 -1
  375. package/dist/src/manifest-types.d.ts +5 -3
  376. package/dist/src/pipeline/executor.js +1 -1
  377. package/dist/src/pipeline/executor.test.js +8 -0
  378. package/dist/src/pipeline/registry.d.ts +9 -0
  379. package/dist/src/pipeline/registry.js +13 -1
  380. package/dist/src/pipeline/steps/browser.d.ts +1 -0
  381. package/dist/src/pipeline/steps/browser.js +10 -0
  382. package/dist/src/pipeline/steps/download.test.js +1 -0
  383. package/dist/src/registry-api.d.ts +1 -1
  384. package/dist/src/registry.d.ts +12 -11
  385. package/dist/src/registry.js +16 -6
  386. package/dist/src/registry.test.js +2 -2
  387. package/dist/src/runtime.d.ts +2 -1
  388. package/dist/src/runtime.js +1 -1
  389. package/dist/src/serialization.d.ts +2 -2
  390. package/dist/src/serialization.js +4 -6
  391. package/dist/src/serialization.test.js +17 -0
  392. package/dist/src/types.d.ts +17 -0
  393. package/dist/src/validate.js +15 -11
  394. package/dist/src/validate.test.d.ts +9 -0
  395. package/dist/src/validate.test.js +90 -0
  396. package/package.json +1 -1
  397. package/scripts/fetch-adapters.js +1 -1
  398. package/scripts/typed-error-lint-baseline.json +5 -77
  399. package/clis/ctrip/search.test.js +0 -64
  400. package/clis/gov-policy/commands.test.js +0 -27
  401. package/clis/linux-do/category.js +0 -37
  402. package/clis/linux-do/hot.js +0 -26
  403. package/clis/linux-do/latest.js +0 -19
  404. package/clis/pixiv/test-utils.js +0 -23
  405. package/clis/toutiao/articles.test.js +0 -30
  406. package/dist/src/analysis.d.ts +0 -40
  407. package/dist/src/analysis.js +0 -172
@@ -96,6 +96,90 @@ describe('createProgram root help descriptions', () => {
96
96
  registry.set(key, value);
97
97
  }
98
98
  });
99
+ it('groups adapters into App / Site buckets by domain field', () => {
100
+ const registry = getRegistry();
101
+ const snapshot = new Map(registry);
102
+ registry.clear();
103
+ try {
104
+ cli({
105
+ site: 'bilibili',
106
+ name: 'hot',
107
+ access: 'read',
108
+ description: 'Bilibili hot videos',
109
+ domain: 'www.bilibili.com',
110
+ strategy: Strategy.PUBLIC,
111
+ browser: false,
112
+ });
113
+ cli({
114
+ site: 'chatwise',
115
+ name: 'ask',
116
+ access: 'write',
117
+ description: 'Ask Chatwise desktop app',
118
+ domain: 'localhost',
119
+ strategy: Strategy.UI,
120
+ browser: true,
121
+ });
122
+ const program = createProgram('', '');
123
+ const help = program.helpInformation();
124
+ // Two separate sections, each with own count
125
+ expect(help).toContain('App adapters (1):');
126
+ expect(help).toMatch(/App adapters \(1\):\n {2}chatwise/);
127
+ expect(help).toContain('Site adapters (1):');
128
+ expect(help).toMatch(/Site adapters \(1\):\n {2}bilibili/);
129
+ // App adapters appear before Site adapters (External CLIs are absent here)
130
+ expect(help.indexOf('App adapters')).toBeLessThan(help.indexOf('Site adapters'));
131
+ }
132
+ finally {
133
+ registry.clear();
134
+ for (const [key, value] of snapshot)
135
+ registry.set(key, value);
136
+ }
137
+ });
138
+ it('exposes external_clis / app_adapters / site_adapters in structured help', () => {
139
+ const registry = getRegistry();
140
+ const snapshot = new Map(registry);
141
+ const argv = process.argv;
142
+ registry.clear();
143
+ try {
144
+ cli({
145
+ site: 'bilibili',
146
+ name: 'hot',
147
+ access: 'read',
148
+ description: 'Bilibili hot videos',
149
+ domain: 'www.bilibili.com',
150
+ strategy: Strategy.PUBLIC,
151
+ browser: false,
152
+ });
153
+ cli({
154
+ site: 'chatwise',
155
+ name: 'ask',
156
+ access: 'write',
157
+ description: 'Ask Chatwise desktop app',
158
+ domain: 'localhost',
159
+ strategy: Strategy.UI,
160
+ browser: true,
161
+ });
162
+ const program = createProgram('', '');
163
+ process.argv = ['node', 'opencli', '--help', '-f', 'yaml'];
164
+ const data = yaml.load(program.helpInformation());
165
+ expect(data.app_adapters.count).toBe(1);
166
+ expect(data.app_adapters.apps).toEqual(['chatwise']);
167
+ expect(data.site_adapters.count).toBe(1);
168
+ expect(data.site_adapters.sites).toEqual(['bilibili']);
169
+ expect(data.external_clis.count).toBeGreaterThanOrEqual(0);
170
+ expect(Array.isArray(data.external_clis.clis)).toBe(true);
171
+ // Adapters must NOT leak into the core commands list
172
+ const commandNames = data.commands.map((cmd) => cmd.name);
173
+ expect(commandNames).not.toContain('bilibili');
174
+ expect(commandNames).not.toContain('chatwise');
175
+ }
176
+ finally {
177
+ process.argv = argv;
178
+ registry.clear();
179
+ for (const [key, value] of snapshot)
180
+ registry.set(key, value);
181
+ }
182
+ });
99
183
  it('renders root structured help with built-ins and site adapter names', () => {
100
184
  const registry = getRegistry();
101
185
  const snapshot = new Map(registry);
@@ -1624,6 +1708,16 @@ describe('browser click/type commands', () => {
1624
1708
  evaluate: vi.fn().mockResolvedValue(false),
1625
1709
  click: vi.fn().mockResolvedValue({ matches_n: 1, match_level: 'exact' }),
1626
1710
  typeText: vi.fn().mockResolvedValue({ matches_n: 1, match_level: 'exact' }),
1711
+ fillText: vi.fn().mockResolvedValue({
1712
+ filled: true,
1713
+ verified: true,
1714
+ expected: '',
1715
+ actual: '',
1716
+ length: 0,
1717
+ matches_n: 1,
1718
+ match_level: 'exact',
1719
+ mode: 'input',
1720
+ }),
1627
1721
  wait: vi.fn().mockResolvedValue(undefined),
1628
1722
  }));
1629
1723
  it('emits {clicked, target, matches_n, match_level} on success', async () => {
@@ -1721,6 +1815,62 @@ describe('browser click/type commands', () => {
1721
1815
  expect(browserState.page.click).toHaveBeenCalledWith('.field', { nth: 3 });
1722
1816
  expect(browserState.page.typeText).toHaveBeenCalledWith('.field', 'x', { nth: 3 });
1723
1817
  });
1818
+ it('fill: delegates exact raw text to page.fillText and emits verification details', async () => {
1819
+ browserState.page.fillText.mockResolvedValueOnce({
1820
+ filled: true,
1821
+ verified: true,
1822
+ expected: 'line1\\n/ / raw',
1823
+ actual: 'line1\\n/ / raw',
1824
+ length: 14,
1825
+ matches_n: 1,
1826
+ match_level: 'exact',
1827
+ mode: 'textarea',
1828
+ });
1829
+ const program = createProgram('', '');
1830
+ await program.parseAsync(['node', 'opencli', 'browser', 'fill', '#msg', 'line1\\n/ / raw']);
1831
+ expect(browserState.page.fillText).toHaveBeenCalledWith('#msg', 'line1\\n/ / raw', {});
1832
+ expect(lastJsonLog()).toEqual({
1833
+ filled: true,
1834
+ verified: true,
1835
+ target: '#msg',
1836
+ text: 'line1\\n/ / raw',
1837
+ actual: 'line1\\n/ / raw',
1838
+ length: 14,
1839
+ matches_n: 1,
1840
+ match_level: 'exact',
1841
+ mode: 'textarea',
1842
+ });
1843
+ expect(process.exitCode).toBeUndefined();
1844
+ });
1845
+ it('fill: sets a non-zero exit code when verification fails', async () => {
1846
+ browserState.page.fillText.mockResolvedValueOnce({
1847
+ filled: true,
1848
+ verified: false,
1849
+ expected: 'expected',
1850
+ actual: 'actual',
1851
+ length: 6,
1852
+ matches_n: 1,
1853
+ match_level: 'exact',
1854
+ });
1855
+ const program = createProgram('', '');
1856
+ await program.parseAsync(['node', 'opencli', 'browser', 'fill', '#msg', 'expected']);
1857
+ expect(lastJsonLog()).toEqual({
1858
+ filled: true,
1859
+ verified: false,
1860
+ target: '#msg',
1861
+ text: 'expected',
1862
+ actual: 'actual',
1863
+ length: 6,
1864
+ matches_n: 1,
1865
+ match_level: 'exact',
1866
+ });
1867
+ expect(process.exitCode).toBeDefined();
1868
+ });
1869
+ it('fill: forwards --nth to page.fillText', async () => {
1870
+ const program = createProgram('', '');
1871
+ await program.parseAsync(['node', 'opencli', 'browser', 'fill', '.field', 'x', '--nth', '2']);
1872
+ expect(browserState.page.fillText).toHaveBeenCalledWith('.field', 'x', { nth: 2 });
1873
+ });
1724
1874
  });
1725
1875
  describe('browser select command', () => {
1726
1876
  const { lastJsonLog } = installSelectorFirstTestHarness('select', () => ({
@@ -89,11 +89,6 @@ export function registerCommandToProgram(siteCmd, cmd) {
89
89
  const formatExplicit = subCmd.getOptionValueSource('format') === 'cli';
90
90
  if (verbose)
91
91
  process.env.OPENCLI_VERBOSE = '1';
92
- if (cmd.deprecated) {
93
- const message = typeof cmd.deprecated === 'string' ? cmd.deprecated : `${fullName(cmd)} is deprecated.`;
94
- const replacement = cmd.replacedBy ? ` Use ${cmd.replacedBy} instead.` : '';
95
- log.warn(`Deprecated: ${message}${replacement}`);
96
- }
97
92
  const globals = typeof subCmd.optsWithGlobals === 'function' ? subCmd.optsWithGlobals() : {};
98
93
  const result = await executeCommand(cmd, kwargs, verbose, {
99
94
  prepared: true,
@@ -279,7 +279,6 @@ describe('commanderAdapter error envelope output', () => {
279
279
  expect(output).toContain('xsec_token');
280
280
  expect(output).toContain('--trace=retain-on-failure');
281
281
  expect(output).toContain('opencli xiaohongshu note --trace retain-on-failure');
282
- expect(output).not.toContain('OPENCLI_DIAGNOSTIC');
283
282
  stderrSpy.mockRestore();
284
283
  });
285
284
  it('outputs YAML error envelope for selector errors', async () => {
@@ -131,12 +131,11 @@ async function loadFromManifest(manifestPath, clisDir) {
131
131
  browser: entry.browser,
132
132
  args: entry.args ?? [],
133
133
  columns: entry.columns,
134
+ defaultFormat: entry.defaultFormat,
134
135
  pipeline: entry.pipeline,
135
- timeoutSeconds: entry.timeout,
136
136
  source: entry.sourceFile ? path.resolve(clisDir, entry.sourceFile) : modulePath,
137
- deprecated: entry.deprecated,
138
- replacedBy: entry.replacedBy,
139
137
  navigateBefore: entry.navigateBefore,
138
+ browserSession: entry.browserSession,
140
139
  _lazy: true,
141
140
  _modulePath: modulePath,
142
141
  };
@@ -170,7 +169,6 @@ async function discoverClisFromFs(dir) {
170
169
  await Promise.all(files.map(async (file) => {
171
170
  const filePath = path.join(siteDir, file);
172
171
  if (file.endsWith('.yaml') || file.endsWith('.yml')) {
173
- log.warn(`Ignoring YAML adapter ${filePath} — YAML format is no longer supported. Convert to JavaScript using cli() from '@jackwener/opencli/registry'.`);
174
172
  return;
175
173
  }
176
174
  if (file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.endsWith('.test.ts')) {
@@ -218,7 +216,6 @@ async function discoverPluginDir(dir, site) {
218
216
  await Promise.all(files.map(async (file) => {
219
217
  const filePath = path.join(dir, file);
220
218
  if (file.endsWith('.yaml') || file.endsWith('.yml')) {
221
- log.warn(`Ignoring YAML plugin ${filePath} — YAML format is no longer supported. Convert to JavaScript using cli() from '@jackwener/opencli/registry'.`);
222
219
  return;
223
220
  }
224
221
  if (file.endsWith('.js') && !file.endsWith('.d.js')) {
@@ -73,7 +73,7 @@ export class AuthRequiredError extends CliError {
73
73
  }
74
74
  export class TimeoutError extends CliError {
75
75
  constructor(label, seconds, hint) {
76
- super('TIMEOUT', `${label} timed out after ${seconds}s`, hint ?? 'Try again, or increase timeout with OPENCLI_BROWSER_COMMAND_TIMEOUT env var', EXIT_CODES.TEMPFAIL);
76
+ super('TIMEOUT', `${label} timed out after ${seconds}s`, hint ?? 'Try again, or increase timeout with --timeout <seconds> (or OPENCLI_BROWSER_COMMAND_TIMEOUT for the global default)', EXIT_CODES.TEMPFAIL);
77
77
  }
78
78
  }
79
79
  export class ArgumentError extends CliError {
@@ -4,7 +4,7 @@
4
4
  * This is the single entry point for executing any CLI command. It handles:
5
5
  * 1. Argument validation and coercion
6
6
  * 2. Browser session lifecycle (if needed)
7
- * 3. Domain pre-navigation for cookie/header strategies
7
+ * 3. Domain pre-navigation for cookie strategies
8
8
  * 4. Timeout enforcement
9
9
  * 5. Lazy-loading of TS modules from manifest
10
10
  * 6. Lifecycle hooks (onBeforeExecute / onAfterExecute)
@@ -4,13 +4,14 @@
4
4
  * This is the single entry point for executing any CLI command. It handles:
5
5
  * 1. Argument validation and coercion
6
6
  * 2. Browser session lifecycle (if needed)
7
- * 3. Domain pre-navigation for cookie/header strategies
7
+ * 3. Domain pre-navigation for cookie strategies
8
8
  * 4. Timeout enforcement
9
9
  * 5. Lazy-loading of TS modules from manifest
10
10
  * 6. Lifecycle hooks (onBeforeExecute / onAfterExecute)
11
11
  */
12
12
  import { getRegistry, fullName, } from './registry.js';
13
13
  import { pathToFileURL } from 'node:url';
14
+ import * as crypto from 'node:crypto';
14
15
  import * as fs from 'node:fs';
15
16
  import * as os from 'node:os';
16
17
  import { executePipeline } from './pipeline/index.js';
@@ -28,6 +29,7 @@ const _loadedModules = new Map();
28
29
  /** Track mtime of loaded user adapter files for hot-reload in daemon mode. */
29
30
  const _moduleMtimes = new Map();
30
31
  const _userClisDir = `${os.homedir()}/.opencli/clis/`;
32
+ const INTERACTIVE_BROWSER_IDLE_TIMEOUT_SECONDS = 600;
31
33
  function normalizeTraceMode(raw) {
32
34
  if (raw === undefined || raw === null || raw === '' || raw === 'off')
33
35
  return 'off';
@@ -138,14 +140,37 @@ function resolvePreNav(cmd) {
138
140
  // strategy → navigateBefore expansion already happened in normalizeCommand().
139
141
  return null;
140
142
  }
141
- function ensureRequiredEnv(cmd) {
142
- const missing = (cmd.requiredEnv ?? []).find(({ name }) => {
143
- const value = process.env[name];
144
- return value === undefined || value === null || value === '';
145
- });
146
- if (!missing)
147
- return;
148
- throw new CommandExecutionError(`Command ${fullName(cmd)} requires environment variable ${missing.name}.`, missing.help ?? `Set ${missing.name} before running ${fullName(cmd)}.`);
143
+ function urlMatchesDomain(url, domain) {
144
+ if (!url || !domain)
145
+ return false;
146
+ try {
147
+ const hostname = new URL(url).hostname;
148
+ return hostname === domain || hostname.endsWith(`.${domain}`);
149
+ }
150
+ catch {
151
+ return false;
152
+ }
153
+ }
154
+ function isDomainRootPreNav(preNavUrl, domain) {
155
+ if (!domain)
156
+ return false;
157
+ try {
158
+ const parsed = new URL(preNavUrl);
159
+ const hostnameMatches = parsed.hostname === domain || parsed.hostname.endsWith(`.${domain}`);
160
+ const rootPath = parsed.pathname === '' || parsed.pathname === '/';
161
+ return hostnameMatches && rootPath && parsed.search === '' && parsed.hash === '';
162
+ }
163
+ catch {
164
+ return false;
165
+ }
166
+ }
167
+ async function shouldRunPreNav(cmd, page, reuse, preNavUrl) {
168
+ if (reuse !== 'site' || !cmd.domain)
169
+ return true;
170
+ if (!isDomainRootPreNav(preNavUrl, cmd.domain))
171
+ return true;
172
+ const currentUrl = await page.getCurrentUrl?.().catch(() => null);
173
+ return !urlMatchesDomain(currentUrl, cmd.domain);
149
174
  }
150
175
  export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
151
176
  let kwargs;
@@ -157,6 +182,7 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
157
182
  throw err;
158
183
  throw new ArgumentError(getErrorMessage(err));
159
184
  }
185
+ const userTimeoutSec = readUserTimeoutSeconds(cmd, kwargs);
160
186
  const traceMode = normalizeTraceMode(opts.trace);
161
187
  const hookCtx = {
162
188
  command: fullName(cmd),
@@ -183,17 +209,19 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
183
209
  cdpEndpoint = await resolveElectronEndpoint(cmd.site);
184
210
  }
185
211
  }
186
- ensureRequiredEnv(cmd);
187
212
  const BrowserFactory = getBrowserFactory(cmd.site);
188
213
  const contextId = resolveProfileContextId(opts.profile);
189
214
  const internal = cmd;
215
+ const browserReuse = resolveBrowserSessionReuse(cmd);
216
+ const workspace = resolveBrowserWorkspace(cmd, browserReuse);
217
+ const idleTimeout = browserReuse === 'site' ? INTERACTIVE_BROWSER_IDLE_TIMEOUT_SECONDS : undefined;
190
218
  result = await browserSession(BrowserFactory, async (page) => {
191
219
  const observation = traceMode === 'off'
192
220
  ? null
193
221
  : new ObservationSession({
194
222
  scope: {
195
223
  contextId,
196
- workspace: `site:${cmd.site}`,
224
+ workspace,
197
225
  target: page.getActivePage?.(),
198
226
  site: cmd.site,
199
227
  command: fullName(cmd),
@@ -210,7 +238,7 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
210
238
  await page.startNetworkCapture?.().catch(() => false);
211
239
  }
212
240
  const preNavUrl = resolvePreNav(cmd);
213
- if (preNavUrl) {
241
+ if (preNavUrl && await shouldRunPreNav(cmd, page, browserReuse, preNavUrl)) {
214
242
  observation?.record({
215
243
  stream: 'action',
216
244
  name: 'pre_navigate',
@@ -253,12 +281,15 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
253
281
  throw wrapped;
254
282
  }
255
283
  }
256
- // --live / OPENCLI_LIVE=1 keeps the automation window open after the
257
- // command finishes, so agents (or humans) can inspect the page state.
258
- const keepOpen = process.env.OPENCLI_LIVE === '1' || process.env.OPENCLI_LIVE === 'true';
284
+ // --live / OPENCLI_LIVE=1 keeps the current automation tab lease after
285
+ // the command finishes, so agents (or humans) can inspect the page state.
286
+ const keepOpen = browserReuse !== 'none' || process.env.OPENCLI_LIVE === '1' || process.env.OPENCLI_LIVE === 'true';
259
287
  try {
288
+ const browserTimeout = userTimeoutSec !== null
289
+ ? userTimeoutSec + RUNTIME_TIMEOUT_PADDING_SECONDS
290
+ : DEFAULT_BROWSER_COMMAND_TIMEOUT;
260
291
  const result = await runWithTimeout(runCommand(cmd, page, kwargs, debug), {
261
- timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT,
292
+ timeout: browserTimeout,
262
293
  label: fullName(cmd),
263
294
  });
264
295
  observation?.record({
@@ -270,8 +301,9 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
270
301
  await collectObservationEvidence(observation, page).catch(() => { });
271
302
  exportTraceArtifact(observation, 'success', undefined, opts.onTraceExport);
272
303
  }
273
- // Adapter commands are one-shot — close the automation window immediately
274
- // instead of waiting for the 30s idle timeout.
304
+ // Adapter commands are one-shot — release the current tab lease immediately
305
+ // instead of waiting for the 30s idle timeout. The automation container
306
+ // window stays open for reuse.
275
307
  if (!keepOpen)
276
308
  await page.closeWindow?.().catch(() => { });
277
309
  return result;
@@ -294,23 +326,26 @@ export async function executeCommand(cmd, rawKwargs, debug = false, opts = {}) {
294
326
  exportTraceArtifact(observation, 'failure', err, opts.onTraceExport);
295
327
  }
296
328
  }
297
- // Close the automation window on failure too — without this, the window
298
- // lingers until the extension's idle timer fires (unreliable on Windows
299
- // where MV3 service workers may be suspended before setTimeout triggers).
329
+ // Release the tab lease on failure too — without this, the lease lingers
330
+ // until the extension's idle timer fires (unreliable on Windows where
331
+ // MV3 service workers may be suspended before setTimeout triggers).
300
332
  if (!keepOpen)
301
333
  await page.closeWindow?.().catch(() => { });
302
334
  throw err;
303
335
  }
304
- }, { workspace: `site:${cmd.site}:${crypto.randomUUID()}`, cdpEndpoint, contextId });
336
+ }, { workspace, cdpEndpoint, contextId, idleTimeout });
305
337
  }
306
338
  else {
307
- // Non-browser commands: apply timeout only when explicitly configured.
308
- const timeout = cmd.timeoutSeconds;
309
- if (timeout !== undefined && timeout > 0) {
339
+ // Non-browser commands: enforce a timeout only when the command exposes
340
+ // a `--timeout` arg (and the resolved value is positive). Without that
341
+ // arg there is no meaningful default non-browser cmds are diverse
342
+ // enough that a hard cap would do more harm than good.
343
+ if (userTimeoutSec !== null) {
344
+ const ceiling = userTimeoutSec + RUNTIME_TIMEOUT_PADDING_SECONDS;
310
345
  result = await runWithTimeout(runCommand(cmd, null, kwargs, debug), {
311
- timeout,
346
+ timeout: ceiling,
312
347
  label: fullName(cmd),
313
- hint: `Increase the adapter's timeoutSeconds setting (currently ${timeout}s)`,
348
+ hint: `Pass a higher --timeout value (currently ${userTimeoutSec}s)`,
314
349
  });
315
350
  }
316
351
  else {
@@ -401,3 +436,52 @@ export function prepareCommandArgs(cmd, rawKwargs) {
401
436
  cmd.validateArgs?.(kwargs);
402
437
  return kwargs;
403
438
  }
439
+ /**
440
+ * Runtime ceiling padding (seconds) added on top of the user's `--timeout`.
441
+ * The adapter's polling loop typically uses the full user value; the padding
442
+ * gives us room for the adapter to return + closeWindow + trace export before
443
+ * the runtime kills the Promise.
444
+ */
445
+ const RUNTIME_TIMEOUT_PADDING_SECONDS = 30;
446
+ function readEnvBrowserSessionReuse() {
447
+ const raw = process.env.OPENCLI_BROWSER_REUSE;
448
+ if (raw === undefined || raw === '')
449
+ return null;
450
+ if (raw === 'none' || raw === 'site')
451
+ return raw;
452
+ throw new ArgumentError(`--reuse must be one of: none, site. Received: "${raw}"`);
453
+ }
454
+ function resolveBrowserSessionReuse(cmd) {
455
+ return readEnvBrowserSessionReuse() ?? cmd.browserSession?.reuse ?? 'none';
456
+ }
457
+ function resolveBrowserWorkspace(cmd, reuse) {
458
+ if (reuse === 'site')
459
+ return `site:${cmd.site}`;
460
+ return `site:${cmd.site}:${crypto.randomUUID()}`;
461
+ }
462
+ /**
463
+ * Resolve the user-controllable `--timeout` arg, in seconds.
464
+ *
465
+ * Convention: a command opts into runtime-enforced timeouts by declaring an
466
+ * arg named `timeout`. The arg's `default` flows through `prepareCommandArgs`
467
+ * into `kwargs.timeout`, so by the time runtime enforcement runs, the value
468
+ * is the merged user-supplied-or-default seconds.
469
+ *
470
+ * Returns the parsed positive integer (seconds), or null if the command does
471
+ * not expose a `timeout` arg. Declaring `timeout` opts into runtime timeout
472
+ * enforcement, so invalid values must fail upfront instead of silently
473
+ * disabling the runtime ceiling.
474
+ */
475
+ function readUserTimeoutSeconds(cmd, kwargs) {
476
+ if (!cmd.args.some(a => a.name === 'timeout'))
477
+ return null;
478
+ const raw = kwargs.timeout;
479
+ if (raw === undefined || raw === null || raw === '') {
480
+ throw new ArgumentError(`Argument "timeout" must be a positive integer. Received: "${String(raw)}"`);
481
+ }
482
+ const parsed = Number(raw);
483
+ if (!Number.isInteger(parsed) || parsed <= 0) {
484
+ throw new ArgumentError(`Argument "timeout" must be a positive integer. Received: "${String(raw)}"`);
485
+ }
486
+ return parsed;
487
+ }