@jackwener/opencli 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-extension.yml +3 -3
- package/.github/workflows/ci.yml +6 -6
- package/.github/workflows/doc-check.yml +3 -3
- package/.github/workflows/e2e-headed.yml +2 -2
- package/.github/workflows/pkg-pr-new.yml +2 -2
- package/.github/workflows/release.yml +3 -3
- package/.github/workflows/security.yml +2 -2
- package/CONTRIBUTING.md +39 -1
- package/README.md +13 -10
- package/README.zh-CN.md +43 -17
- package/SKILL.md +10 -5
- package/dist/browser/cdp.d.ts +4 -4
- package/dist/browser/cdp.js +39 -16
- package/dist/browser/daemon-client.d.ts +4 -2
- package/dist/browser/daemon-client.js +17 -4
- package/dist/browser/dom-helpers.js +38 -7
- package/dist/browser/dom-snapshot.d.ts +86 -0
- package/dist/browser/dom-snapshot.js +729 -0
- package/dist/browser/dom-snapshot.test.d.ts +11 -0
- package/dist/browser/dom-snapshot.test.js +212 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/mcp.js +3 -1
- package/dist/browser/page.d.ts +14 -24
- package/dist/browser/page.js +46 -6
- package/dist/build-manifest.d.ts +11 -4
- package/dist/build-manifest.js +59 -21
- package/dist/build-manifest.test.js +58 -2
- package/dist/cli-manifest.json +3856 -1509
- package/dist/cli.js +66 -0
- package/dist/clis/barchart/greeks.js +1 -1
- package/dist/clis/barchart/options.js +1 -1
- package/dist/clis/barchart/quote.js +1 -1
- package/dist/clis/bilibili/download.js +1 -1
- package/dist/clis/bilibili/following.js +1 -1
- package/dist/clis/bilibili/subtitle.js +1 -1
- package/dist/clis/bilibili/user-videos.js +1 -1
- package/dist/clis/boss/batchgreet.js +10 -97
- package/dist/clis/boss/chatlist.js +8 -25
- package/dist/clis/boss/chatmsg.js +11 -42
- package/dist/clis/boss/common.d.ts +92 -0
- package/dist/clis/boss/common.js +223 -0
- package/dist/clis/boss/detail.js +7 -49
- package/dist/clis/boss/exchange.js +13 -79
- package/dist/clis/boss/greet.js +18 -145
- package/dist/clis/boss/invite.js +26 -121
- package/dist/clis/boss/joblist.js +6 -31
- package/dist/clis/boss/mark.js +12 -85
- package/dist/clis/boss/recommend.js +10 -49
- package/dist/clis/boss/resume.js +18 -118
- package/dist/clis/boss/search.js +12 -60
- package/dist/clis/boss/send.js +17 -151
- package/dist/clis/boss/stats.js +18 -69
- package/dist/clis/coupang/add-to-cart.js +1 -1
- package/dist/clis/devto/tag.yaml +34 -0
- package/dist/clis/devto/top.yaml +29 -0
- package/dist/clis/devto/user.yaml +33 -0
- package/dist/clis/douban/book-hot.d.ts +1 -0
- package/dist/clis/douban/book-hot.js +14 -0
- package/dist/clis/douban/marks.d.ts +1 -0
- package/dist/clis/douban/marks.js +115 -0
- package/dist/clis/douban/movie-hot.d.ts +1 -0
- package/dist/clis/douban/movie-hot.js +14 -0
- package/dist/clis/douban/reviews.d.ts +1 -0
- package/dist/clis/douban/reviews.js +106 -0
- package/dist/clis/douban/search.d.ts +1 -0
- package/dist/clis/douban/search.js +16 -0
- package/dist/clis/douban/shared.d.ts +4 -0
- package/dist/clis/douban/shared.js +155 -0
- package/dist/clis/douban/subject.yaml +76 -0
- package/dist/clis/douban/top250.yaml +70 -0
- package/dist/clis/douban/utils.d.ts +35 -0
- package/dist/clis/douban/utils.js +48 -0
- package/dist/clis/facebook/add-friend.yaml +43 -0
- package/dist/clis/facebook/events.yaml +44 -0
- package/dist/clis/facebook/feed.yaml +63 -0
- package/dist/clis/facebook/friends.yaml +42 -0
- package/dist/clis/facebook/groups.yaml +50 -0
- package/dist/clis/facebook/join-group.yaml +44 -0
- package/dist/clis/facebook/memories.yaml +39 -0
- package/dist/clis/facebook/notifications.yaml +40 -0
- package/dist/clis/facebook/profile.yaml +37 -0
- package/dist/clis/facebook/search.yaml +46 -0
- package/dist/clis/google/news.d.ts +5 -0
- package/dist/clis/google/news.js +58 -0
- package/dist/clis/google/search.d.ts +10 -0
- package/dist/clis/google/search.js +127 -0
- package/dist/clis/google/suggest.d.ts +5 -0
- package/dist/clis/google/suggest.js +34 -0
- package/dist/clis/google/trends.d.ts +5 -0
- package/dist/clis/google/trends.js +38 -0
- package/dist/clis/google/utils.d.ts +9 -0
- package/dist/clis/google/utils.js +23 -0
- package/dist/clis/google/utils.test.d.ts +1 -0
- package/dist/clis/google/utils.test.js +75 -0
- package/dist/clis/grok/ask.d.ts +14 -0
- package/dist/clis/grok/ask.js +257 -65
- package/dist/clis/grok/ask.test.d.ts +1 -0
- package/dist/clis/grok/ask.test.js +36 -0
- package/dist/clis/instagram/comment.yaml +52 -0
- package/dist/clis/instagram/explore.yaml +43 -0
- package/dist/clis/instagram/follow.yaml +41 -0
- package/dist/clis/instagram/followers.yaml +51 -0
- package/dist/clis/instagram/following.yaml +51 -0
- package/dist/clis/instagram/like.yaml +46 -0
- package/dist/clis/instagram/profile.yaml +42 -0
- package/dist/clis/instagram/save.yaml +46 -0
- package/dist/clis/instagram/saved.yaml +40 -0
- package/dist/clis/instagram/search.yaml +43 -0
- package/dist/clis/instagram/unfollow.yaml +38 -0
- package/dist/clis/instagram/unlike.yaml +46 -0
- package/dist/clis/instagram/unsave.yaml +46 -0
- package/dist/clis/instagram/user.yaml +54 -0
- package/dist/clis/jike/repost.js +1 -1
- package/dist/clis/jimeng/generate.yaml +1 -0
- package/dist/clis/linux-do/category.yaml +1 -0
- package/dist/clis/lobsters/active.yaml +29 -0
- package/dist/clis/lobsters/hot.yaml +29 -0
- package/dist/clis/lobsters/newest.yaml +29 -0
- package/dist/clis/lobsters/tag.yaml +34 -0
- package/dist/clis/medium/feed.d.ts +1 -0
- package/dist/clis/medium/feed.js +15 -0
- package/dist/clis/medium/search.d.ts +1 -0
- package/dist/clis/medium/search.js +15 -0
- package/dist/clis/medium/shared.d.ts +5 -0
- package/dist/clis/medium/shared.js +78 -0
- package/dist/clis/medium/user.d.ts +1 -0
- package/dist/clis/medium/user.js +15 -0
- package/dist/clis/reddit/comment.js +1 -1
- package/dist/clis/reddit/read.js +1 -1
- package/dist/clis/reddit/save.js +1 -1
- package/dist/clis/reddit/subreddit.yaml +1 -0
- package/dist/clis/reddit/subscribe.js +1 -1
- package/dist/clis/reddit/upvote.js +1 -1
- package/dist/clis/sinablog/article.d.ts +1 -0
- package/dist/clis/sinablog/article.js +14 -0
- package/dist/clis/sinablog/hot.d.ts +1 -0
- package/dist/clis/sinablog/hot.js +14 -0
- package/dist/clis/sinablog/search.d.ts +1 -0
- package/dist/clis/sinablog/search.js +51 -0
- package/dist/clis/sinablog/shared.d.ts +7 -0
- package/dist/clis/sinablog/shared.js +187 -0
- package/dist/clis/sinablog/user.d.ts +1 -0
- package/dist/clis/sinablog/user.js +15 -0
- package/dist/clis/substack/feed.d.ts +1 -0
- package/dist/clis/substack/feed.js +15 -0
- package/dist/clis/substack/publication.d.ts +1 -0
- package/dist/clis/substack/publication.js +15 -0
- package/dist/clis/substack/search.d.ts +1 -0
- package/dist/clis/substack/search.js +77 -0
- package/dist/clis/substack/shared.d.ts +4 -0
- package/dist/clis/substack/shared.js +129 -0
- package/dist/clis/tiktok/comment.yaml +66 -0
- package/dist/clis/tiktok/explore.yaml +39 -0
- package/dist/clis/tiktok/follow.yaml +39 -0
- package/dist/clis/tiktok/following.yaml +46 -0
- package/dist/clis/tiktok/friends.yaml +47 -0
- package/dist/clis/tiktok/like.yaml +38 -0
- package/dist/clis/tiktok/live.yaml +51 -0
- package/dist/clis/tiktok/notifications.yaml +52 -0
- package/dist/clis/tiktok/profile.yaml +45 -0
- package/dist/clis/tiktok/save.yaml +34 -0
- package/dist/clis/tiktok/search.yaml +46 -0
- package/dist/clis/tiktok/unfollow.yaml +44 -0
- package/dist/clis/tiktok/unlike.yaml +38 -0
- package/dist/clis/tiktok/unsave.yaml +36 -0
- package/dist/clis/tiktok/user.yaml +44 -0
- package/dist/clis/twitter/download.d.ts +1 -1
- package/dist/clis/twitter/download.js +3 -3
- package/dist/clis/twitter/followers.js +1 -1
- package/dist/clis/twitter/following.js +1 -1
- package/dist/clis/twitter/thread.js +1 -1
- package/dist/clis/twitter/timeline.d.ts +23 -0
- package/dist/clis/twitter/timeline.js +42 -14
- package/dist/clis/twitter/timeline.test.d.ts +1 -0
- package/dist/clis/twitter/timeline.test.js +102 -0
- package/dist/clis/wikipedia/random.d.ts +1 -0
- package/dist/clis/wikipedia/random.js +19 -0
- package/dist/clis/wikipedia/search.js +3 -3
- package/dist/clis/wikipedia/summary.js +4 -9
- package/dist/clis/wikipedia/trending.d.ts +1 -0
- package/dist/clis/wikipedia/trending.js +35 -0
- package/dist/clis/wikipedia/utils.d.ts +28 -0
- package/dist/clis/wikipedia/utils.js +13 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
- package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
- package/dist/clis/xiaohongshu/download.js +1 -1
- package/dist/clis/xueqiu/earnings-date.yaml +69 -0
- package/dist/clis/xueqiu/search.yaml +2 -1
- package/dist/clis/xueqiu/stock.yaml +2 -0
- package/dist/clis/yahoo-finance/quote.js +1 -1
- package/dist/commanderAdapter.js +13 -7
- package/dist/daemon.js +21 -0
- package/dist/discovery.d.ts +8 -0
- package/dist/discovery.js +105 -19
- package/dist/doctor.js +3 -1
- package/dist/doctor.test.js +46 -2
- package/dist/engine.test.d.ts +0 -3
- package/dist/engine.test.js +74 -6
- package/dist/execution.d.ts +4 -2
- package/dist/execution.js +31 -7
- package/dist/explore.d.ts +76 -3
- package/dist/explore.js +11 -4
- package/dist/generate.d.ts +41 -2
- package/dist/generate.js +5 -4
- package/dist/main.js +2 -1
- package/dist/pipeline/executor.d.ts +4 -2
- package/dist/pipeline/executor.js +54 -15
- package/dist/pipeline/executor.test.js +33 -6
- package/dist/pipeline/registry.d.ts +1 -1
- package/dist/pipeline/steps/browser.d.ts +7 -7
- package/dist/pipeline/steps/browser.js +15 -7
- package/dist/pipeline/steps/fetch.d.ts +1 -1
- package/dist/pipeline/steps/fetch.js +11 -7
- package/dist/pipeline/steps/transform.d.ts +6 -5
- package/dist/pipeline/steps/transform.js +30 -9
- package/dist/pipeline/template.d.ts +6 -6
- package/dist/pipeline/template.js +43 -5
- package/dist/pipeline/template.test.js +18 -0
- package/dist/pipeline/transform.test.js +11 -0
- package/dist/plugin.d.ts +31 -0
- package/dist/plugin.js +216 -0
- package/dist/plugin.test.d.ts +4 -0
- package/dist/plugin.test.js +76 -0
- package/dist/registry-api.d.ts +11 -0
- package/dist/registry-api.js +9 -0
- package/dist/registry.d.ts +11 -0
- package/dist/registry.js +6 -1
- package/dist/synthesize.d.ts +94 -4
- package/dist/synthesize.js +5 -4
- package/dist/types.d.ts +39 -26
- package/dist/validate.js +8 -2
- package/docs/.vitepress/config.mts +6 -4
- package/docs/adapters/browser/barchart.md +6 -5
- package/docs/adapters/browser/bilibili.md +9 -0
- package/docs/adapters/browser/devto.md +35 -0
- package/docs/adapters/browser/douban.md +38 -0
- package/docs/adapters/browser/facebook.md +36 -0
- package/docs/adapters/browser/google.md +62 -0
- package/docs/adapters/browser/grok.md +26 -8
- package/docs/adapters/browser/instagram.md +46 -0
- package/docs/adapters/browser/lobsters.md +32 -0
- package/docs/adapters/browser/medium.md +32 -0
- package/docs/adapters/browser/reddit.md +9 -0
- package/docs/adapters/browser/sinablog.md +36 -0
- package/docs/adapters/browser/substack.md +38 -0
- package/docs/adapters/browser/tiktok.md +68 -0
- package/docs/adapters/browser/wikipedia.md +11 -2
- package/docs/adapters/browser/xueqiu.md +10 -0
- package/docs/adapters/browser/yahoo-finance.md +6 -5
- package/docs/adapters/desktop/antigravity.md +6 -0
- package/docs/adapters/desktop/chatgpt.md +2 -1
- package/docs/adapters/desktop/codex.md +5 -1
- package/docs/adapters/desktop/cursor.md +4 -0
- package/docs/adapters/desktop/discord.md +7 -7
- package/docs/adapters/index.md +1 -4
- package/docs/guide/getting-started.md +1 -0
- package/docs/guide/plugins.md +153 -0
- package/docs/zh/guide/plugins.md +107 -0
- package/extension/dist/background.js +91 -23
- package/extension/src/background.ts +82 -29
- package/extension/src/cdp.ts +42 -1
- package/package.json +10 -5
- package/scripts/clean-dist.cjs +13 -0
- package/src/browser/cdp.ts +71 -31
- package/src/browser/daemon-client.ts +21 -5
- package/src/browser/dom-helpers.ts +38 -7
- package/src/browser/dom-snapshot.test.ts +249 -0
- package/src/browser/dom-snapshot.ts +770 -0
- package/src/browser/index.ts +2 -0
- package/src/browser/mcp.ts +3 -1
- package/src/browser/page.ts +57 -21
- package/src/build-manifest.test.ts +70 -2
- package/src/build-manifest.ts +94 -26
- package/src/cli.ts +71 -2
- package/src/clis/barchart/greeks.ts +1 -1
- package/src/clis/barchart/options.ts +1 -1
- package/src/clis/barchart/quote.ts +1 -1
- package/src/clis/bilibili/download.ts +1 -1
- package/src/clis/bilibili/following.ts +1 -1
- package/src/clis/bilibili/subtitle.ts +1 -1
- package/src/clis/bilibili/user-videos.ts +1 -1
- package/src/clis/boss/batchgreet.ts +14 -106
- package/src/clis/boss/chatlist.ts +12 -26
- package/src/clis/boss/chatmsg.ts +16 -40
- package/src/clis/boss/common.ts +287 -0
- package/src/clis/boss/detail.ts +8 -54
- package/src/clis/boss/exchange.ts +15 -89
- package/src/clis/boss/greet.ts +23 -160
- package/src/clis/boss/invite.ts +36 -133
- package/src/clis/boss/joblist.ts +7 -36
- package/src/clis/boss/mark.ts +13 -94
- package/src/clis/boss/recommend.ts +12 -57
- package/src/clis/boss/resume.ts +19 -124
- package/src/clis/boss/search.ts +13 -66
- package/src/clis/boss/send.ts +21 -161
- package/src/clis/boss/stats.ts +19 -74
- package/src/clis/coupang/add-to-cart.ts +1 -1
- package/src/clis/devto/tag.yaml +34 -0
- package/src/clis/devto/top.yaml +29 -0
- package/src/clis/devto/user.yaml +33 -0
- package/src/clis/douban/book-hot.ts +15 -0
- package/src/clis/douban/marks.ts +135 -0
- package/src/clis/douban/movie-hot.ts +15 -0
- package/src/clis/douban/reviews.ts +127 -0
- package/src/clis/douban/search.ts +17 -0
- package/src/clis/douban/shared.ts +165 -0
- package/src/clis/douban/subject.yaml +76 -0
- package/src/clis/douban/top250.yaml +70 -0
- package/src/clis/douban/utils.ts +81 -0
- package/src/clis/facebook/add-friend.yaml +43 -0
- package/src/clis/facebook/events.yaml +44 -0
- package/src/clis/facebook/feed.yaml +63 -0
- package/src/clis/facebook/friends.yaml +42 -0
- package/src/clis/facebook/groups.yaml +50 -0
- package/src/clis/facebook/join-group.yaml +44 -0
- package/src/clis/facebook/memories.yaml +39 -0
- package/src/clis/facebook/notifications.yaml +40 -0
- package/src/clis/facebook/profile.yaml +37 -0
- package/src/clis/facebook/search.yaml +46 -0
- package/src/clis/google/news.ts +66 -0
- package/src/clis/google/search.ts +133 -0
- package/src/clis/google/suggest.ts +40 -0
- package/src/clis/google/trends.ts +44 -0
- package/src/clis/google/utils.test.ts +82 -0
- package/src/clis/google/utils.ts +24 -0
- package/src/clis/grok/ask.test.ts +53 -0
- package/src/clis/grok/ask.ts +300 -69
- package/src/clis/instagram/comment.yaml +52 -0
- package/src/clis/instagram/explore.yaml +43 -0
- package/src/clis/instagram/follow.yaml +41 -0
- package/src/clis/instagram/followers.yaml +51 -0
- package/src/clis/instagram/following.yaml +51 -0
- package/src/clis/instagram/like.yaml +46 -0
- package/src/clis/instagram/profile.yaml +42 -0
- package/src/clis/instagram/save.yaml +46 -0
- package/src/clis/instagram/saved.yaml +40 -0
- package/src/clis/instagram/search.yaml +43 -0
- package/src/clis/instagram/unfollow.yaml +38 -0
- package/src/clis/instagram/unlike.yaml +46 -0
- package/src/clis/instagram/unsave.yaml +46 -0
- package/src/clis/instagram/user.yaml +54 -0
- package/src/clis/jike/repost.ts +1 -1
- package/src/clis/jimeng/generate.yaml +1 -0
- package/src/clis/linux-do/category.yaml +1 -0
- package/src/clis/lobsters/active.yaml +29 -0
- package/src/clis/lobsters/hot.yaml +29 -0
- package/src/clis/lobsters/newest.yaml +29 -0
- package/src/clis/lobsters/tag.yaml +34 -0
- package/src/clis/medium/feed.ts +16 -0
- package/src/clis/medium/search.ts +16 -0
- package/src/clis/medium/shared.ts +83 -0
- package/src/clis/medium/user.ts +16 -0
- package/src/clis/reddit/comment.ts +1 -1
- package/src/clis/reddit/read.ts +1 -1
- package/src/clis/reddit/save.ts +1 -1
- package/src/clis/reddit/subreddit.yaml +1 -0
- package/src/clis/reddit/subscribe.ts +1 -1
- package/src/clis/reddit/upvote.ts +1 -1
- package/src/clis/sinablog/article.ts +15 -0
- package/src/clis/sinablog/hot.ts +15 -0
- package/src/clis/sinablog/search.ts +56 -0
- package/src/clis/sinablog/shared.ts +198 -0
- package/src/clis/sinablog/user.ts +16 -0
- package/src/clis/substack/feed.ts +16 -0
- package/src/clis/substack/publication.ts +16 -0
- package/src/clis/substack/search.ts +91 -0
- package/src/clis/substack/shared.ts +132 -0
- package/src/clis/tiktok/comment.yaml +66 -0
- package/src/clis/tiktok/explore.yaml +39 -0
- package/src/clis/tiktok/follow.yaml +39 -0
- package/src/clis/tiktok/following.yaml +46 -0
- package/src/clis/tiktok/friends.yaml +47 -0
- package/src/clis/tiktok/like.yaml +38 -0
- package/src/clis/tiktok/live.yaml +51 -0
- package/src/clis/tiktok/notifications.yaml +52 -0
- package/src/clis/tiktok/profile.yaml +45 -0
- package/src/clis/tiktok/save.yaml +34 -0
- package/src/clis/tiktok/search.yaml +46 -0
- package/src/clis/tiktok/unfollow.yaml +44 -0
- package/src/clis/tiktok/unlike.yaml +38 -0
- package/src/clis/tiktok/unsave.yaml +36 -0
- package/src/clis/tiktok/user.yaml +44 -0
- package/src/clis/twitter/download.ts +3 -3
- package/src/clis/twitter/followers.ts +1 -1
- package/src/clis/twitter/following.ts +1 -1
- package/src/clis/twitter/thread.ts +1 -1
- package/src/clis/twitter/timeline.test.ts +109 -0
- package/src/clis/twitter/timeline.ts +59 -19
- package/src/clis/wikipedia/random.ts +19 -0
- package/src/clis/wikipedia/search.ts +10 -4
- package/src/clis/wikipedia/summary.ts +4 -9
- package/src/clis/wikipedia/trending.ts +41 -0
- package/src/clis/wikipedia/utils.ts +31 -0
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
- package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
- package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
- package/src/clis/xiaohongshu/download.ts +1 -1
- package/src/clis/xueqiu/earnings-date.yaml +69 -0
- package/src/clis/xueqiu/search.yaml +2 -1
- package/src/clis/xueqiu/stock.yaml +2 -0
- package/src/clis/yahoo-finance/quote.ts +1 -1
- package/src/commanderAdapter.ts +17 -10
- package/src/daemon.ts +23 -0
- package/src/discovery.ts +134 -24
- package/src/doctor.test.ts +59 -2
- package/src/doctor.ts +4 -2
- package/src/engine.test.ts +79 -6
- package/src/execution.ts +42 -16
- package/src/explore.ts +77 -9
- package/src/generate.ts +58 -9
- package/src/main.ts +2 -1
- package/src/pipeline/executor.test.ts +35 -6
- package/src/pipeline/executor.ts +68 -19
- package/src/pipeline/registry.ts +3 -3
- package/src/pipeline/steps/browser.ts +24 -15
- package/src/pipeline/steps/fetch.ts +18 -13
- package/src/pipeline/steps/transform.ts +40 -15
- package/src/pipeline/template.test.ts +18 -0
- package/src/pipeline/template.ts +86 -13
- package/src/pipeline/transform.test.ts +15 -2
- package/src/plugin.test.ts +86 -0
- package/src/plugin.ts +254 -0
- package/src/registry-api.ts +12 -0
- package/src/registry.ts +19 -1
- package/src/synthesize.ts +102 -21
- package/src/types.ts +44 -12
- package/src/validate.ts +19 -4
- package/tests/e2e/browser-public.test.ts +11 -0
- package/tests/e2e/public-commands.test.ts +64 -0
- package/dist/clis/feishu/new.d.ts +0 -1
- package/dist/clis/feishu/new.js +0 -27
- package/dist/clis/feishu/read.d.ts +0 -1
- package/dist/clis/feishu/read.js +0 -40
- package/dist/clis/feishu/search.d.ts +0 -1
- package/dist/clis/feishu/search.js +0 -30
- package/dist/clis/feishu/send.d.ts +0 -1
- package/dist/clis/feishu/send.js +0 -39
- package/dist/clis/feishu/status.d.ts +0 -1
- package/dist/clis/feishu/status.js +0 -28
- package/dist/clis/neteasemusic/like.d.ts +0 -1
- package/dist/clis/neteasemusic/like.js +0 -25
- package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
- package/dist/clis/neteasemusic/lyrics.js +0 -47
- package/dist/clis/neteasemusic/next.d.ts +0 -1
- package/dist/clis/neteasemusic/next.js +0 -26
- package/dist/clis/neteasemusic/play.d.ts +0 -1
- package/dist/clis/neteasemusic/play.js +0 -26
- package/dist/clis/neteasemusic/playing.d.ts +0 -1
- package/dist/clis/neteasemusic/playing.js +0 -59
- package/dist/clis/neteasemusic/playlist.d.ts +0 -1
- package/dist/clis/neteasemusic/playlist.js +0 -46
- package/dist/clis/neteasemusic/prev.d.ts +0 -1
- package/dist/clis/neteasemusic/prev.js +0 -25
- package/dist/clis/neteasemusic/search.d.ts +0 -1
- package/dist/clis/neteasemusic/search.js +0 -52
- package/dist/clis/neteasemusic/status.d.ts +0 -1
- package/dist/clis/neteasemusic/status.js +0 -16
- package/dist/clis/neteasemusic/volume.d.ts +0 -1
- package/dist/clis/neteasemusic/volume.js +0 -54
- package/dist/clis/wechat/chats.d.ts +0 -1
- package/dist/clis/wechat/chats.js +0 -28
- package/dist/clis/wechat/contacts.d.ts +0 -1
- package/dist/clis/wechat/contacts.js +0 -28
- package/dist/clis/wechat/read.d.ts +0 -1
- package/dist/clis/wechat/read.js +0 -58
- package/dist/clis/wechat/search.d.ts +0 -1
- package/dist/clis/wechat/search.js +0 -31
- package/dist/clis/wechat/send.d.ts +0 -1
- package/dist/clis/wechat/send.js +0 -42
- package/dist/clis/wechat/status.d.ts +0 -1
- package/dist/clis/wechat/status.js +0 -29
- package/dist/pipeline.d.ts +0 -7
- package/dist/pipeline.js +0 -7
- package/docs/adapters/browser/github.md +0 -26
- package/docs/adapters/desktop/feishu.md +0 -20
- package/docs/adapters/desktop/neteasemusic.md +0 -31
- package/docs/adapters/desktop/wechat.md +0 -28
- package/src/clis/antigravity/README.md +0 -5
- package/src/clis/antigravity/README.zh-CN.md +0 -51
- package/src/clis/chaoxing/README.md +0 -14
- package/src/clis/chaoxing/README.zh-CN.md +0 -35
- package/src/clis/chatgpt/README.md +0 -5
- package/src/clis/chatgpt/README.zh-CN.md +0 -44
- package/src/clis/chatwise/README.md +0 -5
- package/src/clis/chatwise/README.zh-CN.md +0 -38
- package/src/clis/codex/README.md +0 -5
- package/src/clis/codex/README.zh-CN.md +0 -33
- package/src/clis/cursor/README.md +0 -5
- package/src/clis/cursor/README.zh-CN.md +0 -33
- package/src/clis/discord-app/README.md +0 -5
- package/src/clis/discord-app/README.zh-CN.md +0 -28
- package/src/clis/feishu/README.md +0 -5
- package/src/clis/feishu/README.zh-CN.md +0 -20
- package/src/clis/feishu/new.ts +0 -32
- package/src/clis/feishu/read.ts +0 -48
- package/src/clis/feishu/search.ts +0 -35
- package/src/clis/feishu/send.ts +0 -46
- package/src/clis/feishu/status.ts +0 -34
- package/src/clis/neteasemusic/README.md +0 -5
- package/src/clis/neteasemusic/README.zh-CN.md +0 -31
- package/src/clis/neteasemusic/like.ts +0 -28
- package/src/clis/neteasemusic/lyrics.ts +0 -53
- package/src/clis/neteasemusic/next.ts +0 -30
- package/src/clis/neteasemusic/play.ts +0 -30
- package/src/clis/neteasemusic/playing.ts +0 -62
- package/src/clis/neteasemusic/playlist.ts +0 -51
- package/src/clis/neteasemusic/prev.ts +0 -29
- package/src/clis/neteasemusic/search.ts +0 -58
- package/src/clis/neteasemusic/status.ts +0 -18
- package/src/clis/neteasemusic/volume.ts +0 -61
- package/src/clis/notion/README.md +0 -5
- package/src/clis/notion/README.zh-CN.md +0 -29
- package/src/clis/wechat/README.md +0 -5
- package/src/clis/wechat/README.zh-CN.md +0 -28
- package/src/clis/wechat/chats.ts +0 -33
- package/src/clis/wechat/contacts.ts +0 -33
- package/src/clis/wechat/read.ts +0 -72
- package/src/clis/wechat/search.ts +0 -36
- package/src/clis/wechat/send.ts +0 -49
- package/src/clis/wechat/status.ts +0 -35
- package/src/pipeline.ts +0 -8
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pipeline template engine: ${{ ... }} expression rendering.
|
|
3
3
|
*/
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
4
7
|
export function render(template, ctx) {
|
|
5
8
|
if (typeof template !== 'string')
|
|
6
9
|
return template;
|
|
10
|
+
const trimmed = template.trim();
|
|
7
11
|
// Full expression: entire string is a single ${{ ... }}
|
|
8
12
|
// Use [^}] to prevent matching across }} boundaries (e.g. "${{ a }}-${{ b }}")
|
|
9
|
-
const fullMatch =
|
|
10
|
-
if (fullMatch && !
|
|
13
|
+
const fullMatch = trimmed.match(/^\$\{\{\s*([^}]*(?:\}[^}][^}]*)*)\s*\}\}$/);
|
|
14
|
+
if (fullMatch && !trimmed.includes('}}-') && !trimmed.includes('}}${{'))
|
|
11
15
|
return evalExpr(fullMatch[1].trim(), ctx);
|
|
12
16
|
// Check if the entire string is a single expression (no other text around it)
|
|
13
|
-
const singleExpr =
|
|
17
|
+
const singleExpr = trimmed.match(/^\$\{\{\s*([\s\S]*?)\s*\}\}$/);
|
|
14
18
|
if (singleExpr) {
|
|
15
19
|
// Verify it's truly a single expression (no other ${{ inside)
|
|
16
20
|
const inner = singleExpr[1];
|
|
@@ -62,7 +66,10 @@ export function evalExpr(expr, ctx) {
|
|
|
62
66
|
const right = orMatch[2].trim();
|
|
63
67
|
return right.replace(/^['"]|['"]$/g, '');
|
|
64
68
|
}
|
|
65
|
-
|
|
69
|
+
const resolved = resolvePath(expr, { args, item, data, index });
|
|
70
|
+
if (resolved !== null && resolved !== undefined)
|
|
71
|
+
return resolved;
|
|
72
|
+
return evalJsExpr(expr, { args, item, data, index });
|
|
66
73
|
}
|
|
67
74
|
/**
|
|
68
75
|
* Apply a named filter to a value.
|
|
@@ -142,6 +149,10 @@ function applyFilter(filterExpr, value) {
|
|
|
142
149
|
const parts = value.split(/[/\\]/);
|
|
143
150
|
return parts[parts.length - 1] || value;
|
|
144
151
|
}
|
|
152
|
+
case 'urlencode':
|
|
153
|
+
return typeof value === 'string' ? encodeURIComponent(value) : value;
|
|
154
|
+
case 'urldecode':
|
|
155
|
+
return typeof value === 'string' ? decodeURIComponent(value) : value;
|
|
145
156
|
default:
|
|
146
157
|
return value;
|
|
147
158
|
}
|
|
@@ -174,7 +185,7 @@ export function resolvePath(pathStr, ctx) {
|
|
|
174
185
|
rest = parts;
|
|
175
186
|
}
|
|
176
187
|
for (const part of rest) {
|
|
177
|
-
if (
|
|
188
|
+
if (isRecord(obj))
|
|
178
189
|
obj = obj[part];
|
|
179
190
|
else if (Array.isArray(obj) && /^\d+$/.test(part))
|
|
180
191
|
obj = obj[parseInt(part, 10)];
|
|
@@ -183,6 +194,33 @@ export function resolvePath(pathStr, ctx) {
|
|
|
183
194
|
}
|
|
184
195
|
return obj;
|
|
185
196
|
}
|
|
197
|
+
/**
|
|
198
|
+
* Evaluate arbitrary JS expressions as a last-resort fallback.
|
|
199
|
+
*
|
|
200
|
+
* ⚠️ SECURITY NOTE: Uses `new Function()` to execute the expression.
|
|
201
|
+
* This is acceptable here because:
|
|
202
|
+
* 1. YAML adapters are authored by trusted repo contributors only.
|
|
203
|
+
* 2. The expression runs in the same Node.js process (no sandbox).
|
|
204
|
+
* 3. Only a curated set of globals is exposed (no require/import/process/fs).
|
|
205
|
+
* If opencli ever loads untrusted third-party adapters, this MUST be replaced
|
|
206
|
+
* with a proper sandboxed evaluator.
|
|
207
|
+
*/
|
|
208
|
+
function evalJsExpr(expr, ctx) {
|
|
209
|
+
// Guard against absurdly long expressions that could indicate injection.
|
|
210
|
+
if (expr.length > 2000)
|
|
211
|
+
return undefined;
|
|
212
|
+
const args = ctx.args ?? {};
|
|
213
|
+
const item = ctx.item ?? {};
|
|
214
|
+
const data = ctx.data;
|
|
215
|
+
const index = ctx.index ?? 0;
|
|
216
|
+
try {
|
|
217
|
+
const fn = new Function('args', 'item', 'data', 'index', 'encodeURIComponent', 'decodeURIComponent', 'JSON', 'Math', 'Number', 'String', 'Boolean', 'Array', 'Object', 'Date', `"use strict"; return (${expr});`);
|
|
218
|
+
return fn(args, item, data, index, encodeURIComponent, decodeURIComponent, JSON, Math, Number, String, Boolean, Array, Object, Date);
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return undefined;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
186
224
|
/**
|
|
187
225
|
* Normalize JavaScript source for browser evaluate() calls.
|
|
188
226
|
*/
|
|
@@ -54,6 +54,15 @@ describe('evalExpr', () => {
|
|
|
54
54
|
it('resolves simple path', () => {
|
|
55
55
|
expect(evalExpr('item.title', { item: { title: 'Test' } })).toBe('Test');
|
|
56
56
|
});
|
|
57
|
+
it('evaluates JS helper expressions', () => {
|
|
58
|
+
expect(evalExpr('encodeURIComponent(args.keyword)', { args: { keyword: 'hello world' } })).toBe('hello%20world');
|
|
59
|
+
});
|
|
60
|
+
it('evaluates ternary expressions', () => {
|
|
61
|
+
expect(evalExpr("args.kind === 'tech' ? 'technology' : args.kind", { args: { kind: 'tech' } })).toBe('technology');
|
|
62
|
+
});
|
|
63
|
+
it('evaluates method calls on values', () => {
|
|
64
|
+
expect(evalExpr("args.username.startsWith('@') ? args.username : '@' + args.username", { args: { username: 'alice' } })).toBe('@alice');
|
|
65
|
+
});
|
|
57
66
|
it('applies join filter', () => {
|
|
58
67
|
expect(evalExpr('item.tags | join(,)', { item: { tags: ['a', 'b', 'c'] } })).toBe('a,b,c');
|
|
59
68
|
});
|
|
@@ -100,6 +109,15 @@ describe('render', () => {
|
|
|
100
109
|
it('renders URL template', () => {
|
|
101
110
|
expect(render('https://api.example.com/search?q=${{ args.keyword }}', { args: { keyword: 'test' } })).toBe('https://api.example.com/search?q=test');
|
|
102
111
|
});
|
|
112
|
+
it('renders inline helper expressions', () => {
|
|
113
|
+
expect(render('https://example.com/search?q=${{ encodeURIComponent(args.keyword) }}', { args: { keyword: 'hello world' } })).toBe('https://example.com/search?q=hello%20world');
|
|
114
|
+
});
|
|
115
|
+
it('renders full multiline expressions', () => {
|
|
116
|
+
expect(render("${{\n args.topic ? `https://medium.com/tag/${args.topic}` : 'https://medium.com/tag/technology'\n}}", { args: { topic: 'ai' } })).toBe('https://medium.com/tag/ai');
|
|
117
|
+
});
|
|
118
|
+
it('renders block expressions with surrounding whitespace', () => {
|
|
119
|
+
expect(render("\n ${{ args.kind === 'tech' ? 'technology' : args.kind }}\n", { args: { kind: 'tech' } })).toBe('technology');
|
|
120
|
+
});
|
|
103
121
|
});
|
|
104
122
|
describe('normalizeEvaluateSource', () => {
|
|
105
123
|
it('wraps bare expression', () => {
|
|
@@ -49,6 +49,17 @@ describe('stepMap', () => {
|
|
|
49
49
|
it('returns null/undefined as-is', async () => {
|
|
50
50
|
expect(await stepMap(null, { x: '${{ item.x }}' }, null, {})).toBeNull();
|
|
51
51
|
});
|
|
52
|
+
it('supports inline select before mapping', async () => {
|
|
53
|
+
const result = await stepMap(null, {
|
|
54
|
+
select: 'posts',
|
|
55
|
+
title: '${{ item.title }}',
|
|
56
|
+
rank: '${{ index + 1 }}',
|
|
57
|
+
}, { posts: [{ title: 'One' }, { title: 'Two' }] }, {});
|
|
58
|
+
expect(result).toEqual([
|
|
59
|
+
{ title: 'One', rank: 1 },
|
|
60
|
+
{ title: 'Two', rank: 2 },
|
|
61
|
+
]);
|
|
62
|
+
});
|
|
52
63
|
});
|
|
53
64
|
describe('stepFilter', () => {
|
|
54
65
|
it('filters by expression', async () => {
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin management: install, uninstall, and list plugins.
|
|
3
|
+
*
|
|
4
|
+
* Plugins live in ~/.opencli/plugins/<name>/.
|
|
5
|
+
* Install source format: "github:user/repo"
|
|
6
|
+
*/
|
|
7
|
+
export interface PluginInfo {
|
|
8
|
+
name: string;
|
|
9
|
+
path: string;
|
|
10
|
+
commands: string[];
|
|
11
|
+
source?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Install a plugin from a source.
|
|
15
|
+
* Currently supports "github:user/repo" format (git clone wrapper).
|
|
16
|
+
*/
|
|
17
|
+
export declare function installPlugin(source: string): string;
|
|
18
|
+
/**
|
|
19
|
+
* Uninstall a plugin by name.
|
|
20
|
+
*/
|
|
21
|
+
export declare function uninstallPlugin(name: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* List all installed plugins.
|
|
24
|
+
*/
|
|
25
|
+
export declare function listPlugins(): PluginInfo[];
|
|
26
|
+
/** Parse a plugin source string into clone URL and name */
|
|
27
|
+
declare function parseSource(source: string): {
|
|
28
|
+
cloneUrl: string;
|
|
29
|
+
name: string;
|
|
30
|
+
} | null;
|
|
31
|
+
export { parseSource as _parseSource };
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin management: install, uninstall, and list plugins.
|
|
3
|
+
*
|
|
4
|
+
* Plugins live in ~/.opencli/plugins/<name>/.
|
|
5
|
+
* Install source format: "github:user/repo"
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import { execSync, execFileSync } from 'node:child_process';
|
|
10
|
+
import { PLUGINS_DIR } from './discovery.js';
|
|
11
|
+
import { log } from './logger.js';
|
|
12
|
+
/**
|
|
13
|
+
* Install a plugin from a source.
|
|
14
|
+
* Currently supports "github:user/repo" format (git clone wrapper).
|
|
15
|
+
*/
|
|
16
|
+
export function installPlugin(source) {
|
|
17
|
+
const parsed = parseSource(source);
|
|
18
|
+
if (!parsed) {
|
|
19
|
+
throw new Error(`Invalid plugin source: "${source}"\n` +
|
|
20
|
+
`Supported formats:\n` +
|
|
21
|
+
` github:user/repo\n` +
|
|
22
|
+
` https://github.com/user/repo`);
|
|
23
|
+
}
|
|
24
|
+
const { cloneUrl, name } = parsed;
|
|
25
|
+
const targetDir = path.join(PLUGINS_DIR, name);
|
|
26
|
+
if (fs.existsSync(targetDir)) {
|
|
27
|
+
throw new Error(`Plugin "${name}" is already installed at ${targetDir}`);
|
|
28
|
+
}
|
|
29
|
+
// Ensure plugins directory exists
|
|
30
|
+
fs.mkdirSync(PLUGINS_DIR, { recursive: true });
|
|
31
|
+
try {
|
|
32
|
+
execFileSync('git', ['clone', '--depth', '1', cloneUrl, targetDir], {
|
|
33
|
+
encoding: 'utf-8',
|
|
34
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
throw new Error(`Failed to clone plugin: ${err.message}`);
|
|
39
|
+
}
|
|
40
|
+
// If the plugin has a package.json, run npm install for regular deps,
|
|
41
|
+
// then symlink the host opencli into node_modules for peerDep resolution.
|
|
42
|
+
const pkgJsonPath = path.join(targetDir, 'package.json');
|
|
43
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
44
|
+
try {
|
|
45
|
+
execFileSync('npm', ['install', '--omit=dev'], {
|
|
46
|
+
cwd: targetDir,
|
|
47
|
+
encoding: 'utf-8',
|
|
48
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
// Non-fatal: npm install may fail if no real deps
|
|
53
|
+
}
|
|
54
|
+
// Symlink host opencli into plugin's node_modules so TS plugins
|
|
55
|
+
// can resolve '@jackwener/opencli/registry' against the running host.
|
|
56
|
+
// This is more reliable than depending on the npm-published version
|
|
57
|
+
// which may lag behind the local installation.
|
|
58
|
+
linkHostOpencli(targetDir);
|
|
59
|
+
// Transpile TS plugin files to JS so they work in production mode
|
|
60
|
+
// (node cannot load .ts files directly without tsx).
|
|
61
|
+
transpilePluginTs(targetDir);
|
|
62
|
+
}
|
|
63
|
+
return name;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Uninstall a plugin by name.
|
|
67
|
+
*/
|
|
68
|
+
export function uninstallPlugin(name) {
|
|
69
|
+
const targetDir = path.join(PLUGINS_DIR, name);
|
|
70
|
+
if (!fs.existsSync(targetDir)) {
|
|
71
|
+
throw new Error(`Plugin "${name}" is not installed.`);
|
|
72
|
+
}
|
|
73
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* List all installed plugins.
|
|
77
|
+
*/
|
|
78
|
+
export function listPlugins() {
|
|
79
|
+
if (!fs.existsSync(PLUGINS_DIR))
|
|
80
|
+
return [];
|
|
81
|
+
const entries = fs.readdirSync(PLUGINS_DIR, { withFileTypes: true });
|
|
82
|
+
const plugins = [];
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
if (!entry.isDirectory())
|
|
85
|
+
continue;
|
|
86
|
+
const pluginDir = path.join(PLUGINS_DIR, entry.name);
|
|
87
|
+
const commands = scanPluginCommands(pluginDir);
|
|
88
|
+
const source = getPluginSource(pluginDir);
|
|
89
|
+
plugins.push({
|
|
90
|
+
name: entry.name,
|
|
91
|
+
path: pluginDir,
|
|
92
|
+
commands,
|
|
93
|
+
source,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return plugins;
|
|
97
|
+
}
|
|
98
|
+
/** Scan a plugin directory for command files */
|
|
99
|
+
function scanPluginCommands(dir) {
|
|
100
|
+
try {
|
|
101
|
+
const files = fs.readdirSync(dir);
|
|
102
|
+
const names = new Set(files
|
|
103
|
+
.filter(f => f.endsWith('.yaml') || f.endsWith('.yml') ||
|
|
104
|
+
(f.endsWith('.ts') && !f.endsWith('.d.ts') && !f.endsWith('.test.ts')) ||
|
|
105
|
+
(f.endsWith('.js') && !f.endsWith('.d.js')))
|
|
106
|
+
.map(f => path.basename(f, path.extname(f))));
|
|
107
|
+
return [...names];
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Get git remote origin URL */
|
|
114
|
+
function getPluginSource(dir) {
|
|
115
|
+
try {
|
|
116
|
+
return execSync('git config --get remote.origin.url', {
|
|
117
|
+
cwd: dir,
|
|
118
|
+
encoding: 'utf-8',
|
|
119
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
120
|
+
}).trim();
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Parse a plugin source string into clone URL and name */
|
|
127
|
+
function parseSource(source) {
|
|
128
|
+
// github:user/repo
|
|
129
|
+
const githubMatch = source.match(/^github:([\w.-]+)\/([\w.-]+)$/);
|
|
130
|
+
if (githubMatch) {
|
|
131
|
+
const [, user, repo] = githubMatch;
|
|
132
|
+
const name = repo.replace(/^opencli-plugin-/, '');
|
|
133
|
+
return {
|
|
134
|
+
cloneUrl: `https://github.com/${user}/${repo}.git`,
|
|
135
|
+
name,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
// https://github.com/user/repo (or .git)
|
|
139
|
+
const urlMatch = source.match(/^https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?$/);
|
|
140
|
+
if (urlMatch) {
|
|
141
|
+
const [, user, repo] = urlMatch;
|
|
142
|
+
const name = repo.replace(/^opencli-plugin-/, '');
|
|
143
|
+
return {
|
|
144
|
+
cloneUrl: `https://github.com/${user}/${repo}.git`,
|
|
145
|
+
name,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Symlink the host opencli package into a plugin's node_modules.
|
|
152
|
+
* This ensures TS plugins resolve '@jackwener/opencli/registry' against
|
|
153
|
+
* the running host installation rather than a stale npm-published version.
|
|
154
|
+
*/
|
|
155
|
+
function linkHostOpencli(pluginDir) {
|
|
156
|
+
try {
|
|
157
|
+
// Determine the host opencli package root from this module's location.
|
|
158
|
+
// Both dev (tsx src/plugin.ts) and prod (node dist/plugin.js) are one level
|
|
159
|
+
// deep, so path.dirname + '..' always gives us the package root.
|
|
160
|
+
const thisFile = new URL(import.meta.url).pathname;
|
|
161
|
+
const hostRoot = path.resolve(path.dirname(thisFile), '..');
|
|
162
|
+
const targetLink = path.join(pluginDir, 'node_modules', '@jackwener', 'opencli');
|
|
163
|
+
// Remove existing (npm-installed copy or stale symlink)
|
|
164
|
+
if (fs.existsSync(targetLink)) {
|
|
165
|
+
fs.rmSync(targetLink, { recursive: true, force: true });
|
|
166
|
+
}
|
|
167
|
+
// Ensure parent directory exists
|
|
168
|
+
fs.mkdirSync(path.dirname(targetLink), { recursive: true });
|
|
169
|
+
// Create symlink
|
|
170
|
+
fs.symlinkSync(hostRoot, targetLink, 'dir');
|
|
171
|
+
log.debug(`Linked host opencli into plugin: ${targetLink} → ${hostRoot}`);
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
log.warn(`Failed to link host opencli into plugin: ${err.message}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Transpile TS plugin files to JS so they work in production mode.
|
|
179
|
+
* Uses esbuild from the host opencli's node_modules for fast single-file transpilation.
|
|
180
|
+
*/
|
|
181
|
+
function transpilePluginTs(pluginDir) {
|
|
182
|
+
try {
|
|
183
|
+
// Resolve esbuild binary from the host opencli's node_modules
|
|
184
|
+
const thisFile = new URL(import.meta.url).pathname;
|
|
185
|
+
const hostRoot = path.resolve(path.dirname(thisFile), '..');
|
|
186
|
+
const esbuildBin = path.join(hostRoot, 'node_modules', '.bin', 'esbuild');
|
|
187
|
+
if (!fs.existsSync(esbuildBin)) {
|
|
188
|
+
log.debug('esbuild not found in host node_modules, skipping TS transpilation');
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const files = fs.readdirSync(pluginDir);
|
|
192
|
+
const tsFiles = files.filter(f => f.endsWith('.ts') && !f.endsWith('.d.ts') && !f.endsWith('.test.ts'));
|
|
193
|
+
for (const tsFile of tsFiles) {
|
|
194
|
+
const jsFile = tsFile.replace(/\.ts$/, '.js');
|
|
195
|
+
const jsPath = path.join(pluginDir, jsFile);
|
|
196
|
+
// Skip if .js already exists (plugin may ship pre-compiled)
|
|
197
|
+
if (fs.existsSync(jsPath))
|
|
198
|
+
continue;
|
|
199
|
+
try {
|
|
200
|
+
execFileSync(esbuildBin, [tsFile, `--outfile=${jsFile}`, '--format=esm', '--platform=node'], {
|
|
201
|
+
cwd: pluginDir,
|
|
202
|
+
encoding: 'utf-8',
|
|
203
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
204
|
+
});
|
|
205
|
+
log.debug(`Transpiled plugin file: ${tsFile} → ${jsFile}`);
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
log.warn(`Failed to transpile ${tsFile}: ${err.message}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Non-fatal: skip transpilation if anything goes wrong
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
export { parseSource as _parseSource };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for plugin management: install, uninstall, list.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import { PLUGINS_DIR } from './discovery.js';
|
|
8
|
+
import { listPlugins, uninstallPlugin, _parseSource } from './plugin.js';
|
|
9
|
+
describe('parseSource', () => {
|
|
10
|
+
it('parses github:user/repo format', () => {
|
|
11
|
+
const result = _parseSource('github:ByteYue/opencli-plugin-github-trending');
|
|
12
|
+
expect(result).toEqual({
|
|
13
|
+
cloneUrl: 'https://github.com/ByteYue/opencli-plugin-github-trending.git',
|
|
14
|
+
name: 'github-trending',
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
it('parses https URL format', () => {
|
|
18
|
+
const result = _parseSource('https://github.com/ByteYue/opencli-plugin-hot-digest');
|
|
19
|
+
expect(result).toEqual({
|
|
20
|
+
cloneUrl: 'https://github.com/ByteYue/opencli-plugin-hot-digest.git',
|
|
21
|
+
name: 'hot-digest',
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
it('strips opencli-plugin- prefix from name', () => {
|
|
25
|
+
const result = _parseSource('github:user/opencli-plugin-my-tool');
|
|
26
|
+
expect(result.name).toBe('my-tool');
|
|
27
|
+
});
|
|
28
|
+
it('keeps name without prefix', () => {
|
|
29
|
+
const result = _parseSource('github:user/awesome-cli');
|
|
30
|
+
expect(result.name).toBe('awesome-cli');
|
|
31
|
+
});
|
|
32
|
+
it('returns null for invalid source', () => {
|
|
33
|
+
expect(_parseSource('invalid')).toBeNull();
|
|
34
|
+
expect(_parseSource('npm:some-package')).toBeNull();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('listPlugins', () => {
|
|
38
|
+
const testDir = path.join(PLUGINS_DIR, '__test-list-plugin__');
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
try {
|
|
41
|
+
fs.rmSync(testDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
catch { }
|
|
44
|
+
});
|
|
45
|
+
it('lists installed plugins', () => {
|
|
46
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
47
|
+
fs.writeFileSync(path.join(testDir, 'hello.yaml'), 'site: test\nname: hello\n');
|
|
48
|
+
const plugins = listPlugins();
|
|
49
|
+
const found = plugins.find(p => p.name === '__test-list-plugin__');
|
|
50
|
+
expect(found).toBeDefined();
|
|
51
|
+
expect(found.commands).toContain('hello');
|
|
52
|
+
});
|
|
53
|
+
it('returns empty array when no plugins dir', () => {
|
|
54
|
+
// listPlugins should handle missing dir gracefully
|
|
55
|
+
const plugins = listPlugins();
|
|
56
|
+
expect(Array.isArray(plugins)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
describe('uninstallPlugin', () => {
|
|
60
|
+
const testDir = path.join(PLUGINS_DIR, '__test-uninstall__');
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
try {
|
|
63
|
+
fs.rmSync(testDir, { recursive: true });
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
});
|
|
67
|
+
it('removes plugin directory', () => {
|
|
68
|
+
fs.mkdirSync(testDir, { recursive: true });
|
|
69
|
+
fs.writeFileSync(path.join(testDir, 'test.yaml'), 'site: test');
|
|
70
|
+
uninstallPlugin('__test-uninstall__');
|
|
71
|
+
expect(fs.existsSync(testDir)).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
it('throws for non-existent plugin', () => {
|
|
74
|
+
expect(() => uninstallPlugin('__nonexistent__')).toThrow('not installed');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API for opencli plugins.
|
|
3
|
+
*
|
|
4
|
+
* TS plugins should import from '@jackwener/opencli/registry' which resolves to
|
|
5
|
+
* this file. It re-exports ONLY the core registration API — no serialization,
|
|
6
|
+
* no transitive side-effects — to avoid circular dependency deadlocks when
|
|
7
|
+
* plugins are dynamically imported during discoverPlugins().
|
|
8
|
+
*/
|
|
9
|
+
export { cli, Strategy, getRegistry, fullName, registerCommand } from './registry.js';
|
|
10
|
+
export type { CliCommand, Arg, CliOptions } from './registry.js';
|
|
11
|
+
export type { IPage } from './types.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public API for opencli plugins.
|
|
3
|
+
*
|
|
4
|
+
* TS plugins should import from '@jackwener/opencli/registry' which resolves to
|
|
5
|
+
* this file. It re-exports ONLY the core registration API — no serialization,
|
|
6
|
+
* no transitive side-effects — to avoid circular dependency deadlocks when
|
|
7
|
+
* plugins are dynamically imported during discoverPlugins().
|
|
8
|
+
*/
|
|
9
|
+
export { cli, Strategy, getRegistry, fullName, registerCommand } from './registry.js';
|
package/dist/registry.d.ts
CHANGED
|
@@ -32,6 +32,17 @@ export interface CliCommand {
|
|
|
32
32
|
timeoutSeconds?: number;
|
|
33
33
|
source?: string;
|
|
34
34
|
footerExtra?: (kwargs: Record<string, any>) => string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Control pre-navigation for cookie/header context before command execution.
|
|
37
|
+
*
|
|
38
|
+
* Browser adapters using COOKIE/HEADER strategy need the page to be on the
|
|
39
|
+
* target domain so that `fetch(url, { credentials: 'include' })` carries cookies.
|
|
40
|
+
*
|
|
41
|
+
* - `undefined` / `true`: navigate to `https://${domain}` (default)
|
|
42
|
+
* - `false`: skip — adapter handles its own navigation (e.g. boss common.ts)
|
|
43
|
+
* - `string`: navigate to this specific URL instead of the domain root
|
|
44
|
+
*/
|
|
45
|
+
navigateBefore?: boolean | string;
|
|
35
46
|
}
|
|
36
47
|
/** Internal extension for lazy-loaded TS modules (not exposed in public API) */
|
|
37
48
|
export interface InternalCliCommand extends CliCommand {
|
package/dist/registry.js
CHANGED
|
@@ -9,7 +9,11 @@ export var Strategy;
|
|
|
9
9
|
Strategy["INTERCEPT"] = "intercept";
|
|
10
10
|
Strategy["UI"] = "ui";
|
|
11
11
|
})(Strategy || (Strategy = {}));
|
|
12
|
-
|
|
12
|
+
// Use globalThis to ensure a single shared registry across all module instances.
|
|
13
|
+
// This is critical for TS plugins loaded via npm link / peerDependency — without
|
|
14
|
+
// this, the plugin's import creates a separate module instance with its own Map.
|
|
15
|
+
const REGISTRY_KEY = '__opencli_registry__';
|
|
16
|
+
const _registry = globalThis[REGISTRY_KEY] ??= new Map();
|
|
13
17
|
export function cli(opts) {
|
|
14
18
|
const strategy = opts.strategy ?? (opts.browser === false ? Strategy.PUBLIC : Strategy.COOKIE);
|
|
15
19
|
const browser = opts.browser ?? (strategy !== Strategy.PUBLIC);
|
|
@@ -26,6 +30,7 @@ export function cli(opts) {
|
|
|
26
30
|
pipeline: opts.pipeline,
|
|
27
31
|
timeoutSeconds: opts.timeoutSeconds,
|
|
28
32
|
footerExtra: opts.footerExtra,
|
|
33
|
+
navigateBefore: opts.navigateBefore,
|
|
29
34
|
};
|
|
30
35
|
const key = fullName(cmd);
|
|
31
36
|
_registry.set(key, cmd);
|
package/dist/synthesize.d.ts
CHANGED
|
@@ -2,12 +2,102 @@
|
|
|
2
2
|
* Synthesize candidate CLIs from explore artifacts.
|
|
3
3
|
* Generates evaluate-based YAML pipelines (matching hand-written adapter patterns).
|
|
4
4
|
*/
|
|
5
|
+
import type { ExploreAuthSummary, ExploreEndpointArtifact, ExploreManifest } from './explore.js';
|
|
6
|
+
interface RecommendedArg {
|
|
7
|
+
name: string;
|
|
8
|
+
type?: string;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
default?: unknown;
|
|
11
|
+
}
|
|
12
|
+
interface StoreHint {
|
|
13
|
+
store: string;
|
|
14
|
+
action: string;
|
|
15
|
+
}
|
|
16
|
+
export interface SynthesizeCapability {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
strategy: string;
|
|
20
|
+
confidence?: number;
|
|
21
|
+
endpoint?: string;
|
|
22
|
+
itemPath?: string | null;
|
|
23
|
+
recommendedColumns?: string[];
|
|
24
|
+
recommendedArgs?: RecommendedArg[];
|
|
25
|
+
recommended_args?: RecommendedArg[];
|
|
26
|
+
recommendedColumnsLegacy?: string[];
|
|
27
|
+
recommended_columns?: string[];
|
|
28
|
+
storeHint?: StoreHint;
|
|
29
|
+
}
|
|
30
|
+
export interface GeneratedArgDefinition {
|
|
31
|
+
type: string;
|
|
32
|
+
required?: boolean;
|
|
33
|
+
default?: unknown;
|
|
34
|
+
description?: string;
|
|
35
|
+
}
|
|
36
|
+
type CandidatePipelineStep = {
|
|
37
|
+
navigate: string;
|
|
38
|
+
} | {
|
|
39
|
+
wait: number;
|
|
40
|
+
} | {
|
|
41
|
+
evaluate: string;
|
|
42
|
+
} | {
|
|
43
|
+
select: string;
|
|
44
|
+
} | {
|
|
45
|
+
map: Record<string, string>;
|
|
46
|
+
} | {
|
|
47
|
+
limit: string;
|
|
48
|
+
} | {
|
|
49
|
+
fetch: {
|
|
50
|
+
url: string;
|
|
51
|
+
};
|
|
52
|
+
} | {
|
|
53
|
+
tap: {
|
|
54
|
+
store: string;
|
|
55
|
+
action: string;
|
|
56
|
+
timeout: number;
|
|
57
|
+
capture?: string;
|
|
58
|
+
select?: string | null;
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
export interface CandidateYaml {
|
|
62
|
+
site: string;
|
|
63
|
+
name: string;
|
|
64
|
+
description: string;
|
|
65
|
+
domain: string;
|
|
66
|
+
strategy: string;
|
|
67
|
+
browser: boolean;
|
|
68
|
+
args: Record<string, GeneratedArgDefinition>;
|
|
69
|
+
pipeline: CandidatePipelineStep[];
|
|
70
|
+
columns: string[];
|
|
71
|
+
}
|
|
72
|
+
export interface SynthesizeCandidateSummary {
|
|
73
|
+
name: string;
|
|
74
|
+
path: string;
|
|
75
|
+
strategy: string;
|
|
76
|
+
confidence?: number;
|
|
77
|
+
}
|
|
78
|
+
export interface SynthesizeResult {
|
|
79
|
+
site: string;
|
|
80
|
+
explore_dir: string;
|
|
81
|
+
out_dir: string;
|
|
82
|
+
candidate_count: number;
|
|
83
|
+
candidates: SynthesizeCandidateSummary[];
|
|
84
|
+
}
|
|
85
|
+
interface LoadedExploreBundle {
|
|
86
|
+
manifest: ExploreManifest;
|
|
87
|
+
endpoints: ExploreEndpointArtifact[];
|
|
88
|
+
capabilities: SynthesizeCapability[];
|
|
89
|
+
auth: ExploreAuthSummary;
|
|
90
|
+
}
|
|
5
91
|
export declare function synthesizeFromExplore(target: string, opts?: {
|
|
6
92
|
outDir?: string;
|
|
7
93
|
top?: number;
|
|
8
|
-
}):
|
|
9
|
-
export declare function renderSynthesizeSummary(result:
|
|
94
|
+
}): SynthesizeResult;
|
|
95
|
+
export declare function renderSynthesizeSummary(result: SynthesizeResult): string;
|
|
10
96
|
export declare function resolveExploreDir(target: string): string;
|
|
11
|
-
export declare function loadExploreBundle(exploreDir: string):
|
|
97
|
+
export declare function loadExploreBundle(exploreDir: string): LoadedExploreBundle;
|
|
12
98
|
/** Backward-compatible export for scaffold.ts */
|
|
13
|
-
export declare function buildCandidate(site: string, targetUrl: string, cap:
|
|
99
|
+
export declare function buildCandidate(site: string, targetUrl: string, cap: SynthesizeCapability, endpoint: ExploreEndpointArtifact): {
|
|
100
|
+
name: string;
|
|
101
|
+
yaml: CandidateYaml;
|
|
102
|
+
};
|
|
103
|
+
export {};
|
package/dist/synthesize.js
CHANGED
|
@@ -60,11 +60,12 @@ function chooseEndpoint(cap, endpoints) {
|
|
|
60
60
|
return null;
|
|
61
61
|
// Match by endpoint pattern from capability
|
|
62
62
|
if (cap.endpoint) {
|
|
63
|
-
const
|
|
63
|
+
const endpointPattern = cap.endpoint;
|
|
64
|
+
const match = endpoints.find((endpoint) => endpoint.pattern === endpointPattern || endpoint.url?.includes(endpointPattern));
|
|
64
65
|
if (match)
|
|
65
66
|
return match;
|
|
66
67
|
}
|
|
67
|
-
return endpoints.sort((a, b) => (b.score ?? 0) - (a.score ?? 0))[0];
|
|
68
|
+
return [...endpoints].sort((a, b) => (b.score ?? 0) - (a.score ?? 0))[0];
|
|
68
69
|
}
|
|
69
70
|
// ── URL templating ─────────────────────────────────────────────────────────
|
|
70
71
|
function buildTemplatedUrl(rawUrl, cap, _endpoint) {
|
|
@@ -72,7 +73,7 @@ function buildTemplatedUrl(rawUrl, cap, _endpoint) {
|
|
|
72
73
|
const u = new URL(rawUrl);
|
|
73
74
|
const base = `${u.protocol}//${u.host}${u.pathname}`;
|
|
74
75
|
const params = [];
|
|
75
|
-
const hasKeyword = cap.recommendedArgs?.some((
|
|
76
|
+
const hasKeyword = cap.recommendedArgs?.some((arg) => arg.name === 'keyword');
|
|
76
77
|
u.searchParams.forEach((v, k) => {
|
|
77
78
|
if (VOLATILE_PARAMS.has(k))
|
|
78
79
|
return;
|
|
@@ -166,7 +167,7 @@ function buildCandidateYaml(site, manifest, cap, endpoint) {
|
|
|
166
167
|
// Map fields
|
|
167
168
|
const mapStep = {};
|
|
168
169
|
const columns = cap.recommendedColumns ?? ['title', 'url'];
|
|
169
|
-
if (!cap.recommendedArgs?.some((
|
|
170
|
+
if (!cap.recommendedArgs?.some((arg) => arg.name === 'keyword'))
|
|
170
171
|
mapStep['rank'] = '${{ index + 1 }}';
|
|
171
172
|
const detectedFields = endpoint?.detectedFields ?? {};
|
|
172
173
|
for (const col of columns) {
|