@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
|
@@ -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 () => {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for plugin management: install, uninstall, list.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
6
|
+
import * as fs from 'node:fs';
|
|
7
|
+
import * as path from 'node:path';
|
|
8
|
+
import { PLUGINS_DIR } from './discovery.js';
|
|
9
|
+
import { listPlugins, uninstallPlugin, _parseSource } from './plugin.js';
|
|
10
|
+
|
|
11
|
+
describe('parseSource', () => {
|
|
12
|
+
it('parses github:user/repo format', () => {
|
|
13
|
+
const result = _parseSource('github:ByteYue/opencli-plugin-github-trending');
|
|
14
|
+
expect(result).toEqual({
|
|
15
|
+
cloneUrl: 'https://github.com/ByteYue/opencli-plugin-github-trending.git',
|
|
16
|
+
name: 'github-trending',
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('parses https URL format', () => {
|
|
21
|
+
const result = _parseSource('https://github.com/ByteYue/opencli-plugin-hot-digest');
|
|
22
|
+
expect(result).toEqual({
|
|
23
|
+
cloneUrl: 'https://github.com/ByteYue/opencli-plugin-hot-digest.git',
|
|
24
|
+
name: 'hot-digest',
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('strips opencli-plugin- prefix from name', () => {
|
|
29
|
+
const result = _parseSource('github:user/opencli-plugin-my-tool');
|
|
30
|
+
expect(result!.name).toBe('my-tool');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('keeps name without prefix', () => {
|
|
34
|
+
const result = _parseSource('github:user/awesome-cli');
|
|
35
|
+
expect(result!.name).toBe('awesome-cli');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('returns null for invalid source', () => {
|
|
39
|
+
expect(_parseSource('invalid')).toBeNull();
|
|
40
|
+
expect(_parseSource('npm:some-package')).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('listPlugins', () => {
|
|
45
|
+
const testDir = path.join(PLUGINS_DIR, '__test-list-plugin__');
|
|
46
|
+
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
try { fs.rmSync(testDir, { recursive: true }); } catch {}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('lists installed plugins', () => {
|
|
52
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
53
|
+
fs.writeFileSync(path.join(testDir, 'hello.yaml'), 'site: test\nname: hello\n');
|
|
54
|
+
|
|
55
|
+
const plugins = listPlugins();
|
|
56
|
+
const found = plugins.find(p => p.name === '__test-list-plugin__');
|
|
57
|
+
expect(found).toBeDefined();
|
|
58
|
+
expect(found!.commands).toContain('hello');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('returns empty array when no plugins dir', () => {
|
|
62
|
+
// listPlugins should handle missing dir gracefully
|
|
63
|
+
const plugins = listPlugins();
|
|
64
|
+
expect(Array.isArray(plugins)).toBe(true);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('uninstallPlugin', () => {
|
|
69
|
+
const testDir = path.join(PLUGINS_DIR, '__test-uninstall__');
|
|
70
|
+
|
|
71
|
+
afterEach(() => {
|
|
72
|
+
try { fs.rmSync(testDir, { recursive: true }); } catch {}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('removes plugin directory', () => {
|
|
76
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
77
|
+
fs.writeFileSync(path.join(testDir, 'test.yaml'), 'site: test');
|
|
78
|
+
|
|
79
|
+
uninstallPlugin('__test-uninstall__');
|
|
80
|
+
expect(fs.existsSync(testDir)).toBe(false);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('throws for non-existent plugin', () => {
|
|
84
|
+
expect(() => uninstallPlugin('__nonexistent__')).toThrow('not installed');
|
|
85
|
+
});
|
|
86
|
+
});
|