@jackwener/opencli 1.7.12 → 1.7.14

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 (419) hide show
  1. package/README.md +8 -7
  2. package/README.zh-CN.md +9 -8
  3. package/cli-manifest.json +12128 -6665
  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/quote.js +139 -0
  298. package/clis/twitter/quote.test.js +106 -0
  299. package/clis/twitter/reply-dm.js +1 -1
  300. package/clis/twitter/reply.test.js +1 -29
  301. package/clis/twitter/retweet.js +99 -0
  302. package/clis/twitter/retweet.test.js +69 -0
  303. package/clis/twitter/shared.js +38 -0
  304. package/clis/twitter/shared.test.js +28 -1
  305. package/clis/twitter/unlike.js +87 -0
  306. package/clis/twitter/unlike.test.js +72 -0
  307. package/clis/twitter/unretweet.js +99 -0
  308. package/clis/twitter/unretweet.test.js +69 -0
  309. package/clis/uisdc/news.js +105 -0
  310. package/clis/uisdc/news.test.js +66 -0
  311. package/clis/wanfang/search.js +0 -1
  312. package/clis/web/read.js +47 -17
  313. package/clis/web/read.test.js +101 -1
  314. package/clis/weixin/create-draft.js +1 -1
  315. package/clis/weixin/drafts.js +1 -1
  316. package/clis/weixin/drafts.test.js +5 -1
  317. package/clis/weixin/search.js +157 -0
  318. package/clis/weixin/search.test.js +227 -0
  319. package/clis/wikidata/entity.js +60 -0
  320. package/clis/wikidata/search.js +50 -0
  321. package/clis/wikidata/utils.js +117 -0
  322. package/clis/wikidata/wikidata.test.js +83 -0
  323. package/clis/wikipedia/page.js +95 -0
  324. package/clis/wttr/current.js +63 -0
  325. package/clis/wttr/forecast.js +71 -0
  326. package/clis/wttr/utils.js +50 -0
  327. package/clis/wttr/wttr.test.js +84 -0
  328. package/clis/xianyu/chat.js +16 -4
  329. package/clis/xianyu/chat.test.js +64 -0
  330. package/clis/xianyu/publish.js +485 -0
  331. package/clis/xianyu/publish.test.js +220 -0
  332. package/clis/xiaoe/catalog.js +105 -40
  333. package/clis/xiaoe/content.js +164 -29
  334. package/clis/xiaoe/courses.js +86 -29
  335. package/clis/xiaoe/xiaoe.test.js +486 -0
  336. package/clis/xiaohongshu/creator-notes-summary.js +1 -1
  337. package/clis/xiaohongshu/publish.js +16 -3
  338. package/clis/xiaohongshu/publish.test.js +46 -1
  339. package/clis/youtube/transcript.js +13 -19
  340. package/clis/youtube/transcript.test.js +17 -0
  341. package/clis/yuanbao/ask.js +17 -66
  342. package/clis/yuanbao/ask.test.js +5 -5
  343. package/clis/yuanbao/detail.js +65 -0
  344. package/clis/yuanbao/history.js +51 -0
  345. package/clis/yuanbao/new.js +1 -0
  346. package/clis/yuanbao/read.js +38 -0
  347. package/clis/yuanbao/send.js +57 -0
  348. package/clis/yuanbao/shared.js +297 -5
  349. package/clis/yuanbao/shared.test.js +80 -0
  350. package/clis/yuanbao/status.js +44 -0
  351. package/clis/zlibrary/commands.test.js +1 -11
  352. package/dist/src/browser/base-page.d.ts +9 -0
  353. package/dist/src/browser/base-page.js +44 -1
  354. package/dist/src/browser/base-page.test.js +66 -0
  355. package/dist/src/browser/bridge.js +47 -45
  356. package/dist/src/browser/cdp.d.ts +1 -0
  357. package/dist/src/browser/cdp.js +51 -9
  358. package/dist/src/browser/daemon-client.d.ts +4 -0
  359. package/dist/src/browser/errors.js +1 -1
  360. package/dist/src/browser/page.d.ts +1 -1
  361. package/dist/src/browser/page.js +3 -1
  362. package/dist/src/browser/page.test.js +29 -0
  363. package/dist/src/browser/target-errors.d.ts +2 -1
  364. package/dist/src/browser/target-errors.js +1 -0
  365. package/dist/src/browser/target-resolver.d.ts +25 -0
  366. package/dist/src/browser/target-resolver.js +43 -0
  367. package/dist/src/browser.test.js +18 -0
  368. package/dist/src/build-manifest.js +9 -4
  369. package/dist/src/build-manifest.test.js +2 -8
  370. package/dist/src/capabilityRouting.d.ts +16 -1
  371. package/dist/src/capabilityRouting.js +24 -1
  372. package/dist/src/capabilityRouting.test.js +19 -1
  373. package/dist/src/cli.js +76 -11
  374. package/dist/src/cli.test.js +241 -1
  375. package/dist/src/commanderAdapter.js +23 -9
  376. package/dist/src/commanderAdapter.test.js +0 -1
  377. package/dist/src/discovery.js +2 -5
  378. package/dist/src/errors.js +1 -1
  379. package/dist/src/execution.d.ts +1 -1
  380. package/dist/src/execution.js +111 -27
  381. package/dist/src/execution.test.js +326 -17
  382. package/dist/src/help.d.ts +27 -2
  383. package/dist/src/help.js +196 -23
  384. package/dist/src/help.test.d.ts +1 -0
  385. package/dist/src/help.test.js +54 -0
  386. package/dist/src/main.js +14 -1
  387. package/dist/src/manifest-types.d.ts +5 -3
  388. package/dist/src/pipeline/executor.js +1 -1
  389. package/dist/src/pipeline/executor.test.js +8 -0
  390. package/dist/src/pipeline/registry.d.ts +9 -0
  391. package/dist/src/pipeline/registry.js +13 -1
  392. package/dist/src/pipeline/steps/browser.d.ts +1 -0
  393. package/dist/src/pipeline/steps/browser.js +10 -0
  394. package/dist/src/pipeline/steps/download.test.js +1 -0
  395. package/dist/src/registry-api.d.ts +1 -1
  396. package/dist/src/registry.d.ts +12 -11
  397. package/dist/src/registry.js +16 -6
  398. package/dist/src/registry.test.js +2 -2
  399. package/dist/src/runtime.d.ts +2 -1
  400. package/dist/src/runtime.js +1 -1
  401. package/dist/src/serialization.d.ts +2 -2
  402. package/dist/src/serialization.js +4 -6
  403. package/dist/src/serialization.test.js +17 -0
  404. package/dist/src/types.d.ts +17 -0
  405. package/dist/src/validate.js +15 -11
  406. package/dist/src/validate.test.d.ts +9 -0
  407. package/dist/src/validate.test.js +90 -0
  408. package/package.json +1 -1
  409. package/scripts/fetch-adapters.js +1 -1
  410. package/scripts/typed-error-lint-baseline.json +5 -77
  411. package/clis/ctrip/search.test.js +0 -64
  412. package/clis/gov-policy/commands.test.js +0 -27
  413. package/clis/linux-do/category.js +0 -37
  414. package/clis/linux-do/hot.js +0 -26
  415. package/clis/linux-do/latest.js +0 -19
  416. package/clis/pixiv/test-utils.js +0 -23
  417. package/clis/toutiao/articles.test.js +0 -30
  418. package/dist/src/analysis.d.ts +0 -40
  419. package/dist/src/analysis.js +0 -172
