@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
|
@@ -61,6 +61,7 @@ export class BasePage {
|
|
|
61
61
|
_lastUrl = null;
|
|
62
62
|
/** Cached previous snapshot hashes for incremental diff marking */
|
|
63
63
|
_prevSnapshotHashes = null;
|
|
64
|
+
_cdpTargetMarkerSeq = 0;
|
|
64
65
|
/**
|
|
65
66
|
* Safely evaluate JS with pre-serialized arguments.
|
|
66
67
|
* Each key in `args` becomes a `const` declaration with JSON-serialized value,
|
|
@@ -154,8 +155,9 @@ export class BasePage {
|
|
|
154
155
|
async click(ref, opts = {}) {
|
|
155
156
|
// Phase 1: Resolve target with fingerprint verification
|
|
156
157
|
const resolved = await runResolve(this, ref, opts);
|
|
158
|
+
const nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
|
|
157
159
|
// Phase 2: Execute click on resolved element
|
|
158
|
-
const result = await this.evaluate(clickResolvedJs());
|
|
160
|
+
const result = await this.evaluate(clickResolvedJs({ skipScroll: nativeScrolled }));
|
|
159
161
|
if (typeof result === 'string' || result == null)
|
|
160
162
|
return resolved;
|
|
161
163
|
if (result.status === 'clicked')
|
|
@@ -217,12 +219,78 @@ export class BasePage {
|
|
|
217
219
|
return false;
|
|
218
220
|
}
|
|
219
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Run a DOM-domain CDP command against `window.__resolved`.
|
|
224
|
+
*
|
|
225
|
+
* CDP DOM.focus / DOM.scrollIntoViewIfNeeded need a nodeId, while our
|
|
226
|
+
* resolver stores the live Element in page JS. Bridge the two worlds with a
|
|
227
|
+
* short-lived marker attribute, then query it through CDP.
|
|
228
|
+
*/
|
|
229
|
+
async tryCdpOnResolvedElement(method) {
|
|
230
|
+
const cdp = this.cdp;
|
|
231
|
+
if (typeof cdp !== 'function')
|
|
232
|
+
return false;
|
|
233
|
+
const markerAttr = 'data-opencli-cdp-target';
|
|
234
|
+
const markerValue = `${Date.now().toString(36)}-${++this._cdpTargetMarkerSeq}`;
|
|
235
|
+
const selector = `[${markerAttr}="${markerValue}"]`;
|
|
236
|
+
let marked = false;
|
|
237
|
+
try {
|
|
238
|
+
const marker = await this.evaluateWithArgs(`
|
|
239
|
+
(() => {
|
|
240
|
+
const el = window.__resolved;
|
|
241
|
+
if (!el || el.nodeType !== 1 || typeof el.setAttribute !== 'function') {
|
|
242
|
+
return { ok: false };
|
|
243
|
+
}
|
|
244
|
+
el.setAttribute(markerAttr, markerValue);
|
|
245
|
+
return { ok: true };
|
|
246
|
+
})()
|
|
247
|
+
`, { markerAttr, markerValue });
|
|
248
|
+
marked = marker?.ok === true;
|
|
249
|
+
if (!marked)
|
|
250
|
+
return false;
|
|
251
|
+
await cdp.call(this, 'DOM.enable', {}).catch(() => undefined);
|
|
252
|
+
const doc = await cdp.call(this, 'DOM.getDocument', {});
|
|
253
|
+
const rootNodeId = doc?.root?.nodeId;
|
|
254
|
+
if (typeof rootNodeId !== 'number')
|
|
255
|
+
return false;
|
|
256
|
+
const query = await cdp.call(this, 'DOM.querySelector', {
|
|
257
|
+
nodeId: rootNodeId,
|
|
258
|
+
selector,
|
|
259
|
+
});
|
|
260
|
+
const nodeId = query?.nodeId;
|
|
261
|
+
if (typeof nodeId !== 'number' || nodeId <= 0)
|
|
262
|
+
return false;
|
|
263
|
+
await cdp.call(this, method, { nodeId });
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
if (marked) {
|
|
271
|
+
await this.evaluateWithArgs(`
|
|
272
|
+
(() => {
|
|
273
|
+
for (const el of document.querySelectorAll(selector)) {
|
|
274
|
+
el.removeAttribute(markerAttr);
|
|
275
|
+
}
|
|
276
|
+
})()
|
|
277
|
+
`, { selector, markerAttr }).catch(() => undefined);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
220
281
|
async typeText(ref, text, opts = {}) {
|
|
221
282
|
const resolved = await runResolve(this, ref, opts);
|
|
222
283
|
let typed = false;
|
|
284
|
+
let nativeScrolled = false;
|
|
285
|
+
let nativeFocused = false;
|
|
223
286
|
if (typeof this.nativeType === 'function' || typeof this.insertText === 'function') {
|
|
224
287
|
try {
|
|
225
|
-
|
|
288
|
+
nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
|
|
289
|
+
nativeFocused = await this.tryCdpOnResolvedElement('DOM.focus');
|
|
290
|
+
const preparation = await this.evaluate(prepareNativeTypeResolvedJs({
|
|
291
|
+
skipScroll: nativeScrolled,
|
|
292
|
+
skipFocus: nativeFocused,
|
|
293
|
+
}));
|
|
226
294
|
typed = preparation?.ok === true && await this.tryNativeType(text);
|
|
227
295
|
}
|
|
228
296
|
catch {
|
|
@@ -243,7 +311,8 @@ export class BasePage {
|
|
|
243
311
|
}
|
|
244
312
|
async scrollTo(ref, opts = {}) {
|
|
245
313
|
const resolved = await runResolve(this, ref, opts);
|
|
246
|
-
const
|
|
314
|
+
const nativeScrolled = await this.tryCdpOnResolvedElement('DOM.scrollIntoViewIfNeeded');
|
|
315
|
+
const result = (await this.evaluate(scrollResolvedJs({ skipScroll: nativeScrolled })));
|
|
247
316
|
// Fold match_level into the scroll payload so the user-facing envelope
|
|
248
317
|
// carries it the same way click / type do.
|
|
249
318
|
if (result && typeof result === 'object') {
|
|
@@ -17,15 +17,23 @@ class TestPage extends BasePage {
|
|
|
17
17
|
}
|
|
18
18
|
class ActionPage extends BasePage {
|
|
19
19
|
results = [];
|
|
20
|
+
withArgsResults = [];
|
|
20
21
|
scripts = [];
|
|
22
|
+
withArgs = [];
|
|
21
23
|
nativeType;
|
|
22
24
|
insertText;
|
|
23
25
|
nativeKeyPress;
|
|
26
|
+
cdp;
|
|
24
27
|
async goto() { }
|
|
25
28
|
async evaluate(js) {
|
|
26
29
|
this.scripts.push(js);
|
|
27
30
|
return this.results.shift() ?? null;
|
|
28
31
|
}
|
|
32
|
+
async evaluateWithArgs(js, args) {
|
|
33
|
+
this.scripts.push(js);
|
|
34
|
+
this.withArgs.push(args);
|
|
35
|
+
return this.withArgsResults.shift() ?? null;
|
|
36
|
+
}
|
|
29
37
|
async getCookies() { return []; }
|
|
30
38
|
async screenshot() { return ''; }
|
|
31
39
|
async tabs() { return []; }
|
|
@@ -100,6 +108,27 @@ describe('BasePage native input routing', () => {
|
|
|
100
108
|
expect(page.scripts[1]).toContain('nearestContentEditableHost');
|
|
101
109
|
expect(page.scripts.join('\n')).not.toContain("return 'typed'");
|
|
102
110
|
});
|
|
111
|
+
it('uses CDP DOM focus and scroll before native text insertion when available', async () => {
|
|
112
|
+
const page = new ActionPage();
|
|
113
|
+
page.nativeType = vi.fn().mockResolvedValue(undefined);
|
|
114
|
+
page.cdp = vi.fn()
|
|
115
|
+
.mockResolvedValueOnce({})
|
|
116
|
+
.mockResolvedValueOnce({ root: { nodeId: 1 } })
|
|
117
|
+
.mockResolvedValueOnce({ nodeId: 7 })
|
|
118
|
+
.mockResolvedValueOnce({})
|
|
119
|
+
.mockResolvedValueOnce({})
|
|
120
|
+
.mockResolvedValueOnce({ root: { nodeId: 1 } })
|
|
121
|
+
.mockResolvedValueOnce({ nodeId: 7 })
|
|
122
|
+
.mockResolvedValueOnce({});
|
|
123
|
+
page.results = [resolveOk, { ok: true, mode: 'input' }];
|
|
124
|
+
page.withArgsResults = [{ ok: true }, undefined, { ok: true }, undefined];
|
|
125
|
+
await page.typeText('#q', 'hello');
|
|
126
|
+
expect(page.cdp).toHaveBeenCalledWith('DOM.scrollIntoViewIfNeeded', { nodeId: 7 });
|
|
127
|
+
expect(page.cdp).toHaveBeenCalledWith('DOM.focus', { nodeId: 7 });
|
|
128
|
+
expect(page.nativeType).toHaveBeenCalledWith('hello');
|
|
129
|
+
expect(page.scripts.at(-1)).toContain('if (false) el.scrollIntoView');
|
|
130
|
+
expect(page.scripts.at(-1)).toContain('if (false) {');
|
|
131
|
+
});
|
|
103
132
|
it('keeps the DOM setter fallback when native text insertion is unavailable', async () => {
|
|
104
133
|
const page = new ActionPage();
|
|
105
134
|
page.results = [resolveOk, 'typed'];
|
|
@@ -117,6 +146,19 @@ describe('BasePage native input routing', () => {
|
|
|
117
146
|
expect(page.scripts).toHaveLength(3);
|
|
118
147
|
expect(page.scripts[2]).toContain("return 'typed'");
|
|
119
148
|
});
|
|
149
|
+
it('uses CDP DOM scrollIntoViewIfNeeded before JS click when available', async () => {
|
|
150
|
+
const page = new ActionPage();
|
|
151
|
+
page.cdp = vi.fn()
|
|
152
|
+
.mockResolvedValueOnce({})
|
|
153
|
+
.mockResolvedValueOnce({ root: { nodeId: 1 } })
|
|
154
|
+
.mockResolvedValueOnce({ nodeId: 9 })
|
|
155
|
+
.mockResolvedValueOnce({});
|
|
156
|
+
page.results = [resolveOk, { status: 'clicked', x: 1, y: 2 }];
|
|
157
|
+
page.withArgsResults = [{ ok: true }, undefined];
|
|
158
|
+
await page.click('#save');
|
|
159
|
+
expect(page.cdp).toHaveBeenCalledWith('DOM.scrollIntoViewIfNeeded', { nodeId: 9 });
|
|
160
|
+
expect(page.scripts.at(-1)).toContain('if (false) el.scrollIntoView');
|
|
161
|
+
});
|
|
120
162
|
it('presses key chords through native CDP key events when available', async () => {
|
|
121
163
|
const page = new ActionPage();
|
|
122
164
|
page.nativeKeyPress = vi.fn().mockResolvedValue(undefined);
|
package/dist/src/browser/cdp.js
CHANGED
|
@@ -320,6 +320,12 @@ class CDPPage extends BasePage {
|
|
|
320
320
|
async cdp(method, params = {}) {
|
|
321
321
|
return this.bridge.send(method, params);
|
|
322
322
|
}
|
|
323
|
+
async handleJavaScriptDialog(accept, promptText) {
|
|
324
|
+
await this.cdp('Page.handleJavaScriptDialog', {
|
|
325
|
+
accept,
|
|
326
|
+
...(promptText !== undefined && { promptText }),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
323
329
|
async nativeClick(x, y) {
|
|
324
330
|
await this.cdp('Input.dispatchMouseEvent', {
|
|
325
331
|
type: 'mousePressed',
|
|
@@ -58,10 +58,12 @@ describe('CDPBridge cookies', () => {
|
|
|
58
58
|
expect(page.nativeType).toBeTypeOf('function');
|
|
59
59
|
expect(page.nativeKeyPress).toBeTypeOf('function');
|
|
60
60
|
expect(page.nativeClick).toBeTypeOf('function');
|
|
61
|
+
expect(page.handleJavaScriptDialog).toBeTypeOf('function');
|
|
61
62
|
expect(page.cdp).toBeTypeOf('function');
|
|
62
63
|
await page.nativeType('hello');
|
|
63
64
|
await page.nativeKeyPress('a', ['Ctrl']);
|
|
64
65
|
await page.nativeClick(10, 20);
|
|
66
|
+
await page.handleJavaScriptDialog(true, 'ok');
|
|
65
67
|
await page.cdp('Page.getLayoutMetrics', {});
|
|
66
68
|
expect(send.mock.calls).toEqual([
|
|
67
69
|
['Input.insertText', { text: 'hello' }],
|
|
@@ -69,6 +71,7 @@ describe('CDPBridge cookies', () => {
|
|
|
69
71
|
['Input.dispatchKeyEvent', { type: 'keyUp', key: 'a', modifiers: 2 }],
|
|
70
72
|
['Input.dispatchMouseEvent', { type: 'mousePressed', x: 10, y: 20, button: 'left', clickCount: 1 }],
|
|
71
73
|
['Input.dispatchMouseEvent', { type: 'mouseReleased', x: 10, y: 20, button: 'left', clickCount: 1 }],
|
|
74
|
+
['Page.handleJavaScriptDialog', { accept: true, promptText: 'ok' }],
|
|
72
75
|
['Page.getLayoutMetrics', {}],
|
|
73
76
|
]);
|
|
74
77
|
});
|
|
@@ -68,6 +68,7 @@ export declare class Page extends BasePage {
|
|
|
68
68
|
}>>;
|
|
69
69
|
evaluateInFrame(js: string, frameIndex: number): Promise<unknown>;
|
|
70
70
|
cdp(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
71
|
+
handleJavaScriptDialog(accept: boolean, promptText?: string): Promise<void>;
|
|
71
72
|
/** CDP native click fallback — called when JS el.click() fails */
|
|
72
73
|
protected tryNativeClick(x: number, y: number): Promise<boolean>;
|
|
73
74
|
/** Precise click using DOM.getContentQuads/getBoxModel for inline elements */
|
package/dist/src/browser/page.js
CHANGED
|
@@ -283,6 +283,12 @@ export class Page extends BasePage {
|
|
|
283
283
|
...this._cmdOpts(),
|
|
284
284
|
});
|
|
285
285
|
}
|
|
286
|
+
async handleJavaScriptDialog(accept, promptText) {
|
|
287
|
+
await this.cdp('Page.handleJavaScriptDialog', {
|
|
288
|
+
accept,
|
|
289
|
+
...(promptText !== undefined && { promptText }),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
286
292
|
/** CDP native click fallback — called when JS el.click() fails */
|
|
287
293
|
async tryNativeClick(x, y) {
|
|
288
294
|
try {
|
|
@@ -102,6 +102,23 @@ describe('Page network capture compatibility', () => {
|
|
|
102
102
|
expect(warnMock).toHaveBeenCalledTimes(1);
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
|
+
describe('Page CDP helpers', () => {
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
sendCommandMock.mockReset();
|
|
108
|
+
sendCommandFullMock.mockReset();
|
|
109
|
+
warnMock.mockReset();
|
|
110
|
+
});
|
|
111
|
+
it('handles JavaScript dialogs through the CDP passthrough', async () => {
|
|
112
|
+
sendCommandMock.mockResolvedValueOnce({});
|
|
113
|
+
const page = new Page('browser:default');
|
|
114
|
+
await page.handleJavaScriptDialog(true, 'confirm');
|
|
115
|
+
expect(sendCommandMock).toHaveBeenCalledWith('cdp', expect.objectContaining({
|
|
116
|
+
workspace: 'browser:default',
|
|
117
|
+
cdpMethod: 'Page.handleJavaScriptDialog',
|
|
118
|
+
cdpParams: { accept: true, promptText: 'confirm' },
|
|
119
|
+
}));
|
|
120
|
+
});
|
|
121
|
+
});
|
|
105
122
|
describe('Page active target tracking', () => {
|
|
106
123
|
beforeEach(() => {
|
|
107
124
|
sendCommandMock.mockReset();
|
|
@@ -72,7 +72,9 @@ export declare function resolveTargetJs(ref: string, opts?: ResolveOptions): str
|
|
|
72
72
|
* Generate JS for click that uses the unified resolver.
|
|
73
73
|
* Assumes resolveTargetJs has been called and __resolved is set.
|
|
74
74
|
*/
|
|
75
|
-
export declare function clickResolvedJs(
|
|
75
|
+
export declare function clickResolvedJs(opts?: {
|
|
76
|
+
skipScroll?: boolean;
|
|
77
|
+
}): string;
|
|
76
78
|
/**
|
|
77
79
|
* Generate JS for type that uses the unified resolver.
|
|
78
80
|
*/
|
|
@@ -84,12 +86,17 @@ export declare function typeResolvedJs(text: string): string;
|
|
|
84
86
|
* focus the editable target, select its current contents, then let CDP insert
|
|
85
87
|
* real browser text input so rich editors can update their internal state.
|
|
86
88
|
*/
|
|
87
|
-
export declare function prepareNativeTypeResolvedJs(
|
|
89
|
+
export declare function prepareNativeTypeResolvedJs(opts?: {
|
|
90
|
+
skipScroll?: boolean;
|
|
91
|
+
skipFocus?: boolean;
|
|
92
|
+
}): string;
|
|
88
93
|
/**
|
|
89
94
|
* Generate JS for scrollTo that uses the unified resolver.
|
|
90
95
|
* Assumes resolveTargetJs has been called and __resolved is set.
|
|
91
96
|
*/
|
|
92
|
-
export declare function scrollResolvedJs(
|
|
97
|
+
export declare function scrollResolvedJs(opts?: {
|
|
98
|
+
skipScroll?: boolean;
|
|
99
|
+
}): string;
|
|
93
100
|
/**
|
|
94
101
|
* Generate JS to get text content of resolved element.
|
|
95
102
|
*/
|
|
@@ -277,12 +277,13 @@ export function resolveTargetJs(ref, opts = {}) {
|
|
|
277
277
|
* Generate JS for click that uses the unified resolver.
|
|
278
278
|
* Assumes resolveTargetJs has been called and __resolved is set.
|
|
279
279
|
*/
|
|
280
|
-
export function clickResolvedJs() {
|
|
280
|
+
export function clickResolvedJs(opts = {}) {
|
|
281
|
+
const shouldScroll = opts.skipScroll ? 'false' : 'true';
|
|
281
282
|
return `
|
|
282
283
|
(() => {
|
|
283
284
|
const el = window.__resolved;
|
|
284
285
|
if (!el) throw new Error('No resolved element');
|
|
285
|
-
el.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
286
|
+
if (${shouldScroll}) el.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
286
287
|
const rect = el.getBoundingClientRect();
|
|
287
288
|
const x = Math.round(rect.left + rect.width / 2);
|
|
288
289
|
const y = Math.round(rect.top + rect.height / 2);
|
|
@@ -338,7 +339,9 @@ export function typeResolvedJs(text) {
|
|
|
338
339
|
* focus the editable target, select its current contents, then let CDP insert
|
|
339
340
|
* real browser text input so rich editors can update their internal state.
|
|
340
341
|
*/
|
|
341
|
-
export function prepareNativeTypeResolvedJs() {
|
|
342
|
+
export function prepareNativeTypeResolvedJs(opts = {}) {
|
|
343
|
+
const shouldScroll = opts.skipScroll ? 'false' : 'true';
|
|
344
|
+
const shouldFocus = opts.skipFocus ? 'false' : 'true';
|
|
342
345
|
return `
|
|
343
346
|
(() => {
|
|
344
347
|
const original = window.__resolved;
|
|
@@ -369,11 +372,13 @@ export function prepareNativeTypeResolvedJs() {
|
|
|
369
372
|
}
|
|
370
373
|
|
|
371
374
|
window.__resolved = el;
|
|
372
|
-
el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' });
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
375
|
+
if (${shouldScroll}) el.scrollIntoView({ behavior: 'instant', block: 'center', inline: 'nearest' });
|
|
376
|
+
if (${shouldFocus}) {
|
|
377
|
+
try {
|
|
378
|
+
el.focus({ preventScroll: true });
|
|
379
|
+
} catch (_) {
|
|
380
|
+
el.focus();
|
|
381
|
+
}
|
|
377
382
|
}
|
|
378
383
|
|
|
379
384
|
if (editableHost) {
|
|
@@ -410,12 +415,13 @@ export function prepareNativeTypeResolvedJs() {
|
|
|
410
415
|
* Generate JS for scrollTo that uses the unified resolver.
|
|
411
416
|
* Assumes resolveTargetJs has been called and __resolved is set.
|
|
412
417
|
*/
|
|
413
|
-
export function scrollResolvedJs() {
|
|
418
|
+
export function scrollResolvedJs(opts = {}) {
|
|
419
|
+
const shouldScroll = opts.skipScroll ? 'false' : 'true';
|
|
414
420
|
return `
|
|
415
421
|
(() => {
|
|
416
422
|
const el = window.__resolved;
|
|
417
423
|
if (!el) throw new Error('No resolved element');
|
|
418
|
-
el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
|
|
424
|
+
if (${shouldScroll}) el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' });
|
|
419
425
|
return { scrolled: true, tag: el.tagName.toLowerCase(), text: (el.textContent || '').trim().slice(0, 80) };
|
|
420
426
|
})()
|
|
421
427
|
`;
|
|
@@ -31,11 +31,15 @@ export type Fixture = {
|
|
|
31
31
|
expect?: FixtureExpect;
|
|
32
32
|
};
|
|
33
33
|
export type ValidationFailure = {
|
|
34
|
-
rule: 'rowCount' | 'column' | 'type' | 'pattern' | 'notEmpty' | 'mustNotContain' | 'mustBeTruthy';
|
|
34
|
+
rule: 'rowCount' | 'column' | 'type' | 'pattern' | 'notEmpty' | 'mustNotContain' | 'mustBeTruthy' | 'shapeKeyCount' | 'shapeDepth' | 'shapeNestedId';
|
|
35
35
|
detail: string;
|
|
36
36
|
rowIndex?: number;
|
|
37
37
|
};
|
|
38
38
|
export type Row = Record<string, unknown>;
|
|
39
|
+
export type RowShapeOptions = {
|
|
40
|
+
maxTopLevelKeys?: number;
|
|
41
|
+
maxNestedDepth?: number;
|
|
42
|
+
};
|
|
39
43
|
export declare function fixturePath(site: string, command: string): string;
|
|
40
44
|
export declare function loadFixture(site: string, command: string): Fixture | null;
|
|
41
45
|
export declare function writeFixture(site: string, command: string, fixture: Fixture): string;
|
|
@@ -51,6 +55,7 @@ export declare function writeFixture(site: string, command: string, fixture: Fix
|
|
|
51
55
|
*/
|
|
52
56
|
export declare function deriveFixture(rows: Row[], args?: FixtureArgs): Fixture;
|
|
53
57
|
export declare function validateRows(rows: Row[], fixture: Fixture): ValidationFailure[];
|
|
58
|
+
export declare function validateRowShape(rows: Row[], opts?: RowShapeOptions): ValidationFailure[];
|
|
54
59
|
/**
|
|
55
60
|
* Convert fixture args into argv tokens appended after the command name.
|
|
56
61
|
* - Array form is passed through verbatim (stringified), supporting positional subjects.
|
|
@@ -30,6 +30,26 @@
|
|
|
30
30
|
import * as fs from 'node:fs';
|
|
31
31
|
import * as os from 'node:os';
|
|
32
32
|
import * as path from 'node:path';
|
|
33
|
+
const DEFAULT_MAX_TOP_LEVEL_KEYS = 12;
|
|
34
|
+
const DEFAULT_MAX_NESTED_DEPTH = 1;
|
|
35
|
+
const ID_SHAPED_KEY_PATTERNS = [
|
|
36
|
+
/^id$/i,
|
|
37
|
+
/_id$/i,
|
|
38
|
+
/Id$/,
|
|
39
|
+
/^short_id$/i,
|
|
40
|
+
/^bvid$/i,
|
|
41
|
+
/^aid$/i,
|
|
42
|
+
/^tid$/i,
|
|
43
|
+
/^asin$/i,
|
|
44
|
+
/^sku$/i,
|
|
45
|
+
/^isbn$/i,
|
|
46
|
+
/^doi$/i,
|
|
47
|
+
/^slug$/i,
|
|
48
|
+
/^hn_id$/i,
|
|
49
|
+
/^username$/i,
|
|
50
|
+
/^handle$/i,
|
|
51
|
+
/^uri$/i,
|
|
52
|
+
];
|
|
33
53
|
export function fixturePath(site, command) {
|
|
34
54
|
return path.join(os.homedir(), '.opencli', 'sites', site, 'verify', `${command}.json`);
|
|
35
55
|
}
|
|
@@ -180,6 +200,39 @@ export function validateRows(rows, fixture) {
|
|
|
180
200
|
});
|
|
181
201
|
return failures;
|
|
182
202
|
}
|
|
203
|
+
export function validateRowShape(rows, opts = {}) {
|
|
204
|
+
const failures = [];
|
|
205
|
+
const maxTopLevelKeys = opts.maxTopLevelKeys ?? DEFAULT_MAX_TOP_LEVEL_KEYS;
|
|
206
|
+
const maxNestedDepth = opts.maxNestedDepth ?? DEFAULT_MAX_NESTED_DEPTH;
|
|
207
|
+
rows.forEach((row, i) => {
|
|
208
|
+
const keys = Object.keys(row);
|
|
209
|
+
if (keys.length > maxTopLevelKeys) {
|
|
210
|
+
failures.push({
|
|
211
|
+
rule: 'shapeKeyCount',
|
|
212
|
+
detail: `row has ${keys.length} top-level keys, expected at most ${maxTopLevelKeys}`,
|
|
213
|
+
rowIndex: i,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
for (const [key, value] of Object.entries(row)) {
|
|
217
|
+
const depth = nestedDepth(value);
|
|
218
|
+
if (depth > maxNestedDepth) {
|
|
219
|
+
failures.push({
|
|
220
|
+
rule: 'shapeDepth',
|
|
221
|
+
detail: `"${key}" nesting depth is ${depth}, expected at most ${maxNestedDepth}`,
|
|
222
|
+
rowIndex: i,
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
for (const path of nestedIdPaths(value, key)) {
|
|
226
|
+
failures.push({
|
|
227
|
+
rule: 'shapeNestedId',
|
|
228
|
+
detail: `id-shaped field "${path}" must be a top-level row key`,
|
|
229
|
+
rowIndex: i,
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
return failures;
|
|
235
|
+
}
|
|
183
236
|
/**
|
|
184
237
|
* Convert fixture args into argv tokens appended after the command name.
|
|
185
238
|
* - Array form is passed through verbatim (stringified), supporting positional subjects.
|
|
@@ -221,6 +274,40 @@ function jsType(v) {
|
|
|
221
274
|
return 'array';
|
|
222
275
|
return typeof v;
|
|
223
276
|
}
|
|
277
|
+
function nestedDepth(value) {
|
|
278
|
+
if (value === null || value === undefined || typeof value !== 'object')
|
|
279
|
+
return 0;
|
|
280
|
+
if (Array.isArray(value)) {
|
|
281
|
+
if (value.length === 0)
|
|
282
|
+
return 1;
|
|
283
|
+
return 1 + Math.max(...value.map(nestedDepth));
|
|
284
|
+
}
|
|
285
|
+
const values = Object.values(value);
|
|
286
|
+
if (values.length === 0)
|
|
287
|
+
return 1;
|
|
288
|
+
return 1 + Math.max(...values.map(nestedDepth));
|
|
289
|
+
}
|
|
290
|
+
function nestedIdPaths(value, prefix) {
|
|
291
|
+
if (value === null || value === undefined || typeof value !== 'object')
|
|
292
|
+
return [];
|
|
293
|
+
const paths = [];
|
|
294
|
+
if (Array.isArray(value)) {
|
|
295
|
+
value.forEach((item, index) => {
|
|
296
|
+
paths.push(...nestedIdPaths(item, `${prefix}[${index}]`));
|
|
297
|
+
});
|
|
298
|
+
return paths;
|
|
299
|
+
}
|
|
300
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
301
|
+
const childPath = `${prefix}.${key}`;
|
|
302
|
+
if (isIdShapedKey(key))
|
|
303
|
+
paths.push(childPath);
|
|
304
|
+
paths.push(...nestedIdPaths(nested, childPath));
|
|
305
|
+
}
|
|
306
|
+
return paths;
|
|
307
|
+
}
|
|
308
|
+
function isIdShapedKey(key) {
|
|
309
|
+
return ID_SHAPED_KEY_PATTERNS.some((pattern) => pattern.test(key));
|
|
310
|
+
}
|
|
224
311
|
function typeMatches(actual, declared) {
|
|
225
312
|
const allowed = declared.split('|').map((s) => s.trim()).filter(Boolean);
|
|
226
313
|
if (allowed.length === 0)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { deriveFixture, expandFixtureArgs, parseSeedArgs, validateRows } from './verify-fixture.js';
|
|
2
|
+
import { deriveFixture, expandFixtureArgs, parseSeedArgs, validateRows, validateRowShape } from './verify-fixture.js';
|
|
3
3
|
describe('validateRows', () => {
|
|
4
4
|
it('passes when rows meet all expectations', () => {
|
|
5
5
|
const fixture = {
|
|
@@ -98,6 +98,49 @@ describe('validateRows', () => {
|
|
|
98
98
|
expect(failures.map((f) => f.rowIndex)).toEqual([1, 2, 3, 4]);
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
|
+
describe('validateRowShape', () => {
|
|
102
|
+
it('passes flat rows with a compact key set', () => {
|
|
103
|
+
const failures = validateRowShape([
|
|
104
|
+
{ id: '1', title: 'A', author: { name: 'Ada' }, tags: ['ai', 'web'] },
|
|
105
|
+
]);
|
|
106
|
+
expect(failures).toEqual([]);
|
|
107
|
+
});
|
|
108
|
+
it('reports rows with too many top-level keys', () => {
|
|
109
|
+
const row = Object.fromEntries(Array.from({ length: 13 }, (_, i) => [`k${i}`, i]));
|
|
110
|
+
const failures = validateRowShape([row]);
|
|
111
|
+
expect(failures).toEqual([
|
|
112
|
+
{
|
|
113
|
+
rule: 'shapeKeyCount',
|
|
114
|
+
detail: 'row has 13 top-level keys, expected at most 12',
|
|
115
|
+
rowIndex: 0,
|
|
116
|
+
},
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
119
|
+
it('reports nesting deeper than one level', () => {
|
|
120
|
+
const failures = validateRowShape([
|
|
121
|
+
{ title: 'A', stats: { author: { name: 'Ada' } } },
|
|
122
|
+
]);
|
|
123
|
+
expect(failures).toEqual([
|
|
124
|
+
{
|
|
125
|
+
rule: 'shapeDepth',
|
|
126
|
+
detail: '"stats" nesting depth is 2, expected at most 1',
|
|
127
|
+
rowIndex: 0,
|
|
128
|
+
},
|
|
129
|
+
]);
|
|
130
|
+
});
|
|
131
|
+
it('reports nested id-shaped fields even when one-level nesting is otherwise allowed', () => {
|
|
132
|
+
const failures = validateRowShape([
|
|
133
|
+
{ title: 'A', author: { user_id: 'u1', name: 'Ada' } },
|
|
134
|
+
]);
|
|
135
|
+
expect(failures).toEqual([
|
|
136
|
+
{
|
|
137
|
+
rule: 'shapeNestedId',
|
|
138
|
+
detail: 'id-shaped field "author.user_id" must be a top-level row key',
|
|
139
|
+
rowIndex: 0,
|
|
140
|
+
},
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
101
144
|
describe('deriveFixture', () => {
|
|
102
145
|
it('returns rowCount.min=0 when rows are empty', () => {
|
|
103
146
|
expect(deriveFixture([])).toEqual({ expect: { rowCount: { min: 0 } } });
|
|
@@ -75,6 +75,7 @@ function isCliCommandValue(value, site) {
|
|
|
75
75
|
&& typeof value.site === 'string'
|
|
76
76
|
&& value.site === site
|
|
77
77
|
&& typeof value.name === 'string'
|
|
78
|
+
&& (value.access === 'read' || value.access === 'write')
|
|
78
79
|
&& Array.isArray(value.args);
|
|
79
80
|
}
|
|
80
81
|
function toManifestEntry(cmd, modulePath, sourceFile) {
|
|
@@ -83,6 +84,8 @@ function toManifestEntry(cmd, modulePath, sourceFile) {
|
|
|
83
84
|
name: cmd.name,
|
|
84
85
|
aliases: cmd.aliases,
|
|
85
86
|
description: cmd.description ?? '',
|
|
87
|
+
access: cmd.access,
|
|
88
|
+
example: cmd.example,
|
|
86
89
|
domain: cmd.domain,
|
|
87
90
|
strategy: (cmd.strategy ?? 'public').toString().toLowerCase(),
|
|
88
91
|
browser: cmd.browser ?? true,
|