@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
|
@@ -74,10 +74,17 @@ function connect(): void {
|
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* After MAX_EAGER_ATTEMPTS (reaching 60s backoff), stop scheduling reconnects.
|
|
79
|
+
* The keepalive alarm (~24s) will still call connect() periodically, but at a
|
|
80
|
+
* much lower frequency — reducing console noise when the daemon is not running.
|
|
81
|
+
*/
|
|
82
|
+
const MAX_EAGER_ATTEMPTS = 6; // 2s, 4s, 8s, 16s, 32s, 60s — then stop
|
|
83
|
+
|
|
77
84
|
function scheduleReconnect(): void {
|
|
78
85
|
if (reconnectTimer) return;
|
|
79
86
|
reconnectAttempts++;
|
|
80
|
-
|
|
87
|
+
if (reconnectAttempts > MAX_EAGER_ATTEMPTS) return; // let keepalive alarm handle it
|
|
81
88
|
const delay = Math.min(WS_RECONNECT_BASE_DELAY * Math.pow(2, reconnectAttempts - 1), WS_RECONNECT_MAX_DELAY);
|
|
82
89
|
reconnectTimer = setTimeout(() => {
|
|
83
90
|
reconnectTimer = null;
|
|
@@ -138,7 +145,7 @@ async function getAutomationWindow(workspace: string): Promise<number> {
|
|
|
138
145
|
// Create a new window with a data: URI that New Tab Override extensions cannot intercept.
|
|
139
146
|
// Using about:blank would be hijacked by extensions like "New Tab Override".
|
|
140
147
|
const win = await chrome.windows.create({
|
|
141
|
-
url:
|
|
148
|
+
url: BLANK_PAGE,
|
|
142
149
|
focused: false,
|
|
143
150
|
width: 1280,
|
|
144
151
|
height: 900,
|
|
@@ -193,6 +200,18 @@ chrome.alarms.onAlarm.addListener((alarm) => {
|
|
|
193
200
|
if (alarm.name === 'keepalive') connect();
|
|
194
201
|
});
|
|
195
202
|
|
|
203
|
+
// ─── Popup status API ───────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
|
|
206
|
+
if (msg?.type === 'getStatus') {
|
|
207
|
+
sendResponse({
|
|
208
|
+
connected: ws?.readyState === WebSocket.OPEN,
|
|
209
|
+
reconnecting: reconnectTimer !== null,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
});
|
|
214
|
+
|
|
196
215
|
// ─── Command dispatcher ─────────────────────────────────────────────
|
|
197
216
|
|
|
198
217
|
async function handleCommand(cmd: Command): Promise<Result> {
|
|
@@ -229,10 +248,37 @@ async function handleCommand(cmd: Command): Promise<Result> {
|
|
|
229
248
|
|
|
230
249
|
// ─── Action handlers ─────────────────────────────────────────────────
|
|
231
250
|
|
|
232
|
-
/**
|
|
251
|
+
/** Internal blank page used when no user URL is provided. */
|
|
252
|
+
const BLANK_PAGE = 'data:text/html,<html></html>';
|
|
253
|
+
|
|
254
|
+
/** Check if a URL can be attached via CDP — only allow http(s) and our internal blank page. */
|
|
233
255
|
function isDebuggableUrl(url?: string): boolean {
|
|
234
256
|
if (!url) return true; // empty/undefined = tab still loading, allow it
|
|
235
|
-
return
|
|
257
|
+
return url.startsWith('http://') || url.startsWith('https://') || url === BLANK_PAGE;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Check if a URL is safe for user-facing navigation (http/https only). */
|
|
261
|
+
function isSafeNavigationUrl(url: string): boolean {
|
|
262
|
+
return url.startsWith('http://') || url.startsWith('https://');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/** Minimal URL normalization for same-page comparison: root slash + default port only. */
|
|
266
|
+
function normalizeUrlForComparison(url?: string): string {
|
|
267
|
+
if (!url) return '';
|
|
268
|
+
try {
|
|
269
|
+
const parsed = new URL(url);
|
|
270
|
+
if ((parsed.protocol === 'https:' && parsed.port === '443') || (parsed.protocol === 'http:' && parsed.port === '80')) {
|
|
271
|
+
parsed.port = '';
|
|
272
|
+
}
|
|
273
|
+
const pathname = parsed.pathname === '/' ? '' : parsed.pathname;
|
|
274
|
+
return `${parsed.protocol}//${parsed.host}${pathname}${parsed.search}${parsed.hash}`;
|
|
275
|
+
} catch {
|
|
276
|
+
return url;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function isTargetUrl(currentUrl: string | undefined, targetUrl: string): boolean {
|
|
281
|
+
return normalizeUrlForComparison(currentUrl) === normalizeUrlForComparison(targetUrl);
|
|
236
282
|
}
|
|
237
283
|
|
|
238
284
|
/**
|
|
@@ -247,9 +293,14 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
|
|
|
247
293
|
if (tabId !== undefined) {
|
|
248
294
|
try {
|
|
249
295
|
const tab = await chrome.tabs.get(tabId);
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
296
|
+
const session = automationSessions.get(workspace);
|
|
297
|
+
if (isDebuggableUrl(tab.url) && session && tab.windowId === session.windowId) return tabId;
|
|
298
|
+
if (session && tab.windowId !== session.windowId) {
|
|
299
|
+
console.warn(`[opencli] Tab ${tabId} belongs to window ${tab.windowId}, not automation window ${session.windowId}, re-resolving`);
|
|
300
|
+
} else if (!isDebuggableUrl(tab.url)) {
|
|
301
|
+
// Tab exists but URL is not debuggable — fall through to auto-resolve
|
|
302
|
+
console.warn(`[opencli] Tab ${tabId} URL is not debuggable (${tab.url}), re-resolving`);
|
|
303
|
+
}
|
|
253
304
|
} catch {
|
|
254
305
|
// Tab was closed — fall through to auto-resolve
|
|
255
306
|
console.warn(`[opencli] Tab ${tabId} no longer exists, re-resolving`);
|
|
@@ -268,7 +319,7 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
|
|
|
268
319
|
// Try to reuse by navigating to a data: URI (not interceptable by New Tab Override).
|
|
269
320
|
const reuseTab = tabs.find(t => t.id);
|
|
270
321
|
if (reuseTab?.id) {
|
|
271
|
-
await chrome.tabs.update(reuseTab.id, { url:
|
|
322
|
+
await chrome.tabs.update(reuseTab.id, { url: BLANK_PAGE });
|
|
272
323
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
273
324
|
try {
|
|
274
325
|
const updated = await chrome.tabs.get(reuseTab.id);
|
|
@@ -280,7 +331,7 @@ async function resolveTabId(tabId: number | undefined, workspace: string): Promi
|
|
|
280
331
|
}
|
|
281
332
|
|
|
282
333
|
// Fallback: create a new tab
|
|
283
|
-
const newTab = await chrome.tabs.create({ windowId, url:
|
|
334
|
+
const newTab = await chrome.tabs.create({ windowId, url: BLANK_PAGE, active: true });
|
|
284
335
|
if (!newTab.id) throw new Error('Failed to create tab in automation window');
|
|
285
336
|
return newTab.id;
|
|
286
337
|
}
|
|
@@ -314,54 +365,79 @@ async function handleExec(cmd: Command, workspace: string): Promise<Result> {
|
|
|
314
365
|
|
|
315
366
|
async function handleNavigate(cmd: Command, workspace: string): Promise<Result> {
|
|
316
367
|
if (!cmd.url) return { id: cmd.id, ok: false, error: 'Missing url' };
|
|
368
|
+
if (!isSafeNavigationUrl(cmd.url)) {
|
|
369
|
+
return { id: cmd.id, ok: false, error: 'Blocked URL scheme -- only http:// and https:// are allowed' };
|
|
370
|
+
}
|
|
317
371
|
const tabId = await resolveTabId(cmd.tabId, workspace);
|
|
318
372
|
|
|
319
|
-
// Capture the current URL before navigation to detect actual URL change
|
|
320
373
|
const beforeTab = await chrome.tabs.get(tabId);
|
|
321
|
-
const
|
|
374
|
+
const beforeNormalized = normalizeUrlForComparison(beforeTab.url);
|
|
322
375
|
const targetUrl = cmd.url;
|
|
323
376
|
|
|
377
|
+
// Fast-path: tab is already at the target URL and fully loaded.
|
|
378
|
+
if (beforeTab.status === 'complete' && isTargetUrl(beforeTab.url, targetUrl)) {
|
|
379
|
+
return {
|
|
380
|
+
id: cmd.id,
|
|
381
|
+
ok: true,
|
|
382
|
+
data: { title: beforeTab.title, url: beforeTab.url, tabId, timedOut: false },
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Detach any existing debugger before top-level navigation.
|
|
387
|
+
// Some sites (observed on creator.xiaohongshu.com flows) can invalidate the
|
|
388
|
+
// current inspected target during navigation, which leaves a stale CDP attach
|
|
389
|
+
// state and causes the next Runtime.evaluate to fail with
|
|
390
|
+
// "Inspected target navigated or closed". Resetting here forces a clean
|
|
391
|
+
// re-attach after navigation.
|
|
392
|
+
await executor.detach(tabId);
|
|
393
|
+
|
|
324
394
|
await chrome.tabs.update(tabId, { url: targetUrl });
|
|
325
395
|
|
|
326
|
-
// Wait
|
|
327
|
-
//
|
|
396
|
+
// Wait until navigation completes. Resolve when status is 'complete' AND either:
|
|
397
|
+
// - the URL matches the target (handles same-URL / canonicalized navigations), OR
|
|
398
|
+
// - the URL differs from the pre-navigation URL (handles redirects).
|
|
328
399
|
let timedOut = false;
|
|
329
400
|
await new Promise<void>((resolve) => {
|
|
330
|
-
let
|
|
401
|
+
let settled = false;
|
|
402
|
+
let checkTimer: ReturnType<typeof setTimeout> | null = null;
|
|
403
|
+
let timeoutTimer: ReturnType<typeof setTimeout> | null = null;
|
|
331
404
|
|
|
332
|
-
const
|
|
333
|
-
if (
|
|
405
|
+
const finish = () => {
|
|
406
|
+
if (settled) return;
|
|
407
|
+
settled = true;
|
|
408
|
+
chrome.tabs.onUpdated.removeListener(listener);
|
|
409
|
+
if (checkTimer) clearTimeout(checkTimer);
|
|
410
|
+
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
411
|
+
resolve();
|
|
412
|
+
};
|
|
334
413
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
}
|
|
414
|
+
const isNavigationDone = (url: string | undefined): boolean => {
|
|
415
|
+
return isTargetUrl(url, targetUrl) || normalizeUrlForComparison(url) !== beforeNormalized;
|
|
416
|
+
};
|
|
339
417
|
|
|
340
|
-
|
|
341
|
-
if (
|
|
342
|
-
|
|
343
|
-
|
|
418
|
+
const listener = (id: number, info: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) => {
|
|
419
|
+
if (id !== tabId) return;
|
|
420
|
+
if (info.status === 'complete' && isNavigationDone(tab.url ?? info.url)) {
|
|
421
|
+
finish();
|
|
344
422
|
}
|
|
345
423
|
};
|
|
346
424
|
chrome.tabs.onUpdated.addListener(listener);
|
|
347
425
|
|
|
348
426
|
// Also check if the tab already navigated (e.g. instant cache hit)
|
|
349
|
-
setTimeout(async () => {
|
|
427
|
+
checkTimer = setTimeout(async () => {
|
|
350
428
|
try {
|
|
351
429
|
const currentTab = await chrome.tabs.get(tabId);
|
|
352
|
-
if (currentTab.
|
|
353
|
-
|
|
354
|
-
resolve();
|
|
430
|
+
if (currentTab.status === 'complete' && isNavigationDone(currentTab.url)) {
|
|
431
|
+
finish();
|
|
355
432
|
}
|
|
356
433
|
} catch { /* tab gone */ }
|
|
357
434
|
}, 100);
|
|
358
435
|
|
|
359
436
|
// Timeout fallback with warning
|
|
360
|
-
setTimeout(() => {
|
|
361
|
-
chrome.tabs.onUpdated.removeListener(listener);
|
|
437
|
+
timeoutTimer = setTimeout(() => {
|
|
362
438
|
timedOut = true;
|
|
363
439
|
console.warn(`[opencli] Navigate to ${targetUrl} timed out after 15s`);
|
|
364
|
-
|
|
440
|
+
finish();
|
|
365
441
|
}, 15000);
|
|
366
442
|
});
|
|
367
443
|
|
|
@@ -388,8 +464,11 @@ async function handleTabs(cmd: Command, workspace: string): Promise<Result> {
|
|
|
388
464
|
return { id: cmd.id, ok: true, data };
|
|
389
465
|
}
|
|
390
466
|
case 'new': {
|
|
467
|
+
if (cmd.url && !isSafeNavigationUrl(cmd.url)) {
|
|
468
|
+
return { id: cmd.id, ok: false, error: 'Blocked URL scheme -- only http:// and https:// are allowed' };
|
|
469
|
+
}
|
|
391
470
|
const windowId = await getAutomationWindow(workspace);
|
|
392
|
-
const tab = await chrome.tabs.create({ windowId, url: cmd.url ??
|
|
471
|
+
const tab = await chrome.tabs.create({ windowId, url: cmd.url ?? BLANK_PAGE, active: true });
|
|
393
472
|
return { id: cmd.id, ok: true, data: { tabId: tab.id, url: tab.url } };
|
|
394
473
|
}
|
|
395
474
|
case 'close': {
|
|
@@ -398,18 +477,28 @@ async function handleTabs(cmd: Command, workspace: string): Promise<Result> {
|
|
|
398
477
|
const target = tabs[cmd.index];
|
|
399
478
|
if (!target?.id) return { id: cmd.id, ok: false, error: `Tab index ${cmd.index} not found` };
|
|
400
479
|
await chrome.tabs.remove(target.id);
|
|
401
|
-
executor.detach(target.id);
|
|
480
|
+
await executor.detach(target.id);
|
|
402
481
|
return { id: cmd.id, ok: true, data: { closed: target.id } };
|
|
403
482
|
}
|
|
404
483
|
const tabId = await resolveTabId(cmd.tabId, workspace);
|
|
405
484
|
await chrome.tabs.remove(tabId);
|
|
406
|
-
executor.detach(tabId);
|
|
485
|
+
await executor.detach(tabId);
|
|
407
486
|
return { id: cmd.id, ok: true, data: { closed: tabId } };
|
|
408
487
|
}
|
|
409
488
|
case 'select': {
|
|
410
489
|
if (cmd.index === undefined && cmd.tabId === undefined)
|
|
411
490
|
return { id: cmd.id, ok: false, error: 'Missing index or tabId' };
|
|
412
491
|
if (cmd.tabId !== undefined) {
|
|
492
|
+
const session = automationSessions.get(workspace);
|
|
493
|
+
let tab: chrome.tabs.Tab;
|
|
494
|
+
try {
|
|
495
|
+
tab = await chrome.tabs.get(cmd.tabId);
|
|
496
|
+
} catch {
|
|
497
|
+
return { id: cmd.id, ok: false, error: `Tab ${cmd.tabId} no longer exists` };
|
|
498
|
+
}
|
|
499
|
+
if (!session || tab.windowId !== session.windowId) {
|
|
500
|
+
return { id: cmd.id, ok: false, error: `Tab ${cmd.tabId} is not in the automation window` };
|
|
501
|
+
}
|
|
413
502
|
await chrome.tabs.update(cmd.tabId, { active: true });
|
|
414
503
|
return { id: cmd.id, ok: true, data: { selected: cmd.tabId } };
|
|
415
504
|
}
|
|
@@ -425,6 +514,9 @@ async function handleTabs(cmd: Command, workspace: string): Promise<Result> {
|
|
|
425
514
|
}
|
|
426
515
|
|
|
427
516
|
async function handleCookies(cmd: Command): Promise<Result> {
|
|
517
|
+
if (!cmd.domain && !cmd.url) {
|
|
518
|
+
return { id: cmd.id, ok: false, error: 'Cookie scope required: provide domain or url to avoid dumping all cookies' };
|
|
519
|
+
}
|
|
428
520
|
const details: chrome.cookies.GetAllDetails = {};
|
|
429
521
|
if (cmd.domain) details.domain = cmd.domain;
|
|
430
522
|
if (cmd.url) details.url = cmd.url;
|
|
@@ -481,6 +573,8 @@ async function handleSessions(cmd: Command): Promise<Result> {
|
|
|
481
573
|
}
|
|
482
574
|
|
|
483
575
|
export const __test__ = {
|
|
576
|
+
handleNavigate,
|
|
577
|
+
isTargetUrl,
|
|
484
578
|
handleTabs,
|
|
485
579
|
handleSessions,
|
|
486
580
|
getAutomationWindowId: (workspace: string = 'default') => automationSessions.get(workspace)?.windowId ?? null,
|
package/extension/src/cdp.ts
CHANGED
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
|
|
9
9
|
const attached = new Set<number>();
|
|
10
10
|
|
|
11
|
-
/**
|
|
11
|
+
/** Internal blank page used when no user URL is provided. */
|
|
12
|
+
const BLANK_PAGE = 'data:text/html,<html></html>';
|
|
13
|
+
|
|
14
|
+
/** Check if a URL can be attached via CDP — only allow http(s) and our internal blank page. */
|
|
12
15
|
function isDebuggableUrl(url?: string): boolean {
|
|
13
16
|
if (!url) return true; // empty/undefined = tab still loading, allow it
|
|
14
|
-
return
|
|
17
|
+
return url.startsWith('http://') || url.startsWith('https://') || url === BLANK_PAGE;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
async function ensureAttached(tabId: number): Promise<void> {
|
|
@@ -144,10 +147,10 @@ export async function screenshot(
|
|
|
144
147
|
}
|
|
145
148
|
}
|
|
146
149
|
|
|
147
|
-
export function detach(tabId: number): void {
|
|
150
|
+
export async function detach(tabId: number): Promise<void> {
|
|
148
151
|
if (!attached.has(tabId)) return;
|
|
149
152
|
attached.delete(tabId);
|
|
150
|
-
try { chrome.debugger.detach({ tabId }); } catch { /* ignore */ }
|
|
153
|
+
try { await chrome.debugger.detach({ tabId }); } catch { /* ignore */ }
|
|
151
154
|
}
|
|
152
155
|
|
|
153
156
|
export function registerListeners(): void {
|
|
@@ -158,12 +161,9 @@ export function registerListeners(): void {
|
|
|
158
161
|
if (source.tabId) attached.delete(source.tabId);
|
|
159
162
|
});
|
|
160
163
|
// Invalidate attached cache when tab URL changes to non-debuggable
|
|
161
|
-
chrome.tabs.onUpdated.addListener((tabId, info) => {
|
|
164
|
+
chrome.tabs.onUpdated.addListener(async (tabId, info) => {
|
|
162
165
|
if (info.url && !isDebuggableUrl(info.url)) {
|
|
163
|
-
|
|
164
|
-
attached.delete(tabId);
|
|
165
|
-
try { chrome.debugger.detach({ tabId }); } catch { /* ignore */ }
|
|
166
|
-
}
|
|
166
|
+
await detach(tabId);
|
|
167
167
|
}
|
|
168
168
|
});
|
|
169
169
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jackwener/opencli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"lint": "tsc --noEmit",
|
|
31
31
|
"prepublishOnly": "npm run build",
|
|
32
32
|
"test": "vitest run --project unit",
|
|
33
|
+
"test:adapter": "vitest run --project adapter",
|
|
33
34
|
"test:all": "vitest run",
|
|
34
35
|
"test:e2e": "vitest run --project e2e",
|
|
35
36
|
"docs:dev": "vitepress dev docs",
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
"@types/turndown": "^5.0.6",
|
|
63
64
|
"@types/ws": "^8.5.13",
|
|
64
65
|
"tsx": "^4.19.3",
|
|
65
|
-
"typescript": "^
|
|
66
|
+
"typescript": "^6.0.2",
|
|
66
67
|
"vitepress": "^1.6.4",
|
|
67
68
|
"vitest": "^4.1.0"
|
|
68
69
|
}
|
|
@@ -28,6 +28,8 @@ total=0
|
|
|
28
28
|
|
|
29
29
|
for adapter_dir in "$SRC_DIR"/*/; do
|
|
30
30
|
adapter_name="$(basename "$adapter_dir")"
|
|
31
|
+
# Skip internal directories (e.g., _shared)
|
|
32
|
+
[[ "$adapter_name" == _* ]] && continue
|
|
31
33
|
total=$((total + 1))
|
|
32
34
|
|
|
33
35
|
# Check if doc exists in browser/ or desktop/ subdirectories
|
package/src/analysis.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared API analysis helpers used by both explore.ts and record.ts.
|
|
3
|
+
*
|
|
4
|
+
* Extracts common logic for:
|
|
5
|
+
* - URL pattern normalization
|
|
6
|
+
* - Array path discovery in JSON responses
|
|
7
|
+
* - Field role detection
|
|
8
|
+
* - Auth indicator inference
|
|
9
|
+
* - Capability name inference
|
|
10
|
+
* - Strategy inference
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
VOLATILE_PARAMS,
|
|
15
|
+
SEARCH_PARAMS,
|
|
16
|
+
PAGINATION_PARAMS,
|
|
17
|
+
LIMIT_PARAMS,
|
|
18
|
+
FIELD_ROLES,
|
|
19
|
+
} from './constants.js';
|
|
20
|
+
|
|
21
|
+
// ── URL pattern normalization ───────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
/** Normalize a full URL into a pattern (replace IDs, strip volatile params). */
|
|
24
|
+
export function urlToPattern(url: string): string {
|
|
25
|
+
try {
|
|
26
|
+
const p = new URL(url);
|
|
27
|
+
const pathNorm = p.pathname
|
|
28
|
+
.replace(/\/\d+/g, '/{id}')
|
|
29
|
+
.replace(/\/[0-9a-fA-F]{8,}/g, '/{hex}')
|
|
30
|
+
.replace(/\/BV[a-zA-Z0-9]{10}/g, '/{bvid}');
|
|
31
|
+
const params: string[] = [];
|
|
32
|
+
p.searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k)) params.push(k); });
|
|
33
|
+
return `${p.host}${pathNorm}${params.length ? '?' + params.sort().map(k => `${k}={}`).join('&') : ''}`;
|
|
34
|
+
} catch { return url; }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Array discovery in JSON responses ───────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
export interface ArrayDiscovery {
|
|
40
|
+
path: string;
|
|
41
|
+
items: unknown[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Find the best (largest) array of objects in a JSON response body. */
|
|
45
|
+
export function findArrayPath(obj: unknown, depth = 0): ArrayDiscovery | null {
|
|
46
|
+
if (depth > 5 || !obj || typeof obj !== 'object') return null;
|
|
47
|
+
if (Array.isArray(obj)) {
|
|
48
|
+
if (obj.length >= 2 && obj.some(i => i && typeof i === 'object' && !Array.isArray(i))) {
|
|
49
|
+
return { path: '', items: obj };
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
let best: ArrayDiscovery | null = null;
|
|
54
|
+
for (const [key, val] of Object.entries(obj as Record<string, unknown>)) {
|
|
55
|
+
const found = findArrayPath(val, depth + 1);
|
|
56
|
+
if (found) {
|
|
57
|
+
const fullPath = found.path ? `${key}.${found.path}` : key;
|
|
58
|
+
const candidate = { path: fullPath, items: found.items };
|
|
59
|
+
if (!best || candidate.items.length > best.items.length) best = candidate;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return best;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ── Field flattening & role detection ───────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
/** Flatten nested object keys up to maxDepth. */
|
|
68
|
+
export function flattenFields(obj: unknown, prefix: string, maxDepth: number): string[] {
|
|
69
|
+
if (maxDepth <= 0 || !obj || typeof obj !== 'object') return [];
|
|
70
|
+
const names: string[] = [];
|
|
71
|
+
const record = obj as Record<string, unknown>;
|
|
72
|
+
for (const key of Object.keys(record)) {
|
|
73
|
+
const full = prefix ? `${prefix}.${key}` : key;
|
|
74
|
+
names.push(full);
|
|
75
|
+
const val = record[key];
|
|
76
|
+
if (val && typeof val === 'object' && !Array.isArray(val)) names.push(...flattenFields(val, full, maxDepth - 1));
|
|
77
|
+
}
|
|
78
|
+
return names;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Detect semantic field roles (title, url, author, etc.) from sample fields. */
|
|
82
|
+
export function detectFieldRoles(sampleFields: string[]): Record<string, string> {
|
|
83
|
+
const detectedFields: Record<string, string> = {};
|
|
84
|
+
for (const [role, aliases] of Object.entries(FIELD_ROLES)) {
|
|
85
|
+
for (const f of sampleFields) {
|
|
86
|
+
if (aliases.includes(f.split('.').pop()?.toLowerCase() ?? '')) {
|
|
87
|
+
detectedFields[role] = f;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return detectedFields;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── Capability name inference ───────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
/** Infer a CLI capability name from a URL. */
|
|
98
|
+
export function inferCapabilityName(url: string, goal?: string): string {
|
|
99
|
+
if (goal) return goal;
|
|
100
|
+
const u = url.toLowerCase();
|
|
101
|
+
if (u.includes('hot') || u.includes('popular') || u.includes('ranking') || u.includes('trending')) return 'hot';
|
|
102
|
+
if (u.includes('search')) return 'search';
|
|
103
|
+
if (u.includes('feed') || u.includes('timeline') || u.includes('dynamic')) return 'feed';
|
|
104
|
+
if (u.includes('comment') || u.includes('reply')) return 'comments';
|
|
105
|
+
if (u.includes('history')) return 'history';
|
|
106
|
+
if (u.includes('profile') || u.includes('userinfo') || u.includes('/me')) return 'me';
|
|
107
|
+
if (u.includes('favorite') || u.includes('collect') || u.includes('bookmark')) return 'favorite';
|
|
108
|
+
try {
|
|
109
|
+
const segs = new URL(url).pathname
|
|
110
|
+
.split('/')
|
|
111
|
+
.filter(s => s && !s.match(/^\d+$/) && !s.match(/^[0-9a-f]{8,}$/i) && !s.match(/^v\d+$/));
|
|
112
|
+
if (segs.length) return segs[segs.length - 1].replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
|
113
|
+
} catch {}
|
|
114
|
+
return 'data';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Strategy inference ──────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
/** Infer auth strategy from detected indicators. */
|
|
120
|
+
export function inferStrategy(authIndicators: string[]): string {
|
|
121
|
+
if (authIndicators.includes('signature')) return 'intercept';
|
|
122
|
+
if (authIndicators.includes('bearer') || authIndicators.includes('csrf')) return 'header';
|
|
123
|
+
return 'cookie';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ── Auth indicator detection ────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
/** Detect auth indicators from HTTP headers. */
|
|
129
|
+
export function detectAuthFromHeaders(headers?: Record<string, string>): string[] {
|
|
130
|
+
if (!headers) return [];
|
|
131
|
+
const indicators: string[] = [];
|
|
132
|
+
const keys = Object.keys(headers).map(k => k.toLowerCase());
|
|
133
|
+
if (keys.some(k => k === 'authorization')) indicators.push('bearer');
|
|
134
|
+
if (keys.some(k => k.startsWith('x-csrf') || k.startsWith('x-xsrf'))) indicators.push('csrf');
|
|
135
|
+
if (keys.some(k => k.startsWith('x-s') || k === 'x-t' || k === 'x-s-common')) indicators.push('signature');
|
|
136
|
+
return indicators;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Detect auth indicators from URL and response body (heuristic). */
|
|
140
|
+
export function detectAuthFromContent(url: string, body: unknown): string[] {
|
|
141
|
+
const indicators: string[] = [];
|
|
142
|
+
if (body && typeof body === 'object') {
|
|
143
|
+
const keys = Object.keys(body as object).map(k => k.toLowerCase());
|
|
144
|
+
if (keys.some(k => k.includes('sign') || k === 'w_rid' || k.includes('token'))) {
|
|
145
|
+
indicators.push('signature');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (url.includes('/wbi/') || url.includes('w_rid=')) indicators.push('signature');
|
|
149
|
+
if (url.includes('bearer') || url.includes('access_token')) indicators.push('bearer');
|
|
150
|
+
return indicators;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ── Query param classification ──────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
/** Extract non-volatile query params and classify them. */
|
|
156
|
+
export function classifyQueryParams(url: string): {
|
|
157
|
+
params: string[];
|
|
158
|
+
hasSearch: boolean;
|
|
159
|
+
hasPagination: boolean;
|
|
160
|
+
hasLimit: boolean;
|
|
161
|
+
} {
|
|
162
|
+
const params: string[] = [];
|
|
163
|
+
try { new URL(url).searchParams.forEach((_v, k) => { if (!VOLATILE_PARAMS.has(k)) params.push(k); }); } catch {}
|
|
164
|
+
return {
|
|
165
|
+
params,
|
|
166
|
+
hasSearch: params.some(p => SEARCH_PARAMS.has(p)),
|
|
167
|
+
hasPagination: params.some(p => PAGINATION_PARAMS.has(p)),
|
|
168
|
+
hasLimit: params.some(p => LIMIT_PARAMS.has(p)),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const { MockWebSocket } = vi.hoisted(() => {
|
|
4
|
+
class MockWebSocket {
|
|
5
|
+
static OPEN = 1;
|
|
6
|
+
readyState = 1;
|
|
7
|
+
private handlers = new Map<string, Array<(...args: any[]) => void>>();
|
|
8
|
+
|
|
9
|
+
constructor(_url: string) {
|
|
10
|
+
queueMicrotask(() => this.emit('open'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
on(event: string, handler: (...args: any[]) => void): void {
|
|
14
|
+
const handlers = this.handlers.get(event) ?? [];
|
|
15
|
+
handlers.push(handler);
|
|
16
|
+
this.handlers.set(event, handlers);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
send(_message: string): void {}
|
|
20
|
+
|
|
21
|
+
close(): void {
|
|
22
|
+
this.readyState = 3;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private emit(event: string, ...args: any[]): void {
|
|
26
|
+
for (const handler of this.handlers.get(event) ?? []) {
|
|
27
|
+
handler(...args);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { MockWebSocket };
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
vi.mock('ws', () => ({
|
|
36
|
+
WebSocket: MockWebSocket,
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
import { CDPBridge } from './cdp.js';
|
|
40
|
+
|
|
41
|
+
describe('CDPBridge cookies', () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.unstubAllEnvs();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('filters cookies by actual domain match instead of substring match', async () => {
|
|
47
|
+
vi.stubEnv('OPENCLI_CDP_ENDPOINT', 'ws://127.0.0.1:9222/devtools/page/1');
|
|
48
|
+
|
|
49
|
+
const bridge = new CDPBridge();
|
|
50
|
+
vi.spyOn(bridge, 'send').mockResolvedValue({
|
|
51
|
+
cookies: [
|
|
52
|
+
{ name: 'good', value: '1', domain: '.example.com' },
|
|
53
|
+
{ name: 'exact', value: '2', domain: 'example.com' },
|
|
54
|
+
{ name: 'bad', value: '3', domain: 'notexample.com' },
|
|
55
|
+
],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const page = await bridge.connect();
|
|
59
|
+
const cookies = await page.getCookies({ domain: 'example.com' });
|
|
60
|
+
|
|
61
|
+
expect(cookies).toEqual([
|
|
62
|
+
{ name: 'good', value: '1', domain: '.example.com' },
|
|
63
|
+
{ name: 'exact', value: '2', domain: 'example.com' },
|
|
64
|
+
]);
|
|
65
|
+
});
|
|
66
|
+
});
|