@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.
- package/README.zh-CN.md +2 -1
- package/cli-manifest.json +1417 -24
- package/clis/1688/assets.js +1 -0
- package/clis/1688/download.js +1 -0
- package/clis/1688/item.js +1 -0
- package/clis/1688/search.js +2 -1
- package/clis/1688/store.js +1 -0
- package/clis/36kr/article.js +1 -0
- package/clis/36kr/hot.js +1 -0
- package/clis/36kr/news.js +1 -0
- package/clis/36kr/search.js +1 -0
- package/clis/51job/company.js +1 -0
- package/clis/51job/detail.js +1 -0
- package/clis/51job/hot.js +1 -0
- package/clis/51job/search.js +1 -0
- package/clis/amazon/bestsellers.js +1 -0
- package/clis/amazon/discussion.js +1 -0
- package/clis/amazon/movers-shakers.js +1 -0
- package/clis/amazon/new-releases.js +1 -0
- package/clis/amazon/offer.js +1 -0
- package/clis/amazon/product.js +1 -0
- package/clis/amazon/rankings.js +1 -0
- package/clis/amazon/search.js +1 -0
- package/clis/antigravity/dump.js +1 -0
- package/clis/antigravity/extract-code.js +1 -0
- package/clis/antigravity/model.js +1 -0
- package/clis/antigravity/new.js +1 -0
- package/clis/antigravity/read.js +1 -0
- package/clis/antigravity/send.js +1 -0
- package/clis/antigravity/status.js +1 -0
- package/clis/antigravity/watch.js +1 -0
- package/clis/apple-podcasts/episodes.js +1 -0
- package/clis/apple-podcasts/search.js +1 -0
- package/clis/apple-podcasts/top.js +1 -0
- package/clis/arxiv/arxiv.test.js +112 -0
- package/clis/arxiv/paper.js +4 -3
- package/clis/arxiv/recent.js +33 -0
- package/clis/arxiv/search.js +19 -7
- package/clis/arxiv/utils.js +68 -5
- package/clis/baidu-scholar/search.js +1 -0
- package/clis/band/bands.js +1 -0
- package/clis/band/mentions.js +1 -0
- package/clis/band/post.js +1 -0
- package/clis/band/posts.js +1 -0
- package/clis/barchart/flow.js +1 -0
- package/clis/barchart/greeks.js +1 -0
- package/clis/barchart/options.js +1 -0
- package/clis/barchart/quote.js +1 -0
- package/clis/bbc/news.js +1 -0
- package/clis/bilibili/comments.js +1 -0
- package/clis/bilibili/download.js +1 -0
- package/clis/bilibili/dynamic.js +1 -0
- package/clis/bilibili/favorite.js +1 -0
- package/clis/bilibili/feed.js +2 -0
- package/clis/bilibili/following.js +1 -0
- package/clis/bilibili/history.js +1 -0
- package/clis/bilibili/hot.js +6 -1
- package/clis/bilibili/hot.test.js +17 -0
- package/clis/bilibili/me.js +1 -1
- package/clis/bilibili/ranking.js +1 -0
- package/clis/bilibili/search.js +1 -1
- package/clis/bilibili/subtitle.js +1 -0
- package/clis/bilibili/user-videos.js +1 -0
- package/clis/bilibili/video.js +1 -0
- package/clis/binance/asks.js +1 -0
- package/clis/binance/depth.js +1 -0
- package/clis/binance/gainers.js +1 -0
- package/clis/binance/klines.js +1 -0
- package/clis/binance/losers.js +1 -0
- package/clis/binance/pairs.js +1 -0
- package/clis/binance/price.js +1 -0
- package/clis/binance/prices.js +1 -0
- package/clis/binance/ticker.js +1 -0
- package/clis/binance/top.js +1 -0
- package/clis/binance/trades.js +1 -0
- package/clis/bloomberg/businessweek.js +1 -0
- package/clis/bloomberg/economics.js +1 -0
- package/clis/bloomberg/feeds.js +1 -0
- package/clis/bloomberg/industries.js +1 -0
- package/clis/bloomberg/main.js +1 -0
- package/clis/bloomberg/markets.js +1 -0
- package/clis/bloomberg/news.js +1 -0
- package/clis/bloomberg/opinions.js +1 -0
- package/clis/bloomberg/politics.js +1 -0
- package/clis/bloomberg/tech.js +1 -0
- package/clis/bluesky/feeds.js +1 -0
- package/clis/bluesky/followers.js +1 -0
- package/clis/bluesky/following.js +1 -0
- package/clis/bluesky/profile.js +1 -0
- package/clis/bluesky/search.js +1 -0
- package/clis/bluesky/starter-packs.js +1 -0
- package/clis/bluesky/thread.js +1 -0
- package/clis/bluesky/trending.js +1 -0
- package/clis/bluesky/user.js +3 -1
- package/clis/boss/batchgreet.js +1 -0
- package/clis/boss/chatlist.js +1 -0
- package/clis/boss/chatmsg.js +1 -0
- package/clis/boss/detail.js +1 -0
- package/clis/boss/exchange.js +1 -0
- package/clis/boss/greet.js +1 -0
- package/clis/boss/invite.js +1 -0
- package/clis/boss/joblist.js +1 -0
- package/clis/boss/mark.js +1 -0
- package/clis/boss/recommend.js +1 -0
- package/clis/boss/resume.js +1 -0
- package/clis/boss/search.js +1 -0
- package/clis/boss/send.js +1 -0
- package/clis/boss/stats.js +1 -0
- package/clis/chaoxing/assignments.js +1 -0
- package/clis/chaoxing/exams.js +1 -0
- package/clis/chatgpt/image.js +1 -0
- package/clis/chatgpt-app/ask.js +1 -0
- package/clis/chatgpt-app/model.js +1 -0
- package/clis/chatgpt-app/new.js +1 -0
- package/clis/chatgpt-app/read.js +1 -0
- package/clis/chatgpt-app/send.js +1 -0
- package/clis/chatgpt-app/status.js +1 -0
- package/clis/chatwise/ask.js +1 -0
- package/clis/chatwise/export.js +1 -0
- package/clis/chatwise/history.js +1 -0
- package/clis/chatwise/model.js +1 -0
- package/clis/chatwise/read.js +1 -0
- package/clis/chatwise/send.js +1 -0
- package/clis/claude/ask.js +1 -0
- package/clis/claude/detail.js +1 -0
- package/clis/claude/history.js +1 -0
- package/clis/claude/new.js +1 -0
- package/clis/claude/read.js +1 -0
- package/clis/claude/send.js +1 -0
- package/clis/claude/status.js +1 -0
- package/clis/cnki/search.js +1 -0
- package/clis/codex/ask.js +1 -0
- package/clis/codex/export.js +1 -0
- package/clis/codex/extract-diff.js +1 -0
- package/clis/codex/history.js +1 -0
- package/clis/codex/model.js +1 -0
- package/clis/codex/read.js +1 -0
- package/clis/codex/send.js +1 -0
- package/clis/coupang/add-to-cart.js +1 -0
- package/clis/coupang/search.js +1 -0
- package/clis/ctrip/search.js +1 -0
- package/clis/cursor/ask.js +1 -0
- package/clis/cursor/composer.js +1 -0
- package/clis/cursor/export.js +1 -0
- package/clis/cursor/extract-code.js +1 -0
- package/clis/cursor/history.js +1 -0
- package/clis/cursor/model.js +1 -0
- package/clis/cursor/read.js +1 -0
- package/clis/cursor/send.js +1 -0
- package/clis/dblp/dblp.test.js +397 -0
- package/clis/dblp/paper.js +40 -0
- package/clis/dblp/search.js +45 -0
- package/clis/dblp/utils.js +290 -0
- package/clis/deepseek/ask.js +1 -0
- package/clis/deepseek/history.js +1 -0
- package/clis/deepseek/new.js +1 -0
- package/clis/deepseek/read.js +1 -0
- package/clis/deepseek/status.js +1 -0
- package/clis/devto/devto.test.js +236 -0
- package/clis/devto/read.js +103 -0
- package/clis/devto/tag.js +5 -1
- package/clis/devto/top.js +5 -1
- package/clis/devto/user.js +5 -1
- package/clis/dianping/__fixtures__/search.html +168 -0
- package/clis/dianping/__fixtures__/shop.html +6 -0
- package/clis/dianping/dianping.test.js +424 -0
- package/clis/dianping/search.js +154 -0
- package/clis/dianping/shop.js +173 -0
- package/clis/dianping/utils.js +157 -0
- package/clis/dictionary/examples.js +1 -0
- package/clis/dictionary/search.js +1 -0
- package/clis/dictionary/synonyms.js +1 -0
- package/clis/discord-app/channels.js +1 -0
- package/clis/discord-app/delete.js +1 -0
- package/clis/discord-app/members.js +1 -0
- package/clis/discord-app/read.js +1 -0
- package/clis/discord-app/search.js +1 -0
- package/clis/discord-app/send.js +1 -0
- package/clis/discord-app/servers.js +1 -0
- package/clis/discord-app/status.js +1 -0
- package/clis/douban/book-hot.js +1 -0
- package/clis/douban/download.js +1 -0
- package/clis/douban/marks.js +1 -0
- package/clis/douban/movie-hot.js +2 -1
- package/clis/douban/movie-hot.test.js +14 -0
- package/clis/douban/photos.js +2 -1
- package/clis/douban/reviews.js +1 -0
- package/clis/douban/search.js +1 -0
- package/clis/douban/subject.js +1 -0
- package/clis/douban/top250.js +1 -0
- package/clis/douban/utils.js +11 -13
- package/clis/douban/utils.test.js +79 -0
- package/clis/doubao/ask.js +1 -0
- package/clis/doubao/detail.js +1 -0
- package/clis/doubao/history.js +1 -0
- package/clis/doubao/meeting-summary.js +1 -0
- package/clis/doubao/meeting-transcript.js +1 -0
- package/clis/doubao/new.js +1 -0
- package/clis/doubao/read.js +1 -0
- package/clis/doubao/send.js +1 -0
- package/clis/doubao/status.js +1 -0
- package/clis/doubao-app/ask.js +1 -0
- package/clis/doubao-app/dump.js +1 -0
- package/clis/doubao-app/new.js +1 -0
- package/clis/doubao-app/read.js +1 -0
- package/clis/doubao-app/screenshot.js +1 -0
- package/clis/doubao-app/send.js +1 -0
- package/clis/doubao-app/status.js +1 -0
- package/clis/douyin/activities.js +1 -0
- package/clis/douyin/collections.js +1 -0
- package/clis/douyin/delete.js +1 -0
- package/clis/douyin/draft.js +1 -0
- package/clis/douyin/drafts.js +1 -0
- package/clis/douyin/hashtag.js +1 -0
- package/clis/douyin/location.js +1 -0
- package/clis/douyin/profile.js +1 -0
- package/clis/douyin/publish.js +1 -0
- package/clis/douyin/stats.js +1 -0
- package/clis/douyin/update.js +1 -0
- package/clis/douyin/user-videos.js +1 -0
- package/clis/douyin/videos.js +1 -0
- package/clis/eastmoney/announcement.js +1 -0
- package/clis/eastmoney/convertible.js +1 -0
- package/clis/eastmoney/etf.js +1 -0
- package/clis/eastmoney/holders.js +1 -0
- package/clis/eastmoney/hot-rank.js +1 -0
- package/clis/eastmoney/index-board.js +1 -0
- package/clis/eastmoney/kline.js +1 -0
- package/clis/eastmoney/kuaixun.js +1 -0
- package/clis/eastmoney/longhu.js +1 -0
- package/clis/eastmoney/money-flow.js +1 -0
- package/clis/eastmoney/northbound.js +1 -0
- package/clis/eastmoney/quote.js +1 -0
- package/clis/eastmoney/rank.js +1 -0
- package/clis/eastmoney/sectors.js +1 -0
- package/clis/facebook/add-friend.js +1 -0
- package/clis/facebook/events.js +1 -0
- package/clis/facebook/feed.js +1 -0
- package/clis/facebook/friends.js +1 -0
- package/clis/facebook/groups.js +1 -0
- package/clis/facebook/join-group.js +1 -0
- package/clis/facebook/marketplace-inbox.js +1 -0
- package/clis/facebook/marketplace-listings.js +1 -0
- package/clis/facebook/memories.js +1 -0
- package/clis/facebook/notifications.js +1 -0
- package/clis/facebook/profile.js +1 -0
- package/clis/facebook/search.js +1 -0
- package/clis/gemini/ask.js +1 -0
- package/clis/gemini/deep-research-result.js +1 -0
- package/clis/gemini/deep-research.js +1 -0
- package/clis/gemini/image.js +1 -0
- package/clis/gemini/new.js +1 -0
- package/clis/gitee/search.js +1 -0
- package/clis/gitee/trending.js +1 -0
- package/clis/gitee/user.js +1 -0
- package/clis/google/news.js +1 -0
- package/clis/google/search.js +1 -0
- package/clis/google/suggest.js +1 -0
- package/clis/google/trends.js +1 -0
- package/clis/google-scholar/cite.js +1 -0
- package/clis/google-scholar/profile.js +1 -0
- package/clis/google-scholar/search.js +1 -0
- package/clis/gov-law/recent.js +1 -0
- package/clis/gov-law/search.js +1 -0
- package/clis/gov-policy/recent.js +1 -0
- package/clis/gov-policy/search.js +1 -0
- package/clis/grok/ask.js +1 -0
- package/clis/grok/image.ts +1 -0
- package/clis/hackernews/ask.js +3 -1
- package/clis/hackernews/best.js +3 -1
- package/clis/hackernews/hackernews.test.js +132 -0
- package/clis/hackernews/jobs.js +3 -1
- package/clis/hackernews/new.js +3 -1
- package/clis/hackernews/read.js +188 -0
- package/clis/hackernews/search.js +3 -1
- package/clis/hackernews/show.js +3 -1
- package/clis/hackernews/top.js +3 -1
- package/clis/hackernews/user.js +1 -0
- package/clis/hf/top.js +1 -0
- package/clis/hupu/detail.js +1 -0
- package/clis/hupu/hot.js +3 -1
- package/clis/hupu/like.js +1 -0
- package/clis/hupu/mentions.js +2 -1
- package/clis/hupu/reply.js +1 -0
- package/clis/hupu/search.js +3 -1
- package/clis/hupu/unlike.js +1 -0
- package/clis/imdb/person.js +1 -0
- package/clis/imdb/reviews.js +1 -0
- package/clis/imdb/search.js +1 -0
- package/clis/imdb/title.js +1 -0
- package/clis/imdb/top.js +1 -0
- package/clis/imdb/trending.js +1 -0
- package/clis/indeed/indeed.test.js +375 -0
- package/clis/indeed/job.js +86 -0
- package/clis/indeed/search.js +110 -0
- package/clis/indeed/utils.js +152 -0
- package/clis/instagram/collection-create.js +1 -0
- package/clis/instagram/collection-delete.js +1 -0
- package/clis/instagram/comment.js +1 -0
- package/clis/instagram/download.js +1 -0
- package/clis/instagram/explore.js +1 -0
- package/clis/instagram/follow.js +1 -0
- package/clis/instagram/followers.js +1 -0
- package/clis/instagram/following.js +1 -0
- package/clis/instagram/like.js +1 -0
- package/clis/instagram/note.js +1 -0
- package/clis/instagram/post.js +1 -0
- package/clis/instagram/profile.js +1 -0
- package/clis/instagram/reel.js +1 -0
- package/clis/instagram/save.js +1 -0
- package/clis/instagram/saved.js +1 -0
- package/clis/instagram/search.js +1 -0
- package/clis/instagram/story.js +1 -0
- package/clis/instagram/unfollow.js +1 -0
- package/clis/instagram/unlike.js +1 -0
- package/clis/instagram/unsave.js +1 -0
- package/clis/instagram/user.js +1 -0
- package/clis/jd/add-cart.js +1 -0
- package/clis/jd/cart.js +1 -0
- package/clis/jd/detail.js +1 -0
- package/clis/jd/item.js +1 -0
- package/clis/jd/reviews.js +1 -0
- package/clis/jd/search.js +1 -0
- package/clis/jianyu/detail.js +1 -0
- package/clis/jianyu/search.js +1 -0
- package/clis/jike/comment.js +1 -0
- package/clis/jike/create.js +1 -0
- package/clis/jike/feed.js +3 -1
- package/clis/jike/like.js +1 -0
- package/clis/jike/notifications.js +1 -0
- package/clis/jike/post.js +1 -0
- package/clis/jike/repost.js +1 -0
- package/clis/jike/search.js +3 -1
- package/clis/jike/topic.js +1 -0
- package/clis/jike/user.js +3 -1
- package/clis/jimeng/generate.js +1 -0
- package/clis/jimeng/history.js +1 -0
- package/clis/jimeng/new.js +1 -0
- package/clis/jimeng/workspaces.js +1 -0
- package/clis/ke/chengjiao.js +1 -0
- package/clis/ke/ershoufang.js +1 -0
- package/clis/ke/xiaoqu.js +1 -0
- package/clis/ke/zufang.js +1 -0
- package/clis/lesswrong/comments.js +1 -0
- package/clis/lesswrong/curated.js +1 -0
- package/clis/lesswrong/frontpage.js +1 -0
- package/clis/lesswrong/new.js +1 -0
- package/clis/lesswrong/read.js +1 -0
- package/clis/lesswrong/sequences.js +1 -0
- package/clis/lesswrong/shortform.js +1 -0
- package/clis/lesswrong/tag.js +1 -0
- package/clis/lesswrong/tags.js +1 -0
- package/clis/lesswrong/top-month.js +1 -0
- package/clis/lesswrong/top-week.js +1 -0
- package/clis/lesswrong/top-year.js +1 -0
- package/clis/lesswrong/top.js +1 -0
- package/clis/lesswrong/user-posts.js +1 -0
- package/clis/lesswrong/user.js +1 -0
- package/clis/linkedin/search.js +1 -0
- package/clis/linkedin/timeline.js +1 -0
- package/clis/linux-do/categories.js +1 -0
- package/clis/linux-do/category.js +1 -0
- package/clis/linux-do/feed.js +1 -0
- package/clis/linux-do/hot.js +1 -0
- package/clis/linux-do/latest.js +1 -0
- package/clis/linux-do/search.js +1 -0
- package/clis/linux-do/tags.js +2 -1
- package/clis/linux-do/topic-content.js +1 -0
- package/clis/linux-do/topic.js +1 -0
- package/clis/linux-do/user-posts.js +1 -0
- package/clis/linux-do/user-topics.js +1 -0
- package/clis/lobsters/active.js +4 -1
- package/clis/lobsters/hot.js +4 -1
- package/clis/lobsters/lobsters.test.js +169 -0
- package/clis/lobsters/newest.js +4 -1
- package/clis/lobsters/read.js +196 -0
- package/clis/lobsters/tag.js +4 -1
- package/clis/maimai/search-talents.js +1 -0
- package/clis/medium/feed.js +1 -0
- package/clis/medium/search.js +1 -0
- package/clis/medium/user.js +1 -0
- package/clis/mubu/doc.js +1 -0
- package/clis/mubu/docs.js +1 -0
- package/clis/mubu/notes.js +1 -0
- package/clis/mubu/recent.js +1 -0
- package/clis/mubu/search.js +1 -0
- package/clis/notebooklm/current.js +1 -0
- package/clis/notebooklm/get.js +1 -0
- package/clis/notebooklm/history.js +1 -0
- package/clis/notebooklm/list.js +1 -0
- package/clis/notebooklm/note-list.js +1 -0
- package/clis/notebooklm/notes-get.js +1 -0
- package/clis/notebooklm/open.js +1 -0
- package/clis/notebooklm/source-fulltext.js +1 -0
- package/clis/notebooklm/source-get.js +1 -0
- package/clis/notebooklm/source-guide.js +1 -0
- package/clis/notebooklm/source-list.js +1 -0
- package/clis/notebooklm/status.js +1 -0
- package/clis/notebooklm/summary.js +1 -0
- package/clis/notion/export.js +1 -0
- package/clis/notion/favorites.js +1 -0
- package/clis/notion/new.js +1 -0
- package/clis/notion/read.js +1 -0
- package/clis/notion/search.js +1 -0
- package/clis/notion/sidebar.js +1 -0
- package/clis/notion/status.js +1 -0
- package/clis/notion/write.js +1 -0
- package/clis/nowcoder/companies.js +1 -0
- package/clis/nowcoder/creators.js +1 -0
- package/clis/nowcoder/detail.js +1 -0
- package/clis/nowcoder/experience.js +1 -0
- package/clis/nowcoder/hot.js +1 -0
- package/clis/nowcoder/jobs.js +1 -0
- package/clis/nowcoder/notifications.js +1 -0
- package/clis/nowcoder/papers.js +1 -0
- package/clis/nowcoder/practice.js +1 -0
- package/clis/nowcoder/recommend.js +1 -0
- package/clis/nowcoder/referral.js +1 -0
- package/clis/nowcoder/salary.js +1 -0
- package/clis/nowcoder/search.js +1 -0
- package/clis/nowcoder/suggest.js +1 -0
- package/clis/nowcoder/topics.js +1 -0
- package/clis/nowcoder/trending.js +1 -0
- package/clis/ones/login.js +1 -0
- package/clis/ones/logout.js +1 -0
- package/clis/ones/me.js +1 -0
- package/clis/ones/my-tasks.js +1 -0
- package/clis/ones/task.js +1 -0
- package/clis/ones/tasks.js +1 -0
- package/clis/ones/token-info.js +1 -0
- package/clis/ones/worklog.js +1 -0
- package/clis/openreview/openreview.test.js +345 -0
- package/clis/openreview/paper.js +43 -0
- package/clis/openreview/reviews.js +131 -0
- package/clis/openreview/search.js +46 -0
- package/clis/openreview/utils.js +158 -0
- package/clis/openreview/venue.js +63 -0
- package/clis/paperreview/feedback.js +1 -0
- package/clis/paperreview/review.js +1 -0
- package/clis/paperreview/submit.js +1 -0
- package/clis/pixiv/detail.js +1 -0
- package/clis/pixiv/download.js +1 -0
- package/clis/pixiv/illusts.js +2 -1
- package/clis/pixiv/ranking.js +2 -1
- package/clis/pixiv/search.js +2 -1
- package/clis/pixiv/user.js +2 -0
- package/clis/powerchina/search.js +1 -0
- package/clis/producthunt/browse.js +1 -0
- package/clis/producthunt/hot.js +1 -0
- package/clis/producthunt/posts.js +1 -0
- package/clis/producthunt/today.js +1 -0
- package/clis/quark/ls.js +1 -0
- package/clis/quark/mkdir.js +1 -0
- package/clis/quark/mv.js +1 -0
- package/clis/quark/rename.js +1 -0
- package/clis/quark/rm.js +1 -0
- package/clis/quark/save.js +1 -0
- package/clis/quark/share-tree.js +1 -0
- package/clis/reddit/comment.js +1 -0
- package/clis/reddit/frontpage.js +1 -0
- package/clis/reddit/hot.js +6 -1
- package/clis/reddit/hot.test.js +18 -0
- package/clis/reddit/popular.js +1 -0
- package/clis/reddit/read.js +1 -0
- package/clis/reddit/save.js +1 -0
- package/clis/reddit/saved.js +1 -0
- package/clis/reddit/search.js +1 -0
- package/clis/reddit/subreddit.js +1 -0
- package/clis/reddit/subscribe.js +1 -0
- package/clis/reddit/upvote.js +1 -0
- package/clis/reddit/upvoted.js +1 -0
- package/clis/reddit/user-comments.js +1 -0
- package/clis/reddit/user-posts.js +1 -0
- package/clis/reddit/user.js +1 -0
- package/clis/reuters/search.js +1 -0
- package/clis/sinablog/article.js +1 -0
- package/clis/sinablog/hot.js +1 -0
- package/clis/sinablog/search.js +1 -0
- package/clis/sinablog/user.js +1 -0
- package/clis/sinafinance/news.js +1 -0
- package/clis/sinafinance/rolling-news.js +1 -0
- package/clis/sinafinance/stock-rank.js +1 -0
- package/clis/sinafinance/stock.js +1 -0
- package/clis/smzdm/search.js +1 -0
- package/clis/spotify/spotify.js +11 -0
- package/clis/stackoverflow/bounties.js +11 -3
- package/clis/stackoverflow/hot.js +10 -2
- package/clis/stackoverflow/read.js +314 -0
- package/clis/stackoverflow/search.js +10 -2
- package/clis/stackoverflow/stackoverflow.test.js +346 -0
- package/clis/stackoverflow/unanswered.js +9 -2
- package/clis/steam/top-sellers.js +1 -0
- package/clis/substack/feed.js +1 -0
- package/clis/substack/publication.js +1 -0
- package/clis/substack/search.js +1 -0
- package/clis/taobao/add-cart.js +1 -0
- package/clis/taobao/cart.js +1 -0
- package/clis/taobao/detail.js +1 -0
- package/clis/taobao/reviews.js +1 -0
- package/clis/taobao/search.js +1 -0
- package/clis/tdx/hot-rank.js +1 -0
- package/clis/ths/hot-rank.js +1 -0
- package/clis/tieba/hot.js +2 -1
- package/clis/tieba/posts.js +1 -0
- package/clis/tieba/read.js +1 -0
- package/clis/tieba/search.js +2 -1
- package/clis/tiktok/comment.js +1 -0
- package/clis/tiktok/explore.js +1 -0
- package/clis/tiktok/follow.js +1 -0
- package/clis/tiktok/following.js +1 -0
- package/clis/tiktok/friends.js +1 -0
- package/clis/tiktok/like.js +1 -0
- package/clis/tiktok/live.js +1 -0
- package/clis/tiktok/notifications.js +1 -0
- package/clis/tiktok/profile.js +1 -0
- package/clis/tiktok/save.js +1 -0
- package/clis/tiktok/search.js +1 -0
- package/clis/tiktok/unfollow.js +1 -0
- package/clis/tiktok/unlike.js +1 -0
- package/clis/tiktok/unsave.js +1 -0
- package/clis/tiktok/user.js +1 -0
- package/clis/toutiao/articles.js +1 -0
- package/clis/twitter/accept.js +1 -0
- package/clis/twitter/article.js +1 -0
- package/clis/twitter/block.js +1 -0
- package/clis/twitter/bookmark.js +1 -0
- package/clis/twitter/bookmarks.js +2 -1
- package/clis/twitter/delete.js +1 -0
- package/clis/twitter/download.js +1 -0
- package/clis/twitter/follow.js +1 -0
- package/clis/twitter/followers.js +1 -0
- package/clis/twitter/following.js +1 -0
- package/clis/twitter/hide-reply.js +1 -0
- package/clis/twitter/like.js +1 -0
- package/clis/twitter/likes.js +2 -1
- package/clis/twitter/list-add.js +1 -0
- package/clis/twitter/list-remove.js +1 -0
- package/clis/twitter/list-tweets.js +1 -0
- package/clis/twitter/lists.js +1 -0
- package/clis/twitter/notifications.js +1 -0
- package/clis/twitter/post.js +1 -0
- package/clis/twitter/profile.js +1 -0
- package/clis/twitter/reply-dm.js +1 -0
- package/clis/twitter/reply.js +1 -0
- package/clis/twitter/search.js +1 -0
- package/clis/twitter/thread.js +1 -0
- package/clis/twitter/timeline.js +1 -0
- package/clis/twitter/trending.js +11 -12
- package/clis/twitter/trending.test.js +15 -0
- package/clis/twitter/tweets.js +2 -1
- package/clis/twitter/tweets.test.js +2 -2
- package/clis/twitter/unblock.js +1 -0
- package/clis/twitter/unbookmark.js +1 -0
- package/clis/twitter/unfollow.js +1 -0
- package/clis/uiverse/code.js +1 -0
- package/clis/uiverse/preview.js +1 -0
- package/clis/v2ex/daily.js +1 -0
- package/clis/v2ex/hot.js +1 -0
- package/clis/v2ex/latest.js +1 -0
- package/clis/v2ex/me.js +1 -0
- package/clis/v2ex/member.js +1 -0
- package/clis/v2ex/node.js +1 -0
- package/clis/v2ex/nodes.js +1 -0
- package/clis/v2ex/notifications.js +1 -0
- package/clis/v2ex/replies.js +1 -0
- package/clis/v2ex/topic.js +1 -0
- package/clis/v2ex/user.js +1 -0
- package/clis/wanfang/search.js +1 -0
- package/clis/web/read.js +1 -0
- package/clis/weibo/comments.js +1 -0
- package/clis/weibo/favorites.js +1 -0
- package/clis/weibo/feed.js +3 -1
- package/clis/weibo/hot.js +1 -0
- package/clis/weibo/me.js +1 -0
- package/clis/weibo/post.js +1 -0
- package/clis/weibo/publish.js +1 -0
- package/clis/weibo/search.js +8 -2
- package/clis/weibo/user.js +1 -0
- package/clis/weixin/create-draft.js +1 -0
- package/clis/weixin/download.js +1 -0
- package/clis/weixin/drafts.js +1 -0
- package/clis/weread/ai-outline.js +1 -0
- package/clis/weread/book.js +1 -0
- package/clis/weread/highlights.js +1 -0
- package/clis/weread/notebooks.js +1 -0
- package/clis/weread/notes.js +1 -0
- package/clis/weread/ranking.js +1 -0
- package/clis/weread/search.js +1 -0
- package/clis/weread/shelf.js +1 -0
- package/clis/wikipedia/random.js +1 -0
- package/clis/wikipedia/search.js +1 -0
- package/clis/wikipedia/summary.js +1 -0
- package/clis/wikipedia/trending.js +1 -0
- package/clis/xianyu/chat.js +1 -0
- package/clis/xianyu/item.js +1 -0
- package/clis/xianyu/search.js +1 -0
- package/clis/xiaoe/catalog.js +2 -1
- package/clis/xiaoe/content.js +1 -0
- package/clis/xiaoe/courses.js +1 -0
- package/clis/xiaoe/detail.js +1 -0
- package/clis/xiaoe/play-url.js +1 -0
- package/clis/xiaohongshu/comments.js +1 -0
- package/clis/xiaohongshu/creator-note-detail.js +1 -0
- package/clis/xiaohongshu/creator-notes-summary.js +1 -0
- package/clis/xiaohongshu/creator-notes.js +1 -0
- package/clis/xiaohongshu/creator-profile.js +1 -0
- package/clis/xiaohongshu/creator-stats.js +1 -0
- package/clis/xiaohongshu/download.js +1 -0
- package/clis/xiaohongshu/feed.js +2 -1
- package/clis/xiaohongshu/note.js +1 -0
- package/clis/xiaohongshu/notifications.js +1 -0
- package/clis/xiaohongshu/publish.js +1 -0
- package/clis/xiaohongshu/search.js +1 -0
- package/clis/xiaohongshu/user.js +1 -0
- package/clis/xiaoyuzhou/download.js +1 -0
- package/clis/xiaoyuzhou/episode.js +1 -0
- package/clis/xiaoyuzhou/podcast-episodes.js +1 -0
- package/clis/xiaoyuzhou/podcast.js +1 -0
- package/clis/xiaoyuzhou/transcript.js +1 -0
- package/clis/xueqiu/comments.js +1 -0
- package/clis/xueqiu/earnings-date.js +1 -0
- package/clis/xueqiu/feed.js +1 -0
- package/clis/xueqiu/fund-holdings.js +1 -0
- package/clis/xueqiu/fund-snapshot.js +1 -0
- package/clis/xueqiu/groups.js +1 -0
- package/clis/xueqiu/hot-stock.js +1 -0
- package/clis/xueqiu/hot.js +1 -0
- package/clis/xueqiu/kline.js +1 -0
- package/clis/xueqiu/search.js +1 -0
- package/clis/xueqiu/stock.js +1 -0
- package/clis/xueqiu/watchlist.js +1 -0
- package/clis/yahoo-finance/quote.js +1 -0
- package/clis/yollomi/background.js +1 -0
- package/clis/yollomi/edit.js +1 -0
- package/clis/yollomi/face-swap.js +1 -0
- package/clis/yollomi/generate.js +1 -0
- package/clis/yollomi/models.js +1 -0
- package/clis/yollomi/object-remover.js +1 -0
- package/clis/yollomi/remove-bg.js +1 -0
- package/clis/yollomi/restore.js +1 -0
- package/clis/yollomi/try-on.js +1 -0
- package/clis/yollomi/upload.js +1 -0
- package/clis/yollomi/upscale.js +1 -0
- package/clis/yollomi/video.js +1 -0
- package/clis/youtube/channel.js +1 -0
- package/clis/youtube/comments.js +1 -0
- package/clis/youtube/feed.js +8 -7
- package/clis/youtube/feed.test.js +131 -0
- package/clis/youtube/history.js +1 -0
- package/clis/youtube/like.js +1 -0
- package/clis/youtube/playlist.js +1 -0
- package/clis/youtube/search.js +1 -0
- package/clis/youtube/subscribe.js +1 -0
- package/clis/youtube/subscriptions.js +1 -0
- package/clis/youtube/transcript.js +1 -0
- package/clis/youtube/unlike.js +1 -0
- package/clis/youtube/unsubscribe.js +1 -0
- package/clis/youtube/video.js +1 -0
- package/clis/youtube/watch-later.js +1 -0
- package/clis/yuanbao/ask.js +1 -0
- package/clis/yuanbao/new.js +1 -0
- package/clis/zhihu/answer.js +1 -0
- package/clis/zhihu/collection.js +1 -0
- package/clis/zhihu/collections.js +1 -0
- package/clis/zhihu/comment.js +1 -0
- package/clis/zhihu/download.js +1 -0
- package/clis/zhihu/favorite.js +1 -0
- package/clis/zhihu/follow.js +1 -0
- package/clis/zhihu/hot.js +1 -0
- package/clis/zhihu/like.js +1 -0
- package/clis/zhihu/question.js +1 -0
- package/clis/zhihu/search.js +1 -0
- package/clis/zlibrary/info.js +1 -0
- package/clis/zlibrary/search.js +1 -0
- package/clis/zsxq/dynamics.js +1 -0
- package/clis/zsxq/groups.js +1 -0
- package/clis/zsxq/search.js +1 -0
- package/clis/zsxq/topic.js +1 -0
- package/clis/zsxq/topics.js +1 -0
- package/dist/src/adapter-source.test.js +1 -1
- package/dist/src/browser/analyze.test.js +2 -0
- package/dist/src/browser/base-page.d.ts +9 -0
- package/dist/src/browser/base-page.js +72 -3
- package/dist/src/browser/base-page.test.js +42 -0
- package/dist/src/browser/cdp.js +6 -0
- package/dist/src/browser/cdp.test.js +3 -0
- package/dist/src/browser/page.d.ts +1 -0
- package/dist/src/browser/page.js +6 -0
- package/dist/src/browser/page.test.js +17 -0
- package/dist/src/browser/target-resolver.d.ts +10 -3
- package/dist/src/browser/target-resolver.js +16 -10
- package/dist/src/browser/verify-fixture.d.ts +6 -1
- package/dist/src/browser/verify-fixture.js +87 -0
- package/dist/src/browser/verify-fixture.test.js +44 -1
- package/dist/src/build-manifest.js +3 -0
- package/dist/src/build-manifest.test.js +18 -12
- package/dist/src/capabilityRouting.test.js +1 -1
- package/dist/src/cli.js +141 -5
- package/dist/src/cli.test.js +179 -0
- package/dist/src/commanderAdapter.d.ts +1 -1
- package/dist/src/commanderAdapter.js +17 -7
- package/dist/src/commanderAdapter.test.js +7 -6
- package/dist/src/convention-audit.d.ts +50 -0
- package/dist/src/convention-audit.js +546 -0
- package/dist/src/convention-audit.test.d.ts +1 -0
- package/dist/src/convention-audit.test.js +226 -0
- package/dist/src/discovery.js +2 -0
- package/dist/src/doctor.js +0 -1
- package/dist/src/doctor.test.js +1 -1
- package/dist/src/engine.test.js +10 -10
- package/dist/src/execution.js +1 -1
- package/dist/src/execution.test.js +9 -9
- package/dist/src/help.d.ts +15 -0
- package/dist/src/help.js +149 -0
- package/dist/src/main.js +13 -7
- package/dist/src/manifest-types.d.ts +2 -0
- package/dist/src/plugin.test.js +26 -26
- package/dist/src/registry.d.ts +5 -0
- package/dist/src/registry.js +8 -0
- package/dist/src/registry.test.js +27 -20
- package/dist/src/serialization.d.ts +4 -0
- package/dist/src/serialization.js +27 -1
- package/dist/src/serialization.test.js +24 -2
- package/dist/src/types.d.ts +2 -0
- package/package.json +4 -1
- package/scripts/check-listing-id-pairing.mjs +193 -0
- package/scripts/check-silent-column-drop.mjs +105 -0
- package/scripts/check-typed-error-lint.mjs +118 -0
- package/scripts/silent-column-drop-baseline.json +962 -0
- 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([]);
|
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.
|
|
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 (
|
|
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.
|
package/dist/src/cli.test.js
CHANGED
|
@@ -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>):
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
171
|
-
siteGroups.set(
|
|
176
|
+
siteCmd = program.command(site);
|
|
177
|
+
siteGroups.set(site, siteCmd);
|
|
178
|
+
}
|
|
179
|
+
for (const cmd of commands) {
|
|
180
|
+
registerCommandToProgram(siteCmd, cmd);
|
|
172
181
|
}
|
|
173
|
-
|
|
182
|
+
installStructuredHelp(siteCmd, () => siteHelpData(site, commands));
|
|
174
183
|
}
|
|
184
|
+
return [...commandsBySite.keys()].sort((a, b) => a.localeCompare(b));
|
|
175
185
|
}
|