@jackwener/opencli 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +39 -1
- package/README.md +9 -10
- package/README.zh-CN.md +39 -17
- package/SKILL.md +10 -5
- package/dist/browser/cdp.d.ts +4 -4
- package/dist/browser/cdp.js +39 -16
- package/dist/browser/daemon-client.d.ts +2 -1
- package/dist/browser/dom-helpers.js +38 -7
- package/dist/browser/dom-snapshot.d.ts +86 -0
- package/dist/browser/dom-snapshot.js +729 -0
- package/dist/browser/dom-snapshot.test.d.ts +11 -0
- package/dist/browser/dom-snapshot.test.js +212 -0
- package/dist/browser/index.d.ts +2 -0
- package/dist/browser/index.js +1 -0
- package/dist/browser/page.d.ts +14 -24
- package/dist/browser/page.js +37 -4
- package/dist/build-manifest.d.ts +11 -4
- package/dist/build-manifest.js +59 -21
- package/dist/build-manifest.test.js +58 -2
- package/dist/cli-manifest.json +3856 -1509
- package/dist/cli.js +66 -0
- package/dist/clis/barchart/greeks.js +1 -1
- package/dist/clis/barchart/options.js +1 -1
- package/dist/clis/barchart/quote.js +1 -1
- package/dist/clis/bilibili/download.js +1 -1
- package/dist/clis/bilibili/following.js +1 -1
- package/dist/clis/bilibili/subtitle.js +1 -1
- package/dist/clis/bilibili/user-videos.js +1 -1
- package/dist/clis/boss/batchgreet.js +10 -97
- package/dist/clis/boss/chatlist.js +8 -25
- package/dist/clis/boss/chatmsg.js +11 -42
- package/dist/clis/boss/common.d.ts +92 -0
- package/dist/clis/boss/common.js +223 -0
- package/dist/clis/boss/detail.js +7 -49
- package/dist/clis/boss/exchange.js +13 -79
- package/dist/clis/boss/greet.js +18 -145
- package/dist/clis/boss/invite.js +26 -121
- package/dist/clis/boss/joblist.js +6 -31
- package/dist/clis/boss/mark.js +12 -85
- package/dist/clis/boss/recommend.js +10 -49
- package/dist/clis/boss/resume.js +18 -118
- package/dist/clis/boss/search.js +12 -60
- package/dist/clis/boss/send.js +17 -151
- package/dist/clis/boss/stats.js +18 -69
- package/dist/clis/coupang/add-to-cart.js +1 -1
- package/dist/clis/devto/tag.yaml +34 -0
- package/dist/clis/devto/top.yaml +29 -0
- package/dist/clis/devto/user.yaml +33 -0
- package/dist/clis/douban/book-hot.d.ts +1 -0
- package/dist/clis/douban/book-hot.js +14 -0
- package/dist/clis/douban/marks.d.ts +1 -0
- package/dist/clis/douban/marks.js +115 -0
- package/dist/clis/douban/movie-hot.d.ts +1 -0
- package/dist/clis/douban/movie-hot.js +14 -0
- package/dist/clis/douban/reviews.d.ts +1 -0
- package/dist/clis/douban/reviews.js +106 -0
- package/dist/clis/douban/search.d.ts +1 -0
- package/dist/clis/douban/search.js +16 -0
- package/dist/clis/douban/shared.d.ts +4 -0
- package/dist/clis/douban/shared.js +155 -0
- package/dist/clis/douban/subject.yaml +76 -0
- package/dist/clis/douban/top250.yaml +70 -0
- package/dist/clis/douban/utils.d.ts +35 -0
- package/dist/clis/douban/utils.js +48 -0
- package/dist/clis/facebook/add-friend.yaml +43 -0
- package/dist/clis/facebook/events.yaml +44 -0
- package/dist/clis/facebook/feed.yaml +63 -0
- package/dist/clis/facebook/friends.yaml +42 -0
- package/dist/clis/facebook/groups.yaml +50 -0
- package/dist/clis/facebook/join-group.yaml +44 -0
- package/dist/clis/facebook/memories.yaml +39 -0
- package/dist/clis/facebook/notifications.yaml +40 -0
- package/dist/clis/facebook/profile.yaml +37 -0
- package/dist/clis/facebook/search.yaml +46 -0
- package/dist/clis/google/news.d.ts +5 -0
- package/dist/clis/google/news.js +58 -0
- package/dist/clis/google/search.d.ts +10 -0
- package/dist/clis/google/search.js +127 -0
- package/dist/clis/google/suggest.d.ts +5 -0
- package/dist/clis/google/suggest.js +34 -0
- package/dist/clis/google/trends.d.ts +5 -0
- package/dist/clis/google/trends.js +38 -0
- package/dist/clis/google/utils.d.ts +9 -0
- package/dist/clis/google/utils.js +23 -0
- package/dist/clis/google/utils.test.d.ts +1 -0
- package/dist/clis/google/utils.test.js +75 -0
- package/dist/clis/grok/ask.d.ts +14 -0
- package/dist/clis/grok/ask.js +257 -65
- package/dist/clis/grok/ask.test.d.ts +1 -0
- package/dist/clis/grok/ask.test.js +36 -0
- package/dist/clis/instagram/comment.yaml +52 -0
- package/dist/clis/instagram/explore.yaml +43 -0
- package/dist/clis/instagram/follow.yaml +41 -0
- package/dist/clis/instagram/followers.yaml +51 -0
- package/dist/clis/instagram/following.yaml +51 -0
- package/dist/clis/instagram/like.yaml +46 -0
- package/dist/clis/instagram/profile.yaml +42 -0
- package/dist/clis/instagram/save.yaml +46 -0
- package/dist/clis/instagram/saved.yaml +40 -0
- package/dist/clis/instagram/search.yaml +43 -0
- package/dist/clis/instagram/unfollow.yaml +38 -0
- package/dist/clis/instagram/unlike.yaml +46 -0
- package/dist/clis/instagram/unsave.yaml +46 -0
- package/dist/clis/instagram/user.yaml +54 -0
- package/dist/clis/jike/repost.js +1 -1
- package/dist/clis/jimeng/generate.yaml +1 -0
- package/dist/clis/linux-do/category.yaml +1 -0
- package/dist/clis/lobsters/active.yaml +29 -0
- package/dist/clis/lobsters/hot.yaml +29 -0
- package/dist/clis/lobsters/newest.yaml +29 -0
- package/dist/clis/lobsters/tag.yaml +34 -0
- package/dist/clis/medium/feed.d.ts +1 -0
- package/dist/clis/medium/feed.js +15 -0
- package/dist/clis/medium/search.d.ts +1 -0
- package/dist/clis/medium/search.js +15 -0
- package/dist/clis/medium/shared.d.ts +5 -0
- package/dist/clis/medium/shared.js +78 -0
- package/dist/clis/medium/user.d.ts +1 -0
- package/dist/clis/medium/user.js +15 -0
- package/dist/clis/reddit/comment.js +1 -1
- package/dist/clis/reddit/read.js +1 -1
- package/dist/clis/reddit/save.js +1 -1
- package/dist/clis/reddit/subreddit.yaml +1 -0
- package/dist/clis/reddit/subscribe.js +1 -1
- package/dist/clis/reddit/upvote.js +1 -1
- package/dist/clis/sinablog/article.d.ts +1 -0
- package/dist/clis/sinablog/article.js +14 -0
- package/dist/clis/sinablog/hot.d.ts +1 -0
- package/dist/clis/sinablog/hot.js +14 -0
- package/dist/clis/sinablog/search.d.ts +1 -0
- package/dist/clis/sinablog/search.js +51 -0
- package/dist/clis/sinablog/shared.d.ts +7 -0
- package/dist/clis/sinablog/shared.js +187 -0
- package/dist/clis/sinablog/user.d.ts +1 -0
- package/dist/clis/sinablog/user.js +15 -0
- package/dist/clis/substack/feed.d.ts +1 -0
- package/dist/clis/substack/feed.js +15 -0
- package/dist/clis/substack/publication.d.ts +1 -0
- package/dist/clis/substack/publication.js +15 -0
- package/dist/clis/substack/search.d.ts +1 -0
- package/dist/clis/substack/search.js +77 -0
- package/dist/clis/substack/shared.d.ts +4 -0
- package/dist/clis/substack/shared.js +129 -0
- package/dist/clis/tiktok/comment.yaml +66 -0
- package/dist/clis/tiktok/explore.yaml +39 -0
- package/dist/clis/tiktok/follow.yaml +39 -0
- package/dist/clis/tiktok/following.yaml +46 -0
- package/dist/clis/tiktok/friends.yaml +47 -0
- package/dist/clis/tiktok/like.yaml +38 -0
- package/dist/clis/tiktok/live.yaml +51 -0
- package/dist/clis/tiktok/notifications.yaml +52 -0
- package/dist/clis/tiktok/profile.yaml +45 -0
- package/dist/clis/tiktok/save.yaml +34 -0
- package/dist/clis/tiktok/search.yaml +46 -0
- package/dist/clis/tiktok/unfollow.yaml +44 -0
- package/dist/clis/tiktok/unlike.yaml +38 -0
- package/dist/clis/tiktok/unsave.yaml +36 -0
- package/dist/clis/tiktok/user.yaml +44 -0
- package/dist/clis/twitter/download.d.ts +1 -1
- package/dist/clis/twitter/download.js +3 -3
- package/dist/clis/twitter/followers.js +1 -1
- package/dist/clis/twitter/following.js +1 -1
- package/dist/clis/twitter/thread.js +1 -1
- package/dist/clis/twitter/timeline.d.ts +23 -0
- package/dist/clis/twitter/timeline.js +42 -14
- package/dist/clis/twitter/timeline.test.d.ts +1 -0
- package/dist/clis/twitter/timeline.test.js +102 -0
- package/dist/clis/wikipedia/random.d.ts +1 -0
- package/dist/clis/wikipedia/random.js +19 -0
- package/dist/clis/wikipedia/search.js +3 -3
- package/dist/clis/wikipedia/summary.js +4 -9
- package/dist/clis/wikipedia/trending.d.ts +1 -0
- package/dist/clis/wikipedia/trending.js +35 -0
- package/dist/clis/wikipedia/utils.d.ts +28 -0
- package/dist/clis/wikipedia/utils.js +13 -0
- package/dist/clis/xiaohongshu/creator-note-detail.js +1 -1
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +2 -0
- package/dist/clis/xiaohongshu/creator-notes.test.js +2 -0
- package/dist/clis/xiaohongshu/download.js +1 -1
- package/dist/clis/xueqiu/earnings-date.yaml +69 -0
- package/dist/clis/xueqiu/search.yaml +2 -1
- package/dist/clis/xueqiu/stock.yaml +2 -0
- package/dist/clis/yahoo-finance/quote.js +1 -1
- package/dist/commanderAdapter.js +13 -7
- package/dist/discovery.d.ts +8 -0
- package/dist/discovery.js +105 -19
- package/dist/doctor.js +3 -1
- package/dist/doctor.test.js +46 -2
- package/dist/engine.test.d.ts +0 -3
- package/dist/engine.test.js +74 -6
- package/dist/execution.d.ts +4 -2
- package/dist/execution.js +31 -7
- package/dist/explore.d.ts +76 -3
- package/dist/explore.js +11 -4
- package/dist/generate.d.ts +41 -2
- package/dist/generate.js +5 -4
- package/dist/main.js +2 -1
- package/dist/pipeline/executor.d.ts +2 -2
- package/dist/pipeline/executor.js +2 -2
- package/dist/pipeline/executor.test.js +33 -6
- package/dist/pipeline/registry.d.ts +1 -1
- package/dist/pipeline/steps/browser.d.ts +7 -7
- package/dist/pipeline/steps/browser.js +15 -7
- package/dist/pipeline/steps/fetch.d.ts +1 -1
- package/dist/pipeline/steps/fetch.js +11 -7
- package/dist/pipeline/steps/transform.d.ts +6 -5
- package/dist/pipeline/steps/transform.js +30 -9
- package/dist/pipeline/template.d.ts +6 -6
- package/dist/pipeline/template.js +43 -5
- package/dist/pipeline/template.test.js +18 -0
- package/dist/pipeline/transform.test.js +11 -0
- package/dist/plugin.d.ts +31 -0
- package/dist/plugin.js +216 -0
- package/dist/plugin.test.d.ts +4 -0
- package/dist/plugin.test.js +76 -0
- package/dist/registry-api.d.ts +11 -0
- package/dist/registry-api.js +9 -0
- package/dist/registry.d.ts +11 -0
- package/dist/registry.js +6 -1
- package/dist/synthesize.d.ts +94 -4
- package/dist/synthesize.js +5 -4
- package/dist/types.d.ts +39 -26
- package/dist/validate.js +8 -2
- package/docs/.vitepress/config.mts +6 -4
- package/docs/adapters/browser/barchart.md +6 -5
- package/docs/adapters/browser/bilibili.md +9 -0
- package/docs/adapters/browser/devto.md +35 -0
- package/docs/adapters/browser/douban.md +38 -0
- package/docs/adapters/browser/facebook.md +36 -0
- package/docs/adapters/browser/google.md +62 -0
- package/docs/adapters/browser/grok.md +26 -8
- package/docs/adapters/browser/instagram.md +46 -0
- package/docs/adapters/browser/lobsters.md +32 -0
- package/docs/adapters/browser/medium.md +32 -0
- package/docs/adapters/browser/reddit.md +9 -0
- package/docs/adapters/browser/sinablog.md +36 -0
- package/docs/adapters/browser/substack.md +38 -0
- package/docs/adapters/browser/tiktok.md +68 -0
- package/docs/adapters/browser/wikipedia.md +11 -2
- package/docs/adapters/browser/xueqiu.md +10 -0
- package/docs/adapters/browser/yahoo-finance.md +6 -5
- package/docs/adapters/desktop/antigravity.md +6 -0
- package/docs/adapters/desktop/chatgpt.md +2 -1
- package/docs/adapters/desktop/codex.md +5 -1
- package/docs/adapters/desktop/cursor.md +4 -0
- package/docs/adapters/desktop/discord.md +7 -7
- package/docs/adapters/index.md +1 -4
- package/docs/guide/getting-started.md +1 -0
- package/docs/guide/plugins.md +153 -0
- package/docs/zh/guide/plugins.md +107 -0
- package/extension/src/background.ts +18 -11
- package/package.json +10 -5
- package/scripts/clean-dist.cjs +13 -0
- package/src/browser/cdp.ts +71 -31
- package/src/browser/daemon-client.ts +2 -1
- package/src/browser/dom-helpers.ts +38 -7
- package/src/browser/dom-snapshot.test.ts +249 -0
- package/src/browser/dom-snapshot.ts +770 -0
- package/src/browser/index.ts +2 -0
- package/src/browser/page.ts +50 -19
- package/src/build-manifest.test.ts +70 -2
- package/src/build-manifest.ts +94 -26
- package/src/cli.ts +71 -2
- package/src/clis/barchart/greeks.ts +1 -1
- package/src/clis/barchart/options.ts +1 -1
- package/src/clis/barchart/quote.ts +1 -1
- package/src/clis/bilibili/download.ts +1 -1
- package/src/clis/bilibili/following.ts +1 -1
- package/src/clis/bilibili/subtitle.ts +1 -1
- package/src/clis/bilibili/user-videos.ts +1 -1
- package/src/clis/boss/batchgreet.ts +14 -106
- package/src/clis/boss/chatlist.ts +12 -26
- package/src/clis/boss/chatmsg.ts +16 -40
- package/src/clis/boss/common.ts +287 -0
- package/src/clis/boss/detail.ts +8 -54
- package/src/clis/boss/exchange.ts +15 -89
- package/src/clis/boss/greet.ts +23 -160
- package/src/clis/boss/invite.ts +36 -133
- package/src/clis/boss/joblist.ts +7 -36
- package/src/clis/boss/mark.ts +13 -94
- package/src/clis/boss/recommend.ts +12 -57
- package/src/clis/boss/resume.ts +19 -124
- package/src/clis/boss/search.ts +13 -66
- package/src/clis/boss/send.ts +21 -161
- package/src/clis/boss/stats.ts +19 -74
- package/src/clis/coupang/add-to-cart.ts +1 -1
- package/src/clis/devto/tag.yaml +34 -0
- package/src/clis/devto/top.yaml +29 -0
- package/src/clis/devto/user.yaml +33 -0
- package/src/clis/douban/book-hot.ts +15 -0
- package/src/clis/douban/marks.ts +135 -0
- package/src/clis/douban/movie-hot.ts +15 -0
- package/src/clis/douban/reviews.ts +127 -0
- package/src/clis/douban/search.ts +17 -0
- package/src/clis/douban/shared.ts +165 -0
- package/src/clis/douban/subject.yaml +76 -0
- package/src/clis/douban/top250.yaml +70 -0
- package/src/clis/douban/utils.ts +81 -0
- package/src/clis/facebook/add-friend.yaml +43 -0
- package/src/clis/facebook/events.yaml +44 -0
- package/src/clis/facebook/feed.yaml +63 -0
- package/src/clis/facebook/friends.yaml +42 -0
- package/src/clis/facebook/groups.yaml +50 -0
- package/src/clis/facebook/join-group.yaml +44 -0
- package/src/clis/facebook/memories.yaml +39 -0
- package/src/clis/facebook/notifications.yaml +40 -0
- package/src/clis/facebook/profile.yaml +37 -0
- package/src/clis/facebook/search.yaml +46 -0
- package/src/clis/google/news.ts +66 -0
- package/src/clis/google/search.ts +133 -0
- package/src/clis/google/suggest.ts +40 -0
- package/src/clis/google/trends.ts +44 -0
- package/src/clis/google/utils.test.ts +82 -0
- package/src/clis/google/utils.ts +24 -0
- package/src/clis/grok/ask.test.ts +53 -0
- package/src/clis/grok/ask.ts +300 -69
- package/src/clis/instagram/comment.yaml +52 -0
- package/src/clis/instagram/explore.yaml +43 -0
- package/src/clis/instagram/follow.yaml +41 -0
- package/src/clis/instagram/followers.yaml +51 -0
- package/src/clis/instagram/following.yaml +51 -0
- package/src/clis/instagram/like.yaml +46 -0
- package/src/clis/instagram/profile.yaml +42 -0
- package/src/clis/instagram/save.yaml +46 -0
- package/src/clis/instagram/saved.yaml +40 -0
- package/src/clis/instagram/search.yaml +43 -0
- package/src/clis/instagram/unfollow.yaml +38 -0
- package/src/clis/instagram/unlike.yaml +46 -0
- package/src/clis/instagram/unsave.yaml +46 -0
- package/src/clis/instagram/user.yaml +54 -0
- package/src/clis/jike/repost.ts +1 -1
- package/src/clis/jimeng/generate.yaml +1 -0
- package/src/clis/linux-do/category.yaml +1 -0
- package/src/clis/lobsters/active.yaml +29 -0
- package/src/clis/lobsters/hot.yaml +29 -0
- package/src/clis/lobsters/newest.yaml +29 -0
- package/src/clis/lobsters/tag.yaml +34 -0
- package/src/clis/medium/feed.ts +16 -0
- package/src/clis/medium/search.ts +16 -0
- package/src/clis/medium/shared.ts +83 -0
- package/src/clis/medium/user.ts +16 -0
- package/src/clis/reddit/comment.ts +1 -1
- package/src/clis/reddit/read.ts +1 -1
- package/src/clis/reddit/save.ts +1 -1
- package/src/clis/reddit/subreddit.yaml +1 -0
- package/src/clis/reddit/subscribe.ts +1 -1
- package/src/clis/reddit/upvote.ts +1 -1
- package/src/clis/sinablog/article.ts +15 -0
- package/src/clis/sinablog/hot.ts +15 -0
- package/src/clis/sinablog/search.ts +56 -0
- package/src/clis/sinablog/shared.ts +198 -0
- package/src/clis/sinablog/user.ts +16 -0
- package/src/clis/substack/feed.ts +16 -0
- package/src/clis/substack/publication.ts +16 -0
- package/src/clis/substack/search.ts +91 -0
- package/src/clis/substack/shared.ts +132 -0
- package/src/clis/tiktok/comment.yaml +66 -0
- package/src/clis/tiktok/explore.yaml +39 -0
- package/src/clis/tiktok/follow.yaml +39 -0
- package/src/clis/tiktok/following.yaml +46 -0
- package/src/clis/tiktok/friends.yaml +47 -0
- package/src/clis/tiktok/like.yaml +38 -0
- package/src/clis/tiktok/live.yaml +51 -0
- package/src/clis/tiktok/notifications.yaml +52 -0
- package/src/clis/tiktok/profile.yaml +45 -0
- package/src/clis/tiktok/save.yaml +34 -0
- package/src/clis/tiktok/search.yaml +46 -0
- package/src/clis/tiktok/unfollow.yaml +44 -0
- package/src/clis/tiktok/unlike.yaml +38 -0
- package/src/clis/tiktok/unsave.yaml +36 -0
- package/src/clis/tiktok/user.yaml +44 -0
- package/src/clis/twitter/download.ts +3 -3
- package/src/clis/twitter/followers.ts +1 -1
- package/src/clis/twitter/following.ts +1 -1
- package/src/clis/twitter/thread.ts +1 -1
- package/src/clis/twitter/timeline.test.ts +109 -0
- package/src/clis/twitter/timeline.ts +59 -19
- package/src/clis/wikipedia/random.ts +19 -0
- package/src/clis/wikipedia/search.ts +10 -4
- package/src/clis/wikipedia/summary.ts +4 -9
- package/src/clis/wikipedia/trending.ts +41 -0
- package/src/clis/wikipedia/utils.ts +31 -0
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +2 -0
- package/src/clis/xiaohongshu/creator-note-detail.ts +1 -1
- package/src/clis/xiaohongshu/creator-notes.test.ts +2 -0
- package/src/clis/xiaohongshu/download.ts +1 -1
- package/src/clis/xueqiu/earnings-date.yaml +69 -0
- package/src/clis/xueqiu/search.yaml +2 -1
- package/src/clis/xueqiu/stock.yaml +2 -0
- package/src/clis/yahoo-finance/quote.ts +1 -1
- package/src/commanderAdapter.ts +17 -10
- package/src/discovery.ts +134 -24
- package/src/doctor.test.ts +59 -2
- package/src/doctor.ts +4 -2
- package/src/engine.test.ts +79 -6
- package/src/execution.ts +42 -16
- package/src/explore.ts +77 -9
- package/src/generate.ts +58 -9
- package/src/main.ts +2 -1
- package/src/pipeline/executor.test.ts +35 -6
- package/src/pipeline/executor.ts +11 -7
- package/src/pipeline/registry.ts +3 -3
- package/src/pipeline/steps/browser.ts +24 -15
- package/src/pipeline/steps/fetch.ts +18 -13
- package/src/pipeline/steps/transform.ts +40 -15
- package/src/pipeline/template.test.ts +18 -0
- package/src/pipeline/template.ts +86 -13
- package/src/pipeline/transform.test.ts +15 -2
- package/src/plugin.test.ts +86 -0
- package/src/plugin.ts +254 -0
- package/src/registry-api.ts +12 -0
- package/src/registry.ts +19 -1
- package/src/synthesize.ts +102 -21
- package/src/types.ts +44 -12
- package/src/validate.ts +19 -4
- package/tests/e2e/browser-public.test.ts +11 -0
- package/tests/e2e/public-commands.test.ts +64 -0
- package/dist/clis/feishu/new.d.ts +0 -1
- package/dist/clis/feishu/new.js +0 -27
- package/dist/clis/feishu/read.d.ts +0 -1
- package/dist/clis/feishu/read.js +0 -40
- package/dist/clis/feishu/search.d.ts +0 -1
- package/dist/clis/feishu/search.js +0 -30
- package/dist/clis/feishu/send.d.ts +0 -1
- package/dist/clis/feishu/send.js +0 -39
- package/dist/clis/feishu/status.d.ts +0 -1
- package/dist/clis/feishu/status.js +0 -28
- package/dist/clis/neteasemusic/like.d.ts +0 -1
- package/dist/clis/neteasemusic/like.js +0 -25
- package/dist/clis/neteasemusic/lyrics.d.ts +0 -1
- package/dist/clis/neteasemusic/lyrics.js +0 -47
- package/dist/clis/neteasemusic/next.d.ts +0 -1
- package/dist/clis/neteasemusic/next.js +0 -26
- package/dist/clis/neteasemusic/play.d.ts +0 -1
- package/dist/clis/neteasemusic/play.js +0 -26
- package/dist/clis/neteasemusic/playing.d.ts +0 -1
- package/dist/clis/neteasemusic/playing.js +0 -59
- package/dist/clis/neteasemusic/playlist.d.ts +0 -1
- package/dist/clis/neteasemusic/playlist.js +0 -46
- package/dist/clis/neteasemusic/prev.d.ts +0 -1
- package/dist/clis/neteasemusic/prev.js +0 -25
- package/dist/clis/neteasemusic/search.d.ts +0 -1
- package/dist/clis/neteasemusic/search.js +0 -52
- package/dist/clis/neteasemusic/status.d.ts +0 -1
- package/dist/clis/neteasemusic/status.js +0 -16
- package/dist/clis/neteasemusic/volume.d.ts +0 -1
- package/dist/clis/neteasemusic/volume.js +0 -54
- package/dist/clis/wechat/chats.d.ts +0 -1
- package/dist/clis/wechat/chats.js +0 -28
- package/dist/clis/wechat/contacts.d.ts +0 -1
- package/dist/clis/wechat/contacts.js +0 -28
- package/dist/clis/wechat/read.d.ts +0 -1
- package/dist/clis/wechat/read.js +0 -58
- package/dist/clis/wechat/search.d.ts +0 -1
- package/dist/clis/wechat/search.js +0 -31
- package/dist/clis/wechat/send.d.ts +0 -1
- package/dist/clis/wechat/send.js +0 -42
- package/dist/clis/wechat/status.d.ts +0 -1
- package/dist/clis/wechat/status.js +0 -29
- package/dist/pipeline.d.ts +0 -7
- package/dist/pipeline.js +0 -7
- package/docs/adapters/browser/github.md +0 -26
- package/docs/adapters/desktop/feishu.md +0 -20
- package/docs/adapters/desktop/neteasemusic.md +0 -31
- package/docs/adapters/desktop/wechat.md +0 -28
- package/src/clis/antigravity/README.md +0 -5
- package/src/clis/antigravity/README.zh-CN.md +0 -51
- package/src/clis/chaoxing/README.md +0 -14
- package/src/clis/chaoxing/README.zh-CN.md +0 -35
- package/src/clis/chatgpt/README.md +0 -5
- package/src/clis/chatgpt/README.zh-CN.md +0 -44
- package/src/clis/chatwise/README.md +0 -5
- package/src/clis/chatwise/README.zh-CN.md +0 -38
- package/src/clis/codex/README.md +0 -5
- package/src/clis/codex/README.zh-CN.md +0 -33
- package/src/clis/cursor/README.md +0 -5
- package/src/clis/cursor/README.zh-CN.md +0 -33
- package/src/clis/discord-app/README.md +0 -5
- package/src/clis/discord-app/README.zh-CN.md +0 -28
- package/src/clis/feishu/README.md +0 -5
- package/src/clis/feishu/README.zh-CN.md +0 -20
- package/src/clis/feishu/new.ts +0 -32
- package/src/clis/feishu/read.ts +0 -48
- package/src/clis/feishu/search.ts +0 -35
- package/src/clis/feishu/send.ts +0 -46
- package/src/clis/feishu/status.ts +0 -34
- package/src/clis/neteasemusic/README.md +0 -5
- package/src/clis/neteasemusic/README.zh-CN.md +0 -31
- package/src/clis/neteasemusic/like.ts +0 -28
- package/src/clis/neteasemusic/lyrics.ts +0 -53
- package/src/clis/neteasemusic/next.ts +0 -30
- package/src/clis/neteasemusic/play.ts +0 -30
- package/src/clis/neteasemusic/playing.ts +0 -62
- package/src/clis/neteasemusic/playlist.ts +0 -51
- package/src/clis/neteasemusic/prev.ts +0 -29
- package/src/clis/neteasemusic/search.ts +0 -58
- package/src/clis/neteasemusic/status.ts +0 -18
- package/src/clis/neteasemusic/volume.ts +0 -61
- package/src/clis/notion/README.md +0 -5
- package/src/clis/notion/README.zh-CN.md +0 -29
- package/src/clis/wechat/README.md +0 -5
- package/src/clis/wechat/README.zh-CN.md +0 -28
- package/src/clis/wechat/chats.ts +0 -33
- package/src/clis/wechat/contacts.ts +0 -33
- package/src/clis/wechat/read.ts +0 -72
- package/src/clis/wechat/search.ts +0 -36
- package/src/clis/wechat/send.ts +0 -49
- package/src/clis/wechat/status.ts +0 -35
- package/src/pipeline.ts +0 -8
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
site: xueqiu
|
|
2
|
+
name: earnings-date
|
|
3
|
+
description: 获取股票预计财报发布日期(公司大事)
|
|
4
|
+
domain: xueqiu.com
|
|
5
|
+
browser: true
|
|
6
|
+
|
|
7
|
+
args:
|
|
8
|
+
symbol:
|
|
9
|
+
positional: true
|
|
10
|
+
type: str
|
|
11
|
+
required: true
|
|
12
|
+
description: 股票代码,如 SH600519、SZ000858、00700
|
|
13
|
+
next:
|
|
14
|
+
type: bool
|
|
15
|
+
default: false
|
|
16
|
+
description: 仅返回最近一次未发布的财报日期
|
|
17
|
+
limit:
|
|
18
|
+
type: int
|
|
19
|
+
default: 10
|
|
20
|
+
description: 返回数量,默认 10
|
|
21
|
+
|
|
22
|
+
pipeline:
|
|
23
|
+
- navigate: https://xueqiu.com
|
|
24
|
+
- evaluate: |
|
|
25
|
+
(async () => {
|
|
26
|
+
const symbol = (${{ args.symbol | json }} || '').toUpperCase();
|
|
27
|
+
const onlyNext = ${{ args.next }};
|
|
28
|
+
if (!symbol) throw new Error('Missing argument: symbol');
|
|
29
|
+
const resp = await fetch(
|
|
30
|
+
`https://stock.xueqiu.com/v5/stock/screener/event/list.json?symbol=${encodeURIComponent(symbol)}&page=1&size=100`,
|
|
31
|
+
{ credentials: 'include' }
|
|
32
|
+
);
|
|
33
|
+
if (!resp.ok) throw new Error('HTTP ' + resp.status + ' Hint: Not logged in?');
|
|
34
|
+
const d = await resp.json();
|
|
35
|
+
if (!d.data || !d.data.items) throw new Error('获取失败: ' + JSON.stringify(d));
|
|
36
|
+
|
|
37
|
+
// subtype 2 = 预计财报发布
|
|
38
|
+
let items = d.data.items.filter(item => item.subtype === 2);
|
|
39
|
+
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
let results = items.map(item => {
|
|
42
|
+
const ts = item.timestamp;
|
|
43
|
+
const dateStr = ts ? new Date(ts).toISOString().split('T')[0] : null;
|
|
44
|
+
const isFuture = ts && ts > now;
|
|
45
|
+
return {
|
|
46
|
+
date: dateStr,
|
|
47
|
+
report: item.message,
|
|
48
|
+
status: isFuture ? '⏳ 未发布' : '✅ 已发布',
|
|
49
|
+
_ts: ts,
|
|
50
|
+
_future: isFuture
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (onlyNext) {
|
|
55
|
+
const future = results.filter(r => r._future).sort((a, b) => a._ts - b._ts);
|
|
56
|
+
results = future.length ? [future[0]] : [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return results;
|
|
60
|
+
})()
|
|
61
|
+
|
|
62
|
+
- map:
|
|
63
|
+
date: ${{ item.date }}
|
|
64
|
+
report: ${{ item.report }}
|
|
65
|
+
status: ${{ item.status }}
|
|
66
|
+
|
|
67
|
+
- limit: ${{ args.limit }}
|
|
68
|
+
|
|
69
|
+
columns: [date, report, status]
|
|
@@ -6,7 +6,9 @@ browser: true
|
|
|
6
6
|
|
|
7
7
|
args:
|
|
8
8
|
query:
|
|
9
|
+
positional: true
|
|
9
10
|
type: str
|
|
11
|
+
required: true
|
|
10
12
|
description: 搜索关键词,如 茅台、AAPL、腾讯
|
|
11
13
|
limit:
|
|
12
14
|
type: int
|
|
@@ -19,7 +21,6 @@ pipeline:
|
|
|
19
21
|
(async () => {
|
|
20
22
|
const query = ${{ args.query | json }};
|
|
21
23
|
const count = ${{ args.limit }};
|
|
22
|
-
if (!query) throw new Error('Missing argument: query');
|
|
23
24
|
const resp = await fetch(`https://xueqiu.com/stock/search.json?code=${encodeURIComponent(query)}&size=${count}`, {credentials: 'include'});
|
|
24
25
|
if (!resp.ok) throw new Error('HTTP ' + resp.status + ' Hint: Not logged in?');
|
|
25
26
|
const d = await resp.json();
|
|
@@ -11,7 +11,7 @@ cli({
|
|
|
11
11
|
domain: 'finance.yahoo.com',
|
|
12
12
|
strategy: Strategy.COOKIE,
|
|
13
13
|
args: [
|
|
14
|
-
{ name: 'symbol', required: true, help: 'Stock ticker (e.g. AAPL, MSFT, TSLA)' },
|
|
14
|
+
{ name: 'symbol', required: true, positional: true, help: 'Stock ticker (e.g. AAPL, MSFT, TSLA)' },
|
|
15
15
|
],
|
|
16
16
|
columns: ['symbol', 'name', 'price', 'change', 'changePercent', 'open', 'high', 'low', 'volume', 'marketCap'],
|
|
17
17
|
func: async (page, kwargs) => {
|
package/src/commanderAdapter.ts
CHANGED
|
@@ -18,6 +18,10 @@ import { render as renderOutput } from './output.js';
|
|
|
18
18
|
import { executeCommand } from './execution.js';
|
|
19
19
|
import { CliError } from './errors.js';
|
|
20
20
|
|
|
21
|
+
function getErrorMessage(error: unknown): string {
|
|
22
|
+
return error instanceof Error ? error.message : String(error);
|
|
23
|
+
}
|
|
24
|
+
|
|
21
25
|
/**
|
|
22
26
|
* Register a single CliCommand as a Commander subcommand.
|
|
23
27
|
*/
|
|
@@ -46,12 +50,13 @@ export function registerCommandToProgram(siteCmd: Command, cmd: CliCommand): voi
|
|
|
46
50
|
|
|
47
51
|
subCmd.addHelpText('after', formatRegistryHelpText(cmd));
|
|
48
52
|
|
|
49
|
-
subCmd.action(async (...actionArgs:
|
|
53
|
+
subCmd.action(async (...actionArgs: unknown[]) => {
|
|
50
54
|
const actionOpts = actionArgs[positionalArgs.length] ?? {};
|
|
55
|
+
const optionsRecord = typeof actionOpts === 'object' && actionOpts !== null ? actionOpts as Record<string, unknown> : {};
|
|
51
56
|
const startTime = Date.now();
|
|
52
57
|
|
|
53
58
|
// ── Collect kwargs ──────────────────────────────────────────────────
|
|
54
|
-
const kwargs: Record<string,
|
|
59
|
+
const kwargs: Record<string, unknown> = {};
|
|
55
60
|
for (let i = 0; i < positionalArgs.length; i++) {
|
|
56
61
|
const v = actionArgs[i];
|
|
57
62
|
if (v !== undefined) kwargs[positionalArgs[i].name] = v;
|
|
@@ -59,36 +64,38 @@ export function registerCommandToProgram(siteCmd: Command, cmd: CliCommand): voi
|
|
|
59
64
|
for (const arg of cmd.args) {
|
|
60
65
|
if (arg.positional) continue;
|
|
61
66
|
const camelName = arg.name.replace(/-([a-z])/g, (_m, ch: string) => ch.toUpperCase());
|
|
62
|
-
const v =
|
|
67
|
+
const v = optionsRecord[arg.name] ?? optionsRecord[camelName];
|
|
63
68
|
if (v !== undefined) kwargs[arg.name] = v;
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
// ── Execute + render ────────────────────────────────────────────────
|
|
67
72
|
try {
|
|
68
|
-
|
|
73
|
+
const verbose = optionsRecord.verbose === true;
|
|
74
|
+
const format = typeof optionsRecord.format === 'string' ? optionsRecord.format : 'table';
|
|
75
|
+
if (verbose) process.env.OPENCLI_VERBOSE = '1';
|
|
69
76
|
|
|
70
|
-
const result = await executeCommand(cmd, kwargs,
|
|
77
|
+
const result = await executeCommand(cmd, kwargs, verbose);
|
|
71
78
|
|
|
72
|
-
if (
|
|
79
|
+
if (verbose && (!result || (Array.isArray(result) && result.length === 0))) {
|
|
73
80
|
console.error(chalk.yellow('[Verbose] Warning: Command returned an empty result.'));
|
|
74
81
|
}
|
|
75
82
|
const resolved = getRegistry().get(fullName(cmd)) ?? cmd;
|
|
76
83
|
renderOutput(result, {
|
|
77
|
-
fmt:
|
|
84
|
+
fmt: format,
|
|
78
85
|
columns: resolved.columns,
|
|
79
86
|
title: `${resolved.site}/${resolved.name}`,
|
|
80
87
|
elapsed: (Date.now() - startTime) / 1000,
|
|
81
88
|
source: fullName(resolved),
|
|
82
89
|
footerExtra: resolved.footerExtra?.(kwargs),
|
|
83
90
|
});
|
|
84
|
-
} catch (err
|
|
91
|
+
} catch (err) {
|
|
85
92
|
if (err instanceof CliError) {
|
|
86
93
|
console.error(chalk.red(`Error [${err.code}]: ${err.message}`));
|
|
87
94
|
if (err.hint) console.error(chalk.yellow(`Hint: ${err.hint}`));
|
|
88
|
-
} else if (
|
|
95
|
+
} else if (optionsRecord.verbose === true && err instanceof Error && err.stack) {
|
|
89
96
|
console.error(chalk.red(err.stack));
|
|
90
97
|
} else {
|
|
91
|
-
console.error(chalk.red(`Error: ${err
|
|
98
|
+
console.error(chalk.red(`Error: ${getErrorMessage(err)}`));
|
|
92
99
|
}
|
|
93
100
|
process.exitCode = 1;
|
|
94
101
|
}
|
package/src/discovery.ts
CHANGED
|
@@ -9,10 +9,55 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import * as fs from 'node:fs';
|
|
12
|
+
import * as os from 'node:os';
|
|
12
13
|
import * as path from 'node:path';
|
|
14
|
+
import { pathToFileURL } from 'node:url';
|
|
13
15
|
import yaml from 'js-yaml';
|
|
14
16
|
import { type CliCommand, type InternalCliCommand, type Arg, Strategy, registerCommand } from './registry.js';
|
|
15
17
|
import { log } from './logger.js';
|
|
18
|
+
import type { ManifestEntry } from './build-manifest.js';
|
|
19
|
+
|
|
20
|
+
/** Plugins directory: ~/.opencli/plugins/ */
|
|
21
|
+
export const PLUGINS_DIR = path.join(os.homedir(), '.opencli', 'plugins');
|
|
22
|
+
const CLI_MODULE_PATTERN = /\bcli\s*\(/;
|
|
23
|
+
|
|
24
|
+
interface YamlArgDefinition {
|
|
25
|
+
type?: string;
|
|
26
|
+
default?: unknown;
|
|
27
|
+
required?: boolean;
|
|
28
|
+
positional?: boolean;
|
|
29
|
+
description?: string;
|
|
30
|
+
help?: string;
|
|
31
|
+
choices?: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
interface YamlCliDefinition {
|
|
35
|
+
site?: string;
|
|
36
|
+
name?: string;
|
|
37
|
+
description?: string;
|
|
38
|
+
domain?: string;
|
|
39
|
+
strategy?: string;
|
|
40
|
+
browser?: boolean;
|
|
41
|
+
args?: Record<string, YamlArgDefinition>;
|
|
42
|
+
columns?: string[];
|
|
43
|
+
pipeline?: Record<string, unknown>[];
|
|
44
|
+
timeout?: number;
|
|
45
|
+
navigateBefore?: boolean | string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function getErrorMessage(error: unknown): string {
|
|
49
|
+
return error instanceof Error ? error.message : String(error);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseStrategy(rawStrategy: string | undefined, fallback: Strategy = Strategy.COOKIE): Strategy {
|
|
53
|
+
if (!rawStrategy) return fallback;
|
|
54
|
+
const key = rawStrategy.toUpperCase() as keyof typeof Strategy;
|
|
55
|
+
return Strategy[key] ?? fallback;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
59
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
60
|
+
}
|
|
16
61
|
|
|
17
62
|
/**
|
|
18
63
|
* Discover and register CLI commands.
|
|
@@ -41,11 +86,11 @@ export async function discoverClis(...dirs: string[]): Promise<void> {
|
|
|
41
86
|
async function loadFromManifest(manifestPath: string, clisDir: string): Promise<void> {
|
|
42
87
|
try {
|
|
43
88
|
const raw = await fs.promises.readFile(manifestPath, 'utf-8');
|
|
44
|
-
const manifest = JSON.parse(raw) as
|
|
89
|
+
const manifest = JSON.parse(raw) as ManifestEntry[];
|
|
45
90
|
for (const entry of manifest) {
|
|
46
91
|
if (entry.type === 'yaml') {
|
|
47
92
|
// YAML pipelines fully inlined in manifest — register directly
|
|
48
|
-
const strategy = (
|
|
93
|
+
const strategy = parseStrategy(entry.strategy);
|
|
49
94
|
const cmd: CliCommand = {
|
|
50
95
|
site: entry.site,
|
|
51
96
|
name: entry.name,
|
|
@@ -58,12 +103,13 @@ async function loadFromManifest(manifestPath: string, clisDir: string): Promise<
|
|
|
58
103
|
pipeline: entry.pipeline,
|
|
59
104
|
timeoutSeconds: entry.timeout,
|
|
60
105
|
source: `manifest:${entry.site}/${entry.name}`,
|
|
106
|
+
navigateBefore: entry.navigateBefore,
|
|
61
107
|
};
|
|
62
108
|
registerCommand(cmd);
|
|
63
109
|
} else if (entry.type === 'ts' && entry.modulePath) {
|
|
64
110
|
// TS adapters: register a lightweight stub.
|
|
65
111
|
// The actual module is loaded lazily on first executeCommand().
|
|
66
|
-
const strategy = (
|
|
112
|
+
const strategy = parseStrategy(entry.strategy ?? 'cookie');
|
|
67
113
|
const modulePath = path.resolve(clisDir, entry.modulePath);
|
|
68
114
|
const cmd: InternalCliCommand = {
|
|
69
115
|
site: entry.site,
|
|
@@ -76,14 +122,15 @@ async function loadFromManifest(manifestPath: string, clisDir: string): Promise<
|
|
|
76
122
|
columns: entry.columns,
|
|
77
123
|
timeoutSeconds: entry.timeout,
|
|
78
124
|
source: modulePath,
|
|
125
|
+
navigateBefore: entry.navigateBefore,
|
|
79
126
|
_lazy: true,
|
|
80
127
|
_modulePath: modulePath,
|
|
81
128
|
};
|
|
82
129
|
registerCommand(cmd);
|
|
83
130
|
}
|
|
84
131
|
}
|
|
85
|
-
} catch (err
|
|
86
|
-
log.warn(`Failed to load manifest ${manifestPath}: ${err
|
|
132
|
+
} catch (err) {
|
|
133
|
+
log.warn(`Failed to load manifest ${manifestPath}: ${getErrorMessage(err)}`);
|
|
87
134
|
}
|
|
88
135
|
}
|
|
89
136
|
|
|
@@ -92,7 +139,7 @@ async function loadFromManifest(manifestPath: string, clisDir: string): Promise<
|
|
|
92
139
|
*/
|
|
93
140
|
async function discoverClisFromFs(dir: string): Promise<void> {
|
|
94
141
|
try { await fs.promises.access(dir); } catch { return; }
|
|
95
|
-
const promises: Promise<
|
|
142
|
+
const promises: Promise<unknown>[] = [];
|
|
96
143
|
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
97
144
|
|
|
98
145
|
for (const entry of entries) {
|
|
@@ -108,9 +155,10 @@ async function discoverClisFromFs(dir: string): Promise<void> {
|
|
|
108
155
|
(file.endsWith('.js') && !file.endsWith('.d.js')) ||
|
|
109
156
|
(file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.endsWith('.test.ts'))
|
|
110
157
|
) {
|
|
158
|
+
if (!(await isCliModule(filePath))) continue;
|
|
111
159
|
promises.push(
|
|
112
|
-
import(
|
|
113
|
-
log.warn(`Failed to load module ${filePath}: ${err
|
|
160
|
+
import(pathToFileURL(filePath).href).catch((err) => {
|
|
161
|
+
log.warn(`Failed to load module ${filePath}: ${getErrorMessage(err)}`);
|
|
114
162
|
})
|
|
115
163
|
);
|
|
116
164
|
}
|
|
@@ -122,18 +170,19 @@ async function discoverClisFromFs(dir: string): Promise<void> {
|
|
|
122
170
|
async function registerYamlCli(filePath: string, defaultSite: string): Promise<void> {
|
|
123
171
|
try {
|
|
124
172
|
const raw = await fs.promises.readFile(filePath, 'utf-8');
|
|
125
|
-
const def = yaml.load(raw) as
|
|
126
|
-
if (!def
|
|
173
|
+
const def = yaml.load(raw) as YamlCliDefinition | null;
|
|
174
|
+
if (!isRecord(def)) return;
|
|
175
|
+
const cliDef = def as YamlCliDefinition;
|
|
127
176
|
|
|
128
|
-
const site =
|
|
129
|
-
const name =
|
|
130
|
-
const strategyStr =
|
|
131
|
-
const strategy = (
|
|
132
|
-
const browser =
|
|
177
|
+
const site = cliDef.site ?? defaultSite;
|
|
178
|
+
const name = cliDef.name ?? path.basename(filePath, path.extname(filePath));
|
|
179
|
+
const strategyStr = cliDef.strategy ?? (cliDef.browser === false ? 'public' : 'cookie');
|
|
180
|
+
const strategy = parseStrategy(strategyStr);
|
|
181
|
+
const browser = cliDef.browser ?? (strategy !== Strategy.PUBLIC);
|
|
133
182
|
|
|
134
183
|
const args: Arg[] = [];
|
|
135
|
-
if (
|
|
136
|
-
for (const [argName, argDef] of Object.entries(
|
|
184
|
+
if (cliDef.args && typeof cliDef.args === 'object') {
|
|
185
|
+
for (const [argName, argDef] of Object.entries(cliDef.args)) {
|
|
137
186
|
args.push({
|
|
138
187
|
name: argName,
|
|
139
188
|
type: argDef?.type ?? 'str',
|
|
@@ -149,19 +198,80 @@ async function registerYamlCli(filePath: string, defaultSite: string): Promise<v
|
|
|
149
198
|
const cmd: CliCommand = {
|
|
150
199
|
site,
|
|
151
200
|
name,
|
|
152
|
-
description:
|
|
153
|
-
domain:
|
|
201
|
+
description: cliDef.description ?? '',
|
|
202
|
+
domain: cliDef.domain,
|
|
154
203
|
strategy,
|
|
155
204
|
browser,
|
|
156
205
|
args,
|
|
157
|
-
columns:
|
|
158
|
-
pipeline:
|
|
159
|
-
timeoutSeconds:
|
|
206
|
+
columns: cliDef.columns,
|
|
207
|
+
pipeline: cliDef.pipeline,
|
|
208
|
+
timeoutSeconds: cliDef.timeout,
|
|
160
209
|
source: filePath,
|
|
210
|
+
navigateBefore: cliDef.navigateBefore,
|
|
161
211
|
};
|
|
162
212
|
|
|
163
213
|
registerCommand(cmd);
|
|
164
|
-
} catch (err
|
|
165
|
-
log.warn(`Failed to load ${filePath}: ${err
|
|
214
|
+
} catch (err) {
|
|
215
|
+
log.warn(`Failed to load ${filePath}: ${getErrorMessage(err)}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Discover and register plugins from ~/.opencli/plugins/.
|
|
221
|
+
* Each subdirectory is treated as a plugin (site = directory name).
|
|
222
|
+
* Files inside are scanned flat (no nested site subdirs).
|
|
223
|
+
*/
|
|
224
|
+
export async function discoverPlugins(): Promise<void> {
|
|
225
|
+
try { await fs.promises.access(PLUGINS_DIR); } catch { return; }
|
|
226
|
+
const entries = await fs.promises.readdir(PLUGINS_DIR, { withFileTypes: true });
|
|
227
|
+
for (const entry of entries) {
|
|
228
|
+
if (!entry.isDirectory()) continue;
|
|
229
|
+
await discoverPluginDir(path.join(PLUGINS_DIR, entry.name), entry.name);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Flat scan: read yaml/ts files directly in a plugin directory.
|
|
235
|
+
* Unlike discoverClisFromFs, this does NOT expect nested site subdirectories.
|
|
236
|
+
*/
|
|
237
|
+
async function discoverPluginDir(dir: string, site: string): Promise<void> {
|
|
238
|
+
const files = await fs.promises.readdir(dir);
|
|
239
|
+
const fileSet = new Set(files);
|
|
240
|
+
const promises: Promise<unknown>[] = [];
|
|
241
|
+
for (const file of files) {
|
|
242
|
+
const filePath = path.join(dir, file);
|
|
243
|
+
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
244
|
+
promises.push(registerYamlCli(filePath, site));
|
|
245
|
+
} else if (file.endsWith('.js') && !file.endsWith('.d.js')) {
|
|
246
|
+
if (!(await isCliModule(filePath))) continue;
|
|
247
|
+
promises.push(
|
|
248
|
+
import(pathToFileURL(filePath).href).catch((err) => {
|
|
249
|
+
log.warn(`Plugin ${site}/${file}: ${getErrorMessage(err)}`);
|
|
250
|
+
})
|
|
251
|
+
);
|
|
252
|
+
} else if (
|
|
253
|
+
file.endsWith('.ts') && !file.endsWith('.d.ts') && !file.endsWith('.test.ts')
|
|
254
|
+
) {
|
|
255
|
+
// Skip .ts if a compiled .js sibling exists (production mode can't load .ts)
|
|
256
|
+
const jsFile = file.replace(/\.ts$/, '.js');
|
|
257
|
+
if (fileSet.has(jsFile)) continue;
|
|
258
|
+
if (!(await isCliModule(filePath))) continue;
|
|
259
|
+
promises.push(
|
|
260
|
+
import(pathToFileURL(filePath).href).catch((err) => {
|
|
261
|
+
log.warn(`Plugin ${site}/${file}: ${getErrorMessage(err)}`);
|
|
262
|
+
})
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
await Promise.all(promises);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function isCliModule(filePath: string): Promise<boolean> {
|
|
270
|
+
try {
|
|
271
|
+
const source = await fs.promises.readFile(filePath, 'utf-8');
|
|
272
|
+
return CLI_MODULE_PATTERN.test(source);
|
|
273
|
+
} catch (err) {
|
|
274
|
+
log.warn(`Failed to inspect module ${filePath}: ${getErrorMessage(err)}`);
|
|
275
|
+
return false;
|
|
166
276
|
}
|
|
167
277
|
}
|
package/src/doctor.test.ts
CHANGED
|
@@ -1,9 +1,36 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const { mockCheckDaemonStatus, mockListSessions, mockConnect, mockClose } = vi.hoisted(() => ({
|
|
4
|
+
mockCheckDaemonStatus: vi.fn(),
|
|
5
|
+
mockListSessions: vi.fn(),
|
|
6
|
+
mockConnect: vi.fn(),
|
|
7
|
+
mockClose: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
vi.mock('./browser/discover.js', () => ({
|
|
11
|
+
checkDaemonStatus: mockCheckDaemonStatus,
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
vi.mock('./browser/daemon-client.js', () => ({
|
|
15
|
+
listSessions: mockListSessions,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
vi.mock('./browser/index.js', () => ({
|
|
19
|
+
BrowserBridge: class {
|
|
20
|
+
connect = mockConnect;
|
|
21
|
+
close = mockClose;
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
import { renderBrowserDoctorReport, runBrowserDoctor } from './doctor.js';
|
|
3
26
|
|
|
4
27
|
describe('doctor report rendering', () => {
|
|
5
28
|
const strip = (s: string) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
6
29
|
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
vi.clearAllMocks();
|
|
32
|
+
});
|
|
33
|
+
|
|
7
34
|
it('renders OK-style report when daemon and extension connected', () => {
|
|
8
35
|
const text = strip(renderBrowserDoctorReport({
|
|
9
36
|
daemonRunning: true,
|
|
@@ -59,4 +86,34 @@ describe('doctor report rendering', () => {
|
|
|
59
86
|
|
|
60
87
|
expect(text).toContain('[SKIP] Connectivity: not tested (use --live)');
|
|
61
88
|
});
|
|
89
|
+
|
|
90
|
+
it('reports consistent status when live check auto-starts the daemon', async () => {
|
|
91
|
+
// With the reordered flow, checkDaemonStatus is called only ONCE — after
|
|
92
|
+
// the connectivity check that may auto-start the daemon.
|
|
93
|
+
mockCheckDaemonStatus.mockResolvedValueOnce({ running: true, extensionConnected: false });
|
|
94
|
+
mockConnect.mockRejectedValueOnce(new Error(
|
|
95
|
+
'Daemon is running but the Browser Extension is not connected.\n' +
|
|
96
|
+
'Please install and enable the opencli Browser Bridge extension in Chrome.',
|
|
97
|
+
));
|
|
98
|
+
|
|
99
|
+
const report = await runBrowserDoctor({ live: true });
|
|
100
|
+
|
|
101
|
+
// Status reflects the post-connectivity state (daemon running)
|
|
102
|
+
expect(report.daemonRunning).toBe(true);
|
|
103
|
+
expect(report.extensionConnected).toBe(false);
|
|
104
|
+
// checkDaemonStatus should only be called once
|
|
105
|
+
expect(mockCheckDaemonStatus).toHaveBeenCalledTimes(1);
|
|
106
|
+
// Should NOT report "daemon not running" since it IS running after live check
|
|
107
|
+
expect(report.issues).not.toContain(
|
|
108
|
+
expect.stringContaining('Daemon is not running'),
|
|
109
|
+
);
|
|
110
|
+
// Should report extension not connected
|
|
111
|
+
expect(report.issues).toEqual(expect.arrayContaining([
|
|
112
|
+
expect.stringContaining('Chrome extension is not connected'),
|
|
113
|
+
]));
|
|
114
|
+
// Should report connectivity failure
|
|
115
|
+
expect(report.issues).toEqual(expect.arrayContaining([
|
|
116
|
+
expect.stringContaining('Browser connectivity test failed'),
|
|
117
|
+
]));
|
|
118
|
+
});
|
|
62
119
|
});
|
package/src/doctor.ts
CHANGED
|
@@ -51,12 +51,14 @@ export async function checkConnectivity(opts?: { timeout?: number }): Promise<Co
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
export async function runBrowserDoctor(opts: DoctorOptions = {}): Promise<DoctorReport> {
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
// Run the live connectivity check first — it may auto-start the daemon as a
|
|
55
|
+
// side-effect, so we read daemon status only *after* all side-effects settle.
|
|
56
56
|
let connectivity: ConnectivityResult | undefined;
|
|
57
57
|
if (opts.live) {
|
|
58
58
|
connectivity = await checkConnectivity();
|
|
59
59
|
}
|
|
60
|
+
|
|
61
|
+
const status = await checkDaemonStatus();
|
|
60
62
|
const sessions = opts.sessions && status.running && status.extensionConnected
|
|
61
63
|
? await listSessions() as Array<{ workspace: string; windowId: number; tabCount: number; idleMsRemaining: number }>
|
|
62
64
|
: undefined;
|
package/src/engine.test.ts
CHANGED
|
@@ -1,17 +1,90 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
-
import { discoverClis } from './discovery.js';
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { discoverClis, discoverPlugins, PLUGINS_DIR } from './discovery.js';
|
|
7
3
|
import { executeCommand } from './execution.js';
|
|
8
4
|
import { getRegistry, cli, Strategy } from './registry.js';
|
|
5
|
+
import * as fs from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
9
7
|
|
|
10
8
|
describe('discoverClis', () => {
|
|
11
9
|
it('handles non-existent directories gracefully', async () => {
|
|
12
10
|
// Should not throw for missing directories
|
|
13
11
|
await expect(discoverClis('/tmp/nonexistent-opencli-test-dir')).resolves.not.toThrow();
|
|
14
12
|
});
|
|
13
|
+
|
|
14
|
+
it('imports only CLI command modules during filesystem discovery', async () => {
|
|
15
|
+
const tempRoot = await fs.promises.mkdtemp(path.join('/tmp', 'opencli-discovery-'));
|
|
16
|
+
const siteDir = path.join(tempRoot, 'temp-site');
|
|
17
|
+
const helperPath = path.join(siteDir, 'helper.ts');
|
|
18
|
+
const commandPath = path.join(siteDir, 'hello.ts');
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
await fs.promises.mkdir(siteDir, { recursive: true });
|
|
22
|
+
await fs.promises.writeFile(helperPath, `
|
|
23
|
+
globalThis.__opencli_helper_loaded__ = true;
|
|
24
|
+
export const helper = true;
|
|
25
|
+
`);
|
|
26
|
+
await fs.promises.writeFile(commandPath, `
|
|
27
|
+
import { cli, Strategy } from '${path.join(process.cwd(), 'src', 'registry.ts')}';
|
|
28
|
+
cli({
|
|
29
|
+
site: 'temp-site',
|
|
30
|
+
name: 'hello',
|
|
31
|
+
description: 'hello command',
|
|
32
|
+
strategy: Strategy.PUBLIC,
|
|
33
|
+
browser: false,
|
|
34
|
+
func: async () => [{ ok: true }],
|
|
35
|
+
});
|
|
36
|
+
`);
|
|
37
|
+
|
|
38
|
+
delete (globalThis as any).__opencli_helper_loaded__;
|
|
39
|
+
await discoverClis(tempRoot);
|
|
40
|
+
|
|
41
|
+
expect((globalThis as any).__opencli_helper_loaded__).toBeUndefined();
|
|
42
|
+
expect(getRegistry().get('temp-site/hello')).toBeDefined();
|
|
43
|
+
} finally {
|
|
44
|
+
delete (globalThis as any).__opencli_helper_loaded__;
|
|
45
|
+
await fs.promises.rm(tempRoot, { recursive: true, force: true });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe('discoverPlugins', () => {
|
|
51
|
+
const testPluginDir = path.join(PLUGINS_DIR, '__test-plugin__');
|
|
52
|
+
const yamlPath = path.join(testPluginDir, 'greeting.yaml');
|
|
53
|
+
|
|
54
|
+
afterEach(async () => {
|
|
55
|
+
try { await fs.promises.rm(testPluginDir, { recursive: true }); } catch {}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('discovers YAML plugins from ~/.opencli/plugins/', async () => {
|
|
59
|
+
// Create a simple YAML adapter in the plugins directory
|
|
60
|
+
await fs.promises.mkdir(testPluginDir, { recursive: true });
|
|
61
|
+
await fs.promises.writeFile(yamlPath, `
|
|
62
|
+
site: __test-plugin__
|
|
63
|
+
name: greeting
|
|
64
|
+
description: Test plugin greeting
|
|
65
|
+
strategy: public
|
|
66
|
+
browser: false
|
|
67
|
+
|
|
68
|
+
pipeline:
|
|
69
|
+
- evaluate: "() => [{ message: 'hello from plugin' }]"
|
|
70
|
+
|
|
71
|
+
columns: [message]
|
|
72
|
+
`);
|
|
73
|
+
|
|
74
|
+
await discoverPlugins();
|
|
75
|
+
|
|
76
|
+
const registry = getRegistry();
|
|
77
|
+
const cmd = registry.get('__test-plugin__/greeting');
|
|
78
|
+
expect(cmd).toBeDefined();
|
|
79
|
+
expect(cmd!.site).toBe('__test-plugin__');
|
|
80
|
+
expect(cmd!.name).toBe('greeting');
|
|
81
|
+
expect(cmd!.description).toBe('Test plugin greeting');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('handles non-existent plugins directory gracefully', async () => {
|
|
85
|
+
// discoverPlugins should not throw if ~/.opencli/plugins/ does not exist
|
|
86
|
+
await expect(discoverPlugins()).resolves.not.toThrow();
|
|
87
|
+
});
|
|
15
88
|
});
|
|
16
89
|
|
|
17
90
|
describe('executeCommand', () => {
|