@jackwener/opencli 1.6.9 → 1.6.10
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 +115 -59
- package/README.zh-CN.md +121 -78
- package/dist/clis/_shared/common.d.ts +3 -0
- package/dist/clis/_shared/common.js +22 -0
- package/dist/clis/bilibili/hot.d.ts +1 -0
- package/dist/clis/bilibili/hot.js +35 -0
- package/dist/clis/bluesky/feeds.d.ts +1 -0
- package/dist/clis/bluesky/feeds.js +27 -0
- package/dist/clis/bluesky/followers.d.ts +1 -0
- package/dist/clis/bluesky/followers.js +27 -0
- package/dist/clis/bluesky/following.d.ts +1 -0
- package/dist/clis/bluesky/following.js +27 -0
- package/dist/clis/bluesky/profile.d.ts +1 -0
- package/dist/clis/bluesky/profile.js +29 -0
- package/dist/clis/bluesky/search.d.ts +1 -0
- package/dist/clis/bluesky/search.js +28 -0
- package/dist/clis/bluesky/starter-packs.d.ts +1 -0
- package/dist/clis/bluesky/starter-packs.js +28 -0
- package/dist/clis/bluesky/thread.d.ts +1 -0
- package/dist/clis/bluesky/thread.js +30 -0
- package/dist/clis/bluesky/trending.d.ts +1 -0
- package/dist/clis/bluesky/trending.js +19 -0
- package/dist/clis/bluesky/user.d.ts +1 -0
- package/dist/clis/bluesky/user.js +33 -0
- package/dist/clis/cnki/search.d.ts +1 -0
- package/dist/clis/cnki/search.js +60 -0
- package/dist/clis/cnki/search.test.d.ts +1 -0
- package/dist/clis/cnki/search.test.js +18 -0
- package/dist/clis/devto/tag.d.ts +1 -0
- package/dist/clis/devto/tag.js +32 -0
- package/dist/clis/devto/top.d.ts +1 -0
- package/dist/clis/devto/top.js +26 -0
- package/dist/clis/devto/user.d.ts +1 -0
- package/dist/clis/devto/user.js +31 -0
- package/dist/clis/dictionary/examples.d.ts +1 -0
- package/dist/clis/dictionary/examples.js +27 -0
- package/dist/clis/dictionary/search.d.ts +1 -0
- package/dist/clis/dictionary/search.js +29 -0
- package/dist/clis/dictionary/synonyms.d.ts +1 -0
- package/dist/clis/dictionary/synonyms.js +27 -0
- package/dist/clis/douban/subject.d.ts +1 -0
- package/dist/clis/douban/subject.js +118 -0
- package/dist/clis/douban/top250.d.ts +1 -0
- package/dist/clis/douban/top250.js +67 -0
- package/dist/clis/facebook/add-friend.d.ts +1 -0
- package/dist/clis/facebook/add-friend.js +43 -0
- package/dist/clis/facebook/events.d.ts +1 -0
- package/dist/clis/facebook/events.js +40 -0
- package/dist/clis/facebook/feed.d.ts +1 -0
- package/dist/clis/facebook/feed.js +59 -0
- package/dist/clis/facebook/friends.d.ts +1 -0
- package/dist/clis/facebook/friends.js +38 -0
- package/dist/clis/facebook/groups.d.ts +1 -0
- package/dist/clis/facebook/groups.js +46 -0
- package/dist/clis/facebook/join-group.d.ts +1 -0
- package/dist/clis/facebook/join-group.js +44 -0
- package/dist/clis/facebook/memories.d.ts +1 -0
- package/dist/clis/facebook/memories.js +35 -0
- package/dist/clis/facebook/notifications.d.ts +1 -0
- package/dist/clis/facebook/notifications.js +36 -0
- package/dist/clis/facebook/profile.d.ts +1 -0
- package/dist/clis/facebook/profile.js +37 -0
- package/dist/clis/facebook/search.d.ts +1 -0
- package/dist/clis/facebook/search.js +38 -0
- package/dist/clis/facebook/search.test.d.ts +1 -1
- package/dist/clis/facebook/search.test.js +6 -9
- package/dist/clis/gitee/index.d.ts +3 -0
- package/dist/clis/gitee/index.js +3 -0
- package/dist/clis/gitee/search.d.ts +1 -0
- package/dist/clis/gitee/search.js +136 -0
- package/dist/clis/gitee/trending.d.ts +1 -0
- package/dist/clis/gitee/trending.js +567 -0
- package/dist/clis/gitee/user.d.ts +1 -0
- package/dist/clis/gitee/user.js +199 -0
- package/dist/clis/gitee/user.test.d.ts +1 -0
- package/dist/clis/gitee/user.test.js +63 -0
- package/dist/clis/hackernews/ask.d.ts +1 -0
- package/dist/clis/hackernews/ask.js +29 -0
- package/dist/clis/hackernews/best.d.ts +1 -0
- package/dist/clis/hackernews/best.js +29 -0
- package/dist/clis/hackernews/jobs.d.ts +1 -0
- package/dist/clis/hackernews/jobs.js +27 -0
- package/dist/clis/hackernews/new.d.ts +1 -0
- package/dist/clis/hackernews/new.js +29 -0
- package/dist/clis/hackernews/search.d.ts +1 -0
- package/dist/clis/hackernews/search.js +36 -0
- package/dist/clis/hackernews/show.d.ts +1 -0
- package/dist/clis/hackernews/show.js +29 -0
- package/dist/clis/hackernews/top.d.ts +1 -0
- package/dist/clis/hackernews/top.js +29 -0
- package/dist/clis/hackernews/user.d.ts +1 -0
- package/dist/clis/hackernews/user.js +22 -0
- package/dist/clis/hupu/hot.d.ts +1 -0
- package/dist/clis/hupu/hot.js +40 -0
- package/dist/clis/instagram/comment.d.ts +1 -0
- package/dist/clis/instagram/comment.js +47 -0
- package/dist/clis/instagram/explore.d.ts +1 -0
- package/dist/clis/instagram/explore.js +41 -0
- package/dist/clis/instagram/follow.d.ts +1 -0
- package/dist/clis/instagram/follow.js +43 -0
- package/dist/clis/instagram/followers.d.ts +1 -0
- package/dist/clis/instagram/followers.js +45 -0
- package/dist/clis/instagram/following.d.ts +1 -0
- package/dist/clis/instagram/following.js +45 -0
- package/dist/clis/instagram/like.d.ts +1 -0
- package/dist/clis/instagram/like.js +45 -0
- package/dist/clis/instagram/profile.d.ts +1 -0
- package/dist/clis/instagram/profile.js +39 -0
- package/dist/clis/instagram/save.d.ts +1 -0
- package/dist/clis/instagram/save.js +45 -0
- package/dist/clis/instagram/saved.d.ts +1 -0
- package/dist/clis/instagram/saved.js +38 -0
- package/dist/clis/instagram/search.d.ts +1 -0
- package/dist/clis/instagram/search.js +38 -0
- package/dist/clis/instagram/unfollow.d.ts +1 -0
- package/dist/clis/instagram/unfollow.js +40 -0
- package/dist/clis/instagram/unlike.d.ts +1 -0
- package/dist/clis/instagram/unlike.js +45 -0
- package/dist/clis/instagram/unsave.d.ts +1 -0
- package/dist/clis/instagram/unsave.js +45 -0
- package/dist/clis/instagram/user.d.ts +1 -0
- package/dist/clis/instagram/user.js +48 -0
- package/dist/clis/jd/add-cart.d.ts +1 -0
- package/dist/clis/jd/add-cart.js +71 -0
- package/dist/clis/jd/cart.d.ts +1 -0
- package/dist/clis/jd/cart.js +79 -0
- package/dist/clis/jd/commands.test.d.ts +5 -0
- package/dist/clis/jd/commands.test.js +64 -0
- package/dist/clis/jd/detail.d.ts +1 -0
- package/dist/clis/jd/detail.js +62 -0
- package/dist/clis/jd/reviews.d.ts +1 -0
- package/dist/clis/jd/reviews.js +54 -0
- package/dist/clis/jd/search.d.ts +1 -0
- package/dist/clis/jd/search.js +65 -0
- package/dist/clis/jike/post.d.ts +1 -0
- package/dist/clis/jike/post.js +61 -0
- package/dist/clis/jike/topic.d.ts +1 -0
- package/dist/clis/jike/topic.js +51 -0
- package/dist/clis/jike/user.d.ts +1 -0
- package/dist/clis/jike/user.js +50 -0
- package/dist/clis/jimeng/generate.d.ts +1 -0
- package/dist/clis/jimeng/generate.js +83 -0
- package/dist/clis/jimeng/history.d.ts +1 -0
- package/dist/clis/jimeng/history.js +47 -0
- package/dist/clis/jimeng/new.d.ts +1 -0
- package/dist/clis/jimeng/new.js +43 -0
- package/dist/clis/jimeng/workspaces.d.ts +1 -0
- package/dist/clis/jimeng/workspaces.js +41 -0
- package/dist/clis/linux-do/categories.d.ts +1 -0
- package/dist/clis/linux-do/categories.js +65 -0
- package/dist/clis/linux-do/search.d.ts +1 -0
- package/dist/clis/linux-do/search.js +41 -0
- package/dist/clis/linux-do/tags.d.ts +1 -0
- package/dist/clis/linux-do/tags.js +39 -0
- package/dist/clis/linux-do/topic-content.test.js +5 -5
- package/dist/clis/linux-do/topic.d.ts +1 -0
- package/dist/clis/linux-do/topic.js +56 -0
- package/dist/clis/linux-do/user-posts.d.ts +1 -0
- package/dist/clis/linux-do/user-posts.js +61 -0
- package/dist/clis/linux-do/user-topics.d.ts +1 -0
- package/dist/clis/linux-do/user-topics.js +48 -0
- package/dist/clis/lobsters/active.d.ts +1 -0
- package/dist/clis/lobsters/active.js +26 -0
- package/dist/clis/lobsters/hot.d.ts +1 -0
- package/dist/clis/lobsters/hot.js +26 -0
- package/dist/clis/lobsters/newest.d.ts +1 -0
- package/dist/clis/lobsters/newest.js +26 -0
- package/dist/clis/lobsters/tag.d.ts +1 -0
- package/dist/clis/lobsters/tag.js +32 -0
- package/dist/clis/pixiv/detail.d.ts +1 -0
- package/dist/clis/pixiv/detail.js +58 -0
- package/dist/clis/pixiv/ranking.d.ts +1 -0
- package/dist/clis/pixiv/ranking.js +59 -0
- package/dist/clis/pixiv/user.d.ts +1 -0
- package/dist/clis/pixiv/user.js +52 -0
- package/dist/clis/reddit/frontpage.d.ts +1 -0
- package/dist/clis/reddit/frontpage.js +31 -0
- package/dist/clis/reddit/hot.d.ts +1 -0
- package/dist/clis/reddit/hot.js +45 -0
- package/dist/clis/reddit/popular.d.ts +1 -0
- package/dist/clis/reddit/popular.js +41 -0
- package/dist/clis/reddit/search.d.ts +1 -0
- package/dist/clis/reddit/search.js +65 -0
- package/dist/clis/reddit/subreddit.d.ts +1 -0
- package/dist/clis/reddit/subreddit.js +52 -0
- package/dist/clis/reddit/user-comments.d.ts +1 -0
- package/dist/clis/reddit/user-comments.js +44 -0
- package/dist/clis/reddit/user-posts.d.ts +1 -0
- package/dist/clis/reddit/user-posts.js +42 -0
- package/dist/clis/reddit/user.d.ts +1 -0
- package/dist/clis/reddit/user.js +37 -0
- package/dist/clis/stackoverflow/bounties.d.ts +1 -0
- package/dist/clis/stackoverflow/bounties.js +27 -0
- package/dist/clis/stackoverflow/hot.d.ts +1 -0
- package/dist/clis/stackoverflow/hot.js +24 -0
- package/dist/clis/stackoverflow/search.d.ts +1 -0
- package/dist/clis/stackoverflow/search.js +27 -0
- package/dist/clis/stackoverflow/unanswered.d.ts +1 -0
- package/dist/clis/stackoverflow/unanswered.js +26 -0
- package/dist/clis/steam/top-sellers.d.ts +1 -0
- package/dist/clis/steam/top-sellers.js +25 -0
- package/dist/clis/taobao/add-cart.d.ts +1 -0
- package/dist/clis/taobao/add-cart.js +149 -0
- package/dist/clis/taobao/cart.d.ts +1 -0
- package/dist/clis/taobao/cart.js +95 -0
- package/dist/clis/taobao/commands.test.d.ts +5 -0
- package/dist/clis/taobao/commands.test.js +64 -0
- package/dist/clis/taobao/detail.d.ts +1 -0
- package/dist/clis/taobao/detail.js +70 -0
- package/dist/clis/taobao/reviews.d.ts +1 -0
- package/dist/clis/taobao/reviews.js +76 -0
- package/dist/clis/taobao/search.d.ts +1 -0
- package/dist/clis/taobao/search.js +96 -0
- package/dist/clis/tiktok/comment.d.ts +1 -0
- package/dist/clis/tiktok/comment.js +57 -0
- package/dist/clis/tiktok/explore.d.ts +1 -0
- package/dist/clis/tiktok/explore.js +35 -0
- package/dist/clis/tiktok/follow.d.ts +1 -0
- package/dist/clis/tiktok/follow.js +39 -0
- package/dist/clis/tiktok/following.d.ts +1 -0
- package/dist/clis/tiktok/following.js +42 -0
- package/dist/clis/tiktok/friends.d.ts +1 -0
- package/dist/clis/tiktok/friends.js +43 -0
- package/dist/clis/tiktok/like.d.ts +1 -0
- package/dist/clis/tiktok/like.js +33 -0
- package/dist/clis/tiktok/live.d.ts +1 -0
- package/dist/clis/tiktok/live.js +47 -0
- package/dist/clis/tiktok/notifications.d.ts +1 -0
- package/dist/clis/tiktok/notifications.js +49 -0
- package/dist/clis/tiktok/profile.d.ts +1 -0
- package/dist/clis/tiktok/profile.js +54 -0
- package/dist/clis/tiktok/save.d.ts +1 -0
- package/dist/clis/tiktok/save.js +29 -0
- package/dist/clis/tiktok/search.d.ts +1 -0
- package/dist/clis/tiktok/search.js +39 -0
- package/dist/clis/tiktok/unfollow.d.ts +1 -0
- package/dist/clis/tiktok/unfollow.js +44 -0
- package/dist/clis/tiktok/unlike.d.ts +1 -0
- package/dist/clis/tiktok/unlike.js +33 -0
- package/dist/clis/tiktok/unsave.d.ts +1 -0
- package/dist/clis/tiktok/unsave.js +31 -0
- package/dist/clis/tiktok/user.d.ts +1 -0
- package/dist/clis/tiktok/user.js +41 -0
- package/dist/clis/v2ex/hot.d.ts +1 -0
- package/dist/clis/v2ex/hot.js +25 -0
- package/dist/clis/v2ex/latest.d.ts +1 -0
- package/dist/clis/v2ex/latest.js +25 -0
- package/dist/clis/v2ex/member.d.ts +1 -0
- package/dist/clis/v2ex/member.js +27 -0
- package/dist/clis/v2ex/node.d.ts +1 -0
- package/dist/clis/v2ex/node.js +38 -0
- package/dist/clis/v2ex/nodes.d.ts +1 -0
- package/dist/clis/v2ex/nodes.js +25 -0
- package/dist/clis/v2ex/replies.d.ts +1 -0
- package/dist/clis/v2ex/replies.js +26 -0
- package/dist/clis/v2ex/topic.d.ts +1 -0
- package/dist/clis/v2ex/topic.js +30 -0
- package/dist/clis/v2ex/user.d.ts +1 -0
- package/dist/clis/v2ex/user.js +33 -0
- package/dist/clis/xiaoe/catalog.d.ts +1 -0
- package/dist/clis/xiaoe/catalog.js +125 -0
- package/dist/clis/xiaoe/content.d.ts +1 -0
- package/dist/clis/xiaoe/content.js +39 -0
- package/dist/clis/xiaoe/courses.d.ts +1 -0
- package/dist/clis/xiaoe/courses.js +69 -0
- package/dist/clis/xiaoe/detail.d.ts +1 -0
- package/dist/clis/xiaoe/detail.js +35 -0
- package/dist/clis/xiaoe/play-url.d.ts +1 -0
- package/dist/clis/xiaoe/play-url.js +120 -0
- package/dist/clis/xiaohongshu/feed.d.ts +1 -0
- package/dist/clis/xiaohongshu/feed.js +32 -0
- package/dist/clis/xiaohongshu/notifications.d.ts +1 -0
- package/dist/clis/xiaohongshu/notifications.js +38 -0
- package/dist/clis/xueqiu/earnings-date.d.ts +1 -0
- package/dist/clis/xueqiu/earnings-date.js +61 -0
- package/dist/clis/xueqiu/feed.d.ts +1 -0
- package/dist/clis/xueqiu/feed.js +48 -0
- package/dist/clis/xueqiu/groups.d.ts +1 -0
- package/dist/clis/xueqiu/groups.js +25 -0
- package/dist/clis/xueqiu/hot-stock.d.ts +1 -0
- package/dist/clis/xueqiu/hot-stock.js +44 -0
- package/dist/clis/xueqiu/hot.d.ts +1 -0
- package/dist/clis/xueqiu/hot.js +44 -0
- package/dist/clis/xueqiu/kline.d.ts +1 -0
- package/dist/clis/xueqiu/kline.js +64 -0
- package/dist/clis/xueqiu/search.d.ts +1 -0
- package/dist/clis/xueqiu/search.js +49 -0
- package/dist/clis/xueqiu/stock.d.ts +1 -0
- package/dist/clis/xueqiu/stock.js +72 -0
- package/dist/clis/xueqiu/watchlist.d.ts +1 -0
- package/dist/clis/xueqiu/watchlist.js +45 -0
- package/dist/clis/zhihu/hot.d.ts +1 -0
- package/dist/clis/zhihu/hot.js +43 -0
- package/dist/clis/zhihu/search.d.ts +1 -0
- package/dist/clis/zhihu/search.js +52 -0
- package/dist/src/browser/daemon-client.d.ts +14 -3
- package/dist/src/browser/daemon-client.js +30 -14
- package/dist/src/browser/dom-helpers.test.js +3 -2
- package/dist/src/browser/errors.d.ts +26 -1
- package/dist/src/browser/errors.js +40 -7
- package/dist/src/browser/errors.test.d.ts +1 -0
- package/dist/src/browser/errors.test.js +51 -0
- package/dist/src/browser/page.d.ts +9 -8
- package/dist/src/browser/page.js +33 -31
- package/dist/src/browser.test.js +26 -6
- package/dist/src/build-manifest.d.ts +5 -11
- package/dist/src/build-manifest.js +6 -75
- package/dist/src/build-manifest.test.js +1 -39
- package/dist/src/cascade.js +3 -2
- package/dist/src/cli.d.ts +3 -3
- package/dist/src/cli.js +70 -62
- package/dist/src/cli.test.js +20 -15
- package/dist/src/clis/binance/asks.d.ts +1 -0
- package/dist/src/clis/binance/asks.js +20 -0
- package/dist/src/clis/binance/commands.test.d.ts +3 -1
- package/dist/src/clis/binance/commands.test.js +10 -6
- package/dist/src/clis/binance/depth.d.ts +1 -0
- package/dist/src/clis/binance/depth.js +20 -0
- package/dist/src/clis/binance/gainers.d.ts +1 -0
- package/dist/src/clis/binance/gainers.js +21 -0
- package/dist/src/clis/binance/klines.d.ts +1 -0
- package/dist/src/clis/binance/klines.js +20 -0
- package/dist/src/clis/binance/losers.d.ts +1 -0
- package/dist/src/clis/binance/losers.js +21 -0
- package/dist/src/clis/binance/pairs.d.ts +1 -0
- package/dist/src/clis/binance/pairs.js +20 -0
- package/dist/src/clis/binance/price.d.ts +1 -0
- package/dist/src/clis/binance/price.js +17 -0
- package/dist/src/clis/binance/prices.d.ts +1 -0
- package/dist/src/clis/binance/prices.js +18 -0
- package/dist/src/clis/binance/ticker.d.ts +1 -0
- package/dist/src/clis/binance/ticker.js +20 -0
- package/dist/src/clis/binance/top.d.ts +1 -0
- package/dist/src/clis/binance/top.js +20 -0
- package/dist/src/clis/binance/trades.d.ts +1 -0
- package/dist/src/clis/binance/trades.js +19 -0
- package/dist/src/completion-fast.d.ts +25 -0
- package/dist/src/completion-fast.js +140 -0
- package/dist/src/completion.d.ts +1 -0
- package/dist/src/completion.js +1 -0
- package/dist/src/diagnostic.test.js +2 -2
- package/dist/src/discovery.d.ts +3 -3
- package/dist/src/discovery.js +34 -97
- package/dist/src/download/index.d.ts +1 -1
- package/dist/src/engine.test.js +4 -19
- package/dist/src/generate-verified.d.ts +105 -0
- package/dist/src/generate-verified.js +696 -0
- package/dist/src/generate-verified.test.d.ts +1 -0
- package/dist/src/generate-verified.test.js +925 -0
- package/dist/src/generate.d.ts +9 -1
- package/dist/src/generate.js +2 -2
- package/dist/src/main.js +65 -12
- package/dist/src/pipeline/steps/download.d.ts +1 -17
- package/dist/src/pipeline/steps/download.js +20 -31
- package/dist/src/pipeline/steps/intercept.d.ts +1 -1
- package/dist/src/pipeline/steps/intercept.js +1 -1
- package/dist/src/pipeline/steps/tap.d.ts +1 -1
- package/dist/src/pipeline/steps/tap.js +1 -1
- package/dist/src/plugin-scaffold.d.ts +2 -2
- package/dist/src/plugin-scaffold.js +24 -21
- package/dist/src/plugin-scaffold.test.js +1 -1
- package/dist/src/plugin.d.ts +1 -1
- package/dist/src/plugin.js +4 -6
- package/dist/src/plugin.test.js +31 -31
- package/dist/src/record.js +26 -25
- package/dist/src/runtime-detect.js +3 -7
- package/dist/src/scripts/framework.d.ts +3 -0
- package/dist/src/scripts/framework.js +8 -4
- package/dist/src/scripts/store.d.ts +5 -1
- package/dist/src/scripts/store.js +5 -1
- package/dist/src/skill-generate.d.ts +30 -0
- package/dist/src/skill-generate.js +75 -0
- package/dist/src/skill-generate.test.d.ts +1 -0
- package/dist/src/skill-generate.test.js +173 -0
- package/dist/src/synthesize.d.ts +1 -1
- package/dist/src/synthesize.js +7 -8
- package/dist/src/types.d.ts +3 -1
- package/package.json +3 -3
- package/dist/clis/bilibili/hot.yaml +0 -38
- package/dist/clis/bluesky/feeds.yaml +0 -29
- package/dist/clis/bluesky/followers.yaml +0 -33
- package/dist/clis/bluesky/following.yaml +0 -33
- package/dist/clis/bluesky/profile.yaml +0 -27
- package/dist/clis/bluesky/search.yaml +0 -34
- package/dist/clis/bluesky/starter-packs.yaml +0 -34
- package/dist/clis/bluesky/thread.yaml +0 -32
- package/dist/clis/bluesky/trending.yaml +0 -27
- package/dist/clis/bluesky/user.yaml +0 -34
- package/dist/clis/devto/tag.yaml +0 -34
- package/dist/clis/devto/top.yaml +0 -29
- package/dist/clis/devto/user.yaml +0 -33
- package/dist/clis/dictionary/examples.yaml +0 -25
- package/dist/clis/dictionary/search.yaml +0 -27
- package/dist/clis/dictionary/synonyms.yaml +0 -25
- package/dist/clis/douban/subject.yaml +0 -107
- package/dist/clis/douban/top250.yaml +0 -70
- package/dist/clis/facebook/add-friend.yaml +0 -43
- package/dist/clis/facebook/events.yaml +0 -44
- package/dist/clis/facebook/feed.yaml +0 -63
- package/dist/clis/facebook/friends.yaml +0 -42
- package/dist/clis/facebook/groups.yaml +0 -50
- package/dist/clis/facebook/join-group.yaml +0 -44
- package/dist/clis/facebook/memories.yaml +0 -39
- package/dist/clis/facebook/notifications.yaml +0 -40
- package/dist/clis/facebook/profile.yaml +0 -37
- package/dist/clis/facebook/search.yaml +0 -47
- package/dist/clis/hackernews/ask.yaml +0 -38
- package/dist/clis/hackernews/best.yaml +0 -38
- package/dist/clis/hackernews/jobs.yaml +0 -36
- package/dist/clis/hackernews/new.yaml +0 -38
- package/dist/clis/hackernews/search.yaml +0 -44
- package/dist/clis/hackernews/show.yaml +0 -38
- package/dist/clis/hackernews/top.yaml +0 -38
- package/dist/clis/hackernews/user.yaml +0 -25
- package/dist/clis/hupu/hot.yaml +0 -43
- package/dist/clis/instagram/comment.yaml +0 -52
- package/dist/clis/instagram/explore.yaml +0 -43
- package/dist/clis/instagram/follow.yaml +0 -41
- package/dist/clis/instagram/followers.yaml +0 -51
- package/dist/clis/instagram/following.yaml +0 -51
- package/dist/clis/instagram/like.yaml +0 -46
- package/dist/clis/instagram/profile.yaml +0 -42
- package/dist/clis/instagram/save.yaml +0 -46
- package/dist/clis/instagram/saved.yaml +0 -40
- package/dist/clis/instagram/search.yaml +0 -44
- package/dist/clis/instagram/unfollow.yaml +0 -38
- package/dist/clis/instagram/unlike.yaml +0 -46
- package/dist/clis/instagram/unsave.yaml +0 -46
- package/dist/clis/instagram/user.yaml +0 -54
- package/dist/clis/jike/post.yaml +0 -59
- package/dist/clis/jike/topic.yaml +0 -53
- package/dist/clis/jike/user.yaml +0 -52
- package/dist/clis/jimeng/generate.yaml +0 -85
- package/dist/clis/jimeng/history.yaml +0 -46
- package/dist/clis/linux-do/categories.yaml +0 -70
- package/dist/clis/linux-do/search.yaml +0 -48
- package/dist/clis/linux-do/tags.yaml +0 -41
- package/dist/clis/linux-do/topic.yaml +0 -62
- package/dist/clis/linux-do/user-posts.yaml +0 -67
- package/dist/clis/linux-do/user-topics.yaml +0 -54
- package/dist/clis/lobsters/active.yaml +0 -29
- package/dist/clis/lobsters/hot.yaml +0 -29
- package/dist/clis/lobsters/newest.yaml +0 -29
- package/dist/clis/lobsters/tag.yaml +0 -34
- package/dist/clis/pixiv/detail.yaml +0 -49
- package/dist/clis/pixiv/ranking.yaml +0 -53
- package/dist/clis/pixiv/user.yaml +0 -46
- package/dist/clis/reddit/frontpage.yaml +0 -30
- package/dist/clis/reddit/hot.yaml +0 -47
- package/dist/clis/reddit/popular.yaml +0 -40
- package/dist/clis/reddit/search.yaml +0 -61
- package/dist/clis/reddit/subreddit.yaml +0 -50
- package/dist/clis/reddit/user-comments.yaml +0 -46
- package/dist/clis/reddit/user-posts.yaml +0 -44
- package/dist/clis/reddit/user.yaml +0 -40
- package/dist/clis/stackoverflow/bounties.yaml +0 -29
- package/dist/clis/stackoverflow/hot.yaml +0 -28
- package/dist/clis/stackoverflow/search.yaml +0 -33
- package/dist/clis/stackoverflow/unanswered.yaml +0 -28
- package/dist/clis/steam/top-sellers.yaml +0 -29
- package/dist/clis/tiktok/comment.yaml +0 -66
- package/dist/clis/tiktok/explore.yaml +0 -39
- package/dist/clis/tiktok/follow.yaml +0 -39
- package/dist/clis/tiktok/following.yaml +0 -46
- package/dist/clis/tiktok/friends.yaml +0 -47
- package/dist/clis/tiktok/like.yaml +0 -38
- package/dist/clis/tiktok/live.yaml +0 -51
- package/dist/clis/tiktok/notifications.yaml +0 -52
- package/dist/clis/tiktok/profile.yaml +0 -45
- package/dist/clis/tiktok/save.yaml +0 -34
- package/dist/clis/tiktok/search.yaml +0 -47
- package/dist/clis/tiktok/unfollow.yaml +0 -44
- package/dist/clis/tiktok/unlike.yaml +0 -38
- package/dist/clis/tiktok/unsave.yaml +0 -36
- package/dist/clis/tiktok/user.yaml +0 -44
- package/dist/clis/v2ex/hot.yaml +0 -28
- package/dist/clis/v2ex/latest.yaml +0 -28
- package/dist/clis/v2ex/member.yaml +0 -29
- package/dist/clis/v2ex/node.yaml +0 -34
- package/dist/clis/v2ex/nodes.yaml +0 -31
- package/dist/clis/v2ex/replies.yaml +0 -32
- package/dist/clis/v2ex/topic.yaml +0 -33
- package/dist/clis/v2ex/user.yaml +0 -34
- package/dist/clis/xiaoe/catalog.yaml +0 -129
- package/dist/clis/xiaoe/content.yaml +0 -43
- package/dist/clis/xiaoe/courses.yaml +0 -73
- package/dist/clis/xiaoe/detail.yaml +0 -39
- package/dist/clis/xiaoe/play-url.yaml +0 -124
- package/dist/clis/xiaohongshu/feed.yaml +0 -31
- package/dist/clis/xiaohongshu/notifications.yaml +0 -37
- package/dist/clis/xueqiu/earnings-date.yaml +0 -69
- package/dist/clis/xueqiu/feed.yaml +0 -53
- package/dist/clis/xueqiu/groups.yaml +0 -23
- package/dist/clis/xueqiu/hot-stock.yaml +0 -49
- package/dist/clis/xueqiu/hot.yaml +0 -46
- package/dist/clis/xueqiu/kline.yaml +0 -65
- package/dist/clis/xueqiu/search.yaml +0 -55
- package/dist/clis/xueqiu/stock.yaml +0 -69
- package/dist/clis/xueqiu/watchlist.yaml +0 -46
- package/dist/clis/zhihu/hot.yaml +0 -46
- package/dist/clis/zhihu/search.yaml +0 -59
- package/dist/src/yaml-schema.d.ts +0 -29
- package/dist/src/yaml-schema.js +0 -22
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { cli } from '@jackwener/opencli/registry';
|
|
2
|
+
cli({
|
|
3
|
+
site: 'xueqiu',
|
|
4
|
+
name: 'watchlist',
|
|
5
|
+
description: '获取雪球自选股/模拟组合股票列表',
|
|
6
|
+
domain: 'xueqiu.com',
|
|
7
|
+
browser: true,
|
|
8
|
+
args: [
|
|
9
|
+
{
|
|
10
|
+
name: 'pid',
|
|
11
|
+
default: '-1',
|
|
12
|
+
help: '分组ID:-1=全部(默认) -4=模拟 -5=沪深 -6=美股 -7=港股 -10=实盘 0=持仓(通过 xueqiu groups 获取)',
|
|
13
|
+
},
|
|
14
|
+
{ name: 'limit', type: 'int', default: 100, help: '默认 100' },
|
|
15
|
+
],
|
|
16
|
+
columns: ['symbol', 'name', 'price', 'changePercent'],
|
|
17
|
+
pipeline: [
|
|
18
|
+
{ navigate: 'https://xueqiu.com' },
|
|
19
|
+
{ evaluate: `(async () => {
|
|
20
|
+
const pid = \${{ args.pid | json }} || '-1';
|
|
21
|
+
const resp = await fetch(\`https://stock.xueqiu.com/v5/stock/portfolio/stock/list.json?size=100&category=1&pid=\${encodeURIComponent(pid)}\`, {credentials: 'include'});
|
|
22
|
+
if (!resp.ok) throw new Error('HTTP ' + resp.status + ' Hint: Not logged in?');
|
|
23
|
+
const d = await resp.json();
|
|
24
|
+
if (!d.data || !d.data.stocks) throw new Error('获取失败,可能未登录');
|
|
25
|
+
|
|
26
|
+
return d.data.stocks.map(s => ({
|
|
27
|
+
symbol: s.symbol,
|
|
28
|
+
name: s.name,
|
|
29
|
+
price: s.current,
|
|
30
|
+
change: s.chg,
|
|
31
|
+
changePercent: s.percent != null ? s.percent.toFixed(2) + '%' : null,
|
|
32
|
+
volume: s.volume,
|
|
33
|
+
url: 'https://xueqiu.com/S/' + s.symbol
|
|
34
|
+
}));
|
|
35
|
+
})()
|
|
36
|
+
` },
|
|
37
|
+
{ map: {
|
|
38
|
+
symbol: '${{ item.symbol }}',
|
|
39
|
+
name: '${{ item.name }}',
|
|
40
|
+
price: '${{ item.price }}',
|
|
41
|
+
changePercent: '${{ item.changePercent }}',
|
|
42
|
+
} },
|
|
43
|
+
{ limit: '${{ args.limit }}' },
|
|
44
|
+
],
|
|
45
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { cli } from '@jackwener/opencli/registry';
|
|
2
|
+
cli({
|
|
3
|
+
site: 'zhihu',
|
|
4
|
+
name: 'hot',
|
|
5
|
+
description: '知乎热榜',
|
|
6
|
+
domain: 'www.zhihu.com',
|
|
7
|
+
args: [
|
|
8
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Number of items to return' },
|
|
9
|
+
],
|
|
10
|
+
columns: ['rank', 'title', 'heat', 'answers'],
|
|
11
|
+
pipeline: [
|
|
12
|
+
{ navigate: 'https://www.zhihu.com' },
|
|
13
|
+
{ evaluate: `(async () => {
|
|
14
|
+
const res = await fetch('https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=50', {
|
|
15
|
+
credentials: 'include'
|
|
16
|
+
});
|
|
17
|
+
const text = await res.text();
|
|
18
|
+
const d = JSON.parse(
|
|
19
|
+
text.replace(/("id"\\s*:\\s*)(\\d{16,})/g, '$1"$2"')
|
|
20
|
+
);
|
|
21
|
+
return (d?.data || []).map((item) => {
|
|
22
|
+
const t = item.target || {};
|
|
23
|
+
const questionId = t.id == null ? '' : String(t.id);
|
|
24
|
+
return {
|
|
25
|
+
title: t.title,
|
|
26
|
+
url: 'https://www.zhihu.com/question/' + questionId,
|
|
27
|
+
answer_count: t.answer_count,
|
|
28
|
+
follower_count: t.follower_count,
|
|
29
|
+
heat: item.detail_text || '',
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
})()
|
|
33
|
+
` },
|
|
34
|
+
{ map: {
|
|
35
|
+
rank: '${{ index + 1 }}',
|
|
36
|
+
title: '${{ item.title }}',
|
|
37
|
+
heat: '${{ item.heat }}',
|
|
38
|
+
answers: '${{ item.answer_count }}',
|
|
39
|
+
url: '${{ item.url }}',
|
|
40
|
+
} },
|
|
41
|
+
{ limit: '${{ args.limit }}' },
|
|
42
|
+
],
|
|
43
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { cli } from '@jackwener/opencli/registry';
|
|
2
|
+
cli({
|
|
3
|
+
site: 'zhihu',
|
|
4
|
+
name: 'search',
|
|
5
|
+
description: '知乎搜索',
|
|
6
|
+
domain: 'www.zhihu.com',
|
|
7
|
+
args: [
|
|
8
|
+
{ name: 'query', required: true, positional: true, help: 'Search query' },
|
|
9
|
+
{ name: 'limit', type: 'int', default: 10, help: 'Number of results' },
|
|
10
|
+
],
|
|
11
|
+
columns: ['rank', 'title', 'type', 'author', 'votes', 'url'],
|
|
12
|
+
pipeline: [
|
|
13
|
+
{ navigate: 'https://www.zhihu.com' },
|
|
14
|
+
{ evaluate: `(async () => {
|
|
15
|
+
const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/ /g, ' ').replace(/</g, '<').replace(/>/g, '>').replace(/&/g, '&').replace(/<em>/g, '').replace(/<\\/em>/g, '').trim();
|
|
16
|
+
const keyword = \${{ args.query | json }};
|
|
17
|
+
const limit = \${{ args.limit }};
|
|
18
|
+
const res = await fetch('https://www.zhihu.com/api/v4/search_v3?q=' + encodeURIComponent(keyword) + '&t=general&offset=0&limit=' + limit, {
|
|
19
|
+
credentials: 'include'
|
|
20
|
+
});
|
|
21
|
+
const d = await res.json();
|
|
22
|
+
return (d?.data || [])
|
|
23
|
+
.filter(item => item.type === 'search_result')
|
|
24
|
+
.map(item => {
|
|
25
|
+
const obj = item.object || {};
|
|
26
|
+
const q = obj.question || {};
|
|
27
|
+
return {
|
|
28
|
+
type: obj.type,
|
|
29
|
+
title: strip(obj.title || q.name || ''),
|
|
30
|
+
excerpt: strip(obj.excerpt || '').substring(0, 100),
|
|
31
|
+
author: obj.author?.name || '',
|
|
32
|
+
votes: obj.voteup_count || 0,
|
|
33
|
+
url: obj.type === 'answer'
|
|
34
|
+
? 'https://www.zhihu.com/question/' + q.id + '/answer/' + obj.id
|
|
35
|
+
: obj.type === 'article'
|
|
36
|
+
? 'https://zhuanlan.zhihu.com/p/' + obj.id
|
|
37
|
+
: 'https://www.zhihu.com/question/' + obj.id,
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
})()
|
|
41
|
+
` },
|
|
42
|
+
{ map: {
|
|
43
|
+
rank: '${{ index + 1 }}',
|
|
44
|
+
title: '${{ item.title }}',
|
|
45
|
+
type: '${{ item.type }}',
|
|
46
|
+
author: '${{ item.author }}',
|
|
47
|
+
votes: '${{ item.votes }}',
|
|
48
|
+
url: '${{ item.url }}',
|
|
49
|
+
} },
|
|
50
|
+
{ limit: '${{ args.limit }}' },
|
|
51
|
+
],
|
|
52
|
+
});
|
|
@@ -7,6 +7,9 @@ import type { BrowserSessionInfo } from '../types.js';
|
|
|
7
7
|
export interface DaemonCommand {
|
|
8
8
|
id: string;
|
|
9
9
|
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'insert-text' | 'bind-current' | 'network-capture-start' | 'network-capture-read' | 'cdp';
|
|
10
|
+
/** Target page identity (targetId). Cross-layer contract — preferred over tabId. */
|
|
11
|
+
page?: string;
|
|
12
|
+
/** @deprecated Legacy tab ID — use `page` (targetId) instead. */
|
|
10
13
|
tabId?: number;
|
|
11
14
|
code?: string;
|
|
12
15
|
workspace?: string;
|
|
@@ -35,6 +38,8 @@ export interface DaemonResult {
|
|
|
35
38
|
ok: boolean;
|
|
36
39
|
data?: unknown;
|
|
37
40
|
error?: string;
|
|
41
|
+
/** Page identity (targetId) — present on page-scoped command responses */
|
|
42
|
+
page?: string;
|
|
38
43
|
}
|
|
39
44
|
export interface DaemonStatus {
|
|
40
45
|
ok: boolean;
|
|
@@ -71,11 +76,17 @@ export declare function requestDaemonShutdown(opts?: {
|
|
|
71
76
|
timeout?: number;
|
|
72
77
|
}): Promise<boolean>;
|
|
73
78
|
/**
|
|
74
|
-
* Send a command to the daemon and
|
|
75
|
-
* Retries up to 4 times: network errors retry at 500ms,
|
|
76
|
-
* transient extension errors retry at 1500ms.
|
|
79
|
+
* Send a command to the daemon and return the result data.
|
|
77
80
|
*/
|
|
78
81
|
export declare function sendCommand(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<unknown>;
|
|
82
|
+
/**
|
|
83
|
+
* Like sendCommand, but returns both data and page identity (targetId).
|
|
84
|
+
* Use this for page-scoped commands where the caller needs the page identity.
|
|
85
|
+
*/
|
|
86
|
+
export declare function sendCommandFull(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<{
|
|
87
|
+
data: unknown;
|
|
88
|
+
page?: string;
|
|
89
|
+
}>;
|
|
79
90
|
export declare function listSessions(): Promise<BrowserSessionInfo[]>;
|
|
80
91
|
export declare function bindCurrentTab(workspace: string, opts?: {
|
|
81
92
|
matchDomain?: string;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { DEFAULT_DAEMON_PORT } from '../constants.js';
|
|
7
7
|
import { sleep } from '../utils.js';
|
|
8
|
-
import {
|
|
8
|
+
import { classifyBrowserError } from './errors.js';
|
|
9
9
|
const DAEMON_PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
|
|
10
10
|
const DAEMON_URL = `http://127.0.0.1:${DAEMON_PORT}`;
|
|
11
11
|
const OPENCLI_HEADERS = { 'X-OpenCLI': '1' };
|
|
@@ -61,14 +61,17 @@ export async function requestDaemonShutdown(opts) {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
64
|
+
* Internal: send a command to the daemon with retry logic.
|
|
65
|
+
* Returns the raw DaemonResult. All retry policy lives here — callers
|
|
66
|
+
* (sendCommand, sendCommandFull) only shape the return value.
|
|
67
|
+
*
|
|
68
|
+
* Retries up to 4 times:
|
|
69
|
+
* - Network errors (TypeError, AbortError): retry at 500ms
|
|
70
|
+
* - Transient browser errors: retry at the delay suggested by classifyBrowserError()
|
|
67
71
|
*/
|
|
68
|
-
|
|
72
|
+
async function sendCommandRaw(action, params) {
|
|
69
73
|
const maxRetries = 4;
|
|
70
74
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
71
|
-
// Generate a fresh ID per attempt to avoid daemon-side duplicate detection
|
|
72
75
|
const id = generateId();
|
|
73
76
|
const command = { id, action, ...params };
|
|
74
77
|
try {
|
|
@@ -80,29 +83,42 @@ export async function sendCommand(action, params = {}) {
|
|
|
80
83
|
});
|
|
81
84
|
const result = (await res.json());
|
|
82
85
|
if (!result.ok) {
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
await sleep(1500);
|
|
86
|
+
const advice = classifyBrowserError(new Error(result.error ?? ''));
|
|
87
|
+
if (advice.retryable && attempt < maxRetries) {
|
|
88
|
+
await sleep(advice.delayMs);
|
|
87
89
|
continue;
|
|
88
90
|
}
|
|
89
91
|
throw new Error(result.error ?? 'Daemon command failed');
|
|
90
92
|
}
|
|
91
|
-
return result
|
|
93
|
+
return result;
|
|
92
94
|
}
|
|
93
95
|
catch (err) {
|
|
94
|
-
const
|
|
96
|
+
const isNetworkError = err instanceof TypeError
|
|
95
97
|
|| (err instanceof Error && err.name === 'AbortError');
|
|
96
|
-
if (
|
|
98
|
+
if (isNetworkError && attempt < maxRetries) {
|
|
97
99
|
await sleep(500);
|
|
98
100
|
continue;
|
|
99
101
|
}
|
|
100
102
|
throw err;
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
|
-
// Unreachable — the loop always returns or throws
|
|
104
105
|
throw new Error('sendCommand: max retries exhausted');
|
|
105
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Send a command to the daemon and return the result data.
|
|
109
|
+
*/
|
|
110
|
+
export async function sendCommand(action, params = {}) {
|
|
111
|
+
const result = await sendCommandRaw(action, params);
|
|
112
|
+
return result.data;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Like sendCommand, but returns both data and page identity (targetId).
|
|
116
|
+
* Use this for page-scoped commands where the caller needs the page identity.
|
|
117
|
+
*/
|
|
118
|
+
export async function sendCommandFull(action, params = {}) {
|
|
119
|
+
const result = await sendCommandRaw(action, params);
|
|
120
|
+
return { data: result.data, page: result.page };
|
|
121
|
+
}
|
|
106
122
|
export async function listSessions() {
|
|
107
123
|
const result = await sendCommand('sessions');
|
|
108
124
|
return Array.isArray(result) ? result : [];
|
|
@@ -24,11 +24,12 @@ describe('waitForCaptureJs', () => {
|
|
|
24
24
|
});
|
|
25
25
|
it('resolves "captured" when __opencli_xhr is populated before deadline', async () => {
|
|
26
26
|
const g = globalThis;
|
|
27
|
-
|
|
27
|
+
const captured = [];
|
|
28
|
+
g.__opencli_xhr = captured;
|
|
28
29
|
g.window = g; // stub window for Node eval
|
|
29
30
|
const code = waitForCaptureJs(1000);
|
|
30
31
|
const promise = eval(code);
|
|
31
|
-
|
|
32
|
+
captured.push({ data: 'test' });
|
|
32
33
|
await expect(promise).resolves.toBe('captured');
|
|
33
34
|
delete g.__opencli_xhr;
|
|
34
35
|
delete g.window;
|
|
@@ -6,7 +6,32 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { BrowserConnectError, type BrowserConnectKind } from '../errors.js';
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* Unified browser error classification.
|
|
10
|
+
*
|
|
11
|
+
* All transient error detection lives here — daemon-client, pipeline executor,
|
|
12
|
+
* and page retry logic all use this single system instead of maintaining
|
|
13
|
+
* separate pattern lists.
|
|
14
|
+
*/
|
|
15
|
+
/** Error category — determines which layer should retry. */
|
|
16
|
+
export type BrowserErrorKind = 'extension-transient' | 'target-navigation' | 'non-retryable';
|
|
17
|
+
/** How the caller should handle the error. */
|
|
18
|
+
export interface RetryAdvice {
|
|
19
|
+
/** Error category — callers use this to decide whether *they* should retry. */
|
|
20
|
+
kind: BrowserErrorKind;
|
|
21
|
+
/** Whether the error is transient and worth retrying. */
|
|
22
|
+
retryable: boolean;
|
|
23
|
+
/** Suggested delay before retry (ms). */
|
|
24
|
+
delayMs: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Classify a browser error and return retry advice.
|
|
28
|
+
*
|
|
29
|
+
* Single source of truth for "is this error transient?" across all layers.
|
|
30
|
+
*/
|
|
31
|
+
export declare function classifyBrowserError(err: unknown): RetryAdvice;
|
|
32
|
+
/**
|
|
33
|
+
* Check if an error is a transient browser error worth retrying.
|
|
34
|
+
* Convenience wrapper around classifyBrowserError().
|
|
10
35
|
*/
|
|
11
36
|
export declare function isTransientBrowserError(err: unknown): boolean;
|
|
12
37
|
export type ConnectFailureKind = BrowserConnectKind;
|
|
@@ -7,24 +7,57 @@
|
|
|
7
7
|
import { BrowserConnectError } from '../errors.js';
|
|
8
8
|
import { DEFAULT_DAEMON_PORT } from '../constants.js';
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* Extension/daemon transient patterns — service worker restarts, attach races,
|
|
11
|
+
* tab closure, daemon hiccups. These warrant a longer retry delay (~1500ms)
|
|
12
|
+
* because the extension needs time to recover.
|
|
13
13
|
*/
|
|
14
|
-
const
|
|
14
|
+
const EXTENSION_TRANSIENT_PATTERNS = [
|
|
15
15
|
'Extension disconnected',
|
|
16
16
|
'Extension not connected',
|
|
17
17
|
'attach failed',
|
|
18
18
|
'no longer exists',
|
|
19
19
|
'CDP connection',
|
|
20
20
|
'Daemon command failed',
|
|
21
|
+
'No window with id',
|
|
21
22
|
];
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
+
* CDP target navigation patterns — SPA client-side redirects can invalidate the
|
|
25
|
+
* CDP target after chrome.tabs reports 'complete'. These warrant a shorter retry
|
|
26
|
+
* delay (~200ms) because the new document is usually available quickly.
|
|
27
|
+
*/
|
|
28
|
+
const TARGET_NAVIGATION_PATTERNS = [
|
|
29
|
+
'Inspected target navigated or closed',
|
|
30
|
+
];
|
|
31
|
+
function errorMessage(err) {
|
|
32
|
+
return err instanceof Error ? err.message : String(err);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Classify a browser error and return retry advice.
|
|
36
|
+
*
|
|
37
|
+
* Single source of truth for "is this error transient?" across all layers.
|
|
38
|
+
*/
|
|
39
|
+
export function classifyBrowserError(err) {
|
|
40
|
+
const msg = errorMessage(err);
|
|
41
|
+
// Extension/daemon transient errors — longer recovery time
|
|
42
|
+
if (EXTENSION_TRANSIENT_PATTERNS.some(p => msg.includes(p))) {
|
|
43
|
+
return { kind: 'extension-transient', retryable: true, delayMs: 1500 };
|
|
44
|
+
}
|
|
45
|
+
// CDP target navigation errors — shorter recovery time
|
|
46
|
+
if (TARGET_NAVIGATION_PATTERNS.some(p => msg.includes(p))) {
|
|
47
|
+
return { kind: 'target-navigation', retryable: true, delayMs: 200 };
|
|
48
|
+
}
|
|
49
|
+
// CDP protocol error with target context (e.g., -32000 "target closed")
|
|
50
|
+
if (msg.includes('-32000') && msg.toLowerCase().includes('target')) {
|
|
51
|
+
return { kind: 'target-navigation', retryable: true, delayMs: 200 };
|
|
52
|
+
}
|
|
53
|
+
return { kind: 'non-retryable', retryable: false, delayMs: 0 };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Check if an error is a transient browser error worth retrying.
|
|
57
|
+
* Convenience wrapper around classifyBrowserError().
|
|
24
58
|
*/
|
|
25
59
|
export function isTransientBrowserError(err) {
|
|
26
|
-
|
|
27
|
-
return TRANSIENT_ERROR_PATTERNS.some(pattern => msg.includes(pattern));
|
|
60
|
+
return classifyBrowserError(err).retryable;
|
|
28
61
|
}
|
|
29
62
|
export function formatBrowserConnectError(kind, detail) {
|
|
30
63
|
switch (kind) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { classifyBrowserError, isTransientBrowserError } from './errors.js';
|
|
3
|
+
describe('classifyBrowserError', () => {
|
|
4
|
+
it('classifies extension transient errors with 1500ms delay', () => {
|
|
5
|
+
for (const msg of [
|
|
6
|
+
'Extension disconnected',
|
|
7
|
+
'Extension not connected',
|
|
8
|
+
'attach failed',
|
|
9
|
+
'no longer exists',
|
|
10
|
+
'CDP connection reset',
|
|
11
|
+
'Daemon command failed',
|
|
12
|
+
'No window with id: 123',
|
|
13
|
+
]) {
|
|
14
|
+
const advice = classifyBrowserError(new Error(msg));
|
|
15
|
+
expect(advice.kind, `expected "${msg}" → extension-transient`).toBe('extension-transient');
|
|
16
|
+
expect(advice.retryable).toBe(true);
|
|
17
|
+
expect(advice.delayMs).toBe(1500);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
it('classifies CDP target navigation errors with 200ms delay', () => {
|
|
21
|
+
const advice = classifyBrowserError(new Error('Inspected target navigated or closed'));
|
|
22
|
+
expect(advice.kind).toBe('target-navigation');
|
|
23
|
+
expect(advice.retryable).toBe(true);
|
|
24
|
+
expect(advice.delayMs).toBe(200);
|
|
25
|
+
});
|
|
26
|
+
it('classifies CDP -32000 target errors with 200ms delay', () => {
|
|
27
|
+
const advice = classifyBrowserError(new Error('{"code":-32000,"message":"Target closed"}'));
|
|
28
|
+
expect(advice.kind).toBe('target-navigation');
|
|
29
|
+
expect(advice.retryable).toBe(true);
|
|
30
|
+
expect(advice.delayMs).toBe(200);
|
|
31
|
+
});
|
|
32
|
+
it('returns non-retryable for unrelated errors', () => {
|
|
33
|
+
for (const msg of ['Permission denied', 'malformed exec payload', 'SyntaxError']) {
|
|
34
|
+
const advice = classifyBrowserError(new Error(msg));
|
|
35
|
+
expect(advice.kind).toBe('non-retryable');
|
|
36
|
+
expect(advice.retryable).toBe(false);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
it('handles non-Error values', () => {
|
|
40
|
+
expect(classifyBrowserError('Extension disconnected').kind).toBe('extension-transient');
|
|
41
|
+
expect(classifyBrowserError(42).kind).toBe('non-retryable');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
describe('isTransientBrowserError (convenience wrapper)', () => {
|
|
45
|
+
it('returns true for transient errors', () => {
|
|
46
|
+
expect(isTransientBrowserError(new Error('No window with id: 123'))).toBe(true);
|
|
47
|
+
});
|
|
48
|
+
it('returns false for non-transient errors', () => {
|
|
49
|
+
expect(isTransientBrowserError(new Error('Permission denied'))).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -4,30 +4,31 @@
|
|
|
4
4
|
* All browser operations are ultimately 'exec' (JS evaluation via CDP)
|
|
5
5
|
* plus a few native Chrome Extension APIs (tabs, cookies, navigate).
|
|
6
6
|
*
|
|
7
|
-
* IMPORTANT: After goto(), we remember the
|
|
8
|
-
* action and pass it to all subsequent commands. This
|
|
9
|
-
*
|
|
10
|
-
* chrome-extension:// tab that can't be debugged.
|
|
7
|
+
* IMPORTANT: After goto(), we remember the page identity (targetId) returned
|
|
8
|
+
* by the navigate action and pass it to all subsequent commands. This ensures
|
|
9
|
+
* page-scoped operations target the correct page without guessing.
|
|
11
10
|
*/
|
|
12
11
|
import type { BrowserCookie, ScreenshotOptions } from '../types.js';
|
|
13
12
|
import { BasePage } from './base-page.js';
|
|
14
|
-
export declare function isRetryableSettleError(err: unknown): boolean;
|
|
15
13
|
/**
|
|
16
14
|
* Page — implements IPage by talking to the daemon via HTTP.
|
|
17
15
|
*/
|
|
18
16
|
export declare class Page extends BasePage {
|
|
19
17
|
private readonly workspace;
|
|
20
18
|
constructor(workspace?: string);
|
|
21
|
-
/** Active
|
|
22
|
-
private
|
|
19
|
+
/** Active page identity (targetId), set after navigate and used in all subsequent commands */
|
|
20
|
+
private _page;
|
|
23
21
|
/** Helper: spread workspace into command params */
|
|
24
22
|
private _wsOpt;
|
|
25
|
-
/** Helper: spread workspace +
|
|
23
|
+
/** Helper: spread workspace + page identity into command params */
|
|
26
24
|
private _cmdOpts;
|
|
27
25
|
goto(url: string, options?: {
|
|
28
26
|
waitUntil?: 'load' | 'none';
|
|
29
27
|
settleMs?: number;
|
|
30
28
|
}): Promise<void>;
|
|
29
|
+
/** Get the active page identity (targetId) */
|
|
30
|
+
getActivePage(): string | undefined;
|
|
31
|
+
/** @deprecated Use getActivePage() instead */
|
|
31
32
|
getActiveTabId(): number | undefined;
|
|
32
33
|
evaluate(js: string): Promise<unknown>;
|
|
33
34
|
getCookies(opts?: {
|
package/dist/src/browser/page.js
CHANGED
|
@@ -4,22 +4,17 @@
|
|
|
4
4
|
* All browser operations are ultimately 'exec' (JS evaluation via CDP)
|
|
5
5
|
* plus a few native Chrome Extension APIs (tabs, cookies, navigate).
|
|
6
6
|
*
|
|
7
|
-
* IMPORTANT: After goto(), we remember the
|
|
8
|
-
* action and pass it to all subsequent commands. This
|
|
9
|
-
*
|
|
10
|
-
* chrome-extension:// tab that can't be debugged.
|
|
7
|
+
* IMPORTANT: After goto(), we remember the page identity (targetId) returned
|
|
8
|
+
* by the navigate action and pass it to all subsequent commands. This ensures
|
|
9
|
+
* page-scoped operations target the correct page without guessing.
|
|
11
10
|
*/
|
|
12
|
-
import { sendCommand } from './daemon-client.js';
|
|
11
|
+
import { sendCommand, sendCommandFull } from './daemon-client.js';
|
|
13
12
|
import { wrapForEval } from './utils.js';
|
|
14
13
|
import { saveBase64ToFile } from '../utils.js';
|
|
15
14
|
import { generateStealthJs } from './stealth.js';
|
|
16
15
|
import { waitForDomStableJs } from './dom-helpers.js';
|
|
17
16
|
import { BasePage } from './base-page.js';
|
|
18
|
-
|
|
19
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
20
|
-
return message.includes('Inspected target navigated or closed')
|
|
21
|
-
|| (message.includes('-32000') && message.toLowerCase().includes('target'));
|
|
22
|
-
}
|
|
17
|
+
import { classifyBrowserError } from './errors.js';
|
|
23
18
|
/**
|
|
24
19
|
* Page — implements IPage by talking to the daemon via HTTP.
|
|
25
20
|
*/
|
|
@@ -29,27 +24,27 @@ export class Page extends BasePage {
|
|
|
29
24
|
super();
|
|
30
25
|
this.workspace = workspace;
|
|
31
26
|
}
|
|
32
|
-
/** Active
|
|
33
|
-
|
|
27
|
+
/** Active page identity (targetId), set after navigate and used in all subsequent commands */
|
|
28
|
+
_page;
|
|
34
29
|
/** Helper: spread workspace into command params */
|
|
35
30
|
_wsOpt() {
|
|
36
31
|
return { workspace: this.workspace };
|
|
37
32
|
}
|
|
38
|
-
/** Helper: spread workspace +
|
|
33
|
+
/** Helper: spread workspace + page identity into command params */
|
|
39
34
|
_cmdOpts() {
|
|
40
35
|
return {
|
|
41
36
|
workspace: this.workspace,
|
|
42
|
-
...(this.
|
|
37
|
+
...(this._page !== undefined && { page: this._page }),
|
|
43
38
|
};
|
|
44
39
|
}
|
|
45
40
|
async goto(url, options) {
|
|
46
|
-
const result = await
|
|
41
|
+
const result = await sendCommandFull('navigate', {
|
|
47
42
|
url,
|
|
48
43
|
...this._cmdOpts(),
|
|
49
44
|
});
|
|
50
|
-
// Remember the
|
|
51
|
-
if (result
|
|
52
|
-
this.
|
|
45
|
+
// Remember the page identity (targetId) for subsequent calls
|
|
46
|
+
if (result.page) {
|
|
47
|
+
this._page = result.page;
|
|
53
48
|
}
|
|
54
49
|
this._lastUrl = url;
|
|
55
50
|
// Inject stealth + settle in a single round-trip instead of two sequential exec calls.
|
|
@@ -65,17 +60,18 @@ export class Page extends BasePage {
|
|
|
65
60
|
await sendCommand('exec', combinedOpts);
|
|
66
61
|
}
|
|
67
62
|
catch (err) {
|
|
68
|
-
|
|
63
|
+
const advice = classifyBrowserError(err);
|
|
64
|
+
// Only settle-retry on target navigation (SPA client-side redirects).
|
|
65
|
+
// Extension/daemon errors are already retried by sendCommandRaw —
|
|
66
|
+
// retrying them here would silently swallow real failures.
|
|
67
|
+
if (advice.kind !== 'target-navigation')
|
|
69
68
|
throw err;
|
|
70
|
-
// SPA client-side redirects can invalidate the CDP target after
|
|
71
|
-
// chrome.tabs reports 'complete'. Wait briefly for the new document
|
|
72
|
-
// to load, then retry the settle probe once.
|
|
73
69
|
try {
|
|
74
|
-
await new Promise((r) => setTimeout(r,
|
|
70
|
+
await new Promise((r) => setTimeout(r, advice.delayMs));
|
|
75
71
|
await sendCommand('exec', combinedOpts);
|
|
76
72
|
}
|
|
77
73
|
catch (retryErr) {
|
|
78
|
-
if (
|
|
74
|
+
if (classifyBrowserError(retryErr).kind !== 'target-navigation')
|
|
79
75
|
throw retryErr;
|
|
80
76
|
}
|
|
81
77
|
}
|
|
@@ -93,8 +89,13 @@ export class Page extends BasePage {
|
|
|
93
89
|
}
|
|
94
90
|
}
|
|
95
91
|
}
|
|
92
|
+
/** Get the active page identity (targetId) */
|
|
93
|
+
getActivePage() {
|
|
94
|
+
return this._page;
|
|
95
|
+
}
|
|
96
|
+
/** @deprecated Use getActivePage() instead */
|
|
96
97
|
getActiveTabId() {
|
|
97
|
-
return
|
|
98
|
+
return undefined;
|
|
98
99
|
}
|
|
99
100
|
async evaluate(js) {
|
|
100
101
|
const code = wrapForEval(js);
|
|
@@ -102,9 +103,10 @@ export class Page extends BasePage {
|
|
|
102
103
|
return await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
103
104
|
}
|
|
104
105
|
catch (err) {
|
|
105
|
-
|
|
106
|
+
const advice = classifyBrowserError(err);
|
|
107
|
+
if (advice.kind !== 'target-navigation')
|
|
106
108
|
throw err;
|
|
107
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
109
|
+
await new Promise((resolve) => setTimeout(resolve, advice.delayMs));
|
|
108
110
|
return sendCommand('exec', { code, ...this._cmdOpts() });
|
|
109
111
|
}
|
|
110
112
|
}
|
|
@@ -121,7 +123,7 @@ export class Page extends BasePage {
|
|
|
121
123
|
// Window may already be closed or daemon may be down
|
|
122
124
|
}
|
|
123
125
|
finally {
|
|
124
|
-
this.
|
|
126
|
+
this._page = undefined;
|
|
125
127
|
this._lastUrl = null;
|
|
126
128
|
}
|
|
127
129
|
}
|
|
@@ -130,9 +132,9 @@ export class Page extends BasePage {
|
|
|
130
132
|
return Array.isArray(result) ? result : [];
|
|
131
133
|
}
|
|
132
134
|
async selectTab(index) {
|
|
133
|
-
const result = await
|
|
134
|
-
if (result
|
|
135
|
-
this.
|
|
135
|
+
const result = await sendCommandFull('tabs', { op: 'select', index, ...this._wsOpt() });
|
|
136
|
+
if (result.page)
|
|
137
|
+
this._page = result.page;
|
|
136
138
|
}
|
|
137
139
|
/**
|
|
138
140
|
* Capture a screenshot via CDP Page.captureScreenshot.
|