@jackwener/opencli 1.7.11 → 1.7.12

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 (730) hide show
  1. package/README.zh-CN.md +2 -1
  2. package/cli-manifest.json +1417 -24
  3. package/clis/1688/assets.js +1 -0
  4. package/clis/1688/download.js +1 -0
  5. package/clis/1688/item.js +1 -0
  6. package/clis/1688/search.js +2 -1
  7. package/clis/1688/store.js +1 -0
  8. package/clis/36kr/article.js +1 -0
  9. package/clis/36kr/hot.js +1 -0
  10. package/clis/36kr/news.js +1 -0
  11. package/clis/36kr/search.js +1 -0
  12. package/clis/51job/company.js +1 -0
  13. package/clis/51job/detail.js +1 -0
  14. package/clis/51job/hot.js +1 -0
  15. package/clis/51job/search.js +1 -0
  16. package/clis/amazon/bestsellers.js +1 -0
  17. package/clis/amazon/discussion.js +1 -0
  18. package/clis/amazon/movers-shakers.js +1 -0
  19. package/clis/amazon/new-releases.js +1 -0
  20. package/clis/amazon/offer.js +1 -0
  21. package/clis/amazon/product.js +1 -0
  22. package/clis/amazon/rankings.js +1 -0
  23. package/clis/amazon/search.js +1 -0
  24. package/clis/antigravity/dump.js +1 -0
  25. package/clis/antigravity/extract-code.js +1 -0
  26. package/clis/antigravity/model.js +1 -0
  27. package/clis/antigravity/new.js +1 -0
  28. package/clis/antigravity/read.js +1 -0
  29. package/clis/antigravity/send.js +1 -0
  30. package/clis/antigravity/status.js +1 -0
  31. package/clis/antigravity/watch.js +1 -0
  32. package/clis/apple-podcasts/episodes.js +1 -0
  33. package/clis/apple-podcasts/search.js +1 -0
  34. package/clis/apple-podcasts/top.js +1 -0
  35. package/clis/arxiv/arxiv.test.js +112 -0
  36. package/clis/arxiv/paper.js +4 -3
  37. package/clis/arxiv/recent.js +33 -0
  38. package/clis/arxiv/search.js +19 -7
  39. package/clis/arxiv/utils.js +68 -5
  40. package/clis/baidu-scholar/search.js +1 -0
  41. package/clis/band/bands.js +1 -0
  42. package/clis/band/mentions.js +1 -0
  43. package/clis/band/post.js +1 -0
  44. package/clis/band/posts.js +1 -0
  45. package/clis/barchart/flow.js +1 -0
  46. package/clis/barchart/greeks.js +1 -0
  47. package/clis/barchart/options.js +1 -0
  48. package/clis/barchart/quote.js +1 -0
  49. package/clis/bbc/news.js +1 -0
  50. package/clis/bilibili/comments.js +1 -0
  51. package/clis/bilibili/download.js +1 -0
  52. package/clis/bilibili/dynamic.js +1 -0
  53. package/clis/bilibili/favorite.js +1 -0
  54. package/clis/bilibili/feed.js +2 -0
  55. package/clis/bilibili/following.js +1 -0
  56. package/clis/bilibili/history.js +1 -0
  57. package/clis/bilibili/hot.js +6 -1
  58. package/clis/bilibili/hot.test.js +17 -0
  59. package/clis/bilibili/me.js +1 -1
  60. package/clis/bilibili/ranking.js +1 -0
  61. package/clis/bilibili/search.js +1 -1
  62. package/clis/bilibili/subtitle.js +1 -0
  63. package/clis/bilibili/user-videos.js +1 -0
  64. package/clis/bilibili/video.js +1 -0
  65. package/clis/binance/asks.js +1 -0
  66. package/clis/binance/depth.js +1 -0
  67. package/clis/binance/gainers.js +1 -0
  68. package/clis/binance/klines.js +1 -0
  69. package/clis/binance/losers.js +1 -0
  70. package/clis/binance/pairs.js +1 -0
  71. package/clis/binance/price.js +1 -0
  72. package/clis/binance/prices.js +1 -0
  73. package/clis/binance/ticker.js +1 -0
  74. package/clis/binance/top.js +1 -0
  75. package/clis/binance/trades.js +1 -0
  76. package/clis/bloomberg/businessweek.js +1 -0
  77. package/clis/bloomberg/economics.js +1 -0
  78. package/clis/bloomberg/feeds.js +1 -0
  79. package/clis/bloomberg/industries.js +1 -0
  80. package/clis/bloomberg/main.js +1 -0
  81. package/clis/bloomberg/markets.js +1 -0
  82. package/clis/bloomberg/news.js +1 -0
  83. package/clis/bloomberg/opinions.js +1 -0
  84. package/clis/bloomberg/politics.js +1 -0
  85. package/clis/bloomberg/tech.js +1 -0
  86. package/clis/bluesky/feeds.js +1 -0
  87. package/clis/bluesky/followers.js +1 -0
  88. package/clis/bluesky/following.js +1 -0
  89. package/clis/bluesky/profile.js +1 -0
  90. package/clis/bluesky/search.js +1 -0
  91. package/clis/bluesky/starter-packs.js +1 -0
  92. package/clis/bluesky/thread.js +1 -0
  93. package/clis/bluesky/trending.js +1 -0
  94. package/clis/bluesky/user.js +3 -1
  95. package/clis/boss/batchgreet.js +1 -0
  96. package/clis/boss/chatlist.js +1 -0
  97. package/clis/boss/chatmsg.js +1 -0
  98. package/clis/boss/detail.js +1 -0
  99. package/clis/boss/exchange.js +1 -0
  100. package/clis/boss/greet.js +1 -0
  101. package/clis/boss/invite.js +1 -0
  102. package/clis/boss/joblist.js +1 -0
  103. package/clis/boss/mark.js +1 -0
  104. package/clis/boss/recommend.js +1 -0
  105. package/clis/boss/resume.js +1 -0
  106. package/clis/boss/search.js +1 -0
  107. package/clis/boss/send.js +1 -0
  108. package/clis/boss/stats.js +1 -0
  109. package/clis/chaoxing/assignments.js +1 -0
  110. package/clis/chaoxing/exams.js +1 -0
  111. package/clis/chatgpt/image.js +1 -0
  112. package/clis/chatgpt-app/ask.js +1 -0
  113. package/clis/chatgpt-app/model.js +1 -0
  114. package/clis/chatgpt-app/new.js +1 -0
  115. package/clis/chatgpt-app/read.js +1 -0
  116. package/clis/chatgpt-app/send.js +1 -0
  117. package/clis/chatgpt-app/status.js +1 -0
  118. package/clis/chatwise/ask.js +1 -0
  119. package/clis/chatwise/export.js +1 -0
  120. package/clis/chatwise/history.js +1 -0
  121. package/clis/chatwise/model.js +1 -0
  122. package/clis/chatwise/read.js +1 -0
  123. package/clis/chatwise/send.js +1 -0
  124. package/clis/claude/ask.js +1 -0
  125. package/clis/claude/detail.js +1 -0
  126. package/clis/claude/history.js +1 -0
  127. package/clis/claude/new.js +1 -0
  128. package/clis/claude/read.js +1 -0
  129. package/clis/claude/send.js +1 -0
  130. package/clis/claude/status.js +1 -0
  131. package/clis/cnki/search.js +1 -0
  132. package/clis/codex/ask.js +1 -0
  133. package/clis/codex/export.js +1 -0
  134. package/clis/codex/extract-diff.js +1 -0
  135. package/clis/codex/history.js +1 -0
  136. package/clis/codex/model.js +1 -0
  137. package/clis/codex/read.js +1 -0
  138. package/clis/codex/send.js +1 -0
  139. package/clis/coupang/add-to-cart.js +1 -0
  140. package/clis/coupang/search.js +1 -0
  141. package/clis/ctrip/search.js +1 -0
  142. package/clis/cursor/ask.js +1 -0
  143. package/clis/cursor/composer.js +1 -0
  144. package/clis/cursor/export.js +1 -0
  145. package/clis/cursor/extract-code.js +1 -0
  146. package/clis/cursor/history.js +1 -0
  147. package/clis/cursor/model.js +1 -0
  148. package/clis/cursor/read.js +1 -0
  149. package/clis/cursor/send.js +1 -0
  150. package/clis/dblp/dblp.test.js +397 -0
  151. package/clis/dblp/paper.js +40 -0
  152. package/clis/dblp/search.js +45 -0
  153. package/clis/dblp/utils.js +290 -0
  154. package/clis/deepseek/ask.js +1 -0
  155. package/clis/deepseek/history.js +1 -0
  156. package/clis/deepseek/new.js +1 -0
  157. package/clis/deepseek/read.js +1 -0
  158. package/clis/deepseek/status.js +1 -0
  159. package/clis/devto/devto.test.js +236 -0
  160. package/clis/devto/read.js +103 -0
  161. package/clis/devto/tag.js +5 -1
  162. package/clis/devto/top.js +5 -1
  163. package/clis/devto/user.js +5 -1
  164. package/clis/dianping/__fixtures__/search.html +168 -0
  165. package/clis/dianping/__fixtures__/shop.html +6 -0
  166. package/clis/dianping/dianping.test.js +424 -0
  167. package/clis/dianping/search.js +154 -0
  168. package/clis/dianping/shop.js +173 -0
  169. package/clis/dianping/utils.js +157 -0
  170. package/clis/dictionary/examples.js +1 -0
  171. package/clis/dictionary/search.js +1 -0
  172. package/clis/dictionary/synonyms.js +1 -0
  173. package/clis/discord-app/channels.js +1 -0
  174. package/clis/discord-app/delete.js +1 -0
  175. package/clis/discord-app/members.js +1 -0
  176. package/clis/discord-app/read.js +1 -0
  177. package/clis/discord-app/search.js +1 -0
  178. package/clis/discord-app/send.js +1 -0
  179. package/clis/discord-app/servers.js +1 -0
  180. package/clis/discord-app/status.js +1 -0
  181. package/clis/douban/book-hot.js +1 -0
  182. package/clis/douban/download.js +1 -0
  183. package/clis/douban/marks.js +1 -0
  184. package/clis/douban/movie-hot.js +2 -1
  185. package/clis/douban/movie-hot.test.js +14 -0
  186. package/clis/douban/photos.js +2 -1
  187. package/clis/douban/reviews.js +1 -0
  188. package/clis/douban/search.js +1 -0
  189. package/clis/douban/subject.js +1 -0
  190. package/clis/douban/top250.js +1 -0
  191. package/clis/douban/utils.js +11 -13
  192. package/clis/douban/utils.test.js +79 -0
  193. package/clis/doubao/ask.js +1 -0
  194. package/clis/doubao/detail.js +1 -0
  195. package/clis/doubao/history.js +1 -0
  196. package/clis/doubao/meeting-summary.js +1 -0
  197. package/clis/doubao/meeting-transcript.js +1 -0
  198. package/clis/doubao/new.js +1 -0
  199. package/clis/doubao/read.js +1 -0
  200. package/clis/doubao/send.js +1 -0
  201. package/clis/doubao/status.js +1 -0
  202. package/clis/doubao-app/ask.js +1 -0
  203. package/clis/doubao-app/dump.js +1 -0
  204. package/clis/doubao-app/new.js +1 -0
  205. package/clis/doubao-app/read.js +1 -0
  206. package/clis/doubao-app/screenshot.js +1 -0
  207. package/clis/doubao-app/send.js +1 -0
  208. package/clis/doubao-app/status.js +1 -0
  209. package/clis/douyin/activities.js +1 -0
  210. package/clis/douyin/collections.js +1 -0
  211. package/clis/douyin/delete.js +1 -0
  212. package/clis/douyin/draft.js +1 -0
  213. package/clis/douyin/drafts.js +1 -0
  214. package/clis/douyin/hashtag.js +1 -0
  215. package/clis/douyin/location.js +1 -0
  216. package/clis/douyin/profile.js +1 -0
  217. package/clis/douyin/publish.js +1 -0
  218. package/clis/douyin/stats.js +1 -0
  219. package/clis/douyin/update.js +1 -0
  220. package/clis/douyin/user-videos.js +1 -0
  221. package/clis/douyin/videos.js +1 -0
  222. package/clis/eastmoney/announcement.js +1 -0
  223. package/clis/eastmoney/convertible.js +1 -0
  224. package/clis/eastmoney/etf.js +1 -0
  225. package/clis/eastmoney/holders.js +1 -0
  226. package/clis/eastmoney/hot-rank.js +1 -0
  227. package/clis/eastmoney/index-board.js +1 -0
  228. package/clis/eastmoney/kline.js +1 -0
  229. package/clis/eastmoney/kuaixun.js +1 -0
  230. package/clis/eastmoney/longhu.js +1 -0
  231. package/clis/eastmoney/money-flow.js +1 -0
  232. package/clis/eastmoney/northbound.js +1 -0
  233. package/clis/eastmoney/quote.js +1 -0
  234. package/clis/eastmoney/rank.js +1 -0
  235. package/clis/eastmoney/sectors.js +1 -0
  236. package/clis/facebook/add-friend.js +1 -0
  237. package/clis/facebook/events.js +1 -0
  238. package/clis/facebook/feed.js +1 -0
  239. package/clis/facebook/friends.js +1 -0
  240. package/clis/facebook/groups.js +1 -0
  241. package/clis/facebook/join-group.js +1 -0
  242. package/clis/facebook/marketplace-inbox.js +1 -0
  243. package/clis/facebook/marketplace-listings.js +1 -0
  244. package/clis/facebook/memories.js +1 -0
  245. package/clis/facebook/notifications.js +1 -0
  246. package/clis/facebook/profile.js +1 -0
  247. package/clis/facebook/search.js +1 -0
  248. package/clis/gemini/ask.js +1 -0
  249. package/clis/gemini/deep-research-result.js +1 -0
  250. package/clis/gemini/deep-research.js +1 -0
  251. package/clis/gemini/image.js +1 -0
  252. package/clis/gemini/new.js +1 -0
  253. package/clis/gitee/search.js +1 -0
  254. package/clis/gitee/trending.js +1 -0
  255. package/clis/gitee/user.js +1 -0
  256. package/clis/google/news.js +1 -0
  257. package/clis/google/search.js +1 -0
  258. package/clis/google/suggest.js +1 -0
  259. package/clis/google/trends.js +1 -0
  260. package/clis/google-scholar/cite.js +1 -0
  261. package/clis/google-scholar/profile.js +1 -0
  262. package/clis/google-scholar/search.js +1 -0
  263. package/clis/gov-law/recent.js +1 -0
  264. package/clis/gov-law/search.js +1 -0
  265. package/clis/gov-policy/recent.js +1 -0
  266. package/clis/gov-policy/search.js +1 -0
  267. package/clis/grok/ask.js +1 -0
  268. package/clis/grok/image.ts +1 -0
  269. package/clis/hackernews/ask.js +3 -1
  270. package/clis/hackernews/best.js +3 -1
  271. package/clis/hackernews/hackernews.test.js +132 -0
  272. package/clis/hackernews/jobs.js +3 -1
  273. package/clis/hackernews/new.js +3 -1
  274. package/clis/hackernews/read.js +188 -0
  275. package/clis/hackernews/search.js +3 -1
  276. package/clis/hackernews/show.js +3 -1
  277. package/clis/hackernews/top.js +3 -1
  278. package/clis/hackernews/user.js +1 -0
  279. package/clis/hf/top.js +1 -0
  280. package/clis/hupu/detail.js +1 -0
  281. package/clis/hupu/hot.js +3 -1
  282. package/clis/hupu/like.js +1 -0
  283. package/clis/hupu/mentions.js +2 -1
  284. package/clis/hupu/reply.js +1 -0
  285. package/clis/hupu/search.js +3 -1
  286. package/clis/hupu/unlike.js +1 -0
  287. package/clis/imdb/person.js +1 -0
  288. package/clis/imdb/reviews.js +1 -0
  289. package/clis/imdb/search.js +1 -0
  290. package/clis/imdb/title.js +1 -0
  291. package/clis/imdb/top.js +1 -0
  292. package/clis/imdb/trending.js +1 -0
  293. package/clis/indeed/indeed.test.js +375 -0
  294. package/clis/indeed/job.js +86 -0
  295. package/clis/indeed/search.js +110 -0
  296. package/clis/indeed/utils.js +152 -0
  297. package/clis/instagram/collection-create.js +1 -0
  298. package/clis/instagram/collection-delete.js +1 -0
  299. package/clis/instagram/comment.js +1 -0
  300. package/clis/instagram/download.js +1 -0
  301. package/clis/instagram/explore.js +1 -0
  302. package/clis/instagram/follow.js +1 -0
  303. package/clis/instagram/followers.js +1 -0
  304. package/clis/instagram/following.js +1 -0
  305. package/clis/instagram/like.js +1 -0
  306. package/clis/instagram/note.js +1 -0
  307. package/clis/instagram/post.js +1 -0
  308. package/clis/instagram/profile.js +1 -0
  309. package/clis/instagram/reel.js +1 -0
  310. package/clis/instagram/save.js +1 -0
  311. package/clis/instagram/saved.js +1 -0
  312. package/clis/instagram/search.js +1 -0
  313. package/clis/instagram/story.js +1 -0
  314. package/clis/instagram/unfollow.js +1 -0
  315. package/clis/instagram/unlike.js +1 -0
  316. package/clis/instagram/unsave.js +1 -0
  317. package/clis/instagram/user.js +1 -0
  318. package/clis/jd/add-cart.js +1 -0
  319. package/clis/jd/cart.js +1 -0
  320. package/clis/jd/detail.js +1 -0
  321. package/clis/jd/item.js +1 -0
  322. package/clis/jd/reviews.js +1 -0
  323. package/clis/jd/search.js +1 -0
  324. package/clis/jianyu/detail.js +1 -0
  325. package/clis/jianyu/search.js +1 -0
  326. package/clis/jike/comment.js +1 -0
  327. package/clis/jike/create.js +1 -0
  328. package/clis/jike/feed.js +3 -1
  329. package/clis/jike/like.js +1 -0
  330. package/clis/jike/notifications.js +1 -0
  331. package/clis/jike/post.js +1 -0
  332. package/clis/jike/repost.js +1 -0
  333. package/clis/jike/search.js +3 -1
  334. package/clis/jike/topic.js +1 -0
  335. package/clis/jike/user.js +3 -1
  336. package/clis/jimeng/generate.js +1 -0
  337. package/clis/jimeng/history.js +1 -0
  338. package/clis/jimeng/new.js +1 -0
  339. package/clis/jimeng/workspaces.js +1 -0
  340. package/clis/ke/chengjiao.js +1 -0
  341. package/clis/ke/ershoufang.js +1 -0
  342. package/clis/ke/xiaoqu.js +1 -0
  343. package/clis/ke/zufang.js +1 -0
  344. package/clis/lesswrong/comments.js +1 -0
  345. package/clis/lesswrong/curated.js +1 -0
  346. package/clis/lesswrong/frontpage.js +1 -0
  347. package/clis/lesswrong/new.js +1 -0
  348. package/clis/lesswrong/read.js +1 -0
  349. package/clis/lesswrong/sequences.js +1 -0
  350. package/clis/lesswrong/shortform.js +1 -0
  351. package/clis/lesswrong/tag.js +1 -0
  352. package/clis/lesswrong/tags.js +1 -0
  353. package/clis/lesswrong/top-month.js +1 -0
  354. package/clis/lesswrong/top-week.js +1 -0
  355. package/clis/lesswrong/top-year.js +1 -0
  356. package/clis/lesswrong/top.js +1 -0
  357. package/clis/lesswrong/user-posts.js +1 -0
  358. package/clis/lesswrong/user.js +1 -0
  359. package/clis/linkedin/search.js +1 -0
  360. package/clis/linkedin/timeline.js +1 -0
  361. package/clis/linux-do/categories.js +1 -0
  362. package/clis/linux-do/category.js +1 -0
  363. package/clis/linux-do/feed.js +1 -0
  364. package/clis/linux-do/hot.js +1 -0
  365. package/clis/linux-do/latest.js +1 -0
  366. package/clis/linux-do/search.js +1 -0
  367. package/clis/linux-do/tags.js +2 -1
  368. package/clis/linux-do/topic-content.js +1 -0
  369. package/clis/linux-do/topic.js +1 -0
  370. package/clis/linux-do/user-posts.js +1 -0
  371. package/clis/linux-do/user-topics.js +1 -0
  372. package/clis/lobsters/active.js +4 -1
  373. package/clis/lobsters/hot.js +4 -1
  374. package/clis/lobsters/lobsters.test.js +169 -0
  375. package/clis/lobsters/newest.js +4 -1
  376. package/clis/lobsters/read.js +196 -0
  377. package/clis/lobsters/tag.js +4 -1
  378. package/clis/maimai/search-talents.js +1 -0
  379. package/clis/medium/feed.js +1 -0
  380. package/clis/medium/search.js +1 -0
  381. package/clis/medium/user.js +1 -0
  382. package/clis/mubu/doc.js +1 -0
  383. package/clis/mubu/docs.js +1 -0
  384. package/clis/mubu/notes.js +1 -0
  385. package/clis/mubu/recent.js +1 -0
  386. package/clis/mubu/search.js +1 -0
  387. package/clis/notebooklm/current.js +1 -0
  388. package/clis/notebooklm/get.js +1 -0
  389. package/clis/notebooklm/history.js +1 -0
  390. package/clis/notebooklm/list.js +1 -0
  391. package/clis/notebooklm/note-list.js +1 -0
  392. package/clis/notebooklm/notes-get.js +1 -0
  393. package/clis/notebooklm/open.js +1 -0
  394. package/clis/notebooklm/source-fulltext.js +1 -0
  395. package/clis/notebooklm/source-get.js +1 -0
  396. package/clis/notebooklm/source-guide.js +1 -0
  397. package/clis/notebooklm/source-list.js +1 -0
  398. package/clis/notebooklm/status.js +1 -0
  399. package/clis/notebooklm/summary.js +1 -0
  400. package/clis/notion/export.js +1 -0
  401. package/clis/notion/favorites.js +1 -0
  402. package/clis/notion/new.js +1 -0
  403. package/clis/notion/read.js +1 -0
  404. package/clis/notion/search.js +1 -0
  405. package/clis/notion/sidebar.js +1 -0
  406. package/clis/notion/status.js +1 -0
  407. package/clis/notion/write.js +1 -0
  408. package/clis/nowcoder/companies.js +1 -0
  409. package/clis/nowcoder/creators.js +1 -0
  410. package/clis/nowcoder/detail.js +1 -0
  411. package/clis/nowcoder/experience.js +1 -0
  412. package/clis/nowcoder/hot.js +1 -0
  413. package/clis/nowcoder/jobs.js +1 -0
  414. package/clis/nowcoder/notifications.js +1 -0
  415. package/clis/nowcoder/papers.js +1 -0
  416. package/clis/nowcoder/practice.js +1 -0
  417. package/clis/nowcoder/recommend.js +1 -0
  418. package/clis/nowcoder/referral.js +1 -0
  419. package/clis/nowcoder/salary.js +1 -0
  420. package/clis/nowcoder/search.js +1 -0
  421. package/clis/nowcoder/suggest.js +1 -0
  422. package/clis/nowcoder/topics.js +1 -0
  423. package/clis/nowcoder/trending.js +1 -0
  424. package/clis/ones/login.js +1 -0
  425. package/clis/ones/logout.js +1 -0
  426. package/clis/ones/me.js +1 -0
  427. package/clis/ones/my-tasks.js +1 -0
  428. package/clis/ones/task.js +1 -0
  429. package/clis/ones/tasks.js +1 -0
  430. package/clis/ones/token-info.js +1 -0
  431. package/clis/ones/worklog.js +1 -0
  432. package/clis/openreview/openreview.test.js +345 -0
  433. package/clis/openreview/paper.js +43 -0
  434. package/clis/openreview/reviews.js +131 -0
  435. package/clis/openreview/search.js +46 -0
  436. package/clis/openreview/utils.js +158 -0
  437. package/clis/openreview/venue.js +63 -0
  438. package/clis/paperreview/feedback.js +1 -0
  439. package/clis/paperreview/review.js +1 -0
  440. package/clis/paperreview/submit.js +1 -0
  441. package/clis/pixiv/detail.js +1 -0
  442. package/clis/pixiv/download.js +1 -0
  443. package/clis/pixiv/illusts.js +2 -1
  444. package/clis/pixiv/ranking.js +2 -1
  445. package/clis/pixiv/search.js +2 -1
  446. package/clis/pixiv/user.js +2 -0
  447. package/clis/powerchina/search.js +1 -0
  448. package/clis/producthunt/browse.js +1 -0
  449. package/clis/producthunt/hot.js +1 -0
  450. package/clis/producthunt/posts.js +1 -0
  451. package/clis/producthunt/today.js +1 -0
  452. package/clis/quark/ls.js +1 -0
  453. package/clis/quark/mkdir.js +1 -0
  454. package/clis/quark/mv.js +1 -0
  455. package/clis/quark/rename.js +1 -0
  456. package/clis/quark/rm.js +1 -0
  457. package/clis/quark/save.js +1 -0
  458. package/clis/quark/share-tree.js +1 -0
  459. package/clis/reddit/comment.js +1 -0
  460. package/clis/reddit/frontpage.js +1 -0
  461. package/clis/reddit/hot.js +6 -1
  462. package/clis/reddit/hot.test.js +18 -0
  463. package/clis/reddit/popular.js +1 -0
  464. package/clis/reddit/read.js +1 -0
  465. package/clis/reddit/save.js +1 -0
  466. package/clis/reddit/saved.js +1 -0
  467. package/clis/reddit/search.js +1 -0
  468. package/clis/reddit/subreddit.js +1 -0
  469. package/clis/reddit/subscribe.js +1 -0
  470. package/clis/reddit/upvote.js +1 -0
  471. package/clis/reddit/upvoted.js +1 -0
  472. package/clis/reddit/user-comments.js +1 -0
  473. package/clis/reddit/user-posts.js +1 -0
  474. package/clis/reddit/user.js +1 -0
  475. package/clis/reuters/search.js +1 -0
  476. package/clis/sinablog/article.js +1 -0
  477. package/clis/sinablog/hot.js +1 -0
  478. package/clis/sinablog/search.js +1 -0
  479. package/clis/sinablog/user.js +1 -0
  480. package/clis/sinafinance/news.js +1 -0
  481. package/clis/sinafinance/rolling-news.js +1 -0
  482. package/clis/sinafinance/stock-rank.js +1 -0
  483. package/clis/sinafinance/stock.js +1 -0
  484. package/clis/smzdm/search.js +1 -0
  485. package/clis/spotify/spotify.js +11 -0
  486. package/clis/stackoverflow/bounties.js +11 -3
  487. package/clis/stackoverflow/hot.js +10 -2
  488. package/clis/stackoverflow/read.js +314 -0
  489. package/clis/stackoverflow/search.js +10 -2
  490. package/clis/stackoverflow/stackoverflow.test.js +346 -0
  491. package/clis/stackoverflow/unanswered.js +9 -2
  492. package/clis/steam/top-sellers.js +1 -0
  493. package/clis/substack/feed.js +1 -0
  494. package/clis/substack/publication.js +1 -0
  495. package/clis/substack/search.js +1 -0
  496. package/clis/taobao/add-cart.js +1 -0
  497. package/clis/taobao/cart.js +1 -0
  498. package/clis/taobao/detail.js +1 -0
  499. package/clis/taobao/reviews.js +1 -0
  500. package/clis/taobao/search.js +1 -0
  501. package/clis/tdx/hot-rank.js +1 -0
  502. package/clis/ths/hot-rank.js +1 -0
  503. package/clis/tieba/hot.js +2 -1
  504. package/clis/tieba/posts.js +1 -0
  505. package/clis/tieba/read.js +1 -0
  506. package/clis/tieba/search.js +2 -1
  507. package/clis/tiktok/comment.js +1 -0
  508. package/clis/tiktok/explore.js +1 -0
  509. package/clis/tiktok/follow.js +1 -0
  510. package/clis/tiktok/following.js +1 -0
  511. package/clis/tiktok/friends.js +1 -0
  512. package/clis/tiktok/like.js +1 -0
  513. package/clis/tiktok/live.js +1 -0
  514. package/clis/tiktok/notifications.js +1 -0
  515. package/clis/tiktok/profile.js +1 -0
  516. package/clis/tiktok/save.js +1 -0
  517. package/clis/tiktok/search.js +1 -0
  518. package/clis/tiktok/unfollow.js +1 -0
  519. package/clis/tiktok/unlike.js +1 -0
  520. package/clis/tiktok/unsave.js +1 -0
  521. package/clis/tiktok/user.js +1 -0
  522. package/clis/toutiao/articles.js +1 -0
  523. package/clis/twitter/accept.js +1 -0
  524. package/clis/twitter/article.js +1 -0
  525. package/clis/twitter/block.js +1 -0
  526. package/clis/twitter/bookmark.js +1 -0
  527. package/clis/twitter/bookmarks.js +2 -1
  528. package/clis/twitter/delete.js +1 -0
  529. package/clis/twitter/download.js +1 -0
  530. package/clis/twitter/follow.js +1 -0
  531. package/clis/twitter/followers.js +1 -0
  532. package/clis/twitter/following.js +1 -0
  533. package/clis/twitter/hide-reply.js +1 -0
  534. package/clis/twitter/like.js +1 -0
  535. package/clis/twitter/likes.js +2 -1
  536. package/clis/twitter/list-add.js +1 -0
  537. package/clis/twitter/list-remove.js +1 -0
  538. package/clis/twitter/list-tweets.js +1 -0
  539. package/clis/twitter/lists.js +1 -0
  540. package/clis/twitter/notifications.js +1 -0
  541. package/clis/twitter/post.js +1 -0
  542. package/clis/twitter/profile.js +1 -0
  543. package/clis/twitter/reply-dm.js +1 -0
  544. package/clis/twitter/reply.js +1 -0
  545. package/clis/twitter/search.js +1 -0
  546. package/clis/twitter/thread.js +1 -0
  547. package/clis/twitter/timeline.js +1 -0
  548. package/clis/twitter/trending.js +11 -12
  549. package/clis/twitter/trending.test.js +15 -0
  550. package/clis/twitter/tweets.js +2 -1
  551. package/clis/twitter/tweets.test.js +2 -2
  552. package/clis/twitter/unblock.js +1 -0
  553. package/clis/twitter/unbookmark.js +1 -0
  554. package/clis/twitter/unfollow.js +1 -0
  555. package/clis/uiverse/code.js +1 -0
  556. package/clis/uiverse/preview.js +1 -0
  557. package/clis/v2ex/daily.js +1 -0
  558. package/clis/v2ex/hot.js +1 -0
  559. package/clis/v2ex/latest.js +1 -0
  560. package/clis/v2ex/me.js +1 -0
  561. package/clis/v2ex/member.js +1 -0
  562. package/clis/v2ex/node.js +1 -0
  563. package/clis/v2ex/nodes.js +1 -0
  564. package/clis/v2ex/notifications.js +1 -0
  565. package/clis/v2ex/replies.js +1 -0
  566. package/clis/v2ex/topic.js +1 -0
  567. package/clis/v2ex/user.js +1 -0
  568. package/clis/wanfang/search.js +1 -0
  569. package/clis/web/read.js +1 -0
  570. package/clis/weibo/comments.js +1 -0
  571. package/clis/weibo/favorites.js +1 -0
  572. package/clis/weibo/feed.js +3 -1
  573. package/clis/weibo/hot.js +1 -0
  574. package/clis/weibo/me.js +1 -0
  575. package/clis/weibo/post.js +1 -0
  576. package/clis/weibo/publish.js +1 -0
  577. package/clis/weibo/search.js +8 -2
  578. package/clis/weibo/user.js +1 -0
  579. package/clis/weixin/create-draft.js +1 -0
  580. package/clis/weixin/download.js +1 -0
  581. package/clis/weixin/drafts.js +1 -0
  582. package/clis/weread/ai-outline.js +1 -0
  583. package/clis/weread/book.js +1 -0
  584. package/clis/weread/highlights.js +1 -0
  585. package/clis/weread/notebooks.js +1 -0
  586. package/clis/weread/notes.js +1 -0
  587. package/clis/weread/ranking.js +1 -0
  588. package/clis/weread/search.js +1 -0
  589. package/clis/weread/shelf.js +1 -0
  590. package/clis/wikipedia/random.js +1 -0
  591. package/clis/wikipedia/search.js +1 -0
  592. package/clis/wikipedia/summary.js +1 -0
  593. package/clis/wikipedia/trending.js +1 -0
  594. package/clis/xianyu/chat.js +1 -0
  595. package/clis/xianyu/item.js +1 -0
  596. package/clis/xianyu/search.js +1 -0
  597. package/clis/xiaoe/catalog.js +2 -1
  598. package/clis/xiaoe/content.js +1 -0
  599. package/clis/xiaoe/courses.js +1 -0
  600. package/clis/xiaoe/detail.js +1 -0
  601. package/clis/xiaoe/play-url.js +1 -0
  602. package/clis/xiaohongshu/comments.js +1 -0
  603. package/clis/xiaohongshu/creator-note-detail.js +1 -0
  604. package/clis/xiaohongshu/creator-notes-summary.js +1 -0
  605. package/clis/xiaohongshu/creator-notes.js +1 -0
  606. package/clis/xiaohongshu/creator-profile.js +1 -0
  607. package/clis/xiaohongshu/creator-stats.js +1 -0
  608. package/clis/xiaohongshu/download.js +1 -0
  609. package/clis/xiaohongshu/feed.js +2 -1
  610. package/clis/xiaohongshu/note.js +1 -0
  611. package/clis/xiaohongshu/notifications.js +1 -0
  612. package/clis/xiaohongshu/publish.js +1 -0
  613. package/clis/xiaohongshu/search.js +1 -0
  614. package/clis/xiaohongshu/user.js +1 -0
  615. package/clis/xiaoyuzhou/download.js +1 -0
  616. package/clis/xiaoyuzhou/episode.js +1 -0
  617. package/clis/xiaoyuzhou/podcast-episodes.js +1 -0
  618. package/clis/xiaoyuzhou/podcast.js +1 -0
  619. package/clis/xiaoyuzhou/transcript.js +1 -0
  620. package/clis/xueqiu/comments.js +1 -0
  621. package/clis/xueqiu/earnings-date.js +1 -0
  622. package/clis/xueqiu/feed.js +1 -0
  623. package/clis/xueqiu/fund-holdings.js +1 -0
  624. package/clis/xueqiu/fund-snapshot.js +1 -0
  625. package/clis/xueqiu/groups.js +1 -0
  626. package/clis/xueqiu/hot-stock.js +1 -0
  627. package/clis/xueqiu/hot.js +1 -0
  628. package/clis/xueqiu/kline.js +1 -0
  629. package/clis/xueqiu/search.js +1 -0
  630. package/clis/xueqiu/stock.js +1 -0
  631. package/clis/xueqiu/watchlist.js +1 -0
  632. package/clis/yahoo-finance/quote.js +1 -0
  633. package/clis/yollomi/background.js +1 -0
  634. package/clis/yollomi/edit.js +1 -0
  635. package/clis/yollomi/face-swap.js +1 -0
  636. package/clis/yollomi/generate.js +1 -0
  637. package/clis/yollomi/models.js +1 -0
  638. package/clis/yollomi/object-remover.js +1 -0
  639. package/clis/yollomi/remove-bg.js +1 -0
  640. package/clis/yollomi/restore.js +1 -0
  641. package/clis/yollomi/try-on.js +1 -0
  642. package/clis/yollomi/upload.js +1 -0
  643. package/clis/yollomi/upscale.js +1 -0
  644. package/clis/yollomi/video.js +1 -0
  645. package/clis/youtube/channel.js +1 -0
  646. package/clis/youtube/comments.js +1 -0
  647. package/clis/youtube/feed.js +8 -7
  648. package/clis/youtube/feed.test.js +131 -0
  649. package/clis/youtube/history.js +1 -0
  650. package/clis/youtube/like.js +1 -0
  651. package/clis/youtube/playlist.js +1 -0
  652. package/clis/youtube/search.js +1 -0
  653. package/clis/youtube/subscribe.js +1 -0
  654. package/clis/youtube/subscriptions.js +1 -0
  655. package/clis/youtube/transcript.js +1 -0
  656. package/clis/youtube/unlike.js +1 -0
  657. package/clis/youtube/unsubscribe.js +1 -0
  658. package/clis/youtube/video.js +1 -0
  659. package/clis/youtube/watch-later.js +1 -0
  660. package/clis/yuanbao/ask.js +1 -0
  661. package/clis/yuanbao/new.js +1 -0
  662. package/clis/zhihu/answer.js +1 -0
  663. package/clis/zhihu/collection.js +1 -0
  664. package/clis/zhihu/collections.js +1 -0
  665. package/clis/zhihu/comment.js +1 -0
  666. package/clis/zhihu/download.js +1 -0
  667. package/clis/zhihu/favorite.js +1 -0
  668. package/clis/zhihu/follow.js +1 -0
  669. package/clis/zhihu/hot.js +1 -0
  670. package/clis/zhihu/like.js +1 -0
  671. package/clis/zhihu/question.js +1 -0
  672. package/clis/zhihu/search.js +1 -0
  673. package/clis/zlibrary/info.js +1 -0
  674. package/clis/zlibrary/search.js +1 -0
  675. package/clis/zsxq/dynamics.js +1 -0
  676. package/clis/zsxq/groups.js +1 -0
  677. package/clis/zsxq/search.js +1 -0
  678. package/clis/zsxq/topic.js +1 -0
  679. package/clis/zsxq/topics.js +1 -0
  680. package/dist/src/adapter-source.test.js +1 -1
  681. package/dist/src/browser/analyze.test.js +2 -0
  682. package/dist/src/browser/base-page.d.ts +9 -0
  683. package/dist/src/browser/base-page.js +72 -3
  684. package/dist/src/browser/base-page.test.js +42 -0
  685. package/dist/src/browser/cdp.js +6 -0
  686. package/dist/src/browser/cdp.test.js +3 -0
  687. package/dist/src/browser/page.d.ts +1 -0
  688. package/dist/src/browser/page.js +6 -0
  689. package/dist/src/browser/page.test.js +17 -0
  690. package/dist/src/browser/target-resolver.d.ts +10 -3
  691. package/dist/src/browser/target-resolver.js +16 -10
  692. package/dist/src/browser/verify-fixture.d.ts +6 -1
  693. package/dist/src/browser/verify-fixture.js +87 -0
  694. package/dist/src/browser/verify-fixture.test.js +44 -1
  695. package/dist/src/build-manifest.js +3 -0
  696. package/dist/src/build-manifest.test.js +18 -12
  697. package/dist/src/capabilityRouting.test.js +1 -1
  698. package/dist/src/cli.js +141 -5
  699. package/dist/src/cli.test.js +179 -0
  700. package/dist/src/commanderAdapter.d.ts +1 -1
  701. package/dist/src/commanderAdapter.js +17 -7
  702. package/dist/src/commanderAdapter.test.js +7 -6
  703. package/dist/src/convention-audit.d.ts +50 -0
  704. package/dist/src/convention-audit.js +546 -0
  705. package/dist/src/convention-audit.test.d.ts +1 -0
  706. package/dist/src/convention-audit.test.js +226 -0
  707. package/dist/src/discovery.js +2 -0
  708. package/dist/src/doctor.js +0 -1
  709. package/dist/src/doctor.test.js +1 -1
  710. package/dist/src/engine.test.js +10 -10
  711. package/dist/src/execution.js +1 -1
  712. package/dist/src/execution.test.js +9 -9
  713. package/dist/src/help.d.ts +15 -0
  714. package/dist/src/help.js +149 -0
  715. package/dist/src/main.js +13 -7
  716. package/dist/src/manifest-types.d.ts +2 -0
  717. package/dist/src/plugin.test.js +26 -26
  718. package/dist/src/registry.d.ts +5 -0
  719. package/dist/src/registry.js +8 -0
  720. package/dist/src/registry.test.js +27 -20
  721. package/dist/src/serialization.d.ts +4 -0
  722. package/dist/src/serialization.js +27 -1
  723. package/dist/src/serialization.test.js +24 -2
  724. package/dist/src/types.d.ts +2 -0
  725. package/package.json +4 -1
  726. package/scripts/check-listing-id-pairing.mjs +193 -0
  727. package/scripts/check-silent-column-drop.mjs +105 -0
  728. package/scripts/check-typed-error-lint.mjs +118 -0
  729. package/scripts/silent-column-drop-baseline.json +962 -0
  730. package/scripts/typed-error-lint-baseline.json +1586 -0
