@jackwener/opencli 1.3.3 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/actions/setup-chrome/action.yml +5 -4
- package/.github/pull_request_template.md +3 -1
- package/.github/workflows/build-extension.yml +7 -1
- package/.github/workflows/ci.yml +46 -6
- package/.github/workflows/docs.yml +1 -1
- package/.github/workflows/e2e-headed.yml +36 -3
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/security.yml +0 -3
- package/CHANGELOG.md +78 -0
- package/CONTRIBUTING.md +6 -3
- package/PRIVACY.md +57 -0
- package/README.md +31 -4
- package/README.zh-CN.md +31 -4
- package/SKILL.md +107 -2
- package/TESTING.md +1 -0
- package/chatwise-opencli.ps1 +82 -0
- package/dist/analysis.d.ts +38 -0
- package/dist/analysis.js +166 -0
- package/dist/browser/cdp.d.ts +0 -4
- package/dist/browser/cdp.js +53 -41
- package/dist/browser/cdp.test.d.ts +1 -0
- package/dist/browser/cdp.test.js +52 -0
- package/dist/browser/dom-snapshot.d.ts +2 -2
- package/dist/browser/dom-snapshot.js +54 -1
- package/dist/browser/dom-snapshot.test.js +36 -0
- package/dist/browser/index.d.ts +2 -2
- package/dist/browser/index.js +1 -1
- package/dist/browser/mcp.d.ts +0 -2
- package/dist/browser/mcp.js +2 -3
- package/dist/browser/page.d.ts +4 -3
- package/dist/browser/page.js +34 -37
- package/dist/browser/stealth.d.ts +0 -2
- package/dist/browser/stealth.js +24 -9
- package/dist/browser.test.js +2 -2
- package/dist/build-manifest.js +15 -9
- package/dist/build-manifest.test.js +12 -0
- package/dist/cascade.js +4 -2
- package/dist/cli-manifest.json +1325 -256
- package/dist/cli.js +57 -29
- package/dist/clis/_shared/desktop-commands.d.ts +22 -0
- package/dist/clis/_shared/desktop-commands.js +108 -0
- package/dist/clis/antigravity/serve.js +5 -2
- package/dist/clis/apple-podcasts/search.js +2 -1
- package/dist/clis/arxiv/search.js +3 -3
- package/dist/clis/bbc/news.js +0 -1
- package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
- package/dist/clis/bilibili/dynamic.test.js +68 -0
- package/dist/clis/bilibili/favorite.js +4 -2
- package/dist/clis/bilibili/following.js +3 -2
- package/dist/clis/bilibili/subtitle.js +8 -7
- package/dist/clis/bilibili/utils.js +2 -2
- package/dist/clis/boss/batchgreet.js +1 -1
- package/dist/clis/boss/chatlist.js +1 -1
- package/dist/clis/boss/chatmsg.js +1 -1
- package/dist/clis/boss/detail.js +1 -1
- package/dist/clis/boss/exchange.js +1 -1
- package/dist/clis/boss/greet.js +1 -1
- package/dist/clis/boss/invite.js +1 -1
- package/dist/clis/boss/joblist.js +1 -1
- package/dist/clis/boss/mark.js +4 -3
- package/dist/clis/boss/recommend.js +1 -1
- package/dist/clis/boss/resume.js +1 -1
- package/dist/clis/boss/search.js +1 -1
- package/dist/clis/boss/send.js +5 -4
- package/dist/clis/boss/stats.js +1 -1
- package/dist/clis/chatgpt/ask.js +4 -0
- package/dist/clis/chatgpt/new.js +5 -1
- package/dist/clis/chatgpt/read.js +5 -1
- package/dist/clis/chatgpt/send.js +2 -1
- package/dist/clis/chatgpt/status.js +5 -1
- package/dist/clis/chatwise/ask.js +8 -2
- package/dist/clis/chatwise/export.js +2 -0
- package/dist/clis/chatwise/history.js +2 -0
- package/dist/clis/chatwise/model.js +8 -3
- package/dist/clis/chatwise/new.js +3 -18
- package/dist/clis/chatwise/read.js +2 -0
- package/dist/clis/chatwise/screenshot.js +3 -27
- package/dist/clis/chatwise/send.js +8 -2
- package/dist/clis/chatwise/shared.d.ts +2 -0
- package/dist/clis/chatwise/shared.js +6 -0
- package/dist/clis/chatwise/status.js +3 -22
- package/dist/clis/codex/ask.js +6 -2
- package/dist/clis/codex/dump.js +2 -25
- package/dist/clis/codex/new.js +2 -25
- package/dist/clis/codex/screenshot.js +2 -27
- package/dist/clis/codex/send.js +6 -4
- package/dist/clis/codex/status.js +2 -22
- package/dist/clis/ctrip/search.js +0 -1
- package/dist/clis/cursor/ask.js +2 -1
- package/dist/clis/cursor/composer.js +2 -1
- package/dist/clis/cursor/dump.js +2 -25
- package/dist/clis/cursor/new.js +2 -18
- package/dist/clis/cursor/read.js +2 -1
- package/dist/clis/cursor/screenshot.js +1 -30
- package/dist/clis/cursor/send.js +2 -1
- package/dist/clis/cursor/status.js +2 -21
- package/dist/clis/dictionary/examples.yaml +25 -0
- package/dist/clis/dictionary/search.yaml +27 -0
- package/dist/clis/dictionary/synonyms.yaml +25 -0
- package/dist/clis/douban/book-hot.js +1 -1
- package/dist/clis/douban/movie-hot.js +1 -1
- package/dist/clis/douban/search.js +1 -1
- package/dist/clis/douban/utils.d.ts +4 -1
- package/dist/clis/douban/utils.js +156 -1
- package/dist/clis/doubao/ask.js +1 -1
- package/dist/clis/doubao/new.js +1 -1
- package/dist/clis/doubao/read.js +1 -1
- package/dist/clis/doubao/send.js +1 -1
- package/dist/clis/doubao/status.js +1 -1
- package/dist/clis/doubao-app/ask.js +1 -1
- package/dist/clis/doubao-app/new.js +1 -1
- package/dist/clis/doubao-app/read.js +1 -1
- package/dist/clis/doubao-app/send.js +1 -1
- 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/grok/ask.d.ts +4 -0
- package/dist/clis/grok/ask.js +28 -10
- package/dist/clis/grok/ask.test.js +18 -0
- package/dist/clis/hackernews/search.yaml +1 -1
- package/dist/clis/instagram/search.yaml +2 -1
- package/dist/clis/jd/item.d.ts +1 -0
- package/dist/clis/jd/item.js +96 -0
- package/dist/clis/jd/item.test.d.ts +1 -0
- package/dist/clis/jd/item.test.js +28 -0
- package/dist/clis/jike/feed.js +1 -1
- package/dist/clis/jike/search.js +1 -1
- package/dist/clis/linkedin/search.js +5 -4
- package/dist/clis/linkedin/timeline.d.ts +21 -0
- package/dist/clis/linkedin/timeline.js +503 -0
- package/dist/clis/linkedin/timeline.test.d.ts +1 -0
- package/dist/clis/linkedin/timeline.test.js +81 -0
- package/dist/clis/linux-do/search.yaml +3 -1
- package/dist/clis/medium/feed.js +1 -1
- package/dist/clis/medium/search.js +2 -2
- package/dist/clis/medium/user.js +1 -1
- package/dist/clis/medium/{shared.js → utils.js} +2 -1
- package/dist/clis/pixiv/detail.yaml +49 -0
- package/dist/clis/pixiv/download.d.ts +7 -0
- package/dist/clis/pixiv/download.js +78 -0
- package/dist/clis/pixiv/download.test.d.ts +1 -0
- package/dist/clis/pixiv/download.test.js +87 -0
- package/dist/clis/pixiv/illusts.d.ts +8 -0
- package/dist/clis/pixiv/illusts.js +65 -0
- package/dist/clis/pixiv/illusts.test.d.ts +1 -0
- package/dist/clis/pixiv/illusts.test.js +99 -0
- package/dist/clis/pixiv/ranking.yaml +53 -0
- package/dist/clis/pixiv/search.d.ts +6 -0
- package/dist/clis/pixiv/search.js +43 -0
- package/dist/clis/pixiv/search.test.d.ts +1 -0
- package/dist/clis/pixiv/search.test.js +83 -0
- package/dist/clis/pixiv/test-utils.d.ts +12 -0
- package/dist/clis/pixiv/test-utils.js +23 -0
- package/dist/clis/pixiv/user.yaml +46 -0
- package/dist/clis/pixiv/utils.d.ts +27 -0
- package/dist/clis/pixiv/utils.js +49 -0
- package/dist/clis/reddit/comment.js +2 -1
- package/dist/clis/reddit/read.js +4 -3
- package/dist/clis/reddit/read.test.d.ts +1 -0
- package/dist/clis/reddit/read.test.js +28 -0
- package/dist/clis/reddit/save.js +2 -1
- package/dist/clis/reddit/saved.js +7 -3
- package/dist/clis/reddit/subscribe.js +2 -1
- package/dist/clis/reddit/upvote.js +2 -1
- package/dist/clis/reddit/upvoted.js +7 -3
- package/dist/clis/reuters/search.js +0 -1
- package/dist/clis/sinablog/article.js +1 -1
- package/dist/clis/sinablog/hot.js +1 -1
- package/dist/clis/sinablog/user.js +1 -1
- package/dist/clis/substack/feed.js +1 -1
- package/dist/clis/substack/publication.js +1 -1
- package/dist/clis/substack/search.js +3 -2
- package/dist/clis/substack/{shared.js → utils.js} +3 -2
- package/dist/clis/tiktok/search.yaml +2 -1
- package/dist/clis/twitter/accept.js +2 -1
- package/dist/clis/twitter/article.js +4 -1
- package/dist/clis/twitter/block.js +2 -1
- package/dist/clis/twitter/bookmark.js +2 -1
- package/dist/clis/twitter/bookmarks.js +3 -2
- package/dist/clis/twitter/delete.js +2 -1
- package/dist/clis/twitter/follow.js +2 -1
- package/dist/clis/twitter/followers.js +3 -2
- package/dist/clis/twitter/following.js +3 -2
- package/dist/clis/twitter/hide-reply.js +2 -1
- package/dist/clis/twitter/like.js +2 -1
- package/dist/clis/twitter/notifications.js +2 -1
- package/dist/clis/twitter/post.js +2 -1
- package/dist/clis/twitter/profile.js +5 -2
- package/dist/clis/twitter/reply-dm.js +2 -1
- package/dist/clis/twitter/reply.js +2 -1
- package/dist/clis/twitter/search.js +32 -13
- package/dist/clis/twitter/search.test.d.ts +1 -0
- package/dist/clis/twitter/search.test.js +156 -0
- package/dist/clis/twitter/thread.js +2 -2
- package/dist/clis/twitter/timeline.js +3 -2
- package/dist/clis/twitter/trending.js +3 -2
- package/dist/clis/twitter/unblock.js +2 -1
- package/dist/clis/twitter/unbookmark.js +2 -1
- package/dist/clis/twitter/unfollow.js +2 -1
- package/dist/clis/v2ex/daily.js +3 -2
- package/dist/clis/v2ex/me.js +3 -2
- package/dist/clis/v2ex/notifications.js +4 -4
- package/dist/clis/web/read.d.ts +16 -0
- package/dist/clis/web/read.js +202 -0
- 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/weread/search.js +3 -2
- package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
- package/dist/clis/xueqiu/danjuan-utils.js +126 -0
- package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
- package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
- package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
- package/dist/clis/xueqiu/fund-holdings.js +28 -0
- package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
- package/dist/clis/xueqiu/fund-snapshot.js +25 -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/youtube/transcript.js +5 -4
- package/dist/clis/youtube/video.js +3 -2
- package/dist/clis/zhihu/search.yaml +2 -1
- package/dist/daemon.js +7 -3
- package/dist/discovery.js +11 -10
- package/dist/doctor.js +2 -1
- package/dist/download/index.d.ts +4 -12
- package/dist/download/index.js +33 -12
- package/dist/download/index.test.js +79 -2
- package/dist/download/media-download.js +4 -2
- package/dist/engine.test.js +76 -4
- package/dist/execution.d.ts +1 -9
- package/dist/execution.js +56 -46
- package/dist/explore.js +12 -111
- package/dist/external-clis.yaml +0 -25
- package/dist/external.js +7 -5
- package/dist/external.test.js +4 -0
- package/dist/generate.d.ts +0 -9
- package/dist/generate.js +4 -20
- package/dist/hooks.d.ts +46 -0
- package/dist/hooks.js +56 -0
- package/dist/hooks.test.d.ts +4 -0
- package/dist/hooks.test.js +92 -0
- package/dist/interceptor.js +70 -23
- package/dist/main.js +2 -0
- package/dist/output.js +12 -6
- package/dist/pipeline/executor.js +1 -1
- package/dist/pipeline/steps/browser.js +1 -3
- package/dist/pipeline/steps/download.js +42 -26
- package/dist/pipeline/steps/download.test.d.ts +1 -0
- package/dist/pipeline/steps/download.test.js +101 -0
- package/dist/pipeline/steps/fetch.js +40 -22
- package/dist/pipeline/steps/fetch.test.d.ts +1 -0
- package/dist/pipeline/steps/fetch.test.js +123 -0
- package/dist/pipeline/steps/transform.js +2 -6
- package/dist/pipeline/template.js +66 -52
- package/dist/pipeline/template.test.js +28 -0
- package/dist/pipeline/transform.test.js +18 -0
- package/dist/plugin.d.ts +40 -1
- package/dist/plugin.js +214 -17
- package/dist/plugin.test.d.ts +1 -1
- package/dist/plugin.test.js +219 -3
- package/dist/record.js +6 -98
- package/dist/registry-api.d.ts +2 -0
- package/dist/registry-api.js +1 -0
- package/dist/registry.d.ts +5 -2
- package/dist/registry.js +1 -2
- package/dist/runtime.d.ts +0 -1
- package/dist/runtime.js +14 -4
- package/dist/snapshotFormatter.d.ts +7 -14
- package/dist/snapshotFormatter.js +38 -78
- package/dist/utils.d.ts +9 -0
- package/dist/utils.js +29 -0
- package/dist/validate.js +3 -5
- 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 +26 -0
- package/dist/yaml-schema.js +5 -0
- package/docs/.vitepress/config.mts +16 -0
- package/docs/adapters/browser/dictionary.md +27 -0
- package/docs/adapters/browser/douyin.md +75 -0
- package/docs/adapters/browser/jd.md +27 -0
- package/docs/adapters/browser/linkedin.md +6 -0
- package/docs/adapters/browser/pixiv.md +92 -0
- package/docs/adapters/browser/twitter.md +6 -0
- package/docs/adapters/browser/web.md +30 -0
- package/docs/adapters/browser/xueqiu.md +27 -9
- package/docs/adapters/index.md +9 -2
- package/docs/comparison.md +125 -0
- package/docs/developer/contributing.md +21 -2
- package/docs/developer/testing.md +14 -8
- package/docs/developer/ts-adapter.md +18 -0
- package/docs/developer/yaml-adapter.md +16 -0
- package/docs/guide/plugins.md +10 -0
- package/docs/zh/guide/plugins.md +10 -0
- package/extension/dist/background.js +100 -35
- package/extension/manifest.json +6 -2
- package/extension/package.json +1 -1
- package/extension/popup.html +84 -0
- package/extension/popup.js +25 -0
- package/extension/src/background.test.ts +46 -1
- package/extension/src/background.ts +128 -34
- package/extension/src/cdp.ts +9 -9
- package/package.json +3 -2
- package/scripts/check-doc-coverage.sh +2 -0
- package/src/analysis.ts +170 -0
- package/src/browser/cdp.test.ts +66 -0
- package/src/browser/cdp.ts +59 -44
- package/src/browser/dom-snapshot.test.ts +42 -0
- package/src/browser/dom-snapshot.ts +56 -3
- package/src/browser/index.ts +2 -2
- package/src/browser/mcp.ts +2 -4
- package/src/browser/page.ts +34 -37
- package/src/browser/stealth.ts +24 -10
- package/src/browser.test.ts +2 -2
- package/src/build-manifest.test.ts +14 -0
- package/src/build-manifest.ts +13 -31
- package/src/cascade.ts +5 -3
- package/src/cli.ts +66 -34
- package/src/clis/_shared/desktop-commands.ts +121 -0
- package/src/clis/antigravity/serve.ts +6 -3
- package/src/clis/apple-podcasts/search.ts +2 -1
- package/src/clis/arxiv/search.ts +3 -3
- package/src/clis/bbc/news.ts +0 -1
- package/src/clis/bilibili/dynamic.test.ts +79 -0
- package/src/clis/bilibili/favorite.ts +5 -2
- package/src/clis/bilibili/following.ts +3 -2
- package/src/clis/bilibili/subtitle.ts +8 -7
- package/src/clis/bilibili/utils.ts +2 -2
- package/src/clis/boss/batchgreet.ts +1 -1
- package/src/clis/boss/chatlist.ts +1 -1
- package/src/clis/boss/chatmsg.ts +1 -1
- package/src/clis/boss/detail.ts +1 -1
- package/src/clis/boss/exchange.ts +1 -1
- package/src/clis/boss/greet.ts +1 -1
- package/src/clis/boss/invite.ts +1 -1
- package/src/clis/boss/joblist.ts +1 -1
- package/src/clis/boss/mark.ts +4 -3
- package/src/clis/boss/recommend.ts +1 -1
- package/src/clis/boss/resume.ts +1 -1
- package/src/clis/boss/search.ts +1 -1
- package/src/clis/boss/send.ts +5 -4
- package/src/clis/boss/stats.ts +1 -1
- package/src/clis/chatgpt/ask.ts +5 -0
- package/src/clis/chatgpt/new.ts +7 -2
- package/src/clis/chatgpt/read.ts +7 -2
- package/src/clis/chatgpt/send.ts +3 -2
- package/src/clis/chatgpt/status.ts +6 -1
- package/src/clis/chatwise/ask.ts +7 -2
- package/src/clis/chatwise/export.ts +2 -0
- package/src/clis/chatwise/history.ts +2 -0
- package/src/clis/chatwise/model.ts +7 -3
- package/src/clis/chatwise/new.ts +3 -20
- package/src/clis/chatwise/read.ts +2 -0
- package/src/clis/chatwise/screenshot.ts +3 -32
- package/src/clis/chatwise/send.ts +7 -2
- package/src/clis/chatwise/shared.ts +8 -0
- package/src/clis/chatwise/status.ts +3 -24
- package/src/clis/codex/ask.ts +5 -2
- package/src/clis/codex/dump.ts +2 -27
- package/src/clis/codex/new.ts +2 -28
- package/src/clis/codex/screenshot.ts +2 -32
- package/src/clis/codex/send.ts +5 -4
- package/src/clis/codex/status.ts +2 -24
- package/src/clis/ctrip/search.ts +0 -1
- package/src/clis/cursor/ask.ts +2 -1
- package/src/clis/cursor/composer.ts +2 -1
- package/src/clis/cursor/dump.ts +2 -27
- package/src/clis/cursor/new.ts +2 -20
- package/src/clis/cursor/read.ts +2 -1
- package/src/clis/cursor/screenshot.ts +1 -36
- package/src/clis/cursor/send.ts +2 -1
- package/src/clis/cursor/status.ts +2 -22
- package/src/clis/dictionary/examples.yaml +25 -0
- package/src/clis/dictionary/search.yaml +27 -0
- package/src/clis/dictionary/synonyms.yaml +25 -0
- package/src/clis/douban/book-hot.ts +1 -1
- package/src/clis/douban/movie-hot.ts +1 -1
- package/src/clis/douban/search.ts +1 -1
- package/src/clis/douban/utils.ts +165 -1
- package/src/clis/doubao/ask.ts +1 -1
- package/src/clis/doubao/new.ts +1 -1
- package/src/clis/doubao/read.ts +1 -1
- package/src/clis/doubao/send.ts +1 -1
- package/src/clis/doubao/status.ts +1 -1
- package/src/clis/doubao-app/ask.ts +1 -1
- package/src/clis/doubao-app/new.ts +1 -1
- package/src/clis/doubao-app/read.ts +1 -1
- package/src/clis/doubao-app/send.ts +1 -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/grok/ask.test.ts +25 -0
- package/src/clis/grok/ask.ts +25 -12
- package/src/clis/hackernews/search.yaml +1 -1
- package/src/clis/instagram/search.yaml +2 -1
- package/src/clis/jd/item.test.ts +35 -0
- package/src/clis/jd/item.ts +101 -0
- package/src/clis/jike/feed.ts +1 -1
- package/src/clis/jike/search.ts +1 -1
- package/src/clis/linkedin/search.ts +5 -4
- package/src/clis/linkedin/timeline.test.ts +99 -0
- package/src/clis/linkedin/timeline.ts +532 -0
- package/src/clis/linux-do/search.yaml +3 -1
- package/src/clis/medium/feed.ts +1 -1
- package/src/clis/medium/search.ts +2 -2
- package/src/clis/medium/user.ts +1 -1
- package/src/clis/medium/{shared.ts → utils.ts} +2 -1
- package/src/clis/pixiv/detail.yaml +49 -0
- package/src/clis/pixiv/download.test.ts +114 -0
- package/src/clis/pixiv/download.ts +91 -0
- package/src/clis/pixiv/illusts.test.ts +115 -0
- package/src/clis/pixiv/illusts.ts +78 -0
- package/src/clis/pixiv/ranking.yaml +53 -0
- package/src/clis/pixiv/search.test.ts +97 -0
- package/src/clis/pixiv/search.ts +53 -0
- package/src/clis/pixiv/test-utils.ts +29 -0
- package/src/clis/pixiv/user.yaml +46 -0
- package/src/clis/pixiv/utils.ts +62 -0
- package/src/clis/reddit/comment.ts +2 -1
- package/src/clis/reddit/read.test.ts +34 -0
- package/src/clis/reddit/read.ts +4 -3
- package/src/clis/reddit/save.ts +2 -1
- package/src/clis/reddit/saved.ts +6 -2
- package/src/clis/reddit/subscribe.ts +2 -1
- package/src/clis/reddit/upvote.ts +2 -1
- package/src/clis/reddit/upvoted.ts +6 -2
- package/src/clis/reuters/search.ts +0 -1
- package/src/clis/sinablog/article.ts +1 -1
- package/src/clis/sinablog/hot.ts +1 -1
- package/src/clis/sinablog/user.ts +1 -1
- package/src/clis/substack/feed.ts +1 -1
- package/src/clis/substack/publication.ts +1 -1
- package/src/clis/substack/search.ts +3 -2
- package/src/clis/substack/{shared.ts → utils.ts} +3 -2
- package/src/clis/tiktok/search.yaml +2 -1
- package/src/clis/twitter/accept.ts +2 -1
- package/src/clis/twitter/article.ts +3 -1
- package/src/clis/twitter/block.ts +2 -1
- package/src/clis/twitter/bookmark.ts +2 -1
- package/src/clis/twitter/bookmarks.ts +3 -2
- package/src/clis/twitter/delete.ts +2 -1
- package/src/clis/twitter/follow.ts +2 -1
- package/src/clis/twitter/followers.ts +3 -2
- package/src/clis/twitter/following.ts +3 -2
- package/src/clis/twitter/hide-reply.ts +2 -1
- package/src/clis/twitter/like.ts +2 -1
- package/src/clis/twitter/notifications.ts +2 -1
- package/src/clis/twitter/post.ts +2 -1
- package/src/clis/twitter/profile.ts +4 -2
- package/src/clis/twitter/reply-dm.ts +2 -1
- package/src/clis/twitter/reply.ts +2 -1
- package/src/clis/twitter/search.test.ts +180 -0
- package/src/clis/twitter/search.ts +40 -14
- package/src/clis/twitter/thread.ts +2 -2
- package/src/clis/twitter/timeline.ts +3 -2
- package/src/clis/twitter/trending.ts +3 -2
- package/src/clis/twitter/unblock.ts +2 -1
- package/src/clis/twitter/unbookmark.ts +2 -1
- package/src/clis/twitter/unfollow.ts +2 -1
- package/src/clis/v2ex/daily.ts +3 -2
- package/src/clis/v2ex/me.ts +3 -2
- package/src/clis/v2ex/notifications.ts +3 -4
- package/src/clis/web/read.ts +210 -0
- 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/weread/search.ts +3 -2
- package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
- package/src/clis/xueqiu/danjuan-utils.ts +176 -0
- package/src/clis/xueqiu/fund-holdings.ts +32 -0
- package/src/clis/xueqiu/fund-snapshot.ts +27 -0
- 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/youtube/transcript.ts +5 -4
- package/src/clis/youtube/video.ts +3 -2
- package/src/clis/zhihu/search.yaml +2 -1
- package/src/daemon.ts +5 -4
- package/src/discovery.ts +12 -34
- package/src/doctor.ts +3 -2
- package/src/download/index.test.ts +93 -2
- package/src/download/index.ts +44 -23
- package/src/download/media-download.ts +5 -3
- package/src/engine.test.ts +84 -3
- package/src/execution.ts +62 -46
- package/src/explore.ts +21 -90
- package/src/external-clis.yaml +0 -25
- package/src/external.test.ts +9 -0
- package/src/external.ts +12 -10
- package/src/generate.ts +4 -41
- package/src/hooks.test.ts +126 -0
- package/src/hooks.ts +90 -0
- package/src/interceptor.ts +73 -23
- package/src/main.ts +2 -0
- package/src/output.ts +14 -6
- package/src/pipeline/executor.ts +1 -1
- package/src/pipeline/steps/browser.ts +1 -3
- package/src/pipeline/steps/download.test.ts +136 -0
- package/src/pipeline/steps/download.ts +47 -34
- package/src/pipeline/steps/fetch.test.ts +179 -0
- package/src/pipeline/steps/fetch.ts +39 -23
- package/src/pipeline/steps/transform.ts +2 -6
- package/src/pipeline/template.test.ts +28 -0
- package/src/pipeline/template.ts +67 -79
- package/src/pipeline/transform.test.ts +20 -0
- package/src/plugin.test.ts +251 -3
- package/src/plugin.ts +265 -21
- package/src/record.ts +12 -84
- package/src/registry-api.ts +2 -0
- package/src/registry.ts +7 -4
- package/src/runtime.ts +14 -4
- package/src/snapshotFormatter.ts +43 -121
- package/src/utils.ts +39 -0
- package/src/validate.ts +3 -5
- package/src/weread-search-regression.test.ts +44 -0
- package/src/yaml-schema.ts +28 -0
- package/tests/e2e/browser-auth.test.ts +25 -0
- package/tests/e2e/browser-public-extended.test.ts +162 -0
- package/tests/e2e/browser-public.test.ts +7 -146
- package/tests/e2e/plugin-management.test.ts +137 -0
- package/tests/e2e/public-commands.test.ts +34 -1
- package/vitest.config.ts +33 -8
- package/.github/workflows/pkg-pr-new.yml +0 -30
- package/dist/clis/douban/shared.d.ts +0 -4
- package/dist/clis/douban/shared.js +0 -155
- package/src/clis/douban/shared.ts +0 -165
- /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/boss/{common.js → utils.js} +0 -0
- /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/doubao/{common.js → utils.js} +0 -0
- /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
- /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/jike/{shared.js → utils.js} +0 -0
- /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
- /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
- /package/src/clis/boss/{common.ts → utils.ts} +0 -0
- /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
- /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
- /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
- /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
package/dist/discovery.js
CHANGED
|
@@ -17,16 +17,15 @@ import { getErrorMessage } from './errors.js';
|
|
|
17
17
|
import { log } from './logger.js';
|
|
18
18
|
/** Plugins directory: ~/.opencli/plugins/ */
|
|
19
19
|
export const PLUGINS_DIR = path.join(os.homedir(), '.opencli', 'plugins');
|
|
20
|
-
|
|
20
|
+
/** Matches files that register commands via cli() or lifecycle hooks */
|
|
21
|
+
const PLUGIN_MODULE_PATTERN = /\b(?:cli|onStartup|onBeforeExecute|onAfterExecute)\s*\(/;
|
|
21
22
|
function parseStrategy(rawStrategy, fallback = Strategy.COOKIE) {
|
|
22
23
|
if (!rawStrategy)
|
|
23
24
|
return fallback;
|
|
24
25
|
const key = rawStrategy.toUpperCase();
|
|
25
26
|
return Strategy[key] ?? fallback;
|
|
26
27
|
}
|
|
27
|
-
|
|
28
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
29
|
-
}
|
|
28
|
+
import { isRecord } from './utils.js';
|
|
30
29
|
/**
|
|
31
30
|
* Discover and register CLI commands.
|
|
32
31
|
* Uses pre-compiled manifest when available for instant startup.
|
|
@@ -37,13 +36,14 @@ export async function discoverClis(...dirs) {
|
|
|
37
36
|
const manifestPath = path.resolve(dir, '..', 'cli-manifest.json');
|
|
38
37
|
try {
|
|
39
38
|
await fs.promises.access(manifestPath);
|
|
40
|
-
await loadFromManifest(manifestPath, dir);
|
|
41
|
-
|
|
39
|
+
const loaded = await loadFromManifest(manifestPath, dir);
|
|
40
|
+
if (loaded)
|
|
41
|
+
continue; // Skip filesystem scan only when manifest is usable
|
|
42
42
|
}
|
|
43
43
|
catch {
|
|
44
|
-
//
|
|
45
|
-
await discoverClisFromFs(dir);
|
|
44
|
+
// Fall through to filesystem scan
|
|
46
45
|
}
|
|
46
|
+
await discoverClisFromFs(dir);
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
@@ -98,9 +98,11 @@ async function loadFromManifest(manifestPath, clisDir) {
|
|
|
98
98
|
registerCommand(cmd);
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
return true;
|
|
101
102
|
}
|
|
102
103
|
catch (err) {
|
|
103
104
|
log.warn(`Failed to load manifest ${manifestPath}: ${getErrorMessage(err)}`);
|
|
105
|
+
return false;
|
|
104
106
|
}
|
|
105
107
|
}
|
|
106
108
|
/**
|
|
@@ -113,7 +115,6 @@ async function discoverClisFromFs(dir) {
|
|
|
113
115
|
catch {
|
|
114
116
|
return;
|
|
115
117
|
}
|
|
116
|
-
const promises = [];
|
|
117
118
|
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
118
119
|
const sitePromises = entries
|
|
119
120
|
.filter(entry => entry.isDirectory())
|
|
@@ -242,7 +243,7 @@ async function discoverPluginDir(dir, site) {
|
|
|
242
243
|
async function isCliModule(filePath) {
|
|
243
244
|
try {
|
|
244
245
|
const source = await fs.promises.readFile(filePath, 'utf-8');
|
|
245
|
-
return
|
|
246
|
+
return PLUGIN_MODULE_PATTERN.test(source);
|
|
246
247
|
}
|
|
247
248
|
catch (err) {
|
|
248
249
|
log.warn(`Failed to inspect module ${filePath}: ${getErrorMessage(err)}`);
|
package/dist/doctor.js
CHANGED
|
@@ -9,6 +9,7 @@ import { DEFAULT_DAEMON_PORT } from './constants.js';
|
|
|
9
9
|
import { checkDaemonStatus } from './browser/discover.js';
|
|
10
10
|
import { BrowserBridge } from './browser/index.js';
|
|
11
11
|
import { listSessions } from './browser/daemon-client.js';
|
|
12
|
+
import { getErrorMessage } from './errors.js';
|
|
12
13
|
/**
|
|
13
14
|
* Test connectivity by attempting a real browser command.
|
|
14
15
|
*/
|
|
@@ -23,7 +24,7 @@ export async function checkConnectivity(opts) {
|
|
|
23
24
|
return { ok: true, durationMs: Date.now() - start };
|
|
24
25
|
}
|
|
25
26
|
catch (err) {
|
|
26
|
-
return { ok: false, error:
|
|
27
|
+
return { ok: false, error: getErrorMessage(err), durationMs: Date.now() - start };
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
export async function runBrowserDoctor(opts = {}) {
|
package/dist/download/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Download utilities: HTTP downloads, yt-dlp wrapper, format conversion.
|
|
3
3
|
*/
|
|
4
|
+
import type { BrowserCookie } from '../types.js';
|
|
5
|
+
export type { BrowserCookie } from '../types.js';
|
|
4
6
|
export interface DownloadOptions {
|
|
5
7
|
cookies?: string;
|
|
6
8
|
headers?: Record<string, string>;
|
|
7
9
|
timeout?: number;
|
|
8
10
|
onProgress?: (received: number, total: number) => void;
|
|
11
|
+
maxRedirects?: number;
|
|
9
12
|
}
|
|
10
13
|
export interface YtdlpOptions {
|
|
11
14
|
cookies?: string;
|
|
@@ -14,19 +17,8 @@ export interface YtdlpOptions {
|
|
|
14
17
|
extraArgs?: string[];
|
|
15
18
|
onProgress?: (percent: number) => void;
|
|
16
19
|
}
|
|
17
|
-
export interface BrowserCookie {
|
|
18
|
-
name: string;
|
|
19
|
-
value: string;
|
|
20
|
-
domain: string;
|
|
21
|
-
path?: string;
|
|
22
|
-
secure?: boolean;
|
|
23
|
-
httpOnly?: boolean;
|
|
24
|
-
expirationDate?: number;
|
|
25
|
-
}
|
|
26
20
|
/** Check if yt-dlp is available in PATH. */
|
|
27
21
|
export declare function checkYtdlp(): boolean;
|
|
28
|
-
/** Check if ffmpeg is available in PATH. */
|
|
29
|
-
export declare function checkFfmpeg(): boolean;
|
|
30
22
|
/**
|
|
31
23
|
* Detect content type from URL and optional headers.
|
|
32
24
|
*/
|
|
@@ -38,7 +30,7 @@ export declare function requiresYtdlp(url: string): boolean;
|
|
|
38
30
|
/**
|
|
39
31
|
* HTTP download with progress callback.
|
|
40
32
|
*/
|
|
41
|
-
export declare function httpDownload(url: string, destPath: string, options?: DownloadOptions): Promise<{
|
|
33
|
+
export declare function httpDownload(url: string, destPath: string, options?: DownloadOptions, redirectCount?: number): Promise<{
|
|
42
34
|
success: boolean;
|
|
43
35
|
size: number;
|
|
44
36
|
error?: string;
|
package/dist/download/index.js
CHANGED
|
@@ -9,14 +9,11 @@ import * as http from 'node:http';
|
|
|
9
9
|
import * as os from 'node:os';
|
|
10
10
|
import { URL } from 'node:url';
|
|
11
11
|
import { isBinaryInstalled } from '../external.js';
|
|
12
|
+
import { getErrorMessage } from '../errors.js';
|
|
12
13
|
/** Check if yt-dlp is available in PATH. */
|
|
13
14
|
export function checkYtdlp() {
|
|
14
15
|
return isBinaryInstalled('yt-dlp');
|
|
15
16
|
}
|
|
16
|
-
/** Check if ffmpeg is available in PATH. */
|
|
17
|
-
export function checkFfmpeg() {
|
|
18
|
-
return isBinaryInstalled('ffmpeg');
|
|
19
|
-
}
|
|
20
17
|
/** Domains that host video content and can be downloaded via yt-dlp. */
|
|
21
18
|
const VIDEO_PLATFORM_DOMAINS = [
|
|
22
19
|
'youtube.com', 'youtu.be', 'bilibili.com', 'twitter.com',
|
|
@@ -59,13 +56,13 @@ export function requiresYtdlp(url) {
|
|
|
59
56
|
/**
|
|
60
57
|
* HTTP download with progress callback.
|
|
61
58
|
*/
|
|
62
|
-
export async function httpDownload(url, destPath, options = {}) {
|
|
63
|
-
const { cookies, headers = {}, timeout = 30000, onProgress } = options;
|
|
59
|
+
export async function httpDownload(url, destPath, options = {}, redirectCount = 0) {
|
|
60
|
+
const { cookies, headers = {}, timeout = 30000, onProgress, maxRedirects = 10 } = options;
|
|
64
61
|
return new Promise((resolve) => {
|
|
65
62
|
const parsedUrl = new URL(url);
|
|
66
63
|
const protocol = parsedUrl.protocol === 'https:' ? https : http;
|
|
67
64
|
const requestHeaders = {
|
|
68
|
-
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/
|
|
65
|
+
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
|
|
69
66
|
...headers,
|
|
70
67
|
};
|
|
71
68
|
if (cookies) {
|
|
@@ -82,7 +79,18 @@ export async function httpDownload(url, destPath, options = {}) {
|
|
|
82
79
|
file.close();
|
|
83
80
|
if (fs.existsSync(tempPath))
|
|
84
81
|
fs.unlinkSync(tempPath);
|
|
85
|
-
|
|
82
|
+
if (redirectCount >= maxRedirects) {
|
|
83
|
+
resolve({ success: false, size: 0, error: `Too many redirects (> ${maxRedirects})` });
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const redirectUrl = resolveRedirectUrl(url, response.headers.location);
|
|
87
|
+
const originalHost = new URL(url).hostname;
|
|
88
|
+
const redirectHost = new URL(redirectUrl).hostname;
|
|
89
|
+
// Do not forward cookies when a redirect crosses host boundaries.
|
|
90
|
+
const redirectOptions = originalHost === redirectHost
|
|
91
|
+
? options
|
|
92
|
+
: { ...options, cookies: undefined, headers: stripCookieHeaders(options.headers) };
|
|
93
|
+
httpDownload(redirectUrl, destPath, redirectOptions, redirectCount + 1).then(resolve);
|
|
86
94
|
return;
|
|
87
95
|
}
|
|
88
96
|
if (response.statusCode !== 200) {
|
|
@@ -125,6 +133,11 @@ export async function httpDownload(url, destPath, options = {}) {
|
|
|
125
133
|
export function resolveRedirectUrl(currentUrl, location) {
|
|
126
134
|
return new URL(location, currentUrl).toString();
|
|
127
135
|
}
|
|
136
|
+
function stripCookieHeaders(headers) {
|
|
137
|
+
if (!headers)
|
|
138
|
+
return headers;
|
|
139
|
+
return Object.fromEntries(Object.entries(headers).filter(([key]) => key.toLowerCase() !== 'cookie'));
|
|
140
|
+
}
|
|
128
141
|
/**
|
|
129
142
|
* Export cookies to Netscape format for yt-dlp.
|
|
130
143
|
*/
|
|
@@ -141,7 +154,9 @@ export function exportCookiesToNetscape(cookies, filePath) {
|
|
|
141
154
|
const cookiePath = cookie.path || '/';
|
|
142
155
|
const secure = cookie.secure ? 'TRUE' : 'FALSE';
|
|
143
156
|
const expiry = Math.floor(Date.now() / 1000) + 86400 * 365; // 1 year from now
|
|
144
|
-
|
|
157
|
+
const safeName = cookie.name.replace(/[\t\n\r]/g, '');
|
|
158
|
+
const safeValue = cookie.value.replace(/[\t\n\r]/g, '');
|
|
159
|
+
lines.push(`${domain}\t${includeSubdomains}\t${cookiePath}\t${secure}\t${expiry}\t${safeName}\t${safeValue}`);
|
|
145
160
|
}
|
|
146
161
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
147
162
|
fs.writeFileSync(filePath, lines.join('\n'));
|
|
@@ -168,8 +183,14 @@ export async function ytdlpDownload(url, destPath, options = {}) {
|
|
|
168
183
|
'--no-playlist',
|
|
169
184
|
'--progress',
|
|
170
185
|
];
|
|
171
|
-
if (cookiesFile
|
|
172
|
-
|
|
186
|
+
if (cookiesFile) {
|
|
187
|
+
if (fs.existsSync(cookiesFile)) {
|
|
188
|
+
args.push('--cookies', cookiesFile);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
console.error(`[download] Cookies file not found: ${cookiesFile}, falling back to browser cookies`);
|
|
192
|
+
args.push('--cookies-from-browser', 'chrome');
|
|
193
|
+
}
|
|
173
194
|
}
|
|
174
195
|
else {
|
|
175
196
|
// Try to use browser cookies
|
|
@@ -251,7 +272,7 @@ export async function saveDocument(content, destPath, format = 'markdown', metad
|
|
|
251
272
|
return { success: true, size: Buffer.byteLength(output, 'utf-8') };
|
|
252
273
|
}
|
|
253
274
|
catch (err) {
|
|
254
|
-
return { success: false, size: 0, error: err
|
|
275
|
+
return { success: false, size: 0, error: getErrorMessage(err) };
|
|
255
276
|
}
|
|
256
277
|
}
|
|
257
278
|
/**
|
|
@@ -1,5 +1,26 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as http from 'node:http';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import { afterEach, describe, expect, it } from 'vitest';
|
|
6
|
+
import { formatCookieHeader, httpDownload, resolveRedirectUrl } from './index.js';
|
|
7
|
+
const servers = [];
|
|
8
|
+
afterEach(async () => {
|
|
9
|
+
await Promise.all(servers.map((server) => new Promise((resolve, reject) => {
|
|
10
|
+
server.close((err) => (err ? reject(err) : resolve()));
|
|
11
|
+
})));
|
|
12
|
+
servers.length = 0;
|
|
13
|
+
});
|
|
14
|
+
async function startServer(handler, hostname = '127.0.0.1') {
|
|
15
|
+
const server = http.createServer(handler);
|
|
16
|
+
servers.push(server);
|
|
17
|
+
await new Promise((resolve) => server.listen(0, hostname, resolve));
|
|
18
|
+
const address = server.address();
|
|
19
|
+
if (!address || typeof address === 'string') {
|
|
20
|
+
throw new Error('Failed to start test server');
|
|
21
|
+
}
|
|
22
|
+
return `http://${hostname}:${address.port}`;
|
|
23
|
+
}
|
|
3
24
|
describe('download helpers', () => {
|
|
4
25
|
it('resolves relative redirects against the original URL', () => {
|
|
5
26
|
expect(resolveRedirectUrl('https://example.com/a/file', '/cdn/file.bin')).toBe('https://example.com/cdn/file.bin');
|
|
@@ -11,4 +32,60 @@ describe('download helpers', () => {
|
|
|
11
32
|
{ name: 'ct0', value: 'def', domain: 'example.com' },
|
|
12
33
|
])).toBe('sid=abc; ct0=def');
|
|
13
34
|
});
|
|
35
|
+
it('fails after exceeding the redirect limit', async () => {
|
|
36
|
+
const baseUrl = await startServer((_req, res) => {
|
|
37
|
+
res.statusCode = 302;
|
|
38
|
+
res.setHeader('Location', '/loop');
|
|
39
|
+
res.end();
|
|
40
|
+
});
|
|
41
|
+
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'opencli-download-'));
|
|
42
|
+
const destPath = path.join(tempDir, 'file.txt');
|
|
43
|
+
const result = await httpDownload(`${baseUrl}/loop`, destPath, { maxRedirects: 2 });
|
|
44
|
+
expect(result).toEqual({
|
|
45
|
+
success: false,
|
|
46
|
+
size: 0,
|
|
47
|
+
error: 'Too many redirects (> 2)',
|
|
48
|
+
});
|
|
49
|
+
expect(fs.existsSync(destPath)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
it('does not forward cookies across cross-domain redirects', async () => {
|
|
52
|
+
let forwardedCookie;
|
|
53
|
+
const targetUrl = await startServer((req, res) => {
|
|
54
|
+
forwardedCookie = req.headers.cookie;
|
|
55
|
+
res.statusCode = 200;
|
|
56
|
+
res.end('ok');
|
|
57
|
+
}, 'localhost');
|
|
58
|
+
const redirectUrl = await startServer((_req, res) => {
|
|
59
|
+
res.statusCode = 302;
|
|
60
|
+
res.setHeader('Location', targetUrl);
|
|
61
|
+
res.end();
|
|
62
|
+
});
|
|
63
|
+
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'opencli-download-'));
|
|
64
|
+
const destPath = path.join(tempDir, 'redirect.txt');
|
|
65
|
+
const result = await httpDownload(`${redirectUrl}/start`, destPath, { cookies: 'sid=abc' });
|
|
66
|
+
expect(result).toEqual({ success: true, size: 2 });
|
|
67
|
+
expect(forwardedCookie).toBeUndefined();
|
|
68
|
+
expect(fs.readFileSync(destPath, 'utf8')).toBe('ok');
|
|
69
|
+
});
|
|
70
|
+
it('does not forward cookie headers across cross-domain redirects', async () => {
|
|
71
|
+
let forwardedCookie;
|
|
72
|
+
const targetUrl = await startServer((req, res) => {
|
|
73
|
+
forwardedCookie = req.headers.cookie;
|
|
74
|
+
res.statusCode = 200;
|
|
75
|
+
res.end('ok');
|
|
76
|
+
}, 'localhost');
|
|
77
|
+
const redirectUrl = await startServer((_req, res) => {
|
|
78
|
+
res.statusCode = 302;
|
|
79
|
+
res.setHeader('Location', targetUrl);
|
|
80
|
+
res.end();
|
|
81
|
+
});
|
|
82
|
+
const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'opencli-download-'));
|
|
83
|
+
const destPath = path.join(tempDir, 'redirect-header.txt');
|
|
84
|
+
const result = await httpDownload(`${redirectUrl}/start`, destPath, {
|
|
85
|
+
headers: { Cookie: 'sid=header-cookie' },
|
|
86
|
+
});
|
|
87
|
+
expect(result).toEqual({ success: true, size: 2 });
|
|
88
|
+
expect(forwardedCookie).toBeUndefined();
|
|
89
|
+
expect(fs.readFileSync(destPath, 'utf8')).toBe('ok');
|
|
90
|
+
});
|
|
14
91
|
});
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from 'node:fs';
|
|
10
10
|
import * as path from 'node:path';
|
|
11
|
+
import { getErrorMessage } from '../errors.js';
|
|
11
12
|
import { httpDownload, ytdlpDownload, checkYtdlp, getTempDir, exportCookiesToNetscape, } from './index.js';
|
|
12
13
|
import { DownloadProgressTracker, formatBytes } from './progress.js';
|
|
13
14
|
// ============================================================
|
|
@@ -89,14 +90,15 @@ export async function downloadMedia(items, options) {
|
|
|
89
90
|
});
|
|
90
91
|
}
|
|
91
92
|
catch (err) {
|
|
93
|
+
const msg = getErrorMessage(err);
|
|
92
94
|
if (progressBar)
|
|
93
|
-
progressBar.fail(
|
|
95
|
+
progressBar.fail(msg);
|
|
94
96
|
tracker.onFileComplete(false);
|
|
95
97
|
results.push({
|
|
96
98
|
index: i + 1,
|
|
97
99
|
type: media.type,
|
|
98
100
|
status: 'failed',
|
|
99
|
-
size:
|
|
101
|
+
size: msg,
|
|
100
102
|
});
|
|
101
103
|
}
|
|
102
104
|
}
|
package/dist/engine.test.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import { describe, it, expect, afterEach } from 'vitest';
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
2
|
import { discoverClis, discoverPlugins, PLUGINS_DIR } from './discovery.js';
|
|
3
3
|
import { executeCommand } from './execution.js';
|
|
4
4
|
import { getRegistry, cli, Strategy } from './registry.js';
|
|
5
|
+
import { clearAllHooks, onAfterExecute } from './hooks.js';
|
|
5
6
|
import * as fs from 'node:fs';
|
|
7
|
+
import * as os from 'node:os';
|
|
6
8
|
import * as path from 'node:path';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
7
10
|
describe('discoverClis', () => {
|
|
8
11
|
it('handles non-existent directories gracefully', async () => {
|
|
9
12
|
// Should not throw for missing directories
|
|
10
|
-
await expect(discoverClis('
|
|
13
|
+
await expect(discoverClis(path.join(os.tmpdir(), 'nonexistent-opencli-test-dir'))).resolves.not.toThrow();
|
|
11
14
|
});
|
|
12
15
|
it('imports only CLI command modules during filesystem discovery', async () => {
|
|
13
|
-
const tempRoot = await fs.promises.mkdtemp(path.join(
|
|
16
|
+
const tempRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'opencli-discovery-'));
|
|
14
17
|
const siteDir = path.join(tempRoot, 'temp-site');
|
|
15
18
|
const helperPath = path.join(siteDir, 'helper.ts');
|
|
16
19
|
const commandPath = path.join(siteDir, 'hello.ts');
|
|
@@ -21,7 +24,7 @@ globalThis.__opencli_helper_loaded__ = true;
|
|
|
21
24
|
export const helper = true;
|
|
22
25
|
`);
|
|
23
26
|
await fs.promises.writeFile(commandPath, `
|
|
24
|
-
import { cli, Strategy } from '${path.join(process.cwd(), 'src', 'registry.ts')}';
|
|
27
|
+
import { cli, Strategy } from '${pathToFileURL(path.join(process.cwd(), 'src', 'registry.ts')).href}';
|
|
25
28
|
cli({
|
|
26
29
|
site: 'temp-site',
|
|
27
30
|
name: 'hello',
|
|
@@ -41,6 +44,33 @@ cli({
|
|
|
41
44
|
await fs.promises.rm(tempRoot, { recursive: true, force: true });
|
|
42
45
|
}
|
|
43
46
|
});
|
|
47
|
+
it('falls back to filesystem discovery when the manifest is invalid', async () => {
|
|
48
|
+
const tempBuildRoot = await fs.promises.mkdtemp(path.join(os.tmpdir(), 'opencli-manifest-fallback-'));
|
|
49
|
+
const distDir = path.join(tempBuildRoot, 'dist');
|
|
50
|
+
const siteDir = path.join(distDir, 'fallback-site');
|
|
51
|
+
const commandPath = path.join(siteDir, 'hello.ts');
|
|
52
|
+
const manifestPath = path.join(tempBuildRoot, 'cli-manifest.json');
|
|
53
|
+
try {
|
|
54
|
+
await fs.promises.mkdir(siteDir, { recursive: true });
|
|
55
|
+
await fs.promises.writeFile(manifestPath, '{ invalid json');
|
|
56
|
+
await fs.promises.writeFile(commandPath, `
|
|
57
|
+
import { cli, Strategy } from '${pathToFileURL(path.join(process.cwd(), 'src', 'registry.ts')).href}';
|
|
58
|
+
cli({
|
|
59
|
+
site: 'fallback-site',
|
|
60
|
+
name: 'hello',
|
|
61
|
+
description: 'hello command',
|
|
62
|
+
strategy: Strategy.PUBLIC,
|
|
63
|
+
browser: false,
|
|
64
|
+
func: async () => [{ ok: true }],
|
|
65
|
+
});
|
|
66
|
+
`);
|
|
67
|
+
await discoverClis(distDir);
|
|
68
|
+
expect(getRegistry().get('fallback-site/hello')).toBeDefined();
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
await fs.promises.rm(tempBuildRoot, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
44
74
|
});
|
|
45
75
|
describe('discoverPlugins', () => {
|
|
46
76
|
const testPluginDir = path.join(PLUGINS_DIR, '__test-plugin__');
|
|
@@ -80,6 +110,10 @@ columns: [message]
|
|
|
80
110
|
});
|
|
81
111
|
});
|
|
82
112
|
describe('executeCommand', () => {
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
clearAllHooks();
|
|
115
|
+
vi.unstubAllEnvs();
|
|
116
|
+
});
|
|
83
117
|
it('accepts kebab-case option names after Commander camelCases them', async () => {
|
|
84
118
|
const cmd = cli({
|
|
85
119
|
site: 'test-engine',
|
|
@@ -148,4 +182,42 @@ describe('executeCommand', () => {
|
|
|
148
182
|
await executeCommand(cmd, {}, true);
|
|
149
183
|
expect(receivedDebug).toBe(true);
|
|
150
184
|
});
|
|
185
|
+
it('fires onAfterExecute even when command execution throws', async () => {
|
|
186
|
+
const seen = [];
|
|
187
|
+
onAfterExecute((ctx) => {
|
|
188
|
+
seen.push({ error: ctx.error, finishedAt: ctx.finishedAt });
|
|
189
|
+
});
|
|
190
|
+
const cmd = cli({
|
|
191
|
+
site: 'test-engine',
|
|
192
|
+
name: 'failing-test',
|
|
193
|
+
description: 'failing command',
|
|
194
|
+
browser: false,
|
|
195
|
+
strategy: Strategy.PUBLIC,
|
|
196
|
+
func: async () => {
|
|
197
|
+
throw new Error('boom');
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
await expect(executeCommand(cmd, {})).rejects.toThrow('boom');
|
|
201
|
+
expect(seen).toHaveLength(1);
|
|
202
|
+
expect(seen[0].error).toBeInstanceOf(Error);
|
|
203
|
+
expect(seen[0].error.message).toBe('boom');
|
|
204
|
+
expect(typeof seen[0].finishedAt).toBe('number');
|
|
205
|
+
});
|
|
206
|
+
it('fails fast for chatwise commands when OPENCLI_CDP_ENDPOINT is missing', async () => {
|
|
207
|
+
const cmd = cli({
|
|
208
|
+
site: 'chatwise',
|
|
209
|
+
name: 'status',
|
|
210
|
+
description: 'chatwise status',
|
|
211
|
+
browser: true,
|
|
212
|
+
strategy: Strategy.PUBLIC,
|
|
213
|
+
requiredEnv: [
|
|
214
|
+
{
|
|
215
|
+
name: 'OPENCLI_CDP_ENDPOINT',
|
|
216
|
+
help: 'Set OPENCLI_CDP_ENDPOINT before running chatwise commands.',
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
func: async () => [{ ok: true }],
|
|
220
|
+
});
|
|
221
|
+
await expect(executeCommand(cmd, {})).rejects.toThrow('requires environment variable OPENCLI_CDP_ENDPOINT');
|
|
222
|
+
});
|
|
151
223
|
});
|
package/dist/execution.d.ts
CHANGED
|
@@ -7,18 +7,10 @@
|
|
|
7
7
|
* 3. Domain pre-navigation for cookie/header strategies
|
|
8
8
|
* 4. Timeout enforcement
|
|
9
9
|
* 5. Lazy-loading of TS modules from manifest
|
|
10
|
+
* 6. Lifecycle hooks (onBeforeExecute / onAfterExecute)
|
|
10
11
|
*/
|
|
11
12
|
import { type CliCommand, type Arg } from './registry.js';
|
|
12
13
|
type CommandArgs = Record<string, unknown>;
|
|
13
|
-
/**
|
|
14
|
-
* Validates and coerces arguments based on the command's Arg definitions.
|
|
15
|
-
*/
|
|
16
14
|
export declare function coerceAndValidateArgs(cmdArgs: Arg[], kwargs: CommandArgs): CommandArgs;
|
|
17
|
-
/**
|
|
18
|
-
* Execute a CLI command. Automatically manages browser sessions when needed.
|
|
19
|
-
*
|
|
20
|
-
* This is the unified entry point — callers don't need to care about
|
|
21
|
-
* whether the command requires a browser or not.
|
|
22
|
-
*/
|
|
23
15
|
export declare function executeCommand(cmd: CliCommand, rawKwargs: CommandArgs, debug?: boolean): Promise<unknown>;
|
|
24
16
|
export {};
|
package/dist/execution.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* 3. Domain pre-navigation for cookie/header strategies
|
|
8
8
|
* 4. Timeout enforcement
|
|
9
9
|
* 5. Lazy-loading of TS modules from manifest
|
|
10
|
+
* 6. Lifecycle hooks (onBeforeExecute / onAfterExecute)
|
|
10
11
|
*/
|
|
11
12
|
import { Strategy, getRegistry, fullName } from './registry.js';
|
|
12
13
|
import { pathToFileURL } from 'node:url';
|
|
@@ -14,21 +15,16 @@ import { executePipeline } from './pipeline/index.js';
|
|
|
14
15
|
import { AdapterLoadError, ArgumentError, CommandExecutionError, getErrorMessage } from './errors.js';
|
|
15
16
|
import { shouldUseBrowserSession } from './capabilityRouting.js';
|
|
16
17
|
import { getBrowserFactory, browserSession, runWithTimeout, DEFAULT_BROWSER_COMMAND_TIMEOUT } from './runtime.js';
|
|
17
|
-
|
|
18
|
+
import { emitHook } from './hooks.js';
|
|
18
19
|
const _loadedModules = new Set();
|
|
19
|
-
/**
|
|
20
|
-
* Validates and coerces arguments based on the command's Arg definitions.
|
|
21
|
-
*/
|
|
22
20
|
export function coerceAndValidateArgs(cmdArgs, kwargs) {
|
|
23
21
|
const result = { ...kwargs };
|
|
24
22
|
for (const argDef of cmdArgs) {
|
|
25
23
|
const val = result[argDef.name];
|
|
26
|
-
// 1. Check required
|
|
27
24
|
if (argDef.required && (val === undefined || val === null || val === '')) {
|
|
28
25
|
throw new ArgumentError(`Argument "${argDef.name}" is required.`, argDef.help ?? `Provide a value for --${argDef.name}`);
|
|
29
26
|
}
|
|
30
27
|
if (val !== undefined && val !== null) {
|
|
31
|
-
// 2. Type coercion
|
|
32
28
|
if (argDef.type === 'int' || argDef.type === 'number') {
|
|
33
29
|
const num = Number(val);
|
|
34
30
|
if (Number.isNaN(num)) {
|
|
@@ -50,7 +46,6 @@ export function coerceAndValidateArgs(cmdArgs, kwargs) {
|
|
|
50
46
|
result[argDef.name] = Boolean(val);
|
|
51
47
|
}
|
|
52
48
|
}
|
|
53
|
-
// 3. Choices validation
|
|
54
49
|
const coercedVal = result[argDef.name];
|
|
55
50
|
if (argDef.choices && argDef.choices.length > 0) {
|
|
56
51
|
if (!argDef.choices.map(String).includes(String(coercedVal))) {
|
|
@@ -64,11 +59,7 @@ export function coerceAndValidateArgs(cmdArgs, kwargs) {
|
|
|
64
59
|
}
|
|
65
60
|
return result;
|
|
66
61
|
}
|
|
67
|
-
/**
|
|
68
|
-
* Run a command's func or pipeline against a page.
|
|
69
|
-
*/
|
|
70
62
|
async function runCommand(cmd, page, kwargs, debug) {
|
|
71
|
-
// Lazy-load TS module on first execution (manifest fast-path)
|
|
72
63
|
const internal = cmd;
|
|
73
64
|
if (internal._lazy && internal._modulePath) {
|
|
74
65
|
const modulePath = internal._modulePath;
|
|
@@ -81,10 +72,13 @@ async function runCommand(cmd, page, kwargs, debug) {
|
|
|
81
72
|
throw new AdapterLoadError(`Failed to load adapter module ${modulePath}: ${getErrorMessage(err)}`, 'Check that the adapter file exists and has no syntax errors.');
|
|
82
73
|
}
|
|
83
74
|
}
|
|
84
|
-
// After loading, the module's cli() call will have updated the registry.
|
|
85
75
|
const updated = getRegistry().get(fullName(cmd));
|
|
86
|
-
if (updated?.func)
|
|
76
|
+
if (updated?.func) {
|
|
77
|
+
if (!page && updated.browser !== false) {
|
|
78
|
+
throw new CommandExecutionError(`Command ${fullName(cmd)} requires a browser session but none was provided`);
|
|
79
|
+
}
|
|
87
80
|
return updated.func(page, kwargs, debug);
|
|
81
|
+
}
|
|
88
82
|
if (updated?.pipeline)
|
|
89
83
|
return executePipeline(page, updated.pipeline, { args: kwargs, debug });
|
|
90
84
|
}
|
|
@@ -94,30 +88,25 @@ async function runCommand(cmd, page, kwargs, debug) {
|
|
|
94
88
|
return executePipeline(page, cmd.pipeline, { args: kwargs, debug });
|
|
95
89
|
throw new CommandExecutionError(`Command ${fullName(cmd)} has no func or pipeline`, 'This is likely a bug in the adapter definition. Please report this issue.');
|
|
96
90
|
}
|
|
97
|
-
/**
|
|
98
|
-
* Resolve the pre-navigation URL for a command, or null to skip.
|
|
99
|
-
*
|
|
100
|
-
* COOKIE/HEADER strategies need the browser on the target domain so
|
|
101
|
-
* `fetch(url, { credentials: 'include' })` carries cookies.
|
|
102
|
-
* Adapters that handle their own navigation set `navigateBefore: false`.
|
|
103
|
-
*/
|
|
104
91
|
function resolvePreNav(cmd) {
|
|
105
92
|
if (cmd.navigateBefore === false)
|
|
106
93
|
return null;
|
|
107
94
|
if (typeof cmd.navigateBefore === 'string')
|
|
108
95
|
return cmd.navigateBefore;
|
|
109
|
-
// Default: pre-navigate for COOKIE/HEADER strategies with a domain
|
|
110
96
|
if ((cmd.strategy === Strategy.COOKIE || cmd.strategy === Strategy.HEADER) && cmd.domain) {
|
|
111
97
|
return `https://${cmd.domain}`;
|
|
112
98
|
}
|
|
113
99
|
return null;
|
|
114
100
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
101
|
+
function ensureRequiredEnv(cmd) {
|
|
102
|
+
const missing = (cmd.requiredEnv ?? []).find(({ name }) => {
|
|
103
|
+
const value = process.env[name];
|
|
104
|
+
return value === undefined || value === null || value === '';
|
|
105
|
+
});
|
|
106
|
+
if (!missing)
|
|
107
|
+
return;
|
|
108
|
+
throw new CommandExecutionError(`Command ${fullName(cmd)} requires environment variable ${missing.name}.`, missing.help ?? `Set ${missing.name} before running ${fullName(cmd)}.`);
|
|
109
|
+
}
|
|
121
110
|
export async function executeCommand(cmd, rawKwargs, debug = false) {
|
|
122
111
|
let kwargs;
|
|
123
112
|
try {
|
|
@@ -128,25 +117,46 @@ export async function executeCommand(cmd, rawKwargs, debug = false) {
|
|
|
128
117
|
throw err;
|
|
129
118
|
throw new ArgumentError(getErrorMessage(err));
|
|
130
119
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
120
|
+
const hookCtx = {
|
|
121
|
+
command: fullName(cmd),
|
|
122
|
+
args: kwargs,
|
|
123
|
+
startedAt: Date.now(),
|
|
124
|
+
};
|
|
125
|
+
await emitHook('onBeforeExecute', hookCtx);
|
|
126
|
+
let result;
|
|
127
|
+
try {
|
|
128
|
+
if (shouldUseBrowserSession(cmd)) {
|
|
129
|
+
ensureRequiredEnv(cmd);
|
|
130
|
+
const BrowserFactory = getBrowserFactory();
|
|
131
|
+
result = await browserSession(BrowserFactory, async (page) => {
|
|
132
|
+
const preNavUrl = resolvePreNav(cmd);
|
|
133
|
+
if (preNavUrl) {
|
|
134
|
+
try {
|
|
135
|
+
await page.goto(preNavUrl);
|
|
136
|
+
await page.wait(2);
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
if (debug)
|
|
140
|
+
console.error(`[pre-nav] Failed to navigate to ${preNavUrl}: ${err instanceof Error ? err.message : err}`);
|
|
141
|
+
}
|
|
141
142
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
143
|
+
return runWithTimeout(runCommand(cmd, page, kwargs, debug), {
|
|
144
|
+
timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT,
|
|
145
|
+
label: fullName(cmd),
|
|
146
|
+
});
|
|
147
|
+
}, { workspace: `site:${cmd.site}` });
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
result = await runCommand(cmd, null, kwargs, debug);
|
|
151
|
+
}
|
|
149
152
|
}
|
|
150
|
-
|
|
151
|
-
|
|
153
|
+
catch (err) {
|
|
154
|
+
hookCtx.error = err;
|
|
155
|
+
hookCtx.finishedAt = Date.now();
|
|
156
|
+
await emitHook('onAfterExecute', hookCtx);
|
|
157
|
+
throw err;
|
|
158
|
+
}
|
|
159
|
+
hookCtx.finishedAt = Date.now();
|
|
160
|
+
await emitHook('onAfterExecute', hookCtx, result);
|
|
161
|
+
return result;
|
|
152
162
|
}
|