@jackwener/opencli 1.1.1 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-extension.yml +3 -3
- package/.github/workflows/ci.yml +6 -6
- package/.github/workflows/doc-check.yml +3 -3
- package/.github/workflows/e2e-headed.yml +2 -2
- package/.github/workflows/pkg-pr-new.yml +2 -2
- package/.github/workflows/release.yml +3 -3
- package/.github/workflows/security.yml +2 -2
- package/CONTRIBUTING.md +39 -1
- package/README.md +13 -10
- package/README.zh-CN.md +43 -17
- package/SKILL.md +10 -5
- package/dist/browser/cdp.d.ts +4 -4
- package/dist/browser/cdp.js +39 -16
- package/dist/browser/daemon-client.d.ts +4 -2
- package/dist/browser/daemon-client.js +17 -4
- package/dist/browser/dom-helpers.js +38 -7
- package/dist/browser/dom-snapshot.d.ts +86 -0
- package/dist/browser/dom-snapshot.js +729 -0
- package/dist/browser/dom-snapshot.test.d.ts +11 -0
- package/dist/browser/dom-snapshot.test.js +212 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/mcp.js +3 -1
- package/dist/browser/page.d.ts +14 -24
- package/dist/browser/page.js +46 -6
- package/dist/build-manifest.d.ts +11 -4
- package/dist/build-manifest.js +59 -21
- package/dist/build-manifest.test.js +58 -2
- package/dist/cli-manifest.json +3856 -1509
- package/dist/cli.js +66 -0
- package/dist/clis/barchart/greeks.js +1 -1
- package/dist/clis/barchart/options.js +1 -1
- package/dist/clis/barchart/quote.js +1 -1
- package/dist/clis/bilibili/download.js +1 -1
- package/dist/clis/bilibili/following.js +1 -1
- package/dist/clis/bilibili/subtitle.js +1 -1
- package/dist/clis/bilibili/user-videos.js +1 -1
- package/dist/clis/boss/batchgreet.js +10 -97
- package/dist/clis/boss/chatlist.js +8 -25
- package/dist/clis/boss/chatmsg.js +11 -42
- package/dist/clis/boss/common.d.ts +92 -0
- package/dist/clis/boss/common.js +223 -0
- package/dist/clis/boss/detail.js +7 -49
- package/dist/clis/boss/exchange.js +13 -79
- package/dist/clis/boss/greet.js +18 -145
- package/dist/clis/boss/invite.js +26 -121
- package/dist/clis/boss/joblist.js +6 -31
- package/dist/clis/boss/mark.js +12 -85
- package/dist/clis/boss/recommend.js +10 -49
- package/dist/clis/boss/resume.js +18 -118
- package/dist/clis/boss/search.js +12 -60
- package/dist/clis/boss/send.js +17 -151
- package/dist/clis/boss/stats.js +18 -69
- package/dist/clis/coupang/add-to-cart.js +1 -1
- package/dist/clis/devto/tag.yaml +34 -0
- package/dist/clis/devto/top.yaml +29 -0
- package/dist/clis/devto/user.yaml +33 -0
- package/dist/clis/douban/book-hot.d.ts +1 -0
- package/dist/clis/douban/book-hot.js +14 -0
- package/dist/clis/douban/marks.d.ts +1 -0
- package/dist/clis/douban/marks.js +115 -0
- package/dist/clis/douban/movie-hot.d.ts +1 -0
- package/dist/clis/douban/movie-hot.js +14 -0
- package/dist/clis/douban/reviews.d.ts +1 -0
- package/dist/clis/douban/reviews.js +106 -0
- package/dist/clis/douban/search.d.ts +1 -0
- package/dist/clis/douban/search.js +16 -0
- package/dist/clis/douban/shared.d.ts +4 -0
- package/dist/clis/douban/shared.js +155 -0
- package/dist/clis/douban/subject.yaml +76 -0
- package/dist/clis/douban/top250.yaml +70 -0
- package/dist/clis/douban/utils.d.ts +35 -0
- package/dist/clis/douban/utils.js +48 -0
- package/dist/clis/facebook/add-friend.yaml +43 -0
- package/dist/clis/facebook/events.yaml +44 -0
- package/dist/clis/facebook/feed.yaml +63 -0
- package/dist/clis/facebook/friends.yaml +42 -0
- package/dist/clis/facebook/groups.yaml +50 -0
- package/dist/clis/facebook/join-group.yaml +44 -0
- package/dist/clis/facebook/memories.yaml +39 -0
- package/dist/clis/facebook/notifications.yaml +40 -0
- package/dist/clis/facebook/profile.yaml +37 -0
- package/dist/clis/facebook/search.yaml +46 -0
- package/dist/clis/google/news.d.ts +5 -0
- package/dist/clis/google/news.js +58 -0
- package/dist/clis/google/search.d.ts +10 -0
- package/dist/clis/google/search.js +127 -0
- package/dist/clis/google/suggest.d.ts +5 -0
- package/dist/clis/google/suggest.js +34 -0
- package/dist/clis/google/trends.d.ts +5 -0
- package/dist/clis/google/trends.js +38 -0
- package/dist/clis/google/utils.d.ts +9 -0
- package/dist/clis/google/utils.js +23 -0
- package/dist/clis/google/utils.test.d.ts +1 -0
- package/dist/clis/google/utils.test.js +75 -0
- package/dist/clis/grok/ask.d.ts +14 -0
- package/dist/clis/grok/ask.js +257 -65
- package/dist/clis/grok/ask.test.d.ts +1 -0
- package/dist/clis/grok/ask.test.js +36 -0
- package/dist/clis/instagram/comment.yaml +52 -0
- package/dist/clis/instagram/explore.yaml +43 -0
- package/dist/clis/instagram/follow.yaml +41 -0
- package/dist/clis/instagram/followers.yaml +51 -0
- package/dist/clis/instagram/following.yaml +51 -0
- package/dist/clis/instagram/like.yaml +46 -0
- package/dist/clis/instagram/profile.yaml +42 -0
- package/dist/clis/instagram/save.yaml +46 -0
- package/dist/clis/instagram/saved.yaml +40 -0
- package/dist/clis/instagram/search.yaml +43 -0
- package/dist/clis/instagram/unfollow.yaml +38 -0
- package/dist/clis/instagram/unlike.yaml +46 -0
- package/dist/clis/instagram/unsave.yaml +46 -0
- package/dist/clis/instagram/user.yaml +54 -0
- package/dist/clis/jike/repost.js +1 -1
- package/dist/clis/jimeng/generate.yaml +1 -0
- package/dist/clis/linux-do/category.yaml +1 -0
- package/dist/clis/lobsters/active.yaml +29 -0
- package/dist/clis/lobsters/hot.yaml +29 -0
- package/dist/clis/lobsters/newest.yaml +29 -0
- package/dist/clis/lobsters/tag.yaml +34 -0
- package/dist/clis/medium/feed.d.ts +1 -0
- package/dist/clis/medium/feed.js +15 -0
- package/dist/clis/medium/search.d.ts +1 -0
- package/dist/clis/medium/search.js +15 -0
- package/dist/clis/medium/shared.d.ts +5 -0
- package/dist/clis/medium/shared.js +78 -0
- package/dist/clis/medium/user.d.ts +1 -0
- package/dist/clis/medium/user.js +15 -0
- package/dist/clis/reddit/comment.js +1 -1
- package/dist/clis/reddit/read.js +1 -1
- package/dist/clis/reddit/save.js +1 -1
- package/dist/clis/reddit/subreddit.yaml +1 -0
- package/dist/clis/reddit/subscribe.js +1 -1
- package/dist/clis/reddit/upvote.js +1 -1
- package/dist/clis/sinablog/article.d.ts +1 -0
- package/dist/clis/sinablog/article.js +14 -0
- package/dist/clis/sinablog/hot.d.ts +1 -0
- package/dist/clis/sinablog/hot.js +14 -0
- package/dist/clis/sinablog/search.d.ts +1 -0
- package/dist/clis/sinablog/search.js +51 -0
- package/dist/clis/sinablog/shared.d.ts +7 -0
- package/dist/clis/sinablog/shared.js +187 -0
- package/dist/clis/sinablog/user.d.ts +1 -0
- package/dist/clis/sinablog/user.js +15 -0
- package/dist/clis/substack/feed.d.ts +1 -0
- package/dist/clis/substack/feed.js +15 -0
- package/dist/clis/substack/publication.d.ts +1 -0
- package/dist/clis/substack/publication.js +15 -0
- package/dist/clis/substack/search.d.ts +1 -0
- package/dist/clis/substack/search.js +77 -0
- package/dist/clis/substack/shared.d.ts +4 -0
- package/dist/clis/substack/shared.js +129 -0
- package/dist/clis/tiktok/comment.yaml +66 -0
- package/dist/clis/tiktok/explore.yaml +39 -0
- package/dist/clis/tiktok/follow.yaml +39 -0
- package/dist/clis/tiktok/following.yaml +46 -0
- package/dist/clis/tiktok/friends.yaml +47 -0
- package/dist/clis/tiktok/like.yaml +38 -0
- package/dist/clis/tiktok/live.yaml +51 -0
- package/dist/clis/tiktok/notifications.yaml +52 -0
- package/dist/clis/tiktok/profile.yaml +45 -0
- package/dist/clis/tiktok/save.yaml +34 -0
- package/dist/clis/tiktok/search.yaml +46 -0
- package/dist/clis/tiktok/unfollow.yaml +44 -0
- package/dist/clis/tiktok/unlike.yaml +38 -0
- package/dist/clis/tiktok/unsave.yaml +36 -0
- package/dist/clis/tiktok/user.yaml +44 -0
- package/dist/clis/twitter/download.d.ts +1 -1
- package/dist/clis/twitter/download.js +3 -3
- package/dist/clis/twitter/followers.js +1 -1
- package/dist/clis/twitter/following.js +1 -1
- package/dist/clis/twitter/thread.js +1 -1
- package/dist/clis/twitter/timeline.d.ts +23 -0
- package/dist/clis/twitter/timeline.js +42 -14
- package/dist/clis/twitter/timeline.test.d.ts +1 -0
- package/dist/clis/twitter/timeline.test.js +102 -0
- package/dist/clis/wikipedia/random.d.ts +1 -0
- package/dist/clis/wikipedia/random.js +19 -0
- package/dist/clis/wikipedia/search.js +3 -3
- package/dist/clis/wikipedia/summary.js +4 -9
- package/dist/clis/wikipedia/trending.d.ts +1 -0
- package/dist/clis/wikipedia/trending.js +35 -0
- package/dist/clis/wikipedia/utils.d.ts +28 -0
- package/dist/clis/wikipedia/utils.js +13 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
- package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
- package/dist/clis/xiaohongshu/download.js +1 -1
- package/dist/clis/xueqiu/earnings-date.yaml +69 -0
- package/dist/clis/xueqiu/search.yaml +2 -1
- package/dist/clis/xueqiu/stock.yaml +2 -0
- package/dist/clis/yahoo-finance/quote.js +1 -1
- package/dist/commanderAdapter.js +13 -7
- package/dist/daemon.js +21 -0
- package/dist/discovery.d.ts +8 -0
- package/dist/discovery.js +105 -19
- package/dist/doctor.js +3 -1
- package/dist/doctor.test.js +46 -2
- package/dist/engine.test.d.ts +0 -3
- package/dist/engine.test.js +74 -6
- package/dist/execution.d.ts +4 -2
- package/dist/execution.js +31 -7
- package/dist/explore.d.ts +76 -3
- package/dist/explore.js +11 -4
- package/dist/generate.d.ts +41 -2
- package/dist/generate.js +5 -4
- package/dist/main.js +2 -1
- package/dist/pipeline/executor.d.ts +4 -2
- package/dist/pipeline/executor.js +54 -15
- package/dist/pipeline/executor.test.js +33 -6
- package/dist/pipeline/registry.d.ts +1 -1
- package/dist/pipeline/steps/browser.d.ts +7 -7
- package/dist/pipeline/steps/browser.js +15 -7
- package/dist/pipeline/steps/fetch.d.ts +1 -1
- package/dist/pipeline/steps/fetch.js +11 -7
- package/dist/pipeline/steps/transform.d.ts +6 -5
- package/dist/pipeline/steps/transform.js +30 -9
- package/dist/pipeline/template.d.ts +6 -6
- package/dist/pipeline/template.js +43 -5
- package/dist/pipeline/template.test.js +18 -0
- package/dist/pipeline/transform.test.js +11 -0
- package/dist/plugin.d.ts +31 -0
- package/dist/plugin.js +216 -0
- package/dist/plugin.test.d.ts +4 -0
- package/dist/plugin.test.js +76 -0
- package/dist/registry-api.d.ts +11 -0
- package/dist/registry-api.js +9 -0
- package/dist/registry.d.ts +11 -0
- package/dist/registry.js +6 -1
- package/dist/synthesize.d.ts +94 -4
- package/dist/synthesize.js +5 -4
- package/dist/types.d.ts +39 -26
- package/dist/validate.js +8 -2
- package/docs/.vitepress/config.mts +6 -4
- package/docs/adapters/browser/barchart.md +6 -5
- package/docs/adapters/browser/bilibili.md +9 -0
- package/docs/adapters/browser/devto.md +35 -0
- package/docs/adapters/browser/douban.md +38 -0
- package/docs/adapters/browser/facebook.md +36 -0
- package/docs/adapters/browser/google.md +62 -0
- package/docs/adapters/browser/grok.md +26 -8
- package/docs/adapters/browser/instagram.md +46 -0
- package/docs/adapters/browser/lobsters.md +32 -0
- package/docs/adapters/browser/medium.md +32 -0
- package/docs/adapters/browser/reddit.md +9 -0
- package/docs/adapters/browser/sinablog.md +36 -0
- package/docs/adapters/browser/substack.md +38 -0
- package/docs/adapters/browser/tiktok.md +68 -0
- package/docs/adapters/browser/wikipedia.md +11 -2
- package/docs/adapters/browser/xueqiu.md +10 -0
- package/docs/adapters/browser/yahoo-finance.md +6 -5
- package/docs/adapters/desktop/antigravity.md +6 -0
- package/docs/adapters/desktop/chatgpt.md +2 -1
- package/docs/adapters/desktop/codex.md +5 -1
- package/docs/adapters/desktop/cursor.md +4 -0
- package/docs/adapters/desktop/discord.md +7 -7
- package/docs/adapters/index.md +1 -4
- package/docs/guide/getting-started.md +1 -0
- package/docs/guide/plugins.md +153 -0
- package/docs/zh/guide/plugins.md +107 -0
- package/extension/dist/background.js +91 -23
- package/extension/src/background.ts +82 -29
- package/extension/src/cdp.ts +42 -1
- package/package.json +10 -5
- package/scripts/clean-dist.cjs +13 -0
- package/src/browser/cdp.ts +71 -31
- package/src/browser/daemon-client.ts +21 -5
- package/src/browser/dom-helpers.ts +38 -7
- package/src/browser/dom-snapshot.test.ts +249 -0
- package/src/browser/dom-snapshot.ts +770 -0
- package/src/browser/index.ts +2 -0
- package/src/browser/mcp.ts +3 -1
- package/src/browser/page.ts +57 -21
- package/src/build-manifest.test.ts +70 -2
- package/src/build-manifest.ts +94 -26
- package/src/cli.ts +71 -2
- package/src/clis/barchart/greeks.ts +1 -1
- package/src/clis/barchart/options.ts +1 -1
- package/src/clis/barchart/quote.ts +1 -1
- package/src/clis/bilibili/download.ts +1 -1
- package/src/clis/bilibili/following.ts +1 -1
- package/src/clis/bilibili/subtitle.ts +1 -1
- package/src/clis/bilibili/user-videos.ts +1 -1
- package/src/clis/boss/batchgreet.ts +14 -106
- package/src/clis/boss/chatlist.ts +12 -26
- package/src/clis/boss/chatmsg.ts +16 -40
- package/src/clis/boss/common.ts +287 -0
- package/src/clis/boss/detail.ts +8 -54
- package/src/clis/boss/exchange.ts +15 -89
- package/src/clis/boss/greet.ts +23 -160
- package/src/clis/boss/invite.ts +36 -133
- package/src/clis/boss/joblist.ts +7 -36
- package/src/clis/boss/mark.ts +13 -94
- package/src/clis/boss/recommend.ts +12 -57
- package/src/clis/boss/resume.ts +19 -124
- package/src/clis/boss/search.ts +13 -66
- package/src/clis/boss/send.ts +21 -161
- package/src/clis/boss/stats.ts +19 -74
- package/src/clis/coupang/add-to-cart.ts +1 -1
- package/src/clis/devto/tag.yaml +34 -0
- package/src/clis/devto/top.yaml +29 -0
- package/src/clis/devto/user.yaml +33 -0
- package/src/clis/douban/book-hot.ts +15 -0
- package/src/clis/douban/marks.ts +135 -0
- package/src/clis/douban/movie-hot.ts +15 -0
- package/src/clis/douban/reviews.ts +127 -0
- package/src/clis/douban/search.ts +17 -0
- package/src/clis/douban/shared.ts +165 -0
- package/src/clis/douban/subject.yaml +76 -0
- package/src/clis/douban/top250.yaml +70 -0
- package/src/clis/douban/utils.ts +81 -0
- package/src/clis/facebook/add-friend.yaml +43 -0
- package/src/clis/facebook/events.yaml +44 -0
- package/src/clis/facebook/feed.yaml +63 -0
- package/src/clis/facebook/friends.yaml +42 -0
- package/src/clis/facebook/groups.yaml +50 -0
- package/src/clis/facebook/join-group.yaml +44 -0
- package/src/clis/facebook/memories.yaml +39 -0
- package/src/clis/facebook/notifications.yaml +40 -0
- package/src/clis/facebook/profile.yaml +37 -0
- package/src/clis/facebook/search.yaml +46 -0
- package/src/clis/google/news.ts +66 -0
- package/src/clis/google/search.ts +133 -0
- package/src/clis/google/suggest.ts +40 -0
- package/src/clis/google/trends.ts +44 -0
- package/src/clis/google/utils.test.ts +82 -0
- package/src/clis/google/utils.ts +24 -0
- package/src/clis/grok/ask.test.ts +53 -0
- package/src/clis/grok/ask.ts +300 -69
- package/src/clis/instagram/comment.yaml +52 -0
- package/src/clis/instagram/explore.yaml +43 -0
- package/src/clis/instagram/follow.yaml +41 -0
- package/src/clis/instagram/followers.yaml +51 -0
- package/src/clis/instagram/following.yaml +51 -0
- package/src/clis/instagram/like.yaml +46 -0
- package/src/clis/instagram/profile.yaml +42 -0
- package/src/clis/instagram/save.yaml +46 -0
- package/src/clis/instagram/saved.yaml +40 -0
- package/src/clis/instagram/search.yaml +43 -0
- package/src/clis/instagram/unfollow.yaml +38 -0
- package/src/clis/instagram/unlike.yaml +46 -0
- package/src/clis/instagram/unsave.yaml +46 -0
- package/src/clis/instagram/user.yaml +54 -0
- package/src/clis/jike/repost.ts +1 -1
- package/src/clis/jimeng/generate.yaml +1 -0
- package/src/clis/linux-do/category.yaml +1 -0
- package/src/clis/lobsters/active.yaml +29 -0
- package/src/clis/lobsters/hot.yaml +29 -0
- package/src/clis/lobsters/newest.yaml +29 -0
- package/src/clis/lobsters/tag.yaml +34 -0
- package/src/clis/medium/feed.ts +16 -0
- package/src/clis/medium/search.ts +16 -0
- package/src/clis/medium/shared.ts +83 -0
- package/src/clis/medium/user.ts +16 -0
- package/src/clis/reddit/comment.ts +1 -1
- package/src/clis/reddit/read.ts +1 -1
- package/src/clis/reddit/save.ts +1 -1
- package/src/clis/reddit/subreddit.yaml +1 -0
- package/src/clis/reddit/subscribe.ts +1 -1
- package/src/clis/reddit/upvote.ts +1 -1
- package/src/clis/sinablog/article.ts +15 -0
- package/src/clis/sinablog/hot.ts +15 -0
- package/src/clis/sinablog/search.ts +56 -0
- package/src/clis/sinablog/shared.ts +198 -0
- package/src/clis/sinablog/user.ts +16 -0
- package/src/clis/substack/feed.ts +16 -0
- package/src/clis/substack/publication.ts +16 -0
- package/src/clis/substack/search.ts +91 -0
- package/src/clis/substack/shared.ts +132 -0
- package/src/clis/tiktok/comment.yaml +66 -0
- package/src/clis/tiktok/explore.yaml +39 -0
- package/src/clis/tiktok/follow.yaml +39 -0
- package/src/clis/tiktok/following.yaml +46 -0
- package/src/clis/tiktok/friends.yaml +47 -0
- package/src/clis/tiktok/like.yaml +38 -0
- package/src/clis/tiktok/live.yaml +51 -0
- package/src/clis/tiktok/notifications.yaml +52 -0
- package/src/clis/tiktok/profile.yaml +45 -0
- package/src/clis/tiktok/save.yaml +34 -0
- package/src/clis/tiktok/search.yaml +46 -0
- package/src/clis/tiktok/unfollow.yaml +44 -0
- package/src/clis/tiktok/unlike.yaml +38 -0
- package/src/clis/tiktok/unsave.yaml +36 -0
- package/src/clis/tiktok/user.yaml +44 -0
- package/src/clis/twitter/download.ts +3 -3
- package/src/clis/twitter/followers.ts +1 -1
- package/src/clis/twitter/following.ts +1 -1
- package/src/clis/twitter/thread.ts +1 -1
- package/src/clis/twitter/timeline.test.ts +109 -0
- package/src/clis/twitter/timeline.ts +59 -19
- package/src/clis/wikipedia/random.ts +19 -0
- package/src/clis/wikipedia/search.ts +10 -4
- package/src/clis/wikipedia/summary.ts +4 -9
- package/src/clis/wikipedia/trending.ts +41 -0
- package/src/clis/wikipedia/utils.ts +31 -0
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
- package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
- package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
- package/src/clis/xiaohongshu/download.ts +1 -1
- package/src/clis/xueqiu/earnings-date.yaml +69 -0
- package/src/clis/xueqiu/search.yaml +2 -1
- package/src/clis/xueqiu/stock.yaml +2 -0
- package/src/clis/yahoo-finance/quote.ts +1 -1
- package/src/commanderAdapter.ts +17 -10
- package/src/daemon.ts +23 -0
- package/src/discovery.ts +134 -24
- package/src/doctor.test.ts +59 -2
- package/src/doctor.ts +4 -2
- package/src/engine.test.ts +79 -6
- package/src/execution.ts +42 -16
- package/src/explore.ts +77 -9
- package/src/generate.ts +58 -9
- package/src/main.ts +2 -1
- package/src/pipeline/executor.test.ts +35 -6
- package/src/pipeline/executor.ts +68 -19
- package/src/pipeline/registry.ts +3 -3
- package/src/pipeline/steps/browser.ts +24 -15
- package/src/pipeline/steps/fetch.ts +18 -13
- package/src/pipeline/steps/transform.ts +40 -15
- package/src/pipeline/template.test.ts +18 -0
- package/src/pipeline/template.ts +86 -13
- package/src/pipeline/transform.test.ts +15 -2
- package/src/plugin.test.ts +86 -0
- package/src/plugin.ts +254 -0
- package/src/registry-api.ts +12 -0
- package/src/registry.ts +19 -1
- package/src/synthesize.ts +102 -21
- package/src/types.ts +44 -12
- package/src/validate.ts +19 -4
- package/tests/e2e/browser-public.test.ts +11 -0
- package/tests/e2e/public-commands.test.ts +64 -0
- package/dist/clis/feishu/new.d.ts +0 -1
- package/dist/clis/feishu/new.js +0 -27
- package/dist/clis/feishu/read.d.ts +0 -1
- package/dist/clis/feishu/read.js +0 -40
- package/dist/clis/feishu/search.d.ts +0 -1
- package/dist/clis/feishu/search.js +0 -30
- package/dist/clis/feishu/send.d.ts +0 -1
- package/dist/clis/feishu/send.js +0 -39
- package/dist/clis/feishu/status.d.ts +0 -1
- package/dist/clis/feishu/status.js +0 -28
- package/dist/clis/neteasemusic/like.d.ts +0 -1
- package/dist/clis/neteasemusic/like.js +0 -25
- package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
- package/dist/clis/neteasemusic/lyrics.js +0 -47
- package/dist/clis/neteasemusic/next.d.ts +0 -1
- package/dist/clis/neteasemusic/next.js +0 -26
- package/dist/clis/neteasemusic/play.d.ts +0 -1
- package/dist/clis/neteasemusic/play.js +0 -26
- package/dist/clis/neteasemusic/playing.d.ts +0 -1
- package/dist/clis/neteasemusic/playing.js +0 -59
- package/dist/clis/neteasemusic/playlist.d.ts +0 -1
- package/dist/clis/neteasemusic/playlist.js +0 -46
- package/dist/clis/neteasemusic/prev.d.ts +0 -1
- package/dist/clis/neteasemusic/prev.js +0 -25
- package/dist/clis/neteasemusic/search.d.ts +0 -1
- package/dist/clis/neteasemusic/search.js +0 -52
- package/dist/clis/neteasemusic/status.d.ts +0 -1
- package/dist/clis/neteasemusic/status.js +0 -16
- package/dist/clis/neteasemusic/volume.d.ts +0 -1
- package/dist/clis/neteasemusic/volume.js +0 -54
- package/dist/clis/wechat/chats.d.ts +0 -1
- package/dist/clis/wechat/chats.js +0 -28
- package/dist/clis/wechat/contacts.d.ts +0 -1
- package/dist/clis/wechat/contacts.js +0 -28
- package/dist/clis/wechat/read.d.ts +0 -1
- package/dist/clis/wechat/read.js +0 -58
- package/dist/clis/wechat/search.d.ts +0 -1
- package/dist/clis/wechat/search.js +0 -31
- package/dist/clis/wechat/send.d.ts +0 -1
- package/dist/clis/wechat/send.js +0 -42
- package/dist/clis/wechat/status.d.ts +0 -1
- package/dist/clis/wechat/status.js +0 -29
- package/dist/pipeline.d.ts +0 -7
- package/dist/pipeline.js +0 -7
- package/docs/adapters/browser/github.md +0 -26
- package/docs/adapters/desktop/feishu.md +0 -20
- package/docs/adapters/desktop/neteasemusic.md +0 -31
- package/docs/adapters/desktop/wechat.md +0 -28
- package/src/clis/antigravity/README.md +0 -5
- package/src/clis/antigravity/README.zh-CN.md +0 -51
- package/src/clis/chaoxing/README.md +0 -14
- package/src/clis/chaoxing/README.zh-CN.md +0 -35
- package/src/clis/chatgpt/README.md +0 -5
- package/src/clis/chatgpt/README.zh-CN.md +0 -44
- package/src/clis/chatwise/README.md +0 -5
- package/src/clis/chatwise/README.zh-CN.md +0 -38
- package/src/clis/codex/README.md +0 -5
- package/src/clis/codex/README.zh-CN.md +0 -33
- package/src/clis/cursor/README.md +0 -5
- package/src/clis/cursor/README.zh-CN.md +0 -33
- package/src/clis/discord-app/README.md +0 -5
- package/src/clis/discord-app/README.zh-CN.md +0 -28
- package/src/clis/feishu/README.md +0 -5
- package/src/clis/feishu/README.zh-CN.md +0 -20
- package/src/clis/feishu/new.ts +0 -32
- package/src/clis/feishu/read.ts +0 -48
- package/src/clis/feishu/search.ts +0 -35
- package/src/clis/feishu/send.ts +0 -46
- package/src/clis/feishu/status.ts +0 -34
- package/src/clis/neteasemusic/README.md +0 -5
- package/src/clis/neteasemusic/README.zh-CN.md +0 -31
- package/src/clis/neteasemusic/like.ts +0 -28
- package/src/clis/neteasemusic/lyrics.ts +0 -53
- package/src/clis/neteasemusic/next.ts +0 -30
- package/src/clis/neteasemusic/play.ts +0 -30
- package/src/clis/neteasemusic/playing.ts +0 -62
- package/src/clis/neteasemusic/playlist.ts +0 -51
- package/src/clis/neteasemusic/prev.ts +0 -29
- package/src/clis/neteasemusic/search.ts +0 -58
- package/src/clis/neteasemusic/status.ts +0 -18
- package/src/clis/neteasemusic/volume.ts +0 -61
- package/src/clis/notion/README.md +0 -5
- package/src/clis/notion/README.zh-CN.md +0 -29
- package/src/clis/wechat/README.md +0 -5
- package/src/clis/wechat/README.zh-CN.md +0 -28
- package/src/clis/wechat/chats.ts +0 -33
- package/src/clis/wechat/contacts.ts +0 -33
- package/src/clis/wechat/read.ts +0 -72
- package/src/clis/wechat/search.ts +0 -36
- package/src/clis/wechat/send.ts +0 -49
- package/src/clis/wechat/status.ts +0 -35
- package/src/pipeline.ts +0 -8
package/src/plugin.ts
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
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
|
+
|
|
8
|
+
import * as fs from 'node:fs';
|
|
9
|
+
import * as path from 'node:path';
|
|
10
|
+
import { execSync, execFileSync } from 'node:child_process';
|
|
11
|
+
import { PLUGINS_DIR } from './discovery.js';
|
|
12
|
+
import { log } from './logger.js';
|
|
13
|
+
|
|
14
|
+
export interface PluginInfo {
|
|
15
|
+
name: string;
|
|
16
|
+
path: string;
|
|
17
|
+
commands: string[];
|
|
18
|
+
source?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Install a plugin from a source.
|
|
23
|
+
* Currently supports "github:user/repo" format (git clone wrapper).
|
|
24
|
+
*/
|
|
25
|
+
export function installPlugin(source: string): string {
|
|
26
|
+
const parsed = parseSource(source);
|
|
27
|
+
if (!parsed) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Invalid plugin source: "${source}"\n` +
|
|
30
|
+
`Supported formats:\n` +
|
|
31
|
+
` github:user/repo\n` +
|
|
32
|
+
` https://github.com/user/repo`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const { cloneUrl, name } = parsed;
|
|
37
|
+
const targetDir = path.join(PLUGINS_DIR, name);
|
|
38
|
+
|
|
39
|
+
if (fs.existsSync(targetDir)) {
|
|
40
|
+
throw new Error(`Plugin "${name}" is already installed at ${targetDir}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Ensure plugins directory exists
|
|
44
|
+
fs.mkdirSync(PLUGINS_DIR, { recursive: true });
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
execFileSync('git', ['clone', '--depth', '1', cloneUrl, targetDir], {
|
|
48
|
+
encoding: 'utf-8',
|
|
49
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
50
|
+
});
|
|
51
|
+
} catch (err: any) {
|
|
52
|
+
throw new Error(`Failed to clone plugin: ${err.message}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// If the plugin has a package.json, run npm install for regular deps,
|
|
56
|
+
// then symlink the host opencli into node_modules for peerDep resolution.
|
|
57
|
+
const pkgJsonPath = path.join(targetDir, 'package.json');
|
|
58
|
+
if (fs.existsSync(pkgJsonPath)) {
|
|
59
|
+
try {
|
|
60
|
+
execFileSync('npm', ['install', '--omit=dev'], {
|
|
61
|
+
cwd: targetDir,
|
|
62
|
+
encoding: 'utf-8',
|
|
63
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
64
|
+
});
|
|
65
|
+
} catch {
|
|
66
|
+
// Non-fatal: npm install may fail if no real deps
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Symlink host opencli into plugin's node_modules so TS plugins
|
|
70
|
+
// can resolve '@jackwener/opencli/registry' against the running host.
|
|
71
|
+
// This is more reliable than depending on the npm-published version
|
|
72
|
+
// which may lag behind the local installation.
|
|
73
|
+
linkHostOpencli(targetDir);
|
|
74
|
+
|
|
75
|
+
// Transpile TS plugin files to JS so they work in production mode
|
|
76
|
+
// (node cannot load .ts files directly without tsx).
|
|
77
|
+
transpilePluginTs(targetDir);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return name;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Uninstall a plugin by name.
|
|
85
|
+
*/
|
|
86
|
+
export function uninstallPlugin(name: string): void {
|
|
87
|
+
const targetDir = path.join(PLUGINS_DIR, name);
|
|
88
|
+
if (!fs.existsSync(targetDir)) {
|
|
89
|
+
throw new Error(`Plugin "${name}" is not installed.`);
|
|
90
|
+
}
|
|
91
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* List all installed plugins.
|
|
96
|
+
*/
|
|
97
|
+
export function listPlugins(): PluginInfo[] {
|
|
98
|
+
if (!fs.existsSync(PLUGINS_DIR)) return [];
|
|
99
|
+
|
|
100
|
+
const entries = fs.readdirSync(PLUGINS_DIR, { withFileTypes: true });
|
|
101
|
+
const plugins: PluginInfo[] = [];
|
|
102
|
+
|
|
103
|
+
for (const entry of entries) {
|
|
104
|
+
if (!entry.isDirectory()) continue;
|
|
105
|
+
const pluginDir = path.join(PLUGINS_DIR, entry.name);
|
|
106
|
+
const commands = scanPluginCommands(pluginDir);
|
|
107
|
+
const source = getPluginSource(pluginDir);
|
|
108
|
+
|
|
109
|
+
plugins.push({
|
|
110
|
+
name: entry.name,
|
|
111
|
+
path: pluginDir,
|
|
112
|
+
commands,
|
|
113
|
+
source,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return plugins;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Scan a plugin directory for command files */
|
|
121
|
+
function scanPluginCommands(dir: string): string[] {
|
|
122
|
+
try {
|
|
123
|
+
const files = fs.readdirSync(dir);
|
|
124
|
+
const names = new Set(
|
|
125
|
+
files
|
|
126
|
+
.filter(f =>
|
|
127
|
+
f.endsWith('.yaml') || f.endsWith('.yml') ||
|
|
128
|
+
(f.endsWith('.ts') && !f.endsWith('.d.ts') && !f.endsWith('.test.ts')) ||
|
|
129
|
+
(f.endsWith('.js') && !f.endsWith('.d.js'))
|
|
130
|
+
)
|
|
131
|
+
.map(f => path.basename(f, path.extname(f)))
|
|
132
|
+
);
|
|
133
|
+
return [...names];
|
|
134
|
+
} catch {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Get git remote origin URL */
|
|
140
|
+
function getPluginSource(dir: string): string | undefined {
|
|
141
|
+
try {
|
|
142
|
+
return execSync('git config --get remote.origin.url', {
|
|
143
|
+
cwd: dir,
|
|
144
|
+
encoding: 'utf-8',
|
|
145
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
146
|
+
}).trim();
|
|
147
|
+
} catch {
|
|
148
|
+
return undefined;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Parse a plugin source string into clone URL and name */
|
|
153
|
+
function parseSource(source: string): { cloneUrl: string; name: string } | null {
|
|
154
|
+
// github:user/repo
|
|
155
|
+
const githubMatch = source.match(/^github:([\w.-]+)\/([\w.-]+)$/);
|
|
156
|
+
if (githubMatch) {
|
|
157
|
+
const [, user, repo] = githubMatch;
|
|
158
|
+
const name = repo.replace(/^opencli-plugin-/, '');
|
|
159
|
+
return {
|
|
160
|
+
cloneUrl: `https://github.com/${user}/${repo}.git`,
|
|
161
|
+
name,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// https://github.com/user/repo (or .git)
|
|
166
|
+
const urlMatch = source.match(/^https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?$/);
|
|
167
|
+
if (urlMatch) {
|
|
168
|
+
const [, user, repo] = urlMatch;
|
|
169
|
+
const name = repo.replace(/^opencli-plugin-/, '');
|
|
170
|
+
return {
|
|
171
|
+
cloneUrl: `https://github.com/${user}/${repo}.git`,
|
|
172
|
+
name,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Symlink the host opencli package into a plugin's node_modules.
|
|
181
|
+
* This ensures TS plugins resolve '@jackwener/opencli/registry' against
|
|
182
|
+
* the running host installation rather than a stale npm-published version.
|
|
183
|
+
*/
|
|
184
|
+
function linkHostOpencli(pluginDir: string): void {
|
|
185
|
+
try {
|
|
186
|
+
// Determine the host opencli package root from this module's location.
|
|
187
|
+
// Both dev (tsx src/plugin.ts) and prod (node dist/plugin.js) are one level
|
|
188
|
+
// deep, so path.dirname + '..' always gives us the package root.
|
|
189
|
+
const thisFile = new URL(import.meta.url).pathname;
|
|
190
|
+
const hostRoot = path.resolve(path.dirname(thisFile), '..');
|
|
191
|
+
|
|
192
|
+
const targetLink = path.join(pluginDir, 'node_modules', '@jackwener', 'opencli');
|
|
193
|
+
|
|
194
|
+
// Remove existing (npm-installed copy or stale symlink)
|
|
195
|
+
if (fs.existsSync(targetLink)) {
|
|
196
|
+
fs.rmSync(targetLink, { recursive: true, force: true });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Ensure parent directory exists
|
|
200
|
+
fs.mkdirSync(path.dirname(targetLink), { recursive: true });
|
|
201
|
+
|
|
202
|
+
// Create symlink
|
|
203
|
+
fs.symlinkSync(hostRoot, targetLink, 'dir');
|
|
204
|
+
log.debug(`Linked host opencli into plugin: ${targetLink} → ${hostRoot}`);
|
|
205
|
+
} catch (err: any) {
|
|
206
|
+
log.warn(`Failed to link host opencli into plugin: ${err.message}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Transpile TS plugin files to JS so they work in production mode.
|
|
212
|
+
* Uses esbuild from the host opencli's node_modules for fast single-file transpilation.
|
|
213
|
+
*/
|
|
214
|
+
function transpilePluginTs(pluginDir: string): void {
|
|
215
|
+
try {
|
|
216
|
+
// Resolve esbuild binary from the host opencli's node_modules
|
|
217
|
+
const thisFile = new URL(import.meta.url).pathname;
|
|
218
|
+
const hostRoot = path.resolve(path.dirname(thisFile), '..');
|
|
219
|
+
const esbuildBin = path.join(hostRoot, 'node_modules', '.bin', 'esbuild');
|
|
220
|
+
|
|
221
|
+
if (!fs.existsSync(esbuildBin)) {
|
|
222
|
+
log.debug('esbuild not found in host node_modules, skipping TS transpilation');
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const files = fs.readdirSync(pluginDir);
|
|
227
|
+
const tsFiles = files.filter(f =>
|
|
228
|
+
f.endsWith('.ts') && !f.endsWith('.d.ts') && !f.endsWith('.test.ts')
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
for (const tsFile of tsFiles) {
|
|
232
|
+
const jsFile = tsFile.replace(/\.ts$/, '.js');
|
|
233
|
+
const jsPath = path.join(pluginDir, jsFile);
|
|
234
|
+
|
|
235
|
+
// Skip if .js already exists (plugin may ship pre-compiled)
|
|
236
|
+
if (fs.existsSync(jsPath)) continue;
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
execFileSync(esbuildBin, [tsFile, `--outfile=${jsFile}`, '--format=esm', '--platform=node'], {
|
|
240
|
+
cwd: pluginDir,
|
|
241
|
+
encoding: 'utf-8',
|
|
242
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
243
|
+
});
|
|
244
|
+
log.debug(`Transpiled plugin file: ${tsFile} → ${jsFile}`);
|
|
245
|
+
} catch (err: any) {
|
|
246
|
+
log.warn(`Failed to transpile ${tsFile}: ${err.message}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
} catch {
|
|
250
|
+
// Non-fatal: skip transpilation if anything goes wrong
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export { parseSource as _parseSource };
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
|
|
10
|
+
export { cli, Strategy, getRegistry, fullName, registerCommand } from './registry.js';
|
|
11
|
+
export type { CliCommand, Arg, CliOptions } from './registry.js';
|
|
12
|
+
export type { IPage } from './types.js';
|
package/src/registry.ts
CHANGED
|
@@ -36,6 +36,17 @@ export interface CliCommand {
|
|
|
36
36
|
timeoutSeconds?: number;
|
|
37
37
|
source?: string;
|
|
38
38
|
footerExtra?: (kwargs: Record<string, any>) => string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Control pre-navigation for cookie/header context before command execution.
|
|
41
|
+
*
|
|
42
|
+
* Browser adapters using COOKIE/HEADER strategy need the page to be on the
|
|
43
|
+
* target domain so that `fetch(url, { credentials: 'include' })` carries cookies.
|
|
44
|
+
*
|
|
45
|
+
* - `undefined` / `true`: navigate to `https://${domain}` (default)
|
|
46
|
+
* - `false`: skip — adapter handles its own navigation (e.g. boss common.ts)
|
|
47
|
+
* - `string`: navigate to this specific URL instead of the domain root
|
|
48
|
+
*/
|
|
49
|
+
navigateBefore?: boolean | string;
|
|
39
50
|
}
|
|
40
51
|
|
|
41
52
|
/** Internal extension for lazy-loaded TS modules (not exposed in public API) */
|
|
@@ -49,7 +60,13 @@ export interface CliOptions extends Partial<Omit<CliCommand, 'args' | 'descripti
|
|
|
49
60
|
description?: string;
|
|
50
61
|
args?: Arg[];
|
|
51
62
|
}
|
|
52
|
-
|
|
63
|
+
|
|
64
|
+
// Use globalThis to ensure a single shared registry across all module instances.
|
|
65
|
+
// This is critical for TS plugins loaded via npm link / peerDependency — without
|
|
66
|
+
// this, the plugin's import creates a separate module instance with its own Map.
|
|
67
|
+
const REGISTRY_KEY = '__opencli_registry__';
|
|
68
|
+
const _registry: Map<string, CliCommand> =
|
|
69
|
+
(globalThis as any)[REGISTRY_KEY] ??= new Map<string, CliCommand>();
|
|
53
70
|
|
|
54
71
|
export function cli(opts: CliOptions): CliCommand {
|
|
55
72
|
const strategy = opts.strategy ?? (opts.browser === false ? Strategy.PUBLIC : Strategy.COOKIE);
|
|
@@ -67,6 +84,7 @@ export function cli(opts: CliOptions): CliCommand {
|
|
|
67
84
|
pipeline: opts.pipeline,
|
|
68
85
|
timeoutSeconds: opts.timeoutSeconds,
|
|
69
86
|
footerExtra: opts.footerExtra,
|
|
87
|
+
navigateBefore: opts.navigateBefore,
|
|
70
88
|
};
|
|
71
89
|
|
|
72
90
|
const key = fullName(cmd);
|
package/src/synthesize.ts
CHANGED
|
@@ -7,16 +7,96 @@ import * as fs from 'node:fs';
|
|
|
7
7
|
import * as path from 'node:path';
|
|
8
8
|
import yaml from 'js-yaml';
|
|
9
9
|
import { VOLATILE_PARAMS, SEARCH_PARAMS, LIMIT_PARAMS, PAGINATION_PARAMS } from './constants.js';
|
|
10
|
+
import type { ExploreAuthSummary, ExploreEndpointArtifact, ExploreManifest } from './explore.js';
|
|
10
11
|
|
|
11
12
|
/** Renamed aliases for backward compatibility with local references */
|
|
12
13
|
const SEARCH_PARAM_NAMES = SEARCH_PARAMS;
|
|
13
14
|
const LIMIT_PARAM_NAMES = LIMIT_PARAMS;
|
|
14
15
|
const PAGE_PARAM_NAMES = PAGINATION_PARAMS;
|
|
15
16
|
|
|
17
|
+
interface RecommendedArg {
|
|
18
|
+
name: string;
|
|
19
|
+
type?: string;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
default?: unknown;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface StoreHint {
|
|
25
|
+
store: string;
|
|
26
|
+
action: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface SynthesizeCapability {
|
|
30
|
+
name: string;
|
|
31
|
+
description: string;
|
|
32
|
+
strategy: string;
|
|
33
|
+
confidence?: number;
|
|
34
|
+
endpoint?: string;
|
|
35
|
+
itemPath?: string | null;
|
|
36
|
+
recommendedColumns?: string[];
|
|
37
|
+
recommendedArgs?: RecommendedArg[];
|
|
38
|
+
recommended_args?: RecommendedArg[];
|
|
39
|
+
recommendedColumnsLegacy?: string[];
|
|
40
|
+
recommended_columns?: string[];
|
|
41
|
+
storeHint?: StoreHint;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface GeneratedArgDefinition {
|
|
45
|
+
type: string;
|
|
46
|
+
required?: boolean;
|
|
47
|
+
default?: unknown;
|
|
48
|
+
description?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type CandidatePipelineStep =
|
|
52
|
+
| { navigate: string }
|
|
53
|
+
| { wait: number }
|
|
54
|
+
| { evaluate: string }
|
|
55
|
+
| { select: string }
|
|
56
|
+
| { map: Record<string, string> }
|
|
57
|
+
| { limit: string }
|
|
58
|
+
| { fetch: { url: string } }
|
|
59
|
+
| { tap: { store: string; action: string; timeout: number; capture?: string; select?: string | null } };
|
|
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
|
+
|
|
73
|
+
export interface SynthesizeCandidateSummary {
|
|
74
|
+
name: string;
|
|
75
|
+
path: string;
|
|
76
|
+
strategy: string;
|
|
77
|
+
confidence?: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface SynthesizeResult {
|
|
81
|
+
site: string;
|
|
82
|
+
explore_dir: string;
|
|
83
|
+
out_dir: string;
|
|
84
|
+
candidate_count: number;
|
|
85
|
+
candidates: SynthesizeCandidateSummary[];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type ExploreManifestLike = Pick<ExploreManifest, 'target_url' | 'final_url'> & Partial<ExploreManifest>;
|
|
89
|
+
interface LoadedExploreBundle {
|
|
90
|
+
manifest: ExploreManifest;
|
|
91
|
+
endpoints: ExploreEndpointArtifact[];
|
|
92
|
+
capabilities: SynthesizeCapability[];
|
|
93
|
+
auth: ExploreAuthSummary;
|
|
94
|
+
}
|
|
95
|
+
|
|
16
96
|
export function synthesizeFromExplore(
|
|
17
97
|
target: string,
|
|
18
98
|
opts: { outDir?: string; top?: number } = {},
|
|
19
|
-
):
|
|
99
|
+
): SynthesizeResult {
|
|
20
100
|
const exploreDir = resolveExploreDir(target);
|
|
21
101
|
const bundle = loadExploreBundle(exploreDir);
|
|
22
102
|
|
|
@@ -25,9 +105,9 @@ export function synthesizeFromExplore(
|
|
|
25
105
|
|
|
26
106
|
const site = bundle.manifest.site;
|
|
27
107
|
const capabilities = (bundle.capabilities ?? [])
|
|
28
|
-
.sort((a
|
|
108
|
+
.sort((a, b) => (b.confidence ?? 0) - (a.confidence ?? 0))
|
|
29
109
|
.slice(0, opts.top ?? 3);
|
|
30
|
-
const candidates:
|
|
110
|
+
const candidates: SynthesizeCandidateSummary[] = [];
|
|
31
111
|
|
|
32
112
|
for (const cap of capabilities) {
|
|
33
113
|
const endpoint = chooseEndpoint(cap, bundle.endpoints);
|
|
@@ -44,7 +124,7 @@ export function synthesizeFromExplore(
|
|
|
44
124
|
return { site, explore_dir: exploreDir, out_dir: targetDir, candidate_count: candidates.length, candidates };
|
|
45
125
|
}
|
|
46
126
|
|
|
47
|
-
export function renderSynthesizeSummary(result:
|
|
127
|
+
export function renderSynthesizeSummary(result: SynthesizeResult): string {
|
|
48
128
|
const lines = ['opencli synthesize: OK', `Site: ${result.site}`, `Source: ${result.explore_dir}`, `Candidates: ${result.candidate_count}`];
|
|
49
129
|
for (const c of result.candidates ?? []) lines.push(` • ${c.name} (${c.strategy}, ${((c.confidence ?? 0) * 100).toFixed(0)}% confidence) → ${c.path}`);
|
|
50
130
|
return lines.join('\n');
|
|
@@ -57,33 +137,34 @@ export function resolveExploreDir(target: string): string {
|
|
|
57
137
|
throw new Error(`Explore directory not found: ${target}`);
|
|
58
138
|
}
|
|
59
139
|
|
|
60
|
-
export function loadExploreBundle(exploreDir: string):
|
|
140
|
+
export function loadExploreBundle(exploreDir: string): LoadedExploreBundle {
|
|
61
141
|
return {
|
|
62
|
-
manifest: JSON.parse(fs.readFileSync(path.join(exploreDir, 'manifest.json'), 'utf-8')),
|
|
63
|
-
endpoints: JSON.parse(fs.readFileSync(path.join(exploreDir, 'endpoints.json'), 'utf-8')),
|
|
64
|
-
capabilities: JSON.parse(fs.readFileSync(path.join(exploreDir, 'capabilities.json'), 'utf-8')),
|
|
142
|
+
manifest: JSON.parse(fs.readFileSync(path.join(exploreDir, 'manifest.json'), 'utf-8')) as ExploreManifest,
|
|
143
|
+
endpoints: JSON.parse(fs.readFileSync(path.join(exploreDir, 'endpoints.json'), 'utf-8')) as ExploreEndpointArtifact[],
|
|
144
|
+
capabilities: JSON.parse(fs.readFileSync(path.join(exploreDir, 'capabilities.json'), 'utf-8')) as SynthesizeCapability[],
|
|
65
145
|
auth: JSON.parse(fs.readFileSync(path.join(exploreDir, 'auth.json'), 'utf-8')),
|
|
66
146
|
};
|
|
67
147
|
}
|
|
68
148
|
|
|
69
|
-
function chooseEndpoint(cap:
|
|
149
|
+
function chooseEndpoint(cap: SynthesizeCapability, endpoints: ExploreEndpointArtifact[]): ExploreEndpointArtifact | null {
|
|
70
150
|
if (!endpoints.length) return null;
|
|
71
151
|
// Match by endpoint pattern from capability
|
|
72
152
|
if (cap.endpoint) {
|
|
73
|
-
const
|
|
153
|
+
const endpointPattern = cap.endpoint;
|
|
154
|
+
const match = endpoints.find((endpoint) => endpoint.pattern === endpointPattern || endpoint.url?.includes(endpointPattern));
|
|
74
155
|
if (match) return match;
|
|
75
156
|
}
|
|
76
|
-
return endpoints.sort((a
|
|
157
|
+
return [...endpoints].sort((a, b) => (b.score ?? 0) - (a.score ?? 0))[0];
|
|
77
158
|
}
|
|
78
159
|
|
|
79
160
|
// ── URL templating ─────────────────────────────────────────────────────────
|
|
80
161
|
|
|
81
|
-
function buildTemplatedUrl(rawUrl: string, cap:
|
|
162
|
+
function buildTemplatedUrl(rawUrl: string, cap: SynthesizeCapability, _endpoint: ExploreEndpointArtifact): string {
|
|
82
163
|
try {
|
|
83
164
|
const u = new URL(rawUrl);
|
|
84
165
|
const base = `${u.protocol}//${u.host}${u.pathname}`;
|
|
85
166
|
const params: Array<[string, string]> = [];
|
|
86
|
-
const hasKeyword = cap.recommendedArgs?.some((
|
|
167
|
+
const hasKeyword = cap.recommendedArgs?.some((arg) => arg.name === 'keyword');
|
|
87
168
|
|
|
88
169
|
u.searchParams.forEach((v, k) => {
|
|
89
170
|
if (VOLATILE_PARAMS.has(k)) return;
|
|
@@ -101,7 +182,7 @@ function buildTemplatedUrl(rawUrl: string, cap: any, _endpoint: any): string {
|
|
|
101
182
|
* Build inline evaluate script for browser-based fetch+parse.
|
|
102
183
|
* Follows patterns from bilibili/hot.yaml and twitter/trending.yaml.
|
|
103
184
|
*/
|
|
104
|
-
function buildEvaluateScript(url: string, itemPath: string, endpoint:
|
|
185
|
+
function buildEvaluateScript(url: string, itemPath: string, endpoint: ExploreEndpointArtifact): string {
|
|
105
186
|
const pathChain = itemPath.split('.').map((p: string) => `?.${p}`).join('');
|
|
106
187
|
const detectedFields = endpoint?.detectedFields ?? {};
|
|
107
188
|
const hasFields = Object.keys(detectedFields).length > 0;
|
|
@@ -127,9 +208,9 @@ function buildEvaluateScript(url: string, itemPath: string, endpoint: any): stri
|
|
|
127
208
|
|
|
128
209
|
// ── YAML pipeline generation ───────────────────────────────────────────────
|
|
129
210
|
|
|
130
|
-
function buildCandidateYaml(site: string, manifest:
|
|
211
|
+
function buildCandidateYaml(site: string, manifest: ExploreManifestLike, cap: SynthesizeCapability, endpoint: ExploreEndpointArtifact): { name: string; yaml: CandidateYaml } {
|
|
131
212
|
const needsBrowser = cap.strategy !== 'public';
|
|
132
|
-
const pipeline:
|
|
213
|
+
const pipeline: CandidatePipelineStep[] = [];
|
|
133
214
|
const templatedUrl = buildTemplatedUrl(endpoint?.url ?? manifest.target_url, cap, endpoint);
|
|
134
215
|
|
|
135
216
|
let domain = '';
|
|
@@ -139,7 +220,7 @@ function buildCandidateYaml(site: string, manifest: any, cap: any, endpoint: any
|
|
|
139
220
|
// Store Action: navigate + wait + tap (declarative, clean)
|
|
140
221
|
pipeline.push({ navigate: manifest.target_url });
|
|
141
222
|
pipeline.push({ wait: 3 });
|
|
142
|
-
const tapStep:
|
|
223
|
+
const tapStep: { store: string; action: string; timeout: number; capture?: string; select?: string | null } = {
|
|
143
224
|
store: cap.storeHint.store,
|
|
144
225
|
action: cap.storeHint.action,
|
|
145
226
|
timeout: 8,
|
|
@@ -170,7 +251,7 @@ function buildCandidateYaml(site: string, manifest: any, cap: any, endpoint: any
|
|
|
170
251
|
// Map fields
|
|
171
252
|
const mapStep: Record<string, string> = {};
|
|
172
253
|
const columns = cap.recommendedColumns ?? ['title', 'url'];
|
|
173
|
-
if (!cap.recommendedArgs?.some((
|
|
254
|
+
if (!cap.recommendedArgs?.some((arg) => arg.name === 'keyword')) mapStep['rank'] = '${{ index + 1 }}';
|
|
174
255
|
const detectedFields = endpoint?.detectedFields ?? {};
|
|
175
256
|
for (const col of columns) {
|
|
176
257
|
const fieldPath = detectedFields[col];
|
|
@@ -180,9 +261,9 @@ function buildCandidateYaml(site: string, manifest: any, cap: any, endpoint: any
|
|
|
180
261
|
pipeline.push({ limit: '${{ args.limit | default(20) }}' });
|
|
181
262
|
|
|
182
263
|
// Args
|
|
183
|
-
const argsDef: Record<string,
|
|
264
|
+
const argsDef: Record<string, GeneratedArgDefinition> = {};
|
|
184
265
|
for (const arg of cap.recommendedArgs ?? []) {
|
|
185
|
-
const def:
|
|
266
|
+
const def: GeneratedArgDefinition = { type: arg.type ?? 'str' };
|
|
186
267
|
if (arg.required) def.required = true;
|
|
187
268
|
if (arg.default != null) def.default = arg.default;
|
|
188
269
|
if (arg.name === 'keyword') def.description = 'Search keyword';
|
|
@@ -203,7 +284,7 @@ function buildCandidateYaml(site: string, manifest: any, cap: any, endpoint: any
|
|
|
203
284
|
}
|
|
204
285
|
|
|
205
286
|
/** Backward-compatible export for scaffold.ts */
|
|
206
|
-
export function buildCandidate(site: string, targetUrl: string, cap:
|
|
287
|
+
export function buildCandidate(site: string, targetUrl: string, cap: SynthesizeCapability, endpoint: ExploreEndpointArtifact): { name: string; yaml: CandidateYaml } {
|
|
207
288
|
// Map old-style field names to new ones
|
|
208
289
|
const normalizedCap = {
|
|
209
290
|
...cap,
|
package/src/types.ts
CHANGED
|
@@ -5,23 +5,55 @@
|
|
|
5
5
|
* instead of `any` for browser interactions.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
export interface BrowserCookie {
|
|
9
|
+
name: string;
|
|
10
|
+
value: string;
|
|
11
|
+
domain: string;
|
|
12
|
+
path?: string;
|
|
13
|
+
secure?: boolean;
|
|
14
|
+
httpOnly?: boolean;
|
|
15
|
+
expirationDate?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface SnapshotOptions {
|
|
19
|
+
interactive?: boolean;
|
|
20
|
+
compact?: boolean;
|
|
21
|
+
maxDepth?: number;
|
|
22
|
+
raw?: boolean;
|
|
23
|
+
viewportExpand?: number;
|
|
24
|
+
maxTextLength?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface WaitOptions {
|
|
28
|
+
text?: string;
|
|
29
|
+
time?: number;
|
|
30
|
+
timeout?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ScreenshotOptions {
|
|
34
|
+
format?: 'png' | 'jpeg';
|
|
35
|
+
quality?: number;
|
|
36
|
+
fullPage?: boolean;
|
|
37
|
+
path?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface BrowserSessionInfo {
|
|
41
|
+
workspace?: string;
|
|
42
|
+
connected?: boolean;
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
8
46
|
export interface IPage {
|
|
9
47
|
goto(url: string, options?: { waitUntil?: 'load' | 'none'; settleMs?: number }): Promise<void>;
|
|
10
48
|
evaluate(js: string): Promise<any>;
|
|
11
|
-
getCookies(opts?: { domain?: string; url?: string }): Promise<
|
|
12
|
-
|
|
13
|
-
value: string;
|
|
14
|
-
domain: string;
|
|
15
|
-
path?: string;
|
|
16
|
-
secure?: boolean;
|
|
17
|
-
httpOnly?: boolean;
|
|
18
|
-
expirationDate?: number;
|
|
19
|
-
}>>;
|
|
20
|
-
snapshot(opts?: { interactive?: boolean; compact?: boolean; maxDepth?: number; raw?: boolean }): Promise<any>;
|
|
49
|
+
getCookies(opts?: { domain?: string; url?: string }): Promise<BrowserCookie[]>;
|
|
50
|
+
snapshot(opts?: SnapshotOptions): Promise<any>;
|
|
21
51
|
click(ref: string): Promise<void>;
|
|
22
52
|
typeText(ref: string, text: string): Promise<void>;
|
|
23
53
|
pressKey(key: string): Promise<void>;
|
|
24
|
-
|
|
54
|
+
scrollTo(ref: string): Promise<any>;
|
|
55
|
+
getFormState(): Promise<any>;
|
|
56
|
+
wait(options: number | WaitOptions): Promise<void>;
|
|
25
57
|
tabs(): Promise<any>;
|
|
26
58
|
closeTab(index?: number): Promise<void>;
|
|
27
59
|
newTab(): Promise<void>;
|
|
@@ -32,5 +64,5 @@ export interface IPage {
|
|
|
32
64
|
autoScroll(options?: { times?: number; delayMs?: number }): Promise<void>;
|
|
33
65
|
installInterceptor(pattern: string): Promise<void>;
|
|
34
66
|
getInterceptedRequests(): Promise<any[]>;
|
|
35
|
-
screenshot(options?:
|
|
67
|
+
screenshot(options?: ScreenshotOptions): Promise<string>;
|
|
36
68
|
}
|
package/src/validate.ts
CHANGED
|
@@ -25,6 +25,22 @@ export interface ValidationReport {
|
|
|
25
25
|
files: number;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
interface ValidatedYamlCliDefinition {
|
|
29
|
+
site?: string;
|
|
30
|
+
name?: string;
|
|
31
|
+
pipeline?: unknown[];
|
|
32
|
+
columns?: unknown[];
|
|
33
|
+
args?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
37
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getErrorMessage(error: unknown): string {
|
|
41
|
+
return error instanceof Error ? error.message : String(error);
|
|
42
|
+
}
|
|
43
|
+
|
|
28
44
|
export function validateClisWithTarget(dirs: string[], target?: string): ValidationReport {
|
|
29
45
|
const results: FileValidationResult[] = [];
|
|
30
46
|
let errors = 0; let warnings = 0; let files = 0;
|
|
@@ -53,8 +69,8 @@ function validateYamlFile(filePath: string): FileValidationResult {
|
|
|
53
69
|
const errors: string[] = []; const warnings: string[] = [];
|
|
54
70
|
try {
|
|
55
71
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
56
|
-
const def = yaml.load(raw) as
|
|
57
|
-
if (!def
|
|
72
|
+
const def = yaml.load(raw) as ValidatedYamlCliDefinition | null;
|
|
73
|
+
if (!isRecord(def)) { errors.push('Not a valid YAML object'); return { path: filePath, errors, warnings }; }
|
|
58
74
|
if (!def.site) errors.push('Missing "site"');
|
|
59
75
|
if (!def.name) errors.push('Missing "name"');
|
|
60
76
|
if (def.pipeline && !Array.isArray(def.pipeline)) errors.push('"pipeline" must be an array');
|
|
@@ -74,7 +90,7 @@ function validateYamlFile(filePath: string): FileValidationResult {
|
|
|
74
90
|
}
|
|
75
91
|
}
|
|
76
92
|
}
|
|
77
|
-
} catch (e
|
|
93
|
+
} catch (e) { errors.push(`YAML parse error: ${getErrorMessage(e)}`); }
|
|
78
94
|
return { path: filePath, errors, warnings };
|
|
79
95
|
}
|
|
80
96
|
|
|
@@ -89,4 +105,3 @@ export function renderValidationReport(report: ValidationReport): string {
|
|
|
89
105
|
}
|
|
90
106
|
return lines.join('\n');
|
|
91
107
|
}
|
|
92
|
-
|
|
@@ -186,6 +186,17 @@ describe('browser public-data commands E2E', () => {
|
|
|
186
186
|
expectDataOrSkip(data, 'xiaohongshu search');
|
|
187
187
|
}, 60_000);
|
|
188
188
|
|
|
189
|
+
// ── google search (browser: true, public strategy) ──
|
|
190
|
+
it('google search returns results', async () => {
|
|
191
|
+
const data = await tryBrowserCommand(['google', 'search', 'typescript', '--limit', '5', '-f', 'json']);
|
|
192
|
+
expectDataOrSkip(data, 'google search');
|
|
193
|
+
if (data) {
|
|
194
|
+
expect(data[0]).toHaveProperty('type');
|
|
195
|
+
expect(data[0]).toHaveProperty('title');
|
|
196
|
+
expect(data[0]).toHaveProperty('url');
|
|
197
|
+
}
|
|
198
|
+
}, 60_000);
|
|
199
|
+
|
|
189
200
|
// ── yahoo-finance (browser: true) ──
|
|
190
201
|
it('yahoo-finance quote returns stock data', async () => {
|
|
191
202
|
const data = await tryBrowserCommand(['yahoo-finance', 'quote', '--symbol', 'AAPL', '-f', 'json']);
|