@jackwener/opencli 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +39 -1
- package/README.md +9 -10
- package/README.zh-CN.md +39 -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 +2 -1
- 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/page.d.ts +14 -24
- package/dist/browser/page.js +37 -4
- 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/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 +2 -2
- package/dist/pipeline/executor.js +2 -2
- 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/src/background.ts +18 -11
- 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 +2 -1
- 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/page.ts +50 -19
- 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/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 +11 -7
- 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.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,7 @@
|
|
|
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
8
|
}
|
|
9
|
-
export declare function executePipeline(page: IPage | null, pipeline:
|
|
9
|
+
export declare function executePipeline(page: IPage | null, pipeline: unknown[], ctx?: PipelineContext): Promise<unknown>;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { getStep } from './registry.js';
|
|
5
5
|
import { log } from '../logger.js';
|
|
6
|
+
import { ConfigError } from '../errors.js';
|
|
6
7
|
export async function executePipeline(page, pipeline, ctx = {}) {
|
|
7
8
|
const args = ctx.args ?? {};
|
|
8
9
|
const debug = ctx.debug ?? false;
|
|
@@ -20,8 +21,7 @@ export async function executePipeline(page, pipeline, ctx = {}) {
|
|
|
20
21
|
data = await handler(page, params, data, args);
|
|
21
22
|
}
|
|
22
23
|
else {
|
|
23
|
-
|
|
24
|
-
log.warn(`Unknown step: ${op}`);
|
|
24
|
+
throw new ConfigError(`Unknown pipeline step "${op}" at index ${i}.`, 'Check the YAML pipeline step name or register the custom step before execution.');
|
|
25
25
|
}
|
|
26
26
|
if (debug)
|
|
27
27
|
debugStepResult(op, data);
|
|
@@ -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
|
*/
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pipeline template engine: ${{ ... }} expression rendering.
|
|
3
3
|
*/
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
4
7
|
export function render(template, ctx) {
|
|
5
8
|
if (typeof template !== 'string')
|
|
6
9
|
return template;
|
|
10
|
+
const trimmed = template.trim();
|
|
7
11
|
// Full expression: entire string is a single ${{ ... }}
|
|
8
12
|
// Use [^}] to prevent matching across }} boundaries (e.g. "${{ a }}-${{ b }}")
|
|
9
|
-
const fullMatch =
|
|
10
|
-
if (fullMatch && !
|
|
13
|
+
const fullMatch = trimmed.match(/^\$\{\{\s*([^}]*(?:\}[^}][^}]*)*)\s*\}\}$/);
|
|
14
|
+
if (fullMatch && !trimmed.includes('}}-') && !trimmed.includes('}}${{'))
|
|
11
15
|
return evalExpr(fullMatch[1].trim(), ctx);
|
|
12
16
|
// Check if the entire string is a single expression (no other text around it)
|
|
13
|
-
const singleExpr =
|
|
17
|
+
const singleExpr = trimmed.match(/^\$\{\{\s*([\s\S]*?)\s*\}\}$/);
|
|
14
18
|
if (singleExpr) {
|
|
15
19
|
// Verify it's truly a single expression (no other ${{ inside)
|
|
16
20
|
const inner = singleExpr[1];
|
|
@@ -62,7 +66,10 @@ export function evalExpr(expr, ctx) {
|
|
|
62
66
|
const right = orMatch[2].trim();
|
|
63
67
|
return right.replace(/^['"]|['"]$/g, '');
|
|
64
68
|
}
|
|
65
|
-
|
|
69
|
+
const resolved = resolvePath(expr, { args, item, data, index });
|
|
70
|
+
if (resolved !== null && resolved !== undefined)
|
|
71
|
+
return resolved;
|
|
72
|
+
return evalJsExpr(expr, { args, item, data, index });
|
|
66
73
|
}
|
|
67
74
|
/**
|
|
68
75
|
* Apply a named filter to a value.
|
|
@@ -142,6 +149,10 @@ function applyFilter(filterExpr, value) {
|
|
|
142
149
|
const parts = value.split(/[/\\]/);
|
|
143
150
|
return parts[parts.length - 1] || value;
|
|
144
151
|
}
|
|
152
|
+
case 'urlencode':
|
|
153
|
+
return typeof value === 'string' ? encodeURIComponent(value) : value;
|
|
154
|
+
case 'urldecode':
|
|
155
|
+
return typeof value === 'string' ? decodeURIComponent(value) : value;
|
|
145
156
|
default:
|
|
146
157
|
return value;
|
|
147
158
|
}
|
|
@@ -174,7 +185,7 @@ export function resolvePath(pathStr, ctx) {
|
|
|
174
185
|
rest = parts;
|
|
175
186
|
}
|
|
176
187
|
for (const part of rest) {
|
|
177
|
-
if (
|
|
188
|
+
if (isRecord(obj))
|
|
178
189
|
obj = obj[part];
|
|
179
190
|
else if (Array.isArray(obj) && /^\d+$/.test(part))
|
|
180
191
|
obj = obj[parseInt(part, 10)];
|
|
@@ -183,6 +194,33 @@ export function resolvePath(pathStr, ctx) {
|
|
|
183
194
|
}
|
|
184
195
|
return obj;
|
|
185
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Evaluate arbitrary JS expressions as a last-resort fallback.
|
|
199
|
+
*
|
|
200
|
+
* ⚠️ SECURITY NOTE: Uses `new Function()` to execute the expression.
|
|
201
|
+
* This is acceptable here because:
|
|
202
|
+
* 1. YAML adapters are authored by trusted repo contributors only.
|
|
203
|
+
* 2. The expression runs in the same Node.js process (no sandbox).
|
|
204
|
+
* 3. Only a curated set of globals is exposed (no require/import/process/fs).
|
|
205
|
+
* If opencli ever loads untrusted third-party adapters, this MUST be replaced
|
|
206
|
+
* with a proper sandboxed evaluator.
|
|
207
|
+
*/
|
|
208
|
+
function evalJsExpr(expr, ctx) {
|
|
209
|
+
// Guard against absurdly long expressions that could indicate injection.
|
|
210
|
+
if (expr.length > 2000)
|
|
211
|
+
return undefined;
|
|
212
|
+
const args = ctx.args ?? {};
|
|
213
|
+
const item = ctx.item ?? {};
|
|
214
|
+
const data = ctx.data;
|
|
215
|
+
const index = ctx.index ?? 0;
|
|
216
|
+
try {
|
|
217
|
+
const fn = new Function('args', 'item', 'data', 'index', 'encodeURIComponent', 'decodeURIComponent', 'JSON', 'Math', 'Number', 'String', 'Boolean', 'Array', 'Object', 'Date', `"use strict"; return (${expr});`);
|
|
218
|
+
return fn(args, item, data, index, encodeURIComponent, decodeURIComponent, JSON, Math, Number, String, Boolean, Array, Object, Date);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
186
224
|
/**
|
|
187
225
|
* Normalize JavaScript source for browser evaluate() calls.
|
|
188
226
|
*/
|
|
@@ -54,6 +54,15 @@ describe('evalExpr', () => {
|
|
|
54
54
|
it('resolves simple path', () => {
|
|
55
55
|
expect(evalExpr('item.title', { item: { title: 'Test' } })).toBe('Test');
|
|
56
56
|
});
|
|
57
|
+
it('evaluates JS helper expressions', () => {
|
|
58
|
+
expect(evalExpr('encodeURIComponent(args.keyword)', { args: { keyword: 'hello world' } })).toBe('hello%20world');
|
|
59
|
+
});
|
|
60
|
+
it('evaluates ternary expressions', () => {
|
|
61
|
+
expect(evalExpr("args.kind === 'tech' ? 'technology' : args.kind", { args: { kind: 'tech' } })).toBe('technology');
|
|
62
|
+
});
|
|
63
|
+
it('evaluates method calls on values', () => {
|
|
64
|
+
expect(evalExpr("args.username.startsWith('@') ? args.username : '@' + args.username", { args: { username: 'alice' } })).toBe('@alice');
|
|
65
|
+
});
|
|
57
66
|
it('applies join filter', () => {
|
|
58
67
|
expect(evalExpr('item.tags | join(,)', { item: { tags: ['a', 'b', 'c'] } })).toBe('a,b,c');
|
|
59
68
|
});
|
|
@@ -100,6 +109,15 @@ describe('render', () => {
|
|
|
100
109
|
it('renders URL template', () => {
|
|
101
110
|
expect(render('https://api.example.com/search?q=${{ args.keyword }}', { args: { keyword: 'test' } })).toBe('https://api.example.com/search?q=test');
|
|
102
111
|
});
|
|
112
|
+
it('renders inline helper expressions', () => {
|
|
113
|
+
expect(render('https://example.com/search?q=${{ encodeURIComponent(args.keyword) }}', { args: { keyword: 'hello world' } })).toBe('https://example.com/search?q=hello%20world');
|
|
114
|
+
});
|
|
115
|
+
it('renders full multiline expressions', () => {
|
|
116
|
+
expect(render("${{\n args.topic ? `https://medium.com/tag/${args.topic}` : 'https://medium.com/tag/technology'\n}}", { args: { topic: 'ai' } })).toBe('https://medium.com/tag/ai');
|
|
117
|
+
});
|
|
118
|
+
it('renders block expressions with surrounding whitespace', () => {
|
|
119
|
+
expect(render("\n ${{ args.kind === 'tech' ? 'technology' : args.kind }}\n", { args: { kind: 'tech' } })).toBe('technology');
|
|
120
|
+
});
|
|
103
121
|
});
|
|
104
122
|
describe('normalizeEvaluateSource', () => {
|
|
105
123
|
it('wraps bare expression', () => {
|
|
@@ -49,6 +49,17 @@ describe('stepMap', () => {
|
|
|
49
49
|
it('returns null/undefined as-is', async () => {
|
|
50
50
|
expect(await stepMap(null, { x: '${{ item.x }}' }, null, {})).toBeNull();
|
|
51
51
|
});
|
|
52
|
+
it('supports inline select before mapping', async () => {
|
|
53
|
+
const result = await stepMap(null, {
|
|
54
|
+
select: 'posts',
|
|
55
|
+
title: '${{ item.title }}',
|
|
56
|
+
rank: '${{ index + 1 }}',
|
|
57
|
+
}, { posts: [{ title: 'One' }, { title: 'Two' }] }, {});
|
|
58
|
+
expect(result).toEqual([
|
|
59
|
+
{ title: 'One', rank: 1 },
|
|
60
|
+
{ title: 'Two', rank: 2 },
|
|
61
|
+
]);
|
|
62
|
+
});
|
|
52
63
|
});
|
|
53
64
|
describe('stepFilter', () => {
|
|
54
65
|
it('filters by expression', async () => {
|