@@ -24,11 +24,12 @@ describe('manifest helper rules', () => {
24
24
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-manifest-'));
25
25
  tempDirs.push(dir);
26
26
  const file = path.join(dir, `${site}.ts`);
27
- fs.writeFileSync(file, `export const command = cli({ site: '${site}', name: 'dynamic' });`);
27
+ fs.writeFileSync(file, `export const command = cli({ site: '${site}', name: 'dynamic', access: 'read' });`);
28
28
  const entries = await loadManifestEntries(file, site, async () => ({
29
29
  command: cli({
30
30
  site,
31
31
  name: 'dynamic',
32
+ access: 'read',
32
33
  description: 'dynamic command',
33
34
  strategy: Strategy.PUBLIC,
34
35
  browser: false,
@@ -53,6 +54,7 @@ describe('manifest helper rules', () => {
53
54
  {
54
55
  site,
55
56
  name: 'dynamic',
57
+ access: 'read',
56
58
  description: 'dynamic command',
57
59
  domain: 'localhost',
58
60
  strategy: 'public',
@@ -87,11 +89,12 @@ describe('manifest helper rules', () => {
87
89
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-manifest-'));
88
90
  tempDirs.push(dir);
89
91
  const file = path.join(dir, `${site}.ts`);
90
- fs.writeFileSync(file, `cli({ site: '${site}', name: 'legacy' });`);
92
+ fs.writeFileSync(file, `cli({ site: '${site}', name: 'legacy', access: 'read' });`);
91
93
  const entries = await loadManifestEntries(file, site, async () => {
92
94
  cli({
93
95
  site,
94
96
  name: 'legacy',
97
+ access: 'read',
95
98
  description: 'legacy command',
96
99
  deprecated: 'legacy is deprecated',
97
100
  replacedBy: 'opencli demo new',
@@ -102,6 +105,7 @@ describe('manifest helper rules', () => {
102
105
  {
103
106
  site,
104
107
  name: 'legacy',
108
+ access: 'read',
105
109
  description: 'legacy command',
106
110
  strategy: 'cookie',
107
111
  browser: true,
@@ -123,16 +127,18 @@ describe('manifest helper rules', () => {
123
127
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-manifest-'));
124
128
  tempDirs.push(dir);
125
129
  const file = path.join(dir, `${site}.ts`);
126
- fs.writeFileSync(file, `export const screen = cli({ site: '${site}', name: 'screen' });`);
130
+ fs.writeFileSync(file, `export const screen = cli({ site: '${site}', name: 'screen', access: 'read' });`);
127
131
  const entries = await loadManifestEntries(file, site, async () => ({
128
132
  screen: cli({
129
133
  site,
130
134
  name: 'screen',
135
+ access: 'read',
131
136
  description: 'capture screen',
132
137
  }),
133
138
  status: cli({
134
139
  site,
135
140
  name: 'status',
141
+ access: 'read',
136
142
  description: 'show status',
137
143
  }),
138
144
  }));
@@ -147,7 +153,7 @@ describe('manifest helper rules', () => {
147
153
  it('serializes manifest json with a trailing newline', () => {
148
154
  const serialized = serializeManifest([{
149
155
  site: 'demo',
150
- name: 'status',
156
+ name: 'status', access: 'read',
151
157
  description: '',
152
158
  strategy: 'public',
153
159
  browser: false,
@@ -164,7 +170,7 @@ describe('manifest helper rules', () => {
164
170
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-manifest-fail-'));
165
171
  tempDirs.push(dir);
166
172
  const file = path.join(dir, 'broken.ts');
167
- fs.writeFileSync(file, `export const command = cli({ site: 'demo', name: 'broken' });`);
173
+ fs.writeFileSync(file, `export const command = cli({ site: 'demo', name: 'broken', access: 'read' });`);
168
174
  const importer = async () => { throw new Error('boom: stale dist'); };
169
175
  await expect(loadManifestEntries(file, 'demo', importer))
170
176
  .rejects.toBeInstanceOf(ManifestImportError);
@@ -194,12 +200,12 @@ describe('manifest helper rules', () => {
194
200
  tempDirs.push(root);
195
201
  const siteDir = path.join(root, 'demo');
196
202
  fs.mkdirSync(siteDir);
197
- fs.writeFileSync(path.join(siteDir, 'good.js'), `export const cmd = cli({ site: 'demo', name: 'good' });`);
198
- fs.writeFileSync(path.join(siteDir, 'broken.js'), `export const cmd = cli({ site: 'demo', name: 'broken' });`);
203
+ fs.writeFileSync(path.join(siteDir, 'good.js'), `export const cmd = cli({ site: 'demo', name: 'good', access: 'read' });`);
204
+ fs.writeFileSync(path.join(siteDir, 'broken.js'), `export const cmd = cli({ site: 'demo', name: 'broken', access: 'read' });`);
199
205
  const importer = async (href) => {
200
206
  if (href.endsWith('broken.js'))
201
207
  throw new Error('stale dist drops broken');
202
- return { cmd: cli({ site: 'demo', name: 'good', description: 'ok' }) };
208
+ return { cmd: cli({ site: 'demo', name: 'good', access: 'read', description: 'ok' }) };
203
209
  };
204
210
  const result = await scanClisDir(root, importer);
205
211
  expect(result.failures).toHaveLength(1);
@@ -211,12 +217,12 @@ describe('manifest helper rules', () => {
211
217
  });
212
218
  it('diffRemovedEntries returns site/name keys present only in prev', () => {
213
219
  const prev = [
214
- { site: 'a', name: '1', description: '', strategy: 'public', browser: false, args: [], type: 'js' },
215
- { site: 'a', name: '2', description: '', strategy: 'public', browser: false, args: [], type: 'js' },
216
- { site: 'b', name: '3', description: '', strategy: 'public', browser: false, args: [], type: 'js' },
220
+ { site: 'a', name: '1', access: 'read', description: '', strategy: 'public', browser: false, args: [], type: 'js' },
221
+ { site: 'a', name: '2', access: 'read', description: '', strategy: 'public', browser: false, args: [], type: 'js' },
222
+ { site: 'b', name: '3', access: 'read', description: '', strategy: 'public', browser: false, args: [], type: 'js' },
217
223
  ];
218
224
  const next = [
219
- { site: 'a', name: '1', description: '', strategy: 'public', browser: false, args: [], type: 'js' },
225
+ { site: 'a', name: '1', access: 'read', description: '', strategy: 'public', browser: false, args: [], type: 'js' },
220
226
  ];
221
227
  expect(diffRemovedEntries(prev, next)).toEqual(['a/2', 'b/3']);
222
228
  expect(diffRemovedEntries(prev, prev)).toEqual([]);
@@ -4,7 +4,7 @@ import { shouldUseBrowserSession } from './capabilityRouting.js';
4
4
  function makeCmd(partial) {
5
5
  return {
6
6
  site: 'test',
7
- name: 'command',
7
+ name: 'command', access: 'read',
8
8
  description: '',
9
9
  args: [],
10
10
  ...partial,
package/dist/src/cli.js CHANGED
@@ -18,6 +18,7 @@ import { PKG_VERSION } from './version.js';
18
18
  import { printCompletionScript } from './completion.js';
19
19
  import { loadExternalClis, executeExternalCli, installExternalCli, registerExternalCli, isBinaryInstalled } from './external.js';
20
20
  import { registerAllCommands } from './commanderAdapter.js';
21
+ import { formatRootAdapterHelpText, installStructuredHelp, rootHelpData } from './help.js';
21
22
  import { EXIT_CODES, getErrorMessage, BrowserConnectError } from './errors.js';
22
23
  import { TargetError } from './browser/target-errors.js';
23
24
  import { resolveTargetJs, getTextResolvedJs, getValueResolvedJs, getAttributesResolvedJs, selectResolvedJs, isAutocompleteResolvedJs } from './browser/target-resolver.js';
@@ -392,6 +393,20 @@ function applyVerbose(opts) {
392
393
  if (opts.verbose)
393
394
  process.env.OPENCLI_VERBOSE = '1';
394
395
  }
396
+ function formatChildCommandSummary(command) {
397
+ return [...new Set(command.commands.map(child => child.name()))]
398
+ .sort((a, b) => a.localeCompare(b))
399
+ .join(', ');
400
+ }
401
+ function applyRootSubcommandSummaries(program) {
402
+ for (const command of program.commands) {
403
+ if (command.commands.length === 0)
404
+ continue;
405
+ const summary = formatChildCommandSummary(command);
406
+ if (summary)
407
+ command.description(summary);
408
+ }
409
+ }
395
410
  export function createProgram(BUILTIN_CLIS, USER_CLIS) {
396
411
  const program = new Command();
397
412
  // enablePositionalOptions: prevents parent from consuming flags meant for subcommands;
@@ -421,13 +436,14 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
421
436
  name: c.name,
422
437
  aliases: c.aliases?.join(', ') ?? '',
423
438
  description: c.description,
439
+ access: c.access,
424
440
  strategy: strategyLabel(c),
425
441
  browser: !!c.browser,
426
442
  args: formatArgSummary(c.args),
427
443
  }));
428
444
  renderOutput(rows, {
429
445
  fmt,
430
- columns: ['command', 'site', 'name', 'aliases', 'description', 'strategy', 'browser', 'args',
446
+ columns: ['command', 'site', 'name', 'aliases', 'description', 'access', 'strategy', 'browser', 'args',
431
447
  ...(isStructured ? ['columns', 'domain'] : [])],
432
448
  title: 'opencli/list',
433
449
  source: 'opencli list',
@@ -489,6 +505,30 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
489
505
  console.log(renderVerifyReport(r));
490
506
  process.exitCode = r.ok ? EXIT_CODES.SUCCESS : EXIT_CODES.GENERIC_ERROR;
491
507
  });
508
+ program
509
+ .command('convention-audit')
510
+ .description('Scan adapters for agent-native convention violations')
511
+ .argument('[target]', 'site or site/name')
512
+ .option('--site <site>', 'Limit audit to one site')
513
+ .option('-f, --format <fmt>', 'Output format: table, json, yaml', 'table')
514
+ .option('--strict', 'Exit non-zero when violations are found', false)
515
+ .action(async (target, opts) => {
516
+ const { runConventionAudit, renderConventionAuditText } = await import('./convention-audit.js');
517
+ const report = runConventionAudit({
518
+ projectRoot: findPackageRoot(CLI_FILE),
519
+ target,
520
+ site: opts.site,
521
+ });
522
+ const fmt = String(opts.format ?? 'table').toLowerCase();
523
+ if (fmt === 'json' || fmt === 'yaml' || fmt === 'yml') {
524
+ renderOutput(report, { fmt });
525
+ }
526
+ else {
527
+ console.log(renderConventionAuditText(report));
528
+ }
529
+ if (opts.strict && !report.ok)
530
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
531
+ });
492
532
  // ── Built-in: browser (browser control for Claude Code skill) ───────────────
493
533
  //
494
534
  // Make websites accessible for AI agents.
@@ -542,6 +582,19 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
542
582
  },
543
583
  }, null, 2));
544
584
  }
585
+ function isJavaScriptDialogMessage(message) {
586
+ const normalized = message.toLowerCase();
587
+ return normalized.includes('javascript dialog');
588
+ }
589
+ function emitJavaScriptDialogError(message) {
590
+ console.log(JSON.stringify({
591
+ error: {
592
+ code: 'javascript_dialog_open',
593
+ message,
594
+ hint: 'Handle the modal first: opencli browser dialog accept (or dismiss). Use --text for prompt dialogs.',
595
+ },
596
+ }, null, 2));
597
+ }
545
598
  /** Wrap browser actions with error handling and optional --json output */
546
599
  function browserAction(fn) {
547
600
  return async (...args) => {
@@ -560,7 +613,10 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
560
613
  log.error(`Hint: ${err.hint}`);
561
614
  }
562
615
  else if (err instanceof BrowserCommandError) {
563
- if (err.code) {
616
+ if (isJavaScriptDialogMessage(err.message)) {
617
+ emitJavaScriptDialogError(err.message);
618
+ }
619
+ else if (err.code) {
564
620
  console.log(JSON.stringify({
565
621
  error: {
566
622
  code: err.code,
@@ -582,7 +638,11 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
582
638
  }
583
639
  else {
584
640
  const msg = getErrorMessage(err);
585
- if (msg.includes('attach failed') || msg.includes('chrome-extension://')) {
641
+ if (isJavaScriptDialogMessage(msg)) {
642
+ emitJavaScriptDialogError(msg);
643
+ log.error(msg);
644
+ }
645
+ else if (msg.includes('attach failed') || msg.includes('chrome-extension://')) {
586
646
  log.error(`Browser attach failed — another extension may be interfering. Try disabling 1Password.`);
587
647
  }
588
648
  else {
@@ -1295,6 +1355,60 @@ export function createProgram(BUILTIN_CLIS, USER_CLIS) {
1295
1355
  await page.pressKey(key);
1296
1356
  console.log(`Pressed: ${key}`);
1297
1357
  }));
1358
+ const browserDialog = browser
1359
+ .command('dialog')
1360
+ .description('Handle a blocking JavaScript alert/confirm/prompt dialog');
1361
+ addBrowserTabOption(browserDialog.command('accept')
1362
+ .option('--text <text>', 'Prompt text to submit for prompt() dialogs')
1363
+ .description('Accept the currently open JavaScript dialog'))
1364
+ .action(browserAction(async (page, opts) => {
1365
+ if (!page.handleJavaScriptDialog) {
1366
+ throw new Error('This browser session does not support JavaScript dialog handling');
1367
+ }
1368
+ try {
1369
+ await page.handleJavaScriptDialog(true, opts?.text);
1370
+ }
1371
+ catch (err) {
1372
+ const message = getErrorMessage(err);
1373
+ if (message.toLowerCase().includes('no dialog')) {
1374
+ console.log(JSON.stringify({
1375
+ error: {
1376
+ code: 'no_javascript_dialog',
1377
+ message: 'No JavaScript dialog is currently open.',
1378
+ },
1379
+ }, null, 2));
1380
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1381
+ return;
1382
+ }
1383
+ throw err;
1384
+ }
1385
+ console.log(JSON.stringify({ handled: true, action: 'accept', ...(opts?.text !== undefined && { text: opts.text }) }, null, 2));
1386
+ }));
1387
+ addBrowserTabOption(browserDialog.command('dismiss')
1388
+ .description('Dismiss the currently open JavaScript dialog'))
1389
+ .action(browserAction(async (page) => {
1390
+ if (!page.handleJavaScriptDialog) {
1391
+ throw new Error('This browser session does not support JavaScript dialog handling');
1392
+ }
1393
+ try {
1394
+ await page.handleJavaScriptDialog(false);
1395
+ }
1396
+ catch (err) {
1397
+ const message = getErrorMessage(err);
1398
+ if (message.toLowerCase().includes('no dialog')) {
1399
+ console.log(JSON.stringify({
1400
+ error: {
1401
+ code: 'no_javascript_dialog',
1402
+ message: 'No JavaScript dialog is currently open.',
1403
+ },
1404
+ }, null, 2));
1405
+ process.exitCode = EXIT_CODES.USAGE_ERROR;
1406
+ return;
1407
+ }
1408
+ throw err;
1409
+ }
1410
+ console.log(JSON.stringify({ handled: true, action: 'dismiss' }, null, 2));
1411
+ }));
1298
1412
  // ── Wait commands ──
1299
1413
  addBrowserTabOption(browser.command('wait'))
1300
1414
  .argument('<type>', 'selector, text, time, or xhr')
@@ -1741,6 +1855,8 @@ cli({
1741
1855
  site: '${site}',
1742
1856
  name: '${command}',
1743
1857
  description: '', // TODO: describe what this command does
1858
+ access: 'read', // TODO: 'read' for queries, 'write' for remote/account state changes
1859
+ example: 'opencli ${site} ${command} -f yaml',
1744
1860
  domain: '${domain}',
1745
1861
  strategy: Strategy.PUBLIC, // TODO: PUBLIC (no auth), COOKIE (needs login), UI (DOM interaction)
1746
1862
  browser: false, // TODO: set true if needs browser
@@ -1792,7 +1908,7 @@ cli({
1792
1908
  return;
1793
1909
  }
1794
1910
  const { execFileSync } = await import('node:child_process');
1795
- const { loadFixture, writeFixture, deriveFixture, validateRows, fixturePath, expandFixtureArgs, parseSeedArgs } = await import('./browser/verify-fixture.js');
1911
+ const { loadFixture, writeFixture, deriveFixture, validateRows, validateRowShape, fixturePath, expandFixtureArgs, parseSeedArgs } = await import('./browser/verify-fixture.js');
1796
1912
  const filePath = path.join(os.homedir(), '.opencli', 'clis', site, `${command}.js`);
1797
1913
  if (!fs.existsSync(filePath)) {
1798
1914
  console.error(`Adapter not found: ${filePath}`);
@@ -1854,6 +1970,20 @@ cli({
1854
1970
  }
1855
1971
  console.log(renderVerifyPreview(rows));
1856
1972
  console.log(`\n → ${rows.length} row${rows.length === 1 ? '' : 's'}`);
1973
+ const shapeFailures = validateRowShape(rows);
1974
+ if (shapeFailures.length > 0) {
1975
+ console.log(`\n ✗ Adapter output violates row shape conventions:`);
1976
+ for (const f of shapeFailures.slice(0, 20)) {
1977
+ const where = f.rowIndex !== undefined ? `row[${f.rowIndex}] ` : '';
1978
+ console.log(` - [${f.rule}] ${where}${f.detail}`);
1979
+ }
1980
+ if (shapeFailures.length > 20) {
1981
+ console.log(` ... and ${shapeFailures.length - 20} more failure(s)`);
1982
+ }
1983
+ console.log(`\n Keep rows agent-native: <=12 top-level keys, nesting depth <=1, and id-shaped fields at top level.`);
1984
+ process.exitCode = EXIT_CODES.GENERIC_ERROR;
1985
+ return;
1986
+ }
1857
1987
  // ── Fixture handling ───────────────────────────────────────────
1858
1988
  if (opts.writeFixture || opts.updateFixture) {
1859
1989
  if (fixture && !opts.updateFixture) {
@@ -2407,7 +2537,13 @@ cli({
2407
2537
  // ── Dynamic adapter commands ──────────────────────────────────────────────
2408
2538
  const siteGroups = new Map();
2409
2539
  siteGroups.set('antigravity', antigravityCmd);
2410
- registerAllCommands(program, siteGroups);
2540
+ const siteNames = registerAllCommands(program, siteGroups);
2541
+ applyRootSubcommandSummaries(program);
2542
+ const siteNameSet = new Set(siteNames);
2543
+ program.configureHelp({
2544
+ visibleCommands: (command) => command.commands.filter(child => command !== program || !siteNameSet.has(child.name())),
2545
+ });
2546
+ installStructuredHelp(program, () => rootHelpData(program, siteNames), () => formatRootAdapterHelpText(siteNames));
2411
2547
  // ── Unknown command fallback ──────────────────────────────────────────────
2412
2548
  // Security: do NOT auto-discover and register arbitrary system binaries.
2413
2549
  // Only explicitly registered external CLIs are allowed.
@@ -2,6 +2,8 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
2
  import * as fs from 'node:fs';
3
3
  import * as os from 'node:os';
4
4
  import * as path from 'node:path';
5
+ import yaml from 'js-yaml';
6
+ import { cli, getRegistry, Strategy } from './registry.js';
5
7
  import { BrowserCommandError } from './browser/daemon-client.js';
6
8
  import { TargetError } from './browser/target-errors.js';
7
9
  import { PKG_VERSION } from './version.js';
@@ -38,6 +40,130 @@ vi.mock('node:child_process', async () => {
38
40
  };
39
41
  });
40
42
  import { createProgram, findPackageRoot, normalizeVerifyRows, renderVerifyPreview, resolveBrowserVerifyInvocation, selectFreshByTimestamp } from './cli.js';
43
+ describe('createProgram root help descriptions', () => {
44
+ function descriptionFor(program, name) {
45
+ return program.commands.find(cmd => cmd.name() === name)?.description();
46
+ }
47
+ it('summarizes built-in command groups with their subcommands', () => {
48
+ const program = createProgram('', '');
49
+ expect(descriptionFor(program, 'browser')).toContain('open');
50
+ expect(descriptionFor(program, 'browser')).toContain('type');
51
+ expect(descriptionFor(program, 'browser')).toContain('verify');
52
+ expect(descriptionFor(program, 'browser')).not.toContain('Browser control');
53
+ expect(descriptionFor(program, 'plugin')).toBe('create, install, list, uninstall, update');
54
+ expect(descriptionFor(program, 'adapter')).toBe('eject, reset, status');
55
+ expect(descriptionFor(program, 'profile')).toBe('list, rename, use');
56
+ expect(descriptionFor(program, 'daemon')).toBe('restart, status, stop');
57
+ expect(descriptionFor(program, 'external')).toBe('install, list, register');
58
+ });
59
+ it('keeps leaf command descriptions unchanged', () => {
60
+ const program = createProgram('', '');
61
+ expect(descriptionFor(program, 'list')).toBe('List all available CLI commands');
62
+ expect(descriptionFor(program, 'doctor')).toBe('Diagnose opencli browser bridge connectivity');
63
+ });
64
+ it('keeps site adapters out of root commands and lists sites in the root help tail', () => {
65
+ const registry = getRegistry();
66
+ const snapshot = new Map(registry);
67
+ registry.clear();
68
+ try {
69
+ cli({
70
+ site: 'bilibili',
71
+ name: 'hot',
72
+ access: 'read',
73
+ description: 'Bilibili hot videos',
74
+ strategy: Strategy.PUBLIC,
75
+ browser: false,
76
+ });
77
+ cli({
78
+ site: 'youtube',
79
+ name: 'search',
80
+ access: 'read',
81
+ description: 'Search YouTube',
82
+ strategy: Strategy.PUBLIC,
83
+ browser: false,
84
+ });
85
+ const program = createProgram('', '');
86
+ const help = program.helpInformation();
87
+ expect(help).toContain('Site adapters (2):');
88
+ expect(help).toContain('bilibili, youtube');
89
+ expect(help).toContain("opencli <site> --help -f yaml");
90
+ expect(help).not.toMatch(/\n bilibili\s+hot/);
91
+ expect(help).not.toMatch(/\n youtube\s+search/);
92
+ }
93
+ finally {
94
+ registry.clear();
95
+ for (const [key, value] of snapshot)
96
+ registry.set(key, value);
97
+ }
98
+ });
99
+ it('renders root structured help with built-ins and site adapter names', () => {
100
+ const registry = getRegistry();
101
+ const snapshot = new Map(registry);
102
+ const argv = process.argv;
103
+ registry.clear();
104
+ try {
105
+ cli({
106
+ site: 'bilibili',
107
+ name: 'hot',
108
+ access: 'read',
109
+ description: 'Bilibili hot videos',
110
+ strategy: Strategy.PUBLIC,
111
+ browser: false,
112
+ });
113
+ const program = createProgram('', '');
114
+ process.argv = ['node', 'opencli', '--help', '-f', 'yaml'];
115
+ const data = yaml.load(program.helpInformation());
116
+ expect(data.site_adapters.count).toBe(1);
117
+ expect(data.site_adapters.sites).toEqual(['bilibili']);
118
+ expect(data.commands.map((cmd) => cmd.name)).toContain('list');
119
+ expect(data.commands.map((cmd) => cmd.name)).not.toContain('bilibili');
120
+ }
121
+ finally {
122
+ process.argv = argv;
123
+ registry.clear();
124
+ for (const [key, value] of snapshot)
125
+ registry.set(key, value);
126
+ }
127
+ });
128
+ it('renders per-site structured help with all commands, access, args, and examples', () => {
129
+ const registry = getRegistry();
130
+ const snapshot = new Map(registry);
131
+ const argv = process.argv;
132
+ registry.clear();
133
+ try {
134
+ cli({
135
+ site: 'bilibili',
136
+ name: 'hot',
137
+ access: 'read',
138
+ description: 'Bilibili hot videos',
139
+ strategy: Strategy.PUBLIC,
140
+ browser: false,
141
+ args: [{ name: 'limit', type: 'int', default: 20, help: 'Number of videos' }],
142
+ });
143
+ const program = createProgram('', '');
144
+ const site = program.commands.find(cmd => cmd.name() === 'bilibili');
145
+ expect(site).toBeTruthy();
146
+ process.argv = ['node', 'opencli', 'bilibili', '--help', '-f', 'yaml'];
147
+ const data = yaml.load(site.helpInformation());
148
+ expect(data.site).toBe('bilibili');
149
+ expect(data.commands).toMatchObject([
150
+ {
151
+ name: 'hot',
152
+ access: 'read',
153
+ description: 'Bilibili hot videos',
154
+ example: 'opencli bilibili hot -f yaml',
155
+ args: [{ name: 'limit', type: 'int', default: 20 }],
156
+ },
157
+ ]);
158
+ }
159
+ finally {
160
+ process.argv = argv;
161
+ registry.clear();
162
+ for (const [key, value] of snapshot)
163
+ registry.set(key, value);
164
+ }
165
+ });
166
+ });
41
167
  describe('resolveBrowserVerifyInvocation', () => {
42
168
  it('prefers the built entry declared in package metadata', () => {
43
169
  const projectRoot = path.join('repo-root');
@@ -209,6 +335,39 @@ describe('browser verify', () => {
209
335
  fs.rmSync(fakeHome, { recursive: true, force: true });
210
336
  }
211
337
  });
338
+ it('fails before fixture handling when output row shape is not agent-native', async () => {
339
+ const originalHome = process.env.HOME;
340
+ const originalUserProfile = process.env.USERPROFILE;
341
+ const fakeHome = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-browser-verify-shape-'));
342
+ process.env.HOME = fakeHome;
343
+ process.env.USERPROFILE = fakeHome;
344
+ mockExecFileSync.mockReturnValue(JSON.stringify([{ title: 'ok', author: { user_id: 'u1' } }]));
345
+ const consoleLogSpy = vi.mocked(console.log);
346
+ consoleLogSpy.mockClear();
347
+ try {
348
+ const adapterDir = path.join(fakeHome, '.opencli', 'clis', 'hn');
349
+ fs.mkdirSync(adapterDir, { recursive: true });
350
+ fs.writeFileSync(path.join(adapterDir, 'top.js'), 'export default {};\n', 'utf-8');
351
+ const program = createProgram('', '');
352
+ await program.parseAsync(['node', 'opencli', 'browser', 'verify', 'hn/top', '--no-fixture']);
353
+ expect(process.exitCode).toBe(1);
354
+ const output = consoleLogSpy.mock.calls.map((args) => args.join(' ')).join('\n');
355
+ expect(output).toContain('Adapter output violates row shape conventions');
356
+ expect(output).toContain('author.user_id');
357
+ }
358
+ finally {
359
+ consoleLogSpy.mockClear();
360
+ if (originalHome === undefined)
361
+ delete process.env.HOME;
362
+ else
363
+ process.env.HOME = originalHome;
364
+ if (originalUserProfile === undefined)
365
+ delete process.env.USERPROFILE;
366
+ else
367
+ process.env.USERPROFILE = originalUserProfile;
368
+ fs.rmSync(fakeHome, { recursive: true, force: true });
369
+ }
370
+ });
212
371
  });
213
372
  describe('profile list', () => {
214
373
  const stdoutSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
@@ -298,6 +457,7 @@ describe('browser tab targeting commands', () => {
298
457
  selectTab: vi.fn().mockResolvedValue(undefined),
299
458
  newTab: vi.fn().mockResolvedValue('tab-3'),
300
459
  closeTab: vi.fn().mockResolvedValue(undefined),
460
+ handleJavaScriptDialog: vi.fn().mockResolvedValue(undefined),
301
461
  frames: vi.fn().mockResolvedValue([
302
462
  { index: 0, frameId: 'frame-1', url: 'https://x.example/embed', name: 'x-embed' },
303
463
  ]),
@@ -370,6 +530,25 @@ describe('browser tab targeting commands', () => {
370
530
  expect(out.error.code).toBe('bound_session_missing');
371
531
  expect(process.exitCode).toBeDefined();
372
532
  });
533
+ it('accepts JavaScript dialogs through the browser dialog command', async () => {
534
+ const program = createProgram('', '');
535
+ await program.parseAsync(['node', 'opencli', 'browser', 'dialog', 'accept', '--text', 'ok']);
536
+ expect(browserState.page?.handleJavaScriptDialog).toHaveBeenCalledWith(true, 'ok');
537
+ const out = lastJsonLog();
538
+ expect(out).toEqual({ handled: true, action: 'accept', text: 'ok' });
539
+ });
540
+ it('emits a structured error when a browser action is blocked by a JavaScript dialog', async () => {
541
+ browserState.page = {
542
+ ...browserState.page,
543
+ evaluate: vi.fn().mockRejectedValue(new Error('JavaScript dialog showing')),
544
+ };
545
+ const program = createProgram('', '');
546
+ await program.parseAsync(['node', 'opencli', 'browser', 'eval', 'document.title']);
547
+ const out = lastJsonLog();
548
+ expect(out.error.code).toBe('javascript_dialog_open');
549
+ expect(out.error.hint).toContain('browser dialog accept');
550
+ expect(process.exitCode).toBeDefined();
551
+ });
373
552
  it('binds browser commands to an explicit target tab via --tab', async () => {
374
553
  const program = createProgram('', '');
375
554
  await program.parseAsync(['node', 'opencli', 'browser', 'eval', '--tab', 'tab-2', 'document.title']);
@@ -18,4 +18,4 @@ export declare function registerCommandToProgram(siteCmd: Command, cmd: CliComma
18
18
  /**
19
19
  * Register all commands from the registry onto a Commander program.
20
20
  */
21
- export declare function registerAllCommands(program: Command, siteGroups: Map<string, Command>): void;
21
+ export declare function registerAllCommands(program: Command, siteGroups: Map<string, Command>): string[];
@@ -15,6 +15,7 @@ import { fullName, getRegistry } from './registry.js';
15
15
  import { formatRegistryHelpText } from './serialization.js';
16
16
  import { render as renderOutput } from './output.js';
17
17
  import { executeCommand, prepareCommandArgs } from './execution.js';
18
+ import { commandHelpData, formatSiteCommandDescription, installStructuredHelp, siteHelpData, } from './help.js';
18
19
  import { CliError, EXIT_CODES, toEnvelope, } from './errors.js';
19
20
  /**
20
21
  * Register a single CliCommand as a Commander subcommand.
@@ -22,8 +23,7 @@ import { CliError, EXIT_CODES, toEnvelope, } from './errors.js';
22
23
  export function registerCommandToProgram(siteCmd, cmd) {
23
24
  if (siteCmd.commands.some((c) => c.name() === cmd.name))
24
25
  return;
25
- const deprecatedSuffix = cmd.deprecated ? ' [deprecated]' : '';
26
- const subCmd = siteCmd.command(cmd.name).description(`${cmd.description}${deprecatedSuffix}`);
26
+ const subCmd = siteCmd.command(cmd.name).description(formatSiteCommandDescription(cmd));
27
27
  if (cmd.aliases?.length)
28
28
  subCmd.aliases(cmd.aliases);
29
29
  // Register positional args first, then named options
@@ -49,7 +49,7 @@ export function registerCommandToProgram(siteCmd, cmd) {
49
49
  .option('-f, --format <fmt>', 'Output format: table, plain, json, yaml, md, csv', 'table')
50
50
  .option('--trace <mode>', 'Trace capture: off, on, retain-on-failure', 'off')
51
51
  .option('-v, --verbose', 'Debug output', false);
52
- subCmd.addHelpText('after', formatRegistryHelpText(cmd));
52
+ installStructuredHelp(subCmd, () => commandHelpData(cmd), () => formatRegistryHelpText(cmd));
53
53
  subCmd.action(async (...actionArgs) => {
54
54
  const actionOpts = actionArgs[positionalArgs.length] ?? {};
55
55
  const optionsRecord = typeof actionOpts === 'object' && actionOpts !== null ? actionOpts : {};
@@ -161,15 +161,25 @@ function renderError(err, cmdName, verbose, traceMode) {
161
161
  */
162
162
  export function registerAllCommands(program, siteGroups) {
163
163
  const seen = new Set();
164
+ const commandsBySite = new Map();
164
165
  for (const [, cmd] of getRegistry()) {
165
166
  if (seen.has(cmd))
166
167
  continue;
167
168
  seen.add(cmd);
168
- let siteCmd = siteGroups.get(cmd.site);
169
+ const commands = commandsBySite.get(cmd.site) ?? [];
170
+ commands.push(cmd);
171
+ commandsBySite.set(cmd.site, commands);
172
+ }
173
+ for (const [site, commands] of commandsBySite) {
174
+ let siteCmd = siteGroups.get(site);
169
175
  if (!siteCmd) {
170
- siteCmd = program.command(cmd.site).description(`${cmd.site} commands`);
171
- siteGroups.set(cmd.site, siteCmd);
176
+ siteCmd = program.command(site);
177
+ siteGroups.set(site, siteCmd);
178
+ }
179
+ for (const cmd of commands) {
180
+ registerCommandToProgram(siteCmd, cmd);
172
181
  }
173
- registerCommandToProgram(siteCmd, cmd);
182
+ installStructuredHelp(siteCmd, () => siteHelpData(site, commands));
174
183
  }
184
+ return [...commandsBySite.keys()].sort((a, b) => a.localeCompare(b));
175
185
  }