package/dist/src/help.js CHANGED
@@ -1,6 +1,33 @@
1
1
  import yaml from 'js-yaml';
2
2
  import { fullName } from './registry.js';
3
3
  import { formatCommandExample } from './serialization.js';
4
+ const COMMON_OPTIONS = [
5
+ {
6
+ flags: '-f, --format <fmt>',
7
+ name: 'format',
8
+ help: 'Output format: table, plain, json, yaml, md, csv',
9
+ default: 'table',
10
+ choices: ['table', 'plain', 'json', 'yaml', 'md', 'csv'],
11
+ },
12
+ {
13
+ flags: '--trace <mode>',
14
+ name: 'trace',
15
+ help: 'Trace capture: off, on, retain-on-failure',
16
+ default: 'off',
17
+ choices: ['off', 'on', 'retain-on-failure'],
18
+ },
19
+ {
20
+ flags: '-v, --verbose',
21
+ name: 'verbose',
22
+ help: 'Debug output',
23
+ default: false,
24
+ },
25
+ {
26
+ flags: '-h, --help',
27
+ name: 'help',
28
+ help: 'display help for command',
29
+ },
30
+ ];
4
31
  function normalizeStructuredHelpFormat(value) {
5
32
  const normalized = value?.toLowerCase();
6
33
  if (normalized === 'yaml' || normalized === 'yml')
@@ -50,18 +77,32 @@ export function wrapCommaList(items, opts = {}) {
50
77
  lines.push(line);
51
78
  return lines.join('\n');
52
79
  }
53
- export function formatRootAdapterHelpText(siteNames) {
54
- if (siteNames.length === 0)
55
- return '';
80
+ export function classifyAdapter(domain) {
81
+ if (!domain)
82
+ return 'site';
83
+ return domain.includes('.') ? 'site' : 'app';
84
+ }
85
+ function formatGroupSection(label, names) {
86
+ if (names.length === 0)
87
+ return [];
56
88
  return [
89
+ `${label} (${names.length}):`,
90
+ wrapCommaList(names),
57
91
  '',
58
- `Site adapters (${siteNames.length}):`,
59
- wrapCommaList(siteNames),
60
- '',
61
- "Run 'opencli list' for full command details, or 'opencli <site> --help' to inspect one site.",
62
- "Agent tip: use 'opencli <site> --help -f yaml' for structured commands, args, access, and examples.",
63
- '',
64
- ].join('\n');
92
+ ];
93
+ }
94
+ export function formatRootAdapterHelpText(groups) {
95
+ const total = groups.external.length + groups.apps.length + groups.sites.length;
96
+ if (total === 0)
97
+ return '';
98
+ const lines = [''];
99
+ lines.push(...formatGroupSection('External CLIs', groups.external));
100
+ lines.push(...formatGroupSection('App adapters', groups.apps));
101
+ lines.push(...formatGroupSection('Site adapters', groups.sites));
102
+ lines.push("Run 'opencli list' for full command details, or 'opencli <site> --help' to inspect one site.");
103
+ lines.push("Agent tip: use 'opencli <site> --help -f yaml' for all command args/options in one structured response.");
104
+ lines.push('');
105
+ return lines.join('\n');
65
106
  }
66
107
  function compactArg(arg) {
67
108
  return {
@@ -75,35 +116,82 @@ function compactArg(arg) {
75
116
  ...(arg.help ? { help: arg.help } : {}),
76
117
  };
77
118
  }
78
- function compactCommand(cmd, opts = {}) {
119
+ function compactCommonOption(option) {
120
+ return {
121
+ name: option.name,
122
+ flags: option.flags,
123
+ help: option.help,
124
+ ...('default' in option ? { default: option.default } : {}),
125
+ ...('choices' in option ? { choices: option.choices } : {}),
126
+ };
127
+ }
128
+ function positionals(cmd) {
129
+ return cmd.args.filter(arg => arg.positional);
130
+ }
131
+ function commandOptions(cmd) {
132
+ return cmd.args.filter(arg => !arg.positional);
133
+ }
134
+ function formatPositionals(args) {
135
+ return args
136
+ .map(arg => arg.required ? `<${arg.name}>` : `[${arg.name}]`)
137
+ .join(' ');
138
+ }
139
+ function formatCommandOptionTerm(arg) {
140
+ if (arg.required || arg.valueRequired)
141
+ return `--${arg.name} <value>`;
142
+ return `--${arg.name} [value]`;
143
+ }
144
+ export function formatCommandListTerm(cmd) {
145
+ const positionalText = formatPositionals(positionals(cmd));
146
+ const optionText = commandOptions(cmd).length > 0 ? ' [options]' : '';
147
+ return `${cmd.name}${positionalText ? ` ${positionalText}` : ''}${optionText}`;
148
+ }
149
+ function formatUsage(cmd) {
150
+ const positionalText = formatPositionals(positionals(cmd));
151
+ return `opencli ${cmd.site} ${cmd.name}${positionalText ? ` ${positionalText}` : ''} [options]`;
152
+ }
153
+ function compactCommand(cmd) {
79
154
  return {
80
155
  name: cmd.name,
81
156
  command: `opencli ${cmd.site} ${cmd.name}`,
157
+ usage: formatUsage(cmd),
82
158
  access: cmd.access,
83
159
  description: cmd.description,
160
+ browser: !!cmd.browser,
161
+ ...(cmd.domain ? { domain: cmd.domain } : {}),
84
162
  ...(cmd.aliases?.length ? { aliases: cmd.aliases } : {}),
85
- args: cmd.args.map(compactArg),
163
+ positionals: positionals(cmd).map(compactArg),
164
+ command_options: commandOptions(cmd).map(compactArg),
86
165
  example: formatCommandExample(cmd),
87
- ...(opts.includeColumns && cmd.columns?.length ? { columns: cmd.columns } : {}),
88
- ...(cmd.deprecated ? { deprecated: cmd.deprecated } : {}),
89
- ...(cmd.replacedBy ? { replacedBy: cmd.replacedBy } : {}),
166
+ ...(cmd.browserSession ? { browserSession: cmd.browserSession } : {}),
167
+ ...(cmd.defaultFormat ? { defaultFormat: cmd.defaultFormat } : {}),
168
+ ...(cmd.columns?.length ? { columns: cmd.columns } : {}),
90
169
  };
91
170
  }
92
- export function rootHelpData(program, siteNames) {
93
- const siteSet = new Set(siteNames);
171
+ export function rootHelpData(program, groups) {
172
+ const adapterNames = new Set([...groups.external, ...groups.apps, ...groups.sites]);
94
173
  const commands = program.commands
95
- .filter(command => !siteSet.has(command.name()))
174
+ .filter(command => !adapterNames.has(command.name()))
96
175
  .map(command => ({
97
176
  name: command.name(),
98
177
  description: command.description(),
99
178
  }));
179
+ const sortLocale = (a, b) => a.localeCompare(b);
100
180
  return {
101
181
  name: program.name(),
102
182
  description: program.description(),
103
183
  commands,
184
+ external_clis: {
185
+ count: groups.external.length,
186
+ clis: [...groups.external].sort(sortLocale),
187
+ },
188
+ app_adapters: {
189
+ count: groups.apps.length,
190
+ apps: [...groups.apps].sort(sortLocale),
191
+ },
104
192
  site_adapters: {
105
- count: siteNames.length,
106
- sites: [...siteNames].sort((a, b) => a.localeCompare(b)),
193
+ count: groups.sites.length,
194
+ sites: [...groups.sites].sort(sortLocale),
107
195
  },
108
196
  next: [
109
197
  'opencli <site> --help -f yaml',
@@ -119,6 +207,7 @@ export function siteHelpData(site, commands) {
119
207
  site,
120
208
  command_count: unique.length,
121
209
  commands: unique.map(cmd => compactCommand(cmd)),
210
+ common_options: COMMON_OPTIONS.map(compactCommonOption),
122
211
  next: [
123
212
  `opencli ${site} <command> --help -f yaml`,
124
213
  `opencli ${site} <command> -f yaml`,
@@ -128,10 +217,95 @@ export function siteHelpData(site, commands) {
128
217
  export function commandHelpData(cmd) {
129
218
  return {
130
219
  site: cmd.site,
131
- ...compactCommand(cmd, { includeColumns: true }),
220
+ ...compactCommand(cmd),
221
+ common_options: COMMON_OPTIONS.map(compactCommonOption),
132
222
  output_formats: ['table', 'plain', 'yaml', 'json', 'md', 'csv'],
133
223
  };
134
224
  }
225
+ function formatRows(rows) {
226
+ if (rows.length === 0)
227
+ return [];
228
+ const width = Math.min(Math.max(...rows.map(([left]) => left.length)), 34);
229
+ return rows.map(([left, right]) => ` ${left.padEnd(width + 2)}${right}`);
230
+ }
231
+ function formatArgHelp(arg) {
232
+ const parts = [];
233
+ if (arg.help)
234
+ parts.push(arg.help);
235
+ if (arg.default !== undefined)
236
+ parts.push(`default: ${arg.default}`);
237
+ if (arg.choices?.length)
238
+ parts.push(`choices: ${arg.choices.join(', ')}`);
239
+ return parts.join(' ');
240
+ }
241
+ export function formatCommonOptionsHelpText() {
242
+ const rows = COMMON_OPTIONS.map(option => {
243
+ const details = [option.help];
244
+ if ('default' in option)
245
+ details.push(`default: ${option.default}`);
246
+ if ('choices' in option)
247
+ details.push(`choices: ${option.choices.join(', ')}`);
248
+ return [option.flags, details.join(' ')];
249
+ });
250
+ return ['Common options:', ...formatRows(rows)].join('\n');
251
+ }
252
+ export function formatSiteHelpText(site, commands) {
253
+ const unique = [...new Map(commands.map(cmd => [fullName(cmd), cmd])).values()]
254
+ .sort((a, b) => a.name.localeCompare(b.name));
255
+ const lines = [
256
+ `Usage: opencli ${site} <command> [args] [options]`,
257
+ '',
258
+ wrapCommaList(unique.map(cmd => cmd.name), { indent: '' }),
259
+ '',
260
+ 'Commands:',
261
+ ...formatRows(unique.map(cmd => [formatCommandListTerm(cmd), formatSiteCommandDescription(cmd)])),
262
+ '',
263
+ formatCommonOptionsHelpText(),
264
+ '',
265
+ `Agent tip: use 'opencli ${site} --help -f yaml' to get all command args/options in one structured response.`,
266
+ '',
267
+ ];
268
+ return lines.join('\n');
269
+ }
270
+ export function formatCommandHelpText(cmd) {
271
+ const lines = [
272
+ `Usage: ${formatUsage(cmd)}`,
273
+ '',
274
+ cmd.description,
275
+ '',
276
+ ];
277
+ const positionalRows = positionals(cmd).map(arg => [
278
+ arg.name,
279
+ formatArgHelp(arg),
280
+ ]);
281
+ if (positionalRows.length) {
282
+ lines.push('Arguments:', ...formatRows(positionalRows), '');
283
+ }
284
+ const optionRows = commandOptions(cmd).map(arg => [
285
+ formatCommandOptionTerm(arg),
286
+ formatArgHelp(arg),
287
+ ]);
288
+ if (optionRows.length) {
289
+ lines.push('Command options:', ...formatRows(optionRows), '');
290
+ }
291
+ lines.push(formatCommonOptionsHelpText(), '');
292
+ const meta = [];
293
+ meta.push(`Access: ${cmd.access}`);
294
+ meta.push(`Browser: ${cmd.browser ? 'yes' : 'no'}`);
295
+ if (cmd.domain)
296
+ meta.push(`Domain: ${cmd.domain}`);
297
+ if (cmd.defaultFormat)
298
+ meta.push(`Default format: ${cmd.defaultFormat}`);
299
+ if (cmd.aliases?.length)
300
+ meta.push(`Aliases: ${cmd.aliases.join(', ')}`);
301
+ lines.push(meta.join(' | '));
302
+ lines.push(`Example: ${formatCommandExample(cmd)}`);
303
+ if (cmd.columns?.length)
304
+ lines.push(`Output columns: ${cmd.columns.join(', ')}`);
305
+ lines.push("Agent tip: use '--help -f yaml' for structured args/options.");
306
+ lines.push('');
307
+ return lines.join('\n');
308
+ }
135
309
  export function installStructuredHelp(command, data, textSuffix) {
136
310
  const original = command.helpInformation.bind(command);
137
311
  command.helpInformation = ((contextOptions) => {
@@ -144,6 +318,5 @@ export function installStructuredHelp(command, data, textSuffix) {
144
318
  }
145
319
  export function formatSiteCommandDescription(cmd) {
146
320
  const access = cmd.access === 'write' ? '[write]' : '[read]';
147
- const deprecatedSuffix = cmd.deprecated ? ' [deprecated]' : '';
148
- return `${access} ${cmd.description}${deprecatedSuffix}`;
321
+ return `${access} ${cmd.description}`;
149
322
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,54 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { classifyAdapter, formatRootAdapterHelpText } from './help.js';
3
+ describe('classifyAdapter', () => {
4
+ it('classifies DNS-style domains as site', () => {
5
+ expect(classifyAdapter('www.bilibili.com')).toBe('site');
6
+ expect(classifyAdapter('chatgpt.com')).toBe('site');
7
+ expect(classifyAdapter('claude.ai')).toBe('site');
8
+ expect(classifyAdapter('grok.com')).toBe('site');
9
+ });
10
+ it('classifies localhost as app (Electron / osascript desktop integrations)', () => {
11
+ expect(classifyAdapter('localhost')).toBe('app');
12
+ });
13
+ it('classifies non-DNS domain strings as app (e.g. literal "doubao-app")', () => {
14
+ expect(classifyAdapter('doubao-app')).toBe('app');
15
+ });
16
+ it('defaults missing domain to site (most adapters without explicit domain are public web scrapers)', () => {
17
+ expect(classifyAdapter(undefined)).toBe('site');
18
+ });
19
+ });
20
+ describe('formatRootAdapterHelpText', () => {
21
+ it('renders all three sections in External / App / Site order when populated', () => {
22
+ const text = formatRootAdapterHelpText({
23
+ external: ['gh', 'docker'],
24
+ apps: ['chatwise', 'codex'],
25
+ sites: ['bilibili'],
26
+ });
27
+ expect(text).toContain('External CLIs (2):');
28
+ expect(text).toContain('App adapters (2):');
29
+ expect(text).toContain('Site adapters (1):');
30
+ expect(text.indexOf('External CLIs')).toBeLessThan(text.indexOf('App adapters'));
31
+ expect(text.indexOf('App adapters')).toBeLessThan(text.indexOf('Site adapters'));
32
+ });
33
+ it('omits empty sections instead of rendering a (0) header', () => {
34
+ const text = formatRootAdapterHelpText({
35
+ external: [],
36
+ apps: [],
37
+ sites: ['bilibili'],
38
+ });
39
+ expect(text).not.toContain('External CLIs');
40
+ expect(text).not.toContain('App adapters');
41
+ expect(text).toContain('Site adapters (1):');
42
+ });
43
+ it('returns empty string when all groups are empty', () => {
44
+ expect(formatRootAdapterHelpText({ external: [], apps: [], sites: [] })).toBe('');
45
+ });
46
+ it('always renders the agent discovery hint when any section is populated', () => {
47
+ const text = formatRootAdapterHelpText({
48
+ external: [],
49
+ apps: [],
50
+ sites: ['bilibili'],
51
+ });
52
+ expect(text).toContain("'opencli <site> --help -f yaml'");
53
+ });
54
+ });
package/dist/src/main.js CHANGED
@@ -28,7 +28,7 @@ const __dirname = path.dirname(__filename);
28
28
  const BUILTIN_CLIS = path.join(findPackageRoot(__filename), 'clis');
29
29
  const USER_CLIS = path.join(os.homedir(), '.opencli', 'clis');
30
30
  // ── Session lifecycle flags ──────────────────────────────────────────────
31
- // `--live` / `--focus` are top-level-ish toggles that tweak the automation
31
+ // `--live` / `--focus` / `--reuse` are top-level-ish toggles that tweak the automation
32
32
  // window's lifecycle. We strip them from argv before Commander runs so they
33
33
  // can be placed anywhere and work on any subcommand (adapter or browser).
34
34
  {
@@ -42,6 +42,19 @@ const USER_CLIS = path.join(os.homedir(), '.opencli', 'clis');
42
42
  process.env.OPENCLI_WINDOW_FOCUSED = '1';
43
43
  process.argv.splice(focusIdx, 1);
44
44
  }
45
+ const reuseIdx = process.argv.findIndex(arg => arg === '--reuse' || arg.startsWith('--reuse='));
46
+ if (reuseIdx !== -1) {
47
+ const arg = process.argv[reuseIdx];
48
+ const value = arg.startsWith('--reuse=')
49
+ ? arg.slice('--reuse='.length)
50
+ : process.argv[reuseIdx + 1];
51
+ if (value !== 'none' && value !== 'site') {
52
+ process.stderr.write(`--reuse must be one of: none, site. Received: "${value ?? ''}"\n`);
53
+ process.exit(EXIT_CODES.USAGE_ERROR);
54
+ }
55
+ process.env.OPENCLI_BROWSER_REUSE = value;
56
+ process.argv.splice(reuseIdx, arg.startsWith('--reuse=') ? 1 : 2);
57
+ }
45
58
  }
46
59
  // ── Ultra-fast path: lightweight commands bypass full discovery ──────────
47
60
  // These are high-frequency or trivial paths that must not pay the startup tax.
@@ -28,9 +28,7 @@ export interface ManifestEntry {
28
28
  }>;
29
29
  columns?: string[];
30
30
  pipeline?: Record<string, unknown>[];
31
- timeout?: number;
32
- deprecated?: boolean | string;
33
- replacedBy?: string;
31
+ defaultFormat?: 'table' | 'plain' | 'json' | 'yaml' | 'yml' | 'md' | 'markdown' | 'csv';
34
32
  type: 'js';
35
33
  /** Relative path from clis/ dir, e.g. 'bilibili/search.js' */
36
34
  modulePath?: string;
@@ -38,4 +36,8 @@ export interface ManifestEntry {
38
36
  sourceFile?: string;
39
37
  /** Pre-navigation control — see CliCommand.navigateBefore */
40
38
  navigateBefore?: boolean | string;
39
+ /** Browser session lifecycle defaults — see CliCommand.browserSession */
40
+ browserSession?: {
41
+ reuse?: 'none' | 'site';
42
+ };
41
43
  }
@@ -32,7 +32,7 @@ export async function executePipeline(page, pipeline, ctx = {}) {
32
32
  }
33
33
  }
34
34
  catch (err) {
35
- // Attempt cleanup: close automation window on pipeline failure
35
+ // Attempt cleanup: release automation tab lease on pipeline failure.
36
36
  if (page?.closeWindow) {
37
37
  try {
38
38
  await page.closeWindow();
@@ -14,6 +14,7 @@ function createMockPage(overrides = {}) {
14
14
  snapshot: vi.fn().mockResolvedValue(''),
15
15
  click: vi.fn(),
16
16
  typeText: vi.fn(),
17
+ fillText: vi.fn(),
17
18
  pressKey: vi.fn(),
18
19
  getFormState: vi.fn().mockResolvedValue({}),
19
20
  wait: vi.fn(),
@@ -159,6 +160,13 @@ describe('executePipeline', () => {
159
160
  ]);
160
161
  expect(page.click).toHaveBeenCalledWith('5');
161
162
  });
163
+ it('fill step calls page.fillText with raw rendered text', async () => {
164
+ const page = createMockPage();
165
+ await executePipeline(page, [
166
+ { fill: { ref: '@5', text: 'line1\\n/ / ${{ args.tail }}' } },
167
+ ], { args: { tail: 'raw' } });
168
+ expect(page.fillText).toHaveBeenCalledWith('5', 'line1\\n/ / raw');
169
+ });
162
170
  it('navigate preserves existing data through pipeline', async () => {
163
171
  const page = createMockPage({
164
172
  evaluate: vi.fn().mockResolvedValue([{ a: 1 }]),
@@ -13,6 +13,15 @@ export type StepHandler<TData = unknown, TResult = unknown, TParams = unknown> =
13
13
  * Get a registered step handler by name.
14
14
  */
15
15
  export declare function getStep(name: string): StepHandler | undefined;
16
+ /**
17
+ * List all currently registered step names. Used by `validate.ts` to allowlist
18
+ * step names without maintaining a parallel hand-coded list.
19
+ *
20
+ * Note: this depends on registerStep() side effects below already having run.
21
+ * Importing this module triggers all core registrations at the bottom of the
22
+ * file, so the returned array reflects every core + plugin step at call time.
23
+ */
24
+ export declare function getRegisteredStepNames(): string[];
16
25
  /**
17
26
  * Register a new custom step handler for the YAML pipeline.
18
27
  */
@@ -3,7 +3,7 @@
3
3
  * Allows core and third-party plugins to register custom YAML operations.
4
4
  */
5
5
  // Import core steps
6
- import { stepNavigate, stepClick, stepType, stepWait, stepPress, stepSnapshot, stepEvaluate } from './steps/browser.js';
6
+ import { stepNavigate, stepClick, stepType, stepFill, stepWait, stepPress, stepSnapshot, stepEvaluate } from './steps/browser.js';
7
7
  import { stepFetch } from './steps/fetch.js';
8
8
  import { stepSelect, stepMap, stepFilter, stepSort, stepLimit } from './steps/transform.js';
9
9
  import { stepIntercept } from './steps/intercept.js';
@@ -16,6 +16,17 @@ const _stepRegistry = new Map();
16
16
  export function getStep(name) {
17
17
  return _stepRegistry.get(name);
18
18
  }
19
+ /**
20
+ * List all currently registered step names. Used by `validate.ts` to allowlist
21
+ * step names without maintaining a parallel hand-coded list.
22
+ *
23
+ * Note: this depends on registerStep() side effects below already having run.
24
+ * Importing this module triggers all core registrations at the bottom of the
25
+ * file, so the returned array reflects every core + plugin step at call time.
26
+ */
27
+ export function getRegisteredStepNames() {
28
+ return [..._stepRegistry.keys()];
29
+ }
19
30
  /**
20
31
  * Register a new custom step handler for the YAML pipeline.
21
32
  */
@@ -32,6 +43,7 @@ registerStep('evaluate', stepEvaluate);
32
43
  registerStep('snapshot', stepSnapshot);
33
44
  registerStep('click', stepClick);
34
45
  registerStep('type', stepType);
46
+ registerStep('fill', stepFill);
35
47
  registerStep('wait', stepWait);
36
48
  registerStep('press', stepPress);
37
49
  registerStep('map', stepMap);
@@ -6,6 +6,7 @@ import type { IPage } from '../../types.js';
6
6
  export declare function stepNavigate(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
7
7
  export declare function stepClick(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
8
8
  export declare function stepType(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
9
+ export declare function stepFill(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
9
10
  export declare function stepWait(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
10
11
  export declare function stepPress(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
11
12
  export declare function stepSnapshot(page: IPage | null, params: unknown, _data: unknown, _args: Record<string, unknown>): Promise<unknown>;
@@ -29,6 +29,16 @@ export async function stepType(page, params, data, args) {
29
29
  }
30
30
  return data;
31
31
  }
32
+ export async function stepFill(page, params, data, args) {
33
+ if (isRecord(params)) {
34
+ const ref = String(render(params.ref ?? '', { args, data })).replace(/^@/, '');
35
+ const text = String(render(params.text ?? '', { args, data }));
36
+ await page.fillText(ref, text);
37
+ if (params.submit)
38
+ await page.pressKey('Enter');
39
+ }
40
+ return data;
41
+ }
32
42
  export async function stepWait(page, params, data, args) {
33
43
  if (typeof params === 'number')
34
44
  await page.wait(params);
@@ -25,6 +25,7 @@ function createMockPage(getCookies) {
25
25
  snapshot: vi.fn().mockResolvedValue(''),
26
26
  click: vi.fn(),
27
27
  typeText: vi.fn(),
28
+ fillText: vi.fn(),
28
29
  pressKey: vi.fn(),
29
30
  scrollTo: vi.fn(),
30
31
  getFormState: vi.fn().mockResolvedValue({}),
@@ -7,7 +7,7 @@
7
7
  * plugins are dynamically imported during discoverPlugins().
8
8
  */
9
9
  export { cli, Strategy, getRegistry, fullName, registerCommand } from './registry.js';
10
- export type { CliCommand, Arg, CliOptions, CommandArgs } from './registry.js';
10
+ export type { CliCommand, Arg, CliOptions, CommandArgs, BrowserSessionOptions, BrowserSessionReuse } from './registry.js';
11
11
  export type { IPage } from './types.js';
12
12
  export { onStartup, onBeforeExecute, onAfterExecute } from './hooks.js';
13
13
  export type { HookFn, HookContext, HookName } from './hooks.js';
@@ -6,7 +6,6 @@ export declare enum Strategy {
6
6
  PUBLIC = "public",
7
7
  LOCAL = "local",
8
8
  COOKIE = "cookie",
9
- HEADER = "header",
10
9
  INTERCEPT = "intercept",
11
10
  UI = "ui"
12
11
  }
@@ -20,14 +19,20 @@ export interface Arg {
20
19
  help?: string;
21
20
  choices?: string[];
22
21
  }
23
- export interface RequiredEnv {
24
- name: string;
25
- help?: string;
26
- }
27
22
  export type CommandArgs = Record<string, any>;
28
23
  export type BrowserCommandFunc = (page: IPage, kwargs: CommandArgs, debug?: boolean) => Promise<unknown>;
29
24
  export type NonBrowserCommandFunc = (kwargs: CommandArgs, debug?: boolean) => Promise<unknown>;
30
25
  export type CommandAccess = 'read' | 'write';
26
+ export type BrowserSessionReuse = 'none' | 'site';
27
+ export interface BrowserSessionOptions {
28
+ /**
29
+ * Control whether browser-backed adapter commands reuse a stable tab lease.
30
+ *
31
+ * - `none`: one-shot workspace per command execution (default)
32
+ * - `site`: all commands for this site share `site:<site>` until idle expiry
33
+ */
34
+ reuse?: BrowserSessionReuse;
35
+ }
31
36
  interface BaseCliCommand {
32
37
  site: string;
33
38
  name: string;
@@ -41,16 +46,10 @@ interface BaseCliCommand {
41
46
  args: Arg[];
42
47
  columns?: string[];
43
48
  pipeline?: Record<string, unknown>[];
44
- timeoutSeconds?: number;
45
49
  /** Origin of this command: 'yaml', 'ts', or plugin name. */
46
50
  source?: string;
47
51
  footerExtra?: (kwargs: CommandArgs) => string | undefined;
48
- requiredEnv?: RequiredEnv[];
49
52
  validateArgs?: (kwargs: CommandArgs) => void;
50
- /** Deprecation note shown in help / execution warnings. */
51
- deprecated?: boolean | string;
52
- /** Preferred replacement command, if any. */
53
- replacedBy?: string;
54
53
  /**
55
54
  * Control pre-navigation and browser-session requirement.
56
55
  *
@@ -67,6 +66,8 @@ interface BaseCliCommand {
67
66
  * Adapter authors can set this explicitly to override the strategy-based default.
68
67
  */
69
68
  navigateBefore?: boolean | string;
69
+ /** Browser session lifecycle defaults for adapter commands. */
70
+ browserSession?: BrowserSessionOptions;
70
71
  /** Override the default CLI output format when the user does not pass -f/--format. */
71
72
  defaultFormat?: 'table' | 'plain' | 'json' | 'yaml' | 'yml' | 'md' | 'markdown' | 'csv';
72
73
  }
@@ -6,7 +6,6 @@ export var Strategy;
6
6
  Strategy["PUBLIC"] = "public";
7
7
  Strategy["LOCAL"] = "local";
8
8
  Strategy["COOKIE"] = "cookie";
9
- Strategy["HEADER"] = "header";
10
9
  Strategy["INTERCEPT"] = "intercept";
11
10
  Strategy["UI"] = "ui";
12
11
  })(Strategy || (Strategy = {}));
@@ -18,6 +17,7 @@ export function cli(opts) {
18
17
  aliases: opts.aliases,
19
18
  description: opts.description ?? '',
20
19
  access: opts.access,
20
+ example: opts.example,
21
21
  domain: opts.domain,
22
22
  strategy: opts.strategy,
23
23
  browser: opts.browser,
@@ -25,12 +25,9 @@ export function cli(opts) {
25
25
  columns: opts.columns,
26
26
  func: opts.func,
27
27
  pipeline: opts.pipeline,
28
- timeoutSeconds: opts.timeoutSeconds,
29
28
  footerExtra: opts.footerExtra,
30
- requiredEnv: opts.requiredEnv,
31
- deprecated: opts.deprecated,
32
- replacedBy: opts.replacedBy,
33
29
  navigateBefore: opts.navigateBefore,
30
+ browserSession: opts.browserSession,
34
31
  defaultFormat: opts.defaultFormat,
35
32
  };
36
33
  registerCommand(cmd);
@@ -60,11 +57,12 @@ export function strategyLabel(cmd) {
60
57
  */
61
58
  function normalizeCommand(cmd) {
62
59
  assertCommandAccess(cmd);
60
+ assertBrowserSessionOptions(cmd);
63
61
  const strategy = cmd.strategy ?? (cmd.browser === false ? Strategy.PUBLIC : Strategy.COOKIE);
64
62
  const browser = cmd.browser ?? (strategy !== Strategy.PUBLIC && strategy !== Strategy.LOCAL);
65
63
  let navigateBefore = cmd.navigateBefore;
66
64
  if (navigateBefore === undefined) {
67
- if ((strategy === Strategy.COOKIE || strategy === Strategy.HEADER) && cmd.domain) {
65
+ if (strategy === Strategy.COOKIE && cmd.domain) {
68
66
  navigateBefore = `https://${cmd.domain}`;
69
67
  }
70
68
  else if (strategy !== Strategy.PUBLIC && strategy !== Strategy.LOCAL) {
@@ -84,6 +82,18 @@ function assertCommandAccess(cmd) {
84
82
  const key = `${cmd.site}/${cmd.name}`;
85
83
  throw new Error(`Command ${key} must declare access: 'read' | 'write'`);
86
84
  }
85
+ function assertBrowserSessionOptions(cmd) {
86
+ if (cmd.browserSession === undefined)
87
+ return;
88
+ const key = `${cmd.site}/${cmd.name}`;
89
+ if (cmd.browserSession === null || typeof cmd.browserSession !== 'object' || Array.isArray(cmd.browserSession)) {
90
+ throw new Error(`Command ${key} browserSession must be an object`);
91
+ }
92
+ const reuse = cmd.browserSession.reuse;
93
+ if (reuse !== undefined && reuse !== 'none' && reuse !== 'site') {
94
+ throw new Error(`Command ${key} browserSession.reuse must be one of: none, site`);
95
+ }
96
+ }
87
97
  export function registerCommand(cmd) {
88
98
  const normalized = normalizeCommand(cmd);
89
99
  const canonicalKey = fullName(normalized);
@@ -121,12 +121,12 @@ describe('registerCommand', () => {
121
121
  name: 'direct-reg', access: 'read',
122
122
  description: 'directly registered',
123
123
  args: [],
124
- strategy: Strategy.HEADER,
124
+ strategy: Strategy.COOKIE,
125
125
  browser: true,
126
126
  };
127
127
  registerCommand(cmd);
128
128
  const reg = getRegistry();
129
- expect(reg.get('test-registry/direct-reg')?.strategy).toBe(Strategy.HEADER);
129
+ expect(reg.get('test-registry/direct-reg')?.strategy).toBe(Strategy.COOKIE);
130
130
  });
131
131
  });
132
132
  describe('normalizeCommand (via registerCommand)', () => {