@jackwener/opencli 1.7.10 → 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 (740) hide show
  1. package/README.md +3 -3
  2. package/README.zh-CN.md +5 -4
  3. package/cli-manifest.json +1443 -24
  4. package/clis/1688/assets.js +1 -0
  5. package/clis/1688/download.js +1 -0
  6. package/clis/1688/item.js +1 -0
  7. package/clis/1688/search.js +2 -1
  8. package/clis/1688/store.js +1 -0
  9. package/clis/36kr/article.js +1 -0
  10. package/clis/36kr/hot.js +1 -0
  11. package/clis/36kr/news.js +1 -0
  12. package/clis/36kr/search.js +1 -0
  13. package/clis/51job/company.js +1 -0
  14. package/clis/51job/detail.js +1 -0
  15. package/clis/51job/hot.js +1 -0
  16. package/clis/51job/search.js +1 -0
  17. package/clis/amazon/bestsellers.js +1 -0
  18. package/clis/amazon/discussion.js +1 -0
  19. package/clis/amazon/movers-shakers.js +1 -0
  20. package/clis/amazon/new-releases.js +1 -0
  21. package/clis/amazon/offer.js +1 -0
  22. package/clis/amazon/product.js +1 -0
  23. package/clis/amazon/rankings.js +1 -0
  24. package/clis/amazon/search.js +1 -0
  25. package/clis/antigravity/dump.js +1 -0
  26. package/clis/antigravity/extract-code.js +1 -0
  27. package/clis/antigravity/model.js +1 -0
  28. package/clis/antigravity/new.js +1 -0
  29. package/clis/antigravity/read.js +1 -0
  30. package/clis/antigravity/send.js +1 -0
  31. package/clis/antigravity/status.js +1 -0
  32. package/clis/antigravity/watch.js +1 -0
  33. package/clis/apple-podcasts/episodes.js +1 -0
  34. package/clis/apple-podcasts/search.js +1 -0
  35. package/clis/apple-podcasts/top.js +1 -0
  36. package/clis/arxiv/arxiv.test.js +112 -0
  37. package/clis/arxiv/paper.js +4 -3
  38. package/clis/arxiv/recent.js +33 -0
  39. package/clis/arxiv/search.js +19 -7
  40. package/clis/arxiv/utils.js +68 -5
  41. package/clis/baidu-scholar/search.js +1 -0
  42. package/clis/band/bands.js +1 -0
  43. package/clis/band/mentions.js +1 -0
  44. package/clis/band/post.js +1 -0
  45. package/clis/band/posts.js +1 -0
  46. package/clis/barchart/flow.js +1 -0
  47. package/clis/barchart/greeks.js +1 -0
  48. package/clis/barchart/options.js +1 -0
  49. package/clis/barchart/quote.js +1 -0
  50. package/clis/bbc/news.js +1 -0
  51. package/clis/bilibili/comments.js +1 -0
  52. package/clis/bilibili/download.js +1 -0
  53. package/clis/bilibili/dynamic.js +1 -0
  54. package/clis/bilibili/favorite.js +1 -0
  55. package/clis/bilibili/feed.js +2 -0
  56. package/clis/bilibili/following.js +1 -0
  57. package/clis/bilibili/history.js +1 -0
  58. package/clis/bilibili/hot.js +6 -1
  59. package/clis/bilibili/hot.test.js +17 -0
  60. package/clis/bilibili/me.js +1 -1
  61. package/clis/bilibili/ranking.js +1 -0
  62. package/clis/bilibili/search.js +1 -1
  63. package/clis/bilibili/subtitle.js +1 -0
  64. package/clis/bilibili/user-videos.js +1 -0
  65. package/clis/bilibili/video.js +1 -0
  66. package/clis/binance/asks.js +1 -0
  67. package/clis/binance/depth.js +1 -0
  68. package/clis/binance/gainers.js +1 -0
  69. package/clis/binance/klines.js +1 -0
  70. package/clis/binance/losers.js +1 -0
  71. package/clis/binance/pairs.js +1 -0
  72. package/clis/binance/price.js +1 -0
  73. package/clis/binance/prices.js +1 -0
  74. package/clis/binance/ticker.js +1 -0
  75. package/clis/binance/top.js +1 -0
  76. package/clis/binance/trades.js +1 -0
  77. package/clis/bloomberg/businessweek.js +1 -0
  78. package/clis/bloomberg/economics.js +1 -0
  79. package/clis/bloomberg/feeds.js +1 -0
  80. package/clis/bloomberg/industries.js +1 -0
  81. package/clis/bloomberg/main.js +1 -0
  82. package/clis/bloomberg/markets.js +1 -0
  83. package/clis/bloomberg/news.js +1 -0
  84. package/clis/bloomberg/opinions.js +1 -0
  85. package/clis/bloomberg/politics.js +1 -0
  86. package/clis/bloomberg/tech.js +1 -0
  87. package/clis/bluesky/feeds.js +1 -0
  88. package/clis/bluesky/followers.js +1 -0
  89. package/clis/bluesky/following.js +1 -0
  90. package/clis/bluesky/profile.js +1 -0
  91. package/clis/bluesky/search.js +1 -0
  92. package/clis/bluesky/starter-packs.js +1 -0
  93. package/clis/bluesky/thread.js +1 -0
  94. package/clis/bluesky/trending.js +1 -0
  95. package/clis/bluesky/user.js +3 -1
  96. package/clis/boss/batchgreet.js +1 -0
  97. package/clis/boss/chatlist.js +1 -0
  98. package/clis/boss/chatmsg.js +1 -0
  99. package/clis/boss/detail.js +1 -0
  100. package/clis/boss/exchange.js +1 -0
  101. package/clis/boss/greet.js +1 -0
  102. package/clis/boss/invite.js +1 -0
  103. package/clis/boss/joblist.js +1 -0
  104. package/clis/boss/mark.js +1 -0
  105. package/clis/boss/recommend.js +1 -0
  106. package/clis/boss/resume.js +1 -0
  107. package/clis/boss/search.js +1 -0
  108. package/clis/boss/send.js +1 -0
  109. package/clis/boss/stats.js +1 -0
  110. package/clis/chaoxing/assignments.js +1 -0
  111. package/clis/chaoxing/exams.js +1 -0
  112. package/clis/chatgpt/image.js +1 -0
  113. package/clis/chatgpt-app/ask.js +1 -0
  114. package/clis/chatgpt-app/model.js +1 -0
  115. package/clis/chatgpt-app/new.js +1 -0
  116. package/clis/chatgpt-app/read.js +1 -0
  117. package/clis/chatgpt-app/send.js +1 -0
  118. package/clis/chatgpt-app/status.js +1 -0
  119. package/clis/chatwise/ask.js +1 -0
  120. package/clis/chatwise/export.js +1 -0
  121. package/clis/chatwise/history.js +1 -0
  122. package/clis/chatwise/model.js +1 -0
  123. package/clis/chatwise/read.js +1 -0
  124. package/clis/chatwise/send.js +1 -0
  125. package/clis/claude/ask.js +1 -0
  126. package/clis/claude/detail.js +1 -0
  127. package/clis/claude/history.js +1 -0
  128. package/clis/claude/new.js +1 -0
  129. package/clis/claude/read.js +1 -0
  130. package/clis/claude/send.js +1 -0
  131. package/clis/claude/status.js +1 -0
  132. package/clis/cnki/search.js +1 -0
  133. package/clis/codex/ask.js +1 -0
  134. package/clis/codex/export.js +1 -0
  135. package/clis/codex/extract-diff.js +1 -0
  136. package/clis/codex/history.js +1 -0
  137. package/clis/codex/model.js +1 -0
  138. package/clis/codex/read.js +1 -0
  139. package/clis/codex/send.js +1 -0
  140. package/clis/coupang/add-to-cart.js +1 -0
  141. package/clis/coupang/search.js +1 -0
  142. package/clis/ctrip/search.js +1 -0
  143. package/clis/cursor/ask.js +1 -0
  144. package/clis/cursor/composer.js +1 -0
  145. package/clis/cursor/export.js +1 -0
  146. package/clis/cursor/extract-code.js +1 -0
  147. package/clis/cursor/history.js +1 -0
  148. package/clis/cursor/model.js +1 -0
  149. package/clis/cursor/read.js +1 -0
  150. package/clis/cursor/send.js +1 -0
  151. package/clis/dblp/dblp.test.js +397 -0
  152. package/clis/dblp/paper.js +40 -0
  153. package/clis/dblp/search.js +45 -0
  154. package/clis/dblp/utils.js +290 -0
  155. package/clis/deepseek/ask.js +1 -0
  156. package/clis/deepseek/history.js +1 -0
  157. package/clis/deepseek/new.js +1 -0
  158. package/clis/deepseek/read.js +1 -0
  159. package/clis/deepseek/status.js +1 -0
  160. package/clis/devto/devto.test.js +236 -0
  161. package/clis/devto/read.js +103 -0
  162. package/clis/devto/tag.js +5 -1
  163. package/clis/devto/top.js +5 -1
  164. package/clis/devto/user.js +5 -1
  165. package/clis/dianping/__fixtures__/search.html +168 -0
  166. package/clis/dianping/__fixtures__/shop.html +6 -0
  167. package/clis/dianping/dianping.test.js +424 -0
  168. package/clis/dianping/search.js +154 -0
  169. package/clis/dianping/shop.js +173 -0
  170. package/clis/dianping/utils.js +157 -0
  171. package/clis/dictionary/examples.js +1 -0
  172. package/clis/dictionary/search.js +1 -0
  173. package/clis/dictionary/synonyms.js +1 -0
  174. package/clis/discord-app/channels.js +1 -0
  175. package/clis/discord-app/delete.js +1 -0
  176. package/clis/discord-app/members.js +1 -0
  177. package/clis/discord-app/read.js +1 -0
  178. package/clis/discord-app/search.js +1 -0
  179. package/clis/discord-app/send.js +1 -0
  180. package/clis/discord-app/servers.js +1 -0
  181. package/clis/discord-app/status.js +1 -0
  182. package/clis/douban/book-hot.js +1 -0
  183. package/clis/douban/download.js +1 -0
  184. package/clis/douban/marks.js +1 -0
  185. package/clis/douban/movie-hot.js +2 -1
  186. package/clis/douban/movie-hot.test.js +14 -0
  187. package/clis/douban/photos.js +2 -1
  188. package/clis/douban/reviews.js +1 -0
  189. package/clis/douban/search.js +1 -0
  190. package/clis/douban/subject.js +1 -0
  191. package/clis/douban/top250.js +1 -0
  192. package/clis/douban/utils.js +11 -13
  193. package/clis/douban/utils.test.js +79 -0
  194. package/clis/doubao/ask.js +1 -0
  195. package/clis/doubao/detail.js +1 -0
  196. package/clis/doubao/history.js +1 -0
  197. package/clis/doubao/meeting-summary.js +1 -0
  198. package/clis/doubao/meeting-transcript.js +1 -0
  199. package/clis/doubao/new.js +1 -0
  200. package/clis/doubao/read.js +1 -0
  201. package/clis/doubao/send.js +1 -0
  202. package/clis/doubao/status.js +1 -0
  203. package/clis/doubao-app/ask.js +1 -0
  204. package/clis/doubao-app/dump.js +1 -0
  205. package/clis/doubao-app/new.js +1 -0
  206. package/clis/doubao-app/read.js +1 -0
  207. package/clis/doubao-app/screenshot.js +1 -0
  208. package/clis/doubao-app/send.js +1 -0
  209. package/clis/doubao-app/status.js +1 -0
  210. package/clis/douyin/activities.js +1 -0
  211. package/clis/douyin/collections.js +1 -0
  212. package/clis/douyin/delete.js +1 -0
  213. package/clis/douyin/draft.js +1 -0
  214. package/clis/douyin/drafts.js +1 -0
  215. package/clis/douyin/hashtag.js +1 -0
  216. package/clis/douyin/location.js +1 -0
  217. package/clis/douyin/profile.js +1 -0
  218. package/clis/douyin/publish.js +1 -0
  219. package/clis/douyin/stats.js +1 -0
  220. package/clis/douyin/update.js +1 -0
  221. package/clis/douyin/user-videos.js +1 -0
  222. package/clis/douyin/videos.js +1 -0
  223. package/clis/eastmoney/announcement.js +1 -0
  224. package/clis/eastmoney/convertible.js +1 -0
  225. package/clis/eastmoney/etf.js +1 -0
  226. package/clis/eastmoney/holders.js +1 -0
  227. package/clis/eastmoney/hot-rank.js +1 -0
  228. package/clis/eastmoney/index-board.js +1 -0
  229. package/clis/eastmoney/kline.js +1 -0
  230. package/clis/eastmoney/kuaixun.js +1 -0
  231. package/clis/eastmoney/longhu.js +1 -0
  232. package/clis/eastmoney/money-flow.js +1 -0
  233. package/clis/eastmoney/northbound.js +1 -0
  234. package/clis/eastmoney/quote.js +1 -0
  235. package/clis/eastmoney/rank.js +1 -0
  236. package/clis/eastmoney/sectors.js +1 -0
  237. package/clis/facebook/add-friend.js +1 -0
  238. package/clis/facebook/events.js +1 -0
  239. package/clis/facebook/feed.js +1 -0
  240. package/clis/facebook/friends.js +1 -0
  241. package/clis/facebook/groups.js +1 -0
  242. package/clis/facebook/join-group.js +1 -0
  243. package/clis/facebook/marketplace-inbox.js +1 -0
  244. package/clis/facebook/marketplace-listings.js +1 -0
  245. package/clis/facebook/memories.js +1 -0
  246. package/clis/facebook/notifications.js +1 -0
  247. package/clis/facebook/profile.js +1 -0
  248. package/clis/facebook/search.js +1 -0
  249. package/clis/gemini/ask.js +1 -0
  250. package/clis/gemini/deep-research-result.js +1 -0
  251. package/clis/gemini/deep-research.js +1 -0
  252. package/clis/gemini/image.js +1 -0
  253. package/clis/gemini/new.js +1 -0
  254. package/clis/gitee/search.js +1 -0
  255. package/clis/gitee/trending.js +1 -0
  256. package/clis/gitee/user.js +1 -0
  257. package/clis/google/news.js +1 -0
  258. package/clis/google/search.js +1 -0
  259. package/clis/google/suggest.js +1 -0
  260. package/clis/google/trends.js +1 -0
  261. package/clis/google-scholar/cite.js +1 -0
  262. package/clis/google-scholar/profile.js +1 -0
  263. package/clis/google-scholar/search.js +1 -0
  264. package/clis/gov-law/recent.js +1 -0
  265. package/clis/gov-law/search.js +1 -0
  266. package/clis/gov-policy/recent.js +1 -0
  267. package/clis/gov-policy/search.js +1 -0
  268. package/clis/grok/ask.js +1 -0
  269. package/clis/grok/image.ts +1 -0
  270. package/clis/hackernews/ask.js +3 -1
  271. package/clis/hackernews/best.js +3 -1
  272. package/clis/hackernews/hackernews.test.js +132 -0
  273. package/clis/hackernews/jobs.js +3 -1
  274. package/clis/hackernews/new.js +3 -1
  275. package/clis/hackernews/read.js +188 -0
  276. package/clis/hackernews/search.js +3 -1
  277. package/clis/hackernews/show.js +3 -1
  278. package/clis/hackernews/top.js +3 -1
  279. package/clis/hackernews/user.js +1 -0
  280. package/clis/hf/top.js +1 -0
  281. package/clis/hupu/detail.js +1 -0
  282. package/clis/hupu/hot.js +3 -1
  283. package/clis/hupu/like.js +1 -0
  284. package/clis/hupu/mentions.js +2 -1
  285. package/clis/hupu/reply.js +1 -0
  286. package/clis/hupu/search.js +3 -1
  287. package/clis/hupu/unlike.js +1 -0
  288. package/clis/imdb/person.js +1 -0
  289. package/clis/imdb/reviews.js +1 -0
  290. package/clis/imdb/search.js +1 -0
  291. package/clis/imdb/title.js +1 -0
  292. package/clis/imdb/top.js +1 -0
  293. package/clis/imdb/trending.js +1 -0
  294. package/clis/indeed/indeed.test.js +375 -0
  295. package/clis/indeed/job.js +86 -0
  296. package/clis/indeed/search.js +110 -0
  297. package/clis/indeed/utils.js +152 -0
  298. package/clis/instagram/collection-create.js +1 -0
  299. package/clis/instagram/collection-delete.js +92 -0
  300. package/clis/instagram/comment.js +1 -0
  301. package/clis/instagram/download.js +1 -0
  302. package/clis/instagram/explore.js +1 -0
  303. package/clis/instagram/follow.js +1 -0
  304. package/clis/instagram/followers.js +1 -0
  305. package/clis/instagram/following.js +1 -0
  306. package/clis/instagram/like.js +1 -0
  307. package/clis/instagram/note.js +1 -0
  308. package/clis/instagram/post.js +1 -0
  309. package/clis/instagram/profile.js +1 -0
  310. package/clis/instagram/reel.js +1 -0
  311. package/clis/instagram/save.js +1 -0
  312. package/clis/instagram/saved.js +1 -0
  313. package/clis/instagram/search.js +1 -0
  314. package/clis/instagram/story.js +1 -0
  315. package/clis/instagram/unfollow.js +1 -0
  316. package/clis/instagram/unlike.js +1 -0
  317. package/clis/instagram/unsave.js +1 -0
  318. package/clis/instagram/user.js +1 -0
  319. package/clis/jd/add-cart.js +1 -0
  320. package/clis/jd/cart.js +1 -0
  321. package/clis/jd/detail.js +1 -0
  322. package/clis/jd/item.js +1 -0
  323. package/clis/jd/reviews.js +1 -0
  324. package/clis/jd/search.js +1 -0
  325. package/clis/jianyu/detail.js +1 -0
  326. package/clis/jianyu/search.js +1 -0
  327. package/clis/jike/comment.js +1 -0
  328. package/clis/jike/create.js +1 -0
  329. package/clis/jike/feed.js +3 -1
  330. package/clis/jike/like.js +1 -0
  331. package/clis/jike/notifications.js +1 -0
  332. package/clis/jike/post.js +1 -0
  333. package/clis/jike/repost.js +1 -0
  334. package/clis/jike/search.js +3 -1
  335. package/clis/jike/topic.js +1 -0
  336. package/clis/jike/user.js +3 -1
  337. package/clis/jimeng/generate.js +1 -0
  338. package/clis/jimeng/history.js +1 -0
  339. package/clis/jimeng/new.js +1 -0
  340. package/clis/jimeng/workspaces.js +1 -0
  341. package/clis/ke/chengjiao.js +1 -0
  342. package/clis/ke/ershoufang.js +1 -0
  343. package/clis/ke/xiaoqu.js +1 -0
  344. package/clis/ke/zufang.js +1 -0
  345. package/clis/lesswrong/comments.js +1 -0
  346. package/clis/lesswrong/curated.js +1 -0
  347. package/clis/lesswrong/frontpage.js +1 -0
  348. package/clis/lesswrong/new.js +1 -0
  349. package/clis/lesswrong/read.js +1 -0
  350. package/clis/lesswrong/sequences.js +1 -0
  351. package/clis/lesswrong/shortform.js +1 -0
  352. package/clis/lesswrong/tag.js +1 -0
  353. package/clis/lesswrong/tags.js +1 -0
  354. package/clis/lesswrong/top-month.js +1 -0
  355. package/clis/lesswrong/top-week.js +1 -0
  356. package/clis/lesswrong/top-year.js +1 -0
  357. package/clis/lesswrong/top.js +1 -0
  358. package/clis/lesswrong/user-posts.js +1 -0
  359. package/clis/lesswrong/user.js +1 -0
  360. package/clis/linkedin/search.js +1 -0
  361. package/clis/linkedin/timeline.js +1 -0
  362. package/clis/linux-do/categories.js +1 -0
  363. package/clis/linux-do/category.js +1 -0
  364. package/clis/linux-do/feed.js +1 -0
  365. package/clis/linux-do/hot.js +1 -0
  366. package/clis/linux-do/latest.js +1 -0
  367. package/clis/linux-do/search.js +1 -0
  368. package/clis/linux-do/tags.js +2 -1
  369. package/clis/linux-do/topic-content.js +1 -0
  370. package/clis/linux-do/topic.js +1 -0
  371. package/clis/linux-do/user-posts.js +1 -0
  372. package/clis/linux-do/user-topics.js +1 -0
  373. package/clis/lobsters/active.js +4 -1
  374. package/clis/lobsters/hot.js +4 -1
  375. package/clis/lobsters/lobsters.test.js +169 -0
  376. package/clis/lobsters/newest.js +4 -1
  377. package/clis/lobsters/read.js +196 -0
  378. package/clis/lobsters/tag.js +4 -1
  379. package/clis/maimai/search-talents.js +1 -0
  380. package/clis/medium/feed.js +1 -0
  381. package/clis/medium/search.js +1 -0
  382. package/clis/medium/user.js +1 -0
  383. package/clis/mubu/doc.js +1 -0
  384. package/clis/mubu/docs.js +1 -0
  385. package/clis/mubu/notes.js +1 -0
  386. package/clis/mubu/recent.js +1 -0
  387. package/clis/mubu/search.js +1 -0
  388. package/clis/notebooklm/current.js +1 -0
  389. package/clis/notebooklm/get.js +1 -0
  390. package/clis/notebooklm/history.js +1 -0
  391. package/clis/notebooklm/list.js +1 -0
  392. package/clis/notebooklm/note-list.js +1 -0
  393. package/clis/notebooklm/notes-get.js +1 -0
  394. package/clis/notebooklm/open.js +1 -0
  395. package/clis/notebooklm/source-fulltext.js +1 -0
  396. package/clis/notebooklm/source-get.js +1 -0
  397. package/clis/notebooklm/source-guide.js +1 -0
  398. package/clis/notebooklm/source-list.js +1 -0
  399. package/clis/notebooklm/status.js +1 -0
  400. package/clis/notebooklm/summary.js +1 -0
  401. package/clis/notion/export.js +1 -0
  402. package/clis/notion/favorites.js +1 -0
  403. package/clis/notion/new.js +1 -0
  404. package/clis/notion/read.js +1 -0
  405. package/clis/notion/search.js +1 -0
  406. package/clis/notion/sidebar.js +1 -0
  407. package/clis/notion/status.js +1 -0
  408. package/clis/notion/write.js +1 -0
  409. package/clis/nowcoder/companies.js +1 -0
  410. package/clis/nowcoder/creators.js +1 -0
  411. package/clis/nowcoder/detail.js +1 -0
  412. package/clis/nowcoder/experience.js +1 -0
  413. package/clis/nowcoder/hot.js +1 -0
  414. package/clis/nowcoder/jobs.js +1 -0
  415. package/clis/nowcoder/notifications.js +1 -0
  416. package/clis/nowcoder/papers.js +1 -0
  417. package/clis/nowcoder/practice.js +1 -0
  418. package/clis/nowcoder/recommend.js +1 -0
  419. package/clis/nowcoder/referral.js +1 -0
  420. package/clis/nowcoder/salary.js +1 -0
  421. package/clis/nowcoder/search.js +1 -0
  422. package/clis/nowcoder/suggest.js +1 -0
  423. package/clis/nowcoder/topics.js +1 -0
  424. package/clis/nowcoder/trending.js +1 -0
  425. package/clis/ones/login.js +1 -0
  426. package/clis/ones/logout.js +1 -0
  427. package/clis/ones/me.js +1 -0
  428. package/clis/ones/my-tasks.js +1 -0
  429. package/clis/ones/task.js +1 -0
  430. package/clis/ones/tasks.js +1 -0
  431. package/clis/ones/token-info.js +1 -0
  432. package/clis/ones/worklog.js +1 -0
  433. package/clis/openreview/openreview.test.js +345 -0
  434. package/clis/openreview/paper.js +43 -0
  435. package/clis/openreview/reviews.js +131 -0
  436. package/clis/openreview/search.js +46 -0
  437. package/clis/openreview/utils.js +158 -0
  438. package/clis/openreview/venue.js +63 -0
  439. package/clis/paperreview/feedback.js +1 -0
  440. package/clis/paperreview/review.js +1 -0
  441. package/clis/paperreview/submit.js +1 -0
  442. package/clis/pixiv/detail.js +1 -0
  443. package/clis/pixiv/download.js +1 -0
  444. package/clis/pixiv/illusts.js +2 -1
  445. package/clis/pixiv/ranking.js +2 -1
  446. package/clis/pixiv/search.js +2 -1
  447. package/clis/pixiv/user.js +2 -0
  448. package/clis/powerchina/search.js +1 -0
  449. package/clis/producthunt/browse.js +1 -0
  450. package/clis/producthunt/hot.js +1 -0
  451. package/clis/producthunt/posts.js +1 -0
  452. package/clis/producthunt/today.js +1 -0
  453. package/clis/quark/ls.js +1 -0
  454. package/clis/quark/mkdir.js +1 -0
  455. package/clis/quark/mv.js +1 -0
  456. package/clis/quark/rename.js +1 -0
  457. package/clis/quark/rm.js +1 -0
  458. package/clis/quark/save.js +1 -0
  459. package/clis/quark/share-tree.js +1 -0
  460. package/clis/reddit/comment.js +1 -0
  461. package/clis/reddit/frontpage.js +1 -0
  462. package/clis/reddit/hot.js +6 -1
  463. package/clis/reddit/hot.test.js +18 -0
  464. package/clis/reddit/popular.js +1 -0
  465. package/clis/reddit/read.js +1 -0
  466. package/clis/reddit/save.js +1 -0
  467. package/clis/reddit/saved.js +1 -0
  468. package/clis/reddit/search.js +1 -0
  469. package/clis/reddit/subreddit.js +1 -0
  470. package/clis/reddit/subscribe.js +1 -0
  471. package/clis/reddit/upvote.js +1 -0
  472. package/clis/reddit/upvoted.js +1 -0
  473. package/clis/reddit/user-comments.js +1 -0
  474. package/clis/reddit/user-posts.js +1 -0
  475. package/clis/reddit/user.js +1 -0
  476. package/clis/reuters/search.js +1 -0
  477. package/clis/sinablog/article.js +1 -0
  478. package/clis/sinablog/hot.js +1 -0
  479. package/clis/sinablog/search.js +1 -0
  480. package/clis/sinablog/user.js +1 -0
  481. package/clis/sinafinance/news.js +1 -0
  482. package/clis/sinafinance/rolling-news.js +1 -0
  483. package/clis/sinafinance/stock-rank.js +1 -0
  484. package/clis/sinafinance/stock.js +1 -0
  485. package/clis/smzdm/search.js +1 -0
  486. package/clis/spotify/spotify.js +11 -0
  487. package/clis/stackoverflow/bounties.js +11 -3
  488. package/clis/stackoverflow/hot.js +10 -2
  489. package/clis/stackoverflow/read.js +314 -0
  490. package/clis/stackoverflow/search.js +10 -2
  491. package/clis/stackoverflow/stackoverflow.test.js +346 -0
  492. package/clis/stackoverflow/unanswered.js +9 -2
  493. package/clis/steam/top-sellers.js +1 -0
  494. package/clis/substack/feed.js +1 -0
  495. package/clis/substack/publication.js +1 -0
  496. package/clis/substack/search.js +1 -0
  497. package/clis/taobao/add-cart.js +1 -0
  498. package/clis/taobao/cart.js +1 -0
  499. package/clis/taobao/detail.js +1 -0
  500. package/clis/taobao/reviews.js +1 -0
  501. package/clis/taobao/search.js +1 -0
  502. package/clis/tdx/hot-rank.js +1 -0
  503. package/clis/ths/hot-rank.js +1 -0
  504. package/clis/tieba/hot.js +2 -1
  505. package/clis/tieba/posts.js +1 -0
  506. package/clis/tieba/read.js +1 -0
  507. package/clis/tieba/search.js +2 -1
  508. package/clis/tiktok/comment.js +1 -0
  509. package/clis/tiktok/explore.js +1 -0
  510. package/clis/tiktok/follow.js +1 -0
  511. package/clis/tiktok/following.js +1 -0
  512. package/clis/tiktok/friends.js +1 -0
  513. package/clis/tiktok/like.js +1 -0
  514. package/clis/tiktok/live.js +1 -0
  515. package/clis/tiktok/notifications.js +1 -0
  516. package/clis/tiktok/profile.js +1 -0
  517. package/clis/tiktok/save.js +1 -0
  518. package/clis/tiktok/search.js +1 -0
  519. package/clis/tiktok/unfollow.js +1 -0
  520. package/clis/tiktok/unlike.js +1 -0
  521. package/clis/tiktok/unsave.js +1 -0
  522. package/clis/tiktok/user.js +1 -0
  523. package/clis/toutiao/articles.js +1 -0
  524. package/clis/twitter/accept.js +1 -0
  525. package/clis/twitter/article.js +1 -0
  526. package/clis/twitter/block.js +1 -0
  527. package/clis/twitter/bookmark.js +1 -0
  528. package/clis/twitter/bookmarks.js +2 -1
  529. package/clis/twitter/delete.js +1 -0
  530. package/clis/twitter/download.js +1 -0
  531. package/clis/twitter/follow.js +1 -0
  532. package/clis/twitter/followers.js +1 -0
  533. package/clis/twitter/following.js +1 -0
  534. package/clis/twitter/hide-reply.js +1 -0
  535. package/clis/twitter/like.js +1 -0
  536. package/clis/twitter/likes.js +2 -1
  537. package/clis/twitter/list-add.js +1 -0
  538. package/clis/twitter/list-remove.js +1 -0
  539. package/clis/twitter/list-tweets.js +1 -0
  540. package/clis/twitter/lists.js +1 -0
  541. package/clis/twitter/notifications.js +1 -0
  542. package/clis/twitter/post.js +1 -0
  543. package/clis/twitter/profile.js +1 -0
  544. package/clis/twitter/reply-dm.js +1 -0
  545. package/clis/twitter/reply.js +1 -0
  546. package/clis/twitter/search.js +1 -0
  547. package/clis/twitter/thread.js +1 -0
  548. package/clis/twitter/timeline.js +1 -0
  549. package/clis/twitter/trending.js +11 -12
  550. package/clis/twitter/trending.test.js +15 -0
  551. package/clis/twitter/tweets.js +2 -1
  552. package/clis/twitter/tweets.test.js +2 -2
  553. package/clis/twitter/unblock.js +1 -0
  554. package/clis/twitter/unbookmark.js +1 -0
  555. package/clis/twitter/unfollow.js +1 -0
  556. package/clis/uiverse/code.js +1 -0
  557. package/clis/uiverse/preview.js +1 -0
  558. package/clis/v2ex/daily.js +1 -0
  559. package/clis/v2ex/hot.js +1 -0
  560. package/clis/v2ex/latest.js +1 -0
  561. package/clis/v2ex/me.js +1 -0
  562. package/clis/v2ex/member.js +1 -0
  563. package/clis/v2ex/node.js +1 -0
  564. package/clis/v2ex/nodes.js +1 -0
  565. package/clis/v2ex/notifications.js +1 -0
  566. package/clis/v2ex/replies.js +1 -0
  567. package/clis/v2ex/topic.js +1 -0
  568. package/clis/v2ex/user.js +1 -0
  569. package/clis/wanfang/search.js +1 -0
  570. package/clis/web/read.js +1 -0
  571. package/clis/weibo/comments.js +1 -0
  572. package/clis/weibo/favorites.js +1 -0
  573. package/clis/weibo/feed.js +3 -1
  574. package/clis/weibo/hot.js +1 -0
  575. package/clis/weibo/me.js +1 -0
  576. package/clis/weibo/post.js +1 -0
  577. package/clis/weibo/publish.js +1 -0
  578. package/clis/weibo/search.js +8 -2
  579. package/clis/weibo/user.js +1 -0
  580. package/clis/weixin/create-draft.js +1 -0
  581. package/clis/weixin/download.js +1 -0
  582. package/clis/weixin/drafts.js +1 -0
  583. package/clis/weread/ai-outline.js +1 -0
  584. package/clis/weread/book.js +1 -0
  585. package/clis/weread/highlights.js +1 -0
  586. package/clis/weread/notebooks.js +1 -0
  587. package/clis/weread/notes.js +1 -0
  588. package/clis/weread/ranking.js +1 -0
  589. package/clis/weread/search.js +1 -0
  590. package/clis/weread/shelf.js +1 -0
  591. package/clis/wikipedia/random.js +1 -0
  592. package/clis/wikipedia/search.js +1 -0
  593. package/clis/wikipedia/summary.js +1 -0
  594. package/clis/wikipedia/trending.js +1 -0
  595. package/clis/xianyu/chat.js +1 -0
  596. package/clis/xianyu/item.js +1 -0
  597. package/clis/xianyu/search.js +1 -0
  598. package/clis/xiaoe/catalog.js +2 -1
  599. package/clis/xiaoe/content.js +1 -0
  600. package/clis/xiaoe/courses.js +1 -0
  601. package/clis/xiaoe/detail.js +1 -0
  602. package/clis/xiaoe/play-url.js +1 -0
  603. package/clis/xiaohongshu/comments.js +1 -0
  604. package/clis/xiaohongshu/creator-note-detail.js +1 -0
  605. package/clis/xiaohongshu/creator-notes-summary.js +1 -0
  606. package/clis/xiaohongshu/creator-notes.js +1 -0
  607. package/clis/xiaohongshu/creator-profile.js +1 -0
  608. package/clis/xiaohongshu/creator-stats.js +1 -0
  609. package/clis/xiaohongshu/download.js +1 -0
  610. package/clis/xiaohongshu/feed.js +2 -1
  611. package/clis/xiaohongshu/note.js +1 -0
  612. package/clis/xiaohongshu/notifications.js +1 -0
  613. package/clis/xiaohongshu/publish.js +1 -0
  614. package/clis/xiaohongshu/search.js +1 -0
  615. package/clis/xiaohongshu/user.js +1 -0
  616. package/clis/xiaoyuzhou/download.js +1 -0
  617. package/clis/xiaoyuzhou/episode.js +1 -0
  618. package/clis/xiaoyuzhou/podcast-episodes.js +1 -0
  619. package/clis/xiaoyuzhou/podcast.js +1 -0
  620. package/clis/xiaoyuzhou/transcript.js +1 -0
  621. package/clis/xueqiu/comments.js +1 -0
  622. package/clis/xueqiu/earnings-date.js +1 -0
  623. package/clis/xueqiu/feed.js +1 -0
  624. package/clis/xueqiu/fund-holdings.js +1 -0
  625. package/clis/xueqiu/fund-snapshot.js +1 -0
  626. package/clis/xueqiu/groups.js +1 -0
  627. package/clis/xueqiu/hot-stock.js +1 -0
  628. package/clis/xueqiu/hot.js +1 -0
  629. package/clis/xueqiu/kline.js +1 -0
  630. package/clis/xueqiu/search.js +1 -0
  631. package/clis/xueqiu/stock.js +1 -0
  632. package/clis/xueqiu/watchlist.js +1 -0
  633. package/clis/yahoo-finance/quote.js +1 -0
  634. package/clis/yollomi/background.js +1 -0
  635. package/clis/yollomi/edit.js +1 -0
  636. package/clis/yollomi/face-swap.js +1 -0
  637. package/clis/yollomi/generate.js +1 -0
  638. package/clis/yollomi/models.js +1 -0
  639. package/clis/yollomi/object-remover.js +1 -0
  640. package/clis/yollomi/remove-bg.js +1 -0
  641. package/clis/yollomi/restore.js +1 -0
  642. package/clis/yollomi/try-on.js +1 -0
  643. package/clis/yollomi/upload.js +1 -0
  644. package/clis/yollomi/upscale.js +1 -0
  645. package/clis/yollomi/video.js +1 -0
  646. package/clis/youtube/channel.js +1 -0
  647. package/clis/youtube/comments.js +1 -0
  648. package/clis/youtube/feed.js +8 -7
  649. package/clis/youtube/feed.test.js +131 -0
  650. package/clis/youtube/history.js +1 -0
  651. package/clis/youtube/like.js +1 -0
  652. package/clis/youtube/playlist.js +1 -0
  653. package/clis/youtube/search.js +1 -0
  654. package/clis/youtube/subscribe.js +1 -0
  655. package/clis/youtube/subscriptions.js +1 -0
  656. package/clis/youtube/transcript.js +1 -0
  657. package/clis/youtube/unlike.js +1 -0
  658. package/clis/youtube/unsubscribe.js +1 -0
  659. package/clis/youtube/video.js +1 -0
  660. package/clis/youtube/watch-later.js +1 -0
  661. package/clis/yuanbao/ask.js +1 -0
  662. package/clis/yuanbao/new.js +1 -0
  663. package/clis/zhihu/answer.js +1 -0
  664. package/clis/zhihu/collection.js +1 -0
  665. package/clis/zhihu/collections.js +1 -0
  666. package/clis/zhihu/comment.js +1 -0
  667. package/clis/zhihu/download.js +1 -0
  668. package/clis/zhihu/favorite.js +1 -0
  669. package/clis/zhihu/follow.js +1 -0
  670. package/clis/zhihu/hot.js +1 -0
  671. package/clis/zhihu/like.js +1 -0
  672. package/clis/zhihu/question.js +1 -0
  673. package/clis/zhihu/search.js +1 -0
  674. package/clis/zlibrary/info.js +1 -0
  675. package/clis/zlibrary/search.js +1 -0
  676. package/clis/zsxq/dynamics.js +1 -0
  677. package/clis/zsxq/groups.js +1 -0
  678. package/clis/zsxq/search.js +1 -0
  679. package/clis/zsxq/topic.js +1 -0
  680. package/clis/zsxq/topics.js +1 -0
  681. package/dist/src/adapter-shadow.d.ts +11 -0
  682. package/dist/src/adapter-shadow.js +72 -0
  683. package/dist/src/adapter-shadow.test.d.ts +1 -0
  684. package/dist/src/adapter-shadow.test.js +49 -0
  685. package/dist/src/adapter-source.test.js +1 -1
  686. package/dist/src/browser/analyze.test.js +2 -0
  687. package/dist/src/browser/base-page.d.ts +15 -2
  688. package/dist/src/browser/base-page.js +159 -8
  689. package/dist/src/browser/base-page.test.js +103 -1
  690. package/dist/src/browser/cdp.js +54 -0
  691. package/dist/src/browser/cdp.test.js +26 -0
  692. package/dist/src/browser/dom-helpers.d.ts +1 -1
  693. package/dist/src/browser/dom-helpers.js +15 -3
  694. package/dist/src/browser/page.d.ts +1 -0
  695. package/dist/src/browser/page.js +7 -1
  696. package/dist/src/browser/page.test.js +17 -0
  697. package/dist/src/browser/target-resolver.d.ts +17 -2
  698. package/dist/src/browser/target-resolver.js +85 -4
  699. package/dist/src/browser/verify-fixture.d.ts +7 -1
  700. package/dist/src/browser/verify-fixture.js +105 -0
  701. package/dist/src/browser/verify-fixture.test.js +59 -1
  702. package/dist/src/build-manifest.d.ts +68 -33
  703. package/dist/src/build-manifest.js +178 -29
  704. package/dist/src/build-manifest.test.js +85 -5
  705. package/dist/src/capabilityRouting.test.js +1 -1
  706. package/dist/src/cli.js +150 -11
  707. package/dist/src/cli.test.js +237 -0
  708. package/dist/src/commanderAdapter.d.ts +1 -1
  709. package/dist/src/commanderAdapter.js +17 -7
  710. package/dist/src/commanderAdapter.test.js +7 -6
  711. package/dist/src/convention-audit.d.ts +50 -0
  712. package/dist/src/convention-audit.js +546 -0
  713. package/dist/src/convention-audit.test.d.ts +1 -0
  714. package/dist/src/convention-audit.test.js +226 -0
  715. package/dist/src/discovery.js +2 -0
  716. package/dist/src/doctor.d.ts +2 -0
  717. package/dist/src/doctor.js +6 -0
  718. package/dist/src/doctor.test.js +36 -1
  719. package/dist/src/engine.test.js +10 -10
  720. package/dist/src/execution.js +1 -1
  721. package/dist/src/execution.test.js +9 -9
  722. package/dist/src/help.d.ts +15 -0
  723. package/dist/src/help.js +149 -0
  724. package/dist/src/main.js +13 -7
  725. package/dist/src/manifest-types.d.ts +41 -0
  726. package/dist/src/manifest-types.js +9 -0
  727. package/dist/src/plugin.test.js +26 -26
  728. package/dist/src/registry.d.ts +5 -0
  729. package/dist/src/registry.js +8 -0
  730. package/dist/src/registry.test.js +27 -20
  731. package/dist/src/serialization.d.ts +4 -0
  732. package/dist/src/serialization.js +27 -1
  733. package/dist/src/serialization.test.js +24 -2
  734. package/dist/src/types.d.ts +2 -0
  735. package/package.json +5 -2
  736. package/scripts/check-listing-id-pairing.mjs +193 -0
  737. package/scripts/check-silent-column-drop.mjs +105 -0
  738. package/scripts/check-typed-error-lint.mjs +118 -0
  739. package/scripts/silent-column-drop-baseline.json +962 -0
  740. package/scripts/typed-error-lint-baseline.json +1586 -0
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
@@ -1759,6 +1875,7 @@ cli({
1759
1875
  fs.mkdirSync(dir, { recursive: true });
1760
1876
  fs.writeFileSync(filePath, template, 'utf-8');
1761
1877
  console.log(`Created: ${filePath}`);
1878
+ console.log('First time on this site? Run: opencli browser analyze <url>');
1762
1879
  console.log(`Edit the file to implement your adapter, then run: opencli browser verify ${name}`);
1763
1880
  }
1764
1881
  catch (err) {
@@ -1773,6 +1890,7 @@ cli({
1773
1890
  .option('--update-fixture', 'Overwrite an existing fixture with one derived from current output')
1774
1891
  .option('--no-fixture', 'Ignore any fixture file for this run (no value-level validation)')
1775
1892
  .option('--strict-memory', 'Fail (not just warn) when ~/.opencli/sites/<site>/endpoints.json or notes.md is missing')
1893
+ .option('--seed-args <value>', 'Seed args when no fixture exists; use JSON array/object for multiple args or flags')
1776
1894
  .option('--trace <mode>', 'Trace capture for the adapter subprocess: off, on, retain-on-failure', 'off')
1777
1895
  .description('Execute an adapter and validate output; uses fixture at ~/.opencli/sites/<site>/verify/<cmd>.json when present')
1778
1896
  .action(async (name, opts = {}) => {
@@ -1790,7 +1908,7 @@ cli({
1790
1908
  return;
1791
1909
  }
1792
1910
  const { execFileSync } = await import('node:child_process');
1793
- const { loadFixture, writeFixture, deriveFixture, validateRows, fixturePath, expandFixtureArgs } = await import('./browser/verify-fixture.js');
1911
+ const { loadFixture, writeFixture, deriveFixture, validateRows, validateRowShape, fixturePath, expandFixtureArgs, parseSeedArgs } = await import('./browser/verify-fixture.js');
1794
1912
  const filePath = path.join(os.homedir(), '.opencli', 'clis', site, `${command}.js`);
1795
1913
  if (!fs.existsSync(filePath)) {
1796
1914
  console.error(`Adapter not found: ${filePath}`);
@@ -1807,9 +1925,10 @@ cli({
1807
1925
  // - array form ["123", "--limit", "3"] → verbatim (for positional subjects)
1808
1926
  const adapterSrc = fs.readFileSync(filePath, 'utf-8');
1809
1927
  const hasLimitArg = /['"]limit['"]/.test(adapterSrc);
1810
- const fixtureArgs = fixture?.args;
1811
- const cliArgs = expandFixtureArgs(fixtureArgs);
1812
- if (cliArgs.length === 0 && hasLimitArg)
1928
+ const seedArgs = parseSeedArgs(opts.seedArgs);
1929
+ const explicitArgs = fixture?.args ?? seedArgs;
1930
+ const cliArgs = expandFixtureArgs(explicitArgs);
1931
+ if (explicitArgs === undefined && cliArgs.length === 0 && hasLimitArg)
1813
1932
  cliArgs.push('--limit', '3');
1814
1933
  const traceArgs = opts.trace && opts.trace !== 'off' ? ['--trace', opts.trace] : [];
1815
1934
  const argDisplay = [...cliArgs, ...traceArgs].join(' ');
@@ -1851,6 +1970,20 @@ cli({
1851
1970
  }
1852
1971
  console.log(renderVerifyPreview(rows));
1853
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
+ }
1854
1987
  // ── Fixture handling ───────────────────────────────────────────
1855
1988
  if (opts.writeFixture || opts.updateFixture) {
1856
1989
  if (fixture && !opts.updateFixture) {
@@ -1858,10 +1991,10 @@ cli({
1858
1991
  console.log(` Use --update-fixture to overwrite.`);
1859
1992
  }
1860
1993
  else {
1861
- const seedArgs = fixtureArgs !== undefined
1862
- ? fixtureArgs
1994
+ const fixtureArgs = explicitArgs !== undefined
1995
+ ? explicitArgs
1863
1996
  : (hasLimitArg ? { limit: 3 } : undefined);
1864
- const derived = deriveFixture(rows, seedArgs);
1997
+ const derived = deriveFixture(rows, fixtureArgs);
1865
1998
  const p = writeFixture(site, command, derived);
1866
1999
  console.log(`\n ${fixture ? '↻ Updated' : '✎ Wrote'} fixture: ${p}`);
1867
2000
  console.log(` Review and hand-tune the derived expectations (add patterns / notEmpty, tighten rowCount).`);
@@ -2404,7 +2537,13 @@ cli({
2404
2537
  // ── Dynamic adapter commands ──────────────────────────────────────────────
2405
2538
  const siteGroups = new Map();
2406
2539
  siteGroups.set('antigravity', antigravityCmd);
2407
- 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));
2408
2547
  // ── Unknown command fallback ──────────────────────────────────────────────
2409
2548
  // Security: do NOT auto-discover and register arbitrary system binaries.
2410
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');
@@ -151,6 +277,97 @@ describe('browser verify', () => {
151
277
  fs.rmSync(fakeHome, { recursive: true, force: true });
152
278
  }
153
279
  });
280
+ it('uses --seed-args when no fixture args exist', async () => {
281
+ const originalHome = process.env.HOME;
282
+ const originalUserProfile = process.env.USERPROFILE;
283
+ const fakeHome = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-browser-verify-seed-'));
284
+ process.env.HOME = fakeHome;
285
+ process.env.USERPROFILE = fakeHome;
286
+ try {
287
+ const adapterDir = path.join(fakeHome, '.opencli', 'clis', 'hn');
288
+ fs.mkdirSync(adapterDir, { recursive: true });
289
+ fs.writeFileSync(path.join(adapterDir, 'top.js'), 'export default {};\n', 'utf-8');
290
+ const program = createProgram('', '');
291
+ await program.parseAsync(['node', 'opencli', 'browser', 'verify', 'hn/top', '--no-fixture', '--seed-args', 'opencli-verify']);
292
+ expect(mockExecFileSync).toHaveBeenCalledTimes(1);
293
+ const [, execArgs] = mockExecFileSync.mock.calls[0];
294
+ expect(execArgs.slice(-5)).toEqual(['hn', 'top', 'opencli-verify', '--format', 'json']);
295
+ }
296
+ finally {
297
+ if (originalHome === undefined)
298
+ delete process.env.HOME;
299
+ else
300
+ process.env.HOME = originalHome;
301
+ if (originalUserProfile === undefined)
302
+ delete process.env.USERPROFILE;
303
+ else
304
+ process.env.USERPROFILE = originalUserProfile;
305
+ fs.rmSync(fakeHome, { recursive: true, force: true });
306
+ }
307
+ });
308
+ it('writes --seed-args into a starter fixture', async () => {
309
+ const originalHome = process.env.HOME;
310
+ const originalUserProfile = process.env.USERPROFILE;
311
+ const fakeHome = fs.mkdtempSync(path.join(os.tmpdir(), 'opencli-browser-verify-write-seed-'));
312
+ process.env.HOME = fakeHome;
313
+ process.env.USERPROFILE = fakeHome;
314
+ mockExecFileSync.mockReturnValue(JSON.stringify([{ title: 'ok' }]));
315
+ try {
316
+ const adapterDir = path.join(fakeHome, '.opencli', 'clis', 'hn');
317
+ fs.mkdirSync(adapterDir, { recursive: true });
318
+ fs.writeFileSync(path.join(adapterDir, 'top.js'), 'export default {};\n', 'utf-8');
319
+ const program = createProgram('', '');
320
+ await program.parseAsync(['node', 'opencli', 'browser', 'verify', 'hn/top', '--write-fixture', '--seed-args', 'opencli-verify']);
321
+ const fixtureFile = path.join(fakeHome, '.opencli', 'sites', 'hn', 'verify', 'top.json');
322
+ const fixture = JSON.parse(fs.readFileSync(fixtureFile, 'utf-8'));
323
+ expect(fixture.args).toEqual(['opencli-verify']);
324
+ expect(fixture.expect.columns).toEqual(['title']);
325
+ }
326
+ finally {
327
+ if (originalHome === undefined)
328
+ delete process.env.HOME;
329
+ else
330
+ process.env.HOME = originalHome;
331
+ if (originalUserProfile === undefined)
332
+ delete process.env.USERPROFILE;
333
+ else
334
+ process.env.USERPROFILE = originalUserProfile;
335
+ fs.rmSync(fakeHome, { recursive: true, force: true });
336
+ }
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
+ });
154
371
  });
155
372
  describe('profile list', () => {
156
373
  const stdoutSpy = vi.spyOn(console, 'log').mockImplementation(() => { });
@@ -240,6 +457,7 @@ describe('browser tab targeting commands', () => {
240
457
  selectTab: vi.fn().mockResolvedValue(undefined),
241
458
  newTab: vi.fn().mockResolvedValue('tab-3'),
242
459
  closeTab: vi.fn().mockResolvedValue(undefined),
460
+ handleJavaScriptDialog: vi.fn().mockResolvedValue(undefined),
243
461
  frames: vi.fn().mockResolvedValue([
244
462
  { index: 0, frameId: 'frame-1', url: 'https://x.example/embed', name: 'x-embed' },
245
463
  ]),
@@ -312,6 +530,25 @@ describe('browser tab targeting commands', () => {
312
530
  expect(out.error.code).toBe('bound_session_missing');
313
531
  expect(process.exitCode).toBeDefined();
314
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
+ });
315
552
  it('binds browser commands to an explicit target tab via --tab', async () => {
316
553
  const program = createProgram('', '');
317
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
  }