@jackwener/opencli 1.1.0 → 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/.agents/skills/cross-project-adapter-migration/SKILL.md +2 -2
- package/.github/pull_request_template.md +7 -0
- package/.github/workflows/doc-check.yml +36 -0
- package/.github/workflows/docs.yml +7 -42
- package/CHANGELOG.md +23 -0
- package/CLI-EXPLORER.md +9 -8
- package/CONTRIBUTING.md +39 -1
- package/README.md +33 -19
- package/README.zh-CN.md +64 -27
- package/SKILL.md +102 -33
- package/dist/browser/cdp.d.ts +4 -4
- package/dist/browser/cdp.js +45 -17
- 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 +18 -25
- package/dist/browser/page.js +44 -5
- package/dist/build-manifest.d.ts +11 -4
- package/dist/build-manifest.js +79 -34
- package/dist/build-manifest.test.js +58 -2
- package/dist/cli-manifest.json +4273 -1771
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +255 -162
- package/dist/clis/apple-podcasts/commands.test.d.ts +2 -0
- package/dist/clis/apple-podcasts/commands.test.js +76 -0
- package/dist/clis/apple-podcasts/search.js +2 -2
- package/dist/clis/apple-podcasts/top.js +9 -2
- package/dist/clis/arxiv/search.js +1 -1
- 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/dynamic.js +1 -1
- package/dist/clis/bilibili/favorite.js +1 -1
- package/dist/clis/bilibili/feed.js +1 -1
- package/dist/clis/bilibili/following.js +2 -2
- package/dist/clis/bilibili/history.js +1 -1
- package/dist/clis/bilibili/me.js +1 -1
- package/dist/clis/bilibili/ranking.js +1 -1
- package/dist/clis/bilibili/search.js +3 -3
- package/dist/clis/bilibili/subtitle.js +2 -2
- package/dist/clis/bilibili/user-videos.js +2 -2
- package/dist/{bilibili.d.ts → clis/bilibili/utils.d.ts} +1 -1
- package/dist/clis/bloomberg/businessweek.js +17 -0
- package/dist/clis/bloomberg/economics.js +17 -0
- package/dist/clis/bloomberg/feeds.d.ts +1 -0
- package/dist/clis/bloomberg/feeds.js +15 -0
- package/dist/clis/bloomberg/industries.d.ts +1 -0
- package/dist/clis/bloomberg/industries.js +17 -0
- package/dist/clis/bloomberg/main.d.ts +1 -0
- package/dist/clis/bloomberg/main.js +17 -0
- package/dist/clis/bloomberg/markets.d.ts +1 -0
- package/dist/clis/bloomberg/markets.js +17 -0
- package/dist/clis/bloomberg/news.d.ts +1 -0
- package/dist/clis/bloomberg/news.js +105 -0
- package/dist/clis/bloomberg/opinions.d.ts +1 -0
- package/dist/clis/bloomberg/opinions.js +17 -0
- package/dist/clis/bloomberg/politics.d.ts +1 -0
- package/dist/clis/bloomberg/politics.js +17 -0
- package/dist/clis/bloomberg/tech.d.ts +1 -0
- package/dist/clis/bloomberg/tech.js +17 -0
- package/dist/clis/bloomberg/utils.d.ts +34 -0
- package/dist/clis/bloomberg/utils.js +364 -0
- package/dist/clis/bloomberg/utils.test.d.ts +1 -0
- package/dist/clis/bloomberg/utils.test.js +129 -0
- package/dist/clis/boss/batchgreet.js +12 -99
- package/dist/clis/boss/chatlist.js +9 -26
- 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 +8 -50
- package/dist/clis/boss/exchange.js +13 -79
- package/dist/clis/boss/greet.js +20 -147
- 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 +13 -61
- package/dist/clis/boss/send.js +18 -152
- package/dist/clis/boss/stats.js +20 -71
- package/dist/clis/chaoxing/assignments.js +1 -1
- package/dist/clis/chaoxing/exams.js +1 -1
- package/dist/{chaoxing.d.ts → clis/chaoxing/utils.d.ts} +1 -1
- package/dist/{chaoxing.js → clis/chaoxing/utils.js} +0 -2
- package/dist/clis/chaoxing/utils.test.d.ts +1 -0
- package/dist/{chaoxing.test.js → clis/chaoxing/utils.test.js} +1 -1
- package/dist/clis/chatgpt/read.js +1 -1
- package/dist/clis/chatwise/export.js +1 -1
- package/dist/clis/chatwise/model.js +2 -2
- package/dist/clis/chatwise/screenshot.js +1 -1
- package/dist/clis/codex/export.js +1 -1
- package/dist/clis/codex/model.js +2 -2
- package/dist/clis/codex/screenshot.js +1 -1
- package/dist/clis/coupang/add-to-cart.js +3 -4
- package/dist/clis/coupang/search.js +2 -4
- package/dist/clis/coupang/utils.test.d.ts +1 -0
- package/dist/{coupang.test.js → clis/coupang/utils.test.js} +1 -1
- package/dist/clis/ctrip/search.js +1 -1
- package/dist/clis/cursor/export.js +1 -1
- package/dist/clis/cursor/model.js +2 -2
- package/dist/clis/cursor/screenshot.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/comment.js +2 -3
- package/dist/clis/jike/create.js +1 -2
- package/dist/clis/jike/feed.js +0 -1
- package/dist/clis/jike/like.js +1 -2
- package/dist/clis/jike/notifications.js +0 -1
- package/dist/clis/jike/post.yaml +1 -0
- package/dist/clis/jike/repost.js +2 -3
- package/dist/clis/jike/search.js +2 -3
- package/dist/clis/jike/topic.yaml +1 -0
- package/dist/clis/jike/user.yaml +1 -0
- package/dist/clis/jimeng/generate.yaml +1 -0
- package/dist/clis/jimeng/history.yaml +0 -1
- package/dist/clis/linkedin/search.js +7 -7
- package/dist/clis/linux-do/category.yaml +2 -0
- package/dist/clis/linux-do/search.yaml +4 -3
- package/dist/clis/linux-do/topic.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/notion/export.js +1 -1
- package/dist/clis/reddit/comment.js +3 -4
- package/dist/clis/reddit/read.js +4 -5
- package/dist/clis/reddit/save.js +2 -3
- package/dist/clis/reddit/saved.js +0 -1
- package/dist/clis/reddit/search.yaml +1 -0
- package/dist/clis/reddit/subreddit.yaml +1 -0
- package/dist/clis/reddit/subscribe.js +1 -2
- package/dist/clis/reddit/upvote.js +2 -3
- package/dist/clis/reddit/upvoted.js +0 -1
- package/dist/clis/reddit/user-comments.yaml +1 -0
- package/dist/clis/reddit/user-posts.yaml +1 -0
- package/dist/clis/reddit/user.yaml +1 -0
- package/dist/clis/reuters/search.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/smzdm/search.js +2 -3
- package/dist/clis/stackoverflow/search.yaml +1 -0
- package/dist/clis/steam/top-sellers.yaml +29 -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/accept.js +2 -2
- package/dist/clis/twitter/article.js +2 -2
- package/dist/clis/twitter/block.d.ts +1 -0
- package/dist/clis/twitter/block.js +88 -0
- package/dist/clis/twitter/delete.js +1 -1
- 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/hide-reply.d.ts +1 -0
- package/dist/clis/twitter/hide-reply.js +66 -0
- package/dist/clis/twitter/like.js +1 -1
- package/dist/clis/twitter/post.js +1 -1
- package/dist/clis/twitter/reply-dm.js +1 -1
- package/dist/clis/twitter/reply.js +2 -2
- package/dist/clis/twitter/search.js +1 -1
- package/dist/clis/twitter/thread.js +2 -2
- 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/twitter/trending.d.ts +1 -0
- package/dist/clis/twitter/trending.js +91 -0
- package/dist/clis/twitter/unblock.d.ts +1 -0
- package/dist/clis/twitter/unblock.js +71 -0
- package/dist/clis/v2ex/topic.yaml +1 -0
- package/dist/clis/weibo/hot.js +0 -1
- package/dist/clis/weread/book.js +1 -1
- package/dist/clis/weread/highlights.js +1 -1
- package/dist/clis/weread/notes.js +1 -1
- package/dist/clis/weread/search.js +1 -1
- package/dist/clis/wikipedia/random.d.ts +1 -0
- package/dist/clis/wikipedia/random.js +19 -0
- package/dist/clis/wikipedia/search.js +4 -4
- 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.d.ts +15 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +69 -5
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +82 -33
- package/dist/clis/xiaohongshu/creator-notes.js +35 -5
- package/dist/clis/xiaohongshu/creator-notes.test.js +37 -6
- package/dist/clis/xiaohongshu/creator-profile.js +0 -1
- package/dist/clis/xiaohongshu/creator-stats.js +0 -1
- package/dist/clis/xiaohongshu/download.js +2 -3
- package/dist/clis/xiaohongshu/feed.yaml +0 -1
- package/dist/clis/xiaohongshu/notifications.yaml +0 -1
- package/dist/clis/xiaohongshu/search.js +2 -2
- package/dist/clis/xiaohongshu/user.js +1 -2
- 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 -2
- package/dist/clis/youtube/search.js +1 -1
- package/dist/clis/youtube/transcript.js +1 -1
- package/dist/clis/youtube/video.js +1 -1
- package/dist/clis/zhihu/download.js +1 -2
- package/dist/clis/zhihu/question.js +1 -1
- package/dist/clis/zhihu/search.yaml +4 -3
- package/dist/commanderAdapter.d.ts +21 -0
- package/dist/commanderAdapter.js +117 -0
- package/dist/{engine.d.ts → discovery.d.ts} +6 -4
- package/dist/{engine.js → discovery.js} +93 -104
- package/dist/doctor.js +3 -1
- package/dist/doctor.test.js +46 -2
- package/dist/download/index.d.ts +2 -6
- package/dist/download/index.js +19 -46
- package/dist/engine.test.d.ts +0 -3
- package/dist/engine.test.js +80 -11
- package/dist/execution.d.ts +24 -0
- package/dist/execution.js +153 -0
- package/dist/explore.d.ts +76 -3
- package/dist/explore.js +132 -111
- package/dist/external-clis.yaml +48 -0
- package/dist/external.d.ts +7 -2
- package/dist/external.js +11 -14
- 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 +21 -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 +13 -0
- package/dist/registry.js +8 -1
- package/dist/runtime.d.ts +5 -0
- package/dist/runtime.js +8 -0
- package/dist/serialization.d.ts +34 -0
- package/dist/serialization.js +63 -0
- package/dist/synthesize.d.ts +94 -4
- package/dist/synthesize.js +5 -4
- package/dist/types.d.ts +43 -27
- package/dist/validate.js +8 -2
- package/docs/.vitepress/config.mts +20 -7
- package/docs/adapters/browser/arxiv.md +27 -0
- package/docs/adapters/browser/barchart.md +33 -0
- package/docs/adapters/browser/bilibili.md +9 -0
- package/docs/adapters/browser/bloomberg.md +70 -0
- package/docs/adapters/browser/chaoxing.md +39 -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 +53 -0
- package/docs/adapters/browser/hf.md +42 -0
- package/docs/adapters/browser/instagram.md +46 -0
- package/docs/adapters/browser/jike.md +45 -0
- package/docs/adapters/browser/jimeng.md +39 -0
- package/docs/adapters/browser/linux-do.md +45 -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/sinafinance.md +35 -0
- package/docs/adapters/browser/stackoverflow.md +35 -0
- package/docs/adapters/browser/steam.md +26 -0
- package/docs/adapters/browser/substack.md +38 -0
- package/docs/adapters/browser/tiktok.md +68 -0
- package/docs/adapters/browser/twitter.md +3 -0
- package/docs/adapters/browser/weread.md +48 -0
- package/docs/adapters/browser/wikipedia.md +39 -0
- package/docs/adapters/browser/xiaohongshu.md +5 -1
- 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 +5 -4
- 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 +14 -4
- package/docs/advanced/download.md +4 -4
- package/docs/developer/architecture.md +17 -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/check-doc-coverage.sh +69 -0
- package/scripts/clean-dist.cjs +13 -0
- package/scripts/copy-yaml.cjs +7 -0
- package/src/browser/cdp.ts +77 -32
- 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 +57 -20
- package/src/build-manifest.test.ts +70 -2
- package/src/build-manifest.ts +114 -40
- package/src/cli.ts +287 -139
- package/src/clis/apple-podcasts/commands.test.ts +95 -0
- package/src/clis/apple-podcasts/search.ts +2 -2
- package/src/clis/apple-podcasts/top.ts +12 -2
- package/src/clis/arxiv/search.ts +1 -1
- 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/dynamic.ts +1 -1
- package/src/clis/bilibili/favorite.ts +1 -1
- package/src/clis/bilibili/feed.ts +1 -1
- package/src/clis/bilibili/following.ts +2 -2
- package/src/clis/bilibili/history.ts +1 -1
- package/src/clis/bilibili/me.ts +1 -1
- package/src/clis/bilibili/ranking.ts +1 -1
- package/src/clis/bilibili/search.ts +3 -3
- package/src/clis/bilibili/subtitle.ts +2 -2
- package/src/clis/bilibili/user-videos.ts +2 -2
- package/src/{bilibili.ts → clis/bilibili/utils.ts} +1 -1
- package/src/clis/bloomberg/businessweek.ts +18 -0
- package/src/clis/bloomberg/economics.ts +18 -0
- package/src/clis/bloomberg/feeds.ts +16 -0
- package/src/clis/bloomberg/industries.ts +18 -0
- package/src/clis/bloomberg/main.ts +18 -0
- package/src/clis/bloomberg/markets.ts +18 -0
- package/src/clis/bloomberg/news.ts +136 -0
- package/src/clis/bloomberg/opinions.ts +18 -0
- package/src/clis/bloomberg/politics.ts +18 -0
- package/src/clis/bloomberg/tech.ts +18 -0
- package/src/clis/bloomberg/utils.test.ts +135 -0
- package/src/clis/bloomberg/utils.ts +429 -0
- package/src/clis/boss/batchgreet.ts +16 -108
- package/src/clis/boss/chatlist.ts +13 -27
- package/src/clis/boss/chatmsg.ts +16 -40
- package/src/clis/boss/common.ts +287 -0
- package/src/clis/boss/detail.ts +9 -55
- package/src/clis/boss/exchange.ts +15 -89
- package/src/clis/boss/greet.ts +25 -162
- 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 +14 -67
- package/src/clis/boss/send.ts +22 -162
- package/src/clis/boss/stats.ts +21 -76
- package/src/clis/chaoxing/assignments.ts +1 -1
- package/src/clis/chaoxing/exams.ts +1 -1
- package/src/{chaoxing.test.ts → clis/chaoxing/utils.test.ts} +1 -1
- package/src/{chaoxing.ts → clis/chaoxing/utils.ts} +1 -3
- package/src/clis/chatgpt/read.ts +1 -1
- package/src/clis/chatwise/export.ts +1 -1
- package/src/clis/chatwise/model.ts +2 -2
- package/src/clis/chatwise/screenshot.ts +1 -1
- package/src/clis/codex/export.ts +1 -1
- package/src/clis/codex/model.ts +2 -2
- package/src/clis/codex/screenshot.ts +1 -1
- package/src/clis/coupang/add-to-cart.ts +3 -4
- package/src/clis/coupang/search.ts +2 -4
- package/src/{coupang.test.ts → clis/coupang/utils.test.ts} +1 -1
- package/src/clis/ctrip/search.ts +1 -1
- package/src/clis/cursor/export.ts +1 -1
- package/src/clis/cursor/model.ts +2 -2
- package/src/clis/cursor/screenshot.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/comment.ts +2 -3
- package/src/clis/jike/create.ts +1 -2
- package/src/clis/jike/feed.ts +0 -1
- package/src/clis/jike/like.ts +1 -2
- package/src/clis/jike/notifications.ts +0 -1
- package/src/clis/jike/post.yaml +1 -0
- package/src/clis/jike/repost.ts +2 -3
- package/src/clis/jike/search.ts +2 -3
- package/src/clis/jike/topic.yaml +1 -0
- package/src/clis/jike/user.yaml +1 -0
- package/src/clis/jimeng/generate.yaml +1 -0
- package/src/clis/jimeng/history.yaml +0 -1
- package/src/clis/linkedin/search.ts +7 -7
- package/src/clis/linux-do/category.yaml +2 -0
- package/src/clis/linux-do/search.yaml +4 -3
- package/src/clis/linux-do/topic.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/notion/export.ts +1 -1
- package/src/clis/reddit/comment.ts +3 -4
- package/src/clis/reddit/read.ts +4 -5
- package/src/clis/reddit/save.ts +2 -3
- package/src/clis/reddit/saved.ts +0 -1
- package/src/clis/reddit/search.yaml +1 -0
- package/src/clis/reddit/subreddit.yaml +1 -0
- package/src/clis/reddit/subscribe.ts +1 -2
- package/src/clis/reddit/upvote.ts +2 -3
- package/src/clis/reddit/upvoted.ts +0 -1
- package/src/clis/reddit/user-comments.yaml +1 -0
- package/src/clis/reddit/user-posts.yaml +1 -0
- package/src/clis/reddit/user.yaml +1 -0
- package/src/clis/reuters/search.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/smzdm/search.ts +2 -3
- package/src/clis/stackoverflow/search.yaml +1 -0
- package/src/clis/steam/top-sellers.yaml +29 -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/accept.ts +2 -2
- package/src/clis/twitter/article.ts +2 -2
- package/src/clis/twitter/block.ts +92 -0
- package/src/clis/twitter/delete.ts +1 -1
- 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/hide-reply.ts +70 -0
- package/src/clis/twitter/like.ts +1 -1
- package/src/clis/twitter/post.ts +1 -1
- package/src/clis/twitter/reply-dm.ts +1 -1
- package/src/clis/twitter/reply.ts +2 -2
- package/src/clis/twitter/search.ts +1 -1
- package/src/clis/twitter/thread.ts +2 -2
- package/src/clis/twitter/timeline.test.ts +109 -0
- package/src/clis/twitter/timeline.ts +59 -19
- package/src/clis/twitter/trending.ts +113 -0
- package/src/clis/twitter/unblock.ts +75 -0
- package/src/clis/v2ex/topic.yaml +1 -0
- package/src/clis/weibo/hot.ts +0 -1
- package/src/clis/weread/book.ts +1 -1
- package/src/clis/weread/highlights.ts +1 -1
- package/src/clis/weread/notes.ts +1 -1
- package/src/clis/weread/search.ts +1 -1
- package/src/clis/wikipedia/random.ts +19 -0
- package/src/clis/wikipedia/search.ts +11 -5
- 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 +84 -33
- package/src/clis/xiaohongshu/creator-note-detail.ts +89 -5
- package/src/clis/xiaohongshu/creator-notes.test.ts +41 -6
- package/src/clis/xiaohongshu/creator-notes.ts +44 -5
- package/src/clis/xiaohongshu/creator-profile.ts +0 -1
- package/src/clis/xiaohongshu/creator-stats.ts +0 -1
- package/src/clis/xiaohongshu/download.ts +2 -3
- package/src/clis/xiaohongshu/feed.yaml +0 -1
- package/src/clis/xiaohongshu/notifications.yaml +0 -1
- package/src/clis/xiaohongshu/search.ts +2 -2
- package/src/clis/xiaohongshu/user.ts +1 -2
- 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 -2
- package/src/clis/youtube/search.ts +1 -1
- package/src/clis/youtube/transcript.ts +1 -1
- package/src/clis/youtube/video.ts +1 -1
- package/src/clis/zhihu/download.ts +1 -2
- package/src/clis/zhihu/question.ts +1 -1
- package/src/clis/zhihu/search.yaml +4 -3
- package/src/commanderAdapter.ts +120 -0
- package/src/discovery.ts +277 -0
- package/src/doctor.test.ts +59 -2
- package/src/doctor.ts +4 -2
- package/src/download/index.ts +21 -54
- package/src/engine.test.ts +85 -11
- package/src/execution.ts +164 -0
- package/src/explore.ts +211 -117
- package/src/external-clis.yaml +9 -0
- package/src/external.ts +15 -12
- 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 +29 -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 +24 -1
- package/src/runtime.ts +9 -0
- package/src/serialization.ts +79 -0
- package/src/synthesize.ts +102 -21
- package/src/types.ts +45 -13
- package/src/validate.ts +19 -4
- package/tests/e2e/browser-public.test.ts +36 -0
- package/tests/e2e/public-commands.test.ts +119 -1
- 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/twitter/trending.yaml +0 -46
- 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/docs/public/CNAME +0 -1
- 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/twitter/trending.yaml +0 -46
- 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/engine.ts +0 -274
- package/src/pipeline.ts +0 -8
- /package/dist/{bilibili.js → clis/bilibili/utils.js} +0 -0
- /package/dist/{chaoxing.test.d.ts → clis/bloomberg/businessweek.d.ts} +0 -0
- /package/dist/{coupang.test.d.ts → clis/bloomberg/economics.d.ts} +0 -0
- /package/dist/{coupang.d.ts → clis/coupang/utils.d.ts} +0 -0
- /package/dist/{coupang.js → clis/coupang/utils.js} +0 -0
- /package/src/{coupang.ts → clis/coupang/utils.ts} +0 -0
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { describe, it, expect, vi } from 'vitest';
|
|
6
6
|
import { executePipeline } from './index.js';
|
|
7
|
+
import { ConfigError } from '../errors.js';
|
|
7
8
|
import type { IPage } from '../types.js';
|
|
8
9
|
|
|
9
10
|
/** Create a minimal mock page for testing */
|
|
@@ -16,6 +17,7 @@ function createMockPage(overrides: Partial<IPage> = {}): IPage {
|
|
|
16
17
|
click: vi.fn(),
|
|
17
18
|
typeText: vi.fn(),
|
|
18
19
|
pressKey: vi.fn(),
|
|
20
|
+
getFormState: vi.fn().mockResolvedValue({}),
|
|
19
21
|
wait: vi.fn(),
|
|
20
22
|
tabs: vi.fn().mockResolvedValue([]),
|
|
21
23
|
closeTab: vi.fn(),
|
|
@@ -24,6 +26,7 @@ function createMockPage(overrides: Partial<IPage> = {}): IPage {
|
|
|
24
26
|
networkRequests: vi.fn().mockResolvedValue([]),
|
|
25
27
|
consoleMessages: vi.fn().mockResolvedValue(''),
|
|
26
28
|
scroll: vi.fn(),
|
|
29
|
+
scrollTo: vi.fn(),
|
|
27
30
|
autoScroll: vi.fn(),
|
|
28
31
|
installInterceptor: vi.fn(),
|
|
29
32
|
getInterceptedRequests: vi.fn().mockResolvedValue([]),
|
|
@@ -79,6 +82,32 @@ describe('executePipeline', () => {
|
|
|
79
82
|
]);
|
|
80
83
|
});
|
|
81
84
|
|
|
85
|
+
it('runs inline select inside map step', async () => {
|
|
86
|
+
const page = createMockPage({
|
|
87
|
+
evaluate: vi.fn().mockResolvedValue({
|
|
88
|
+
posts: [
|
|
89
|
+
{ title: 'First', rank: 1 },
|
|
90
|
+
{ title: 'Second', rank: 2 },
|
|
91
|
+
],
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
const result = await executePipeline(page, [
|
|
95
|
+
{ evaluate: 'test' },
|
|
96
|
+
{
|
|
97
|
+
map: {
|
|
98
|
+
select: 'posts',
|
|
99
|
+
title: '${{ item.title }}',
|
|
100
|
+
rank: '${{ item.rank }}',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
expect(result).toEqual([
|
|
106
|
+
{ title: 'First', rank: 1 },
|
|
107
|
+
{ title: 'Second', rank: 2 },
|
|
108
|
+
]);
|
|
109
|
+
});
|
|
110
|
+
|
|
82
111
|
it('executes limit step', async () => {
|
|
83
112
|
const page = createMockPage({
|
|
84
113
|
evaluate: vi.fn().mockResolvedValue([1, 2, 3, 4, 5]),
|
|
@@ -120,13 +149,13 @@ describe('executePipeline', () => {
|
|
|
120
149
|
expect(page.wait).toHaveBeenCalledWith(2);
|
|
121
150
|
});
|
|
122
151
|
|
|
123
|
-
it('
|
|
124
|
-
|
|
125
|
-
|
|
152
|
+
it('fails fast on unknown steps', async () => {
|
|
153
|
+
await expect(executePipeline(null, [
|
|
154
|
+
{ unknownStep: 'test' },
|
|
155
|
+
], { debug: true })).rejects.toBeInstanceOf(ConfigError);
|
|
156
|
+
await expect(executePipeline(null, [
|
|
126
157
|
{ unknownStep: 'test' },
|
|
127
|
-
], { debug: true });
|
|
128
|
-
expect(stderr).toHaveBeenCalledWith(expect.stringContaining('Unknown step'));
|
|
129
|
-
stderr.mockRestore();
|
|
158
|
+
], { debug: true })).rejects.toThrow('Unknown pipeline step "unknownStep"');
|
|
130
159
|
});
|
|
131
160
|
|
|
132
161
|
it('passes args through template rendering', async () => {
|
package/src/pipeline/executor.ts
CHANGED
|
@@ -6,20 +6,21 @@ import chalk from 'chalk';
|
|
|
6
6
|
import type { IPage } from '../types.js';
|
|
7
7
|
import { getStep, type StepHandler } from './registry.js';
|
|
8
8
|
import { log } from '../logger.js';
|
|
9
|
+
import { ConfigError } from '../errors.js';
|
|
9
10
|
|
|
10
11
|
export interface PipelineContext {
|
|
11
|
-
args?: Record<string,
|
|
12
|
+
args?: Record<string, unknown>;
|
|
12
13
|
debug?: boolean;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export async function executePipeline(
|
|
16
17
|
page: IPage | null,
|
|
17
|
-
pipeline:
|
|
18
|
+
pipeline: unknown[],
|
|
18
19
|
ctx: PipelineContext = {},
|
|
19
|
-
): Promise<
|
|
20
|
+
): Promise<unknown> {
|
|
20
21
|
const args = ctx.args ?? {};
|
|
21
22
|
const debug = ctx.debug ?? false;
|
|
22
|
-
let data:
|
|
23
|
+
let data: unknown = null;
|
|
23
24
|
const total = pipeline.length;
|
|
24
25
|
|
|
25
26
|
for (let i = 0; i < pipeline.length; i++) {
|
|
@@ -32,7 +33,10 @@ export async function executePipeline(
|
|
|
32
33
|
if (handler) {
|
|
33
34
|
data = await handler(page, params, data, args);
|
|
34
35
|
} else {
|
|
35
|
-
|
|
36
|
+
throw new ConfigError(
|
|
37
|
+
`Unknown pipeline step "${op}" at index ${i}.`,
|
|
38
|
+
'Check the YAML pipeline step name or register the custom step before execution.',
|
|
39
|
+
);
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
if (debug) debugStepResult(op, data);
|
|
@@ -41,7 +45,7 @@ export async function executePipeline(
|
|
|
41
45
|
return data;
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
function debugStepStart(stepNum: number, total: number, op: string, params:
|
|
48
|
+
function debugStepStart(stepNum: number, total: number, op: string, params: unknown): void {
|
|
45
49
|
let preview = '';
|
|
46
50
|
if (typeof params === 'string') {
|
|
47
51
|
preview = params.length <= 80 ? ` → ${params}` : ` → ${params.slice(0, 77)}...`;
|
|
@@ -51,7 +55,7 @@ function debugStepStart(stepNum: number, total: number, op: string, params: any)
|
|
|
51
55
|
log.step(stepNum, total, op, preview);
|
|
52
56
|
}
|
|
53
57
|
|
|
54
|
-
function debugStepResult(op: string, data:
|
|
58
|
+
function debugStepResult(op: string, data: unknown): void {
|
|
55
59
|
if (data === null || data === undefined) {
|
|
56
60
|
log.stepResult('(no data)');
|
|
57
61
|
} else if (Array.isArray(data)) {
|
package/src/pipeline/registry.ts
CHANGED
|
@@ -18,11 +18,11 @@ import { stepDownload } from './steps/download.js';
|
|
|
18
18
|
* TData is the type of the `data` state flowing into the step.
|
|
19
19
|
* TResult is the expected return type.
|
|
20
20
|
*/
|
|
21
|
-
export type StepHandler<TData =
|
|
21
|
+
export type StepHandler<TData = unknown, TResult = unknown, TParams = unknown> = (
|
|
22
22
|
page: IPage | null,
|
|
23
|
-
params:
|
|
23
|
+
params: TParams,
|
|
24
24
|
data: TData,
|
|
25
|
-
args: Record<string,
|
|
25
|
+
args: Record<string, unknown>
|
|
26
26
|
) => Promise<TResult>;
|
|
27
27
|
|
|
28
28
|
const _stepRegistry = new Map<string, StepHandler>();
|
|
@@ -6,19 +6,28 @@
|
|
|
6
6
|
import type { IPage } from '../../types.js';
|
|
7
7
|
import { render } from '../template.js';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
10
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function stepNavigate(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> {
|
|
14
|
+
if (isRecord(params) && 'url' in params) {
|
|
15
|
+
const url = String(render(params.url, { args, data }));
|
|
16
|
+
await page!.goto(url, { waitUntil: params.waitUntil as 'load' | 'none' | undefined, settleMs: typeof params.settleMs === 'number' ? params.settleMs : undefined });
|
|
17
|
+
} else {
|
|
18
|
+
const url = render(params, { args, data });
|
|
19
|
+
await page!.goto(String(url));
|
|
20
|
+
}
|
|
12
21
|
return data;
|
|
13
22
|
}
|
|
14
23
|
|
|
15
|
-
export async function stepClick(page: IPage | null, params:
|
|
24
|
+
export async function stepClick(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> {
|
|
16
25
|
await page!.click(String(render(params, { args, data })).replace(/^@/, ''));
|
|
17
26
|
return data;
|
|
18
27
|
}
|
|
19
28
|
|
|
20
|
-
export async function stepType(page: IPage | null, params:
|
|
21
|
-
if (
|
|
29
|
+
export async function stepType(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> {
|
|
30
|
+
if (isRecord(params)) {
|
|
22
31
|
const ref = String(render(params.ref ?? '', { args, data })).replace(/^@/, '');
|
|
23
32
|
const text = String(render(params.text ?? '', { args, data }));
|
|
24
33
|
await page!.typeText(ref, text);
|
|
@@ -27,32 +36,37 @@ export async function stepType(page: IPage | null, params: any, data: any, args:
|
|
|
27
36
|
return data;
|
|
28
37
|
}
|
|
29
38
|
|
|
30
|
-
export async function stepWait(page: IPage | null, params:
|
|
39
|
+
export async function stepWait(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> {
|
|
31
40
|
if (typeof params === 'number') await page!.wait(params);
|
|
32
|
-
else if (
|
|
41
|
+
else if (isRecord(params)) {
|
|
33
42
|
if ('text' in params) {
|
|
34
43
|
await page!.wait({
|
|
35
44
|
text: String(render(params.text, { args, data })),
|
|
36
|
-
timeout: params.timeout
|
|
45
|
+
timeout: typeof params.timeout === 'number' ? params.timeout : undefined,
|
|
37
46
|
});
|
|
38
47
|
} else if ('time' in params) await page!.wait(Number(params.time));
|
|
39
48
|
} else if (typeof params === 'string') await page!.wait(Number(render(params, { args, data })));
|
|
40
49
|
return data;
|
|
41
50
|
}
|
|
42
51
|
|
|
43
|
-
export async function stepPress(page: IPage | null, params:
|
|
52
|
+
export async function stepPress(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> {
|
|
44
53
|
await page!.pressKey(String(render(params, { args, data })));
|
|
45
54
|
return data;
|
|
46
55
|
}
|
|
47
56
|
|
|
48
|
-
export async function stepSnapshot(page: IPage | null, params:
|
|
49
|
-
const opts = (
|
|
50
|
-
return page!.snapshot({
|
|
57
|
+
export async function stepSnapshot(page: IPage | null, params: unknown, _data: unknown, _args: Record<string, unknown>): Promise<unknown> {
|
|
58
|
+
const opts = isRecord(params) ? params : {};
|
|
59
|
+
return page!.snapshot({
|
|
60
|
+
interactive: typeof opts.interactive === 'boolean' ? opts.interactive : false,
|
|
61
|
+
compact: typeof opts.compact === 'boolean' ? opts.compact : false,
|
|
62
|
+
maxDepth: typeof opts.max_depth === 'number' ? opts.max_depth : undefined,
|
|
63
|
+
raw: typeof opts.raw === 'boolean' ? opts.raw : false,
|
|
64
|
+
});
|
|
51
65
|
}
|
|
52
66
|
|
|
53
|
-
export async function stepEvaluate(page: IPage | null, params:
|
|
67
|
+
export async function stepEvaluate(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> {
|
|
54
68
|
const js = String(render(params, { args, data }));
|
|
55
|
-
let result = await page!.evaluate(js);
|
|
69
|
+
let result: unknown = await page!.evaluate(js);
|
|
56
70
|
// MCP may return JSON as a string — auto-parse it
|
|
57
71
|
if (typeof result === 'string') {
|
|
58
72
|
const trimmed = result.trim();
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
import type { IPage } from '../../types.js';
|
|
6
6
|
import { render } from '../template.js';
|
|
7
7
|
|
|
8
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
9
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
/** Simple async concurrency limiter */
|
|
9
13
|
async function mapConcurrent<T, R>(items: T[], limit: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]> {
|
|
10
14
|
const results: R[] = new Array(items.length);
|
|
@@ -25,9 +29,9 @@ async function mapConcurrent<T, R>(items: T[], limit: number, fn: (item: T, inde
|
|
|
25
29
|
/** Single URL fetch helper */
|
|
26
30
|
async function fetchSingle(
|
|
27
31
|
page: IPage | null, url: string, method: string,
|
|
28
|
-
queryParams: Record<string,
|
|
29
|
-
args: Record<string,
|
|
30
|
-
): Promise<
|
|
32
|
+
queryParams: Record<string, unknown>, headers: Record<string, unknown>,
|
|
33
|
+
args: Record<string, unknown>, data: unknown,
|
|
34
|
+
): Promise<unknown> {
|
|
31
35
|
const renderedParams: Record<string, string> = {};
|
|
32
36
|
for (const [k, v] of Object.entries(queryParams)) renderedParams[k] = String(render(v, { args, data }));
|
|
33
37
|
const renderedHeaders: Record<string, string> = {};
|
|
@@ -65,10 +69,10 @@ async function fetchSingle(
|
|
|
65
69
|
async function fetchBatchInBrowser(
|
|
66
70
|
page: IPage, urls: string[], method: string,
|
|
67
71
|
headers: Record<string, string>, concurrency: number,
|
|
68
|
-
): Promise<
|
|
72
|
+
): Promise<unknown[]> {
|
|
69
73
|
const headersJs = JSON.stringify(headers);
|
|
70
74
|
const urlsJs = JSON.stringify(urls);
|
|
71
|
-
return page.evaluate(`
|
|
75
|
+
return (await page.evaluate(`
|
|
72
76
|
async () => {
|
|
73
77
|
const urls = ${urlsJs};
|
|
74
78
|
const method = "${method}";
|
|
@@ -94,19 +98,20 @@ async function fetchBatchInBrowser(
|
|
|
94
98
|
await Promise.all(workers);
|
|
95
99
|
return results;
|
|
96
100
|
}
|
|
97
|
-
`);
|
|
101
|
+
`)) as unknown[];
|
|
98
102
|
}
|
|
99
103
|
|
|
100
|
-
export async function stepFetch(page: IPage | null, params:
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
const
|
|
104
|
-
const
|
|
104
|
+
export async function stepFetch(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> {
|
|
105
|
+
const paramObject = isRecord(params) ? params : {};
|
|
106
|
+
const urlOrObj = typeof params === 'string' ? params : (paramObject.url ?? '');
|
|
107
|
+
const method = typeof paramObject.method === 'string' ? paramObject.method : 'GET';
|
|
108
|
+
const queryParams = isRecord(paramObject.params) ? paramObject.params : {};
|
|
109
|
+
const headers = isRecord(paramObject.headers) ? paramObject.headers : {};
|
|
105
110
|
const urlTemplate = String(urlOrObj);
|
|
106
111
|
|
|
107
112
|
// Per-item fetch when data is array and URL references item
|
|
108
113
|
if (Array.isArray(data) && urlTemplate.includes('item')) {
|
|
109
|
-
const concurrency = typeof
|
|
114
|
+
const concurrency = typeof paramObject.concurrency === 'number' ? paramObject.concurrency : 5;
|
|
110
115
|
|
|
111
116
|
// Render all URLs upfront
|
|
112
117
|
const renderedHeaders: Record<string, string> = {};
|
|
@@ -114,7 +119,7 @@ export async function stepFetch(page: IPage | null, params: any, data: any, args
|
|
|
114
119
|
const renderedParams: Record<string, string> = {};
|
|
115
120
|
for (const [k, v] of Object.entries(queryParams)) renderedParams[k] = String(render(v, { args, data }));
|
|
116
121
|
|
|
117
|
-
const urls = data.map((item
|
|
122
|
+
const urls = data.map((item, index) => {
|
|
118
123
|
let url = String(render(urlTemplate, { args, data, item, index }));
|
|
119
124
|
if (Object.keys(renderedParams).length > 0) {
|
|
120
125
|
const qs = new URLSearchParams(renderedParams).toString();
|
|
@@ -2,14 +2,19 @@
|
|
|
2
2
|
* Pipeline steps: data transforms — select, map, filter, sort, limit.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import type { IPage } from '../../types.js';
|
|
5
6
|
import { render, evalExpr } from '../template.js';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
9
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function stepSelect(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> {
|
|
8
13
|
const pathStr = String(render(params, { args, data }));
|
|
9
14
|
if (data && typeof data === 'object') {
|
|
10
|
-
let current = data;
|
|
15
|
+
let current: unknown = data;
|
|
11
16
|
for (const part of pathStr.split('.')) {
|
|
12
|
-
if (
|
|
17
|
+
if (isRecord(current)) current = current[part];
|
|
13
18
|
else if (Array.isArray(current) && /^\d+$/.test(part)) current = current[parseInt(part, 10)];
|
|
14
19
|
else return null;
|
|
15
20
|
}
|
|
@@ -18,33 +23,53 @@ export async function stepSelect(_page: any, params: any, data: any, args: Recor
|
|
|
18
23
|
return data;
|
|
19
24
|
}
|
|
20
25
|
|
|
21
|
-
export async function stepMap(_page:
|
|
26
|
+
export async function stepMap(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> {
|
|
22
27
|
if (!data || typeof data !== 'object') return data;
|
|
23
|
-
let
|
|
24
|
-
|
|
25
|
-
|
|
28
|
+
let source: unknown = data;
|
|
29
|
+
|
|
30
|
+
// Support inline select: { map: { select: 'path', key: '${{ item.x }}' } }
|
|
31
|
+
if (isRecord(params) && 'select' in params) {
|
|
32
|
+
source = await stepSelect(null, params.select, data, args);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!source || typeof source !== 'object') return source;
|
|
36
|
+
|
|
37
|
+
let items: unknown[] = Array.isArray(source) ? source : [source];
|
|
38
|
+
if (isRecord(source) && Array.isArray(source.data)) items = source.data;
|
|
39
|
+
const result: Array<Record<string, unknown>> = [];
|
|
40
|
+
const templateParams = isRecord(params) ? params : {};
|
|
26
41
|
for (let i = 0; i < items.length; i++) {
|
|
27
42
|
const item = items[i];
|
|
28
|
-
const row: Record<string,
|
|
29
|
-
for (const [key, template] of Object.entries(
|
|
43
|
+
const row: Record<string, unknown> = {};
|
|
44
|
+
for (const [key, template] of Object.entries(templateParams)) {
|
|
45
|
+
if (key === 'select') continue;
|
|
46
|
+
row[key] = render(template, { args, data: source, item, index: i });
|
|
47
|
+
}
|
|
30
48
|
result.push(row);
|
|
31
49
|
}
|
|
32
50
|
return result;
|
|
33
51
|
}
|
|
34
52
|
|
|
35
|
-
export async function stepFilter(_page:
|
|
53
|
+
export async function stepFilter(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> {
|
|
36
54
|
if (!Array.isArray(data)) return data;
|
|
37
55
|
return data.filter((item, i) => evalExpr(String(params), { args, item, index: i }));
|
|
38
56
|
}
|
|
39
57
|
|
|
40
|
-
export async function stepSort(_page:
|
|
58
|
+
export async function stepSort(_page: IPage | null, params: unknown, data: unknown, _args: Record<string, unknown>): Promise<unknown> {
|
|
41
59
|
if (!Array.isArray(data)) return data;
|
|
42
|
-
const key =
|
|
43
|
-
const reverse =
|
|
44
|
-
return [...data].sort((a, b) => {
|
|
60
|
+
const key = isRecord(params) ? String(params.by ?? '') : String(params);
|
|
61
|
+
const reverse = isRecord(params) ? params.order === 'desc' : false;
|
|
62
|
+
return [...data].sort((a, b) => {
|
|
63
|
+
const left = isRecord(a) ? a[key] : undefined;
|
|
64
|
+
const right = isRecord(b) ? b[key] : undefined;
|
|
65
|
+
const va = left ?? '';
|
|
66
|
+
const vb = right ?? '';
|
|
67
|
+
const cmp = va < vb ? -1 : va > vb ? 1 : 0;
|
|
68
|
+
return reverse ? -cmp : cmp;
|
|
69
|
+
});
|
|
45
70
|
}
|
|
46
71
|
|
|
47
|
-
export async function stepLimit(_page:
|
|
72
|
+
export async function stepLimit(_page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> {
|
|
48
73
|
if (!Array.isArray(data)) return data;
|
|
49
74
|
return data.slice(0, Number(render(params, { args, data })));
|
|
50
75
|
}
|
|
@@ -57,6 +57,15 @@ describe('evalExpr', () => {
|
|
|
57
57
|
it('resolves simple path', () => {
|
|
58
58
|
expect(evalExpr('item.title', { item: { title: 'Test' } })).toBe('Test');
|
|
59
59
|
});
|
|
60
|
+
it('evaluates JS helper expressions', () => {
|
|
61
|
+
expect(evalExpr('encodeURIComponent(args.keyword)', { args: { keyword: 'hello world' } })).toBe('hello%20world');
|
|
62
|
+
});
|
|
63
|
+
it('evaluates ternary expressions', () => {
|
|
64
|
+
expect(evalExpr("args.kind === 'tech' ? 'technology' : args.kind", { args: { kind: 'tech' } })).toBe('technology');
|
|
65
|
+
});
|
|
66
|
+
it('evaluates method calls on values', () => {
|
|
67
|
+
expect(evalExpr("args.username.startsWith('@') ? args.username : '@' + args.username", { args: { username: 'alice' } })).toBe('@alice');
|
|
68
|
+
});
|
|
60
69
|
it('applies join filter', () => {
|
|
61
70
|
expect(evalExpr('item.tags | join(,)', { item: { tags: ['a', 'b', 'c'] } })).toBe('a,b,c');
|
|
62
71
|
});
|
|
@@ -104,6 +113,15 @@ describe('render', () => {
|
|
|
104
113
|
it('renders URL template', () => {
|
|
105
114
|
expect(render('https://api.example.com/search?q=${{ args.keyword }}', { args: { keyword: 'test' } })).toBe('https://api.example.com/search?q=test');
|
|
106
115
|
});
|
|
116
|
+
it('renders inline helper expressions', () => {
|
|
117
|
+
expect(render('https://example.com/search?q=${{ encodeURIComponent(args.keyword) }}', { args: { keyword: 'hello world' } })).toBe('https://example.com/search?q=hello%20world');
|
|
118
|
+
});
|
|
119
|
+
it('renders full multiline expressions', () => {
|
|
120
|
+
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');
|
|
121
|
+
});
|
|
122
|
+
it('renders block expressions with surrounding whitespace', () => {
|
|
123
|
+
expect(render("\n ${{ args.kind === 'tech' ? 'technology' : args.kind }}\n", { args: { kind: 'tech' } })).toBe('technology');
|
|
124
|
+
});
|
|
107
125
|
});
|
|
108
126
|
|
|
109
127
|
describe('normalizeEvaluateSource', () => {
|
package/src/pipeline/template.ts
CHANGED
|
@@ -3,20 +3,25 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export interface RenderContext {
|
|
6
|
-
args?: Record<string,
|
|
7
|
-
data?:
|
|
8
|
-
item?:
|
|
6
|
+
args?: Record<string, unknown>;
|
|
7
|
+
data?: unknown;
|
|
8
|
+
item?: unknown;
|
|
9
9
|
index?: number;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
13
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function render(template: unknown, ctx: RenderContext): unknown {
|
|
13
17
|
if (typeof template !== 'string') return template;
|
|
18
|
+
const trimmed = template.trim();
|
|
14
19
|
// Full expression: entire string is a single ${{ ... }}
|
|
15
20
|
// Use [^}] to prevent matching across }} boundaries (e.g. "${{ a }}-${{ b }}")
|
|
16
|
-
const fullMatch =
|
|
17
|
-
if (fullMatch && !
|
|
21
|
+
const fullMatch = trimmed.match(/^\$\{\{\s*([^}]*(?:\}[^}][^}]*)*)\s*\}\}$/);
|
|
22
|
+
if (fullMatch && !trimmed.includes('}}-') && !trimmed.includes('}}${{')) return evalExpr(fullMatch[1].trim(), ctx);
|
|
18
23
|
// Check if the entire string is a single expression (no other text around it)
|
|
19
|
-
const singleExpr =
|
|
24
|
+
const singleExpr = trimmed.match(/^\$\{\{\s*([\s\S]*?)\s*\}\}$/);
|
|
20
25
|
if (singleExpr) {
|
|
21
26
|
// Verify it's truly a single expression (no other ${{ inside)
|
|
22
27
|
const inner = singleExpr[1];
|
|
@@ -25,7 +30,7 @@ export function render(template: any, ctx: RenderContext): any {
|
|
|
25
30
|
return template.replace(/\$\{\{\s*(.*?)\s*\}\}/g, (_m, expr) => String(evalExpr(expr.trim(), ctx)));
|
|
26
31
|
}
|
|
27
32
|
|
|
28
|
-
export function evalExpr(expr: string, ctx: RenderContext):
|
|
33
|
+
export function evalExpr(expr: string, ctx: RenderContext): unknown {
|
|
29
34
|
const args = ctx.args ?? {};
|
|
30
35
|
const item = ctx.item ?? {};
|
|
31
36
|
const data = ctx.data;
|
|
@@ -68,7 +73,10 @@ export function evalExpr(expr: string, ctx: RenderContext): any {
|
|
|
68
73
|
return right.replace(/^['"]|['"]$/g, '');
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
|
|
76
|
+
const resolved = resolvePath(expr, { args, item, data, index });
|
|
77
|
+
if (resolved !== null && resolved !== undefined) return resolved;
|
|
78
|
+
|
|
79
|
+
return evalJsExpr(expr, { args, item, data, index });
|
|
72
80
|
}
|
|
73
81
|
|
|
74
82
|
/**
|
|
@@ -77,7 +85,7 @@ export function evalExpr(expr: string, ctx: RenderContext): any {
|
|
|
77
85
|
* default(val), join(sep), upper, lower, truncate(n), trim,
|
|
78
86
|
* replace(old,new), keys, length, first, last, json
|
|
79
87
|
*/
|
|
80
|
-
function applyFilter(filterExpr: string, value:
|
|
88
|
+
function applyFilter(filterExpr: string, value: unknown): unknown {
|
|
81
89
|
const match = filterExpr.match(/^(\w+)(?:\((.+)\))?$/);
|
|
82
90
|
if (!match) return value;
|
|
83
91
|
const [, name, rawArgs] = match;
|
|
@@ -145,32 +153,97 @@ function applyFilter(filterExpr: string, value: any): any {
|
|
|
145
153
|
const parts = value.split(/[/\\]/);
|
|
146
154
|
return parts[parts.length - 1] || value;
|
|
147
155
|
}
|
|
156
|
+
case 'urlencode':
|
|
157
|
+
return typeof value === 'string' ? encodeURIComponent(value) : value;
|
|
158
|
+
case 'urldecode':
|
|
159
|
+
return typeof value === 'string' ? decodeURIComponent(value) : value;
|
|
148
160
|
default:
|
|
149
161
|
return value;
|
|
150
162
|
}
|
|
151
163
|
}
|
|
152
164
|
|
|
153
|
-
export function resolvePath(pathStr: string, ctx: RenderContext):
|
|
165
|
+
export function resolvePath(pathStr: string, ctx: RenderContext): unknown {
|
|
154
166
|
const args = ctx.args ?? {};
|
|
155
167
|
const item = ctx.item ?? {};
|
|
156
168
|
const data = ctx.data;
|
|
157
169
|
const index = ctx.index ?? 0;
|
|
158
170
|
const parts = pathStr.split('.');
|
|
159
171
|
const rootName = parts[0];
|
|
160
|
-
let obj:
|
|
172
|
+
let obj: unknown;
|
|
173
|
+
let rest: string[];
|
|
161
174
|
if (rootName === 'args') { obj = args; rest = parts.slice(1); }
|
|
162
175
|
else if (rootName === 'item') { obj = item; rest = parts.slice(1); }
|
|
163
176
|
else if (rootName === 'data') { obj = data; rest = parts.slice(1); }
|
|
164
177
|
else if (rootName === 'index') return index;
|
|
165
178
|
else { obj = item; rest = parts; }
|
|
166
179
|
for (const part of rest) {
|
|
167
|
-
if (
|
|
180
|
+
if (isRecord(obj)) obj = obj[part];
|
|
168
181
|
else if (Array.isArray(obj) && /^\d+$/.test(part)) obj = obj[parseInt(part, 10)];
|
|
169
182
|
else return null;
|
|
170
183
|
}
|
|
171
184
|
return obj;
|
|
172
185
|
}
|
|
173
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Evaluate arbitrary JS expressions as a last-resort fallback.
|
|
189
|
+
*
|
|
190
|
+
* ⚠️ SECURITY NOTE: Uses `new Function()` to execute the expression.
|
|
191
|
+
* This is acceptable here because:
|
|
192
|
+
* 1. YAML adapters are authored by trusted repo contributors only.
|
|
193
|
+
* 2. The expression runs in the same Node.js process (no sandbox).
|
|
194
|
+
* 3. Only a curated set of globals is exposed (no require/import/process/fs).
|
|
195
|
+
* If opencli ever loads untrusted third-party adapters, this MUST be replaced
|
|
196
|
+
* with a proper sandboxed evaluator.
|
|
197
|
+
*/
|
|
198
|
+
function evalJsExpr(expr: string, ctx: RenderContext): unknown {
|
|
199
|
+
// Guard against absurdly long expressions that could indicate injection.
|
|
200
|
+
if (expr.length > 2000) return undefined;
|
|
201
|
+
|
|
202
|
+
const args = ctx.args ?? {};
|
|
203
|
+
const item = ctx.item ?? {};
|
|
204
|
+
const data = ctx.data;
|
|
205
|
+
const index = ctx.index ?? 0;
|
|
206
|
+
|
|
207
|
+
try {
|
|
208
|
+
const fn = new Function(
|
|
209
|
+
'args',
|
|
210
|
+
'item',
|
|
211
|
+
'data',
|
|
212
|
+
'index',
|
|
213
|
+
'encodeURIComponent',
|
|
214
|
+
'decodeURIComponent',
|
|
215
|
+
'JSON',
|
|
216
|
+
'Math',
|
|
217
|
+
'Number',
|
|
218
|
+
'String',
|
|
219
|
+
'Boolean',
|
|
220
|
+
'Array',
|
|
221
|
+
'Object',
|
|
222
|
+
'Date',
|
|
223
|
+
`"use strict"; return (${expr});`,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
return fn(
|
|
227
|
+
args,
|
|
228
|
+
item,
|
|
229
|
+
data,
|
|
230
|
+
index,
|
|
231
|
+
encodeURIComponent,
|
|
232
|
+
decodeURIComponent,
|
|
233
|
+
JSON,
|
|
234
|
+
Math,
|
|
235
|
+
Number,
|
|
236
|
+
String,
|
|
237
|
+
Boolean,
|
|
238
|
+
Array,
|
|
239
|
+
Object,
|
|
240
|
+
Date,
|
|
241
|
+
);
|
|
242
|
+
} catch {
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
174
247
|
/**
|
|
175
248
|
* Normalize JavaScript source for browser evaluate() calls.
|
|
176
249
|
*/
|
|
@@ -58,6 +58,19 @@ describe('stepMap', () => {
|
|
|
58
58
|
it('returns null/undefined as-is', async () => {
|
|
59
59
|
expect(await stepMap(null, { x: '${{ item.x }}' }, null, {})).toBeNull();
|
|
60
60
|
});
|
|
61
|
+
|
|
62
|
+
it('supports inline select before mapping', async () => {
|
|
63
|
+
const result = await stepMap(null, {
|
|
64
|
+
select: 'posts',
|
|
65
|
+
title: '${{ item.title }}',
|
|
66
|
+
rank: '${{ index + 1 }}',
|
|
67
|
+
}, { posts: [{ title: 'One' }, { title: 'Two' }] }, {});
|
|
68
|
+
|
|
69
|
+
expect(result).toEqual([
|
|
70
|
+
{ title: 'One', rank: 1 },
|
|
71
|
+
{ title: 'Two', rank: 2 },
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
61
74
|
});
|
|
62
75
|
|
|
63
76
|
describe('stepFilter', () => {
|
|
@@ -75,12 +88,12 @@ describe('stepFilter', () => {
|
|
|
75
88
|
describe('stepSort', () => {
|
|
76
89
|
it('sorts ascending by key', async () => {
|
|
77
90
|
const result = await stepSort(null, 'score', SAMPLE_DATA, {});
|
|
78
|
-
expect(result.map((r
|
|
91
|
+
expect((result as typeof SAMPLE_DATA).map((r) => r.title)).toEqual(['Alpha', 'Gamma', 'Beta']);
|
|
79
92
|
});
|
|
80
93
|
|
|
81
94
|
it('sorts descending', async () => {
|
|
82
95
|
const result = await stepSort(null, { by: 'score', order: 'desc' }, SAMPLE_DATA, {});
|
|
83
|
-
expect(result.map((r
|
|
96
|
+
expect((result as typeof SAMPLE_DATA).map((r) => r.title)).toEqual(['Beta', 'Gamma', 'Alpha']);
|
|
84
97
|
});
|
|
85
98
|
|
|
86
99
|
it('does not mutate original', async () => {
|