@jackwener/opencli 1.6.8 → 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 +117 -59
- package/README.zh-CN.md +123 -79
- 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/jianyu/search.d.ts +14 -0
- package/dist/clis/jianyu/search.js +135 -0
- package/dist/clis/jianyu/search.test.d.ts +1 -0
- package/dist/clis/jianyu/search.test.js +23 -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/quark/ls.d.ts +1 -0
- package/dist/clis/quark/ls.js +63 -0
- package/dist/clis/quark/mkdir.d.ts +1 -0
- package/dist/clis/quark/mkdir.js +36 -0
- package/dist/clis/quark/mv.d.ts +1 -0
- package/dist/clis/quark/mv.js +53 -0
- package/dist/clis/quark/rename.d.ts +1 -0
- package/dist/clis/quark/rename.js +26 -0
- package/dist/clis/quark/rm.d.ts +1 -0
- package/dist/clis/quark/rm.js +24 -0
- package/dist/clis/quark/save.d.ts +1 -0
- package/dist/clis/quark/save.js +80 -0
- package/dist/clis/quark/share-tree.d.ts +1 -0
- package/dist/clis/quark/share-tree.js +45 -0
- package/dist/clis/quark/utils.d.ts +50 -0
- package/dist/clis/quark/utils.js +146 -0
- package/dist/clis/quark/utils.test.d.ts +1 -0
- package/dist/clis/quark/utils.test.js +58 -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/twitter/reply.js +3 -8
- package/dist/clis/twitter/reply.test.js +5 -5
- 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/note.js +8 -3
- package/dist/clis/xiaohongshu/note.test.js +11 -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/answer.d.ts +1 -0
- package/dist/clis/zhihu/answer.js +194 -0
- package/dist/clis/zhihu/answer.test.d.ts +1 -0
- package/dist/clis/zhihu/answer.test.js +81 -0
- package/dist/clis/zhihu/comment.d.ts +1 -0
- package/dist/clis/zhihu/comment.js +335 -0
- package/dist/clis/zhihu/comment.test.d.ts +1 -0
- package/dist/clis/zhihu/comment.test.js +54 -0
- package/dist/clis/zhihu/favorite.d.ts +1 -0
- package/dist/clis/zhihu/favorite.js +224 -0
- package/dist/clis/zhihu/favorite.test.d.ts +1 -0
- package/dist/clis/zhihu/favorite.test.js +196 -0
- package/dist/clis/zhihu/follow.d.ts +1 -0
- package/dist/clis/zhihu/follow.js +80 -0
- package/dist/clis/zhihu/follow.test.d.ts +1 -0
- package/dist/clis/zhihu/follow.test.js +45 -0
- package/dist/clis/zhihu/hot.d.ts +1 -0
- package/dist/clis/zhihu/hot.js +43 -0
- package/dist/clis/zhihu/like.d.ts +1 -0
- package/dist/clis/zhihu/like.js +91 -0
- package/dist/clis/zhihu/like.test.d.ts +1 -0
- package/dist/clis/zhihu/like.test.js +64 -0
- package/dist/clis/zhihu/search.d.ts +1 -0
- package/dist/clis/zhihu/search.js +52 -0
- package/dist/clis/zhihu/target.d.ts +24 -0
- package/dist/clis/zhihu/target.js +91 -0
- package/dist/clis/zhihu/target.test.d.ts +1 -0
- package/dist/clis/zhihu/target.test.js +77 -0
- package/dist/clis/zhihu/write-shared.d.ts +32 -0
- package/dist/clis/zhihu/write-shared.js +221 -0
- package/dist/clis/zhihu/write-shared.test.d.ts +1 -0
- package/dist/clis/zhihu/write-shared.test.js +175 -0
- package/dist/src/browser/bridge.d.ts +2 -0
- package/dist/src/browser/bridge.js +30 -24
- package/dist/src/browser/daemon-client.d.ts +30 -10
- package/dist/src/browser/daemon-client.js +42 -27
- package/dist/src/browser/daemon-client.test.js +32 -25
- 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/index.d.ts +2 -1
- package/dist/src/browser/index.js +1 -1
- package/dist/src/browser/page.d.ts +9 -8
- package/dist/src/browser/page.js +33 -31
- package/dist/src/browser.test.js +27 -8
- 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 +73 -65
- 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 -0
- package/dist/src/clis/binance/commands.test.js +58 -0
- 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/commanderAdapter.js +19 -6
- 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.d.ts +1 -0
- package/dist/src/diagnostic.js +64 -2
- package/dist/src/diagnostic.test.js +93 -3
- package/dist/src/discovery.d.ts +3 -3
- package/dist/src/discovery.js +34 -97
- package/dist/src/doctor.d.ts +2 -0
- package/dist/src/doctor.js +59 -31
- package/dist/src/doctor.test.js +89 -16
- package/dist/src/download/index.d.ts +1 -1
- package/dist/src/engine.test.js +4 -19
- package/dist/src/execution.js +1 -13
- package/dist/src/explore.js +1 -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 +11 -6
- package/dist/src/generate.js +4 -7
- 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 +3 -2
- package/dist/src/plugin.js +29 -14
- package/dist/src/plugin.test.js +47 -32
- 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 +5 -5
- 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/browser/discover.d.ts +0 -15
- package/dist/src/browser/discover.js +0 -19
- package/dist/src/yaml-schema.d.ts +0 -29
- package/dist/src/yaml-schema.js +0 -22
|
@@ -6,8 +6,9 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import * as fs from 'node:fs';
|
|
8
8
|
import { Page } from './page.js';
|
|
9
|
-
import {
|
|
9
|
+
import { getDaemonHealth } from './daemon-client.js';
|
|
10
10
|
import { DEFAULT_DAEMON_PORT } from '../constants.js';
|
|
11
|
+
import { BrowserConnectError } from '../errors.js';
|
|
11
12
|
const DAEMON_SPAWN_TIMEOUT = 10000; // 10s to wait for daemon + extension
|
|
12
13
|
/**
|
|
13
14
|
* Browser factory: manages daemon lifecycle and provides IPage instances.
|
|
@@ -52,25 +53,22 @@ export class BrowserBridge {
|
|
|
52
53
|
async _ensureDaemon(timeoutSeconds) {
|
|
53
54
|
const effectiveSeconds = (timeoutSeconds && timeoutSeconds > 0) ? timeoutSeconds : Math.ceil(DAEMON_SPAWN_TIMEOUT / 1000);
|
|
54
55
|
const timeoutMs = effectiveSeconds * 1000;
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
if (status?.extensionConnected)
|
|
56
|
+
const health = await getDaemonHealth();
|
|
57
|
+
// Fast path: everything ready
|
|
58
|
+
if (health.state === 'ready')
|
|
59
59
|
return;
|
|
60
60
|
// Daemon running but no extension — wait for extension with progress
|
|
61
|
-
if (
|
|
61
|
+
if (health.state === 'no-extension') {
|
|
62
62
|
if (process.env.OPENCLI_VERBOSE || process.stderr.isTTY) {
|
|
63
63
|
process.stderr.write('⏳ Waiting for Chrome/Chromium extension to connect...\n');
|
|
64
64
|
process.stderr.write(' Make sure Chrome or Chromium is open and the OpenCLI extension is enabled.\n');
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
throw new Error('Daemon is running but the Browser Extension is not connected.\n' +
|
|
73
|
-
'Please install and enable the opencli Browser Bridge extension in Chrome or Chromium.');
|
|
66
|
+
if (await this._pollUntilReady(timeoutMs))
|
|
67
|
+
return;
|
|
68
|
+
throw new BrowserConnectError('Browser Bridge extension not connected', 'Install the Browser Bridge:\n' +
|
|
69
|
+
' 1. Download: https://github.com/jackwener/opencli/releases\n' +
|
|
70
|
+
' 2. In Chrome or Chromium, open chrome://extensions → Developer Mode → Load unpacked\n' +
|
|
71
|
+
' Then run: opencli doctor', 'extension-not-connected');
|
|
74
72
|
}
|
|
75
73
|
// No daemon — spawn one
|
|
76
74
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -91,19 +89,27 @@ export class BrowserBridge {
|
|
|
91
89
|
env: { ...process.env },
|
|
92
90
|
});
|
|
93
91
|
this._daemonProc.unref();
|
|
94
|
-
// Wait for daemon + extension
|
|
92
|
+
// Wait for daemon + extension
|
|
93
|
+
if (await this._pollUntilReady(timeoutMs))
|
|
94
|
+
return;
|
|
95
|
+
const finalHealth = await getDaemonHealth();
|
|
96
|
+
if (finalHealth.state === 'no-extension') {
|
|
97
|
+
throw new BrowserConnectError('Browser Bridge extension not connected', 'Install the Browser Bridge:\n' +
|
|
98
|
+
' 1. Download: https://github.com/jackwener/opencli/releases\n' +
|
|
99
|
+
' 2. In Chrome or Chromium, open chrome://extensions → Developer Mode → Load unpacked\n' +
|
|
100
|
+
' Then run: opencli doctor', 'extension-not-connected');
|
|
101
|
+
}
|
|
102
|
+
throw new BrowserConnectError('Failed to start opencli daemon', `Try running manually:\n node ${daemonPath}\nMake sure port ${DEFAULT_DAEMON_PORT} is available.`, 'daemon-not-running');
|
|
103
|
+
}
|
|
104
|
+
/** Poll getDaemonHealth() until state is 'ready' or deadline is reached. */
|
|
105
|
+
async _pollUntilReady(timeoutMs) {
|
|
95
106
|
const deadline = Date.now() + timeoutMs;
|
|
96
107
|
while (Date.now() < deadline) {
|
|
97
108
|
await new Promise(resolve => setTimeout(resolve, 200));
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if ((await fetchDaemonStatus()) !== null) {
|
|
102
|
-
throw new Error('Daemon is running but the Browser Extension is not connected.\n' +
|
|
103
|
-
'Please install and enable the opencli Browser Bridge extension in Chrome or Chromium.');
|
|
109
|
+
const h = await getDaemonHealth();
|
|
110
|
+
if (h.state === 'ready')
|
|
111
|
+
return true;
|
|
104
112
|
}
|
|
105
|
-
|
|
106
|
-
` node ${daemonPath}\n` +
|
|
107
|
-
`Make sure port ${DEFAULT_DAEMON_PORT} is available.`);
|
|
113
|
+
return false;
|
|
108
114
|
}
|
|
109
115
|
}
|
|
@@ -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;
|
|
@@ -50,23 +55,38 @@ export interface DaemonStatus {
|
|
|
50
55
|
export declare function fetchDaemonStatus(opts?: {
|
|
51
56
|
timeout?: number;
|
|
52
57
|
}): Promise<DaemonStatus | null>;
|
|
58
|
+
export type DaemonHealth = {
|
|
59
|
+
state: 'stopped';
|
|
60
|
+
status: null;
|
|
61
|
+
} | {
|
|
62
|
+
state: 'no-extension';
|
|
63
|
+
status: DaemonStatus;
|
|
64
|
+
} | {
|
|
65
|
+
state: 'ready';
|
|
66
|
+
status: DaemonStatus;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* Unified daemon health check — single entry point for all status queries.
|
|
70
|
+
* Replaces isDaemonRunning(), isExtensionConnected(), and checkDaemonStatus().
|
|
71
|
+
*/
|
|
72
|
+
export declare function getDaemonHealth(opts?: {
|
|
73
|
+
timeout?: number;
|
|
74
|
+
}): Promise<DaemonHealth>;
|
|
53
75
|
export declare function requestDaemonShutdown(opts?: {
|
|
54
76
|
timeout?: number;
|
|
55
77
|
}): Promise<boolean>;
|
|
56
78
|
/**
|
|
57
|
-
*
|
|
79
|
+
* Send a command to the daemon and return the result data.
|
|
58
80
|
*/
|
|
59
|
-
export declare function
|
|
60
|
-
/**
|
|
61
|
-
* Check if daemon is running AND the extension is connected.
|
|
62
|
-
*/
|
|
63
|
-
export declare function isExtensionConnected(): Promise<boolean>;
|
|
81
|
+
export declare function sendCommand(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<unknown>;
|
|
64
82
|
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* transient extension errors retry at 1500ms.
|
|
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.
|
|
68
85
|
*/
|
|
69
|
-
export declare function
|
|
86
|
+
export declare function sendCommandFull(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<{
|
|
87
|
+
data: unknown;
|
|
88
|
+
page?: string;
|
|
89
|
+
}>;
|
|
70
90
|
export declare function listSessions(): Promise<BrowserSessionInfo[]>;
|
|
71
91
|
export declare function bindCurrentTab(workspace: string, opts?: {
|
|
72
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' };
|
|
@@ -39,6 +39,18 @@ export async function fetchDaemonStatus(opts) {
|
|
|
39
39
|
return null;
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Unified daemon health check — single entry point for all status queries.
|
|
44
|
+
* Replaces isDaemonRunning(), isExtensionConnected(), and checkDaemonStatus().
|
|
45
|
+
*/
|
|
46
|
+
export async function getDaemonHealth(opts) {
|
|
47
|
+
const status = await fetchDaemonStatus(opts);
|
|
48
|
+
if (!status)
|
|
49
|
+
return { state: 'stopped', status: null };
|
|
50
|
+
if (!status.extensionConnected)
|
|
51
|
+
return { state: 'no-extension', status };
|
|
52
|
+
return { state: 'ready', status };
|
|
53
|
+
}
|
|
42
54
|
export async function requestDaemonShutdown(opts) {
|
|
43
55
|
try {
|
|
44
56
|
const res = await requestDaemon('/shutdown', { method: 'POST', timeout: opts?.timeout ?? 5000 });
|
|
@@ -49,27 +61,17 @@ export async function requestDaemonShutdown(opts) {
|
|
|
49
61
|
}
|
|
50
62
|
}
|
|
51
63
|
/**
|
|
52
|
-
*
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
*
|
|
59
|
-
*/
|
|
60
|
-
export async function isExtensionConnected() {
|
|
61
|
-
const status = await fetchDaemonStatus();
|
|
62
|
-
return !!status?.extensionConnected;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Send a command to the daemon and wait for a result.
|
|
66
|
-
* Retries up to 4 times: network errors retry at 500ms,
|
|
67
|
-
* transient extension errors retry at 1500ms.
|
|
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()
|
|
68
71
|
*/
|
|
69
|
-
|
|
72
|
+
async function sendCommandRaw(action, params) {
|
|
70
73
|
const maxRetries = 4;
|
|
71
74
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
72
|
-
// Generate a fresh ID per attempt to avoid daemon-side duplicate detection
|
|
73
75
|
const id = generateId();
|
|
74
76
|
const command = { id, action, ...params };
|
|
75
77
|
try {
|
|
@@ -81,29 +83,42 @@ export async function sendCommand(action, params = {}) {
|
|
|
81
83
|
});
|
|
82
84
|
const result = (await res.json());
|
|
83
85
|
if (!result.ok) {
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
await sleep(1500);
|
|
86
|
+
const advice = classifyBrowserError(new Error(result.error ?? ''));
|
|
87
|
+
if (advice.retryable && attempt < maxRetries) {
|
|
88
|
+
await sleep(advice.delayMs);
|
|
88
89
|
continue;
|
|
89
90
|
}
|
|
90
91
|
throw new Error(result.error ?? 'Daemon command failed');
|
|
91
92
|
}
|
|
92
|
-
return result
|
|
93
|
+
return result;
|
|
93
94
|
}
|
|
94
95
|
catch (err) {
|
|
95
|
-
const
|
|
96
|
+
const isNetworkError = err instanceof TypeError
|
|
96
97
|
|| (err instanceof Error && err.name === 'AbortError');
|
|
97
|
-
if (
|
|
98
|
+
if (isNetworkError && attempt < maxRetries) {
|
|
98
99
|
await sleep(500);
|
|
99
100
|
continue;
|
|
100
101
|
}
|
|
101
102
|
throw err;
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
|
-
// Unreachable — the loop always returns or throws
|
|
105
105
|
throw new Error('sendCommand: max retries exhausted');
|
|
106
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
|
+
}
|
|
107
122
|
export async function listSessions() {
|
|
108
123
|
const result = await sendCommand('sessions');
|
|
109
124
|
return Array.isArray(result) ? result : [];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { fetchDaemonStatus,
|
|
2
|
+
import { fetchDaemonStatus, getDaemonHealth, requestDaemonShutdown, } from './daemon-client.js';
|
|
3
3
|
describe('daemon-client', () => {
|
|
4
4
|
beforeEach(() => {
|
|
5
5
|
vi.stubGlobal('fetch', vi.fn());
|
|
@@ -42,36 +42,43 @@ describe('daemon-client', () => {
|
|
|
42
42
|
headers: expect.objectContaining({ 'X-OpenCLI': '1' }),
|
|
43
43
|
}));
|
|
44
44
|
});
|
|
45
|
-
it('
|
|
45
|
+
it('getDaemonHealth returns stopped when daemon is not reachable', async () => {
|
|
46
|
+
vi.mocked(fetch).mockRejectedValue(new Error('ECONNREFUSED'));
|
|
47
|
+
await expect(getDaemonHealth()).resolves.toEqual({ state: 'stopped', status: null });
|
|
48
|
+
});
|
|
49
|
+
it('getDaemonHealth returns no-extension when daemon is running but extension disconnected', async () => {
|
|
50
|
+
const status = {
|
|
51
|
+
ok: true,
|
|
52
|
+
pid: 123,
|
|
53
|
+
uptime: 10,
|
|
54
|
+
extensionConnected: false,
|
|
55
|
+
pending: 0,
|
|
56
|
+
lastCliRequestTime: Date.now(),
|
|
57
|
+
memoryMB: 16,
|
|
58
|
+
port: 19825,
|
|
59
|
+
};
|
|
46
60
|
vi.mocked(fetch).mockResolvedValue({
|
|
47
61
|
ok: true,
|
|
48
|
-
json: () => Promise.resolve(
|
|
49
|
-
ok: true,
|
|
50
|
-
pid: 123,
|
|
51
|
-
uptime: 10,
|
|
52
|
-
extensionConnected: false,
|
|
53
|
-
pending: 0,
|
|
54
|
-
lastCliRequestTime: Date.now(),
|
|
55
|
-
memoryMB: 16,
|
|
56
|
-
port: 19825,
|
|
57
|
-
}),
|
|
62
|
+
json: () => Promise.resolve(status),
|
|
58
63
|
});
|
|
59
|
-
await expect(
|
|
64
|
+
await expect(getDaemonHealth()).resolves.toEqual({ state: 'no-extension', status });
|
|
60
65
|
});
|
|
61
|
-
it('
|
|
66
|
+
it('getDaemonHealth returns ready when daemon and extension are both connected', async () => {
|
|
67
|
+
const status = {
|
|
68
|
+
ok: true,
|
|
69
|
+
pid: 123,
|
|
70
|
+
uptime: 10,
|
|
71
|
+
extensionConnected: true,
|
|
72
|
+
extensionVersion: '1.2.3',
|
|
73
|
+
pending: 0,
|
|
74
|
+
lastCliRequestTime: Date.now(),
|
|
75
|
+
memoryMB: 32,
|
|
76
|
+
port: 19825,
|
|
77
|
+
};
|
|
62
78
|
vi.mocked(fetch).mockResolvedValue({
|
|
63
79
|
ok: true,
|
|
64
|
-
json: () => Promise.resolve(
|
|
65
|
-
ok: true,
|
|
66
|
-
pid: 123,
|
|
67
|
-
uptime: 10,
|
|
68
|
-
extensionConnected: false,
|
|
69
|
-
pending: 0,
|
|
70
|
-
lastCliRequestTime: Date.now(),
|
|
71
|
-
memoryMB: 16,
|
|
72
|
-
port: 19825,
|
|
73
|
-
}),
|
|
80
|
+
json: () => Promise.resolve(status),
|
|
74
81
|
});
|
|
75
|
-
await expect(
|
|
82
|
+
await expect(getDaemonHealth()).resolves.toEqual({ state: 'ready', status });
|
|
76
83
|
});
|
|
77
84
|
});
|
|
@@ -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
|
+
});
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
export { Page } from './page.js';
|
|
8
8
|
export { BrowserBridge } from './bridge.js';
|
|
9
9
|
export { CDPBridge } from './cdp.js';
|
|
10
|
-
export {
|
|
10
|
+
export { getDaemonHealth } from './daemon-client.js';
|
|
11
|
+
export type { DaemonHealth } from './daemon-client.js';
|
|
11
12
|
export { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
|
|
12
13
|
export { generateStealthJs } from './stealth.js';
|
|
13
14
|
export type { DomSnapshotOptions } from './dom-snapshot.js';
|
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
export { Page } from './page.js';
|
|
8
8
|
export { BrowserBridge } from './bridge.js';
|
|
9
9
|
export { CDPBridge } from './cdp.js';
|
|
10
|
-
export {
|
|
10
|
+
export { getDaemonHealth } from './daemon-client.js';
|
|
11
11
|
export { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
|
|
12
12
|
export { generateStealthJs } from './stealth.js';
|
|
@@ -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?: {
|