@jackwener/opencli 1.3.2 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/pull_request_template.md +3 -1
- package/.github/workflows/build-extension.yml +7 -1
- package/.github/workflows/ci.yml +29 -3
- package/.github/workflows/docs.yml +1 -1
- package/.github/workflows/e2e-headed.yml +20 -0
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/security.yml +0 -3
- package/CHANGELOG.md +55 -0
- package/CONTRIBUTING.md +6 -3
- package/README.md +37 -10
- package/README.zh-CN.md +37 -10
- package/SKILL.md +7 -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 +59 -38
- package/dist/browser/cdp.test.d.ts +1 -0
- package/dist/browser/cdp.test.js +52 -0
- package/dist/browser/daemon-client.js +2 -1
- package/dist/browser/discover.js +2 -1
- 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/errors.js +2 -1
- package/dist/browser/index.d.ts +3 -2
- package/dist/browser/index.js +2 -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 +44 -35
- package/dist/browser/stealth.d.ts +16 -0
- package/dist/browser/stealth.js +155 -0
- package/dist/browser.test.js +47 -1
- 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 +639 -258
- 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/arxiv/search.js +1 -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/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/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/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/medium/feed.js +1 -1
- package/dist/clis/medium/search.js +1 -1
- 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/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 +30 -13
- package/dist/clis/twitter/search.test.d.ts +1 -0
- package/dist/clis/twitter/search.test.js +104 -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/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/youtube/transcript.js +5 -4
- package/dist/clis/youtube/video.js +3 -2
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/daemon.js +9 -4
- package/dist/discovery.js +11 -10
- package/dist/doctor.js +4 -2
- 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 -8
- 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/yaml-schema.d.ts +26 -0
- package/dist/yaml-schema.js +5 -0
- package/docs/.vitepress/config.mts +3 -0
- package/docs/adapters/browser/dictionary.md +27 -0
- package/docs/adapters/browser/douban.md +18 -8
- 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/web.md +30 -0
- package/docs/adapters/browser/wikipedia.md +0 -9
- package/docs/adapters/browser/xueqiu.md +27 -9
- package/docs/adapters/desktop/antigravity.md +0 -3
- package/docs/adapters/index.md +11 -9
- 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 +519 -444
- package/extension/manifest.json +1 -1
- package/extension/package.json +1 -1
- package/extension/src/background.test.ts +46 -1
- package/extension/src/background.ts +108 -33
- 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 +64 -41
- package/src/browser/daemon-client.ts +4 -3
- package/src/browser/discover.ts +2 -1
- package/src/browser/dom-snapshot.test.ts +42 -0
- package/src/browser/dom-snapshot.ts +56 -3
- package/src/browser/errors.ts +2 -1
- package/src/browser/index.ts +3 -2
- package/src/browser/mcp.ts +2 -4
- package/src/browser/page.ts +43 -35
- package/src/browser/stealth.ts +156 -0
- package/src/browser.test.ts +51 -1
- package/src/build-manifest.test.ts +14 -0
- package/src/build-manifest.ts +13 -32
- 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/arxiv/search.ts +1 -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/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/grok/ask.test.ts +25 -0
- package/src/clis/grok/ask.ts +25 -12
- 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/medium/feed.ts +1 -1
- package/src/clis/medium/search.ts +1 -1
- 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/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 +113 -0
- package/src/clis/twitter/search.ts +38 -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/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/youtube/transcript.ts +5 -4
- package/src/clis/youtube/video.ts +3 -2
- package/src/constants.ts +3 -0
- package/src/daemon.ts +7 -5
- package/src/discovery.ts +12 -34
- package/src/doctor.ts +5 -3
- 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 -8
- 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 -6
- package/src/yaml-schema.ts +28 -0
- package/tests/e2e/browser-auth.test.ts +25 -0
- package/tests/e2e/plugin-management.test.ts +137 -0
- package/tests/e2e/public-commands.test.ts +34 -1
- package/vitest.config.ts +19 -1
- 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/browser/index.d.ts
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* External code should import from './browser/index.js' (or './browser.js' via Node resolution).
|
|
6
6
|
*/
|
|
7
7
|
export { Page } from './page.js';
|
|
8
|
-
export { BrowserBridge
|
|
8
|
+
export { BrowserBridge } from './mcp.js';
|
|
9
9
|
export { CDPBridge } from './cdp.js';
|
|
10
10
|
export { isDaemonRunning } from './daemon-client.js';
|
|
11
11
|
export { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
|
|
12
|
-
export
|
|
12
|
+
export { generateStealthJs } from './stealth.js';
|
|
13
|
+
export type { DomSnapshotOptions } from './dom-snapshot.js';
|
|
13
14
|
import { extractTabEntries, diffTabIndexes, appendLimited } from './tabs.js';
|
|
14
15
|
import { withTimeoutMs } from '../runtime.js';
|
|
15
16
|
export declare const __test__: {
|
package/dist/browser/index.js
CHANGED
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* External code should import from './browser/index.js' (or './browser.js' via Node resolution).
|
|
6
6
|
*/
|
|
7
7
|
export { Page } from './page.js';
|
|
8
|
-
export { BrowserBridge
|
|
8
|
+
export { BrowserBridge } from './mcp.js';
|
|
9
9
|
export { CDPBridge } from './cdp.js';
|
|
10
10
|
export { isDaemonRunning } from './daemon-client.js';
|
|
11
11
|
export { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
|
|
12
|
+
export { generateStealthJs } from './stealth.js';
|
|
12
13
|
import { extractTabEntries, diffTabIndexes, appendLimited } from './tabs.js';
|
|
13
14
|
import { __test__ as cdpTest } from './cdp.js';
|
|
14
15
|
import { withTimeoutMs } from '../runtime.js';
|
package/dist/browser/mcp.d.ts
CHANGED
package/dist/browser/mcp.js
CHANGED
|
@@ -7,6 +7,7 @@ import * as path from 'node:path';
|
|
|
7
7
|
import * as fs from 'node:fs';
|
|
8
8
|
import { Page } from './page.js';
|
|
9
9
|
import { isDaemonRunning, isExtensionConnected } from './daemon-client.js';
|
|
10
|
+
import { DEFAULT_DAEMON_PORT } from '../constants.js';
|
|
10
11
|
const DAEMON_SPAWN_TIMEOUT = 10000; // 10s to wait for daemon + extension
|
|
11
12
|
/**
|
|
12
13
|
* Browser factory: manages daemon lifecycle and provides IPage instances.
|
|
@@ -95,8 +96,6 @@ export class BrowserBridge {
|
|
|
95
96
|
}
|
|
96
97
|
throw new Error('Failed to start opencli daemon. Try running manually:\n' +
|
|
97
98
|
` node ${daemonPath}\n` +
|
|
98
|
-
|
|
99
|
+
`Make sure port ${DEFAULT_DAEMON_PORT} is available.`);
|
|
99
100
|
}
|
|
100
101
|
}
|
|
101
|
-
/** @deprecated Use BrowserBridge instead */
|
|
102
|
-
export const PlaywrightMCP = BrowserBridge;
|
package/dist/browser/page.d.ts
CHANGED
|
@@ -18,9 +18,10 @@ export declare class Page implements IPage {
|
|
|
18
18
|
constructor(workspace?: string);
|
|
19
19
|
/** Active tab ID, set after navigate and used in all subsequent commands */
|
|
20
20
|
private _tabId;
|
|
21
|
-
/** Helper: spread
|
|
22
|
-
private
|
|
23
|
-
|
|
21
|
+
/** Helper: spread workspace into command params */
|
|
22
|
+
private _wsOpt;
|
|
23
|
+
/** Helper: spread workspace + tabId into command params */
|
|
24
|
+
private _cmdOpts;
|
|
24
25
|
goto(url: string, options?: {
|
|
25
26
|
waitUntil?: 'load' | 'none';
|
|
26
27
|
settleMs?: number;
|
package/dist/browser/page.js
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
import { formatSnapshot } from '../snapshotFormatter.js';
|
|
13
13
|
import { sendCommand } from './daemon-client.js';
|
|
14
14
|
import { wrapForEval } from './utils.js';
|
|
15
|
+
import { saveBase64ToFile } from '../utils.js';
|
|
15
16
|
import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
|
|
17
|
+
import { generateStealthJs } from './stealth.js';
|
|
16
18
|
import { clickJs, typeTextJs, pressKeyJs, waitForTextJs, scrollJs, autoScrollJs, networkRequestsJs, waitForDomStableJs, } from './dom-helpers.js';
|
|
17
19
|
/**
|
|
18
20
|
* Page — implements IPage by talking to the daemon via HTTP.
|
|
@@ -24,38 +26,50 @@ export class Page {
|
|
|
24
26
|
}
|
|
25
27
|
/** Active tab ID, set after navigate and used in all subsequent commands */
|
|
26
28
|
_tabId;
|
|
27
|
-
/** Helper: spread
|
|
28
|
-
|
|
29
|
-
return this._tabId !== undefined ? { tabId: this._tabId } : {};
|
|
30
|
-
}
|
|
31
|
-
_workspaceOpt() {
|
|
29
|
+
/** Helper: spread workspace into command params */
|
|
30
|
+
_wsOpt() {
|
|
32
31
|
return { workspace: this.workspace };
|
|
33
32
|
}
|
|
33
|
+
/** Helper: spread workspace + tabId into command params */
|
|
34
|
+
_cmdOpts() {
|
|
35
|
+
return {
|
|
36
|
+
workspace: this.workspace,
|
|
37
|
+
...(this._tabId !== undefined && { tabId: this._tabId }),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
34
40
|
async goto(url, options) {
|
|
35
41
|
const result = await sendCommand('navigate', {
|
|
36
42
|
url,
|
|
37
|
-
...this.
|
|
38
|
-
...this._tabOpt(),
|
|
43
|
+
...this._cmdOpts(),
|
|
39
44
|
});
|
|
40
45
|
// Remember the tabId for subsequent exec calls
|
|
41
46
|
if (result?.tabId) {
|
|
42
47
|
this._tabId = result.tabId;
|
|
43
48
|
}
|
|
49
|
+
// Inject stealth anti-detection patches (guard flag prevents double-injection).
|
|
50
|
+
try {
|
|
51
|
+
await sendCommand('exec', {
|
|
52
|
+
code: generateStealthJs(),
|
|
53
|
+
...this._cmdOpts(),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// Non-fatal: stealth is best-effort
|
|
58
|
+
}
|
|
44
59
|
// Smart settle: use DOM stability detection instead of fixed sleep.
|
|
45
60
|
// settleMs is now a timeout cap (default 1000ms), not a fixed wait.
|
|
46
61
|
if (options?.waitUntil !== 'none') {
|
|
47
62
|
const maxMs = options?.settleMs ?? 1000;
|
|
48
63
|
await sendCommand('exec', {
|
|
49
64
|
code: waitForDomStableJs(maxMs, Math.min(500, maxMs)),
|
|
50
|
-
...this.
|
|
51
|
-
...this._tabOpt(),
|
|
65
|
+
...this._cmdOpts(),
|
|
52
66
|
});
|
|
53
67
|
}
|
|
54
68
|
}
|
|
55
69
|
/** Close the automation window in the extension */
|
|
56
70
|
async closeWindow() {
|
|
57
71
|
try {
|
|
58
|
-
await sendCommand('close-window', { ...this.
|
|
72
|
+
await sendCommand('close-window', { ...this._wsOpt() });
|
|
59
73
|
}
|
|
60
74
|
catch {
|
|
61
75
|
// Window may already be closed or daemon may be down
|
|
@@ -63,10 +77,10 @@ export class Page {
|
|
|
63
77
|
}
|
|
64
78
|
async evaluate(js) {
|
|
65
79
|
const code = wrapForEval(js);
|
|
66
|
-
return sendCommand('exec', { code, ...this.
|
|
80
|
+
return sendCommand('exec', { code, ...this._cmdOpts() });
|
|
67
81
|
}
|
|
68
82
|
async getCookies(opts = {}) {
|
|
69
|
-
const result = await sendCommand('cookies', { ...this.
|
|
83
|
+
const result = await sendCommand('cookies', { ...this._wsOpt(), ...opts });
|
|
70
84
|
return Array.isArray(result) ? result : [];
|
|
71
85
|
}
|
|
72
86
|
async snapshot(opts = {}) {
|
|
@@ -80,7 +94,7 @@ export class Page {
|
|
|
80
94
|
bboxDedup: true,
|
|
81
95
|
});
|
|
82
96
|
try {
|
|
83
|
-
const result = await sendCommand('exec', { code: snapshotJs, ...this.
|
|
97
|
+
const result = await sendCommand('exec', { code: snapshotJs, ...this._cmdOpts() });
|
|
84
98
|
// The advanced engine already produces a clean, pruned, LLM-friendly output.
|
|
85
99
|
// Do NOT pass through formatSnapshot — its format is incompatible.
|
|
86
100
|
return result;
|
|
@@ -120,7 +134,7 @@ export class Page {
|
|
|
120
134
|
return buildTree(document.body, 0);
|
|
121
135
|
})()
|
|
122
136
|
`;
|
|
123
|
-
const raw = await sendCommand('exec', { code, ...this.
|
|
137
|
+
const raw = await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
124
138
|
if (opts.raw)
|
|
125
139
|
return raw;
|
|
126
140
|
if (typeof raw === 'string')
|
|
@@ -129,62 +143,62 @@ export class Page {
|
|
|
129
143
|
}
|
|
130
144
|
async click(ref) {
|
|
131
145
|
const code = clickJs(ref);
|
|
132
|
-
await sendCommand('exec', { code, ...this.
|
|
146
|
+
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
133
147
|
}
|
|
134
148
|
async typeText(ref, text) {
|
|
135
149
|
const code = typeTextJs(ref, text);
|
|
136
|
-
await sendCommand('exec', { code, ...this.
|
|
150
|
+
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
137
151
|
}
|
|
138
152
|
async pressKey(key) {
|
|
139
153
|
const code = pressKeyJs(key);
|
|
140
|
-
await sendCommand('exec', { code, ...this.
|
|
154
|
+
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
141
155
|
}
|
|
142
156
|
async scrollTo(ref) {
|
|
143
157
|
const code = scrollToRefJs(ref);
|
|
144
|
-
return sendCommand('exec', { code, ...this.
|
|
158
|
+
return sendCommand('exec', { code, ...this._cmdOpts() });
|
|
145
159
|
}
|
|
146
160
|
async getFormState() {
|
|
147
161
|
const code = getFormStateJs();
|
|
148
|
-
return (await sendCommand('exec', { code, ...this.
|
|
162
|
+
return (await sendCommand('exec', { code, ...this._cmdOpts() }));
|
|
149
163
|
}
|
|
150
164
|
async wait(options) {
|
|
151
165
|
if (typeof options === 'number') {
|
|
152
166
|
await new Promise(resolve => setTimeout(resolve, options * 1000));
|
|
153
167
|
return;
|
|
154
168
|
}
|
|
155
|
-
if (options.time) {
|
|
169
|
+
if (typeof options.time === 'number') {
|
|
156
170
|
await new Promise(resolve => setTimeout(resolve, options.time * 1000));
|
|
157
171
|
return;
|
|
158
172
|
}
|
|
159
173
|
if (options.text) {
|
|
160
174
|
const timeout = (options.timeout ?? 30) * 1000;
|
|
161
175
|
const code = waitForTextJs(options.text, timeout);
|
|
162
|
-
await sendCommand('exec', { code, ...this.
|
|
176
|
+
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
163
177
|
}
|
|
164
178
|
}
|
|
165
179
|
async tabs() {
|
|
166
|
-
const result = await sendCommand('tabs', { op: 'list', ...this.
|
|
180
|
+
const result = await sendCommand('tabs', { op: 'list', ...this._wsOpt() });
|
|
167
181
|
return Array.isArray(result) ? result : [];
|
|
168
182
|
}
|
|
169
183
|
async closeTab(index) {
|
|
170
|
-
await sendCommand('tabs', { op: 'close', ...this.
|
|
184
|
+
await sendCommand('tabs', { op: 'close', ...this._wsOpt(), ...(index !== undefined ? { index } : {}) });
|
|
171
185
|
// Invalidate cached tabId — the closed tab might have been our active one.
|
|
172
186
|
// We can't know for sure (close-by-index doesn't return tabId), so reset.
|
|
173
187
|
this._tabId = undefined;
|
|
174
188
|
}
|
|
175
189
|
async newTab() {
|
|
176
|
-
const result = await sendCommand('tabs', { op: 'new', ...this.
|
|
190
|
+
const result = await sendCommand('tabs', { op: 'new', ...this._wsOpt() });
|
|
177
191
|
if (result?.tabId)
|
|
178
192
|
this._tabId = result.tabId;
|
|
179
193
|
}
|
|
180
194
|
async selectTab(index) {
|
|
181
|
-
const result = await sendCommand('tabs', { op: 'select', index, ...this.
|
|
195
|
+
const result = await sendCommand('tabs', { op: 'select', index, ...this._wsOpt() });
|
|
182
196
|
if (result?.selected)
|
|
183
197
|
this._tabId = result.selected;
|
|
184
198
|
}
|
|
185
199
|
async networkRequests(includeStatic = false) {
|
|
186
200
|
const code = networkRequestsJs(includeStatic);
|
|
187
|
-
const result = await sendCommand('exec', { code, ...this.
|
|
201
|
+
const result = await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
188
202
|
return Array.isArray(result) ? result : [];
|
|
189
203
|
}
|
|
190
204
|
/**
|
|
@@ -204,30 +218,25 @@ export class Page {
|
|
|
204
218
|
*/
|
|
205
219
|
async screenshot(options = {}) {
|
|
206
220
|
const base64 = await sendCommand('screenshot', {
|
|
207
|
-
...this.
|
|
221
|
+
...this._cmdOpts(),
|
|
208
222
|
format: options.format,
|
|
209
223
|
quality: options.quality,
|
|
210
224
|
fullPage: options.fullPage,
|
|
211
|
-
...this._tabOpt(),
|
|
212
225
|
});
|
|
213
226
|
if (options.path) {
|
|
214
|
-
|
|
215
|
-
const path = await import('node:path');
|
|
216
|
-
const dir = path.dirname(options.path);
|
|
217
|
-
await fs.promises.mkdir(dir, { recursive: true });
|
|
218
|
-
await fs.promises.writeFile(options.path, Buffer.from(base64, 'base64'));
|
|
227
|
+
await saveBase64ToFile(base64, options.path);
|
|
219
228
|
}
|
|
220
229
|
return base64;
|
|
221
230
|
}
|
|
222
231
|
async scroll(direction = 'down', amount = 500) {
|
|
223
232
|
const code = scrollJs(direction, amount);
|
|
224
|
-
await sendCommand('exec', { code, ...this.
|
|
233
|
+
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
225
234
|
}
|
|
226
235
|
async autoScroll(options = {}) {
|
|
227
236
|
const times = options.times ?? 3;
|
|
228
237
|
const delayMs = options.delayMs ?? 2000;
|
|
229
238
|
const code = autoScrollJs(times, delayMs);
|
|
230
|
-
await sendCommand('exec', { code, ...this.
|
|
239
|
+
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
231
240
|
}
|
|
232
241
|
async installInterceptor(pattern) {
|
|
233
242
|
const { generateInterceptorJs } = await import('../interceptor.js');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stealth anti-detection module.
|
|
3
|
+
*
|
|
4
|
+
* Generates JS code that patches browser globals to hide automation
|
|
5
|
+
* fingerprints (e.g. navigator.webdriver, missing chrome object, empty
|
|
6
|
+
* plugin list). Injected before page scripts run so that websites cannot
|
|
7
|
+
* detect CDP / extension-based control.
|
|
8
|
+
*
|
|
9
|
+
* Inspired by puppeteer-extra-plugin-stealth.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Return a self-contained JS string that, when evaluated in a page context,
|
|
13
|
+
* applies all stealth patches. Safe to call multiple times — the guard flag
|
|
14
|
+
* ensures patches are applied only once.
|
|
15
|
+
*/
|
|
16
|
+
export declare function generateStealthJs(): string;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stealth anti-detection module.
|
|
3
|
+
*
|
|
4
|
+
* Generates JS code that patches browser globals to hide automation
|
|
5
|
+
* fingerprints (e.g. navigator.webdriver, missing chrome object, empty
|
|
6
|
+
* plugin list). Injected before page scripts run so that websites cannot
|
|
7
|
+
* detect CDP / extension-based control.
|
|
8
|
+
*
|
|
9
|
+
* Inspired by puppeteer-extra-plugin-stealth.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Return a self-contained JS string that, when evaluated in a page context,
|
|
13
|
+
* applies all stealth patches. Safe to call multiple times — the guard flag
|
|
14
|
+
* ensures patches are applied only once.
|
|
15
|
+
*/
|
|
16
|
+
export function generateStealthJs() {
|
|
17
|
+
return `
|
|
18
|
+
(() => {
|
|
19
|
+
// Guard: prevent double-injection across separate CDP evaluations.
|
|
20
|
+
// We cannot use a closure variable (each eval is a fresh scope), and
|
|
21
|
+
// window properties / Symbols are discoverable by anti-bot scripts.
|
|
22
|
+
// Instead, stash the flag in a non-enumerable getter on a built-in
|
|
23
|
+
// prototype that fingerprinters are unlikely to scan.
|
|
24
|
+
const _gProto = EventTarget.prototype;
|
|
25
|
+
const _gKey = '__lsn'; // looks like an internal listener cache
|
|
26
|
+
if (_gProto[_gKey]) return 'skipped';
|
|
27
|
+
try {
|
|
28
|
+
Object.defineProperty(_gProto, _gKey, { value: true, enumerable: false, configurable: true });
|
|
29
|
+
} catch {}
|
|
30
|
+
|
|
31
|
+
// 1. navigator.webdriver → false
|
|
32
|
+
// Most common check; Playwright/Puppeteer/CDP set this to true.
|
|
33
|
+
// Real Chrome returns false (not undefined) — returning undefined is
|
|
34
|
+
// itself a detection signal for advanced fingerprinters.
|
|
35
|
+
try {
|
|
36
|
+
Object.defineProperty(navigator, 'webdriver', {
|
|
37
|
+
get: () => false,
|
|
38
|
+
configurable: true,
|
|
39
|
+
});
|
|
40
|
+
} catch {}
|
|
41
|
+
|
|
42
|
+
// 2. window.chrome stub
|
|
43
|
+
// Real Chrome exposes window.chrome with runtime, loadTimes, csi.
|
|
44
|
+
// Headless/automated Chrome may not have it.
|
|
45
|
+
try {
|
|
46
|
+
if (!window.chrome) {
|
|
47
|
+
window.chrome = {
|
|
48
|
+
runtime: {
|
|
49
|
+
onConnect: { addListener: () => {}, removeListener: () => {} },
|
|
50
|
+
onMessage: { addListener: () => {}, removeListener: () => {} },
|
|
51
|
+
},
|
|
52
|
+
loadTimes: () => ({}),
|
|
53
|
+
csi: () => ({}),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
} catch {}
|
|
57
|
+
|
|
58
|
+
// 3. navigator.plugins — fake population only if empty
|
|
59
|
+
// Real user browser already has plugins; only patch in automated/headless
|
|
60
|
+
// contexts where the list is empty (overwriting real plugins with fakes
|
|
61
|
+
// would be counterproductive and detectable).
|
|
62
|
+
try {
|
|
63
|
+
if (!navigator.plugins || navigator.plugins.length === 0) {
|
|
64
|
+
const fakePlugins = [
|
|
65
|
+
{ name: 'PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
|
|
66
|
+
{ name: 'Chrome PDF Viewer', filename: 'internal-pdf-viewer', description: '' },
|
|
67
|
+
{ name: 'Chromium PDF Viewer', filename: 'internal-pdf-viewer', description: '' },
|
|
68
|
+
{ name: 'Microsoft Edge PDF Viewer', filename: 'internal-pdf-viewer', description: '' },
|
|
69
|
+
{ name: 'WebKit built-in PDF', filename: 'internal-pdf-viewer', description: '' },
|
|
70
|
+
];
|
|
71
|
+
fakePlugins.item = (i) => fakePlugins[i] || null;
|
|
72
|
+
fakePlugins.namedItem = (n) => fakePlugins.find(p => p.name === n) || null;
|
|
73
|
+
fakePlugins.refresh = () => {};
|
|
74
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
75
|
+
get: () => fakePlugins,
|
|
76
|
+
configurable: true,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
} catch {}
|
|
80
|
+
|
|
81
|
+
// 4. navigator.languages — guarantee non-empty
|
|
82
|
+
// Some automated contexts return undefined or empty array.
|
|
83
|
+
try {
|
|
84
|
+
if (!navigator.languages || navigator.languages.length === 0) {
|
|
85
|
+
Object.defineProperty(navigator, 'languages', {
|
|
86
|
+
get: () => ['en-US', 'en'],
|
|
87
|
+
configurable: true,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
} catch {}
|
|
91
|
+
|
|
92
|
+
// 5. Permissions.query — normalize notification permission
|
|
93
|
+
// Headless Chrome throws on Permissions.query({ name: 'notifications' }).
|
|
94
|
+
try {
|
|
95
|
+
const origQuery = window.Permissions?.prototype?.query;
|
|
96
|
+
if (origQuery) {
|
|
97
|
+
window.Permissions.prototype.query = function (parameters) {
|
|
98
|
+
if (parameters?.name === 'notifications') {
|
|
99
|
+
return Promise.resolve({ state: Notification.permission, onchange: null });
|
|
100
|
+
}
|
|
101
|
+
return origQuery.call(this, parameters);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
} catch {}
|
|
105
|
+
|
|
106
|
+
// 6. Clean automation artifacts
|
|
107
|
+
// Remove properties left by Playwright, Puppeteer, or CDP injection.
|
|
108
|
+
try {
|
|
109
|
+
delete window.__playwright;
|
|
110
|
+
delete window.__puppeteer;
|
|
111
|
+
// ChromeDriver injects cdc_ prefixed globals; the suffix varies by version,
|
|
112
|
+
// so scan window for any matching property rather than hardcoding names.
|
|
113
|
+
for (const prop of Object.getOwnPropertyNames(window)) {
|
|
114
|
+
if (prop.startsWith('cdc_') || prop.startsWith('__cdc_')) {
|
|
115
|
+
try { delete window[prop]; } catch {}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
} catch {}
|
|
119
|
+
|
|
120
|
+
// 7. CDP stack trace cleanup
|
|
121
|
+
// Runtime.evaluate injects scripts whose source URLs appear in Error
|
|
122
|
+
// stack traces (e.g. __puppeteer_evaluation_script__, pptr:, debugger://).
|
|
123
|
+
// Websites detect automation by doing: new Error().stack and inspecting it.
|
|
124
|
+
// We override the stack property getter on Error.prototype to filter them.
|
|
125
|
+
// Note: Error.prepareStackTrace is V8/Node-only and not available in
|
|
126
|
+
// browser page context, so we use a property descriptor approach instead.
|
|
127
|
+
// We use generic protocol patterns instead of product-specific names to
|
|
128
|
+
// also catch our own injected code frames without leaking identifiers.
|
|
129
|
+
try {
|
|
130
|
+
const _origDescriptor = Object.getOwnPropertyDescriptor(Error.prototype, 'stack');
|
|
131
|
+
const _cdpPatterns = [
|
|
132
|
+
'puppeteer_evaluation_script',
|
|
133
|
+
'pptr:',
|
|
134
|
+
'debugger://',
|
|
135
|
+
'__playwright',
|
|
136
|
+
'__puppeteer',
|
|
137
|
+
];
|
|
138
|
+
if (_origDescriptor && _origDescriptor.get) {
|
|
139
|
+
Object.defineProperty(Error.prototype, 'stack', {
|
|
140
|
+
get: function () {
|
|
141
|
+
const raw = _origDescriptor.get.call(this);
|
|
142
|
+
if (typeof raw !== 'string') return raw;
|
|
143
|
+
return raw.split('\\n').filter(line =>
|
|
144
|
+
!_cdpPatterns.some(p => line.includes(p))
|
|
145
|
+
).join('\\n');
|
|
146
|
+
},
|
|
147
|
+
configurable: true,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} catch {}
|
|
151
|
+
|
|
152
|
+
return 'applied';
|
|
153
|
+
})()
|
|
154
|
+
`;
|
|
155
|
+
}
|
package/dist/browser.test.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { BrowserBridge, __test__ } from './browser/index.js';
|
|
2
|
+
import { BrowserBridge, __test__, generateStealthJs } from './browser/index.js';
|
|
3
3
|
import * as daemonClient from './browser/daemon-client.js';
|
|
4
4
|
describe('browser helpers', () => {
|
|
5
5
|
it('extracts tab entries from string snapshots', () => {
|
|
@@ -102,3 +102,49 @@ describe('BrowserBridge state', () => {
|
|
|
102
102
|
await expect(mcp.connect()).rejects.toThrow('Browser Extension is not connected');
|
|
103
103
|
});
|
|
104
104
|
});
|
|
105
|
+
describe('stealth anti-detection', () => {
|
|
106
|
+
it('generates non-empty JS string', () => {
|
|
107
|
+
const js = generateStealthJs();
|
|
108
|
+
expect(typeof js).toBe('string');
|
|
109
|
+
expect(js.length).toBeGreaterThan(100);
|
|
110
|
+
});
|
|
111
|
+
it('contains all 7 anti-detection patches', () => {
|
|
112
|
+
const js = generateStealthJs();
|
|
113
|
+
// 1. webdriver
|
|
114
|
+
expect(js).toContain('navigator');
|
|
115
|
+
expect(js).toContain('webdriver');
|
|
116
|
+
// 2. chrome stub
|
|
117
|
+
expect(js).toContain('window.chrome');
|
|
118
|
+
// 3. plugins
|
|
119
|
+
expect(js).toContain('plugins');
|
|
120
|
+
expect(js).toContain('PDF Viewer');
|
|
121
|
+
// 4. languages
|
|
122
|
+
expect(js).toContain('languages');
|
|
123
|
+
// 5. permissions
|
|
124
|
+
expect(js).toContain('Permissions');
|
|
125
|
+
expect(js).toContain('notifications');
|
|
126
|
+
// 6. automation artifacts (dynamic cdc_ scan)
|
|
127
|
+
expect(js).toContain('__playwright');
|
|
128
|
+
expect(js).toContain('__puppeteer');
|
|
129
|
+
expect(js).toContain('getOwnPropertyNames');
|
|
130
|
+
expect(js).toContain('cdc_');
|
|
131
|
+
// 7. CDP stack trace cleanup
|
|
132
|
+
expect(js).toContain('Error.prototype');
|
|
133
|
+
expect(js).toContain('puppeteer_evaluation_script');
|
|
134
|
+
expect(js).toContain('getOwnPropertyDescriptor');
|
|
135
|
+
});
|
|
136
|
+
it('includes guard flag to prevent double-injection', () => {
|
|
137
|
+
const js = generateStealthJs();
|
|
138
|
+
// Guard uses a non-enumerable property on a built-in prototype
|
|
139
|
+
expect(js).toContain("EventTarget.prototype");
|
|
140
|
+
// Guard should check early and return 'skipped'
|
|
141
|
+
expect(js).toContain("return 'skipped'");
|
|
142
|
+
// Normal path returns 'applied'
|
|
143
|
+
expect(js).toContain("return 'applied'");
|
|
144
|
+
});
|
|
145
|
+
it('generates syntactically valid JS', () => {
|
|
146
|
+
const js = generateStealthJs();
|
|
147
|
+
// Should not throw when parsed
|
|
148
|
+
expect(() => new Function(js)).not.toThrow();
|
|
149
|
+
});
|
|
150
|
+
});
|
package/dist/build-manifest.js
CHANGED
|
@@ -16,9 +16,7 @@ import { getErrorMessage } from './errors.js';
|
|
|
16
16
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
17
|
const CLIS_DIR = path.resolve(__dirname, 'clis');
|
|
18
18
|
const OUTPUT = path.resolve(__dirname, '..', 'dist', 'cli-manifest.json');
|
|
19
|
-
|
|
20
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
21
|
-
}
|
|
19
|
+
import { isRecord } from './utils.js';
|
|
22
20
|
function extractBalancedBlock(source, startIndex, openChar, closeChar) {
|
|
23
21
|
let depth = 0;
|
|
24
22
|
let quote = null;
|
|
@@ -112,7 +110,9 @@ export function parseTsArgsBlock(argsBlock) {
|
|
|
112
110
|
help: helpMatch?.[1] ?? '',
|
|
113
111
|
choices: parseInlineChoices(body),
|
|
114
112
|
});
|
|
115
|
-
cursor = objectStart + body.length
|
|
113
|
+
cursor = objectStart + body.length;
|
|
114
|
+
if (cursor <= objectStart)
|
|
115
|
+
break; // safety: prevent infinite loop
|
|
116
116
|
}
|
|
117
117
|
return args;
|
|
118
118
|
}
|
|
@@ -208,10 +208,16 @@ export function scanTs(filePath, site) {
|
|
|
208
208
|
if (argsBlock) {
|
|
209
209
|
entry.args = parseTsArgsBlock(argsBlock);
|
|
210
210
|
}
|
|
211
|
-
// Extract navigateBefore: false
|
|
212
|
-
const
|
|
213
|
-
if (
|
|
214
|
-
entry.navigateBefore =
|
|
211
|
+
// Extract navigateBefore: false / true / 'https://...'
|
|
212
|
+
const navBoolMatch = src.match(/navigateBefore\s*:\s*(true|false)/);
|
|
213
|
+
if (navBoolMatch) {
|
|
214
|
+
entry.navigateBefore = navBoolMatch[1] === 'true';
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
const navStringMatch = src.match(/navigateBefore\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
218
|
+
if (navStringMatch)
|
|
219
|
+
entry.navigateBefore = navStringMatch[1];
|
|
220
|
+
}
|
|
215
221
|
return entry;
|
|
216
222
|
}
|
|
217
223
|
catch (err) {
|
|
@@ -226,7 +232,7 @@ export function scanTs(filePath, site) {
|
|
|
226
232
|
*/
|
|
227
233
|
export function shouldReplaceManifestEntry(current, next) {
|
|
228
234
|
if (current.type === next.type)
|
|
229
|
-
return
|
|
235
|
+
return false;
|
|
230
236
|
return current.type === 'yaml' && next.type === 'ts';
|
|
231
237
|
}
|
|
232
238
|
export function buildManifest() {
|
|
@@ -113,4 +113,16 @@ describe('manifest helper rules', () => {
|
|
|
113
113
|
fs.writeFileSync(file, `export function helper() { return 'noop'; }`);
|
|
114
114
|
expect(scanTs(file, 'demo')).toBeNull();
|
|
115
115
|
});
|
|
116
|
+
it('keeps literal domain and navigateBefore for TS adapters', () => {
|
|
117
|
+
const file = path.join(process.cwd(), 'src', 'clis', 'xueqiu', 'fund-holdings.ts');
|
|
118
|
+
const entry = scanTs(file, 'xueqiu');
|
|
119
|
+
expect(entry).toMatchObject({
|
|
120
|
+
site: 'xueqiu',
|
|
121
|
+
name: 'fund-holdings',
|
|
122
|
+
domain: 'danjuanfunds.com',
|
|
123
|
+
navigateBefore: 'https://danjuanfunds.com/my-money',
|
|
124
|
+
type: 'ts',
|
|
125
|
+
modulePath: 'xueqiu/fund-holdings.js',
|
|
126
|
+
});
|
|
127
|
+
});
|
|
116
128
|
});
|
package/dist/cascade.js
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* automatically finds the minimum-privilege strategy that works.
|
|
11
11
|
*/
|
|
12
12
|
import { Strategy } from './registry.js';
|
|
13
|
+
import { getErrorMessage } from './errors.js';
|
|
13
14
|
/** Strategy cascade order (simplest → most complex) */
|
|
14
15
|
const CASCADE_ORDER = [
|
|
15
16
|
Strategy.PUBLIC,
|
|
@@ -98,7 +99,7 @@ export async function probeEndpoint(page, url, strategy, _opts = {}) {
|
|
|
98
99
|
}
|
|
99
100
|
catch (err) {
|
|
100
101
|
result.success = false;
|
|
101
|
-
result.error =
|
|
102
|
+
result.error = getErrorMessage(err);
|
|
102
103
|
}
|
|
103
104
|
return result;
|
|
104
105
|
}
|
|
@@ -107,9 +108,10 @@ export async function probeEndpoint(page, url, strategy, _opts = {}) {
|
|
|
107
108
|
* Returns the simplest working strategy.
|
|
108
109
|
*/
|
|
109
110
|
export async function cascadeProbe(page, url, opts = {}) {
|
|
110
|
-
const
|
|
111
|
+
const rawIdx = opts.maxStrategy
|
|
111
112
|
? CASCADE_ORDER.indexOf(opts.maxStrategy)
|
|
112
113
|
: CASCADE_ORDER.indexOf(Strategy.HEADER); // Don't auto-try INTERCEPT/UI
|
|
114
|
+
const maxIdx = rawIdx === -1 ? CASCADE_ORDER.indexOf(Strategy.HEADER) : rawIdx;
|
|
113
115
|
const probes = [];
|
|
114
116
|
for (let i = 0; i <= Math.min(maxIdx, CASCADE_ORDER.length - 1); i++) {
|
|
115
117
|
const strategy = CASCADE_ORDER[i];
|