@jackwener/opencli 1.0.6 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/cross-project-adapter-migration/SKILL.md +2 -2
- package/.github/pull_request_template.md +7 -0
- package/.github/workflows/doc-check.yml +36 -0
- package/.github/workflows/docs.yml +7 -42
- package/CHANGELOG.md +23 -0
- package/CLI-EXPLORER.md +9 -8
- package/README.md +51 -10
- package/README.zh-CN.md +29 -11
- package/SKILL.md +102 -33
- package/dist/browser/cdp.js +6 -1
- package/dist/browser/page.d.ts +4 -1
- package/dist/browser/page.js +7 -1
- package/dist/build-manifest.js +23 -16
- package/dist/cli-manifest.json +951 -296
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +225 -148
- package/dist/clis/antigravity/serve.js +296 -47
- package/dist/clis/apple-podcasts/commands.test.d.ts +2 -0
- package/dist/clis/apple-podcasts/commands.test.js +76 -0
- package/dist/clis/apple-podcasts/search.js +2 -2
- package/dist/clis/apple-podcasts/top.js +9 -2
- package/dist/clis/arxiv/paper.js +21 -0
- package/dist/clis/arxiv/search.js +24 -0
- package/dist/clis/arxiv/utils.d.ts +18 -0
- package/dist/clis/arxiv/utils.js +49 -0
- package/dist/clis/bilibili/dynamic.js +1 -1
- package/dist/clis/bilibili/favorite.js +1 -1
- package/dist/clis/bilibili/feed.js +1 -1
- package/dist/clis/bilibili/following.js +1 -1
- package/dist/clis/bilibili/history.js +1 -1
- package/dist/clis/bilibili/me.js +1 -1
- package/dist/clis/bilibili/ranking.js +1 -1
- package/dist/clis/bilibili/search.js +3 -3
- package/dist/clis/bilibili/subtitle.js +1 -1
- package/dist/clis/bilibili/user-videos.js +1 -1
- package/dist/{bilibili.d.ts → clis/bilibili/utils.d.ts} +1 -1
- package/dist/clis/bloomberg/businessweek.d.ts +1 -0
- package/dist/clis/bloomberg/businessweek.js +17 -0
- package/dist/clis/bloomberg/economics.d.ts +1 -0
- package/dist/clis/bloomberg/economics.js +17 -0
- package/dist/clis/bloomberg/feeds.d.ts +1 -0
- package/dist/clis/bloomberg/feeds.js +15 -0
- package/dist/clis/bloomberg/industries.d.ts +1 -0
- package/dist/clis/bloomberg/industries.js +17 -0
- package/dist/clis/bloomberg/main.d.ts +1 -0
- package/dist/clis/bloomberg/main.js +17 -0
- package/dist/clis/bloomberg/markets.d.ts +1 -0
- package/dist/clis/bloomberg/markets.js +17 -0
- package/dist/clis/bloomberg/news.d.ts +1 -0
- package/dist/clis/bloomberg/news.js +105 -0
- package/dist/clis/bloomberg/opinions.d.ts +1 -0
- package/dist/clis/bloomberg/opinions.js +17 -0
- package/dist/clis/bloomberg/politics.d.ts +1 -0
- package/dist/clis/bloomberg/politics.js +17 -0
- package/dist/clis/bloomberg/tech.d.ts +1 -0
- package/dist/clis/bloomberg/tech.js +17 -0
- package/dist/clis/bloomberg/utils.d.ts +34 -0
- package/dist/clis/bloomberg/utils.js +364 -0
- package/dist/clis/bloomberg/utils.test.d.ts +1 -0
- package/dist/clis/bloomberg/utils.test.js +129 -0
- package/dist/clis/boss/batchgreet.d.ts +1 -0
- package/dist/clis/boss/batchgreet.js +147 -0
- package/dist/clis/boss/chatlist.js +2 -2
- package/dist/clis/boss/detail.js +2 -2
- package/dist/clis/boss/exchange.d.ts +1 -0
- package/dist/clis/boss/exchange.js +111 -0
- package/dist/clis/boss/greet.d.ts +1 -0
- package/dist/clis/boss/greet.js +175 -0
- package/dist/clis/boss/invite.d.ts +1 -0
- package/dist/clis/boss/invite.js +158 -0
- package/dist/clis/boss/joblist.d.ts +1 -0
- package/dist/clis/boss/joblist.js +55 -0
- package/dist/clis/boss/mark.d.ts +1 -0
- package/dist/clis/boss/mark.js +141 -0
- package/dist/clis/boss/recommend.d.ts +1 -0
- package/dist/clis/boss/recommend.js +83 -0
- package/dist/clis/boss/search.js +1 -1
- package/dist/clis/boss/send.js +1 -1
- package/dist/clis/boss/stats.d.ts +1 -0
- package/dist/clis/boss/stats.js +116 -0
- package/dist/clis/chaoxing/assignments.js +1 -1
- package/dist/clis/chaoxing/exams.js +1 -1
- package/dist/{chaoxing.d.ts → clis/chaoxing/utils.d.ts} +1 -1
- package/dist/{chaoxing.js → clis/chaoxing/utils.js} +0 -2
- package/dist/clis/chaoxing/utils.test.d.ts +1 -0
- package/dist/{chaoxing.test.js → clis/chaoxing/utils.test.js} +1 -1
- package/dist/clis/chatgpt/read.js +1 -1
- package/dist/clis/chatwise/export.js +1 -1
- package/dist/clis/chatwise/model.js +2 -2
- package/dist/clis/chatwise/screenshot.js +1 -1
- package/dist/clis/codex/export.js +1 -1
- package/dist/clis/codex/model.js +2 -2
- package/dist/clis/codex/screenshot.js +1 -1
- package/dist/clis/coupang/add-to-cart.js +3 -4
- package/dist/clis/coupang/search.js +2 -4
- package/dist/clis/coupang/utils.test.d.ts +1 -0
- package/dist/{coupang.test.js → clis/coupang/utils.test.js} +1 -1
- package/dist/clis/ctrip/search.js +1 -1
- package/dist/clis/cursor/export.js +1 -1
- package/dist/clis/cursor/model.js +2 -2
- package/dist/clis/cursor/screenshot.js +1 -1
- package/dist/clis/jike/comment.js +2 -3
- package/dist/clis/jike/create.js +1 -2
- package/dist/clis/jike/feed.js +0 -1
- package/dist/clis/jike/like.js +1 -2
- package/dist/clis/jike/notifications.js +0 -1
- package/dist/clis/jike/post.yaml +1 -0
- package/dist/clis/jike/repost.js +1 -2
- package/dist/clis/jike/search.js +2 -3
- package/dist/clis/jike/topic.yaml +1 -0
- package/dist/clis/jike/user.yaml +1 -0
- package/dist/clis/jimeng/history.yaml +0 -1
- package/dist/clis/linkedin/search.js +7 -7
- package/dist/clis/linux-do/category.yaml +1 -0
- package/dist/clis/linux-do/search.yaml +4 -3
- package/dist/clis/linux-do/topic.yaml +1 -0
- package/dist/clis/notion/export.js +1 -1
- package/dist/clis/reddit/comment.js +3 -4
- package/dist/clis/reddit/read.js +4 -5
- package/dist/clis/reddit/save.js +2 -3
- package/dist/clis/reddit/saved.js +0 -1
- package/dist/clis/reddit/search.yaml +1 -0
- package/dist/clis/reddit/subscribe.js +0 -1
- package/dist/clis/reddit/upvote.js +2 -3
- package/dist/clis/reddit/upvoted.js +0 -1
- package/dist/clis/reddit/user-comments.yaml +1 -0
- package/dist/clis/reddit/user-posts.yaml +1 -0
- package/dist/clis/reddit/user.yaml +1 -0
- package/dist/clis/reuters/search.js +1 -1
- package/dist/clis/sinafinance/news.d.ts +7 -0
- package/dist/clis/sinafinance/news.js +61 -0
- package/dist/clis/smzdm/search.js +2 -3
- package/dist/clis/stackoverflow/search.yaml +1 -0
- package/dist/clis/steam/top-sellers.yaml +29 -0
- package/dist/clis/twitter/accept.js +2 -2
- package/dist/clis/twitter/article.js +2 -2
- package/dist/clis/twitter/block.d.ts +1 -0
- package/dist/clis/twitter/block.js +88 -0
- package/dist/clis/twitter/delete.js +1 -1
- package/dist/clis/twitter/hide-reply.d.ts +1 -0
- package/dist/clis/twitter/hide-reply.js +66 -0
- package/dist/clis/twitter/like.js +1 -1
- package/dist/clis/twitter/post.js +1 -1
- package/dist/clis/twitter/reply-dm.js +1 -1
- package/dist/clis/twitter/reply.js +2 -2
- package/dist/clis/twitter/search.js +1 -1
- package/dist/clis/twitter/thread.js +2 -2
- package/dist/clis/twitter/trending.d.ts +1 -0
- package/dist/clis/twitter/trending.js +91 -0
- package/dist/clis/twitter/unblock.d.ts +1 -0
- package/dist/clis/twitter/unblock.js +71 -0
- package/dist/clis/v2ex/topic.yaml +1 -0
- package/dist/clis/weibo/hot.js +0 -1
- package/dist/clis/weread/book.js +1 -1
- package/dist/clis/weread/highlights.js +1 -1
- package/dist/clis/weread/notes.js +1 -1
- package/dist/clis/weread/search.js +1 -1
- package/dist/clis/wikipedia/search.d.ts +1 -0
- package/dist/clis/wikipedia/search.js +30 -0
- package/dist/clis/wikipedia/summary.d.ts +1 -0
- package/dist/clis/wikipedia/summary.js +28 -0
- package/dist/clis/wikipedia/utils.d.ts +8 -0
- package/dist/clis/wikipedia/utils.js +18 -0
- package/dist/clis/xiaohongshu/creator-note-detail.d.ts +79 -5
- package/dist/clis/xiaohongshu/creator-note-detail.js +323 -70
- package/dist/clis/xiaohongshu/creator-note-detail.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +258 -0
- package/dist/clis/xiaohongshu/creator-notes-summary.d.ts +28 -0
- package/dist/clis/xiaohongshu/creator-notes-summary.js +92 -0
- package/dist/clis/xiaohongshu/creator-notes-summary.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/creator-notes-summary.test.js +49 -0
- package/dist/clis/xiaohongshu/creator-notes.d.ts +18 -5
- package/dist/clis/xiaohongshu/creator-notes.js +189 -71
- package/dist/clis/xiaohongshu/creator-notes.test.d.ts +1 -0
- package/dist/clis/xiaohongshu/creator-notes.test.js +191 -0
- package/dist/clis/xiaohongshu/creator-profile.js +0 -1
- package/dist/clis/xiaohongshu/creator-stats.js +0 -1
- package/dist/clis/xiaohongshu/download.js +2 -3
- package/dist/clis/xiaohongshu/feed.yaml +0 -1
- package/dist/clis/xiaohongshu/notifications.yaml +0 -1
- package/dist/clis/xiaohongshu/search.js +2 -2
- package/dist/clis/xiaohongshu/user.js +1 -2
- package/dist/clis/yahoo-finance/quote.js +0 -1
- package/dist/clis/youtube/search.js +1 -1
- package/dist/clis/youtube/transcript.js +1 -1
- package/dist/clis/youtube/video.js +1 -1
- package/dist/clis/zhihu/download.js +1 -2
- package/dist/clis/zhihu/question.js +1 -1
- package/dist/clis/zhihu/search.yaml +4 -3
- package/dist/commanderAdapter.d.ts +21 -0
- package/dist/commanderAdapter.js +111 -0
- package/dist/{engine.d.ts → discovery.d.ts} +0 -6
- package/dist/{engine.js → discovery.js} +1 -98
- package/dist/download/index.d.ts +2 -6
- package/dist/download/index.js +19 -46
- package/dist/engine.test.d.ts +1 -1
- package/dist/engine.test.js +8 -7
- package/dist/execution.d.ts +22 -0
- package/dist/execution.js +129 -0
- package/dist/explore.js +121 -107
- package/dist/external-clis.yaml +48 -0
- package/dist/external.d.ts +25 -0
- package/dist/external.js +156 -0
- package/dist/main.js +1 -1
- package/dist/pipeline/steps/browser.js +8 -2
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +2 -0
- package/dist/runtime.d.ts +5 -0
- package/dist/runtime.js +8 -0
- package/dist/serialization.d.ts +34 -0
- package/dist/serialization.js +63 -0
- package/dist/types.d.ts +4 -1
- package/docs/.vitepress/config.mts +14 -3
- package/docs/adapters/browser/arxiv.md +27 -0
- package/docs/adapters/browser/barchart.md +32 -0
- package/docs/adapters/browser/bloomberg.md +70 -0
- package/docs/adapters/browser/chaoxing.md +39 -0
- package/docs/adapters/browser/grok.md +35 -0
- package/docs/adapters/browser/hf.md +42 -0
- package/docs/adapters/browser/jike.md +45 -0
- package/docs/adapters/browser/jimeng.md +39 -0
- package/docs/adapters/browser/linux-do.md +45 -0
- package/docs/adapters/browser/sinafinance.md +35 -0
- package/docs/adapters/browser/stackoverflow.md +35 -0
- package/docs/adapters/browser/steam.md +26 -0
- package/docs/adapters/browser/twitter.md +3 -0
- package/docs/adapters/browser/weread.md +48 -0
- package/docs/adapters/browser/wikipedia.md +30 -0
- package/docs/adapters/browser/xiaohongshu.md +5 -1
- package/docs/adapters/desktop/chatgpt.md +3 -3
- package/docs/adapters/index.md +13 -0
- package/docs/advanced/download.md +4 -4
- package/docs/developer/architecture.md +17 -4
- package/package.json +1 -1
- package/scripts/check-doc-coverage.sh +69 -0
- package/scripts/copy-yaml.cjs +7 -0
- package/src/browser/cdp.ts +9 -4
- package/src/browser/page.ts +7 -1
- package/src/build-manifest.ts +25 -19
- package/src/cli.ts +253 -119
- package/src/clis/antigravity/serve.ts +323 -50
- package/src/clis/apple-podcasts/commands.test.ts +95 -0
- package/src/clis/apple-podcasts/search.ts +2 -2
- package/src/clis/apple-podcasts/top.ts +12 -2
- package/src/clis/arxiv/paper.ts +21 -0
- package/src/clis/arxiv/search.ts +24 -0
- package/src/clis/arxiv/utils.ts +63 -0
- package/src/clis/bilibili/dynamic.ts +1 -1
- package/src/clis/bilibili/favorite.ts +1 -1
- package/src/clis/bilibili/feed.ts +1 -1
- package/src/clis/bilibili/following.ts +1 -1
- package/src/clis/bilibili/history.ts +1 -1
- package/src/clis/bilibili/me.ts +1 -1
- package/src/clis/bilibili/ranking.ts +1 -1
- package/src/clis/bilibili/search.ts +3 -3
- package/src/clis/bilibili/subtitle.ts +1 -1
- package/src/clis/bilibili/user-videos.ts +1 -1
- package/src/{bilibili.ts → clis/bilibili/utils.ts} +1 -1
- package/src/clis/bloomberg/businessweek.ts +18 -0
- package/src/clis/bloomberg/economics.ts +18 -0
- package/src/clis/bloomberg/feeds.ts +16 -0
- package/src/clis/bloomberg/industries.ts +18 -0
- package/src/clis/bloomberg/main.ts +18 -0
- package/src/clis/bloomberg/markets.ts +18 -0
- package/src/clis/bloomberg/news.ts +136 -0
- package/src/clis/bloomberg/opinions.ts +18 -0
- package/src/clis/bloomberg/politics.ts +18 -0
- package/src/clis/bloomberg/tech.ts +18 -0
- package/src/clis/bloomberg/utils.test.ts +135 -0
- package/src/clis/bloomberg/utils.ts +429 -0
- package/src/clis/boss/batchgreet.ts +167 -0
- package/src/clis/boss/chatlist.ts +2 -2
- package/src/clis/boss/detail.ts +2 -2
- package/src/clis/boss/exchange.ts +126 -0
- package/src/clis/boss/greet.ts +198 -0
- package/src/clis/boss/invite.ts +177 -0
- package/src/clis/boss/joblist.ts +63 -0
- package/src/clis/boss/mark.ts +155 -0
- package/src/clis/boss/recommend.ts +94 -0
- package/src/clis/boss/search.ts +1 -1
- package/src/clis/boss/send.ts +1 -1
- package/src/clis/boss/stats.ts +130 -0
- package/src/clis/chaoxing/assignments.ts +1 -1
- package/src/clis/chaoxing/exams.ts +1 -1
- package/src/{chaoxing.test.ts → clis/chaoxing/utils.test.ts} +1 -1
- package/src/{chaoxing.ts → clis/chaoxing/utils.ts} +1 -3
- package/src/clis/chatgpt/README.zh-CN.md +3 -3
- package/src/clis/chatgpt/read.ts +1 -1
- package/src/clis/chatwise/export.ts +1 -1
- package/src/clis/chatwise/model.ts +2 -2
- package/src/clis/chatwise/screenshot.ts +1 -1
- package/src/clis/codex/export.ts +1 -1
- package/src/clis/codex/model.ts +2 -2
- package/src/clis/codex/screenshot.ts +1 -1
- package/src/clis/coupang/add-to-cart.ts +3 -4
- package/src/clis/coupang/search.ts +2 -4
- package/src/{coupang.test.ts → clis/coupang/utils.test.ts} +1 -1
- package/src/clis/ctrip/search.ts +1 -1
- package/src/clis/cursor/export.ts +1 -1
- package/src/clis/cursor/model.ts +2 -2
- package/src/clis/cursor/screenshot.ts +1 -1
- package/src/clis/jike/comment.ts +2 -3
- package/src/clis/jike/create.ts +1 -2
- package/src/clis/jike/feed.ts +0 -1
- package/src/clis/jike/like.ts +1 -2
- package/src/clis/jike/notifications.ts +0 -1
- package/src/clis/jike/post.yaml +1 -0
- package/src/clis/jike/repost.ts +1 -2
- package/src/clis/jike/search.ts +2 -3
- package/src/clis/jike/topic.yaml +1 -0
- package/src/clis/jike/user.yaml +1 -0
- package/src/clis/jimeng/history.yaml +0 -1
- package/src/clis/linkedin/search.ts +7 -7
- package/src/clis/linux-do/category.yaml +1 -0
- package/src/clis/linux-do/search.yaml +4 -3
- package/src/clis/linux-do/topic.yaml +1 -0
- package/src/clis/notion/export.ts +1 -1
- package/src/clis/reddit/comment.ts +3 -4
- package/src/clis/reddit/read.ts +4 -5
- package/src/clis/reddit/save.ts +2 -3
- package/src/clis/reddit/saved.ts +0 -1
- package/src/clis/reddit/search.yaml +1 -0
- package/src/clis/reddit/subscribe.ts +0 -1
- package/src/clis/reddit/upvote.ts +2 -3
- package/src/clis/reddit/upvoted.ts +0 -1
- package/src/clis/reddit/user-comments.yaml +1 -0
- package/src/clis/reddit/user-posts.yaml +1 -0
- package/src/clis/reddit/user.yaml +1 -0
- package/src/clis/reuters/search.ts +1 -1
- package/src/clis/sinafinance/news.ts +76 -0
- package/src/clis/smzdm/search.ts +2 -3
- package/src/clis/stackoverflow/search.yaml +1 -0
- package/src/clis/steam/top-sellers.yaml +29 -0
- package/src/clis/twitter/accept.ts +2 -2
- package/src/clis/twitter/article.ts +2 -2
- package/src/clis/twitter/block.ts +92 -0
- package/src/clis/twitter/delete.ts +1 -1
- package/src/clis/twitter/hide-reply.ts +70 -0
- package/src/clis/twitter/like.ts +1 -1
- package/src/clis/twitter/post.ts +1 -1
- package/src/clis/twitter/reply-dm.ts +1 -1
- package/src/clis/twitter/reply.ts +2 -2
- package/src/clis/twitter/search.ts +1 -1
- package/src/clis/twitter/thread.ts +2 -2
- package/src/clis/twitter/trending.ts +113 -0
- package/src/clis/twitter/unblock.ts +75 -0
- package/src/clis/v2ex/topic.yaml +1 -0
- package/src/clis/weibo/hot.ts +0 -1
- package/src/clis/weread/book.ts +1 -1
- package/src/clis/weread/highlights.ts +1 -1
- package/src/clis/weread/notes.ts +1 -1
- package/src/clis/weread/search.ts +1 -1
- package/src/clis/wikipedia/search.ts +32 -0
- package/src/clis/wikipedia/summary.ts +28 -0
- package/src/clis/wikipedia/utils.ts +20 -0
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +272 -0
- package/src/clis/xiaohongshu/creator-note-detail.ts +425 -73
- package/src/clis/xiaohongshu/creator-notes-summary.test.ts +54 -0
- package/src/clis/xiaohongshu/creator-notes-summary.ts +120 -0
- package/src/clis/xiaohongshu/creator-notes.test.ts +211 -0
- package/src/clis/xiaohongshu/creator-notes.ts +254 -75
- package/src/clis/xiaohongshu/creator-profile.ts +0 -1
- package/src/clis/xiaohongshu/creator-stats.ts +0 -1
- package/src/clis/xiaohongshu/download.ts +2 -3
- package/src/clis/xiaohongshu/feed.yaml +0 -1
- package/src/clis/xiaohongshu/notifications.yaml +0 -1
- package/src/clis/xiaohongshu/search.ts +2 -2
- package/src/clis/xiaohongshu/user.ts +1 -2
- package/src/clis/yahoo-finance/quote.ts +0 -1
- package/src/clis/youtube/search.ts +1 -1
- package/src/clis/youtube/transcript.ts +1 -1
- package/src/clis/youtube/video.ts +1 -1
- package/src/clis/zhihu/download.ts +1 -2
- package/src/clis/zhihu/question.ts +1 -1
- package/src/clis/zhihu/search.yaml +4 -3
- package/src/commanderAdapter.ts +113 -0
- package/src/daemon.ts +3 -3
- package/src/{engine.ts → discovery.ts} +1 -108
- package/src/download/index.ts +21 -54
- package/src/engine.test.ts +8 -7
- package/src/execution.ts +138 -0
- package/src/explore.ts +135 -109
- package/src/external-clis.yaml +48 -0
- package/src/external.ts +185 -0
- package/src/main.ts +1 -1
- package/src/pipeline/steps/browser.ts +7 -2
- package/src/registry.ts +5 -0
- package/src/runtime.ts +9 -0
- package/src/serialization.ts +79 -0
- package/src/types.ts +1 -1
- package/tests/e2e/browser-public.test.ts +25 -0
- package/tests/e2e/public-commands.test.ts +55 -1
- package/dist/clis/twitter/trending.yaml +0 -46
- package/src/clis/twitter/trending.yaml +0 -46
- /package/dist/{chaoxing.test.d.ts → clis/arxiv/paper.d.ts} +0 -0
- /package/dist/{coupang.test.d.ts → clis/arxiv/search.d.ts} +0 -0
- /package/dist/{bilibili.js → clis/bilibili/utils.js} +0 -0
- /package/dist/{coupang.d.ts → clis/coupang/utils.d.ts} +0 -0
- /package/dist/{coupang.js → clis/coupang/utils.js} +0 -0
- /package/src/{coupang.ts → clis/coupang/utils.ts} +0 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command execution: validates args, manages browser sessions, runs commands.
|
|
3
|
+
*
|
|
4
|
+
* This is the single entry point for executing any CLI command. It handles:
|
|
5
|
+
* 1. Argument validation and coercion
|
|
6
|
+
* 2. Browser session lifecycle (if needed)
|
|
7
|
+
* 3. Domain pre-navigation for cookie/header strategies
|
|
8
|
+
* 4. Timeout enforcement
|
|
9
|
+
* 5. Lazy-loading of TS modules from manifest
|
|
10
|
+
*/
|
|
11
|
+
import { Strategy, getRegistry, fullName } from './registry.js';
|
|
12
|
+
import { executePipeline } from './pipeline.js';
|
|
13
|
+
import { AdapterLoadError } from './errors.js';
|
|
14
|
+
import { shouldUseBrowserSession } from './capabilityRouting.js';
|
|
15
|
+
import { getBrowserFactory, browserSession, runWithTimeout, DEFAULT_BROWSER_COMMAND_TIMEOUT } from './runtime.js';
|
|
16
|
+
/** Set of TS module paths that have been loaded */
|
|
17
|
+
const _loadedModules = new Set();
|
|
18
|
+
/**
|
|
19
|
+
* Validates and coerces arguments based on the command's Arg definitions.
|
|
20
|
+
*/
|
|
21
|
+
export function coerceAndValidateArgs(cmdArgs, kwargs) {
|
|
22
|
+
const result = { ...kwargs };
|
|
23
|
+
for (const argDef of cmdArgs) {
|
|
24
|
+
const val = result[argDef.name];
|
|
25
|
+
// 1. Check required
|
|
26
|
+
if (argDef.required && (val === undefined || val === null || val === '')) {
|
|
27
|
+
throw new Error(`Argument "${argDef.name}" is required.\n${argDef.help ? `Hint: ${argDef.help}` : ''}`);
|
|
28
|
+
}
|
|
29
|
+
if (val !== undefined && val !== null) {
|
|
30
|
+
// 2. Type coercion
|
|
31
|
+
if (argDef.type === 'int' || argDef.type === 'number') {
|
|
32
|
+
const num = Number(val);
|
|
33
|
+
if (Number.isNaN(num)) {
|
|
34
|
+
throw new Error(`Argument "${argDef.name}" must be a valid number. Received: "${val}"`);
|
|
35
|
+
}
|
|
36
|
+
result[argDef.name] = num;
|
|
37
|
+
}
|
|
38
|
+
else if (argDef.type === 'boolean' || argDef.type === 'bool') {
|
|
39
|
+
if (typeof val === 'string') {
|
|
40
|
+
const lower = val.toLowerCase();
|
|
41
|
+
if (lower === 'true' || lower === '1')
|
|
42
|
+
result[argDef.name] = true;
|
|
43
|
+
else if (lower === 'false' || lower === '0')
|
|
44
|
+
result[argDef.name] = false;
|
|
45
|
+
else
|
|
46
|
+
throw new Error(`Argument "${argDef.name}" must be a boolean (true/false). Received: "${val}"`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
result[argDef.name] = Boolean(val);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// 3. Choices validation
|
|
53
|
+
const coercedVal = result[argDef.name];
|
|
54
|
+
if (argDef.choices && argDef.choices.length > 0) {
|
|
55
|
+
if (!argDef.choices.map(String).includes(String(coercedVal))) {
|
|
56
|
+
throw new Error(`Argument "${argDef.name}" must be one of: ${argDef.choices.join(', ')}. Received: "${coercedVal}"`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (argDef.default !== undefined) {
|
|
61
|
+
result[argDef.name] = argDef.default;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Run a command's func or pipeline against a page.
|
|
68
|
+
*/
|
|
69
|
+
async function runCommand(cmd, page, kwargs, debug) {
|
|
70
|
+
// Lazy-load TS module on first execution (manifest fast-path)
|
|
71
|
+
const internal = cmd;
|
|
72
|
+
if (internal._lazy && internal._modulePath) {
|
|
73
|
+
const modulePath = internal._modulePath;
|
|
74
|
+
if (!_loadedModules.has(modulePath)) {
|
|
75
|
+
try {
|
|
76
|
+
await import(`file://${modulePath}`);
|
|
77
|
+
_loadedModules.add(modulePath);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
throw new AdapterLoadError(`Failed to load adapter module ${modulePath}: ${err.message}`, 'Check that the adapter file exists and has no syntax errors.');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// After loading, the module's cli() call will have updated the registry.
|
|
84
|
+
const updated = getRegistry().get(fullName(cmd));
|
|
85
|
+
if (updated?.func)
|
|
86
|
+
return updated.func(page, kwargs, debug);
|
|
87
|
+
if (updated?.pipeline)
|
|
88
|
+
return executePipeline(page, updated.pipeline, { args: kwargs, debug });
|
|
89
|
+
}
|
|
90
|
+
if (cmd.func)
|
|
91
|
+
return cmd.func(page, kwargs, debug);
|
|
92
|
+
if (cmd.pipeline)
|
|
93
|
+
return executePipeline(page, cmd.pipeline, { args: kwargs, debug });
|
|
94
|
+
throw new Error(`Command ${fullName(cmd)} has no func or pipeline`);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Execute a CLI command. Automatically manages browser sessions when needed.
|
|
98
|
+
*
|
|
99
|
+
* This is the unified entry point — callers don't need to care about
|
|
100
|
+
* whether the command requires a browser or not.
|
|
101
|
+
*/
|
|
102
|
+
export async function executeCommand(cmd, rawKwargs, debug = false) {
|
|
103
|
+
let kwargs;
|
|
104
|
+
try {
|
|
105
|
+
kwargs = coerceAndValidateArgs(cmd.args, rawKwargs);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
throw new Error(`[Argument Validation Error]\n${err.message}`);
|
|
109
|
+
}
|
|
110
|
+
if (shouldUseBrowserSession(cmd)) {
|
|
111
|
+
const BrowserFactory = getBrowserFactory();
|
|
112
|
+
return browserSession(BrowserFactory, async (page) => {
|
|
113
|
+
// Cookie/header strategies require same-origin context for credentialed fetch.
|
|
114
|
+
if ((cmd.strategy === Strategy.COOKIE || cmd.strategy === Strategy.HEADER) && cmd.domain) {
|
|
115
|
+
try {
|
|
116
|
+
await page.goto(`https://${cmd.domain}`);
|
|
117
|
+
await page.wait(2);
|
|
118
|
+
}
|
|
119
|
+
catch { }
|
|
120
|
+
}
|
|
121
|
+
return runWithTimeout(runCommand(cmd, page, kwargs, debug), {
|
|
122
|
+
timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT,
|
|
123
|
+
label: fullName(cmd),
|
|
124
|
+
});
|
|
125
|
+
}, { workspace: `site:${cmd.site}` });
|
|
126
|
+
}
|
|
127
|
+
// Non-browser commands run directly
|
|
128
|
+
return runCommand(cmd, null, kwargs, debug);
|
|
129
|
+
}
|
package/dist/explore.js
CHANGED
|
@@ -210,6 +210,120 @@ const FRAMEWORK_DETECT_JS = detectFramework.toString();
|
|
|
210
210
|
const STORE_DISCOVER_JS = discoverStores.toString();
|
|
211
211
|
// ── Auto-Interaction (Fuzzing) ─────────────────────────────────────────────
|
|
212
212
|
const INTERACT_FUZZ_JS = interactFuzz.toString();
|
|
213
|
+
// ── Analysis helpers (extracted from exploreUrl) ───────────────────────────
|
|
214
|
+
/** Filter, deduplicate, and score network endpoints. */
|
|
215
|
+
function analyzeEndpoints(networkEntries) {
|
|
216
|
+
const seen = new Map();
|
|
217
|
+
for (const entry of networkEntries) {
|
|
218
|
+
if (!entry.url)
|
|
219
|
+
continue;
|
|
220
|
+
const ct = entry.contentType.toLowerCase();
|
|
221
|
+
if (ct.includes('image/') || ct.includes('font/') || ct.includes('css') || ct.includes('javascript') || ct.includes('wasm'))
|
|
222
|
+
continue;
|
|
223
|
+
if (entry.status && entry.status >= 400)
|
|
224
|
+
continue;
|
|
225
|
+
const pattern = urlToPattern(entry.url);
|
|
226
|
+
const key = `${entry.method}:${pattern}`;
|
|
227
|
+
if (seen.has(key))
|
|
228
|
+
continue;
|
|
229
|
+
const qp = [];
|
|
230
|
+
try {
|
|
231
|
+
new URL(entry.url).searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k))
|
|
232
|
+
qp.push(k); });
|
|
233
|
+
}
|
|
234
|
+
catch { }
|
|
235
|
+
const ep = {
|
|
236
|
+
pattern, method: entry.method, url: entry.url, status: entry.status, contentType: ct,
|
|
237
|
+
queryParams: qp, hasSearchParam: qp.some(p => SEARCH_PARAMS.has(p)),
|
|
238
|
+
hasPaginationParam: qp.some(p => PAGINATION_PARAMS.has(p)),
|
|
239
|
+
hasLimitParam: qp.some(p => LIMIT_PARAMS.has(p)),
|
|
240
|
+
authIndicators: detectAuthIndicators(entry.requestHeaders),
|
|
241
|
+
responseAnalysis: entry.responseBody ? analyzeResponseBody(entry.responseBody) : null,
|
|
242
|
+
score: 0,
|
|
243
|
+
};
|
|
244
|
+
ep.score = scoreEndpoint(ep);
|
|
245
|
+
seen.set(key, ep);
|
|
246
|
+
}
|
|
247
|
+
const analyzed = [...seen.values()].filter(ep => ep.score >= 5).sort((a, b) => b.score - a.score);
|
|
248
|
+
return { analyzed, totalCount: seen.size };
|
|
249
|
+
}
|
|
250
|
+
/** Infer CLI capabilities from analyzed endpoints. */
|
|
251
|
+
function inferCapabilitiesFromEndpoints(endpoints, stores, opts) {
|
|
252
|
+
const capabilities = [];
|
|
253
|
+
const usedNames = new Set();
|
|
254
|
+
for (const ep of endpoints.slice(0, 8)) {
|
|
255
|
+
let capName = inferCapabilityName(ep.url, opts.goal);
|
|
256
|
+
if (usedNames.has(capName)) {
|
|
257
|
+
const suffix = ep.pattern.split('/').filter(s => s && !s.startsWith('{') && !s.includes('.')).pop();
|
|
258
|
+
capName = suffix ? `${capName}_${suffix}` : `${capName}_${usedNames.size}`;
|
|
259
|
+
}
|
|
260
|
+
usedNames.add(capName);
|
|
261
|
+
const cols = [];
|
|
262
|
+
if (ep.responseAnalysis) {
|
|
263
|
+
for (const role of ['title', 'url', 'author', 'score', 'time']) {
|
|
264
|
+
if (ep.responseAnalysis.detectedFields[role])
|
|
265
|
+
cols.push(role);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const args = [];
|
|
269
|
+
if (ep.hasSearchParam)
|
|
270
|
+
args.push({ name: 'keyword', type: 'str', required: true });
|
|
271
|
+
args.push({ name: 'limit', type: 'int', required: false, default: 20 });
|
|
272
|
+
if (ep.hasPaginationParam)
|
|
273
|
+
args.push({ name: 'page', type: 'int', required: false, default: 1 });
|
|
274
|
+
const epStrategy = inferStrategy(ep.authIndicators);
|
|
275
|
+
let storeHint;
|
|
276
|
+
if ((epStrategy === 'intercept' || ep.authIndicators.includes('signature')) && stores.length > 0) {
|
|
277
|
+
for (const s of stores) {
|
|
278
|
+
const matchingAction = s.actions.find(a => capName.split('_').some(part => a.toLowerCase().includes(part)) ||
|
|
279
|
+
a.toLowerCase().includes('fetch') || a.toLowerCase().includes('get'));
|
|
280
|
+
if (matchingAction) {
|
|
281
|
+
storeHint = { store: s.id, action: matchingAction };
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
capabilities.push({
|
|
287
|
+
name: capName, description: `${opts.site ?? detectSiteName(opts.url)} ${capName}`,
|
|
288
|
+
strategy: storeHint ? 'store-action' : epStrategy,
|
|
289
|
+
confidence: Math.min(ep.score / 20, 1.0), endpoint: ep.pattern,
|
|
290
|
+
itemPath: ep.responseAnalysis?.itemPath ?? null,
|
|
291
|
+
recommendedColumns: cols.length ? cols : ['title', 'url'],
|
|
292
|
+
recommendedArgs: args,
|
|
293
|
+
...(storeHint ? { storeHint } : {}),
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
const allAuth = new Set(endpoints.flatMap(ep => ep.authIndicators));
|
|
297
|
+
const topStrategy = allAuth.has('signature') ? 'intercept'
|
|
298
|
+
: allAuth.has('bearer') || allAuth.has('csrf') ? 'header'
|
|
299
|
+
: allAuth.size === 0 ? 'public' : 'cookie';
|
|
300
|
+
return { capabilities, topStrategy, authIndicators: [...allAuth] };
|
|
301
|
+
}
|
|
302
|
+
/** Write explore artifacts (manifest, endpoints, capabilities, auth, stores) to disk. */
|
|
303
|
+
async function writeExploreArtifacts(targetDir, result, analyzedEndpoints, stores) {
|
|
304
|
+
await fs.promises.mkdir(targetDir, { recursive: true });
|
|
305
|
+
const tasks = [
|
|
306
|
+
fs.promises.writeFile(path.join(targetDir, 'manifest.json'), JSON.stringify({
|
|
307
|
+
site: result.site, target_url: result.target_url, final_url: result.final_url, title: result.title,
|
|
308
|
+
framework: result.framework, stores: stores.map(s => ({ type: s.type, id: s.id, actions: s.actions })),
|
|
309
|
+
top_strategy: result.top_strategy, explored_at: new Date().toISOString(),
|
|
310
|
+
}, null, 2)),
|
|
311
|
+
fs.promises.writeFile(path.join(targetDir, 'endpoints.json'), JSON.stringify(analyzedEndpoints.map(ep => ({
|
|
312
|
+
pattern: ep.pattern, method: ep.method, url: ep.url, status: ep.status,
|
|
313
|
+
contentType: ep.contentType, score: ep.score, queryParams: ep.queryParams,
|
|
314
|
+
itemPath: ep.responseAnalysis?.itemPath ?? null, itemCount: ep.responseAnalysis?.itemCount ?? 0,
|
|
315
|
+
detectedFields: ep.responseAnalysis?.detectedFields ?? {}, authIndicators: ep.authIndicators,
|
|
316
|
+
})), null, 2)),
|
|
317
|
+
fs.promises.writeFile(path.join(targetDir, 'capabilities.json'), JSON.stringify(result.capabilities, null, 2)),
|
|
318
|
+
fs.promises.writeFile(path.join(targetDir, 'auth.json'), JSON.stringify({
|
|
319
|
+
top_strategy: result.top_strategy, indicators: result.auth_indicators, framework: result.framework,
|
|
320
|
+
}, null, 2)),
|
|
321
|
+
];
|
|
322
|
+
if (stores.length > 0) {
|
|
323
|
+
tasks.push(fs.promises.writeFile(path.join(targetDir, 'stores.json'), JSON.stringify(stores, null, 2)));
|
|
324
|
+
}
|
|
325
|
+
await Promise.all(tasks);
|
|
326
|
+
}
|
|
213
327
|
// ── Main explore function ──────────────────────────────────────────────────
|
|
214
328
|
export async function exploreUrl(url, opts) {
|
|
215
329
|
const waitSeconds = opts.waitSeconds ?? 3.0;
|
|
@@ -301,120 +415,20 @@ export async function exploreUrl(url, opts) {
|
|
|
301
415
|
}
|
|
302
416
|
catch { }
|
|
303
417
|
}
|
|
304
|
-
// Step 7: Analyze endpoints
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
continue;
|
|
309
|
-
const ct = entry.contentType.toLowerCase();
|
|
310
|
-
if (ct.includes('image/') || ct.includes('font/') || ct.includes('css') || ct.includes('javascript') || ct.includes('wasm'))
|
|
311
|
-
continue;
|
|
312
|
-
if (entry.status && entry.status >= 400)
|
|
313
|
-
continue;
|
|
314
|
-
const pattern = urlToPattern(entry.url);
|
|
315
|
-
const key = `${entry.method}:${pattern}`;
|
|
316
|
-
if (seen.has(key))
|
|
317
|
-
continue;
|
|
318
|
-
const qp = [];
|
|
319
|
-
try {
|
|
320
|
-
new URL(entry.url).searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k))
|
|
321
|
-
qp.push(k); });
|
|
322
|
-
}
|
|
323
|
-
catch { }
|
|
324
|
-
const ep = {
|
|
325
|
-
pattern, method: entry.method, url: entry.url, status: entry.status, contentType: ct,
|
|
326
|
-
queryParams: qp, hasSearchParam: qp.some(p => SEARCH_PARAMS.has(p)),
|
|
327
|
-
hasPaginationParam: qp.some(p => PAGINATION_PARAMS.has(p)),
|
|
328
|
-
hasLimitParam: qp.some(p => LIMIT_PARAMS.has(p)),
|
|
329
|
-
authIndicators: detectAuthIndicators(entry.requestHeaders),
|
|
330
|
-
responseAnalysis: entry.responseBody ? analyzeResponseBody(entry.responseBody) : null,
|
|
331
|
-
score: 0,
|
|
332
|
-
};
|
|
333
|
-
ep.score = scoreEndpoint(ep);
|
|
334
|
-
seen.set(key, ep);
|
|
335
|
-
}
|
|
336
|
-
const analyzedEndpoints = [...seen.values()].filter(ep => ep.score >= 5).sort((a, b) => b.score - a.score);
|
|
337
|
-
// Step 8: Infer capabilities
|
|
338
|
-
const capabilities = [];
|
|
339
|
-
const usedNames = new Set();
|
|
340
|
-
for (const ep of analyzedEndpoints.slice(0, 8)) {
|
|
341
|
-
let capName = inferCapabilityName(ep.url, opts.goal);
|
|
342
|
-
if (usedNames.has(capName)) {
|
|
343
|
-
const suffix = ep.pattern.split('/').filter(s => s && !s.startsWith('{') && !s.includes('.')).pop();
|
|
344
|
-
capName = suffix ? `${capName}_${suffix}` : `${capName}_${usedNames.size}`;
|
|
345
|
-
}
|
|
346
|
-
usedNames.add(capName);
|
|
347
|
-
const cols = [];
|
|
348
|
-
if (ep.responseAnalysis) {
|
|
349
|
-
for (const role of ['title', 'url', 'author', 'score', 'time']) {
|
|
350
|
-
if (ep.responseAnalysis.detectedFields[role])
|
|
351
|
-
cols.push(role);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
const args = [];
|
|
355
|
-
if (ep.hasSearchParam)
|
|
356
|
-
args.push({ name: 'keyword', type: 'str', required: true });
|
|
357
|
-
args.push({ name: 'limit', type: 'int', required: false, default: 20 });
|
|
358
|
-
if (ep.hasPaginationParam)
|
|
359
|
-
args.push({ name: 'page', type: 'int', required: false, default: 1 });
|
|
360
|
-
// Link store actions to capabilities when store-action strategy is recommended
|
|
361
|
-
const epStrategy = inferStrategy(ep.authIndicators);
|
|
362
|
-
let storeHint;
|
|
363
|
-
if ((epStrategy === 'intercept' || ep.authIndicators.includes('signature')) && stores.length > 0) {
|
|
364
|
-
// Try to find a store/action that matches this endpoint's purpose
|
|
365
|
-
for (const s of stores) {
|
|
366
|
-
const matchingAction = s.actions.find(a => capName.split('_').some(part => a.toLowerCase().includes(part)) ||
|
|
367
|
-
a.toLowerCase().includes('fetch') || a.toLowerCase().includes('get'));
|
|
368
|
-
if (matchingAction) {
|
|
369
|
-
storeHint = { store: s.id, action: matchingAction };
|
|
370
|
-
break;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
capabilities.push({
|
|
375
|
-
name: capName, description: `${opts.site ?? detectSiteName(url)} ${capName}`,
|
|
376
|
-
strategy: storeHint ? 'store-action' : epStrategy,
|
|
377
|
-
confidence: Math.min(ep.score / 20, 1.0), endpoint: ep.pattern,
|
|
378
|
-
itemPath: ep.responseAnalysis?.itemPath ?? null,
|
|
379
|
-
recommendedColumns: cols.length ? cols : ['title', 'url'],
|
|
380
|
-
recommendedArgs: args,
|
|
381
|
-
...(storeHint ? { storeHint } : {}),
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
// Step 9: Determine overall auth strategy
|
|
385
|
-
const allAuth = new Set(analyzedEndpoints.flatMap(ep => ep.authIndicators));
|
|
386
|
-
const topStrategy = allAuth.has('signature') ? 'intercept' : allAuth.has('bearer') || allAuth.has('csrf') ? 'header' : allAuth.size === 0 ? 'public' : 'cookie';
|
|
418
|
+
// Step 7+8: Analyze endpoints and infer capabilities
|
|
419
|
+
const { analyzed: analyzedEndpoints, totalCount } = analyzeEndpoints(networkEntries);
|
|
420
|
+
const { capabilities, topStrategy, authIndicators } = inferCapabilitiesFromEndpoints(analyzedEndpoints, stores, { site: opts.site, goal: opts.goal, url });
|
|
421
|
+
// Step 9: Assemble result and write artifacts
|
|
387
422
|
const siteName = opts.site ?? detectSiteName(metadata.url || url);
|
|
388
423
|
const targetDir = opts.outDir ?? path.join('.opencli', 'explore', siteName);
|
|
389
|
-
await fs.promises.mkdir(targetDir, { recursive: true });
|
|
390
424
|
const result = {
|
|
391
425
|
site: siteName, target_url: url, final_url: metadata.url, title: metadata.title,
|
|
392
426
|
framework, stores, top_strategy: topStrategy,
|
|
393
|
-
endpoint_count:
|
|
427
|
+
endpoint_count: totalCount,
|
|
394
428
|
api_endpoint_count: analyzedEndpoints.length,
|
|
395
|
-
capabilities, auth_indicators:
|
|
429
|
+
capabilities, auth_indicators: authIndicators,
|
|
396
430
|
};
|
|
397
|
-
|
|
398
|
-
const writeTasks = [];
|
|
399
|
-
writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'manifest.json'), JSON.stringify({
|
|
400
|
-
site: siteName, target_url: url, final_url: metadata.url, title: metadata.title,
|
|
401
|
-
framework, stores: stores.map(s => ({ type: s.type, id: s.id, actions: s.actions })),
|
|
402
|
-
top_strategy: topStrategy, explored_at: new Date().toISOString(),
|
|
403
|
-
}, null, 2)));
|
|
404
|
-
writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'endpoints.json'), JSON.stringify(analyzedEndpoints.map(ep => ({
|
|
405
|
-
pattern: ep.pattern, method: ep.method, url: ep.url, status: ep.status,
|
|
406
|
-
contentType: ep.contentType, score: ep.score, queryParams: ep.queryParams,
|
|
407
|
-
itemPath: ep.responseAnalysis?.itemPath ?? null, itemCount: ep.responseAnalysis?.itemCount ?? 0,
|
|
408
|
-
detectedFields: ep.responseAnalysis?.detectedFields ?? {}, authIndicators: ep.authIndicators,
|
|
409
|
-
})), null, 2)));
|
|
410
|
-
writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'capabilities.json'), JSON.stringify(capabilities, null, 2)));
|
|
411
|
-
writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'auth.json'), JSON.stringify({
|
|
412
|
-
top_strategy: topStrategy, indicators: [...allAuth], framework,
|
|
413
|
-
}, null, 2)));
|
|
414
|
-
if (stores.length > 0) {
|
|
415
|
-
writeTasks.push(fs.promises.writeFile(path.join(targetDir, 'stores.json'), JSON.stringify(stores, null, 2)));
|
|
416
|
-
}
|
|
417
|
-
await Promise.all(writeTasks);
|
|
431
|
+
await writeExploreArtifacts(targetDir, result, analyzedEndpoints, stores);
|
|
418
432
|
return { ...result, out_dir: targetDir };
|
|
419
433
|
})(), { timeout: exploreTimeout, label: `Explore ${url}` });
|
|
420
434
|
}, { workspace: opts.workspace });
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
- name: gh
|
|
2
|
+
binary: gh
|
|
3
|
+
description: "GitHub CLI — repos, PRs, issues, releases, gists"
|
|
4
|
+
homepage: "https://cli.github.com"
|
|
5
|
+
tags: [github, git, dev]
|
|
6
|
+
install:
|
|
7
|
+
mac: "brew install gh"
|
|
8
|
+
|
|
9
|
+
- name: obsidian
|
|
10
|
+
binary: obsidian
|
|
11
|
+
description: "Obsidian vault management — notes, search, tags, tasks, sync"
|
|
12
|
+
homepage: "https://obsidian.md/help/cli"
|
|
13
|
+
tags: [notes, knowledge, markdown]
|
|
14
|
+
install:
|
|
15
|
+
mac: "brew install --cask obsidian"
|
|
16
|
+
|
|
17
|
+
- name: readwise
|
|
18
|
+
binary: readwise
|
|
19
|
+
description: "Readwise & Reader CLI — highlights, annotations, reading list"
|
|
20
|
+
homepage: "https://github.com/readwiseio/readwise-cli"
|
|
21
|
+
tags: [reading, highlights]
|
|
22
|
+
install:
|
|
23
|
+
default: "npm install -g @readwiseio/readwise-cli"
|
|
24
|
+
|
|
25
|
+
- name: kubectl
|
|
26
|
+
binary: kubectl
|
|
27
|
+
description: "Kubernetes command-line tool"
|
|
28
|
+
homepage: "https://kubernetes.io/docs/reference/kubectl/"
|
|
29
|
+
tags: [kubernetes, k8s, devops]
|
|
30
|
+
install:
|
|
31
|
+
mac: "brew install kubectl"
|
|
32
|
+
|
|
33
|
+
- name: docker
|
|
34
|
+
binary: docker
|
|
35
|
+
description: "Docker command-line interface"
|
|
36
|
+
homepage: "https://docs.docker.com/engine/reference/commandline/cli/"
|
|
37
|
+
tags: [docker, containers, devops]
|
|
38
|
+
install:
|
|
39
|
+
mac: "brew install --cask docker"
|
|
40
|
+
|
|
41
|
+
- name: gws
|
|
42
|
+
binary: gws
|
|
43
|
+
description: "Google Workspace CLI — Docs, Sheets, Drive, Gmail, Calendar"
|
|
44
|
+
homepage: "https://github.com/nicholasgasior/gws"
|
|
45
|
+
tags: [google, docs, sheets, drive, workspace]
|
|
46
|
+
install:
|
|
47
|
+
mac: "brew install gws"
|
|
48
|
+
default: "npm install -g @nicholasgasior/gws"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface ExternalCliInstall {
|
|
2
|
+
mac?: string;
|
|
3
|
+
linux?: string;
|
|
4
|
+
windows?: string;
|
|
5
|
+
default?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ExternalCliConfig {
|
|
8
|
+
name: string;
|
|
9
|
+
binary: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
homepage?: string;
|
|
12
|
+
tags?: string[];
|
|
13
|
+
install?: ExternalCliInstall;
|
|
14
|
+
}
|
|
15
|
+
export declare function loadExternalClis(): ExternalCliConfig[];
|
|
16
|
+
export declare function isBinaryInstalled(binary: string): boolean;
|
|
17
|
+
export declare function getInstallCmd(installConfig?: ExternalCliInstall): string | null;
|
|
18
|
+
export declare function installExternalCli(cli: ExternalCliConfig): boolean;
|
|
19
|
+
export declare function executeExternalCli(name: string, args: string[], preloaded?: ExternalCliConfig[]): void;
|
|
20
|
+
export interface RegisterOptions {
|
|
21
|
+
binary?: string;
|
|
22
|
+
install?: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function registerExternalCli(name: string, opts?: RegisterOptions): void;
|
package/dist/external.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { spawnSync, execSync, execFileSync } from 'node:child_process';
|
|
6
|
+
import yaml from 'js-yaml';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { log } from './logger.js';
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
function getUserRegistryPath() {
|
|
11
|
+
const home = os.homedir();
|
|
12
|
+
return path.join(home, '.opencli', 'external-clis.yaml');
|
|
13
|
+
}
|
|
14
|
+
export function loadExternalClis() {
|
|
15
|
+
const configs = new Map();
|
|
16
|
+
// 1. Load built-in
|
|
17
|
+
const builtinPath = path.resolve(__dirname, 'external-clis.yaml');
|
|
18
|
+
try {
|
|
19
|
+
if (fs.existsSync(builtinPath)) {
|
|
20
|
+
const raw = fs.readFileSync(builtinPath, 'utf8');
|
|
21
|
+
const parsed = (yaml.load(raw) || []);
|
|
22
|
+
for (const item of parsed)
|
|
23
|
+
configs.set(item.name, item);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
log.warn(`Failed to parse built-in external-clis.yaml: ${err.message}`);
|
|
28
|
+
}
|
|
29
|
+
// 2. Load user custom
|
|
30
|
+
const userPath = getUserRegistryPath();
|
|
31
|
+
try {
|
|
32
|
+
if (fs.existsSync(userPath)) {
|
|
33
|
+
const raw = fs.readFileSync(userPath, 'utf8');
|
|
34
|
+
const parsed = (yaml.load(raw) || []);
|
|
35
|
+
for (const item of parsed) {
|
|
36
|
+
configs.set(item.name, item); // Overwrite built-in if duplicated
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
log.warn(`Failed to parse user external-clis.yaml: ${err.message}`);
|
|
42
|
+
}
|
|
43
|
+
return Array.from(configs.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
44
|
+
}
|
|
45
|
+
export function isBinaryInstalled(binary) {
|
|
46
|
+
try {
|
|
47
|
+
const isWindows = os.platform() === 'win32';
|
|
48
|
+
execFileSync(isWindows ? 'where' : 'which', [binary], { stdio: 'ignore' });
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function getInstallCmd(installConfig) {
|
|
56
|
+
if (!installConfig)
|
|
57
|
+
return null;
|
|
58
|
+
const platform = os.platform();
|
|
59
|
+
if (platform === 'darwin' && installConfig.mac)
|
|
60
|
+
return installConfig.mac;
|
|
61
|
+
if (platform === 'linux' && installConfig.linux)
|
|
62
|
+
return installConfig.linux;
|
|
63
|
+
if (platform === 'win32' && installConfig.windows)
|
|
64
|
+
return installConfig.windows;
|
|
65
|
+
if (installConfig.default)
|
|
66
|
+
return installConfig.default;
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
export function installExternalCli(cli) {
|
|
70
|
+
if (!cli.install) {
|
|
71
|
+
console.error(chalk.red(`No auto-install command configured for '${cli.name}'.`));
|
|
72
|
+
console.error(`Please install '${cli.binary}' manually.`);
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
const cmd = getInstallCmd(cli.install);
|
|
76
|
+
if (!cmd) {
|
|
77
|
+
console.error(chalk.red(`No install command for your platform (${os.platform()}) for '${cli.name}'.`));
|
|
78
|
+
if (cli.homepage)
|
|
79
|
+
console.error(`See: ${cli.homepage}`);
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
console.log(chalk.cyan(`🔹 '${cli.name}' is not installed. Auto-installing...`));
|
|
83
|
+
console.log(chalk.dim(`$ ${cmd}`));
|
|
84
|
+
try {
|
|
85
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
86
|
+
console.log(chalk.green(`✅ Installed '${cli.name}' successfully.\n`));
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
console.error(chalk.red(`❌ Failed to install '${cli.name}': ${err.message}`));
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function executeExternalCli(name, args, preloaded) {
|
|
95
|
+
const configs = preloaded ?? loadExternalClis();
|
|
96
|
+
const cli = configs.find((c) => c.name === name);
|
|
97
|
+
if (!cli) {
|
|
98
|
+
throw new Error(`External CLI '${name}' not found in registry.`);
|
|
99
|
+
}
|
|
100
|
+
// 1. Check if installed
|
|
101
|
+
if (!isBinaryInstalled(cli.binary)) {
|
|
102
|
+
// 2. Try to auto install
|
|
103
|
+
const success = installExternalCli(cli);
|
|
104
|
+
if (!success) {
|
|
105
|
+
process.exitCode = 1;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// 3. Passthrough execution with stdio inherited
|
|
110
|
+
const result = spawnSync(cli.binary, args, { stdio: 'inherit' });
|
|
111
|
+
if (result.error) {
|
|
112
|
+
console.error(chalk.red(`Failed to execute '${cli.binary}': ${result.error.message}`));
|
|
113
|
+
process.exitCode = 1;
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (result.status !== null) {
|
|
117
|
+
process.exitCode = result.status;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
export function registerExternalCli(name, opts) {
|
|
121
|
+
const userPath = getUserRegistryPath();
|
|
122
|
+
const configDir = path.dirname(userPath);
|
|
123
|
+
if (!fs.existsSync(configDir)) {
|
|
124
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
125
|
+
}
|
|
126
|
+
let items = [];
|
|
127
|
+
if (fs.existsSync(userPath)) {
|
|
128
|
+
try {
|
|
129
|
+
const raw = fs.readFileSync(userPath, 'utf8');
|
|
130
|
+
items = (yaml.load(raw) || []);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Ignore
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const existingIndex = items.findIndex((c) => c.name === name);
|
|
137
|
+
const newItem = {
|
|
138
|
+
name,
|
|
139
|
+
binary: opts?.binary || name,
|
|
140
|
+
};
|
|
141
|
+
if (opts?.description)
|
|
142
|
+
newItem.description = opts.description;
|
|
143
|
+
if (opts?.install)
|
|
144
|
+
newItem.install = { default: opts.install };
|
|
145
|
+
if (existingIndex >= 0) {
|
|
146
|
+
items[existingIndex] = { ...items[existingIndex], ...newItem };
|
|
147
|
+
console.log(chalk.green(`Updated '${name}' in user registry.`));
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
items.push(newItem);
|
|
151
|
+
console.log(chalk.green(`Registered '${name}' in user registry.`));
|
|
152
|
+
}
|
|
153
|
+
const dump = yaml.dump(items, { indent: 2, sortKeys: true });
|
|
154
|
+
fs.writeFileSync(userPath, dump, 'utf8');
|
|
155
|
+
console.log(chalk.dim(userPath));
|
|
156
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import * as os from 'node:os';
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
|
-
import { discoverClis } from './
|
|
8
|
+
import { discoverClis } from './discovery.js';
|
|
9
9
|
import { getCompletions } from './completion.js';
|
|
10
10
|
import { runCli } from './cli.js';
|
|
11
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { render } from '../template.js';
|
|
6
6
|
export async function stepNavigate(page, params, data, args) {
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
if (typeof params === 'object' && params && 'url' in params) {
|
|
8
|
+
const url = String(render(params.url, { args, data }));
|
|
9
|
+
await page.goto(url, { waitUntil: params.waitUntil, settleMs: params.settleMs });
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
const url = render(params, { args, data });
|
|
13
|
+
await page.goto(String(url));
|
|
14
|
+
}
|
|
9
15
|
return data;
|
|
10
16
|
}
|
|
11
17
|
export async function stepClick(page, params, data, args) {
|
package/dist/registry.d.ts
CHANGED
|
@@ -49,3 +49,5 @@ export declare function getRegistry(): Map<string, CliCommand>;
|
|
|
49
49
|
export declare function fullName(cmd: CliCommand): string;
|
|
50
50
|
export declare function strategyLabel(cmd: CliCommand): string;
|
|
51
51
|
export declare function registerCommand(cmd: CliCommand): void;
|
|
52
|
+
export { serializeArg, serializeCommand, formatArgSummary, formatRegistryHelpText } from './serialization.js';
|
|
53
|
+
export type { SerializedArg } from './serialization.js';
|
package/dist/registry.js
CHANGED
|
@@ -43,3 +43,5 @@ export function strategyLabel(cmd) {
|
|
|
43
43
|
export function registerCommand(cmd) {
|
|
44
44
|
_registry.set(fullName(cmd), cmd);
|
|
45
45
|
}
|
|
46
|
+
// Re-export serialization helpers from their dedicated module
|
|
47
|
+
export { serializeArg, serializeCommand, formatArgSummary, formatRegistryHelpText } from './serialization.js';
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { IPage } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Returns the appropriate browser factory based on environment config.
|
|
4
|
+
* Uses CDPBridge when OPENCLI_CDP_ENDPOINT is set, otherwise BrowserBridge.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getBrowserFactory(): new () => IBrowserFactory;
|
|
2
7
|
export declare const DEFAULT_BROWSER_CONNECT_TIMEOUT: number;
|
|
3
8
|
export declare const DEFAULT_BROWSER_COMMAND_TIMEOUT: number;
|
|
4
9
|
export declare const DEFAULT_BROWSER_EXPLORE_TIMEOUT: number;
|