@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/src/browser/page.ts
CHANGED
|
@@ -14,7 +14,9 @@ import { formatSnapshot } from '../snapshotFormatter.js';
|
|
|
14
14
|
import type { BrowserCookie, IPage, ScreenshotOptions, SnapshotOptions, WaitOptions } from '../types.js';
|
|
15
15
|
import { sendCommand } from './daemon-client.js';
|
|
16
16
|
import { wrapForEval } from './utils.js';
|
|
17
|
+
import { saveBase64ToFile } from '../utils.js';
|
|
17
18
|
import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
|
|
19
|
+
import { generateStealthJs } from './stealth.js';
|
|
18
20
|
import {
|
|
19
21
|
clickJs,
|
|
20
22
|
typeTextJs,
|
|
@@ -35,33 +37,44 @@ export class Page implements IPage {
|
|
|
35
37
|
/** Active tab ID, set after navigate and used in all subsequent commands */
|
|
36
38
|
private _tabId: number | undefined;
|
|
37
39
|
|
|
38
|
-
/** Helper: spread
|
|
39
|
-
private
|
|
40
|
-
return
|
|
40
|
+
/** Helper: spread workspace into command params */
|
|
41
|
+
private _wsOpt(): { workspace: string } {
|
|
42
|
+
return { workspace: this.workspace };
|
|
41
43
|
}
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
/** Helper: spread workspace + tabId into command params */
|
|
46
|
+
private _cmdOpts(): Record<string, unknown> {
|
|
47
|
+
return {
|
|
48
|
+
workspace: this.workspace,
|
|
49
|
+
...(this._tabId !== undefined && { tabId: this._tabId }),
|
|
50
|
+
};
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
async goto(url: string, options?: { waitUntil?: 'load' | 'none'; settleMs?: number }): Promise<void> {
|
|
48
54
|
const result = await sendCommand('navigate', {
|
|
49
55
|
url,
|
|
50
|
-
...this.
|
|
51
|
-
...this._tabOpt(),
|
|
56
|
+
...this._cmdOpts(),
|
|
52
57
|
}) as { tabId?: number };
|
|
53
58
|
// Remember the tabId for subsequent exec calls
|
|
54
59
|
if (result?.tabId) {
|
|
55
60
|
this._tabId = result.tabId;
|
|
56
61
|
}
|
|
62
|
+
// Inject stealth anti-detection patches (guard flag prevents double-injection).
|
|
63
|
+
try {
|
|
64
|
+
await sendCommand('exec', {
|
|
65
|
+
code: generateStealthJs(),
|
|
66
|
+
...this._cmdOpts(),
|
|
67
|
+
});
|
|
68
|
+
} catch {
|
|
69
|
+
// Non-fatal: stealth is best-effort
|
|
70
|
+
}
|
|
57
71
|
// Smart settle: use DOM stability detection instead of fixed sleep.
|
|
58
72
|
// settleMs is now a timeout cap (default 1000ms), not a fixed wait.
|
|
59
73
|
if (options?.waitUntil !== 'none') {
|
|
60
74
|
const maxMs = options?.settleMs ?? 1000;
|
|
61
75
|
await sendCommand('exec', {
|
|
62
76
|
code: waitForDomStableJs(maxMs, Math.min(500, maxMs)),
|
|
63
|
-
...this.
|
|
64
|
-
...this._tabOpt(),
|
|
77
|
+
...this._cmdOpts(),
|
|
65
78
|
});
|
|
66
79
|
}
|
|
67
80
|
}
|
|
@@ -69,7 +82,7 @@ export class Page implements IPage {
|
|
|
69
82
|
/** Close the automation window in the extension */
|
|
70
83
|
async closeWindow(): Promise<void> {
|
|
71
84
|
try {
|
|
72
|
-
await sendCommand('close-window', { ...this.
|
|
85
|
+
await sendCommand('close-window', { ...this._wsOpt() });
|
|
73
86
|
} catch {
|
|
74
87
|
// Window may already be closed or daemon may be down
|
|
75
88
|
}
|
|
@@ -77,11 +90,11 @@ export class Page implements IPage {
|
|
|
77
90
|
|
|
78
91
|
async evaluate(js: string): Promise<unknown> {
|
|
79
92
|
const code = wrapForEval(js);
|
|
80
|
-
return sendCommand('exec', { code, ...this.
|
|
93
|
+
return sendCommand('exec', { code, ...this._cmdOpts() });
|
|
81
94
|
}
|
|
82
95
|
|
|
83
96
|
async getCookies(opts: { domain?: string; url?: string } = {}): Promise<BrowserCookie[]> {
|
|
84
|
-
const result = await sendCommand('cookies', { ...this.
|
|
97
|
+
const result = await sendCommand('cookies', { ...this._wsOpt(), ...opts });
|
|
85
98
|
return Array.isArray(result) ? result : [];
|
|
86
99
|
}
|
|
87
100
|
|
|
@@ -97,7 +110,7 @@ export class Page implements IPage {
|
|
|
97
110
|
});
|
|
98
111
|
|
|
99
112
|
try {
|
|
100
|
-
const result = await sendCommand('exec', { code: snapshotJs, ...this.
|
|
113
|
+
const result = await sendCommand('exec', { code: snapshotJs, ...this._cmdOpts() });
|
|
101
114
|
// The advanced engine already produces a clean, pruned, LLM-friendly output.
|
|
102
115
|
// Do NOT pass through formatSnapshot — its format is incompatible.
|
|
103
116
|
return result;
|
|
@@ -137,7 +150,7 @@ export class Page implements IPage {
|
|
|
137
150
|
return buildTree(document.body, 0);
|
|
138
151
|
})()
|
|
139
152
|
`;
|
|
140
|
-
const raw = await sendCommand('exec', { code, ...this.
|
|
153
|
+
const raw = await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
141
154
|
if (opts.raw) return raw;
|
|
142
155
|
if (typeof raw === 'string') return formatSnapshot(raw, opts);
|
|
143
156
|
return raw;
|
|
@@ -145,27 +158,27 @@ export class Page implements IPage {
|
|
|
145
158
|
|
|
146
159
|
async click(ref: string): Promise<void> {
|
|
147
160
|
const code = clickJs(ref);
|
|
148
|
-
await sendCommand('exec', { code, ...this.
|
|
161
|
+
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
149
162
|
}
|
|
150
163
|
|
|
151
164
|
async typeText(ref: string, text: string): Promise<void> {
|
|
152
165
|
const code = typeTextJs(ref, text);
|
|
153
|
-
await sendCommand('exec', { code, ...this.
|
|
166
|
+
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
154
167
|
}
|
|
155
168
|
|
|
156
169
|
async pressKey(key: string): Promise<void> {
|
|
157
170
|
const code = pressKeyJs(key);
|
|
158
|
-
await sendCommand('exec', { code, ...this.
|
|
171
|
+
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
159
172
|
}
|
|
160
173
|
|
|
161
174
|
async scrollTo(ref: string): Promise<unknown> {
|
|
162
175
|
const code = scrollToRefJs(ref);
|
|
163
|
-
return sendCommand('exec', { code, ...this.
|
|
176
|
+
return sendCommand('exec', { code, ...this._cmdOpts() });
|
|
164
177
|
}
|
|
165
178
|
|
|
166
179
|
async getFormState(): Promise<Record<string, unknown>> {
|
|
167
180
|
const code = getFormStateJs();
|
|
168
|
-
return (await sendCommand('exec', { code, ...this.
|
|
181
|
+
return (await sendCommand('exec', { code, ...this._cmdOpts() })) as Record<string, unknown>;
|
|
169
182
|
}
|
|
170
183
|
|
|
171
184
|
async wait(options: number | WaitOptions): Promise<void> {
|
|
@@ -173,42 +186,42 @@ export class Page implements IPage {
|
|
|
173
186
|
await new Promise(resolve => setTimeout(resolve, options * 1000));
|
|
174
187
|
return;
|
|
175
188
|
}
|
|
176
|
-
if (options.time) {
|
|
189
|
+
if (typeof options.time === 'number') {
|
|
177
190
|
await new Promise(resolve => setTimeout(resolve, options.time! * 1000));
|
|
178
191
|
return;
|
|
179
192
|
}
|
|
180
193
|
if (options.text) {
|
|
181
194
|
const timeout = (options.timeout ?? 30) * 1000;
|
|
182
195
|
const code = waitForTextJs(options.text, timeout);
|
|
183
|
-
await sendCommand('exec', { code, ...this.
|
|
196
|
+
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
184
197
|
}
|
|
185
198
|
}
|
|
186
199
|
|
|
187
200
|
async tabs(): Promise<unknown[]> {
|
|
188
|
-
const result = await sendCommand('tabs', { op: 'list', ...this.
|
|
201
|
+
const result = await sendCommand('tabs', { op: 'list', ...this._wsOpt() });
|
|
189
202
|
return Array.isArray(result) ? result : [];
|
|
190
203
|
}
|
|
191
204
|
|
|
192
205
|
async closeTab(index?: number): Promise<void> {
|
|
193
|
-
await sendCommand('tabs', { op: 'close', ...this.
|
|
206
|
+
await sendCommand('tabs', { op: 'close', ...this._wsOpt(), ...(index !== undefined ? { index } : {}) });
|
|
194
207
|
// Invalidate cached tabId — the closed tab might have been our active one.
|
|
195
208
|
// We can't know for sure (close-by-index doesn't return tabId), so reset.
|
|
196
209
|
this._tabId = undefined;
|
|
197
210
|
}
|
|
198
211
|
|
|
199
212
|
async newTab(): Promise<void> {
|
|
200
|
-
const result = await sendCommand('tabs', { op: 'new', ...this.
|
|
213
|
+
const result = await sendCommand('tabs', { op: 'new', ...this._wsOpt() }) as { tabId?: number };
|
|
201
214
|
if (result?.tabId) this._tabId = result.tabId;
|
|
202
215
|
}
|
|
203
216
|
|
|
204
217
|
async selectTab(index: number): Promise<void> {
|
|
205
|
-
const result = await sendCommand('tabs', { op: 'select', index, ...this.
|
|
218
|
+
const result = await sendCommand('tabs', { op: 'select', index, ...this._wsOpt() }) as { selected?: number };
|
|
206
219
|
if (result?.selected) this._tabId = result.selected;
|
|
207
220
|
}
|
|
208
221
|
|
|
209
222
|
async networkRequests(includeStatic: boolean = false): Promise<unknown[]> {
|
|
210
223
|
const code = networkRequestsJs(includeStatic);
|
|
211
|
-
const result = await sendCommand('exec', { code, ...this.
|
|
224
|
+
const result = await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
212
225
|
return Array.isArray(result) ? result : [];
|
|
213
226
|
}
|
|
214
227
|
|
|
@@ -230,19 +243,14 @@ export class Page implements IPage {
|
|
|
230
243
|
*/
|
|
231
244
|
async screenshot(options: ScreenshotOptions = {}): Promise<string> {
|
|
232
245
|
const base64 = await sendCommand('screenshot', {
|
|
233
|
-
...this.
|
|
246
|
+
...this._cmdOpts(),
|
|
234
247
|
format: options.format,
|
|
235
248
|
quality: options.quality,
|
|
236
249
|
fullPage: options.fullPage,
|
|
237
|
-
...this._tabOpt(),
|
|
238
250
|
}) as string;
|
|
239
251
|
|
|
240
252
|
if (options.path) {
|
|
241
|
-
|
|
242
|
-
const path = await import('node:path');
|
|
243
|
-
const dir = path.dirname(options.path);
|
|
244
|
-
await fs.promises.mkdir(dir, { recursive: true });
|
|
245
|
-
await fs.promises.writeFile(options.path, Buffer.from(base64, 'base64'));
|
|
253
|
+
await saveBase64ToFile(base64, options.path);
|
|
246
254
|
}
|
|
247
255
|
|
|
248
256
|
return base64;
|
|
@@ -250,14 +258,14 @@ export class Page implements IPage {
|
|
|
250
258
|
|
|
251
259
|
async scroll(direction: string = 'down', amount: number = 500): Promise<void> {
|
|
252
260
|
const code = scrollJs(direction, amount);
|
|
253
|
-
await sendCommand('exec', { code, ...this.
|
|
261
|
+
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
254
262
|
}
|
|
255
263
|
|
|
256
264
|
async autoScroll(options: { times?: number; delayMs?: number } = {}): Promise<void> {
|
|
257
265
|
const times = options.times ?? 3;
|
|
258
266
|
const delayMs = options.delayMs ?? 2000;
|
|
259
267
|
const code = autoScrollJs(times, delayMs);
|
|
260
|
-
await sendCommand('exec', { code, ...this.
|
|
268
|
+
await sendCommand('exec', { code, ...this._cmdOpts() });
|
|
261
269
|
}
|
|
262
270
|
|
|
263
271
|
async installInterceptor(pattern: string): Promise<void> {
|
|
@@ -0,0 +1,156 @@
|
|
|
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
|
+
/**
|
|
13
|
+
* Return a self-contained JS string that, when evaluated in a page context,
|
|
14
|
+
* applies all stealth patches. Safe to call multiple times — the guard flag
|
|
15
|
+
* ensures patches are applied only once.
|
|
16
|
+
*/
|
|
17
|
+
export function generateStealthJs(): string {
|
|
18
|
+
return `
|
|
19
|
+
(() => {
|
|
20
|
+
// Guard: prevent double-injection across separate CDP evaluations.
|
|
21
|
+
// We cannot use a closure variable (each eval is a fresh scope), and
|
|
22
|
+
// window properties / Symbols are discoverable by anti-bot scripts.
|
|
23
|
+
// Instead, stash the flag in a non-enumerable getter on a built-in
|
|
24
|
+
// prototype that fingerprinters are unlikely to scan.
|
|
25
|
+
const _gProto = EventTarget.prototype;
|
|
26
|
+
const _gKey = '__lsn'; // looks like an internal listener cache
|
|
27
|
+
if (_gProto[_gKey]) return 'skipped';
|
|
28
|
+
try {
|
|
29
|
+
Object.defineProperty(_gProto, _gKey, { value: true, enumerable: false, configurable: true });
|
|
30
|
+
} catch {}
|
|
31
|
+
|
|
32
|
+
// 1. navigator.webdriver → false
|
|
33
|
+
// Most common check; Playwright/Puppeteer/CDP set this to true.
|
|
34
|
+
// Real Chrome returns false (not undefined) — returning undefined is
|
|
35
|
+
// itself a detection signal for advanced fingerprinters.
|
|
36
|
+
try {
|
|
37
|
+
Object.defineProperty(navigator, 'webdriver', {
|
|
38
|
+
get: () => false,
|
|
39
|
+
configurable: true,
|
|
40
|
+
});
|
|
41
|
+
} catch {}
|
|
42
|
+
|
|
43
|
+
// 2. window.chrome stub
|
|
44
|
+
// Real Chrome exposes window.chrome with runtime, loadTimes, csi.
|
|
45
|
+
// Headless/automated Chrome may not have it.
|
|
46
|
+
try {
|
|
47
|
+
if (!window.chrome) {
|
|
48
|
+
window.chrome = {
|
|
49
|
+
runtime: {
|
|
50
|
+
onConnect: { addListener: () => {}, removeListener: () => {} },
|
|
51
|
+
onMessage: { addListener: () => {}, removeListener: () => {} },
|
|
52
|
+
},
|
|
53
|
+
loadTimes: () => ({}),
|
|
54
|
+
csi: () => ({}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
} catch {}
|
|
58
|
+
|
|
59
|
+
// 3. navigator.plugins — fake population only if empty
|
|
60
|
+
// Real user browser already has plugins; only patch in automated/headless
|
|
61
|
+
// contexts where the list is empty (overwriting real plugins with fakes
|
|
62
|
+
// would be counterproductive and detectable).
|
|
63
|
+
try {
|
|
64
|
+
if (!navigator.plugins || navigator.plugins.length === 0) {
|
|
65
|
+
const fakePlugins = [
|
|
66
|
+
{ name: 'PDF Viewer', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
|
|
67
|
+
{ name: 'Chrome PDF Viewer', filename: 'internal-pdf-viewer', description: '' },
|
|
68
|
+
{ name: 'Chromium PDF Viewer', filename: 'internal-pdf-viewer', description: '' },
|
|
69
|
+
{ name: 'Microsoft Edge PDF Viewer', filename: 'internal-pdf-viewer', description: '' },
|
|
70
|
+
{ name: 'WebKit built-in PDF', filename: 'internal-pdf-viewer', description: '' },
|
|
71
|
+
];
|
|
72
|
+
fakePlugins.item = (i) => fakePlugins[i] || null;
|
|
73
|
+
fakePlugins.namedItem = (n) => fakePlugins.find(p => p.name === n) || null;
|
|
74
|
+
fakePlugins.refresh = () => {};
|
|
75
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
76
|
+
get: () => fakePlugins,
|
|
77
|
+
configurable: true,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
} catch {}
|
|
81
|
+
|
|
82
|
+
// 4. navigator.languages — guarantee non-empty
|
|
83
|
+
// Some automated contexts return undefined or empty array.
|
|
84
|
+
try {
|
|
85
|
+
if (!navigator.languages || navigator.languages.length === 0) {
|
|
86
|
+
Object.defineProperty(navigator, 'languages', {
|
|
87
|
+
get: () => ['en-US', 'en'],
|
|
88
|
+
configurable: true,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
} catch {}
|
|
92
|
+
|
|
93
|
+
// 5. Permissions.query — normalize notification permission
|
|
94
|
+
// Headless Chrome throws on Permissions.query({ name: 'notifications' }).
|
|
95
|
+
try {
|
|
96
|
+
const origQuery = window.Permissions?.prototype?.query;
|
|
97
|
+
if (origQuery) {
|
|
98
|
+
window.Permissions.prototype.query = function (parameters) {
|
|
99
|
+
if (parameters?.name === 'notifications') {
|
|
100
|
+
return Promise.resolve({ state: Notification.permission, onchange: null });
|
|
101
|
+
}
|
|
102
|
+
return origQuery.call(this, parameters);
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
} catch {}
|
|
106
|
+
|
|
107
|
+
// 6. Clean automation artifacts
|
|
108
|
+
// Remove properties left by Playwright, Puppeteer, or CDP injection.
|
|
109
|
+
try {
|
|
110
|
+
delete window.__playwright;
|
|
111
|
+
delete window.__puppeteer;
|
|
112
|
+
// ChromeDriver injects cdc_ prefixed globals; the suffix varies by version,
|
|
113
|
+
// so scan window for any matching property rather than hardcoding names.
|
|
114
|
+
for (const prop of Object.getOwnPropertyNames(window)) {
|
|
115
|
+
if (prop.startsWith('cdc_') || prop.startsWith('__cdc_')) {
|
|
116
|
+
try { delete window[prop]; } catch {}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
} catch {}
|
|
120
|
+
|
|
121
|
+
// 7. CDP stack trace cleanup
|
|
122
|
+
// Runtime.evaluate injects scripts whose source URLs appear in Error
|
|
123
|
+
// stack traces (e.g. __puppeteer_evaluation_script__, pptr:, debugger://).
|
|
124
|
+
// Websites detect automation by doing: new Error().stack and inspecting it.
|
|
125
|
+
// We override the stack property getter on Error.prototype to filter them.
|
|
126
|
+
// Note: Error.prepareStackTrace is V8/Node-only and not available in
|
|
127
|
+
// browser page context, so we use a property descriptor approach instead.
|
|
128
|
+
// We use generic protocol patterns instead of product-specific names to
|
|
129
|
+
// also catch our own injected code frames without leaking identifiers.
|
|
130
|
+
try {
|
|
131
|
+
const _origDescriptor = Object.getOwnPropertyDescriptor(Error.prototype, 'stack');
|
|
132
|
+
const _cdpPatterns = [
|
|
133
|
+
'puppeteer_evaluation_script',
|
|
134
|
+
'pptr:',
|
|
135
|
+
'debugger://',
|
|
136
|
+
'__playwright',
|
|
137
|
+
'__puppeteer',
|
|
138
|
+
];
|
|
139
|
+
if (_origDescriptor && _origDescriptor.get) {
|
|
140
|
+
Object.defineProperty(Error.prototype, 'stack', {
|
|
141
|
+
get: function () {
|
|
142
|
+
const raw = _origDescriptor.get.call(this);
|
|
143
|
+
if (typeof raw !== 'string') return raw;
|
|
144
|
+
return raw.split('\\n').filter(line =>
|
|
145
|
+
!_cdpPatterns.some(p => line.includes(p))
|
|
146
|
+
).join('\\n');
|
|
147
|
+
},
|
|
148
|
+
configurable: true,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
} catch {}
|
|
152
|
+
|
|
153
|
+
return 'applied';
|
|
154
|
+
})()
|
|
155
|
+
`;
|
|
156
|
+
}
|
package/src/browser.test.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { afterEach, 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
|
|
|
5
5
|
describe('browser helpers', () => {
|
|
@@ -133,3 +133,53 @@ describe('BrowserBridge state', () => {
|
|
|
133
133
|
await expect(mcp.connect()).rejects.toThrow('Browser Extension is not connected');
|
|
134
134
|
});
|
|
135
135
|
});
|
|
136
|
+
|
|
137
|
+
describe('stealth anti-detection', () => {
|
|
138
|
+
it('generates non-empty JS string', () => {
|
|
139
|
+
const js = generateStealthJs();
|
|
140
|
+
expect(typeof js).toBe('string');
|
|
141
|
+
expect(js.length).toBeGreaterThan(100);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('contains all 7 anti-detection patches', () => {
|
|
145
|
+
const js = generateStealthJs();
|
|
146
|
+
// 1. webdriver
|
|
147
|
+
expect(js).toContain('navigator');
|
|
148
|
+
expect(js).toContain('webdriver');
|
|
149
|
+
// 2. chrome stub
|
|
150
|
+
expect(js).toContain('window.chrome');
|
|
151
|
+
// 3. plugins
|
|
152
|
+
expect(js).toContain('plugins');
|
|
153
|
+
expect(js).toContain('PDF Viewer');
|
|
154
|
+
// 4. languages
|
|
155
|
+
expect(js).toContain('languages');
|
|
156
|
+
// 5. permissions
|
|
157
|
+
expect(js).toContain('Permissions');
|
|
158
|
+
expect(js).toContain('notifications');
|
|
159
|
+
// 6. automation artifacts (dynamic cdc_ scan)
|
|
160
|
+
expect(js).toContain('__playwright');
|
|
161
|
+
expect(js).toContain('__puppeteer');
|
|
162
|
+
expect(js).toContain('getOwnPropertyNames');
|
|
163
|
+
expect(js).toContain('cdc_');
|
|
164
|
+
// 7. CDP stack trace cleanup
|
|
165
|
+
expect(js).toContain('Error.prototype');
|
|
166
|
+
expect(js).toContain('puppeteer_evaluation_script');
|
|
167
|
+
expect(js).toContain('getOwnPropertyDescriptor');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('includes guard flag to prevent double-injection', () => {
|
|
171
|
+
const js = generateStealthJs();
|
|
172
|
+
// Guard uses a non-enumerable property on a built-in prototype
|
|
173
|
+
expect(js).toContain("EventTarget.prototype");
|
|
174
|
+
// Guard should check early and return 'skipped'
|
|
175
|
+
expect(js).toContain("return 'skipped'");
|
|
176
|
+
// Normal path returns 'applied'
|
|
177
|
+
expect(js).toContain("return 'applied'");
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('generates syntactically valid JS', () => {
|
|
181
|
+
const js = generateStealthJs();
|
|
182
|
+
// Should not throw when parsed
|
|
183
|
+
expect(() => new Function(js)).not.toThrow();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
@@ -129,4 +129,18 @@ describe('manifest helper rules', () => {
|
|
|
129
129
|
|
|
130
130
|
expect(scanTs(file, 'demo')).toBeNull();
|
|
131
131
|
});
|
|
132
|
+
|
|
133
|
+
it('keeps literal domain and navigateBefore for TS adapters', () => {
|
|
134
|
+
const file = path.join(process.cwd(), 'src', 'clis', 'xueqiu', 'fund-holdings.ts');
|
|
135
|
+
const entry = scanTs(file, 'xueqiu');
|
|
136
|
+
|
|
137
|
+
expect(entry).toMatchObject({
|
|
138
|
+
site: 'xueqiu',
|
|
139
|
+
name: 'fund-holdings',
|
|
140
|
+
domain: 'danjuanfunds.com',
|
|
141
|
+
navigateBefore: 'https://danjuanfunds.com/my-money',
|
|
142
|
+
type: 'ts',
|
|
143
|
+
modulePath: 'xueqiu/fund-holdings.js',
|
|
144
|
+
});
|
|
145
|
+
});
|
|
132
146
|
});
|
package/src/build-manifest.ts
CHANGED
|
@@ -46,34 +46,9 @@ export interface ManifestEntry {
|
|
|
46
46
|
navigateBefore?: boolean | string;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
type?: string;
|
|
51
|
-
default?: unknown;
|
|
52
|
-
required?: boolean;
|
|
53
|
-
positional?: boolean;
|
|
54
|
-
description?: string;
|
|
55
|
-
help?: string;
|
|
56
|
-
choices?: string[];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
interface YamlCliDefinition {
|
|
60
|
-
site?: string;
|
|
61
|
-
name?: string;
|
|
62
|
-
description?: string;
|
|
63
|
-
domain?: string;
|
|
64
|
-
strategy?: string;
|
|
65
|
-
browser?: boolean;
|
|
66
|
-
args?: Record<string, YamlArgDefinition>;
|
|
67
|
-
columns?: string[];
|
|
68
|
-
pipeline?: Record<string, unknown>[];
|
|
69
|
-
timeout?: number;
|
|
70
|
-
navigateBefore?: boolean | string;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
74
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
75
|
-
}
|
|
49
|
+
import type { YamlCliDefinition } from './yaml-schema.js';
|
|
76
50
|
|
|
51
|
+
import { isRecord } from './utils.js';
|
|
77
52
|
|
|
78
53
|
|
|
79
54
|
function extractBalancedBlock(
|
|
@@ -180,7 +155,8 @@ export function parseTsArgsBlock(argsBlock: string): ManifestEntry['args'] {
|
|
|
180
155
|
choices: parseInlineChoices(body),
|
|
181
156
|
});
|
|
182
157
|
|
|
183
|
-
cursor = objectStart + body.length
|
|
158
|
+
cursor = objectStart + body.length;
|
|
159
|
+
if (cursor <= objectStart) break; // safety: prevent infinite loop
|
|
184
160
|
}
|
|
185
161
|
|
|
186
162
|
return args;
|
|
@@ -284,9 +260,14 @@ export function scanTs(filePath: string, site: string): ManifestEntry | null {
|
|
|
284
260
|
entry.args = parseTsArgsBlock(argsBlock);
|
|
285
261
|
}
|
|
286
262
|
|
|
287
|
-
// Extract navigateBefore: false
|
|
288
|
-
const
|
|
289
|
-
if (
|
|
263
|
+
// Extract navigateBefore: false / true / 'https://...'
|
|
264
|
+
const navBoolMatch = src.match(/navigateBefore\s*:\s*(true|false)/);
|
|
265
|
+
if (navBoolMatch) {
|
|
266
|
+
entry.navigateBefore = navBoolMatch[1] === 'true';
|
|
267
|
+
} else {
|
|
268
|
+
const navStringMatch = src.match(/navigateBefore\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
269
|
+
if (navStringMatch) entry.navigateBefore = navStringMatch[1];
|
|
270
|
+
}
|
|
290
271
|
|
|
291
272
|
return entry;
|
|
292
273
|
} catch (err) {
|
|
@@ -301,7 +282,7 @@ export function scanTs(filePath: string, site: string): ManifestEntry | null {
|
|
|
301
282
|
* prefer the TS version (it self-registers and typically has richer logic).
|
|
302
283
|
*/
|
|
303
284
|
export function shouldReplaceManifestEntry(current: ManifestEntry, next: ManifestEntry): boolean {
|
|
304
|
-
if (current.type === next.type) return
|
|
285
|
+
if (current.type === next.type) return false;
|
|
305
286
|
return current.type === 'yaml' && next.type === 'ts';
|
|
306
287
|
}
|
|
307
288
|
|
package/src/cascade.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { Strategy } from './registry.js';
|
|
14
14
|
import type { IPage } from './types.js';
|
|
15
|
+
import { getErrorMessage } from './errors.js';
|
|
15
16
|
|
|
16
17
|
/** Strategy cascade order (simplest → most complex) */
|
|
17
18
|
const CASCADE_ORDER: Strategy[] = [
|
|
@@ -128,9 +129,9 @@ export async function probeEndpoint(
|
|
|
128
129
|
result.error = `Strategy ${strategy} requires site-specific implementation`;
|
|
129
130
|
break;
|
|
130
131
|
}
|
|
131
|
-
} catch (err
|
|
132
|
+
} catch (err) {
|
|
132
133
|
result.success = false;
|
|
133
|
-
result.error =
|
|
134
|
+
result.error = getErrorMessage(err);
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
return result;
|
|
@@ -145,9 +146,10 @@ export async function cascadeProbe(
|
|
|
145
146
|
url: string,
|
|
146
147
|
opts: { maxStrategy?: Strategy; timeout?: number } = {},
|
|
147
148
|
): Promise<CascadeResult> {
|
|
148
|
-
const
|
|
149
|
+
const rawIdx = opts.maxStrategy
|
|
149
150
|
? CASCADE_ORDER.indexOf(opts.maxStrategy)
|
|
150
151
|
: CASCADE_ORDER.indexOf(Strategy.HEADER); // Don't auto-try INTERCEPT/UI
|
|
152
|
+
const maxIdx = rawIdx === -1 ? CASCADE_ORDER.indexOf(Strategy.HEADER) : rawIdx;
|
|
151
153
|
|
|
152
154
|
const probes: ProbeResult[] = [];
|
|
153
155
|
|