@jackwener/opencli 1.1.1 → 1.2.1
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/.github/workflows/build-extension.yml +3 -3
- package/.github/workflows/ci.yml +6 -6
- package/.github/workflows/doc-check.yml +3 -3
- package/.github/workflows/e2e-headed.yml +2 -2
- package/.github/workflows/pkg-pr-new.yml +2 -2
- package/.github/workflows/release.yml +3 -3
- package/.github/workflows/security.yml +2 -2
- package/CONTRIBUTING.md +39 -1
- package/README.md +13 -10
- package/README.zh-CN.md +43 -17
- package/SKILL.md +10 -5
- package/dist/browser/cdp.d.ts +4 -4
- package/dist/browser/cdp.js +39 -16
- package/dist/browser/daemon-client.d.ts +4 -2
- package/dist/browser/daemon-client.js +17 -4
- package/dist/browser/dom-helpers.js +38 -7
- package/dist/browser/dom-snapshot.d.ts +86 -0
- package/dist/browser/dom-snapshot.js +729 -0
- package/dist/browser/dom-snapshot.test.d.ts +11 -0
- package/dist/browser/dom-snapshot.test.js +212 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/mcp.js +3 -1
- package/dist/browser/page.d.ts +14 -24
- package/dist/browser/page.js +46 -6
- package/dist/build-manifest.d.ts +11 -4
- package/dist/build-manifest.js +59 -21
- package/dist/build-manifest.test.js +58 -2
- package/dist/cli-manifest.json +3856 -1509
- package/dist/cli.js +66 -0
- package/dist/clis/barchart/greeks.js +1 -1
- package/dist/clis/barchart/options.js +1 -1
- package/dist/clis/barchart/quote.js +1 -1
- package/dist/clis/bilibili/download.js +1 -1
- package/dist/clis/bilibili/following.js +1 -1
- package/dist/clis/bilibili/subtitle.js +1 -1
- package/dist/clis/bilibili/user-videos.js +1 -1
- package/dist/clis/boss/batchgreet.js +10 -97
- package/dist/clis/boss/chatlist.js +8 -25
- package/dist/clis/boss/chatmsg.js +11 -42
- package/dist/clis/boss/common.d.ts +92 -0
- package/dist/clis/boss/common.js +223 -0
- package/dist/clis/boss/detail.js +7 -49
- package/dist/clis/boss/exchange.js +13 -79
- package/dist/clis/boss/greet.js +18 -145
- package/dist/clis/boss/invite.js +26 -121
- package/dist/clis/boss/joblist.js +6 -31
- package/dist/clis/boss/mark.js +12 -85
- package/dist/clis/boss/recommend.js +10 -49
- package/dist/clis/boss/resume.js +18 -118
- package/dist/clis/boss/search.js +12 -60
- package/dist/clis/boss/send.js +17 -151
- package/dist/clis/boss/stats.js +18 -69
- package/dist/clis/coupang/add-to-cart.js +1 -1
- package/dist/clis/devto/tag.yaml +34 -0
- package/dist/clis/devto/top.yaml +29 -0
- package/dist/clis/devto/user.yaml +33 -0
- package/dist/clis/douban/book-hot.d.ts +1 -0
- package/dist/clis/douban/book-hot.js +14 -0
- package/dist/clis/douban/marks.d.ts +1 -0
- package/dist/clis/douban/marks.js +115 -0
- package/dist/clis/douban/movie-hot.d.ts +1 -0
- package/dist/clis/douban/movie-hot.js +14 -0
- package/dist/clis/douban/reviews.d.ts +1 -0
- package/dist/clis/douban/reviews.js +106 -0
- package/dist/clis/douban/search.d.ts +1 -0
- package/dist/clis/douban/search.js +16 -0
- package/dist/clis/douban/shared.d.ts +4 -0
- package/dist/clis/douban/shared.js +155 -0
- package/dist/clis/douban/subject.yaml +76 -0
- package/dist/clis/douban/top250.yaml +70 -0
- package/dist/clis/douban/utils.d.ts +35 -0
- package/dist/clis/douban/utils.js +48 -0
- package/dist/clis/facebook/add-friend.yaml +43 -0
- package/dist/clis/facebook/events.yaml +44 -0
- package/dist/clis/facebook/feed.yaml +63 -0
- package/dist/clis/facebook/friends.yaml +42 -0
- package/dist/clis/facebook/groups.yaml +50 -0
- package/dist/clis/facebook/join-group.yaml +44 -0
- package/dist/clis/facebook/memories.yaml +39 -0
- package/dist/clis/facebook/notifications.yaml +40 -0
- package/dist/clis/facebook/profile.yaml +37 -0
- package/dist/clis/facebook/search.yaml +46 -0
- package/dist/clis/google/news.d.ts +5 -0
- package/dist/clis/google/news.js +58 -0
- package/dist/clis/google/search.d.ts +10 -0
- package/dist/clis/google/search.js +127 -0
- package/dist/clis/google/suggest.d.ts +5 -0
- package/dist/clis/google/suggest.js +34 -0
- package/dist/clis/google/trends.d.ts +5 -0
- package/dist/clis/google/trends.js +38 -0
- package/dist/clis/google/utils.d.ts +9 -0
- package/dist/clis/google/utils.js +23 -0
- package/dist/clis/google/utils.test.d.ts +1 -0
- package/dist/clis/google/utils.test.js +75 -0
- package/dist/clis/grok/ask.d.ts +14 -0
- package/dist/clis/grok/ask.js +257 -65
- package/dist/clis/grok/ask.test.d.ts +1 -0
- package/dist/clis/grok/ask.test.js +36 -0
- package/dist/clis/instagram/comment.yaml +52 -0
- package/dist/clis/instagram/explore.yaml +43 -0
- package/dist/clis/instagram/follow.yaml +41 -0
- package/dist/clis/instagram/followers.yaml +51 -0
- package/dist/clis/instagram/following.yaml +51 -0
- package/dist/clis/instagram/like.yaml +46 -0
- package/dist/clis/instagram/profile.yaml +42 -0
- package/dist/clis/instagram/save.yaml +46 -0
- package/dist/clis/instagram/saved.yaml +40 -0
- package/dist/clis/instagram/search.yaml +43 -0
- package/dist/clis/instagram/unfollow.yaml +38 -0
- package/dist/clis/instagram/unlike.yaml +46 -0
- package/dist/clis/instagram/unsave.yaml +46 -0
- package/dist/clis/instagram/user.yaml +54 -0
- package/dist/clis/jike/repost.js +1 -1
- package/dist/clis/jimeng/generate.yaml +1 -0
- package/dist/clis/linux-do/category.yaml +1 -0
- package/dist/clis/lobsters/active.yaml +29 -0
- package/dist/clis/lobsters/hot.yaml +29 -0
- package/dist/clis/lobsters/newest.yaml +29 -0
- package/dist/clis/lobsters/tag.yaml +34 -0
- package/dist/clis/medium/feed.d.ts +1 -0
- package/dist/clis/medium/feed.js +15 -0
- package/dist/clis/medium/search.d.ts +1 -0
- package/dist/clis/medium/search.js +15 -0
- package/dist/clis/medium/shared.d.ts +5 -0
- package/dist/clis/medium/shared.js +78 -0
- package/dist/clis/medium/user.d.ts +1 -0
- package/dist/clis/medium/user.js +15 -0
- package/dist/clis/reddit/comment.js +1 -1
- package/dist/clis/reddit/read.js +1 -1
- package/dist/clis/reddit/save.js +1 -1
- package/dist/clis/reddit/subreddit.yaml +1 -0
- package/dist/clis/reddit/subscribe.js +1 -1
- package/dist/clis/reddit/upvote.js +1 -1
- package/dist/clis/sinablog/article.d.ts +1 -0
- package/dist/clis/sinablog/article.js +14 -0
- package/dist/clis/sinablog/hot.d.ts +1 -0
- package/dist/clis/sinablog/hot.js +14 -0
- package/dist/clis/sinablog/search.d.ts +1 -0
- package/dist/clis/sinablog/search.js +51 -0
- package/dist/clis/sinablog/shared.d.ts +7 -0
- package/dist/clis/sinablog/shared.js +187 -0
- package/dist/clis/sinablog/user.d.ts +1 -0
- package/dist/clis/sinablog/user.js +15 -0
- package/dist/clis/substack/feed.d.ts +1 -0
- package/dist/clis/substack/feed.js +15 -0
- package/dist/clis/substack/publication.d.ts +1 -0
- package/dist/clis/substack/publication.js +15 -0
- package/dist/clis/substack/search.d.ts +1 -0
- package/dist/clis/substack/search.js +77 -0
- package/dist/clis/substack/shared.d.ts +4 -0
- package/dist/clis/substack/shared.js +129 -0
- package/dist/clis/tiktok/comment.yaml +66 -0
- package/dist/clis/tiktok/explore.yaml +39 -0
- package/dist/clis/tiktok/follow.yaml +39 -0
- package/dist/clis/tiktok/following.yaml +46 -0
- package/dist/clis/tiktok/friends.yaml +47 -0
- package/dist/clis/tiktok/like.yaml +38 -0
- package/dist/clis/tiktok/live.yaml +51 -0
- package/dist/clis/tiktok/notifications.yaml +52 -0
- package/dist/clis/tiktok/profile.yaml +45 -0
- package/dist/clis/tiktok/save.yaml +34 -0
- package/dist/clis/tiktok/search.yaml +46 -0
- package/dist/clis/tiktok/unfollow.yaml +44 -0
- package/dist/clis/tiktok/unlike.yaml +38 -0
- package/dist/clis/tiktok/unsave.yaml +36 -0
- package/dist/clis/tiktok/user.yaml +44 -0
- package/dist/clis/twitter/download.d.ts +1 -1
- package/dist/clis/twitter/download.js +3 -3
- package/dist/clis/twitter/followers.js +1 -1
- package/dist/clis/twitter/following.js +1 -1
- package/dist/clis/twitter/thread.js +1 -1
- package/dist/clis/twitter/timeline.d.ts +23 -0
- package/dist/clis/twitter/timeline.js +42 -14
- package/dist/clis/twitter/timeline.test.d.ts +1 -0
- package/dist/clis/twitter/timeline.test.js +102 -0
- package/dist/clis/wikipedia/random.d.ts +1 -0
- package/dist/clis/wikipedia/random.js +19 -0
- package/dist/clis/wikipedia/search.js +3 -3
- package/dist/clis/wikipedia/summary.js +4 -9
- package/dist/clis/wikipedia/trending.d.ts +1 -0
- package/dist/clis/wikipedia/trending.js +35 -0
- package/dist/clis/wikipedia/utils.d.ts +28 -0
- package/dist/clis/wikipedia/utils.js +13 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
- package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
- package/dist/clis/xiaohongshu/download.js +1 -1
- package/dist/clis/xueqiu/earnings-date.yaml +69 -0
- package/dist/clis/xueqiu/search.yaml +2 -1
- package/dist/clis/xueqiu/stock.yaml +2 -0
- package/dist/clis/yahoo-finance/quote.js +1 -1
- package/dist/commanderAdapter.js +13 -7
- package/dist/daemon.js +21 -0
- package/dist/discovery.d.ts +8 -0
- package/dist/discovery.js +105 -19
- package/dist/doctor.js +3 -1
- package/dist/doctor.test.js +46 -2
- package/dist/engine.test.d.ts +0 -3
- package/dist/engine.test.js +74 -6
- package/dist/execution.d.ts +4 -2
- package/dist/execution.js +31 -7
- package/dist/explore.d.ts +76 -3
- package/dist/explore.js +11 -4
- package/dist/generate.d.ts +41 -2
- package/dist/generate.js +5 -4
- package/dist/main.js +2 -1
- package/dist/pipeline/executor.d.ts +4 -2
- package/dist/pipeline/executor.js +54 -15
- package/dist/pipeline/executor.test.js +33 -6
- package/dist/pipeline/registry.d.ts +1 -1
- package/dist/pipeline/steps/browser.d.ts +7 -7
- package/dist/pipeline/steps/browser.js +15 -7
- package/dist/pipeline/steps/fetch.d.ts +1 -1
- package/dist/pipeline/steps/fetch.js +11 -7
- package/dist/pipeline/steps/transform.d.ts +6 -5
- package/dist/pipeline/steps/transform.js +30 -9
- package/dist/pipeline/template.d.ts +6 -6
- package/dist/pipeline/template.js +43 -5
- package/dist/pipeline/template.test.js +18 -0
- package/dist/pipeline/transform.test.js +11 -0
- package/dist/plugin.d.ts +31 -0
- package/dist/plugin.js +216 -0
- package/dist/plugin.test.d.ts +4 -0
- package/dist/plugin.test.js +76 -0
- package/dist/registry-api.d.ts +11 -0
- package/dist/registry-api.js +9 -0
- package/dist/registry.d.ts +11 -0
- package/dist/registry.js +6 -1
- package/dist/synthesize.d.ts +94 -4
- package/dist/synthesize.js +5 -4
- package/dist/types.d.ts +39 -26
- package/dist/validate.js +8 -2
- package/docs/.vitepress/config.mts +6 -4
- package/docs/adapters/browser/barchart.md +6 -5
- package/docs/adapters/browser/bilibili.md +9 -0
- package/docs/adapters/browser/devto.md +35 -0
- package/docs/adapters/browser/douban.md +38 -0
- package/docs/adapters/browser/facebook.md +36 -0
- package/docs/adapters/browser/google.md +62 -0
- package/docs/adapters/browser/grok.md +26 -8
- package/docs/adapters/browser/instagram.md +46 -0
- package/docs/adapters/browser/lobsters.md +32 -0
- package/docs/adapters/browser/medium.md +32 -0
- package/docs/adapters/browser/reddit.md +9 -0
- package/docs/adapters/browser/sinablog.md +36 -0
- package/docs/adapters/browser/substack.md +38 -0
- package/docs/adapters/browser/tiktok.md +68 -0
- package/docs/adapters/browser/wikipedia.md +11 -2
- package/docs/adapters/browser/xueqiu.md +10 -0
- package/docs/adapters/browser/yahoo-finance.md +6 -5
- package/docs/adapters/desktop/antigravity.md +6 -0
- package/docs/adapters/desktop/chatgpt.md +2 -1
- package/docs/adapters/desktop/codex.md +5 -1
- package/docs/adapters/desktop/cursor.md +4 -0
- package/docs/adapters/desktop/discord.md +7 -7
- package/docs/adapters/index.md +1 -4
- package/docs/guide/getting-started.md +1 -0
- package/docs/guide/plugins.md +153 -0
- package/docs/zh/guide/plugins.md +107 -0
- package/extension/dist/background.js +91 -23
- package/extension/src/background.ts +82 -29
- package/extension/src/cdp.ts +42 -1
- package/package.json +10 -5
- package/scripts/clean-dist.cjs +13 -0
- package/src/browser/cdp.ts +71 -31
- package/src/browser/daemon-client.ts +21 -5
- package/src/browser/dom-helpers.ts +38 -7
- package/src/browser/dom-snapshot.test.ts +249 -0
- package/src/browser/dom-snapshot.ts +770 -0
- package/src/browser/index.ts +2 -0
- package/src/browser/mcp.ts +3 -1
- package/src/browser/page.ts +57 -21
- package/src/build-manifest.test.ts +70 -2
- package/src/build-manifest.ts +94 -26
- package/src/cli.ts +71 -2
- package/src/clis/barchart/greeks.ts +1 -1
- package/src/clis/barchart/options.ts +1 -1
- package/src/clis/barchart/quote.ts +1 -1
- package/src/clis/bilibili/download.ts +1 -1
- package/src/clis/bilibili/following.ts +1 -1
- package/src/clis/bilibili/subtitle.ts +1 -1
- package/src/clis/bilibili/user-videos.ts +1 -1
- package/src/clis/boss/batchgreet.ts +14 -106
- package/src/clis/boss/chatlist.ts +12 -26
- package/src/clis/boss/chatmsg.ts +16 -40
- package/src/clis/boss/common.ts +287 -0
- package/src/clis/boss/detail.ts +8 -54
- package/src/clis/boss/exchange.ts +15 -89
- package/src/clis/boss/greet.ts +23 -160
- package/src/clis/boss/invite.ts +36 -133
- package/src/clis/boss/joblist.ts +7 -36
- package/src/clis/boss/mark.ts +13 -94
- package/src/clis/boss/recommend.ts +12 -57
- package/src/clis/boss/resume.ts +19 -124
- package/src/clis/boss/search.ts +13 -66
- package/src/clis/boss/send.ts +21 -161
- package/src/clis/boss/stats.ts +19 -74
- package/src/clis/coupang/add-to-cart.ts +1 -1
- package/src/clis/devto/tag.yaml +34 -0
- package/src/clis/devto/top.yaml +29 -0
- package/src/clis/devto/user.yaml +33 -0
- package/src/clis/douban/book-hot.ts +15 -0
- package/src/clis/douban/marks.ts +135 -0
- package/src/clis/douban/movie-hot.ts +15 -0
- package/src/clis/douban/reviews.ts +127 -0
- package/src/clis/douban/search.ts +17 -0
- package/src/clis/douban/shared.ts +165 -0
- package/src/clis/douban/subject.yaml +76 -0
- package/src/clis/douban/top250.yaml +70 -0
- package/src/clis/douban/utils.ts +81 -0
- package/src/clis/facebook/add-friend.yaml +43 -0
- package/src/clis/facebook/events.yaml +44 -0
- package/src/clis/facebook/feed.yaml +63 -0
- package/src/clis/facebook/friends.yaml +42 -0
- package/src/clis/facebook/groups.yaml +50 -0
- package/src/clis/facebook/join-group.yaml +44 -0
- package/src/clis/facebook/memories.yaml +39 -0
- package/src/clis/facebook/notifications.yaml +40 -0
- package/src/clis/facebook/profile.yaml +37 -0
- package/src/clis/facebook/search.yaml +46 -0
- package/src/clis/google/news.ts +66 -0
- package/src/clis/google/search.ts +133 -0
- package/src/clis/google/suggest.ts +40 -0
- package/src/clis/google/trends.ts +44 -0
- package/src/clis/google/utils.test.ts +82 -0
- package/src/clis/google/utils.ts +24 -0
- package/src/clis/grok/ask.test.ts +53 -0
- package/src/clis/grok/ask.ts +300 -69
- package/src/clis/instagram/comment.yaml +52 -0
- package/src/clis/instagram/explore.yaml +43 -0
- package/src/clis/instagram/follow.yaml +41 -0
- package/src/clis/instagram/followers.yaml +51 -0
- package/src/clis/instagram/following.yaml +51 -0
- package/src/clis/instagram/like.yaml +46 -0
- package/src/clis/instagram/profile.yaml +42 -0
- package/src/clis/instagram/save.yaml +46 -0
- package/src/clis/instagram/saved.yaml +40 -0
- package/src/clis/instagram/search.yaml +43 -0
- package/src/clis/instagram/unfollow.yaml +38 -0
- package/src/clis/instagram/unlike.yaml +46 -0
- package/src/clis/instagram/unsave.yaml +46 -0
- package/src/clis/instagram/user.yaml +54 -0
- package/src/clis/jike/repost.ts +1 -1
- package/src/clis/jimeng/generate.yaml +1 -0
- package/src/clis/linux-do/category.yaml +1 -0
- package/src/clis/lobsters/active.yaml +29 -0
- package/src/clis/lobsters/hot.yaml +29 -0
- package/src/clis/lobsters/newest.yaml +29 -0
- package/src/clis/lobsters/tag.yaml +34 -0
- package/src/clis/medium/feed.ts +16 -0
- package/src/clis/medium/search.ts +16 -0
- package/src/clis/medium/shared.ts +83 -0
- package/src/clis/medium/user.ts +16 -0
- package/src/clis/reddit/comment.ts +1 -1
- package/src/clis/reddit/read.ts +1 -1
- package/src/clis/reddit/save.ts +1 -1
- package/src/clis/reddit/subreddit.yaml +1 -0
- package/src/clis/reddit/subscribe.ts +1 -1
- package/src/clis/reddit/upvote.ts +1 -1
- package/src/clis/sinablog/article.ts +15 -0
- package/src/clis/sinablog/hot.ts +15 -0
- package/src/clis/sinablog/search.ts +56 -0
- package/src/clis/sinablog/shared.ts +198 -0
- package/src/clis/sinablog/user.ts +16 -0
- package/src/clis/substack/feed.ts +16 -0
- package/src/clis/substack/publication.ts +16 -0
- package/src/clis/substack/search.ts +91 -0
- package/src/clis/substack/shared.ts +132 -0
- package/src/clis/tiktok/comment.yaml +66 -0
- package/src/clis/tiktok/explore.yaml +39 -0
- package/src/clis/tiktok/follow.yaml +39 -0
- package/src/clis/tiktok/following.yaml +46 -0
- package/src/clis/tiktok/friends.yaml +47 -0
- package/src/clis/tiktok/like.yaml +38 -0
- package/src/clis/tiktok/live.yaml +51 -0
- package/src/clis/tiktok/notifications.yaml +52 -0
- package/src/clis/tiktok/profile.yaml +45 -0
- package/src/clis/tiktok/save.yaml +34 -0
- package/src/clis/tiktok/search.yaml +46 -0
- package/src/clis/tiktok/unfollow.yaml +44 -0
- package/src/clis/tiktok/unlike.yaml +38 -0
- package/src/clis/tiktok/unsave.yaml +36 -0
- package/src/clis/tiktok/user.yaml +44 -0
- package/src/clis/twitter/download.ts +3 -3
- package/src/clis/twitter/followers.ts +1 -1
- package/src/clis/twitter/following.ts +1 -1
- package/src/clis/twitter/thread.ts +1 -1
- package/src/clis/twitter/timeline.test.ts +109 -0
- package/src/clis/twitter/timeline.ts +59 -19
- package/src/clis/wikipedia/random.ts +19 -0
- package/src/clis/wikipedia/search.ts +10 -4
- package/src/clis/wikipedia/summary.ts +4 -9
- package/src/clis/wikipedia/trending.ts +41 -0
- package/src/clis/wikipedia/utils.ts +31 -0
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
- package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
- package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
- package/src/clis/xiaohongshu/download.ts +1 -1
- package/src/clis/xueqiu/earnings-date.yaml +69 -0
- package/src/clis/xueqiu/search.yaml +2 -1
- package/src/clis/xueqiu/stock.yaml +2 -0
- package/src/clis/yahoo-finance/quote.ts +1 -1
- package/src/commanderAdapter.ts +17 -10
- package/src/daemon.ts +23 -0
- package/src/discovery.ts +134 -24
- package/src/doctor.test.ts +59 -2
- package/src/doctor.ts +4 -2
- package/src/engine.test.ts +79 -6
- package/src/execution.ts +42 -16
- package/src/explore.ts +77 -9
- package/src/generate.ts +58 -9
- package/src/main.ts +2 -1
- package/src/pipeline/executor.test.ts +35 -6
- package/src/pipeline/executor.ts +68 -19
- package/src/pipeline/registry.ts +3 -3
- package/src/pipeline/steps/browser.ts +24 -15
- package/src/pipeline/steps/fetch.ts +18 -13
- package/src/pipeline/steps/transform.ts +40 -15
- package/src/pipeline/template.test.ts +18 -0
- package/src/pipeline/template.ts +86 -13
- package/src/pipeline/transform.test.ts +15 -2
- package/src/plugin.test.ts +86 -0
- package/src/plugin.ts +254 -0
- package/src/registry-api.ts +12 -0
- package/src/registry.ts +19 -1
- package/src/synthesize.ts +102 -21
- package/src/types.ts +44 -12
- package/src/validate.ts +19 -4
- package/tests/e2e/browser-public.test.ts +11 -0
- package/tests/e2e/public-commands.test.ts +64 -0
- package/dist/clis/feishu/new.d.ts +0 -1
- package/dist/clis/feishu/new.js +0 -27
- package/dist/clis/feishu/read.d.ts +0 -1
- package/dist/clis/feishu/read.js +0 -40
- package/dist/clis/feishu/search.d.ts +0 -1
- package/dist/clis/feishu/search.js +0 -30
- package/dist/clis/feishu/send.d.ts +0 -1
- package/dist/clis/feishu/send.js +0 -39
- package/dist/clis/feishu/status.d.ts +0 -1
- package/dist/clis/feishu/status.js +0 -28
- package/dist/clis/neteasemusic/like.d.ts +0 -1
- package/dist/clis/neteasemusic/like.js +0 -25
- package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
- package/dist/clis/neteasemusic/lyrics.js +0 -47
- package/dist/clis/neteasemusic/next.d.ts +0 -1
- package/dist/clis/neteasemusic/next.js +0 -26
- package/dist/clis/neteasemusic/play.d.ts +0 -1
- package/dist/clis/neteasemusic/play.js +0 -26
- package/dist/clis/neteasemusic/playing.d.ts +0 -1
- package/dist/clis/neteasemusic/playing.js +0 -59
- package/dist/clis/neteasemusic/playlist.d.ts +0 -1
- package/dist/clis/neteasemusic/playlist.js +0 -46
- package/dist/clis/neteasemusic/prev.d.ts +0 -1
- package/dist/clis/neteasemusic/prev.js +0 -25
- package/dist/clis/neteasemusic/search.d.ts +0 -1
- package/dist/clis/neteasemusic/search.js +0 -52
- package/dist/clis/neteasemusic/status.d.ts +0 -1
- package/dist/clis/neteasemusic/status.js +0 -16
- package/dist/clis/neteasemusic/volume.d.ts +0 -1
- package/dist/clis/neteasemusic/volume.js +0 -54
- package/dist/clis/wechat/chats.d.ts +0 -1
- package/dist/clis/wechat/chats.js +0 -28
- package/dist/clis/wechat/contacts.d.ts +0 -1
- package/dist/clis/wechat/contacts.js +0 -28
- package/dist/clis/wechat/read.d.ts +0 -1
- package/dist/clis/wechat/read.js +0 -58
- package/dist/clis/wechat/search.d.ts +0 -1
- package/dist/clis/wechat/search.js +0 -31
- package/dist/clis/wechat/send.d.ts +0 -1
- package/dist/clis/wechat/send.js +0 -42
- package/dist/clis/wechat/status.d.ts +0 -1
- package/dist/clis/wechat/status.js +0 -29
- package/dist/pipeline.d.ts +0 -7
- package/dist/pipeline.js +0 -7
- package/docs/adapters/browser/github.md +0 -26
- package/docs/adapters/desktop/feishu.md +0 -20
- package/docs/adapters/desktop/neteasemusic.md +0 -31
- package/docs/adapters/desktop/wechat.md +0 -28
- package/src/clis/antigravity/README.md +0 -5
- package/src/clis/antigravity/README.zh-CN.md +0 -51
- package/src/clis/chaoxing/README.md +0 -14
- package/src/clis/chaoxing/README.zh-CN.md +0 -35
- package/src/clis/chatgpt/README.md +0 -5
- package/src/clis/chatgpt/README.zh-CN.md +0 -44
- package/src/clis/chatwise/README.md +0 -5
- package/src/clis/chatwise/README.zh-CN.md +0 -38
- package/src/clis/codex/README.md +0 -5
- package/src/clis/codex/README.zh-CN.md +0 -33
- package/src/clis/cursor/README.md +0 -5
- package/src/clis/cursor/README.zh-CN.md +0 -33
- package/src/clis/discord-app/README.md +0 -5
- package/src/clis/discord-app/README.zh-CN.md +0 -28
- package/src/clis/feishu/README.md +0 -5
- package/src/clis/feishu/README.zh-CN.md +0 -20
- package/src/clis/feishu/new.ts +0 -32
- package/src/clis/feishu/read.ts +0 -48
- package/src/clis/feishu/search.ts +0 -35
- package/src/clis/feishu/send.ts +0 -46
- package/src/clis/feishu/status.ts +0 -34
- package/src/clis/neteasemusic/README.md +0 -5
- package/src/clis/neteasemusic/README.zh-CN.md +0 -31
- package/src/clis/neteasemusic/like.ts +0 -28
- package/src/clis/neteasemusic/lyrics.ts +0 -53
- package/src/clis/neteasemusic/next.ts +0 -30
- package/src/clis/neteasemusic/play.ts +0 -30
- package/src/clis/neteasemusic/playing.ts +0 -62
- package/src/clis/neteasemusic/playlist.ts +0 -51
- package/src/clis/neteasemusic/prev.ts +0 -29
- package/src/clis/neteasemusic/search.ts +0 -58
- package/src/clis/neteasemusic/status.ts +0 -18
- package/src/clis/neteasemusic/volume.ts +0 -61
- package/src/clis/notion/README.md +0 -5
- package/src/clis/notion/README.zh-CN.md +0 -29
- package/src/clis/wechat/README.md +0 -5
- package/src/clis/wechat/README.zh-CN.md +0 -28
- package/src/clis/wechat/chats.ts +0 -33
- package/src/clis/wechat/contacts.ts +0 -33
- package/src/clis/wechat/read.ts +0 -72
- package/src/clis/wechat/search.ts +0 -36
- package/src/clis/wechat/send.ts +0 -49
- package/src/clis/wechat/status.ts +0 -35
- package/src/pipeline.ts +0 -8
package/dist/explore.d.ts
CHANGED
|
@@ -5,8 +5,80 @@
|
|
|
5
5
|
* captures network traffic, analyzes JSON responses, and automatically
|
|
6
6
|
* infers CLI capabilities from discovered API endpoints.
|
|
7
7
|
*/
|
|
8
|
+
import type { IBrowserFactory } from './runtime.js';
|
|
8
9
|
export declare function detectSiteName(url: string): string;
|
|
9
10
|
export declare function slugify(value: string): string;
|
|
11
|
+
interface InferredCapability {
|
|
12
|
+
name: string;
|
|
13
|
+
description: string;
|
|
14
|
+
strategy: string;
|
|
15
|
+
confidence: number;
|
|
16
|
+
endpoint: string;
|
|
17
|
+
itemPath: string | null;
|
|
18
|
+
recommendedColumns: string[];
|
|
19
|
+
recommendedArgs: Array<{
|
|
20
|
+
name: string;
|
|
21
|
+
type: string;
|
|
22
|
+
required: boolean;
|
|
23
|
+
default?: unknown;
|
|
24
|
+
}>;
|
|
25
|
+
storeHint?: {
|
|
26
|
+
store: string;
|
|
27
|
+
action: string;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface ExploreManifest {
|
|
31
|
+
site: string;
|
|
32
|
+
target_url: string;
|
|
33
|
+
final_url: string;
|
|
34
|
+
title: string;
|
|
35
|
+
framework: Record<string, boolean>;
|
|
36
|
+
stores: Array<{
|
|
37
|
+
type: DiscoveredStore['type'];
|
|
38
|
+
id: string;
|
|
39
|
+
actions: string[];
|
|
40
|
+
}>;
|
|
41
|
+
top_strategy: string;
|
|
42
|
+
explored_at?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface ExploreAuthSummary {
|
|
45
|
+
top_strategy: string;
|
|
46
|
+
indicators: string[];
|
|
47
|
+
framework: Record<string, boolean>;
|
|
48
|
+
}
|
|
49
|
+
export interface ExploreEndpointArtifact {
|
|
50
|
+
pattern: string;
|
|
51
|
+
method: string;
|
|
52
|
+
url: string;
|
|
53
|
+
status: number | null;
|
|
54
|
+
contentType: string;
|
|
55
|
+
score: number;
|
|
56
|
+
queryParams: string[];
|
|
57
|
+
itemPath: string | null;
|
|
58
|
+
itemCount: number;
|
|
59
|
+
detectedFields: Record<string, string>;
|
|
60
|
+
authIndicators: string[];
|
|
61
|
+
}
|
|
62
|
+
export interface ExploreResult {
|
|
63
|
+
site: string;
|
|
64
|
+
target_url: string;
|
|
65
|
+
final_url: string;
|
|
66
|
+
title: string;
|
|
67
|
+
framework: Record<string, boolean>;
|
|
68
|
+
stores: DiscoveredStore[];
|
|
69
|
+
top_strategy: string;
|
|
70
|
+
endpoint_count: number;
|
|
71
|
+
api_endpoint_count: number;
|
|
72
|
+
capabilities: InferredCapability[];
|
|
73
|
+
auth_indicators: string[];
|
|
74
|
+
out_dir: string;
|
|
75
|
+
}
|
|
76
|
+
export interface ExploreBundle {
|
|
77
|
+
manifest: ExploreManifest;
|
|
78
|
+
endpoints: ExploreEndpointArtifact[];
|
|
79
|
+
capabilities: InferredCapability[];
|
|
80
|
+
auth: ExploreAuthSummary;
|
|
81
|
+
}
|
|
10
82
|
export interface DiscoveredStore {
|
|
11
83
|
type: 'pinia' | 'vuex';
|
|
12
84
|
id: string;
|
|
@@ -14,7 +86,7 @@ export interface DiscoveredStore {
|
|
|
14
86
|
stateKeys: string[];
|
|
15
87
|
}
|
|
16
88
|
export declare function exploreUrl(url: string, opts: {
|
|
17
|
-
BrowserFactory: new () =>
|
|
89
|
+
BrowserFactory: new () => IBrowserFactory;
|
|
18
90
|
site?: string;
|
|
19
91
|
goal?: string;
|
|
20
92
|
authenticated?: boolean;
|
|
@@ -24,5 +96,6 @@ export declare function exploreUrl(url: string, opts: {
|
|
|
24
96
|
clickLabels?: string[];
|
|
25
97
|
auto?: boolean;
|
|
26
98
|
workspace?: string;
|
|
27
|
-
}): Promise<
|
|
28
|
-
export declare function renderExploreSummary(result:
|
|
99
|
+
}): Promise<ExploreResult>;
|
|
100
|
+
export declare function renderExploreSummary(result: ExploreResult): string;
|
|
101
|
+
export {};
|
package/dist/explore.js
CHANGED
|
@@ -147,6 +147,9 @@ function flattenFields(obj, prefix, maxDepth) {
|
|
|
147
147
|
}
|
|
148
148
|
return names;
|
|
149
149
|
}
|
|
150
|
+
function isBooleanRecord(value) {
|
|
151
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
152
|
+
}
|
|
150
153
|
function scoreEndpoint(ep) {
|
|
151
154
|
let s = 0;
|
|
152
155
|
if (ep.contentType.includes('json'))
|
|
@@ -401,7 +404,7 @@ export async function exploreUrl(url, opts) {
|
|
|
401
404
|
let framework = {};
|
|
402
405
|
try {
|
|
403
406
|
const fw = await page.evaluate(FRAMEWORK_DETECT_JS);
|
|
404
|
-
if (fw
|
|
407
|
+
if (isBooleanRecord(fw))
|
|
405
408
|
framework = fw;
|
|
406
409
|
}
|
|
407
410
|
catch { }
|
|
@@ -458,11 +461,15 @@ export function renderExploreSummary(result) {
|
|
|
458
461
|
lines.push(`Output: ${result.out_dir}`);
|
|
459
462
|
return lines.join('\n');
|
|
460
463
|
}
|
|
461
|
-
async function readPageMetadata(page
|
|
464
|
+
async function readPageMetadata(page) {
|
|
462
465
|
try {
|
|
463
466
|
const result = await page.evaluate(`() => ({ url: window.location.href, title: document.title || '' })`);
|
|
464
|
-
if (result && typeof result === 'object')
|
|
465
|
-
return {
|
|
467
|
+
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
468
|
+
return {
|
|
469
|
+
url: String(result.url ?? ''),
|
|
470
|
+
title: String(result.title ?? ''),
|
|
471
|
+
};
|
|
472
|
+
}
|
|
466
473
|
}
|
|
467
474
|
catch { }
|
|
468
475
|
return { url: '', title: '' };
|
package/dist/generate.d.ts
CHANGED
|
@@ -7,5 +7,44 @@
|
|
|
7
7
|
* Includes Strategy Cascade: if the initial strategy fails,
|
|
8
8
|
* automatically downgrades and retries.
|
|
9
9
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
import type { IBrowserFactory } from './runtime.js';
|
|
11
|
+
import { type SynthesizeCandidateSummary } from './synthesize.js';
|
|
12
|
+
interface RegisterCandidatesResult {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
count: number;
|
|
15
|
+
}
|
|
16
|
+
export interface GenerateCliOptions {
|
|
17
|
+
url: string;
|
|
18
|
+
BrowserFactory: new () => IBrowserFactory;
|
|
19
|
+
builtinClis?: string;
|
|
20
|
+
userClis?: string;
|
|
21
|
+
goal?: string | null;
|
|
22
|
+
site?: string;
|
|
23
|
+
waitSeconds?: number;
|
|
24
|
+
top?: number;
|
|
25
|
+
register?: boolean;
|
|
26
|
+
workspace?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface GenerateCliResult {
|
|
29
|
+
ok: boolean;
|
|
30
|
+
goal?: string | null;
|
|
31
|
+
normalized_goal?: string | null;
|
|
32
|
+
site: string;
|
|
33
|
+
selected_candidate: SynthesizeCandidateSummary | null;
|
|
34
|
+
selected_command: string;
|
|
35
|
+
explore: {
|
|
36
|
+
endpoint_count: number;
|
|
37
|
+
api_endpoint_count: number;
|
|
38
|
+
capability_count: number;
|
|
39
|
+
top_strategy: string;
|
|
40
|
+
framework: Record<string, boolean>;
|
|
41
|
+
};
|
|
42
|
+
synthesize: {
|
|
43
|
+
candidate_count: number;
|
|
44
|
+
candidates: Array<Pick<SynthesizeCandidateSummary, 'name' | 'strategy' | 'confidence'>>;
|
|
45
|
+
};
|
|
46
|
+
register: RegisterCandidatesResult | null;
|
|
47
|
+
}
|
|
48
|
+
export declare function generateCliFromUrl(opts: GenerateCliOptions): Promise<GenerateCliResult>;
|
|
49
|
+
export declare function renderGenerateSummary(r: GenerateCliResult): string;
|
|
50
|
+
export {};
|
package/dist/generate.js
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { exploreUrl } from './explore.js';
|
|
11
11
|
import { synthesizeFromExplore } from './synthesize.js';
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
function registerCandidates(_opts) {
|
|
13
|
+
return { ok: true, count: 0 };
|
|
14
|
+
}
|
|
14
15
|
const CAPABILITY_ALIASES = {
|
|
15
16
|
search: ['search', '搜索', '查找', 'query', 'keyword'],
|
|
16
17
|
hot: ['hot', '热门', '热榜', '热搜', 'popular', 'top', 'ranking'],
|
|
@@ -58,7 +59,7 @@ export async function generateCliFromUrl(opts) {
|
|
|
58
59
|
const exploreResult = await exploreUrl(opts.url, {
|
|
59
60
|
BrowserFactory: opts.BrowserFactory,
|
|
60
61
|
site: opts.site,
|
|
61
|
-
goal: normalizeGoal(opts.goal) ?? opts.goal,
|
|
62
|
+
goal: normalizeGoal(opts.goal) ?? opts.goal ?? undefined,
|
|
62
63
|
waitSeconds: opts.waitSeconds ?? 3,
|
|
63
64
|
workspace: opts.workspace,
|
|
64
65
|
});
|
|
@@ -68,7 +69,7 @@ export async function generateCliFromUrl(opts) {
|
|
|
68
69
|
});
|
|
69
70
|
// Step 3: Select best candidate for goal
|
|
70
71
|
const selected = selectCandidate(synthesizeResult.candidates ?? [], opts.goal);
|
|
71
|
-
const selectedSite =
|
|
72
|
+
const selectedSite = synthesizeResult.site ?? exploreResult.site;
|
|
72
73
|
// Step 4: Register (if requested)
|
|
73
74
|
let registerResult = null;
|
|
74
75
|
if (opts.register !== false && synthesizeResult.candidate_count > 0) {
|
package/dist/main.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as os from 'node:os';
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import { discoverClis } from './discovery.js';
|
|
8
|
+
import { discoverClis, discoverPlugins } from './discovery.js';
|
|
9
9
|
import { getCompletions } from './completion.js';
|
|
10
10
|
import { runCli } from './cli.js';
|
|
11
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -13,6 +13,7 @@ const __dirname = path.dirname(__filename);
|
|
|
13
13
|
const BUILTIN_CLIS = path.resolve(__dirname, 'clis');
|
|
14
14
|
const USER_CLIS = path.join(os.homedir(), '.opencli', 'clis');
|
|
15
15
|
await discoverClis(BUILTIN_CLIS, USER_CLIS);
|
|
16
|
+
await discoverPlugins();
|
|
16
17
|
// ── Fast-path: handle --get-completions before commander parses ─────────
|
|
17
18
|
// Usage: opencli --get-completions --cursor <N> [word1 word2 ...]
|
|
18
19
|
const getCompIdx = process.argv.indexOf('--get-completions');
|
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import type { IPage } from '../types.js';
|
|
5
5
|
export interface PipelineContext {
|
|
6
|
-
args?: Record<string,
|
|
6
|
+
args?: Record<string, unknown>;
|
|
7
7
|
debug?: boolean;
|
|
8
|
+
/** Max retry attempts per step (default: 2 for browser steps, 0 for others) */
|
|
9
|
+
stepRetries?: number;
|
|
8
10
|
}
|
|
9
|
-
export declare function executePipeline(page: IPage | null, pipeline:
|
|
11
|
+
export declare function executePipeline(page: IPage | null, pipeline: unknown[], ctx?: PipelineContext): Promise<unknown>;
|
|
@@ -3,32 +3,71 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { getStep } from './registry.js';
|
|
5
5
|
import { log } from '../logger.js';
|
|
6
|
+
import { ConfigError } from '../errors.js';
|
|
7
|
+
/** Steps that interact with the browser and may fail transiently */
|
|
8
|
+
const BROWSER_STEPS = new Set(['navigate', 'evaluate', 'click', 'type', 'press', 'wait', 'snapshot', 'scroll']);
|
|
6
9
|
export async function executePipeline(page, pipeline, ctx = {}) {
|
|
7
10
|
const args = ctx.args ?? {};
|
|
8
11
|
const debug = ctx.debug ?? false;
|
|
9
12
|
let data = null;
|
|
10
13
|
const total = pipeline.length;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
14
|
+
try {
|
|
15
|
+
for (let i = 0; i < pipeline.length; i++) {
|
|
16
|
+
const step = pipeline[i];
|
|
17
|
+
if (!step || typeof step !== 'object')
|
|
18
|
+
continue;
|
|
19
|
+
for (const [op, params] of Object.entries(step)) {
|
|
20
|
+
if (debug)
|
|
21
|
+
debugStepStart(i + 1, total, op, params);
|
|
22
|
+
const handler = getStep(op);
|
|
23
|
+
if (handler) {
|
|
24
|
+
data = await executeStepWithRetry(handler, page, params, data, args, op, ctx.stepRetries);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
throw new ConfigError(`Unknown pipeline step "${op}" at index ${i}.`, 'Check the YAML pipeline step name or register the custom step before execution.');
|
|
28
|
+
}
|
|
23
29
|
if (debug)
|
|
24
|
-
|
|
30
|
+
debugStepResult(op, data);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
// Attempt cleanup: close automation window on pipeline failure
|
|
36
|
+
if (page && typeof page.closeWindow === 'function') {
|
|
37
|
+
try {
|
|
38
|
+
await page.closeWindow();
|
|
25
39
|
}
|
|
26
|
-
|
|
27
|
-
debugStepResult(op, data);
|
|
40
|
+
catch { /* ignore */ }
|
|
28
41
|
}
|
|
42
|
+
throw err;
|
|
29
43
|
}
|
|
30
44
|
return data;
|
|
31
45
|
}
|
|
46
|
+
async function executeStepWithRetry(handler, page, params, data, args, op, configRetries) {
|
|
47
|
+
const maxRetries = configRetries ?? (BROWSER_STEPS.has(op) ? 2 : 0);
|
|
48
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
49
|
+
try {
|
|
50
|
+
return await handler(page, params, data, args);
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
if (attempt >= maxRetries)
|
|
54
|
+
throw err;
|
|
55
|
+
// Only retry on transient browser errors
|
|
56
|
+
const msg = err instanceof Error ? err.message : '';
|
|
57
|
+
const isTransient = msg.includes('Extension disconnected')
|
|
58
|
+
|| msg.includes('attach failed')
|
|
59
|
+
|| msg.includes('no longer exists')
|
|
60
|
+
|| msg.includes('CDP connection')
|
|
61
|
+
|| msg.includes('Daemon command failed');
|
|
62
|
+
if (!isTransient)
|
|
63
|
+
throw err;
|
|
64
|
+
// Brief delay before retry
|
|
65
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Unreachable
|
|
69
|
+
throw new Error(`Step "${op}" failed after ${maxRetries} retries`);
|
|
70
|
+
}
|
|
32
71
|
function debugStepStart(stepNum, total, op, params) {
|
|
33
72
|
let preview = '';
|
|
34
73
|
if (typeof params === 'string') {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { describe, it, expect, vi } from 'vitest';
|
|
5
5
|
import { executePipeline } from './index.js';
|
|
6
|
+
import { ConfigError } from '../errors.js';
|
|
6
7
|
/** Create a minimal mock page for testing */
|
|
7
8
|
function createMockPage(overrides = {}) {
|
|
8
9
|
return {
|
|
@@ -13,6 +14,7 @@ function createMockPage(overrides = {}) {
|
|
|
13
14
|
click: vi.fn(),
|
|
14
15
|
typeText: vi.fn(),
|
|
15
16
|
pressKey: vi.fn(),
|
|
17
|
+
getFormState: vi.fn().mockResolvedValue({}),
|
|
16
18
|
wait: vi.fn(),
|
|
17
19
|
tabs: vi.fn().mockResolvedValue([]),
|
|
18
20
|
closeTab: vi.fn(),
|
|
@@ -21,6 +23,7 @@ function createMockPage(overrides = {}) {
|
|
|
21
23
|
networkRequests: vi.fn().mockResolvedValue([]),
|
|
22
24
|
consoleMessages: vi.fn().mockResolvedValue(''),
|
|
23
25
|
scroll: vi.fn(),
|
|
26
|
+
scrollTo: vi.fn(),
|
|
24
27
|
autoScroll: vi.fn(),
|
|
25
28
|
installInterceptor: vi.fn(),
|
|
26
29
|
getInterceptedRequests: vi.fn().mockResolvedValue([]),
|
|
@@ -70,6 +73,30 @@ describe('executePipeline', () => {
|
|
|
70
73
|
{ name: 'World', score: 20 },
|
|
71
74
|
]);
|
|
72
75
|
});
|
|
76
|
+
it('runs inline select inside map step', async () => {
|
|
77
|
+
const page = createMockPage({
|
|
78
|
+
evaluate: vi.fn().mockResolvedValue({
|
|
79
|
+
posts: [
|
|
80
|
+
{ title: 'First', rank: 1 },
|
|
81
|
+
{ title: 'Second', rank: 2 },
|
|
82
|
+
],
|
|
83
|
+
}),
|
|
84
|
+
});
|
|
85
|
+
const result = await executePipeline(page, [
|
|
86
|
+
{ evaluate: 'test' },
|
|
87
|
+
{
|
|
88
|
+
map: {
|
|
89
|
+
select: 'posts',
|
|
90
|
+
title: '${{ item.title }}',
|
|
91
|
+
rank: '${{ item.rank }}',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
]);
|
|
95
|
+
expect(result).toEqual([
|
|
96
|
+
{ title: 'First', rank: 1 },
|
|
97
|
+
{ title: 'Second', rank: 2 },
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
73
100
|
it('executes limit step', async () => {
|
|
74
101
|
const page = createMockPage({
|
|
75
102
|
evaluate: vi.fn().mockResolvedValue([1, 2, 3, 4, 5]),
|
|
@@ -107,13 +134,13 @@ describe('executePipeline', () => {
|
|
|
107
134
|
]);
|
|
108
135
|
expect(page.wait).toHaveBeenCalledWith(2);
|
|
109
136
|
});
|
|
110
|
-
it('
|
|
111
|
-
|
|
112
|
-
|
|
137
|
+
it('fails fast on unknown steps', async () => {
|
|
138
|
+
await expect(executePipeline(null, [
|
|
139
|
+
{ unknownStep: 'test' },
|
|
140
|
+
], { debug: true })).rejects.toBeInstanceOf(ConfigError);
|
|
141
|
+
await expect(executePipeline(null, [
|
|
113
142
|
{ unknownStep: 'test' },
|
|
114
|
-
], { debug: true });
|
|
115
|
-
expect(stderr).toHaveBeenCalledWith(expect.stringContaining('Unknown step'));
|
|
116
|
-
stderr.mockRestore();
|
|
143
|
+
], { debug: true })).rejects.toThrow('Unknown pipeline step "unknownStep"');
|
|
117
144
|
});
|
|
118
145
|
it('passes args through template rendering', async () => {
|
|
119
146
|
const page = createMockPage({
|
|
@@ -8,7 +8,7 @@ import type { IPage } from '../types.js';
|
|
|
8
8
|
* TData is the type of the `data` state flowing into the step.
|
|
9
9
|
* TResult is the expected return type.
|
|
10
10
|
*/
|
|
11
|
-
export type StepHandler<TData =
|
|
11
|
+
export type StepHandler<TData = unknown, TResult = unknown, TParams = unknown> = (page: IPage | null, params: TParams, data: TData, args: Record<string, unknown>) => Promise<TResult>;
|
|
12
12
|
/**
|
|
13
13
|
* Get a registered step handler by name.
|
|
14
14
|
*/
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
* Browser interaction primitives.
|
|
4
4
|
*/
|
|
5
5
|
import type { IPage } from '../../types.js';
|
|
6
|
-
export declare function stepNavigate(page: IPage | null, params:
|
|
7
|
-
export declare function stepClick(page: IPage | null, params:
|
|
8
|
-
export declare function stepType(page: IPage | null, params:
|
|
9
|
-
export declare function stepWait(page: IPage | null, params:
|
|
10
|
-
export declare function stepPress(page: IPage | null, params:
|
|
11
|
-
export declare function stepSnapshot(page: IPage | null, params:
|
|
12
|
-
export declare function stepEvaluate(page: IPage | null, params:
|
|
6
|
+
export declare function stepNavigate(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
7
|
+
export declare function stepClick(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
8
|
+
export declare function stepType(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
9
|
+
export declare function stepWait(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
10
|
+
export declare function stepPress(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
11
|
+
export declare function stepSnapshot(page: IPage | null, params: unknown, _data: unknown, _args: Record<string, unknown>): Promise<unknown>;
|
|
12
|
+
export declare function stepEvaluate(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
* Browser interaction primitives.
|
|
4
4
|
*/
|
|
5
5
|
import { render } from '../template.js';
|
|
6
|
+
function isRecord(value) {
|
|
7
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
6
9
|
export async function stepNavigate(page, params, data, args) {
|
|
7
|
-
if (
|
|
10
|
+
if (isRecord(params) && 'url' in params) {
|
|
8
11
|
const url = String(render(params.url, { args, data }));
|
|
9
|
-
await page.goto(url, { waitUntil: params.waitUntil, settleMs: params.settleMs });
|
|
12
|
+
await page.goto(url, { waitUntil: params.waitUntil, settleMs: typeof params.settleMs === 'number' ? params.settleMs : undefined });
|
|
10
13
|
}
|
|
11
14
|
else {
|
|
12
15
|
const url = render(params, { args, data });
|
|
@@ -19,7 +22,7 @@ export async function stepClick(page, params, data, args) {
|
|
|
19
22
|
return data;
|
|
20
23
|
}
|
|
21
24
|
export async function stepType(page, params, data, args) {
|
|
22
|
-
if (
|
|
25
|
+
if (isRecord(params)) {
|
|
23
26
|
const ref = String(render(params.ref ?? '', { args, data })).replace(/^@/, '');
|
|
24
27
|
const text = String(render(params.text ?? '', { args, data }));
|
|
25
28
|
await page.typeText(ref, text);
|
|
@@ -31,11 +34,11 @@ export async function stepType(page, params, data, args) {
|
|
|
31
34
|
export async function stepWait(page, params, data, args) {
|
|
32
35
|
if (typeof params === 'number')
|
|
33
36
|
await page.wait(params);
|
|
34
|
-
else if (
|
|
37
|
+
else if (isRecord(params)) {
|
|
35
38
|
if ('text' in params) {
|
|
36
39
|
await page.wait({
|
|
37
40
|
text: String(render(params.text, { args, data })),
|
|
38
|
-
timeout: params.timeout
|
|
41
|
+
timeout: typeof params.timeout === 'number' ? params.timeout : undefined,
|
|
39
42
|
});
|
|
40
43
|
}
|
|
41
44
|
else if ('time' in params)
|
|
@@ -50,8 +53,13 @@ export async function stepPress(page, params, data, args) {
|
|
|
50
53
|
return data;
|
|
51
54
|
}
|
|
52
55
|
export async function stepSnapshot(page, params, _data, _args) {
|
|
53
|
-
const opts = (
|
|
54
|
-
return page.snapshot({
|
|
56
|
+
const opts = isRecord(params) ? params : {};
|
|
57
|
+
return page.snapshot({
|
|
58
|
+
interactive: typeof opts.interactive === 'boolean' ? opts.interactive : false,
|
|
59
|
+
compact: typeof opts.compact === 'boolean' ? opts.compact : false,
|
|
60
|
+
maxDepth: typeof opts.max_depth === 'number' ? opts.max_depth : undefined,
|
|
61
|
+
raw: typeof opts.raw === 'boolean' ? opts.raw : false,
|
|
62
|
+
});
|
|
55
63
|
}
|
|
56
64
|
export async function stepEvaluate(page, params, data, args) {
|
|
57
65
|
const js = String(render(params, { args, data }));
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Pipeline step: fetch — HTTP API requests.
|
|
3
3
|
*/
|
|
4
4
|
import type { IPage } from '../../types.js';
|
|
5
|
-
export declare function stepFetch(page: IPage | null, params:
|
|
5
|
+
export declare function stepFetch(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
* Pipeline step: fetch — HTTP API requests.
|
|
3
3
|
*/
|
|
4
4
|
import { render } from '../template.js';
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
5
8
|
/** Simple async concurrency limiter */
|
|
6
9
|
async function mapConcurrent(items, limit, fn) {
|
|
7
10
|
const results = new Array(items.length);
|
|
@@ -53,7 +56,7 @@ async function fetchSingle(page, url, method, queryParams, headers, args, data)
|
|
|
53
56
|
async function fetchBatchInBrowser(page, urls, method, headers, concurrency) {
|
|
54
57
|
const headersJs = JSON.stringify(headers);
|
|
55
58
|
const urlsJs = JSON.stringify(urls);
|
|
56
|
-
return page.evaluate(`
|
|
59
|
+
return (await page.evaluate(`
|
|
57
60
|
async () => {
|
|
58
61
|
const urls = ${urlsJs};
|
|
59
62
|
const method = "${method}";
|
|
@@ -79,17 +82,18 @@ async function fetchBatchInBrowser(page, urls, method, headers, concurrency) {
|
|
|
79
82
|
await Promise.all(workers);
|
|
80
83
|
return results;
|
|
81
84
|
}
|
|
82
|
-
`);
|
|
85
|
+
`));
|
|
83
86
|
}
|
|
84
87
|
export async function stepFetch(page, params, data, args) {
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
88
|
+
const paramObject = isRecord(params) ? params : {};
|
|
89
|
+
const urlOrObj = typeof params === 'string' ? params : (paramObject.url ?? '');
|
|
90
|
+
const method = typeof paramObject.method === 'string' ? paramObject.method : 'GET';
|
|
91
|
+
const queryParams = isRecord(paramObject.params) ? paramObject.params : {};
|
|
92
|
+
const headers = isRecord(paramObject.headers) ? paramObject.headers : {};
|
|
89
93
|
const urlTemplate = String(urlOrObj);
|
|
90
94
|
// Per-item fetch when data is array and URL references item
|
|
91
95
|
if (Array.isArray(data) && urlTemplate.includes('item')) {
|
|
92
|
-
const concurrency = typeof
|
|
96
|
+
const concurrency = typeof paramObject.concurrency === 'number' ? paramObject.concurrency : 5;
|
|
93
97
|
// Render all URLs upfront
|
|
94
98
|
const renderedHeaders = {};
|
|
95
99
|
for (const [k, v] of Object.entries(headers))
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pipeline steps: data transforms — select, map, filter, sort, limit.
|
|
3
3
|
*/
|
|
4
|
-
|
|
5
|
-
export declare function
|
|
6
|
-
export declare function
|
|
7
|
-
export declare function
|
|
8
|
-
export declare function
|
|
4
|
+
import type { IPage } from '../../types.js';
|
|
5
|
+
export declare function stepSelect(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
6
|
+
export declare function stepMap(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
7
|
+
export declare function stepFilter(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
8
|
+
export declare function stepSort(_page: IPage | null, params: unknown, data: unknown, _args: Record<string, unknown>): Promise<unknown>;
|
|
9
|
+
export declare function stepLimit(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown>;
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* Pipeline steps: data transforms — select, map, filter, sort, limit.
|
|
3
3
|
*/
|
|
4
4
|
import { render, evalExpr } from '../template.js';
|
|
5
|
+
function isRecord(value) {
|
|
6
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
7
|
+
}
|
|
5
8
|
export async function stepSelect(_page, params, data, args) {
|
|
6
9
|
const pathStr = String(render(params, { args, data }));
|
|
7
10
|
if (data && typeof data === 'object') {
|
|
8
11
|
let current = data;
|
|
9
12
|
for (const part of pathStr.split('.')) {
|
|
10
|
-
if (
|
|
13
|
+
if (isRecord(current))
|
|
11
14
|
current = current[part];
|
|
12
15
|
else if (Array.isArray(current) && /^\d+$/.test(part))
|
|
13
16
|
current = current[parseInt(part, 10)];
|
|
@@ -21,15 +24,26 @@ export async function stepSelect(_page, params, data, args) {
|
|
|
21
24
|
export async function stepMap(_page, params, data, args) {
|
|
22
25
|
if (!data || typeof data !== 'object')
|
|
23
26
|
return data;
|
|
24
|
-
let
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
let source = data;
|
|
28
|
+
// Support inline select: { map: { select: 'path', key: '${{ item.x }}' } }
|
|
29
|
+
if (isRecord(params) && 'select' in params) {
|
|
30
|
+
source = await stepSelect(null, params.select, data, args);
|
|
31
|
+
}
|
|
32
|
+
if (!source || typeof source !== 'object')
|
|
33
|
+
return source;
|
|
34
|
+
let items = Array.isArray(source) ? source : [source];
|
|
35
|
+
if (isRecord(source) && Array.isArray(source.data))
|
|
36
|
+
items = source.data;
|
|
27
37
|
const result = [];
|
|
38
|
+
const templateParams = isRecord(params) ? params : {};
|
|
28
39
|
for (let i = 0; i < items.length; i++) {
|
|
29
40
|
const item = items[i];
|
|
30
41
|
const row = {};
|
|
31
|
-
for (const [key, template] of Object.entries(
|
|
32
|
-
|
|
42
|
+
for (const [key, template] of Object.entries(templateParams)) {
|
|
43
|
+
if (key === 'select')
|
|
44
|
+
continue;
|
|
45
|
+
row[key] = render(template, { args, data: source, item, index: i });
|
|
46
|
+
}
|
|
33
47
|
result.push(row);
|
|
34
48
|
}
|
|
35
49
|
return result;
|
|
@@ -42,9 +56,16 @@ export async function stepFilter(_page, params, data, args) {
|
|
|
42
56
|
export async function stepSort(_page, params, data, _args) {
|
|
43
57
|
if (!Array.isArray(data))
|
|
44
58
|
return data;
|
|
45
|
-
const key =
|
|
46
|
-
const reverse =
|
|
47
|
-
return [...data].sort((a, b) => {
|
|
59
|
+
const key = isRecord(params) ? String(params.by ?? '') : String(params);
|
|
60
|
+
const reverse = isRecord(params) ? params.order === 'desc' : false;
|
|
61
|
+
return [...data].sort((a, b) => {
|
|
62
|
+
const left = isRecord(a) ? a[key] : undefined;
|
|
63
|
+
const right = isRecord(b) ? b[key] : undefined;
|
|
64
|
+
const va = left ?? '';
|
|
65
|
+
const vb = right ?? '';
|
|
66
|
+
const cmp = va < vb ? -1 : va > vb ? 1 : 0;
|
|
67
|
+
return reverse ? -cmp : cmp;
|
|
68
|
+
});
|
|
48
69
|
}
|
|
49
70
|
export async function stepLimit(_page, params, data, args) {
|
|
50
71
|
if (!Array.isArray(data))
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* Pipeline template engine: ${{ ... }} expression rendering.
|
|
3
3
|
*/
|
|
4
4
|
export interface RenderContext {
|
|
5
|
-
args?: Record<string,
|
|
6
|
-
data?:
|
|
7
|
-
item?:
|
|
5
|
+
args?: Record<string, unknown>;
|
|
6
|
+
data?: unknown;
|
|
7
|
+
item?: unknown;
|
|
8
8
|
index?: number;
|
|
9
9
|
}
|
|
10
|
-
export declare function render(template:
|
|
11
|
-
export declare function evalExpr(expr: string, ctx: RenderContext):
|
|
12
|
-
export declare function resolvePath(pathStr: string, ctx: RenderContext):
|
|
10
|
+
export declare function render(template: unknown, ctx: RenderContext): unknown;
|
|
11
|
+
export declare function evalExpr(expr: string, ctx: RenderContext): unknown;
|
|
12
|
+
export declare function resolvePath(pathStr: string, ctx: RenderContext): unknown;
|
|
13
13
|
/**
|
|
14
14
|
* Normalize JavaScript source for browser evaluate() calls.
|
|
15
15
|
*/
|