@jackwener/opencli 1.7.11 → 1.7.13
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.md +8 -7
- package/README.zh-CN.md +11 -9
- package/cli-manifest.json +12952 -6208
- 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/1point3acres/digest.js +35 -0
- package/clis/1point3acres/forum.js +51 -0
- package/clis/1point3acres/forums.js +44 -0
- package/clis/1point3acres/hot.js +35 -0
- package/clis/1point3acres/latest.js +35 -0
- package/clis/1point3acres/notifications.js +64 -0
- package/clis/1point3acres/search.js +71 -0
- package/clis/1point3acres/thread.js +117 -0
- package/clis/1point3acres/user.js +77 -0
- package/clis/1point3acres/utils.js +247 -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/_shared/desktop-commands.js +4 -0
- package/clis/aibase/news.js +110 -0
- package/clis/aibase/news.test.js +59 -0
- package/clis/amazon/bestsellers.js +1 -0
- package/clis/amazon/discussion.js +1 -0
- package/clis/amazon/discussion.test.js +1 -28
- 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 +4 -2
- 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/author.js +44 -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 -1
- 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/bbc/topic.js +57 -0
- package/clis/bbc/utils.js +79 -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 +2 -1
- package/clis/chaoxing/exams.js +2 -1
- package/clis/chatgpt/ask.js +57 -0
- package/clis/chatgpt/commands.test.js +45 -0
- package/clis/chatgpt/detail.js +46 -0
- package/clis/chatgpt/history.js +39 -0
- package/clis/chatgpt/image.js +13 -11
- package/clis/chatgpt/image.test.js +23 -0
- package/clis/chatgpt/new.js +25 -0
- package/clis/chatgpt/read.js +43 -0
- package/clis/chatgpt/send.js +46 -0
- package/clis/chatgpt/status.js +29 -0
- package/clis/chatgpt/utils.js +294 -4
- package/clis/chatgpt/utils.test.js +13 -0
- package/clis/chatgpt-app/ask.js +7 -3
- 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 +17 -43
- package/clis/chatwise/composer.test.js +186 -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 +3 -24
- package/clis/chatwise/utils.js +143 -0
- package/clis/claude/ask.js +2 -1
- package/clis/claude/detail.js +2 -0
- package/clis/claude/history.js +2 -0
- package/clis/claude/new.js +2 -0
- package/clis/claude/read.js +2 -0
- package/clis/claude/send.js +2 -0
- package/clis/claude/status.js +2 -0
- package/clis/cnki/search.js +1 -0
- package/clis/codex/ask.js +16 -9
- package/clis/codex/export.js +1 -0
- package/clis/codex/extract-diff.js +1 -0
- package/clis/codex/history.js +17 -33
- package/clis/codex/model.js +1 -0
- package/clis/codex/projects.js +28 -0
- package/clis/codex/read.js +11 -4
- package/clis/codex/send.js +11 -3
- package/clis/codex/sidebar.js +356 -0
- package/clis/codex/sidebar.test.js +329 -0
- package/clis/coingecko/categories.js +75 -0
- package/clis/coingecko/coin.js +107 -0
- package/clis/coingecko/coingecko.test.js +109 -0
- package/clis/coingecko/derivatives.js +84 -0
- package/clis/coingecko/exchanges.js +74 -0
- package/clis/coingecko/global.js +71 -0
- package/clis/coingecko/top.js +64 -0
- package/clis/coingecko/trending.js +55 -0
- package/clis/coupang/add-to-cart.js +22 -13
- package/clis/coupang/coupang.test.js +159 -0
- package/clis/coupang/product.js +257 -0
- package/clis/coupang/search.js +39 -16
- package/clis/coupang/utils.js +55 -1
- package/clis/crates/crate.js +62 -0
- package/clis/crates/search.js +44 -0
- package/clis/crates/utils.js +72 -0
- package/clis/ctrip/ctrip.test.js +234 -0
- package/clis/ctrip/hotel-suggest.js +45 -0
- package/clis/ctrip/search.js +23 -68
- package/clis/ctrip/utils.js +175 -0
- package/clis/cursor/ask.js +7 -3
- 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/author.js +133 -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/dblp/venue.js +64 -0
- package/clis/deepseek/ask.js +13 -7
- package/clis/deepseek/ask.test.js +13 -13
- package/clis/deepseek/detail.js +38 -0
- package/clis/deepseek/detail.test.js +81 -0
- package/clis/deepseek/history.js +2 -0
- package/clis/deepseek/new.js +2 -0
- package/clis/deepseek/read.js +2 -0
- package/clis/deepseek/send.js +140 -0
- package/clis/deepseek/send.test.js +107 -0
- package/clis/deepseek/status.js +2 -0
- package/clis/deepseek/utils.js +66 -0
- package/clis/deepseek/utils.test.js +107 -1
- package/clis/defillama/defillama.test.js +99 -0
- package/clis/defillama/protocol.js +84 -0
- package/clis/defillama/protocols.js +55 -0
- package/clis/defillama/utils.js +99 -0
- package/clis/devto/devto.test.js +236 -0
- package/clis/devto/latest.js +74 -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/dockerhub/image.js +52 -0
- package/clis/dockerhub/search.js +47 -0
- package/clis/dockerhub/utils.js +100 -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 +8 -3
- package/clis/doubao/detail.js +2 -0
- package/clis/doubao/history.js +2 -0
- package/clis/doubao/meeting-summary.js +2 -0
- package/clis/doubao/meeting-transcript.js +2 -0
- package/clis/doubao/new.js +2 -0
- package/clis/doubao/read.js +2 -0
- package/clis/doubao/send.js +2 -0
- package/clis/doubao/status.js +2 -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/draft.test.js +1 -30
- 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/endoflife/endoflife.test.js +51 -0
- package/clis/endoflife/product.js +55 -0
- package/clis/endoflife/utils.js +89 -0
- package/clis/facebook/__fixtures__/notifications-page.html +13 -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 +327 -30
- package/clis/facebook/notifications.test.js +458 -0
- package/clis/facebook/profile.js +1 -0
- package/clis/facebook/search.js +1 -0
- package/clis/flathub/app.js +71 -0
- package/clis/flathub/flathub.test.js +90 -0
- package/clis/flathub/search.js +80 -0
- package/clis/flathub/utils.js +114 -0
- package/clis/gemini/ask.js +8 -3
- package/clis/gemini/ask.test.js +2 -2
- package/clis/gemini/deep-research-result.js +7 -2
- package/clis/gemini/deep-research-result.test.js +15 -14
- package/clis/gemini/deep-research.js +9 -4
- package/clis/gemini/deep-research.test.js +15 -18
- package/clis/gemini/image.js +8 -2
- package/clis/gemini/new.js +2 -0
- package/clis/gemini/utils.js +0 -4
- 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 -1
- package/clis/google-scholar/profile.js +1 -1
- package/clis/google-scholar/search.js +1 -1
- package/clis/goproxy/goproxy.test.js +103 -0
- package/clis/goproxy/module.js +47 -0
- package/clis/goproxy/utils.js +165 -0
- package/clis/goproxy/versions.js +59 -0
- package/clis/gov-law/recent.js +1 -1
- package/clis/gov-law/search.js +1 -1
- package/clis/gov-policy/__fixtures__/recent.html +16 -0
- package/clis/gov-policy/__fixtures__/search.html +41 -0
- package/clis/gov-policy/gov-policy.test.js +224 -0
- package/clis/gov-policy/recent.js +67 -24
- package/clis/gov-policy/search.js +66 -23
- package/clis/gov-policy/utils.js +54 -0
- package/clis/grok/ask.js +50 -265
- package/clis/grok/ask.test.js +21 -46
- package/clis/grok/detail.js +60 -0
- package/clis/grok/history.js +48 -0
- package/clis/grok/{image.ts → image.js} +57 -70
- package/clis/grok/image.test.ts +20 -0
- package/clis/grok/new.js +20 -0
- package/clis/grok/read.js +39 -0
- package/clis/grok/send.js +50 -0
- package/clis/grok/status.js +41 -0
- package/clis/grok/utils.js +326 -0
- package/clis/grok/utils.test.js +103 -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/datasets.js +88 -0
- package/clis/hf/hf.test.js +16 -0
- package/clis/hf/models.js +91 -0
- package/clis/hf/paper.js +79 -0
- package/clis/hf/spaces.js +101 -0
- package/clis/hf/top.js +2 -0
- package/clis/homebrew/cask.js +39 -0
- package/clis/homebrew/formula.js +41 -0
- package/clis/homebrew/popular.js +54 -0
- package/clis/homebrew/utils.js +100 -0
- package/clis/hupu/__fixtures__/hot-home.html +64 -0
- package/clis/hupu/detail.js +1 -1
- package/clis/hupu/hot.js +157 -34
- package/clis/hupu/hot.test.js +224 -0
- 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 -2
- 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 +2 -1
- package/clis/instagram/note.test.js +1 -29
- package/clis/instagram/post.js +2 -1
- package/clis/instagram/post.test.js +1 -1
- package/clis/instagram/profile.js +1 -0
- package/clis/instagram/reel.js +2 -1
- 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 +2 -1
- package/clis/instagram/story.test.js +1 -34
- 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/commands.test.js +1 -24
- 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/lichess/lichess.test.js +85 -0
- package/clis/lichess/top.js +46 -0
- package/clis/lichess/user.js +91 -0
- package/clis/lichess/utils.js +97 -0
- package/clis/linkedin/search.js +108 -10
- package/clis/linkedin/search.test.js +222 -0
- package/clis/linkedin/timeline.js +1 -0
- package/clis/linux-do/categories.js +1 -0
- package/clis/linux-do/feed.js +3 -5
- package/clis/linux-do/feed.test.js +35 -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/domain.js +92 -0
- 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/maven/artifact.js +49 -0
- package/clis/maven/search.js +51 -0
- package/clis/maven/utils.js +110 -0
- package/clis/mdn/search.js +97 -0
- package/clis/medium/feed.js +1 -0
- package/clis/medium/search.js +1 -0
- package/clis/medium/tag.js +135 -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/npm/downloads.js +59 -0
- package/clis/npm/package.js +70 -0
- package/clis/npm/search.js +49 -0
- package/clis/npm/utils.js +76 -0
- package/clis/nuget/nuget.test.js +111 -0
- package/clis/nuget/package.js +101 -0
- package/clis/nuget/search.js +69 -0
- package/clis/nuget/utils.js +87 -0
- package/clis/nvd/cve.js +121 -0
- package/clis/oeis/oeis.test.js +88 -0
- package/clis/oeis/search.js +63 -0
- package/clis/oeis/sequence.js +71 -0
- package/clis/oeis/utils.js +88 -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/openalex/search.js +69 -0
- package/clis/openalex/utils.js +160 -0
- package/clis/openalex/work.js +65 -0
- package/clis/openfda/drug-label.js +74 -0
- package/clis/openfda/food-recall.js +65 -0
- package/clis/openfda/openfda.test.js +114 -0
- package/clis/openfda/utils.js +67 -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/osv/osv.test.js +97 -0
- package/clis/osv/query.js +72 -0
- package/clis/osv/utils.js +169 -0
- package/clis/osv/vulnerability.js +54 -0
- package/clis/packagist/package.js +49 -0
- package/clis/packagist/search.js +43 -0
- package/clis/packagist/utils.js +113 -0
- package/clis/paperreview/feedback.js +2 -1
- package/clis/paperreview/review.js +2 -1
- package/clis/paperreview/submit.js +2 -1
- package/clis/pixiv/detail.js +1 -0
- package/clis/pixiv/download.js +1 -0
- package/clis/pixiv/download.test.js +1 -1
- package/clis/pixiv/illusts.js +2 -1
- package/clis/pixiv/illusts.test.js +1 -1
- package/clis/pixiv/ranking.js +2 -1
- package/clis/pixiv/search.js +2 -1
- package/clis/pixiv/search.test.js +1 -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/pubmed/article.js +50 -0
- package/clis/pubmed/author.js +64 -0
- package/clis/pubmed/citations.js +36 -0
- package/clis/pubmed/pubmed.test.js +276 -0
- package/clis/pubmed/related.js +45 -0
- package/clis/pubmed/search.js +75 -0
- package/clis/pubmed/utils.js +309 -0
- package/clis/pypi/downloads.js +66 -0
- package/clis/pypi/package.js +79 -0
- package/clis/pypi/utils.js +55 -0
- package/clis/quark/ls.js +1 -0
- package/clis/quark/mkdir.js +1 -0
- package/clis/quark/mv.js +2 -1
- package/clis/quark/rename.js +1 -0
- package/clis/quark/rm.js +1 -0
- package/clis/quark/save.js +2 -1
- package/clis/quark/share-tree.js +1 -0
- package/clis/qwen/ask.js +85 -0
- package/clis/qwen/detail.js +62 -0
- package/clis/qwen/history.js +61 -0
- package/clis/qwen/image.js +179 -0
- package/clis/qwen/new.js +23 -0
- package/clis/qwen/read.js +41 -0
- package/clis/qwen/send.js +55 -0
- package/clis/qwen/status.js +37 -0
- package/clis/qwen/utils.js +409 -0
- package/clis/qwen/utils.test.js +45 -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/rest-countries/country.js +65 -0
- package/clis/rest-countries/region.js +64 -0
- package/clis/rest-countries/rest-countries.test.js +83 -0
- package/clis/rest-countries/utils.js +126 -0
- package/clis/reuters/article-detail.js +53 -0
- package/clis/reuters/reuters.test.js +299 -0
- package/clis/reuters/search.js +46 -34
- package/clis/reuters/utils.js +159 -0
- package/clis/rfc/rfc.js +52 -0
- package/clis/rfc/rfc.test.js +74 -0
- package/clis/rfc/utils.js +72 -0
- package/clis/rubygems/gem.js +42 -0
- package/clis/rubygems/search.js +47 -0
- package/clis/rubygems/utils.js +86 -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/related.js +66 -0
- package/clis/stackoverflow/search.js +10 -2
- package/clis/stackoverflow/stackoverflow.test.js +404 -0
- package/clis/stackoverflow/tag.js +60 -0
- package/clis/stackoverflow/unanswered.js +9 -2
- package/clis/stackoverflow/user.js +50 -0
- package/clis/stackoverflow/utils.js +118 -0
- package/clis/steam/app.js +67 -0
- package/clis/steam/search.js +58 -0
- package/clis/steam/steam.test.js +46 -0
- package/clis/steam/top-sellers.js +1 -0
- package/clis/steam/utils.js +107 -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/commands.test.js +1 -24
- 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/test-utils.js +61 -0
- package/clis/ths/hot-rank.js +1 -0
- package/clis/tieba/hot.js +2 -2
- 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 +128 -40
- package/clis/tiktok/creator-videos.js +270 -0
- package/clis/tiktok/creator-videos.test.js +113 -0
- package/clis/tiktok/explore.js +138 -29
- package/clis/tiktok/follow.js +116 -33
- package/clis/tiktok/following.js +157 -35
- package/clis/tiktok/friends.js +140 -37
- package/clis/tiktok/like.js +1 -0
- package/clis/tiktok/live.js +138 -41
- package/clis/tiktok/notifications.js +142 -38
- package/clis/tiktok/profile.js +1 -0
- package/clis/tiktok/refactor.test.js +389 -0
- package/clis/tiktok/save.js +1 -0
- package/clis/tiktok/search.js +1 -0
- package/clis/tiktok/unfollow.js +125 -38
- package/clis/tiktok/unlike.js +1 -0
- package/clis/tiktok/unsave.js +1 -0
- package/clis/tiktok/user.js +204 -29
- package/clis/tiktok/utils.js +505 -0
- package/clis/tiktok/write-refactor.test.js +370 -0
- package/clis/toutiao/articles.js +37 -62
- package/clis/toutiao/hot.js +63 -0
- package/clis/toutiao/toutiao.test.js +378 -0
- package/clis/toutiao/utils.js +161 -0
- package/clis/tvmaze/search.js +61 -0
- package/clis/tvmaze/show.js +60 -0
- package/clis/tvmaze/tvmaze.test.js +93 -0
- package/clis/tvmaze/utils.js +110 -0
- package/clis/twitter/accept.js +2 -1
- 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 +135 -69
- 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 +2 -1
- package/clis/twitter/reply.js +1 -0
- package/clis/twitter/reply.test.js +1 -29
- 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/uisdc/news.js +105 -0
- package/clis/uisdc/news.test.js +66 -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 -1
- package/clis/web/read.js +48 -17
- package/clis/web/read.test.js +101 -1
- 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 +2 -1
- package/clis/weixin/download.js +1 -0
- package/clis/weixin/drafts.js +2 -1
- package/clis/weixin/drafts.test.js +5 -1
- package/clis/weixin/search.js +157 -0
- package/clis/weixin/search.test.js +227 -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/wikidata/entity.js +60 -0
- package/clis/wikidata/search.js +50 -0
- package/clis/wikidata/utils.js +117 -0
- package/clis/wikidata/wikidata.test.js +83 -0
- package/clis/wikipedia/page.js +95 -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/wttr/current.js +63 -0
- package/clis/wttr/forecast.js +71 -0
- package/clis/wttr/utils.js +50 -0
- package/clis/wttr/wttr.test.js +84 -0
- package/clis/xianyu/chat.js +17 -4
- package/clis/xianyu/chat.test.js +64 -0
- package/clis/xianyu/item.js +1 -0
- package/clis/xianyu/publish.js +485 -0
- package/clis/xianyu/publish.test.js +220 -0
- package/clis/xianyu/search.js +1 -0
- package/clis/xiaoe/catalog.js +105 -39
- package/clis/xiaoe/content.js +165 -29
- package/clis/xiaoe/courses.js +86 -28
- package/clis/xiaoe/detail.js +1 -0
- package/clis/xiaoe/play-url.js +1 -0
- package/clis/xiaoe/xiaoe.test.js +486 -0
- package/clis/xiaohongshu/comments.js +1 -0
- package/clis/xiaohongshu/creator-note-detail.js +1 -0
- package/clis/xiaohongshu/creator-notes-summary.js +2 -1
- 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 +17 -3
- package/clis/xiaohongshu/publish.test.js +46 -1
- 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 +14 -19
- package/clis/youtube/transcript.test.js +17 -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 +18 -66
- package/clis/yuanbao/ask.test.js +5 -5
- package/clis/yuanbao/detail.js +65 -0
- package/clis/yuanbao/history.js +51 -0
- package/clis/yuanbao/new.js +2 -0
- package/clis/yuanbao/read.js +38 -0
- package/clis/yuanbao/send.js +57 -0
- package/clis/yuanbao/shared.js +297 -5
- package/clis/yuanbao/shared.test.js +80 -0
- package/clis/yuanbao/status.js +44 -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/commands.test.js +1 -11
- 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 +18 -0
- package/dist/src/browser/base-page.js +116 -4
- package/dist/src/browser/base-page.test.js +108 -0
- package/dist/src/browser/cdp.d.ts +1 -0
- package/dist/src/browser/cdp.js +57 -9
- package/dist/src/browser/cdp.test.js +3 -0
- package/dist/src/browser/daemon-client.d.ts +4 -0
- package/dist/src/browser/errors.js +1 -1
- package/dist/src/browser/page.d.ts +2 -1
- package/dist/src/browser/page.js +9 -1
- package/dist/src/browser/page.test.js +46 -0
- package/dist/src/browser/target-errors.d.ts +2 -1
- package/dist/src/browser/target-errors.js +1 -0
- package/dist/src/browser/target-resolver.d.ts +35 -3
- package/dist/src/browser/target-resolver.js +59 -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 +12 -4
- package/dist/src/build-manifest.test.js +20 -20
- package/dist/src/capabilityRouting.d.ts +16 -1
- package/dist/src/capabilityRouting.js +24 -1
- package/dist/src/capabilityRouting.test.js +20 -2
- package/dist/src/cli.js +213 -12
- package/dist/src/cli.test.js +329 -0
- package/dist/src/commanderAdapter.d.ts +1 -1
- package/dist/src/commanderAdapter.js +17 -12
- package/dist/src/commanderAdapter.test.js +7 -7
- 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 +4 -5
- 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/errors.js +1 -1
- package/dist/src/execution.d.ts +1 -1
- package/dist/src/execution.js +111 -27
- package/dist/src/execution.test.js +334 -25
- package/dist/src/help.d.ts +36 -0
- package/dist/src/help.js +171 -0
- package/dist/src/help.test.d.ts +1 -0
- package/dist/src/help.test.js +54 -0
- package/dist/src/main.js +27 -8
- package/dist/src/manifest-types.d.ts +7 -3
- package/dist/src/pipeline/executor.js +1 -1
- package/dist/src/pipeline/executor.test.js +8 -0
- package/dist/src/pipeline/registry.d.ts +9 -0
- package/dist/src/pipeline/registry.js +13 -1
- package/dist/src/pipeline/steps/browser.d.ts +1 -0
- package/dist/src/pipeline/steps/browser.js +10 -0
- package/dist/src/pipeline/steps/download.test.js +1 -0
- package/dist/src/plugin.test.js +26 -26
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry.d.ts +17 -11
- package/dist/src/registry.js +24 -6
- package/dist/src/registry.test.js +29 -22
- package/dist/src/runtime.d.ts +2 -1
- package/dist/src/runtime.js +1 -1
- package/dist/src/serialization.d.ts +6 -2
- package/dist/src/serialization.js +31 -7
- package/dist/src/serialization.test.js +41 -2
- package/dist/src/types.d.ts +19 -0
- package/dist/src/validate.js +15 -11
- package/dist/src/validate.test.d.ts +9 -0
- package/dist/src/validate.test.js +90 -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/fetch-adapters.js +1 -1
- package/scripts/silent-column-drop-baseline.json +962 -0
- package/scripts/typed-error-lint-baseline.json +1514 -0
- package/clis/ctrip/search.test.js +0 -64
- package/clis/gov-policy/commands.test.js +0 -27
- package/clis/linux-do/category.js +0 -36
- package/clis/linux-do/hot.js +0 -25
- package/clis/linux-do/latest.js +0 -18
- package/clis/pixiv/test-utils.js +0 -23
- package/clis/toutiao/articles.test.js +0 -30
- package/dist/src/analysis.d.ts +0 -40
- package/dist/src/analysis.js +0 -172
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { ArgumentError, AuthRequiredError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
|
|
2
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
3
|
+
import { canonicalizeProductUrl, normalizeProductId, requireProductIdArg } from './utils.js';
|
|
4
|
+
|
|
5
|
+
function escapeJsString(value) {
|
|
6
|
+
return JSON.stringify(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Build the in-page extractor for a Coupang product detail page.
|
|
11
|
+
*
|
|
12
|
+
* Tries three sources in order, mirroring search.js's chain:
|
|
13
|
+
* 1. JSON-LD <script type="application/ld+json"> (Product schema, most stable)
|
|
14
|
+
* 2. window.__INITIAL_STATE__ / __NEXT_DATA__ / similar globals (rich)
|
|
15
|
+
* 3. DOM scrape (fallback when bootstrap state is server-side only)
|
|
16
|
+
*
|
|
17
|
+
* Returns either a partial product object or a structured failure
|
|
18
|
+
* `{ loginHints, ok: false, reason }` so the caller can map it to typed errors.
|
|
19
|
+
*
|
|
20
|
+
* Design note (no-silent-empty): empty strings / null fields here MUST mean
|
|
21
|
+
* "upstream did not provide this field" — they should not be conflated with
|
|
22
|
+
* "extraction failed". A failed extraction returns ok=false so the caller can
|
|
23
|
+
* surface AuthRequiredError or EmptyResultError; partial success returns the
|
|
24
|
+
* fields it found and the caller decides whether to treat the partial row as
|
|
25
|
+
* usable.
|
|
26
|
+
*/
|
|
27
|
+
function buildProductDetailEvaluate(expectedProductId) {
|
|
28
|
+
return `
|
|
29
|
+
(async () => {
|
|
30
|
+
const expectedProductId = ${escapeJsString(expectedProductId)};
|
|
31
|
+
const normalizeText = (value) => (value == null ? '' : String(value).trim());
|
|
32
|
+
const parseNum = (value) => {
|
|
33
|
+
const text = normalizeText(value).replace(/[^\\d.]/g, '');
|
|
34
|
+
if (!text) return null;
|
|
35
|
+
const num = Number(text);
|
|
36
|
+
return Number.isFinite(num) ? num : null;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const loginHints = {
|
|
40
|
+
hasLoginLink: Boolean(document.querySelector('a[href*="login"], a[title*="로그인"]')),
|
|
41
|
+
hasMyCoupang: /마이쿠팡/.test(document.body.innerText || ''),
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const pathMatch = location.pathname.match(/\\/vp\\/products\\/(\\d+)/);
|
|
45
|
+
const currentProductId = pathMatch?.[1] || '';
|
|
46
|
+
if (expectedProductId && currentProductId && expectedProductId !== currentProductId) {
|
|
47
|
+
return { ok: false, reason: 'PRODUCT_MISMATCH', currentProductId, loginHints };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Source 1: JSON-LD Product schema ─────────────────────────────
|
|
51
|
+
const fromJsonLd = (() => {
|
|
52
|
+
const scripts = Array.from(document.querySelectorAll('script[type="application/ld+json"]'));
|
|
53
|
+
for (const script of scripts) {
|
|
54
|
+
try {
|
|
55
|
+
const docs = JSON.parse(script.textContent || 'null');
|
|
56
|
+
const items = Array.isArray(docs) ? docs : [docs];
|
|
57
|
+
for (const doc of items) {
|
|
58
|
+
if (!doc || typeof doc !== 'object') continue;
|
|
59
|
+
const t = doc['@type'];
|
|
60
|
+
const types = Array.isArray(t) ? t : [t];
|
|
61
|
+
if (!types.some((x) => /Product/i.test(String(x || '')))) continue;
|
|
62
|
+
const offers = Array.isArray(doc.offers) ? doc.offers[0] : doc.offers;
|
|
63
|
+
return {
|
|
64
|
+
title: normalizeText(doc.name),
|
|
65
|
+
brand: normalizeText(doc.brand?.name || doc.brand),
|
|
66
|
+
image_url: normalizeText(Array.isArray(doc.image) ? doc.image[0] : doc.image),
|
|
67
|
+
price: parseNum(offers?.price),
|
|
68
|
+
rating: parseNum(doc.aggregateRating?.ratingValue),
|
|
69
|
+
review_count: parseNum(doc.aggregateRating?.reviewCount),
|
|
70
|
+
seller: normalizeText(offers?.seller?.name),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
} catch { /* malformed ld+json — skip and try next source */ }
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
})();
|
|
77
|
+
|
|
78
|
+
// ── Source 2: bootstrap globals (deeply nested vendorItem etc.) ──
|
|
79
|
+
const fromBootstrap = (() => {
|
|
80
|
+
const collect = (root) => {
|
|
81
|
+
if (!root || typeof root !== 'object') return null;
|
|
82
|
+
const queue = [root];
|
|
83
|
+
let depth = 0;
|
|
84
|
+
while (queue.length && depth < 5000) {
|
|
85
|
+
const node = queue.shift();
|
|
86
|
+
depth++;
|
|
87
|
+
if (!node || typeof node !== 'object') continue;
|
|
88
|
+
// A product-like leaf usually has both productId and salePrice / finalPrice / itemName.
|
|
89
|
+
const idCandidate = node.productId || node.product_id || node.id;
|
|
90
|
+
const titleCandidate = node.itemName || node.productName || node.name;
|
|
91
|
+
const priceCandidate = node.salePrice ?? node.finalPrice ?? node.sellingPrice ?? node.price;
|
|
92
|
+
if (idCandidate && titleCandidate && priceCandidate != null && /\\d{6,}/.test(String(idCandidate))) {
|
|
93
|
+
return {
|
|
94
|
+
product_id: String(idCandidate),
|
|
95
|
+
title: normalizeText(titleCandidate),
|
|
96
|
+
price: parseNum(priceCandidate),
|
|
97
|
+
original_price: parseNum(node.originalPrice ?? node.basePrice ?? node.listPrice),
|
|
98
|
+
discount_rate: parseNum(node.discountRate ?? node.discountPercent),
|
|
99
|
+
rating: parseNum(node.ratingAverage ?? node.rating ?? node.reviewRating),
|
|
100
|
+
review_count: parseNum(node.reviewCount ?? node.reviewsCount ?? node.ratingCount),
|
|
101
|
+
seller: normalizeText(node.vendorName ?? node.sellerName ?? node.merchantName),
|
|
102
|
+
brand: normalizeText(node.brandName ?? node.brand),
|
|
103
|
+
rocket: normalizeText(node.rocketType ?? node.deliveryBadgeType),
|
|
104
|
+
delivery_promise: normalizeText(node.deliveryPromise ?? node.arrivalText),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
for (const value of Object.values(node)) {
|
|
108
|
+
if (value && typeof value === 'object') queue.push(value);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
};
|
|
113
|
+
const candidates = [
|
|
114
|
+
window.__INITIAL_STATE__,
|
|
115
|
+
window.__NEXT_DATA__,
|
|
116
|
+
window.__APOLLO_STATE__,
|
|
117
|
+
window.__PRELOADED_STATE__,
|
|
118
|
+
];
|
|
119
|
+
for (const c of candidates) {
|
|
120
|
+
const found = collect(c);
|
|
121
|
+
if (found) return found;
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
})();
|
|
125
|
+
|
|
126
|
+
// ── Source 3: DOM fallback ───────────────────────────────────────
|
|
127
|
+
const fromDom = (() => {
|
|
128
|
+
const titleNode = document.querySelector(
|
|
129
|
+
'.prod-buy-header__title, h1.prod-buy-header__title, h1[class*="prod-buy-header"], h2.prod-buy-header__title, h1[class*="ProductName"], h1[class*="product-name"]'
|
|
130
|
+
);
|
|
131
|
+
const priceNode = document.querySelector(
|
|
132
|
+
'.total-price strong, .prod-sale-price strong, [class*="finalPrice"], [class*="sellingPrice"], [class*="price-value"]'
|
|
133
|
+
);
|
|
134
|
+
const originalPriceNode = document.querySelector(
|
|
135
|
+
'.origin-price, .base-price, del[class*="origin"], del[class*="base"], [class*="strike"], [class*="origin-price"]'
|
|
136
|
+
);
|
|
137
|
+
const discountNode = document.querySelector(
|
|
138
|
+
'.discount-percentage, [class*="discount"][class*="percent"], [class*="discountRate"]'
|
|
139
|
+
);
|
|
140
|
+
const ratingNode = document.querySelector(
|
|
141
|
+
'.rating-star-num, [class*="ratingStar"], [class*="rating-star"], [class*="rating-num"], [class*="ProductRating"]'
|
|
142
|
+
);
|
|
143
|
+
const reviewCountNode = document.querySelector(
|
|
144
|
+
'.count, .rating-total-count, [class*="reviewCount"], [class*="review-count"]'
|
|
145
|
+
);
|
|
146
|
+
const sellerNode = document.querySelector(
|
|
147
|
+
'.prod-sale-vendor-name, [class*="vendor-name"], [class*="vendorName"], [class*="sellerName"]'
|
|
148
|
+
);
|
|
149
|
+
const imageNode = document.querySelector(
|
|
150
|
+
'.prod-image__detail, [class*="prod-image"] img, [class*="ProductImage"] img'
|
|
151
|
+
);
|
|
152
|
+
return {
|
|
153
|
+
title: normalizeText(titleNode?.textContent),
|
|
154
|
+
price: parseNum(priceNode?.textContent),
|
|
155
|
+
original_price: parseNum(originalPriceNode?.textContent),
|
|
156
|
+
discount_rate: parseNum(discountNode?.textContent),
|
|
157
|
+
rating: parseNum(ratingNode?.getAttribute?.('aria-label') || ratingNode?.textContent),
|
|
158
|
+
review_count: parseNum(reviewCountNode?.textContent),
|
|
159
|
+
seller: normalizeText(sellerNode?.textContent),
|
|
160
|
+
image_url: normalizeText(imageNode?.getAttribute?.('src') || imageNode?.getAttribute?.('data-src')),
|
|
161
|
+
};
|
|
162
|
+
})();
|
|
163
|
+
|
|
164
|
+
// Merge with priority: bootstrap > jsonld > dom (bootstrap is freshest /
|
|
165
|
+
// closest to the API; jsonld is well-typed; dom is last-resort).
|
|
166
|
+
const merge = (a, b) => {
|
|
167
|
+
if (!a) return b;
|
|
168
|
+
if (!b) return a;
|
|
169
|
+
const out = { ...a };
|
|
170
|
+
for (const [k, v] of Object.entries(b)) {
|
|
171
|
+
if (out[k] == null || out[k] === '') out[k] = v;
|
|
172
|
+
}
|
|
173
|
+
return out;
|
|
174
|
+
};
|
|
175
|
+
const merged = merge(merge(fromBootstrap, fromJsonLd), fromDom);
|
|
176
|
+
const hasAnyField = merged && (merged.title || merged.price != null);
|
|
177
|
+
if (!hasAnyField) {
|
|
178
|
+
return { ok: false, reason: 'NO_DATA_EXTRACTED', currentProductId, loginHints };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
ok: true,
|
|
183
|
+
currentProductId,
|
|
184
|
+
loginHints,
|
|
185
|
+
data: merged,
|
|
186
|
+
};
|
|
187
|
+
})()
|
|
188
|
+
`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
cli({
|
|
192
|
+
site: 'coupang',
|
|
193
|
+
name: 'product',
|
|
194
|
+
access: 'read',
|
|
195
|
+
description: 'Read full product detail (price, rating, seller, delivery) for a Coupang product',
|
|
196
|
+
domain: 'www.coupang.com',
|
|
197
|
+
strategy: Strategy.COOKIE,
|
|
198
|
+
browser: true,
|
|
199
|
+
args: [
|
|
200
|
+
{ name: 'product-id', positional: true, required: false, help: 'Coupang product ID (digits only)' },
|
|
201
|
+
{ name: 'url', required: false, help: 'Canonical Coupang product URL (alternative to --product-id)' },
|
|
202
|
+
],
|
|
203
|
+
columns: [
|
|
204
|
+
'product_id', 'title', 'price', 'original_price', 'discount_rate',
|
|
205
|
+
'rating', 'review_count', 'seller', 'brand', 'rocket',
|
|
206
|
+
'delivery_promise', 'image_url', 'url',
|
|
207
|
+
],
|
|
208
|
+
func: async (page, kwargs) => {
|
|
209
|
+
const rawProductId = kwargs['product-id'];
|
|
210
|
+
if (!rawProductId && !kwargs.url) {
|
|
211
|
+
throw new ArgumentError('Either --product-id or --url is required');
|
|
212
|
+
}
|
|
213
|
+
const productId = rawProductId
|
|
214
|
+
? requireProductIdArg(rawProductId, 'product-id')
|
|
215
|
+
: requireProductIdArg(kwargs.url, '--url');
|
|
216
|
+
const targetUrl = canonicalizeProductUrl(kwargs.url, productId);
|
|
217
|
+
const finalUrl = targetUrl || canonicalizeProductUrl('', productId);
|
|
218
|
+
await page.goto(finalUrl).catch((error) => {
|
|
219
|
+
throw new CommandExecutionError(`coupang product navigation failed: ${error?.message || error}`);
|
|
220
|
+
});
|
|
221
|
+
await page.wait(2).catch((error) => {
|
|
222
|
+
throw new CommandExecutionError(`coupang product wait failed: ${error?.message || error}`);
|
|
223
|
+
});
|
|
224
|
+
const result = await page.evaluate(buildProductDetailEvaluate(productId)).catch((error) => {
|
|
225
|
+
throw new CommandExecutionError(`coupang product extraction failed: ${error?.message || error}`);
|
|
226
|
+
});
|
|
227
|
+
const loginHints = result?.loginHints ?? {};
|
|
228
|
+
if (loginHints.hasLoginLink && !loginHints.hasMyCoupang) {
|
|
229
|
+
throw new AuthRequiredError('coupang.com', 'Please log into Coupang in Chrome and retry.');
|
|
230
|
+
}
|
|
231
|
+
if (result?.reason === 'PRODUCT_MISMATCH') {
|
|
232
|
+
const actualProductId = normalizeProductId(result?.currentProductId || '');
|
|
233
|
+
const observed = actualProductId ? `got ${actualProductId}` : 'no product id observed';
|
|
234
|
+
throw new EmptyResultError('coupang product', `Product page redirected: expected ${productId}, ${observed} (item may be sold out or unavailable in your region)`);
|
|
235
|
+
}
|
|
236
|
+
if (!result?.ok || !result?.data) {
|
|
237
|
+
throw new EmptyResultError('coupang product', `No product data extracted from ${finalUrl}. The page may have failed to render or this product is restricted.`);
|
|
238
|
+
}
|
|
239
|
+
const actualProductId = normalizeProductId(result?.currentProductId || result.data.product_id || productId);
|
|
240
|
+
const data = result.data;
|
|
241
|
+
return [{
|
|
242
|
+
product_id: actualProductId,
|
|
243
|
+
title: data.title || null,
|
|
244
|
+
price: data.price ?? null,
|
|
245
|
+
original_price: data.original_price ?? null,
|
|
246
|
+
discount_rate: data.discount_rate ?? null,
|
|
247
|
+
rating: data.rating ?? null,
|
|
248
|
+
review_count: data.review_count ?? null,
|
|
249
|
+
seller: data.seller || null,
|
|
250
|
+
brand: data.brand || null,
|
|
251
|
+
rocket: data.rocket || null,
|
|
252
|
+
delivery_promise: data.delivery_promise || null,
|
|
253
|
+
image_url: data.image_url || null,
|
|
254
|
+
url: canonicalizeProductUrl('', actualProductId) || finalUrl,
|
|
255
|
+
}];
|
|
256
|
+
},
|
|
257
|
+
});
|
package/clis/coupang/search.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { ArgumentError, AuthRequiredError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
|
|
1
2
|
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
2
|
-
import { mergeSearchItems, normalizeSearchItem, sanitizeSearchItems } from './utils.js';
|
|
3
|
+
import { mergeSearchItems, normalizeSearchItem, parseLimitArg, parsePageArg, sanitizeSearchItems } from './utils.js';
|
|
3
4
|
function escapeJsString(value) {
|
|
4
5
|
return JSON.stringify(value);
|
|
5
6
|
}
|
|
@@ -399,6 +400,7 @@ function buildSearchEvaluate(query, limit, pageNumber) {
|
|
|
399
400
|
cli({
|
|
400
401
|
site: 'coupang',
|
|
401
402
|
name: 'search',
|
|
403
|
+
access: 'read',
|
|
402
404
|
description: 'Search Coupang products with logged-in browser session',
|
|
403
405
|
domain: 'www.coupang.com',
|
|
404
406
|
strategy: Strategy.COOKIE,
|
|
@@ -409,32 +411,50 @@ cli({
|
|
|
409
411
|
{ name: 'limit', type: 'int', default: 20, help: 'Max results (max 50)' },
|
|
410
412
|
{ name: 'filter', required: false, help: 'Optional search filter (currently supports: rocket)' },
|
|
411
413
|
],
|
|
412
|
-
columns: ['rank', 'title', 'price', 'unit_price', 'rating', 'review_count', 'rocket', 'delivery_type', 'delivery_promise', 'url'],
|
|
414
|
+
columns: ['rank', 'product_id', 'title', 'price', 'unit_price', 'rating', 'review_count', 'rocket', 'delivery_type', 'delivery_promise', 'url'],
|
|
413
415
|
func: async (page, kwargs) => {
|
|
414
416
|
const query = String(kwargs.query || '').trim();
|
|
415
|
-
|
|
416
|
-
|
|
417
|
+
if (!query) {
|
|
418
|
+
throw new ArgumentError('query cannot be empty');
|
|
419
|
+
}
|
|
420
|
+
const pageNumber = parsePageArg(kwargs.page, 1);
|
|
421
|
+
const limit = parseLimitArg(kwargs.limit, 20, 50);
|
|
417
422
|
const filter = String(kwargs.filter || '').trim().toLowerCase();
|
|
418
|
-
if (
|
|
419
|
-
throw new
|
|
423
|
+
if (filter && filter !== 'rocket') {
|
|
424
|
+
throw new ArgumentError(`Unsupported --filter "${filter}" (supported: rocket)`);
|
|
425
|
+
}
|
|
420
426
|
const initialPage = filter ? 1 : pageNumber;
|
|
421
427
|
const url = `https://www.coupang.com/np/search?q=${encodeURIComponent(query)}&channel=user&page=${initialPage}`;
|
|
422
|
-
await page.goto(url)
|
|
428
|
+
await page.goto(url).catch((error) => {
|
|
429
|
+
throw new CommandExecutionError(`coupang search navigation failed: ${error?.message || error}`);
|
|
430
|
+
});
|
|
423
431
|
if (filter) {
|
|
424
|
-
const filterResult = await page.evaluate(buildApplyFilterEvaluate(filter))
|
|
432
|
+
const filterResult = await page.evaluate(buildApplyFilterEvaluate(filter)).catch((error) => {
|
|
433
|
+
throw new CommandExecutionError(`coupang search filter evaluation failed: ${error?.message || error}`);
|
|
434
|
+
});
|
|
425
435
|
if (!filterResult?.ok) {
|
|
426
|
-
throw new
|
|
436
|
+
throw new EmptyResultError('coupang search', `Filter "${filter}" was not available on the current page; try without --filter or wait for Coupang to render the filter bar.`);
|
|
427
437
|
}
|
|
428
|
-
await page.wait(3)
|
|
438
|
+
await page.wait(3).catch((error) => {
|
|
439
|
+
throw new CommandExecutionError(`coupang search wait failed: ${error?.message || error}`);
|
|
440
|
+
});
|
|
429
441
|
if (pageNumber > 1) {
|
|
430
|
-
const locationInfo = await page.evaluate(buildCurrentLocationEvaluate())
|
|
442
|
+
const locationInfo = await page.evaluate(buildCurrentLocationEvaluate()).catch((error) => {
|
|
443
|
+
throw new CommandExecutionError(`coupang search location evaluation failed: ${error?.message || error}`);
|
|
444
|
+
});
|
|
431
445
|
const filteredUrl = new URL(locationInfo?.href || url);
|
|
432
446
|
filteredUrl.searchParams.set('page', String(pageNumber));
|
|
433
|
-
await page.goto(filteredUrl.toString())
|
|
447
|
+
await page.goto(filteredUrl.toString()).catch((error) => {
|
|
448
|
+
throw new CommandExecutionError(`coupang search filtered navigation failed: ${error?.message || error}`);
|
|
449
|
+
});
|
|
434
450
|
}
|
|
435
451
|
}
|
|
436
|
-
await page.autoScroll({ times: filter ? 3 : 2, delayMs: 1500 })
|
|
437
|
-
|
|
452
|
+
await page.autoScroll({ times: filter ? 3 : 2, delayMs: 1500 }).catch((error) => {
|
|
453
|
+
throw new CommandExecutionError(`coupang search scroll failed: ${error?.message || error}`);
|
|
454
|
+
});
|
|
455
|
+
const raw = await page.evaluate(buildSearchEvaluate(query, limit, pageNumber)).catch((error) => {
|
|
456
|
+
throw new CommandExecutionError(`coupang search extraction failed: ${error?.message || error}`);
|
|
457
|
+
});
|
|
438
458
|
const loginHints = raw?.loginHints ?? {};
|
|
439
459
|
const items = Array.isArray(raw?.items) ? raw.items : [];
|
|
440
460
|
const domItems = Array.isArray(raw?.domItems) ? raw.domItems : [];
|
|
@@ -443,8 +463,11 @@ cli({
|
|
|
443
463
|
const normalized = filter
|
|
444
464
|
? sanitizeSearchItems(normalizedDom, limit)
|
|
445
465
|
: mergeSearchItems(normalizedBase, normalizedDom, limit);
|
|
446
|
-
if (!normalized.length
|
|
447
|
-
|
|
466
|
+
if (!normalized.length) {
|
|
467
|
+
if (loginHints.hasLoginLink && !loginHints.hasMyCoupang) {
|
|
468
|
+
throw new AuthRequiredError('coupang.com', 'Please log into Coupang in Chrome and retry.');
|
|
469
|
+
}
|
|
470
|
+
throw new EmptyResultError('coupang search', `No products matched "${query}". Try a more specific keyword or remove --filter.`);
|
|
448
471
|
}
|
|
449
472
|
return normalized;
|
|
450
473
|
},
|
package/clis/coupang/utils.js
CHANGED
|
@@ -1,3 +1,36 @@
|
|
|
1
|
+
import { ArgumentError } from '@jackwener/opencli/errors';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse a positive integer arg (--limit / --page / --review-page).
|
|
5
|
+
*
|
|
6
|
+
* Throws ArgumentError on out-of-range / non-integer values rather than
|
|
7
|
+
* silently clamping. We prefer typed-fail-fast over silent clamping for the
|
|
8
|
+
* same reason as feedback_typed_fail_fast_for_adapters: callers cannot tell
|
|
9
|
+
* that their value was rewritten and end up confused why "limit=999" returned
|
|
10
|
+
* 50 rows.
|
|
11
|
+
*/
|
|
12
|
+
export function parseLimitArg(raw, fallback, max) {
|
|
13
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
16
|
+
const num = Number(raw);
|
|
17
|
+
if (!Number.isInteger(num) || num < 1 || num > max) {
|
|
18
|
+
throw new ArgumentError(`--limit must be an integer between 1 and ${max} (got ${raw})`);
|
|
19
|
+
}
|
|
20
|
+
return num;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function parsePageArg(raw, fallback) {
|
|
24
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
27
|
+
const num = Number(raw);
|
|
28
|
+
if (!Number.isInteger(num) || num < 1) {
|
|
29
|
+
throw new ArgumentError(`--page must be a positive integer (got ${raw})`);
|
|
30
|
+
}
|
|
31
|
+
return num;
|
|
32
|
+
}
|
|
33
|
+
|
|
1
34
|
function itemKey(item) {
|
|
2
35
|
return item.url || item.product_id || `${item.title}:${item.price ?? ''}`;
|
|
3
36
|
}
|
|
@@ -61,7 +94,28 @@ export function normalizeProductId(raw) {
|
|
|
61
94
|
if (!text)
|
|
62
95
|
return '';
|
|
63
96
|
const match = text.match(/\/vp\/products\/(\d+)/) || text.match(/\b(\d{6,})\b/);
|
|
64
|
-
return match?.[1] ??
|
|
97
|
+
return match?.[1] ?? '';
|
|
98
|
+
}
|
|
99
|
+
export function requireProductIdArg(raw, label = '--product-id') {
|
|
100
|
+
const text = asString(raw);
|
|
101
|
+
if (label === '--url') {
|
|
102
|
+
try {
|
|
103
|
+
const url = new URL(text.startsWith('http') ? text : `https://www.coupang.com${text}`);
|
|
104
|
+
const match = url.pathname.match(/^\/vp\/products\/(\d{6,})(?:\/|$)/);
|
|
105
|
+
const isCoupangHost = url.hostname === 'coupang.com' || url.hostname.endsWith('.coupang.com');
|
|
106
|
+
if (isCoupangHost && match) {
|
|
107
|
+
return match[1];
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
// Fall through to the typed validation error below.
|
|
112
|
+
}
|
|
113
|
+
throw new ArgumentError(`${label} must be a Coupang product URL containing /vp/products/<id>`);
|
|
114
|
+
}
|
|
115
|
+
if (!/^\d{6,}$/.test(text)) {
|
|
116
|
+
throw new ArgumentError(`${label} must be a numeric Coupang product ID`);
|
|
117
|
+
}
|
|
118
|
+
return text;
|
|
65
119
|
}
|
|
66
120
|
export function canonicalizeProductUrl(rawUrl, productId) {
|
|
67
121
|
const raw = asString(rawUrl);
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// crates crate — fetch a single crate's metadata.
|
|
2
|
+
//
|
|
3
|
+
// Hits `https://crates.io/api/v1/crates/<name>`. Returns the agent-useful
|
|
4
|
+
// projection: name, latest version, description, total + recent downloads,
|
|
5
|
+
// homepage / docs / repo, license (from latest version row), version count,
|
|
6
|
+
// created / updated timestamps.
|
|
7
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
8
|
+
import { EmptyResultError } from '@jackwener/opencli/errors';
|
|
9
|
+
import { CRATES_BASE, cratesFetch, requireCrateName } from './utils.js';
|
|
10
|
+
|
|
11
|
+
cli({
|
|
12
|
+
site: 'crates',
|
|
13
|
+
name: 'crate',
|
|
14
|
+
access: 'read',
|
|
15
|
+
description: 'Single crates.io crate metadata (latest version, downloads, license, repo)',
|
|
16
|
+
domain: 'crates.io',
|
|
17
|
+
strategy: Strategy.PUBLIC,
|
|
18
|
+
browser: false,
|
|
19
|
+
args: [
|
|
20
|
+
{ name: 'name', positional: true, required: true, help: 'crates.io crate name (e.g. "serde", "tokio")' },
|
|
21
|
+
],
|
|
22
|
+
columns: [
|
|
23
|
+
'name', 'latestVersion', 'description', 'downloads', 'recentDownloads', 'versions',
|
|
24
|
+
'license', 'homepage', 'documentation', 'repository', 'keywords', 'categories', 'created', 'updated', 'url',
|
|
25
|
+
],
|
|
26
|
+
func: async (args) => {
|
|
27
|
+
const name = requireCrateName(args.name);
|
|
28
|
+
const body = await cratesFetch(`${CRATES_BASE}/api/v1/crates/${encodeURIComponent(name)}`, `crates crate ${name}`);
|
|
29
|
+
const c = body?.crate;
|
|
30
|
+
if (!c || !c.id) {
|
|
31
|
+
throw new EmptyResultError('crates crate', `crates.io returned no metadata for "${name}".`);
|
|
32
|
+
}
|
|
33
|
+
const versions = Array.isArray(body.versions) ? body.versions : [];
|
|
34
|
+
const latestRow = versions.find((v) => v.num === c.newest_version)
|
|
35
|
+
|| versions.find((v) => v.num === c.max_stable_version)
|
|
36
|
+
|| versions[0]
|
|
37
|
+
|| {};
|
|
38
|
+
const keywords = Array.isArray(body.keywords)
|
|
39
|
+
? body.keywords.map((k) => k?.keyword || k?.id || '').filter(Boolean).join(', ')
|
|
40
|
+
: '';
|
|
41
|
+
const categories = Array.isArray(body.categories)
|
|
42
|
+
? body.categories.map((cat) => cat?.category || cat?.slug || '').filter(Boolean).join(', ')
|
|
43
|
+
: '';
|
|
44
|
+
return [{
|
|
45
|
+
name: String(c.name ?? c.id),
|
|
46
|
+
latestVersion: String(c.newest_version ?? c.max_stable_version ?? c.max_version ?? ''),
|
|
47
|
+
description: String(c.description ?? '').trim(),
|
|
48
|
+
downloads: c.downloads != null ? Number(c.downloads) : null,
|
|
49
|
+
recentDownloads: c.recent_downloads != null ? Number(c.recent_downloads) : null,
|
|
50
|
+
versions: c.num_versions != null ? Number(c.num_versions) : versions.length,
|
|
51
|
+
license: String(latestRow.license ?? ''),
|
|
52
|
+
homepage: String(c.homepage ?? ''),
|
|
53
|
+
documentation: String(c.documentation ?? ''),
|
|
54
|
+
repository: String(c.repository ?? ''),
|
|
55
|
+
keywords,
|
|
56
|
+
categories,
|
|
57
|
+
created: String(c.created_at ?? '').slice(0, 10),
|
|
58
|
+
updated: String(c.updated_at ?? '').slice(0, 10),
|
|
59
|
+
url: `https://crates.io/crates/${c.name ?? c.id}`,
|
|
60
|
+
}];
|
|
61
|
+
},
|
|
62
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// crates search — search the crates.io registry by free-text query.
|
|
2
|
+
//
|
|
3
|
+
// Hits `https://crates.io/api/v1/crates?q=…&per_page=…`. Returns name (round-
|
|
4
|
+
// trips into `crates crate`), latest version, description, downloads, recent
|
|
5
|
+
// downloads, repository, last-update.
|
|
6
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
7
|
+
import { EmptyResultError } from '@jackwener/opencli/errors';
|
|
8
|
+
import { CRATES_BASE, cratesFetch, requireBoundedInt, requireString } from './utils.js';
|
|
9
|
+
|
|
10
|
+
cli({
|
|
11
|
+
site: 'crates',
|
|
12
|
+
name: 'search',
|
|
13
|
+
access: 'read',
|
|
14
|
+
description: 'Search the public crates.io registry by keyword',
|
|
15
|
+
domain: 'crates.io',
|
|
16
|
+
strategy: Strategy.PUBLIC,
|
|
17
|
+
browser: false,
|
|
18
|
+
args: [
|
|
19
|
+
{ name: 'query', positional: true, required: true, help: 'Search keyword (e.g. "serde", "async runtime")' },
|
|
20
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Max results (1-100)' },
|
|
21
|
+
],
|
|
22
|
+
columns: ['rank', 'name', 'latestVersion', 'description', 'downloads', 'recentDownloads', 'repository', 'updated', 'url'],
|
|
23
|
+
func: async (args) => {
|
|
24
|
+
const query = requireString(args.query, 'query');
|
|
25
|
+
const limit = requireBoundedInt(args.limit, 20, 100);
|
|
26
|
+
const url = `${CRATES_BASE}/api/v1/crates?q=${encodeURIComponent(query)}&per_page=${limit}`;
|
|
27
|
+
const body = await cratesFetch(url, 'crates search');
|
|
28
|
+
const list = Array.isArray(body?.crates) ? body.crates : [];
|
|
29
|
+
if (!list.length) {
|
|
30
|
+
throw new EmptyResultError('crates search', `No crates.io results matched "${query}".`);
|
|
31
|
+
}
|
|
32
|
+
return list.slice(0, limit).map((c, i) => ({
|
|
33
|
+
rank: i + 1,
|
|
34
|
+
name: String(c.name ?? c.id ?? ''),
|
|
35
|
+
latestVersion: String(c.newest_version ?? c.max_stable_version ?? c.max_version ?? ''),
|
|
36
|
+
description: String(c.description ?? '').trim(),
|
|
37
|
+
downloads: c.downloads != null ? Number(c.downloads) : null,
|
|
38
|
+
recentDownloads: c.recent_downloads != null ? Number(c.recent_downloads) : null,
|
|
39
|
+
repository: String(c.repository ?? c.homepage ?? ''),
|
|
40
|
+
updated: String(c.updated_at ?? '').slice(0, 10),
|
|
41
|
+
url: c.name ? `https://crates.io/crates/${c.name}` : '',
|
|
42
|
+
}));
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// Shared helpers for the crates.io adapters.
|
|
2
|
+
import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
|
|
3
|
+
|
|
4
|
+
export const CRATES_BASE = 'https://crates.io';
|
|
5
|
+
const UA = 'opencli-crates-adapter (+https://github.com/jackwener/opencli)';
|
|
6
|
+
|
|
7
|
+
// crates.io crate names: 1-64 chars, ascii letters/digits/-_, must start with a letter.
|
|
8
|
+
const CRATE_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
|
|
9
|
+
|
|
10
|
+
export function requireString(value, label) {
|
|
11
|
+
const s = String(value ?? '').trim();
|
|
12
|
+
if (!s) throw new ArgumentError(`crates ${label} cannot be empty`);
|
|
13
|
+
return s;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function requireCrateName(value) {
|
|
17
|
+
const s = String(value ?? '').trim();
|
|
18
|
+
if (!s) throw new ArgumentError('crates crate name is required (e.g. "serde", "tokio")');
|
|
19
|
+
if (!CRATE_NAME.test(s)) {
|
|
20
|
+
throw new ArgumentError(
|
|
21
|
+
`crates crate name "${value}" is not a valid crates.io name`,
|
|
22
|
+
'Names start with an ASCII letter, then 0-63 chars of letters / digits / "_-".',
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return s;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function requireBoundedInt(value, defaultValue, maxValue, label = 'limit') {
|
|
29
|
+
const raw = value ?? defaultValue;
|
|
30
|
+
const n = typeof raw === 'number' ? raw : Number(raw);
|
|
31
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
32
|
+
throw new ArgumentError(`crates ${label} must be a positive integer`);
|
|
33
|
+
}
|
|
34
|
+
if (n > maxValue) {
|
|
35
|
+
throw new ArgumentError(`crates ${label} must be <= ${maxValue}`);
|
|
36
|
+
}
|
|
37
|
+
return n;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function cratesFetch(url, label) {
|
|
41
|
+
let resp;
|
|
42
|
+
try {
|
|
43
|
+
// crates.io requires a descriptive User-Agent per https://crates.io/data-access
|
|
44
|
+
resp = await fetch(url, { headers: { 'user-agent': UA, accept: 'application/json' } });
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
throw new CommandExecutionError(
|
|
48
|
+
`${label} request failed: ${err?.message ?? err}`,
|
|
49
|
+
'Check that crates.io is reachable from this network.',
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (resp.status === 404) {
|
|
53
|
+
throw new EmptyResultError(label, `crates.io returned 404 for ${url}.`);
|
|
54
|
+
}
|
|
55
|
+
if (resp.status === 429) {
|
|
56
|
+
throw new CommandExecutionError(
|
|
57
|
+
`${label} returned HTTP 429 (rate limited)`,
|
|
58
|
+
'crates.io rate-limits unauthenticated traffic; wait a few seconds and retry.',
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
if (!resp.ok) {
|
|
62
|
+
throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);
|
|
63
|
+
}
|
|
64
|
+
let body;
|
|
65
|
+
try {
|
|
66
|
+
body = await resp.json();
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);
|
|
70
|
+
}
|
|
71
|
+
return body;
|
|
72
|
+
}
|