@jackwener/opencli 1.4.0 → 1.5.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/.github/actions/setup-chrome/action.yml +5 -4
- package/.github/workflows/build-extension.yml +2 -6
- package/.github/workflows/ci.yml +37 -3
- package/.github/workflows/e2e-headed.yml +16 -3
- package/CHANGELOG.md +23 -0
- package/PRIVACY.md +57 -0
- package/README.md +36 -7
- package/README.zh-CN.md +13 -6
- package/SKILL.md +103 -2
- package/dist/browser/cdp.d.ts +2 -1
- package/dist/browser/discover.d.ts +4 -1
- package/dist/browser/discover.js +6 -2
- package/dist/browser/errors.d.ts +2 -2
- package/dist/browser/errors.js +4 -12
- package/dist/browser/mcp.d.ts +2 -1
- package/dist/build-manifest.d.ts +2 -0
- package/dist/build-manifest.js +39 -14
- package/dist/build-manifest.test.js +21 -0
- package/dist/capabilityRouting.d.ts +2 -0
- package/dist/capabilityRouting.js +2 -1
- package/dist/cli-manifest.json +1838 -151
- package/dist/cli.js +34 -3
- package/dist/clis/36kr/article.d.ts +1 -0
- package/dist/clis/36kr/article.js +62 -0
- package/dist/clis/36kr/hot.d.ts +3 -0
- package/dist/clis/36kr/hot.js +80 -0
- package/dist/clis/36kr/hot.test.d.ts +1 -0
- package/dist/clis/36kr/hot.test.js +15 -0
- package/dist/clis/36kr/news.d.ts +1 -0
- package/dist/clis/36kr/news.js +51 -0
- package/dist/clis/36kr/news.test.d.ts +1 -0
- package/dist/clis/36kr/news.test.js +85 -0
- package/dist/clis/36kr/search.d.ts +1 -0
- package/dist/clis/36kr/search.js +72 -0
- package/dist/clis/apple-podcasts/search.js +2 -1
- package/dist/clis/arxiv/search.js +2 -2
- package/dist/clis/bbc/news.js +0 -1
- package/dist/clis/bilibili/comments.d.ts +5 -0
- package/dist/clis/bilibili/comments.js +40 -0
- package/dist/clis/bilibili/comments.test.d.ts +1 -0
- package/dist/clis/bilibili/comments.test.js +82 -0
- package/dist/clis/chatgpt/ask.js +29 -14
- package/dist/clis/chatgpt/ax.d.ts +6 -0
- package/dist/clis/chatgpt/ax.js +172 -1
- package/dist/clis/chatgpt/model.d.ts +1 -0
- package/dist/clis/chatgpt/model.js +24 -0
- package/dist/clis/chatgpt/send.js +12 -3
- package/dist/clis/ctrip/search.js +0 -1
- package/dist/clis/douban/download.d.ts +1 -0
- package/dist/clis/douban/download.js +67 -0
- package/dist/clis/douban/download.test.d.ts +1 -0
- package/dist/clis/douban/download.test.js +170 -0
- package/dist/clis/douban/photos.d.ts +1 -0
- package/dist/clis/douban/photos.js +34 -0
- package/dist/clis/douban/utils.d.ts +25 -0
- package/dist/clis/douban/utils.js +190 -1
- package/dist/clis/douban/utils.test.d.ts +1 -0
- package/dist/clis/douban/utils.test.js +64 -0
- package/dist/clis/douyin/_shared/browser-fetch.d.ts +10 -0
- package/dist/clis/douyin/_shared/browser-fetch.js +30 -0
- package/dist/clis/douyin/_shared/browser-fetch.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/browser-fetch.test.js +31 -0
- package/dist/clis/douyin/_shared/creation-id.d.ts +1 -0
- package/dist/clis/douyin/_shared/creation-id.js +5 -0
- package/dist/clis/douyin/_shared/creation-id.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/creation-id.test.js +22 -0
- package/dist/clis/douyin/_shared/imagex-upload.d.ts +20 -0
- package/dist/clis/douyin/_shared/imagex-upload.js +53 -0
- package/dist/clis/douyin/_shared/imagex-upload.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/imagex-upload.test.js +87 -0
- package/dist/clis/douyin/_shared/sts2.d.ts +8 -0
- package/dist/clis/douyin/_shared/sts2.js +15 -0
- package/dist/clis/douyin/_shared/text-extra.d.ts +18 -0
- package/dist/clis/douyin/_shared/text-extra.js +15 -0
- package/dist/clis/douyin/_shared/text-extra.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/text-extra.test.js +37 -0
- package/dist/clis/douyin/_shared/timing.d.ts +2 -0
- package/dist/clis/douyin/_shared/timing.js +22 -0
- package/dist/clis/douyin/_shared/timing.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/timing.test.js +28 -0
- package/dist/clis/douyin/_shared/tos-upload-short-read.test.d.ts +11 -0
- package/dist/clis/douyin/_shared/tos-upload-short-read.test.js +83 -0
- package/dist/clis/douyin/_shared/tos-upload.d.ts +53 -0
- package/dist/clis/douyin/_shared/tos-upload.js +295 -0
- package/dist/clis/douyin/_shared/tos-upload.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/tos-upload.test.js +229 -0
- package/dist/clis/douyin/_shared/transcode.d.ts +27 -0
- package/dist/clis/douyin/_shared/transcode.js +45 -0
- package/dist/clis/douyin/_shared/transcode.test.d.ts +1 -0
- package/dist/clis/douyin/_shared/transcode.test.js +93 -0
- package/dist/clis/douyin/_shared/types.d.ts +26 -0
- package/dist/clis/douyin/_shared/types.js +1 -0
- package/dist/clis/douyin/activities.d.ts +1 -0
- package/dist/clis/douyin/activities.js +20 -0
- package/dist/clis/douyin/activities.test.d.ts +1 -0
- package/dist/clis/douyin/activities.test.js +22 -0
- package/dist/clis/douyin/collections.d.ts +1 -0
- package/dist/clis/douyin/collections.js +22 -0
- package/dist/clis/douyin/collections.test.d.ts +1 -0
- package/dist/clis/douyin/collections.test.js +23 -0
- package/dist/clis/douyin/delete.d.ts +1 -0
- package/dist/clis/douyin/delete.js +18 -0
- package/dist/clis/douyin/delete.test.d.ts +1 -0
- package/dist/clis/douyin/delete.test.js +11 -0
- package/dist/clis/douyin/draft.d.ts +14 -0
- package/dist/clis/douyin/draft.js +237 -0
- package/dist/clis/douyin/draft.test.d.ts +1 -0
- package/dist/clis/douyin/draft.test.js +11 -0
- package/dist/clis/douyin/drafts.d.ts +1 -0
- package/dist/clis/douyin/drafts.js +23 -0
- package/dist/clis/douyin/drafts.test.d.ts +1 -0
- package/dist/clis/douyin/drafts.test.js +11 -0
- package/dist/clis/douyin/hashtag.d.ts +1 -0
- package/dist/clis/douyin/hashtag.js +45 -0
- package/dist/clis/douyin/hashtag.test.d.ts +1 -0
- package/dist/clis/douyin/hashtag.test.js +25 -0
- package/dist/clis/douyin/location.d.ts +1 -0
- package/dist/clis/douyin/location.js +24 -0
- package/dist/clis/douyin/location.test.d.ts +1 -0
- package/dist/clis/douyin/location.test.js +23 -0
- package/dist/clis/douyin/profile.d.ts +1 -0
- package/dist/clis/douyin/profile.js +28 -0
- package/dist/clis/douyin/profile.test.d.ts +1 -0
- package/dist/clis/douyin/profile.test.js +11 -0
- package/dist/clis/douyin/publish.d.ts +14 -0
- package/dist/clis/douyin/publish.js +288 -0
- package/dist/clis/douyin/publish.test.d.ts +1 -0
- package/dist/clis/douyin/publish.test.js +38 -0
- package/dist/clis/douyin/stats.d.ts +1 -0
- package/dist/clis/douyin/stats.js +27 -0
- package/dist/clis/douyin/stats.test.d.ts +1 -0
- package/dist/clis/douyin/stats.test.js +22 -0
- package/dist/clis/douyin/update.d.ts +1 -0
- package/dist/clis/douyin/update.js +31 -0
- package/dist/clis/douyin/update.test.d.ts +1 -0
- package/dist/clis/douyin/update.test.js +11 -0
- package/dist/clis/douyin/videos.d.ts +1 -0
- package/dist/clis/douyin/videos.js +34 -0
- package/dist/clis/douyin/videos.test.d.ts +1 -0
- package/dist/clis/douyin/videos.test.js +11 -0
- package/dist/clis/hackernews/search.yaml +1 -1
- package/dist/clis/imdb/person.d.ts +1 -0
- package/dist/clis/imdb/person.js +203 -0
- package/dist/clis/imdb/reviews.d.ts +1 -0
- package/dist/clis/imdb/reviews.js +88 -0
- package/dist/clis/imdb/search.d.ts +1 -0
- package/dist/clis/imdb/search.js +161 -0
- package/dist/clis/imdb/title.d.ts +1 -0
- package/dist/clis/imdb/title.js +93 -0
- package/dist/clis/imdb/top.d.ts +1 -0
- package/dist/clis/imdb/top.js +53 -0
- package/dist/clis/imdb/trending.d.ts +1 -0
- package/dist/clis/imdb/trending.js +52 -0
- package/dist/clis/imdb/utils.d.ts +46 -0
- package/dist/clis/imdb/utils.js +285 -0
- package/dist/clis/imdb/utils.test.d.ts +1 -0
- package/dist/clis/imdb/utils.test.js +88 -0
- package/dist/clis/instagram/search.yaml +2 -1
- package/dist/clis/jd/item.d.ts +4 -0
- package/dist/clis/jd/item.js +16 -15
- package/dist/clis/jd/item.test.js +16 -1
- package/dist/clis/linux-do/categories.yaml +38 -9
- package/dist/clis/linux-do/category.d.ts +1 -0
- package/dist/clis/linux-do/category.js +36 -0
- package/dist/clis/linux-do/feed.d.ts +45 -0
- package/dist/clis/linux-do/feed.js +397 -0
- package/dist/clis/linux-do/feed.test.d.ts +1 -0
- package/dist/clis/linux-do/feed.test.js +118 -0
- package/dist/clis/linux-do/hot.d.ts +1 -0
- package/dist/clis/linux-do/hot.js +25 -0
- package/dist/clis/linux-do/latest.d.ts +1 -0
- package/dist/clis/linux-do/latest.js +18 -0
- package/dist/clis/linux-do/search.yaml +3 -1
- package/dist/clis/linux-do/tags.yaml +41 -0
- package/dist/clis/linux-do/topic.yaml +41 -3
- package/dist/clis/linux-do/user-posts.yaml +67 -0
- package/dist/clis/linux-do/user-topics.yaml +54 -0
- package/dist/clis/medium/search.js +1 -1
- package/dist/clis/paperreview/commands.test.d.ts +3 -0
- package/dist/clis/paperreview/commands.test.js +243 -0
- package/dist/clis/paperreview/feedback.d.ts +1 -0
- package/dist/clis/paperreview/feedback.js +52 -0
- package/dist/clis/paperreview/review.d.ts +1 -0
- package/dist/clis/paperreview/review.js +37 -0
- package/dist/clis/paperreview/submit.d.ts +1 -0
- package/dist/clis/paperreview/submit.js +85 -0
- package/dist/clis/paperreview/utils.d.ts +46 -0
- package/dist/clis/paperreview/utils.js +197 -0
- package/dist/clis/paperreview/utils.test.d.ts +1 -0
- package/dist/clis/paperreview/utils.test.js +49 -0
- package/dist/clis/producthunt/browse.d.ts +1 -0
- package/dist/clis/producthunt/browse.js +99 -0
- package/dist/clis/producthunt/hot.d.ts +1 -0
- package/dist/clis/producthunt/hot.js +110 -0
- package/dist/clis/producthunt/posts.d.ts +1 -0
- package/dist/clis/producthunt/posts.js +28 -0
- package/dist/clis/producthunt/today.d.ts +1 -0
- package/dist/clis/producthunt/today.js +35 -0
- package/dist/clis/producthunt/utils.d.ts +29 -0
- package/dist/clis/producthunt/utils.js +99 -0
- package/dist/clis/producthunt/utils.test.d.ts +1 -0
- package/dist/clis/producthunt/utils.test.js +64 -0
- package/dist/clis/reuters/search.js +0 -1
- package/dist/clis/twitter/article.js +4 -28
- package/dist/clis/twitter/likes.d.ts +24 -0
- package/dist/clis/twitter/likes.js +217 -0
- package/dist/clis/twitter/likes.test.d.ts +1 -0
- package/dist/clis/twitter/likes.test.js +85 -0
- package/dist/clis/twitter/profile.js +4 -28
- package/dist/clis/twitter/search.js +7 -4
- package/dist/clis/twitter/search.test.js +56 -2
- package/dist/clis/twitter/shared.d.ts +6 -0
- package/dist/clis/twitter/shared.js +35 -0
- package/dist/clis/twitter/timeline.js +2 -13
- package/dist/clis/weibo/comments.d.ts +1 -0
- package/dist/clis/weibo/comments.js +53 -0
- package/dist/clis/weibo/feed.d.ts +1 -0
- package/dist/clis/weibo/feed.js +56 -0
- package/dist/clis/weibo/hot.js +0 -1
- package/dist/clis/weibo/me.d.ts +1 -0
- package/dist/clis/weibo/me.js +76 -0
- package/dist/clis/weibo/post.d.ts +1 -0
- package/dist/clis/weibo/post.js +75 -0
- package/dist/clis/weibo/user.d.ts +1 -0
- package/dist/clis/weibo/user.js +63 -0
- package/dist/clis/weibo/utils.d.ts +6 -0
- package/dist/clis/weibo/utils.js +30 -0
- package/dist/clis/weixin/download.d.ts +17 -0
- package/dist/clis/weixin/download.js +88 -20
- package/dist/clis/weread/book.js +2 -2
- package/dist/clis/weread/commands.test.d.ts +3 -0
- package/dist/clis/weread/commands.test.js +43 -0
- package/dist/clis/weread/highlights.js +2 -2
- package/dist/clis/weread/notebooks.js +2 -2
- package/dist/clis/weread/notes.js +3 -3
- package/dist/clis/weread/search.js +3 -2
- package/dist/clis/weread/shelf.js +2 -2
- package/dist/clis/weread/utils.d.ts +4 -4
- package/dist/clis/weread/utils.js +32 -14
- package/dist/clis/weread/utils.test.js +1 -28
- package/dist/clis/xiaohongshu/comments.d.ts +5 -0
- package/dist/clis/xiaohongshu/comments.js +74 -0
- package/dist/clis/xiaohongshu/comments.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/comments.test.js +79 -0
- package/dist/clis/xiaohongshu/publish.js +114 -18
- package/dist/clis/xiaohongshu/publish.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/publish.test.js +119 -0
- package/dist/clis/xueqiu/search.yaml +2 -1
- package/dist/clis/yahoo-finance/quote.js +0 -1
- package/dist/clis/youtube/channel.d.ts +1 -0
- package/dist/clis/youtube/channel.js +150 -0
- package/dist/clis/youtube/comments.d.ts +1 -0
- package/dist/clis/youtube/comments.js +95 -0
- package/dist/clis/youtube/search.js +0 -1
- package/dist/clis/zhihu/search.yaml +2 -1
- package/dist/commanderAdapter.d.ts +1 -0
- package/dist/commanderAdapter.js +176 -29
- package/dist/commanderAdapter.test.d.ts +1 -0
- package/dist/commanderAdapter.test.js +62 -0
- package/dist/daemon.js +17 -1
- package/dist/discovery.js +8 -14
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +9 -2
- package/dist/download/index.js +63 -51
- package/dist/download/index.test.js +17 -4
- package/dist/errors.d.ts +3 -1
- package/dist/errors.js +15 -32
- package/dist/execution.d.ts +1 -3
- package/dist/execution.js +21 -1
- package/dist/external-clis.yaml +0 -17
- package/dist/hooks.js +2 -0
- package/dist/main.js +5 -0
- package/dist/output.js +5 -1
- package/dist/pipeline/executor.js +3 -4
- package/dist/plugin-manifest.d.ts +70 -0
- package/dist/plugin-manifest.js +160 -0
- package/dist/plugin-manifest.test.d.ts +4 -0
- package/dist/plugin-manifest.test.js +179 -0
- package/dist/plugin.d.ts +38 -5
- package/dist/plugin.js +267 -33
- package/dist/plugin.test.js +220 -3
- package/dist/registry.d.ts +4 -0
- package/dist/registry.js +2 -0
- package/dist/runtime-detect.d.ts +21 -0
- package/dist/runtime-detect.js +32 -0
- package/dist/runtime-detect.test.d.ts +1 -0
- package/dist/runtime-detect.test.js +27 -0
- package/dist/runtime.js +1 -1
- package/dist/serialization.d.ts +2 -0
- package/dist/serialization.js +6 -0
- package/dist/types.d.ts +1 -0
- package/dist/update-check.d.ts +22 -0
- package/dist/update-check.js +112 -0
- package/dist/weixin-download.test.d.ts +1 -0
- package/dist/weixin-download.test.js +30 -0
- package/dist/weread-private-api-regression.test.d.ts +1 -0
- package/dist/weread-private-api-regression.test.js +122 -0
- package/dist/weread-search-regression.test.d.ts +1 -0
- package/dist/weread-search-regression.test.js +39 -0
- package/dist/yaml-schema.d.ts +3 -0
- package/dist/yaml-schema.js +18 -1
- package/docs/.vitepress/config.mts +17 -0
- package/docs/adapters/browser/36kr.md +47 -0
- package/docs/adapters/browser/douban.md +14 -0
- package/docs/adapters/browser/douyin.md +75 -0
- package/docs/adapters/browser/imdb.md +47 -0
- package/docs/adapters/browser/jd.md +2 -2
- package/docs/adapters/browser/linux-do.md +181 -20
- package/docs/adapters/browser/paperreview.md +43 -0
- package/docs/adapters/browser/producthunt.md +49 -0
- package/docs/adapters/browser/twitter.md +6 -0
- package/docs/adapters/desktop/chatgpt.md +5 -0
- package/docs/adapters/index.md +12 -3
- package/docs/advanced/download.md +4 -0
- package/docs/advanced/rate-limiter-plugin.md +99 -0
- package/docs/guide/electron-app-cli.md +200 -0
- package/docs/guide/getting-started.md +1 -0
- package/docs/guide/plugins.md +87 -0
- package/docs/zh/guide/electron-app-cli.md +188 -0
- package/docs/zh/guide/getting-started.md +1 -0
- package/docs/zh/guide/plugins.md +65 -0
- package/extension/dist/background.js +508 -518
- package/extension/manifest.json +6 -2
- package/extension/package.json +2 -1
- package/extension/popup.html +84 -0
- package/extension/popup.js +25 -0
- package/extension/scripts/package-release.mjs +179 -0
- package/extension/src/background.ts +22 -1
- package/package.json +4 -1
- package/scripts/postinstall.js +10 -0
- package/src/browser/cdp.ts +2 -1
- package/src/browser/discover.ts +8 -3
- package/src/browser/errors.ts +13 -14
- package/src/browser/mcp.ts +2 -1
- package/src/build-manifest.test.ts +23 -0
- package/src/build-manifest.ts +40 -15
- package/src/capabilityRouting.ts +2 -1
- package/src/cli.ts +35 -3
- package/src/clis/36kr/article.ts +69 -0
- package/src/clis/36kr/hot.test.ts +19 -0
- package/src/clis/36kr/hot.ts +100 -0
- package/src/clis/36kr/news.test.ts +90 -0
- package/src/clis/36kr/news.ts +54 -0
- package/src/clis/36kr/search.ts +78 -0
- package/src/clis/apple-podcasts/search.ts +2 -1
- package/src/clis/arxiv/search.ts +2 -2
- package/src/clis/bbc/news.ts +0 -1
- package/src/clis/bilibili/comments.test.ts +102 -0
- package/src/clis/bilibili/comments.ts +44 -0
- package/src/clis/chatgpt/ask.ts +28 -14
- package/src/clis/chatgpt/ax.ts +180 -1
- package/src/clis/chatgpt/model.ts +27 -0
- package/src/clis/chatgpt/send.ts +16 -6
- package/src/clis/ctrip/search.ts +0 -1
- package/src/clis/douban/download.test.ts +196 -0
- package/src/clis/douban/download.ts +78 -0
- package/src/clis/douban/photos.ts +36 -0
- package/src/clis/douban/utils.test.ts +97 -0
- package/src/clis/douban/utils.ts +232 -1
- package/src/clis/douyin/_shared/browser-fetch.test.ts +38 -0
- package/src/clis/douyin/_shared/browser-fetch.ts +45 -0
- package/src/clis/douyin/_shared/creation-id.test.ts +26 -0
- package/src/clis/douyin/_shared/creation-id.ts +8 -0
- package/src/clis/douyin/_shared/imagex-upload.test.ts +113 -0
- package/src/clis/douyin/_shared/imagex-upload.ts +76 -0
- package/src/clis/douyin/_shared/sts2.ts +20 -0
- package/src/clis/douyin/_shared/text-extra.test.ts +42 -0
- package/src/clis/douyin/_shared/text-extra.ts +33 -0
- package/src/clis/douyin/_shared/timing.test.ts +38 -0
- package/src/clis/douyin/_shared/timing.ts +22 -0
- package/src/clis/douyin/_shared/tos-upload-short-read.test.ts +102 -0
- package/src/clis/douyin/_shared/tos-upload.test.ts +281 -0
- package/src/clis/douyin/_shared/tos-upload.ts +444 -0
- package/src/clis/douyin/_shared/transcode.test.ts +117 -0
- package/src/clis/douyin/_shared/transcode.ts +78 -0
- package/src/clis/douyin/_shared/types.ts +29 -0
- package/src/clis/douyin/activities.test.ts +25 -0
- package/src/clis/douyin/activities.ts +23 -0
- package/src/clis/douyin/collections.test.ts +26 -0
- package/src/clis/douyin/collections.ts +25 -0
- package/src/clis/douyin/delete.test.ts +12 -0
- package/src/clis/douyin/delete.ts +20 -0
- package/src/clis/douyin/draft.test.ts +12 -0
- package/src/clis/douyin/draft.ts +282 -0
- package/src/clis/douyin/drafts.test.ts +12 -0
- package/src/clis/douyin/drafts.ts +27 -0
- package/src/clis/douyin/hashtag.test.ts +28 -0
- package/src/clis/douyin/hashtag.ts +56 -0
- package/src/clis/douyin/location.test.ts +26 -0
- package/src/clis/douyin/location.ts +27 -0
- package/src/clis/douyin/profile.test.ts +12 -0
- package/src/clis/douyin/profile.ts +37 -0
- package/src/clis/douyin/publish.test.ts +45 -0
- package/src/clis/douyin/publish.ts +340 -0
- package/src/clis/douyin/stats.test.ts +25 -0
- package/src/clis/douyin/stats.ts +30 -0
- package/src/clis/douyin/update.test.ts +12 -0
- package/src/clis/douyin/update.ts +43 -0
- package/src/clis/douyin/videos.test.ts +12 -0
- package/src/clis/douyin/videos.ts +49 -0
- package/src/clis/hackernews/search.yaml +1 -1
- package/src/clis/imdb/person.ts +232 -0
- package/src/clis/imdb/reviews.ts +111 -0
- package/src/clis/imdb/search.ts +179 -0
- package/src/clis/imdb/title.ts +121 -0
- package/src/clis/imdb/top.ts +67 -0
- package/src/clis/imdb/trending.ts +66 -0
- package/src/clis/imdb/utils.test.ts +117 -0
- package/src/clis/imdb/utils.ts +305 -0
- package/src/clis/instagram/search.yaml +2 -1
- package/src/clis/jd/item.test.ts +18 -1
- package/src/clis/jd/item.ts +18 -15
- package/src/clis/linux-do/categories.yaml +38 -9
- package/src/clis/linux-do/category.ts +37 -0
- package/src/clis/linux-do/feed.test.ts +132 -0
- package/src/clis/linux-do/feed.ts +501 -0
- package/src/clis/linux-do/hot.ts +26 -0
- package/src/clis/linux-do/latest.ts +19 -0
- package/src/clis/linux-do/search.yaml +3 -1
- package/src/clis/linux-do/tags.yaml +41 -0
- package/src/clis/linux-do/topic.yaml +41 -3
- package/src/clis/linux-do/user-posts.yaml +67 -0
- package/src/clis/linux-do/user-topics.yaml +54 -0
- package/src/clis/medium/search.ts +1 -1
- package/src/clis/paperreview/commands.test.ts +283 -0
- package/src/clis/paperreview/feedback.ts +64 -0
- package/src/clis/paperreview/review.ts +47 -0
- package/src/clis/paperreview/submit.ts +119 -0
- package/src/clis/paperreview/utils.test.ts +68 -0
- package/src/clis/paperreview/utils.ts +276 -0
- package/src/clis/producthunt/browse.ts +109 -0
- package/src/clis/producthunt/hot.ts +127 -0
- package/src/clis/producthunt/posts.ts +29 -0
- package/src/clis/producthunt/today.ts +37 -0
- package/src/clis/producthunt/utils.test.ts +72 -0
- package/src/clis/producthunt/utils.ts +122 -0
- package/src/clis/reuters/search.ts +0 -1
- package/src/clis/twitter/article.ts +5 -28
- package/src/clis/twitter/likes.test.ts +91 -0
- package/src/clis/twitter/likes.ts +256 -0
- package/src/clis/twitter/profile.ts +5 -28
- package/src/clis/twitter/search.test.ts +71 -2
- package/src/clis/twitter/search.ts +8 -4
- package/src/clis/twitter/shared.ts +45 -0
- package/src/clis/twitter/timeline.ts +2 -13
- package/src/clis/weibo/comments.ts +54 -0
- package/src/clis/weibo/feed.ts +57 -0
- package/src/clis/weibo/hot.ts +0 -1
- package/src/clis/weibo/me.ts +77 -0
- package/src/clis/weibo/post.ts +77 -0
- package/src/clis/weibo/user.ts +64 -0
- package/src/clis/weibo/utils.ts +32 -0
- package/src/clis/weixin/download.ts +114 -20
- package/src/clis/weread/book.ts +2 -2
- package/src/clis/weread/commands.test.ts +57 -0
- package/src/clis/weread/highlights.ts +2 -2
- package/src/clis/weread/notebooks.ts +2 -2
- package/src/clis/weread/notes.ts +3 -3
- package/src/clis/weread/search.ts +3 -2
- package/src/clis/weread/shelf.ts +2 -2
- package/src/clis/weread/utils.test.ts +1 -32
- package/src/clis/weread/utils.ts +41 -16
- package/src/clis/xiaohongshu/comments.test.ts +96 -0
- package/src/clis/xiaohongshu/comments.ts +81 -0
- package/src/clis/xiaohongshu/publish.test.ts +137 -0
- package/src/clis/xiaohongshu/publish.ts +129 -18
- package/src/clis/xueqiu/search.yaml +2 -1
- package/src/clis/yahoo-finance/quote.ts +0 -1
- package/src/clis/youtube/channel.ts +155 -0
- package/src/clis/youtube/comments.ts +97 -0
- package/src/clis/youtube/search.ts +0 -1
- package/src/clis/zhihu/search.yaml +2 -1
- package/src/commanderAdapter.test.ts +78 -0
- package/src/commanderAdapter.ts +188 -24
- package/src/daemon.ts +19 -1
- package/src/discovery.ts +8 -15
- package/src/doctor.ts +13 -2
- package/src/download/index.test.ts +14 -4
- package/src/download/index.ts +67 -55
- package/src/errors.ts +25 -66
- package/src/execution.ts +28 -3
- package/src/external-clis.yaml +0 -17
- package/src/hooks.ts +1 -0
- package/src/main.ts +6 -0
- package/src/output.ts +3 -1
- package/src/pipeline/executor.ts +4 -6
- package/src/plugin-manifest.test.ts +223 -0
- package/src/plugin-manifest.ts +206 -0
- package/src/plugin.test.ts +246 -2
- package/src/plugin.ts +338 -36
- package/src/registry.ts +6 -1
- package/src/runtime-detect.test.ts +30 -0
- package/src/runtime-detect.ts +36 -0
- package/src/runtime.ts +1 -1
- package/src/serialization.ts +4 -0
- package/src/types.ts +1 -0
- package/src/update-check.ts +114 -0
- package/src/weixin-download.test.ts +64 -0
- package/src/weread-private-api-regression.test.ts +150 -0
- package/src/weread-search-regression.test.ts +44 -0
- package/src/yaml-schema.ts +20 -0
- package/tests/e2e/browser-auth.test.ts +13 -9
- package/tests/e2e/browser-public-extended.test.ts +162 -0
- package/tests/e2e/browser-public.test.ts +55 -136
- package/tests/e2e/helpers.ts +2 -1
- package/tests/e2e/public-commands.test.ts +37 -3
- package/tests/smoke/api-health.test.ts +1 -1
- package/vitest.config.ts +34 -17
- package/dist/clis/linux-do/category.yaml +0 -51
- package/dist/clis/linux-do/hot.yaml +0 -50
- package/dist/clis/linux-do/latest.yaml +0 -40
- package/src/clis/linux-do/category.yaml +0 -51
- package/src/clis/linux-do/hot.yaml +0 -50
- package/src/clis/linux-do/latest.yaml +0 -40
package/src/plugin.ts
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
* Plugin management: install, uninstall, and list plugins.
|
|
3
3
|
*
|
|
4
4
|
* Plugins live in ~/.opencli/plugins/<name>/.
|
|
5
|
-
*
|
|
5
|
+
* Monorepo clones live in ~/.opencli/monorepos/<repo-name>/.
|
|
6
|
+
* Install source format: "github:user/repo" or "github:user/repo/subplugin"
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import * as fs from 'node:fs';
|
|
@@ -13,6 +14,13 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
14
|
import { PLUGINS_DIR } from './discovery.js';
|
|
14
15
|
import { getErrorMessage } from './errors.js';
|
|
15
16
|
import { log } from './logger.js';
|
|
17
|
+
import {
|
|
18
|
+
readPluginManifest,
|
|
19
|
+
isMonorepo,
|
|
20
|
+
getEnabledPlugins,
|
|
21
|
+
checkCompatibility,
|
|
22
|
+
type PluginManifest,
|
|
23
|
+
} from './plugin-manifest.js';
|
|
16
24
|
|
|
17
25
|
const isWindows = process.platform === 'win32';
|
|
18
26
|
|
|
@@ -26,14 +34,27 @@ export function getLockFilePath(): string {
|
|
|
26
34
|
return path.join(getHomeDir(), '.opencli', 'plugins.lock.json');
|
|
27
35
|
}
|
|
28
36
|
|
|
37
|
+
/** Monorepo clones directory: ~/.opencli/monorepos/ */
|
|
38
|
+
export function getMonoreposDir(): string {
|
|
39
|
+
return path.join(getHomeDir(), '.opencli', 'monorepos');
|
|
40
|
+
}
|
|
41
|
+
|
|
29
42
|
// Legacy const for backward compatibility (computed at load time)
|
|
30
43
|
export const LOCK_FILE = path.join(os.homedir(), '.opencli', 'plugins.lock.json');
|
|
44
|
+
export const MONOREPOS_DIR = path.join(os.homedir(), '.opencli', 'monorepos');
|
|
31
45
|
|
|
32
46
|
export interface LockEntry {
|
|
33
47
|
source: string;
|
|
34
48
|
commitHash: string;
|
|
35
49
|
installedAt: string;
|
|
36
50
|
updatedAt?: string;
|
|
51
|
+
/** Present when this plugin comes from a monorepo. */
|
|
52
|
+
monorepo?: {
|
|
53
|
+
/** Monorepo directory name under ~/.opencli/monorepos/ */
|
|
54
|
+
name: string;
|
|
55
|
+
/** Relative path of this sub-plugin within the monorepo. */
|
|
56
|
+
subPath: string;
|
|
57
|
+
};
|
|
37
58
|
}
|
|
38
59
|
|
|
39
60
|
export interface PluginInfo {
|
|
@@ -43,6 +64,10 @@ export interface PluginInfo {
|
|
|
43
64
|
source?: string;
|
|
44
65
|
version?: string;
|
|
45
66
|
installedAt?: string;
|
|
67
|
+
/** If from a monorepo, the monorepo name. */
|
|
68
|
+
monorepoName?: string;
|
|
69
|
+
/** Description from opencli-plugin.json. */
|
|
70
|
+
description?: string;
|
|
46
71
|
}
|
|
47
72
|
|
|
48
73
|
// ── Validation helpers ──────────────────────────────────────────────────────
|
|
@@ -122,25 +147,23 @@ export function validatePluginStructure(pluginDir: string): ValidationResult {
|
|
|
122
147
|
return { valid: errors.length === 0, errors };
|
|
123
148
|
}
|
|
124
149
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
* Called by both installPlugin() and updatePlugin().
|
|
128
|
-
*/
|
|
129
|
-
function postInstallLifecycle(pluginDir: string): void {
|
|
130
|
-
const pkgJsonPath = path.join(pluginDir, 'package.json');
|
|
150
|
+
function installDependencies(dir: string): void {
|
|
151
|
+
const pkgJsonPath = path.join(dir, 'package.json');
|
|
131
152
|
if (!fs.existsSync(pkgJsonPath)) return;
|
|
132
153
|
|
|
133
154
|
try {
|
|
134
155
|
execFileSync('npm', ['install', '--omit=dev'], {
|
|
135
|
-
cwd:
|
|
156
|
+
cwd: dir,
|
|
136
157
|
encoding: 'utf-8',
|
|
137
158
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
138
159
|
...(isWindows && { shell: true }),
|
|
139
160
|
});
|
|
140
161
|
} catch (err) {
|
|
141
|
-
|
|
162
|
+
throw new Error(`npm install failed in ${dir}: ${getErrorMessage(err)}`);
|
|
142
163
|
}
|
|
164
|
+
}
|
|
143
165
|
|
|
166
|
+
function finalizePluginRuntime(pluginDir: string): void {
|
|
144
167
|
// Symlink host opencli so TS plugins resolve '@jackwener/opencli/registry'
|
|
145
168
|
// against the running host, not a stale npm-published version.
|
|
146
169
|
linkHostOpencli(pluginDir);
|
|
@@ -149,33 +172,51 @@ function postInstallLifecycle(pluginDir: string): void {
|
|
|
149
172
|
transpilePluginTs(pluginDir);
|
|
150
173
|
}
|
|
151
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Shared post-install lifecycle for standalone plugins.
|
|
177
|
+
*/
|
|
178
|
+
function postInstallLifecycle(pluginDir: string): void {
|
|
179
|
+
installDependencies(pluginDir);
|
|
180
|
+
finalizePluginRuntime(pluginDir);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Monorepo lifecycle: install shared deps once at repo root, then finalize each sub-plugin.
|
|
185
|
+
*/
|
|
186
|
+
function postInstallMonorepoLifecycle(repoDir: string, pluginDirs: string[]): void {
|
|
187
|
+
installDependencies(repoDir);
|
|
188
|
+
for (const pluginDir of pluginDirs) {
|
|
189
|
+
finalizePluginRuntime(pluginDir);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
152
193
|
/**
|
|
153
194
|
* Install a plugin from a source.
|
|
154
|
-
*
|
|
195
|
+
* Supports:
|
|
196
|
+
* "github:user/repo" — single plugin or full monorepo
|
|
197
|
+
* "github:user/repo/subplugin" — specific sub-plugin from a monorepo
|
|
198
|
+
* "https://github.com/user/repo"
|
|
199
|
+
*
|
|
200
|
+
* Returns the installed plugin name(s).
|
|
155
201
|
*/
|
|
156
|
-
export function installPlugin(source: string): string {
|
|
202
|
+
export function installPlugin(source: string): string | string[] {
|
|
157
203
|
const parsed = parseSource(source);
|
|
158
204
|
if (!parsed) {
|
|
159
205
|
throw new Error(
|
|
160
206
|
`Invalid plugin source: "${source}"\n` +
|
|
161
207
|
`Supported formats:\n` +
|
|
162
208
|
` github:user/repo\n` +
|
|
209
|
+
` github:user/repo/subplugin\n` +
|
|
163
210
|
` https://github.com/user/repo`
|
|
164
211
|
);
|
|
165
212
|
}
|
|
166
213
|
|
|
167
|
-
const { cloneUrl, name } = parsed;
|
|
168
|
-
const targetDir = path.join(PLUGINS_DIR, name);
|
|
169
|
-
|
|
170
|
-
if (fs.existsSync(targetDir)) {
|
|
171
|
-
throw new Error(`Plugin "${name}" is already installed at ${targetDir}`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Ensure plugins directory exists
|
|
175
|
-
fs.mkdirSync(PLUGINS_DIR, { recursive: true });
|
|
214
|
+
const { cloneUrl, name: repoName, subPlugin } = parsed;
|
|
176
215
|
|
|
216
|
+
// Clone to a temporary location first so we can inspect the manifest
|
|
217
|
+
const tmpCloneDir = path.join(os.tmpdir(), `opencli-clone-${Date.now()}`);
|
|
177
218
|
try {
|
|
178
|
-
execFileSync('git', ['clone', '--depth', '1', cloneUrl,
|
|
219
|
+
execFileSync('git', ['clone', '--depth', '1', cloneUrl, tmpCloneDir], {
|
|
179
220
|
encoding: 'utf-8',
|
|
180
221
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
181
222
|
});
|
|
@@ -183,19 +224,56 @@ export function installPlugin(source: string): string {
|
|
|
183
224
|
throw new Error(`Failed to clone plugin: ${getErrorMessage(err)}`);
|
|
184
225
|
}
|
|
185
226
|
|
|
186
|
-
|
|
227
|
+
try {
|
|
228
|
+
const manifest = readPluginManifest(tmpCloneDir);
|
|
229
|
+
|
|
230
|
+
// Check top-level compatibility
|
|
231
|
+
if (manifest?.opencli && !checkCompatibility(manifest.opencli)) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Plugin requires opencli ${manifest.opencli}, but current version is incompatible.`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (manifest && isMonorepo(manifest)) {
|
|
238
|
+
return installMonorepo(tmpCloneDir, cloneUrl, repoName, manifest, subPlugin);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Single plugin mode
|
|
242
|
+
return installSinglePlugin(tmpCloneDir, cloneUrl, repoName, manifest);
|
|
243
|
+
} finally {
|
|
244
|
+
// Clean up temp clone (may already have been moved)
|
|
245
|
+
try { fs.rmSync(tmpCloneDir, { recursive: true, force: true }); } catch {}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Install a single (non-monorepo) plugin. */
|
|
250
|
+
function installSinglePlugin(
|
|
251
|
+
cloneDir: string,
|
|
252
|
+
cloneUrl: string,
|
|
253
|
+
name: string,
|
|
254
|
+
manifest: PluginManifest | null,
|
|
255
|
+
): string {
|
|
256
|
+
const pluginName = manifest?.name ?? name;
|
|
257
|
+
const targetDir = path.join(PLUGINS_DIR, pluginName);
|
|
258
|
+
|
|
259
|
+
if (fs.existsSync(targetDir)) {
|
|
260
|
+
throw new Error(`Plugin "${pluginName}" is already installed at ${targetDir}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const validation = validatePluginStructure(cloneDir);
|
|
187
264
|
if (!validation.valid) {
|
|
188
|
-
// If validation fails, clean up the cloned directory and abort
|
|
189
|
-
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
190
265
|
throw new Error(`Invalid plugin structure:\n- ${validation.errors.join('\n- ')}`);
|
|
191
266
|
}
|
|
192
267
|
|
|
268
|
+
fs.mkdirSync(PLUGINS_DIR, { recursive: true });
|
|
269
|
+
fs.renameSync(cloneDir, targetDir);
|
|
270
|
+
|
|
193
271
|
postInstallLifecycle(targetDir);
|
|
194
272
|
|
|
195
273
|
const commitHash = getCommitHash(targetDir);
|
|
196
274
|
if (commitHash) {
|
|
197
275
|
const lock = readLockFile();
|
|
198
|
-
lock[
|
|
276
|
+
lock[pluginName] = {
|
|
199
277
|
source: cloneUrl,
|
|
200
278
|
commitHash,
|
|
201
279
|
installedAt: new Date().toISOString(),
|
|
@@ -203,28 +281,160 @@ export function installPlugin(source: string): string {
|
|
|
203
281
|
writeLockFile(lock);
|
|
204
282
|
}
|
|
205
283
|
|
|
206
|
-
return
|
|
284
|
+
return pluginName;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Install sub-plugins from a monorepo. */
|
|
288
|
+
function installMonorepo(
|
|
289
|
+
cloneDir: string,
|
|
290
|
+
cloneUrl: string,
|
|
291
|
+
repoName: string,
|
|
292
|
+
manifest: PluginManifest,
|
|
293
|
+
subPlugin?: string,
|
|
294
|
+
): string[] {
|
|
295
|
+
const monoreposDir = getMonoreposDir();
|
|
296
|
+
const repoDir = path.join(monoreposDir, repoName);
|
|
297
|
+
|
|
298
|
+
// Move clone to permanent monorepos location (if not already there)
|
|
299
|
+
if (!fs.existsSync(repoDir)) {
|
|
300
|
+
fs.mkdirSync(monoreposDir, { recursive: true });
|
|
301
|
+
fs.renameSync(cloneDir, repoDir);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let pluginsToInstall = getEnabledPlugins(manifest);
|
|
305
|
+
|
|
306
|
+
// If a specific sub-plugin was requested, filter to just that one
|
|
307
|
+
if (subPlugin) {
|
|
308
|
+
pluginsToInstall = pluginsToInstall.filter((p) => p.name === subPlugin);
|
|
309
|
+
if (pluginsToInstall.length === 0) {
|
|
310
|
+
// Check if it exists but is disabled
|
|
311
|
+
const disabled = manifest.plugins?.[subPlugin];
|
|
312
|
+
if (disabled) {
|
|
313
|
+
throw new Error(`Sub-plugin "${subPlugin}" is disabled in the manifest.`);
|
|
314
|
+
}
|
|
315
|
+
throw new Error(
|
|
316
|
+
`Sub-plugin "${subPlugin}" not found in monorepo. Available: ${Object.keys(manifest.plugins ?? {}).join(', ')}`
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const installedNames: string[] = [];
|
|
322
|
+
const lock = readLockFile();
|
|
323
|
+
const commitHash = getCommitHash(repoDir);
|
|
324
|
+
const eligiblePlugins: Array<{ name: string; entry: typeof pluginsToInstall[number]['entry']; subDir: string }> = [];
|
|
325
|
+
|
|
326
|
+
fs.mkdirSync(PLUGINS_DIR, { recursive: true });
|
|
327
|
+
|
|
328
|
+
for (const { name, entry } of pluginsToInstall) {
|
|
329
|
+
// Check sub-plugin level compatibility (overrides top-level)
|
|
330
|
+
if (entry.opencli && !checkCompatibility(entry.opencli)) {
|
|
331
|
+
log.warn(`Skipping "${name}": requires opencli ${entry.opencli}`);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const subDir = path.join(repoDir, entry.path);
|
|
336
|
+
if (!fs.existsSync(subDir)) {
|
|
337
|
+
log.warn(`Skipping "${name}": path "${entry.path}" not found in repo.`);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const validation = validatePluginStructure(subDir);
|
|
342
|
+
if (!validation.valid) {
|
|
343
|
+
log.warn(`Skipping "${name}": invalid structure — ${validation.errors.join(', ')}`);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const linkPath = path.join(PLUGINS_DIR, name);
|
|
348
|
+
if (fs.existsSync(linkPath)) {
|
|
349
|
+
log.warn(`Skipping "${name}": already installed at ${linkPath}`);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
eligiblePlugins.push({ name, entry, subDir });
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (eligiblePlugins.length > 0) {
|
|
357
|
+
postInstallMonorepoLifecycle(repoDir, eligiblePlugins.map((p) => p.subDir));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
for (const { name, entry, subDir } of eligiblePlugins) {
|
|
361
|
+
const linkPath = path.join(PLUGINS_DIR, name);
|
|
362
|
+
|
|
363
|
+
// Create symlink (junction on Windows)
|
|
364
|
+
const linkType = isWindows ? 'junction' : 'dir';
|
|
365
|
+
fs.symlinkSync(subDir, linkPath, linkType);
|
|
366
|
+
|
|
367
|
+
if (commitHash) {
|
|
368
|
+
lock[name] = {
|
|
369
|
+
source: cloneUrl,
|
|
370
|
+
commitHash,
|
|
371
|
+
installedAt: new Date().toISOString(),
|
|
372
|
+
monorepo: { name: repoName, subPath: entry.path },
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
installedNames.push(name);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
writeLockFile(lock);
|
|
380
|
+
return installedNames;
|
|
207
381
|
}
|
|
208
382
|
|
|
209
383
|
/**
|
|
210
384
|
* Uninstall a plugin by name.
|
|
385
|
+
* For monorepo sub-plugins: removes symlink and cleans up the monorepo
|
|
386
|
+
* directory when no more sub-plugins reference it.
|
|
211
387
|
*/
|
|
212
388
|
export function uninstallPlugin(name: string): void {
|
|
213
389
|
const targetDir = path.join(PLUGINS_DIR, name);
|
|
214
390
|
if (!fs.existsSync(targetDir)) {
|
|
215
391
|
throw new Error(`Plugin "${name}" is not installed.`);
|
|
216
392
|
}
|
|
217
|
-
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
218
393
|
|
|
219
394
|
const lock = readLockFile();
|
|
220
|
-
|
|
395
|
+
const lockEntry = lock[name];
|
|
396
|
+
|
|
397
|
+
// Check if this is a symlink (monorepo sub-plugin)
|
|
398
|
+
const isSymlink = isSymlinkSync(targetDir);
|
|
399
|
+
|
|
400
|
+
if (isSymlink) {
|
|
401
|
+
// Remove symlink only (not the actual directory)
|
|
402
|
+
fs.unlinkSync(targetDir);
|
|
403
|
+
} else {
|
|
404
|
+
fs.rmSync(targetDir, { recursive: true, force: true });
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Clean up monorepo directory if no more sub-plugins reference it
|
|
408
|
+
if (lockEntry?.monorepo) {
|
|
221
409
|
delete lock[name];
|
|
222
|
-
|
|
410
|
+
const monoName = lockEntry.monorepo.name;
|
|
411
|
+
const stillReferenced = Object.values(lock).some(
|
|
412
|
+
(entry) => entry.monorepo?.name === monoName,
|
|
413
|
+
);
|
|
414
|
+
if (!stillReferenced) {
|
|
415
|
+
const monoDir = path.join(getMonoreposDir(), monoName);
|
|
416
|
+
try { fs.rmSync(monoDir, { recursive: true, force: true }); } catch {}
|
|
417
|
+
}
|
|
418
|
+
} else if (lock[name]) {
|
|
419
|
+
delete lock[name];
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
writeLockFile(lock);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** Synchronous check if a path is a symlink. */
|
|
426
|
+
function isSymlinkSync(p: string): boolean {
|
|
427
|
+
try {
|
|
428
|
+
return fs.lstatSync(p).isSymbolicLink();
|
|
429
|
+
} catch {
|
|
430
|
+
return false;
|
|
223
431
|
}
|
|
224
432
|
}
|
|
225
433
|
|
|
226
434
|
/**
|
|
227
435
|
* Update a plugin by name (git pull + re-install lifecycle).
|
|
436
|
+
* For monorepo sub-plugins: pulls the monorepo root and re-runs lifecycle
|
|
437
|
+
* for all sub-plugins from the same monorepo.
|
|
228
438
|
*/
|
|
229
439
|
export function updatePlugin(name: string): void {
|
|
230
440
|
const targetDir = path.join(PLUGINS_DIR, name);
|
|
@@ -232,6 +442,53 @@ export function updatePlugin(name: string): void {
|
|
|
232
442
|
throw new Error(`Plugin "${name}" is not installed.`);
|
|
233
443
|
}
|
|
234
444
|
|
|
445
|
+
const lock = readLockFile();
|
|
446
|
+
const lockEntry = lock[name];
|
|
447
|
+
|
|
448
|
+
if (lockEntry?.monorepo) {
|
|
449
|
+
// Monorepo update: pull the repo root
|
|
450
|
+
const monoDir = path.join(getMonoreposDir(), lockEntry.monorepo.name);
|
|
451
|
+
try {
|
|
452
|
+
execFileSync('git', ['pull', '--ff-only'], {
|
|
453
|
+
cwd: monoDir,
|
|
454
|
+
encoding: 'utf-8',
|
|
455
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
456
|
+
});
|
|
457
|
+
} catch (err) {
|
|
458
|
+
throw new Error(`Failed to update monorepo: ${getErrorMessage(err)}`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Re-run lifecycle for ALL sub-plugins from this monorepo
|
|
462
|
+
const monoName = lockEntry.monorepo.name;
|
|
463
|
+
const commitHash = getCommitHash(monoDir);
|
|
464
|
+
const pluginDirs: string[] = [];
|
|
465
|
+
for (const [pluginName, entry] of Object.entries(lock)) {
|
|
466
|
+
if (entry.monorepo?.name !== monoName) continue;
|
|
467
|
+
const subDir = path.join(monoDir, entry.monorepo.subPath);
|
|
468
|
+
const validation = validatePluginStructure(subDir);
|
|
469
|
+
if (!validation.valid) {
|
|
470
|
+
log.warn(`Plugin "${pluginName}" structure invalid after update:\n- ${validation.errors.join('\n- ')}`);
|
|
471
|
+
}
|
|
472
|
+
pluginDirs.push(subDir);
|
|
473
|
+
}
|
|
474
|
+
if (pluginDirs.length > 0) {
|
|
475
|
+
postInstallMonorepoLifecycle(monoDir, pluginDirs);
|
|
476
|
+
}
|
|
477
|
+
for (const [pluginName, entry] of Object.entries(lock)) {
|
|
478
|
+
if (entry.monorepo?.name !== monoName) continue;
|
|
479
|
+
if (commitHash) {
|
|
480
|
+
lock[pluginName] = {
|
|
481
|
+
...entry,
|
|
482
|
+
commitHash,
|
|
483
|
+
updatedAt: new Date().toISOString(),
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
writeLockFile(lock);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Standard single-plugin update
|
|
235
492
|
try {
|
|
236
493
|
execFileSync('git', ['pull', '--ff-only'], {
|
|
237
494
|
cwd: targetDir,
|
|
@@ -251,7 +508,6 @@ export function updatePlugin(name: string): void {
|
|
|
251
508
|
|
|
252
509
|
const commitHash = getCommitHash(targetDir);
|
|
253
510
|
if (commitHash) {
|
|
254
|
-
const lock = readLockFile();
|
|
255
511
|
const existing = lock[name];
|
|
256
512
|
lock[name] = {
|
|
257
513
|
source: existing?.source ?? getPluginSource(targetDir) ?? '',
|
|
@@ -290,6 +546,7 @@ export function updateAllPlugins(): UpdateResult[] {
|
|
|
290
546
|
|
|
291
547
|
/**
|
|
292
548
|
* List all installed plugins.
|
|
549
|
+
* Reads opencli-plugin.json for description/version when available.
|
|
293
550
|
*/
|
|
294
551
|
export function listPlugins(): PluginInfo[] {
|
|
295
552
|
if (!fs.existsSync(PLUGINS_DIR)) return [];
|
|
@@ -299,19 +556,42 @@ export function listPlugins(): PluginInfo[] {
|
|
|
299
556
|
const plugins: PluginInfo[] = [];
|
|
300
557
|
|
|
301
558
|
for (const entry of entries) {
|
|
302
|
-
|
|
559
|
+
// Accept both real directories and symlinks (monorepo sub-plugins)
|
|
303
560
|
const pluginDir = path.join(PLUGINS_DIR, entry.name);
|
|
561
|
+
const isDir = entry.isDirectory() || isSymlinkSync(pluginDir);
|
|
562
|
+
if (!isDir) continue;
|
|
563
|
+
|
|
304
564
|
const commands = scanPluginCommands(pluginDir);
|
|
305
|
-
const source = getPluginSource(pluginDir);
|
|
306
565
|
const lockEntry = lock[entry.name];
|
|
307
566
|
|
|
567
|
+
// Try to read manifest for metadata
|
|
568
|
+
const manifest = readPluginManifest(pluginDir);
|
|
569
|
+
// For monorepo sub-plugins, also check the monorepo root manifest
|
|
570
|
+
let description = manifest?.description;
|
|
571
|
+
let version = manifest?.version;
|
|
572
|
+
if (lockEntry?.monorepo && !description) {
|
|
573
|
+
const monoDir = path.join(getMonoreposDir(), lockEntry.monorepo.name);
|
|
574
|
+
const monoManifest = readPluginManifest(monoDir);
|
|
575
|
+
const subEntry = monoManifest?.plugins?.[entry.name];
|
|
576
|
+
if (subEntry) {
|
|
577
|
+
description = description ?? subEntry.description;
|
|
578
|
+
version = version ?? subEntry.version;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
const source = lockEntry?.monorepo
|
|
583
|
+
? lockEntry.source
|
|
584
|
+
: getPluginSource(pluginDir);
|
|
585
|
+
|
|
308
586
|
plugins.push({
|
|
309
587
|
name: entry.name,
|
|
310
588
|
path: pluginDir,
|
|
311
589
|
commands,
|
|
312
590
|
source,
|
|
313
|
-
version: lockEntry?.commitHash?.slice(0, 7),
|
|
591
|
+
version: version ?? lockEntry?.commitHash?.slice(0, 7),
|
|
314
592
|
installedAt: lockEntry?.installedAt,
|
|
593
|
+
monorepoName: lockEntry?.monorepo?.name,
|
|
594
|
+
description,
|
|
315
595
|
});
|
|
316
596
|
}
|
|
317
597
|
|
|
@@ -350,8 +630,24 @@ function getPluginSource(dir: string): string | undefined {
|
|
|
350
630
|
}
|
|
351
631
|
}
|
|
352
632
|
|
|
353
|
-
/** Parse a plugin source string into clone URL
|
|
354
|
-
function parseSource(
|
|
633
|
+
/** Parse a plugin source string into clone URL, repo name, and optional sub-plugin. */
|
|
634
|
+
function parseSource(
|
|
635
|
+
source: string,
|
|
636
|
+
): { cloneUrl: string; name: string; subPlugin?: string } | null {
|
|
637
|
+
// github:user/repo/subplugin (monorepo specific sub-plugin)
|
|
638
|
+
const githubSubMatch = source.match(
|
|
639
|
+
/^github:([\w.-]+)\/([\w.-]+)\/([\w.-]+)$/,
|
|
640
|
+
);
|
|
641
|
+
if (githubSubMatch) {
|
|
642
|
+
const [, user, repo, sub] = githubSubMatch;
|
|
643
|
+
const name = repo.replace(/^opencli-plugin-/, '');
|
|
644
|
+
return {
|
|
645
|
+
cloneUrl: `https://github.com/${user}/${repo}.git`,
|
|
646
|
+
name,
|
|
647
|
+
subPlugin: sub,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
355
651
|
// github:user/repo
|
|
356
652
|
const githubMatch = source.match(/^github:([\w.-]+)\/([\w.-]+)$/);
|
|
357
653
|
if (githubMatch) {
|
|
@@ -364,7 +660,9 @@ function parseSource(source: string): { cloneUrl: string; name: string } | null
|
|
|
364
660
|
}
|
|
365
661
|
|
|
366
662
|
// https://github.com/user/repo (or .git)
|
|
367
|
-
const urlMatch = source.match(
|
|
663
|
+
const urlMatch = source.match(
|
|
664
|
+
/^https?:\/\/github\.com\/([\w.-]+)\/([\w.-]+?)(?:\.git)?$/,
|
|
665
|
+
);
|
|
368
666
|
if (urlMatch) {
|
|
369
667
|
const [, user, repo] = urlMatch;
|
|
370
668
|
const name = repo.replace(/^opencli-plugin-/, '');
|
|
@@ -514,9 +812,13 @@ function transpilePluginTs(pluginDir: string): void {
|
|
|
514
812
|
export {
|
|
515
813
|
resolveEsbuildBin as _resolveEsbuildBin,
|
|
516
814
|
getCommitHash as _getCommitHash,
|
|
815
|
+
installDependencies as _installDependencies,
|
|
517
816
|
parseSource as _parseSource,
|
|
817
|
+
postInstallMonorepoLifecycle as _postInstallMonorepoLifecycle,
|
|
518
818
|
readLockFile as _readLockFile,
|
|
519
819
|
updateAllPlugins as _updateAllPlugins,
|
|
520
820
|
validatePluginStructure as _validatePluginStructure,
|
|
521
821
|
writeLockFile as _writeLockFile,
|
|
822
|
+
isSymlinkSync as _isSymlinkSync,
|
|
823
|
+
getMonoreposDir as _getMonoreposDir,
|
|
522
824
|
};
|
package/src/registry.ts
CHANGED
|
@@ -46,6 +46,10 @@ export interface CliCommand {
|
|
|
46
46
|
source?: string;
|
|
47
47
|
footerExtra?: (kwargs: CommandArgs) => string | undefined;
|
|
48
48
|
requiredEnv?: RequiredEnv[];
|
|
49
|
+
/** Deprecation note shown in help / execution warnings. */
|
|
50
|
+
deprecated?: boolean | string;
|
|
51
|
+
/** Preferred replacement command, if any. */
|
|
52
|
+
replacedBy?: string;
|
|
49
53
|
/**
|
|
50
54
|
* Control pre-navigation for cookie/header context before command execution.
|
|
51
55
|
*
|
|
@@ -95,6 +99,8 @@ export function cli(opts: CliOptions): CliCommand {
|
|
|
95
99
|
timeoutSeconds: opts.timeoutSeconds,
|
|
96
100
|
footerExtra: opts.footerExtra,
|
|
97
101
|
requiredEnv: opts.requiredEnv,
|
|
102
|
+
deprecated: opts.deprecated,
|
|
103
|
+
replacedBy: opts.replacedBy,
|
|
98
104
|
navigateBefore: opts.navigateBefore,
|
|
99
105
|
};
|
|
100
106
|
|
|
@@ -118,4 +124,3 @@ export function strategyLabel(cmd: CliCommand): string {
|
|
|
118
124
|
export function registerCommand(cmd: CliCommand): void {
|
|
119
125
|
_registry.set(fullName(cmd), cmd);
|
|
120
126
|
}
|
|
121
|
-
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { detectRuntime, getRuntimeVersion, getRuntimeLabel } from './runtime-detect.js';
|
|
3
|
+
|
|
4
|
+
describe('runtime-detect', () => {
|
|
5
|
+
it('detectRuntime returns a valid runtime string', () => {
|
|
6
|
+
const rt = detectRuntime();
|
|
7
|
+
expect(['bun', 'node']).toContain(rt);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('getRuntimeVersion returns a non-empty version string', () => {
|
|
11
|
+
const ver = getRuntimeVersion();
|
|
12
|
+
expect(typeof ver).toBe('string');
|
|
13
|
+
expect(ver.length).toBeGreaterThan(0);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('getRuntimeLabel returns "<runtime> <version>" format', () => {
|
|
17
|
+
const label = getRuntimeLabel();
|
|
18
|
+
expect(label).toMatch(/^(bun|node) .+$/);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('detects the current environment correctly', () => {
|
|
22
|
+
const isBun = typeof (globalThis as any).Bun !== 'undefined';
|
|
23
|
+
const rt = detectRuntime();
|
|
24
|
+
if (isBun) {
|
|
25
|
+
expect(rt).toBe('bun');
|
|
26
|
+
} else {
|
|
27
|
+
expect(rt).toBe('node');
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime detection — identify whether opencli is running under Node.js or Bun.
|
|
3
|
+
*
|
|
4
|
+
* Bun injects `globalThis.Bun` at startup, making detection trivial.
|
|
5
|
+
* This module centralises the check so other code can adapt behaviour
|
|
6
|
+
* (e.g. logging, diagnostics) without littering runtime sniffing everywhere.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export type Runtime = 'bun' | 'node';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Detect the current JavaScript runtime.
|
|
13
|
+
*/
|
|
14
|
+
export function detectRuntime(): Runtime {
|
|
15
|
+
// Bun always exposes globalThis.Bun (including Bun.version)
|
|
16
|
+
if (typeof (globalThis as any).Bun !== 'undefined') return 'bun';
|
|
17
|
+
return 'node';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Return a human-readable version string for the current runtime.
|
|
22
|
+
* Examples: "v22.13.0" (Node), "1.1.42" (Bun)
|
|
23
|
+
*/
|
|
24
|
+
export function getRuntimeVersion(): string {
|
|
25
|
+
if (detectRuntime() === 'bun') {
|
|
26
|
+
return (globalThis as any).Bun.version as string;
|
|
27
|
+
}
|
|
28
|
+
return process.version; // e.g. "v22.13.0"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Return a combined label like "node v22.13.0" or "bun 1.1.42".
|
|
33
|
+
*/
|
|
34
|
+
export function getRuntimeLabel(): string {
|
|
35
|
+
return `${detectRuntime()} ${getRuntimeVersion()}`;
|
|
36
|
+
}
|
package/src/runtime.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { TimeoutError } from './errors.js';
|
|
|
7
7
|
* Uses CDPBridge when OPENCLI_CDP_ENDPOINT is set, otherwise BrowserBridge.
|
|
8
8
|
*/
|
|
9
9
|
export function getBrowserFactory(): new () => IBrowserFactory {
|
|
10
|
-
return
|
|
10
|
+
return process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
13
|
function parseEnvTimeout(envVar: string, fallback: number): number {
|
package/src/serialization.ts
CHANGED
|
@@ -45,6 +45,8 @@ export function serializeCommand(cmd: CliCommand) {
|
|
|
45
45
|
args: cmd.args.map(serializeArg),
|
|
46
46
|
columns: cmd.columns ?? [],
|
|
47
47
|
domain: cmd.domain ?? null,
|
|
48
|
+
deprecated: cmd.deprecated ?? null,
|
|
49
|
+
replacedBy: cmd.replacedBy ?? null,
|
|
48
50
|
};
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -73,6 +75,8 @@ export function formatRegistryHelpText(cmd: CliCommand): string {
|
|
|
73
75
|
meta.push(`Strategy: ${strategyLabel(cmd)}`);
|
|
74
76
|
meta.push(`Browser: ${cmd.browser ? 'yes' : 'no'}`);
|
|
75
77
|
if (cmd.domain) meta.push(`Domain: ${cmd.domain}`);
|
|
78
|
+
if (cmd.deprecated) meta.push(`Deprecated: ${typeof cmd.deprecated === 'string' ? cmd.deprecated : 'yes'}`);
|
|
79
|
+
if (cmd.replacedBy) meta.push(`Use instead: ${cmd.replacedBy}`);
|
|
76
80
|
lines.push(meta.join(' | '));
|
|
77
81
|
if (cmd.columns?.length) lines.push(`Output columns: ${cmd.columns.join(', ')}`);
|
|
78
82
|
return '\n' + lines.join('\n') + '\n';
|