@jackwener/opencli 1.6.9 → 1.7.0
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 +128 -59
- package/README.zh-CN.md +134 -78
- package/dist/clis/_shared/common.d.ts +3 -0
- package/dist/clis/_shared/common.js +22 -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/jianyu/detail.d.ts +1 -0
- package/dist/clis/jianyu/detail.js +20 -0
- package/dist/clis/jianyu/search.d.ts +41 -4
- package/dist/clis/jianyu/search.js +458 -96
- package/dist/clis/jianyu/search.test.js +105 -0
- package/dist/clis/jianyu/shared/china-bid-search.d.ts +12 -0
- package/dist/clis/jianyu/shared/china-bid-search.js +165 -0
- package/dist/clis/jianyu/shared/procurement-contract.d.ts +68 -0
- package/dist/clis/jianyu/shared/procurement-contract.js +324 -0
- package/dist/clis/jianyu/shared/procurement-contract.test.d.ts +1 -0
- package/dist/clis/jianyu/shared/procurement-contract.test.js +72 -0
- package/dist/clis/jianyu/shared/procurement-detail.d.ts +6 -0
- package/dist/clis/jianyu/shared/procurement-detail.js +92 -0
- package/dist/clis/jianyu/shared/procurement-detail.test.d.ts +1 -0
- package/dist/clis/jianyu/shared/procurement-detail.test.js +72 -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 +161 -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/bridge.js +1 -1
- package/dist/src/browser/daemon-client.d.ts +16 -4
- package/dist/src/browser/daemon-client.js +33 -15
- package/dist/src/browser/daemon-client.test.js +0 -3
- 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 +25 -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 +71 -71
- 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/commands/daemon.d.ts +2 -6
- package/dist/src/commands/daemon.js +2 -58
- package/dist/src/commands/daemon.test.js +24 -120
- 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/constants.d.ts +0 -2
- package/dist/src/constants.js +0 -2
- package/dist/src/daemon.d.ts +1 -1
- package/dist/src/daemon.js +2 -15
- 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/execution.js +5 -1
- 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 +4 -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/daemon.test.js +0 -65
- package/dist/src/idle-manager.d.ts +0 -19
- package/dist/src/idle-manager.js +0 -54
- package/dist/src/yaml-schema.d.ts +0 -29
- package/dist/src/yaml-schema.js +0 -22
- /package/dist/{src/daemon.test.d.ts → clis/bilibili/hot.d.ts} +0 -0
|
@@ -45,7 +45,7 @@ export class BrowserBridge {
|
|
|
45
45
|
if (this._state === 'closed')
|
|
46
46
|
return;
|
|
47
47
|
this._state = 'closing';
|
|
48
|
-
// We don't kill the daemon — it
|
|
48
|
+
// We don't kill the daemon — it's persistent.
|
|
49
49
|
// Just clean up our reference.
|
|
50
50
|
this._page = null;
|
|
51
51
|
this._state = 'closed';
|
|
@@ -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;
|
|
@@ -29,12 +32,16 @@ export interface DaemonCommand {
|
|
|
29
32
|
pattern?: string;
|
|
30
33
|
cdpMethod?: string;
|
|
31
34
|
cdpParams?: Record<string, unknown>;
|
|
35
|
+
/** When true, automation windows are created in the foreground */
|
|
36
|
+
windowFocused?: boolean;
|
|
32
37
|
}
|
|
33
38
|
export interface DaemonResult {
|
|
34
39
|
id: string;
|
|
35
40
|
ok: boolean;
|
|
36
41
|
data?: unknown;
|
|
37
42
|
error?: string;
|
|
43
|
+
/** Page identity (targetId) — present on page-scoped command responses */
|
|
44
|
+
page?: string;
|
|
38
45
|
}
|
|
39
46
|
export interface DaemonStatus {
|
|
40
47
|
ok: boolean;
|
|
@@ -43,7 +50,6 @@ export interface DaemonStatus {
|
|
|
43
50
|
extensionConnected: boolean;
|
|
44
51
|
extensionVersion?: string;
|
|
45
52
|
pending: number;
|
|
46
|
-
lastCliRequestTime: number;
|
|
47
53
|
memoryMB: number;
|
|
48
54
|
port: number;
|
|
49
55
|
}
|
|
@@ -71,11 +77,17 @@ export declare function requestDaemonShutdown(opts?: {
|
|
|
71
77
|
timeout?: number;
|
|
72
78
|
}): Promise<boolean>;
|
|
73
79
|
/**
|
|
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.
|
|
80
|
+
* Send a command to the daemon and return the result data.
|
|
77
81
|
*/
|
|
78
82
|
export declare function sendCommand(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<unknown>;
|
|
83
|
+
/**
|
|
84
|
+
* Like sendCommand, but returns both data and page identity (targetId).
|
|
85
|
+
* Use this for page-scoped commands where the caller needs the page identity.
|
|
86
|
+
*/
|
|
87
|
+
export declare function sendCommandFull(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<{
|
|
88
|
+
data: unknown;
|
|
89
|
+
page?: string;
|
|
90
|
+
}>;
|
|
79
91
|
export declare function listSessions(): Promise<BrowserSessionInfo[]>;
|
|
80
92
|
export declare function bindCurrentTab(workspace: string, opts?: {
|
|
81
93
|
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,16 +61,21 @@ 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
|
-
const
|
|
76
|
+
const wf = process.env.OPENCLI_WINDOW_FOCUSED;
|
|
77
|
+
const windowFocused = (wf === '1' || wf === 'true') ? true : undefined;
|
|
78
|
+
const command = { id, action, ...params, ...(windowFocused && { windowFocused }) };
|
|
74
79
|
try {
|
|
75
80
|
const res = await requestDaemon('/command', {
|
|
76
81
|
method: 'POST',
|
|
@@ -80,29 +85,42 @@ export async function sendCommand(action, params = {}) {
|
|
|
80
85
|
});
|
|
81
86
|
const result = (await res.json());
|
|
82
87
|
if (!result.ok) {
|
|
83
|
-
|
|
84
|
-
if (
|
|
85
|
-
|
|
86
|
-
await sleep(1500);
|
|
88
|
+
const advice = classifyBrowserError(new Error(result.error ?? ''));
|
|
89
|
+
if (advice.retryable && attempt < maxRetries) {
|
|
90
|
+
await sleep(advice.delayMs);
|
|
87
91
|
continue;
|
|
88
92
|
}
|
|
89
93
|
throw new Error(result.error ?? 'Daemon command failed');
|
|
90
94
|
}
|
|
91
|
-
return result
|
|
95
|
+
return result;
|
|
92
96
|
}
|
|
93
97
|
catch (err) {
|
|
94
|
-
const
|
|
98
|
+
const isNetworkError = err instanceof TypeError
|
|
95
99
|
|| (err instanceof Error && err.name === 'AbortError');
|
|
96
|
-
if (
|
|
100
|
+
if (isNetworkError && attempt < maxRetries) {
|
|
97
101
|
await sleep(500);
|
|
98
102
|
continue;
|
|
99
103
|
}
|
|
100
104
|
throw err;
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
|
-
// Unreachable — the loop always returns or throws
|
|
104
107
|
throw new Error('sendCommand: max retries exhausted');
|
|
105
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Send a command to the daemon and return the result data.
|
|
111
|
+
*/
|
|
112
|
+
export async function sendCommand(action, params = {}) {
|
|
113
|
+
const result = await sendCommandRaw(action, params);
|
|
114
|
+
return result.data;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Like sendCommand, but returns both data and page identity (targetId).
|
|
118
|
+
* Use this for page-scoped commands where the caller needs the page identity.
|
|
119
|
+
*/
|
|
120
|
+
export async function sendCommandFull(action, params = {}) {
|
|
121
|
+
const result = await sendCommandRaw(action, params);
|
|
122
|
+
return { data: result.data, page: result.page };
|
|
123
|
+
}
|
|
106
124
|
export async function listSessions() {
|
|
107
125
|
const result = await sendCommand('sessions');
|
|
108
126
|
return Array.isArray(result) ? result : [];
|
|
@@ -15,7 +15,6 @@ describe('daemon-client', () => {
|
|
|
15
15
|
extensionConnected: true,
|
|
16
16
|
extensionVersion: '1.2.3',
|
|
17
17
|
pending: 0,
|
|
18
|
-
lastCliRequestTime: Date.now(),
|
|
19
18
|
memoryMB: 32,
|
|
20
19
|
port: 19825,
|
|
21
20
|
};
|
|
@@ -53,7 +52,6 @@ describe('daemon-client', () => {
|
|
|
53
52
|
uptime: 10,
|
|
54
53
|
extensionConnected: false,
|
|
55
54
|
pending: 0,
|
|
56
|
-
lastCliRequestTime: Date.now(),
|
|
57
55
|
memoryMB: 16,
|
|
58
56
|
port: 19825,
|
|
59
57
|
};
|
|
@@ -71,7 +69,6 @@ describe('daemon-client', () => {
|
|
|
71
69
|
extensionConnected: true,
|
|
72
70
|
extensionVersion: '1.2.3',
|
|
73
71
|
pending: 0,
|
|
74
|
-
lastCliRequestTime: Date.now(),
|
|
75
72
|
memoryMB: 32,
|
|
76
73
|
port: 19825,
|
|
77
74
|
};
|
|
@@ -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.
|
package/dist/src/browser.test.js
CHANGED
|
@@ -3,7 +3,7 @@ import { BrowserBridge, generateStealthJs } from './browser/index.js';
|
|
|
3
3
|
import { extractTabEntries, diffTabIndexes, appendLimited } from './browser/tabs.js';
|
|
4
4
|
import { withTimeoutMs } from './runtime.js';
|
|
5
5
|
import { __test__ as cdpTest } from './browser/cdp.js';
|
|
6
|
-
import {
|
|
6
|
+
import { classifyBrowserError } from './browser/errors.js';
|
|
7
7
|
import * as daemonClient from './browser/daemon-client.js';
|
|
8
8
|
describe('browser helpers', () => {
|
|
9
9
|
it('extracts tab entries from string snapshots', () => {
|
|
@@ -35,10 +35,18 @@ describe('browser helpers', () => {
|
|
|
35
35
|
it('times out slow promises', async () => {
|
|
36
36
|
await expect(withTimeoutMs(new Promise(() => { }), 10, 'timeout')).rejects.toThrow('timeout');
|
|
37
37
|
});
|
|
38
|
-
it('
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
expect(
|
|
38
|
+
it('classifies browser errors with correct kind and retry advice', () => {
|
|
39
|
+
// CDP target navigation — page-level settle retry
|
|
40
|
+
const nav = classifyBrowserError(new Error('{"code":-32000,"message":"Inspected target navigated or closed"}'));
|
|
41
|
+
expect(nav.kind).toBe('target-navigation');
|
|
42
|
+
expect(nav.delayMs).toBe(200);
|
|
43
|
+
// Extension transient — daemon-client retry only, NOT page-level
|
|
44
|
+
const ext = classifyBrowserError(new Error('Extension disconnected'));
|
|
45
|
+
expect(ext.kind).toBe('extension-transient');
|
|
46
|
+
expect(ext.delayMs).toBe(1500);
|
|
47
|
+
// Non-transient errors — not retryable
|
|
48
|
+
expect(classifyBrowserError(new Error('malformed exec payload')).kind).toBe('non-retryable');
|
|
49
|
+
expect(classifyBrowserError(new Error('Permission denied')).kind).toBe('non-retryable');
|
|
42
50
|
});
|
|
43
51
|
it('prefers the real Electron app target over DevTools and blank pages', () => {
|
|
44
52
|
const target = cdpTest.selectCDPTarget([
|
|
@@ -105,7 +113,18 @@ describe('BrowserBridge state', () => {
|
|
|
105
113
|
await expect(bridge.connect()).rejects.toThrow('Session is closing');
|
|
106
114
|
});
|
|
107
115
|
it('fails fast when daemon is running but extension is disconnected', async () => {
|
|
108
|
-
vi.spyOn(daemonClient, 'getDaemonHealth').mockResolvedValue({
|
|
116
|
+
vi.spyOn(daemonClient, 'getDaemonHealth').mockResolvedValue({
|
|
117
|
+
state: 'no-extension',
|
|
118
|
+
status: {
|
|
119
|
+
ok: true,
|
|
120
|
+
pid: 1,
|
|
121
|
+
uptime: 0,
|
|
122
|
+
extensionConnected: false,
|
|
123
|
+
pending: 0,
|
|
124
|
+
memoryMB: 0,
|
|
125
|
+
port: 0,
|
|
126
|
+
},
|
|
127
|
+
});
|
|
109
128
|
const bridge = new BrowserBridge();
|
|
110
129
|
await expect(bridge.connect({ timeout: 0.1 })).rejects.toThrow('Browser Bridge extension not connected');
|
|
111
130
|
});
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Build-time CLI manifest compiler.
|
|
4
4
|
*
|
|
5
|
-
* Scans all
|
|
6
|
-
* manifest.json for instant cold-start registration
|
|
5
|
+
* Scans all TS CLI definitions and pre-compiles them into a single
|
|
6
|
+
* manifest.json for instant cold-start registration.
|
|
7
7
|
*
|
|
8
8
|
* Usage: npx tsx src/build-manifest.ts
|
|
9
9
|
* Output: cli-manifest.json at the package root
|
|
@@ -31,19 +31,13 @@ export interface ManifestEntry {
|
|
|
31
31
|
timeout?: number;
|
|
32
32
|
deprecated?: boolean | string;
|
|
33
33
|
replacedBy?: string;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
/** Relative path from clis/ dir, e.g. 'bilibili/hot.yaml' or 'bilibili/search.js' */
|
|
34
|
+
type: 'ts';
|
|
35
|
+
/** Relative path from clis/ dir, e.g. 'bilibili/search.js' */
|
|
37
36
|
modulePath?: string;
|
|
38
|
-
/** Relative path to the original source file from clis/ dir (
|
|
37
|
+
/** Relative path to the original source file from clis/ dir (e.g. 'site/cmd.ts') */
|
|
39
38
|
sourceFile?: string;
|
|
40
39
|
/** Pre-navigation control — see CliCommand.navigateBefore */
|
|
41
40
|
navigateBefore?: boolean | string;
|
|
42
41
|
}
|
|
43
42
|
export declare function loadTsManifestEntries(filePath: string, site: string, importer?: (moduleHref: string) => Promise<unknown>): Promise<ManifestEntry[]>;
|
|
44
|
-
/**
|
|
45
|
-
* When both YAML and TS adapters exist for the same site/name,
|
|
46
|
-
* prefer the TS version (it self-registers and typically has richer logic).
|
|
47
|
-
*/
|
|
48
|
-
export declare function shouldReplaceManifestEntry(current: ManifestEntry, next: ManifestEntry): boolean;
|
|
49
43
|
export declare function buildManifest(): Promise<ManifestEntry[]>;
|