@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/cdp.ts
CHANGED
|
@@ -9,9 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { WebSocket, type RawData } from 'ws';
|
|
12
|
+
import { request as httpRequest } from 'node:http';
|
|
13
|
+
import { request as httpsRequest } from 'node:https';
|
|
12
14
|
import type { BrowserCookie, IPage, ScreenshotOptions, SnapshotOptions, WaitOptions } from '../types.js';
|
|
13
15
|
import { wrapForEval } from './utils.js';
|
|
14
16
|
import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
|
|
17
|
+
import { generateStealthJs } from './stealth.js';
|
|
15
18
|
import {
|
|
16
19
|
clickJs,
|
|
17
20
|
typeTextJs,
|
|
@@ -22,6 +25,7 @@ import {
|
|
|
22
25
|
networkRequestsJs,
|
|
23
26
|
waitForDomStableJs,
|
|
24
27
|
} from './dom-helpers.js';
|
|
28
|
+
import { isRecord, saveBase64ToFile } from '../utils.js';
|
|
25
29
|
|
|
26
30
|
export interface CDPTarget {
|
|
27
31
|
type?: string;
|
|
@@ -41,7 +45,7 @@ interface RuntimeEvaluateResult {
|
|
|
41
45
|
};
|
|
42
46
|
}
|
|
43
47
|
|
|
44
|
-
const CDP_SEND_TIMEOUT = 30_000;
|
|
48
|
+
const CDP_SEND_TIMEOUT = 30_000;
|
|
45
49
|
|
|
46
50
|
export class CDPBridge {
|
|
47
51
|
private _ws: WebSocket | null = null;
|
|
@@ -50,15 +54,14 @@ export class CDPBridge {
|
|
|
50
54
|
private _eventListeners = new Map<string, Set<(params: unknown) => void>>();
|
|
51
55
|
|
|
52
56
|
async connect(opts?: { timeout?: number; workspace?: string }): Promise<IPage> {
|
|
57
|
+
if (this._ws) throw new Error('CDPBridge is already connected. Call close() before reconnecting.');
|
|
58
|
+
|
|
53
59
|
const endpoint = process.env.OPENCLI_CDP_ENDPOINT;
|
|
54
60
|
if (!endpoint) throw new Error('OPENCLI_CDP_ENDPOINT is not set');
|
|
55
61
|
|
|
56
|
-
// If it's a direct ws:// URL, use it. Otherwise, fetch the /json endpoint to find a page.
|
|
57
62
|
let wsUrl = endpoint;
|
|
58
63
|
if (endpoint.startsWith('http')) {
|
|
59
|
-
const
|
|
60
|
-
if (!res.ok) throw new Error(`Failed to fetch CDP targets: ${res.statusText}`);
|
|
61
|
-
const targets = await res.json() as CDPTarget[];
|
|
64
|
+
const targets = await fetchJsonDirect(`${endpoint.replace(/\/$/, '')}/json`) as CDPTarget[];
|
|
62
65
|
const target = selectCDPTarget(targets);
|
|
63
66
|
if (!target || !target.webSocketDebuggerUrl) {
|
|
64
67
|
throw new Error('No inspectable targets found at CDP endpoint');
|
|
@@ -68,12 +71,16 @@ export class CDPBridge {
|
|
|
68
71
|
|
|
69
72
|
return new Promise((resolve, reject) => {
|
|
70
73
|
const ws = new WebSocket(wsUrl);
|
|
71
|
-
const timeoutMs = (opts?.timeout ?? 10) * 1000;
|
|
74
|
+
const timeoutMs = (opts?.timeout ?? 10) * 1000;
|
|
72
75
|
const timeout = setTimeout(() => reject(new Error('CDP connect timeout')), timeoutMs);
|
|
73
76
|
|
|
74
|
-
ws.on('open', () => {
|
|
77
|
+
ws.on('open', async () => {
|
|
75
78
|
clearTimeout(timeout);
|
|
76
79
|
this._ws = ws;
|
|
80
|
+
try {
|
|
81
|
+
await this.send('Page.enable');
|
|
82
|
+
await this.send('Page.addScriptToEvaluateOnNewDocument', { source: generateStealthJs() });
|
|
83
|
+
} catch {}
|
|
77
84
|
resolve(new CDPPage(this));
|
|
78
85
|
});
|
|
79
86
|
|
|
@@ -85,7 +92,6 @@ export class CDPBridge {
|
|
|
85
92
|
ws.on('message', (data: RawData) => {
|
|
86
93
|
try {
|
|
87
94
|
const msg = JSON.parse(data.toString());
|
|
88
|
-
// Handle command responses
|
|
89
95
|
if (msg.id && this._pending.has(msg.id)) {
|
|
90
96
|
const entry = this._pending.get(msg.id)!;
|
|
91
97
|
clearTimeout(entry.timer);
|
|
@@ -96,16 +102,13 @@ export class CDPBridge {
|
|
|
96
102
|
entry.resolve(msg.result);
|
|
97
103
|
}
|
|
98
104
|
}
|
|
99
|
-
// Handle CDP events
|
|
100
105
|
if (msg.method) {
|
|
101
106
|
const listeners = this._eventListeners.get(msg.method);
|
|
102
107
|
if (listeners) {
|
|
103
108
|
for (const fn of listeners) fn(msg.params);
|
|
104
109
|
}
|
|
105
110
|
}
|
|
106
|
-
} catch {
|
|
107
|
-
// ignore parsing errors
|
|
108
|
-
}
|
|
111
|
+
} catch {}
|
|
109
112
|
});
|
|
110
113
|
});
|
|
111
114
|
}
|
|
@@ -123,7 +126,6 @@ export class CDPBridge {
|
|
|
123
126
|
this._eventListeners.clear();
|
|
124
127
|
}
|
|
125
128
|
|
|
126
|
-
/** Send a CDP command with timeout guard (P0 fix #4) */
|
|
127
129
|
async send(method: string, params: Record<string, unknown> = {}, timeoutMs: number = CDP_SEND_TIMEOUT): Promise<unknown> {
|
|
128
130
|
if (!this._ws || this._ws.readyState !== WebSocket.OPEN) {
|
|
129
131
|
throw new Error('CDP connection is not open');
|
|
@@ -139,19 +141,19 @@ export class CDPBridge {
|
|
|
139
141
|
});
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
/** Listen for a CDP event */
|
|
143
144
|
on(event: string, handler: (params: unknown) => void): void {
|
|
144
145
|
let set = this._eventListeners.get(event);
|
|
145
|
-
if (!set) {
|
|
146
|
+
if (!set) {
|
|
147
|
+
set = new Set();
|
|
148
|
+
this._eventListeners.set(event, set);
|
|
149
|
+
}
|
|
146
150
|
set.add(handler);
|
|
147
151
|
}
|
|
148
152
|
|
|
149
|
-
/** Remove a CDP event listener */
|
|
150
153
|
off(event: string, handler: (params: unknown) => void): void {
|
|
151
154
|
this._eventListeners.get(event)?.delete(handler);
|
|
152
155
|
}
|
|
153
156
|
|
|
154
|
-
/** Wait for a CDP event to fire (one-shot) */
|
|
155
157
|
waitForEvent(event: string, timeoutMs: number = 15_000): Promise<unknown> {
|
|
156
158
|
return new Promise((resolve, reject) => {
|
|
157
159
|
const timer = setTimeout(() => {
|
|
@@ -169,17 +171,17 @@ export class CDPBridge {
|
|
|
169
171
|
}
|
|
170
172
|
|
|
171
173
|
class CDPPage implements IPage {
|
|
174
|
+
private _pageEnabled = false;
|
|
172
175
|
constructor(private bridge: CDPBridge) {}
|
|
173
176
|
|
|
174
|
-
/** Navigate with proper load event waiting (P1 fix #3) */
|
|
175
177
|
async goto(url: string, options?: { waitUntil?: 'load' | 'none'; settleMs?: number }): Promise<void> {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
.
|
|
178
|
+
if (!this._pageEnabled) {
|
|
179
|
+
await this.bridge.send('Page.enable');
|
|
180
|
+
this._pageEnabled = true;
|
|
181
|
+
}
|
|
182
|
+
const loadPromise = this.bridge.waitForEvent('Page.loadEventFired', 30_000).catch(() => {});
|
|
179
183
|
await this.bridge.send('Page.navigate', { url });
|
|
180
184
|
await loadPromise;
|
|
181
|
-
// Smart settle: use DOM stability detection instead of fixed sleep.
|
|
182
|
-
// settleMs is now a timeout cap (default 1000ms), not a fixed wait.
|
|
183
185
|
if (options?.waitUntil !== 'none') {
|
|
184
186
|
const maxMs = options?.settleMs ?? 1000;
|
|
185
187
|
await this.evaluate(waitForDomStableJs(maxMs, Math.min(500, maxMs)));
|
|
@@ -191,7 +193,7 @@ class CDPPage implements IPage {
|
|
|
191
193
|
const result = await this.bridge.send('Runtime.evaluate', {
|
|
192
194
|
expression,
|
|
193
195
|
returnByValue: true,
|
|
194
|
-
awaitPromise: true
|
|
196
|
+
awaitPromise: true,
|
|
195
197
|
}) as RuntimeEvaluateResult;
|
|
196
198
|
if (result.exceptionDetails) {
|
|
197
199
|
throw new Error('Evaluate error: ' + (result.exceptionDetails.exception?.description || 'Unknown exception'));
|
|
@@ -204,7 +206,7 @@ class CDPPage implements IPage {
|
|
|
204
206
|
const cookies = isRecord(result) && Array.isArray(result.cookies) ? result.cookies : [];
|
|
205
207
|
const domain = opts.domain;
|
|
206
208
|
return domain
|
|
207
|
-
? cookies.filter((cookie): cookie is BrowserCookie => isCookie(cookie) && cookie.domain
|
|
209
|
+
? cookies.filter((cookie): cookie is BrowserCookie => isCookie(cookie) && matchesCookieDomain(cookie.domain, domain))
|
|
208
210
|
: cookies;
|
|
209
211
|
}
|
|
210
212
|
|
|
@@ -220,8 +222,6 @@ class CDPPage implements IPage {
|
|
|
220
222
|
return this.evaluate(snapshotJs);
|
|
221
223
|
}
|
|
222
224
|
|
|
223
|
-
// ── Shared DOM operations (P1 fix #5 — using dom-helpers.ts) ──
|
|
224
|
-
|
|
225
225
|
async click(ref: string): Promise<void> {
|
|
226
226
|
await this.evaluate(clickJs(ref));
|
|
227
227
|
}
|
|
@@ -244,12 +244,12 @@ class CDPPage implements IPage {
|
|
|
244
244
|
|
|
245
245
|
async wait(options: number | WaitOptions): Promise<void> {
|
|
246
246
|
if (typeof options === 'number') {
|
|
247
|
-
await new Promise(resolve => setTimeout(resolve, options * 1000));
|
|
247
|
+
await new Promise((resolve) => setTimeout(resolve, options * 1000));
|
|
248
248
|
return;
|
|
249
249
|
}
|
|
250
250
|
if (typeof options.time === 'number') {
|
|
251
251
|
const waitTime = options.time;
|
|
252
|
-
await new Promise(resolve => setTimeout(resolve, waitTime * 1000));
|
|
252
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime * 1000));
|
|
253
253
|
return;
|
|
254
254
|
}
|
|
255
255
|
if (options.text) {
|
|
@@ -258,8 +258,6 @@ class CDPPage implements IPage {
|
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
// ── Implemented methods (P1 fix #2) ──
|
|
262
|
-
|
|
263
261
|
async scroll(direction: string = 'down', amount: number = 500): Promise<void> {
|
|
264
262
|
await this.evaluate(scrollJs(direction, amount));
|
|
265
263
|
}
|
|
@@ -278,11 +276,7 @@ class CDPPage implements IPage {
|
|
|
278
276
|
});
|
|
279
277
|
const base64 = isRecord(result) && typeof result.data === 'string' ? result.data : '';
|
|
280
278
|
if (options.path) {
|
|
281
|
-
|
|
282
|
-
const path = await import('node:path');
|
|
283
|
-
const dir = path.dirname(options.path);
|
|
284
|
-
await fs.promises.mkdir(dir, { recursive: true });
|
|
285
|
-
await fs.promises.writeFile(options.path, Buffer.from(base64, 'base64'));
|
|
279
|
+
await saveBase64ToFile(base64, options.path);
|
|
286
280
|
}
|
|
287
281
|
return base64;
|
|
288
282
|
}
|
|
@@ -327,10 +321,6 @@ class CDPPage implements IPage {
|
|
|
327
321
|
}
|
|
328
322
|
}
|
|
329
323
|
|
|
330
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
331
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
324
|
function isCookie(value: unknown): value is BrowserCookie {
|
|
335
325
|
return isRecord(value)
|
|
336
326
|
&& typeof value.name === 'string'
|
|
@@ -338,7 +328,12 @@ function isCookie(value: unknown): value is BrowserCookie {
|
|
|
338
328
|
&& typeof value.domain === 'string';
|
|
339
329
|
}
|
|
340
330
|
|
|
341
|
-
|
|
331
|
+
function matchesCookieDomain(cookieDomain: string, targetDomain: string): boolean {
|
|
332
|
+
const normalizedCookieDomain = cookieDomain.replace(/^\./, '').toLowerCase();
|
|
333
|
+
const normalizedTargetDomain = targetDomain.replace(/^\./, '').toLowerCase();
|
|
334
|
+
return normalizedTargetDomain === normalizedCookieDomain
|
|
335
|
+
|| normalizedTargetDomain.endsWith(`.${normalizedCookieDomain}`);
|
|
336
|
+
}
|
|
342
337
|
|
|
343
338
|
function selectCDPTarget(targets: CDPTarget[]): CDPTarget | undefined {
|
|
344
339
|
const preferredPattern = compilePreferredPattern(process.env.OPENCLI_CDP_TARGET);
|
|
@@ -412,3 +407,31 @@ export const __test__ = {
|
|
|
412
407
|
selectCDPTarget,
|
|
413
408
|
scoreCDPTarget,
|
|
414
409
|
};
|
|
410
|
+
|
|
411
|
+
function fetchJsonDirect(url: string): Promise<unknown> {
|
|
412
|
+
return new Promise((resolve, reject) => {
|
|
413
|
+
const parsed = new URL(url);
|
|
414
|
+
const request = (parsed.protocol === 'https:' ? httpsRequest : httpRequest)(parsed, (res) => {
|
|
415
|
+
const statusCode = res.statusCode ?? 0;
|
|
416
|
+
if (statusCode < 200 || statusCode >= 300) {
|
|
417
|
+
res.resume();
|
|
418
|
+
reject(new Error(`Failed to fetch CDP targets: HTTP ${statusCode}`));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const chunks: Buffer[] = [];
|
|
423
|
+
res.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
|
424
|
+
res.on('end', () => {
|
|
425
|
+
try {
|
|
426
|
+
resolve(JSON.parse(Buffer.concat(chunks).toString('utf8')));
|
|
427
|
+
} catch (error) {
|
|
428
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
request.on('error', reject);
|
|
434
|
+
request.setTimeout(10_000, () => request.destroy(new Error('Timed out fetching CDP targets')));
|
|
435
|
+
request.end();
|
|
436
|
+
});
|
|
437
|
+
}
|
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
* Provides a typed send() function that posts a Command and returns a Result.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
const DAEMON_URL = `http://127.0.0.1:${DAEMON_PORT}`;
|
|
9
|
-
|
|
7
|
+
import { DEFAULT_DAEMON_PORT } from '../constants.js';
|
|
10
8
|
import type { BrowserSessionInfo } from '../types.js';
|
|
11
9
|
|
|
10
|
+
const DAEMON_PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
|
|
11
|
+
const DAEMON_URL = `http://127.0.0.1:${DAEMON_PORT}`;
|
|
12
|
+
|
|
12
13
|
let _idCounter = 0;
|
|
13
14
|
|
|
14
15
|
function generateId(): string {
|
package/src/browser/discover.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* scanning for @playwright/mcp locations.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { DEFAULT_DAEMON_PORT } from '../constants.js';
|
|
8
9
|
import { isDaemonRunning } from './daemon-client.js';
|
|
9
10
|
|
|
10
11
|
export { isDaemonRunning };
|
|
@@ -17,7 +18,7 @@ export async function checkDaemonStatus(): Promise<{
|
|
|
17
18
|
extensionConnected: boolean;
|
|
18
19
|
}> {
|
|
19
20
|
try {
|
|
20
|
-
const port = parseInt(process.env.OPENCLI_DAEMON_PORT ??
|
|
21
|
+
const port = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
|
|
21
22
|
const res = await fetch(`http://127.0.0.1:${port}/status`, {
|
|
22
23
|
headers: { 'X-OpenCLI': '1' },
|
|
23
24
|
});
|
|
@@ -247,3 +247,45 @@ describe('getFormStateJs', () => {
|
|
|
247
247
|
expect(js).toContain('data-opencli-ref');
|
|
248
248
|
});
|
|
249
249
|
});
|
|
250
|
+
|
|
251
|
+
describe('Search Element Detection', () => {
|
|
252
|
+
it('includes SEARCH_INDICATORS set', () => {
|
|
253
|
+
const js = generateSnapshotJs();
|
|
254
|
+
expect(js).toContain('SEARCH_INDICATORS');
|
|
255
|
+
expect(js).toContain('search');
|
|
256
|
+
expect(js).toContain('magnify');
|
|
257
|
+
expect(js).toContain('glass');
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it('includes hasFormControlDescendant function', () => {
|
|
261
|
+
const js = generateSnapshotJs();
|
|
262
|
+
expect(js).toContain('hasFormControlDescendant');
|
|
263
|
+
expect(js).toContain('input');
|
|
264
|
+
expect(js).toContain('select');
|
|
265
|
+
expect(js).toContain('textarea');
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('includes isSearchElement function', () => {
|
|
269
|
+
const js = generateSnapshotJs();
|
|
270
|
+
expect(js).toContain('isSearchElement');
|
|
271
|
+
expect(js).toContain('className');
|
|
272
|
+
expect(js).toContain('data-');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('checks label wrapper detection in isInteractive', () => {
|
|
276
|
+
const js = generateSnapshotJs();
|
|
277
|
+
// Label elements without "for" attribute should check for form control descendants
|
|
278
|
+
expect(js).toContain('hasFormControlDescendant(el, 2)');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('checks span wrapper detection in isInteractive', () => {
|
|
282
|
+
const js = generateSnapshotJs();
|
|
283
|
+
// Span elements should check for form control descendants
|
|
284
|
+
expect(js).toContain("tag === 'span'");
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('integrates search element detection into isInteractive', () => {
|
|
288
|
+
const js = generateSnapshotJs();
|
|
289
|
+
expect(js).toContain('isSearchElement(el)');
|
|
290
|
+
});
|
|
291
|
+
});
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
// ─── Types ───────────────────────────────────────────────────────────
|
|
28
28
|
|
|
29
|
-
export interface
|
|
29
|
+
export interface DomSnapshotOptions {
|
|
30
30
|
/** Extra pixels beyond viewport to include (default 800) */
|
|
31
31
|
viewportExpand?: number;
|
|
32
32
|
/** Maximum DOM depth to traverse (default 50) */
|
|
@@ -175,7 +175,7 @@ export function getFormStateJs(): string {
|
|
|
175
175
|
* - `|iframe|` — iframe content
|
|
176
176
|
* - `|table|` — markdown table rendering
|
|
177
177
|
*/
|
|
178
|
-
export function generateSnapshotJs(opts:
|
|
178
|
+
export function generateSnapshotJs(opts: DomSnapshotOptions = {}): string {
|
|
179
179
|
const viewportExpand = opts.viewportExpand ?? 800;
|
|
180
180
|
const maxDepth = Math.max(1, Math.min(opts.maxDepth ?? 50, 200));
|
|
181
181
|
const interactiveOnly = opts.interactiveOnly ?? false;
|
|
@@ -271,6 +271,13 @@ export function generateSnapshotJs(opts: SnapshotOptions = {}): string {
|
|
|
271
271
|
|
|
272
272
|
const AD_SELECTOR_RE = /\\b(ad[_-]?(?:banner|container|wrapper|slot|unit|block|frame|leaderboard|sidebar)|google[_-]?ad|sponsored|adsbygoogle|banner[_-]?ad)\\b/i;
|
|
273
273
|
|
|
274
|
+
// Search element indicators for heuristic detection
|
|
275
|
+
const SEARCH_INDICATORS = new Set([
|
|
276
|
+
'search', 'magnify', 'glass', 'lookup', 'find', 'query',
|
|
277
|
+
'search-icon', 'search-btn', 'search-button', 'searchbox',
|
|
278
|
+
'fa-search', 'icon-search', 'btn-search',
|
|
279
|
+
]);
|
|
280
|
+
|
|
274
281
|
// ── Viewport & Layout Helpers ──────────────────────────────────────
|
|
275
282
|
|
|
276
283
|
const vw = window.innerWidth;
|
|
@@ -339,19 +346,65 @@ export function generateSnapshotJs(opts: SnapshotOptions = {}): string {
|
|
|
339
346
|
|
|
340
347
|
// ── Interactivity Detection ────────────────────────────────────────
|
|
341
348
|
|
|
349
|
+
// Check if element contains a form control within limited depth (handles label/span wrappers)
|
|
350
|
+
function hasFormControlDescendant(el, maxDepth = 2) {
|
|
351
|
+
if (maxDepth <= 0) return false;
|
|
352
|
+
for (const child of el.children || []) {
|
|
353
|
+
const tag = child.tagName?.toLowerCase();
|
|
354
|
+
if (tag === 'input' || tag === 'select' || tag === 'textarea') return true;
|
|
355
|
+
if (hasFormControlDescendant(child, maxDepth - 1)) return true;
|
|
356
|
+
}
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
|
|
342
360
|
function isInteractive(el) {
|
|
343
361
|
const tag = el.tagName.toLowerCase();
|
|
344
362
|
if (INTERACTIVE_TAGS.has(tag)) {
|
|
345
|
-
|
|
363
|
+
// Skip labels that proxy via "for" to avoid double-activating external inputs
|
|
364
|
+
if (tag === 'label') {
|
|
365
|
+
if (el.hasAttribute('for')) return false;
|
|
366
|
+
// Detect labels that wrap form controls up to two levels deep (label > span > input)
|
|
367
|
+
if (hasFormControlDescendant(el, 2)) return true;
|
|
368
|
+
}
|
|
346
369
|
if (el.disabled && (tag === 'button' || tag === 'input')) return false;
|
|
347
370
|
return true;
|
|
348
371
|
}
|
|
372
|
+
// Span wrappers for UI components - check if they contain form controls
|
|
373
|
+
if (tag === 'span') {
|
|
374
|
+
if (hasFormControlDescendant(el, 2)) return true;
|
|
375
|
+
}
|
|
349
376
|
const role = el.getAttribute('role');
|
|
350
377
|
if (role && INTERACTIVE_ROLES.has(role)) return true;
|
|
351
378
|
if (el.hasAttribute('onclick') || el.hasAttribute('onmousedown') || el.hasAttribute('ontouchstart')) return true;
|
|
352
379
|
if (el.hasAttribute('tabindex') && el.getAttribute('tabindex') !== '-1') return true;
|
|
353
380
|
try { if (window.getComputedStyle(el).cursor === 'pointer') return true; } catch {}
|
|
354
381
|
if (el.isContentEditable && el.getAttribute('contenteditable') !== 'false') return true;
|
|
382
|
+
// Search element heuristic detection
|
|
383
|
+
if (isSearchElement(el)) return true;
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function isSearchElement(el) {
|
|
388
|
+
// Check class names for search indicators
|
|
389
|
+
const className = el.className?.toLowerCase() || '';
|
|
390
|
+
const classes = className.split(/\\s+/).filter(Boolean);
|
|
391
|
+
for (const cls of classes) {
|
|
392
|
+
const cleaned = cls.replace(/[^a-z0-9-]/g, '');
|
|
393
|
+
if (SEARCH_INDICATORS.has(cleaned)) return true;
|
|
394
|
+
}
|
|
395
|
+
// Check id for search indicators
|
|
396
|
+
const id = el.id?.toLowerCase() || '';
|
|
397
|
+
const cleanedId = id.replace(/[^a-z0-9-]/g, '');
|
|
398
|
+
if (SEARCH_INDICATORS.has(cleanedId)) return true;
|
|
399
|
+
// Check data-* attributes for search functionality
|
|
400
|
+
for (const attr of el.attributes || []) {
|
|
401
|
+
if (attr.name.startsWith('data-')) {
|
|
402
|
+
const value = attr.value.toLowerCase();
|
|
403
|
+
for (const kw of SEARCH_INDICATORS) {
|
|
404
|
+
if (value.includes(kw)) return true;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
355
408
|
return false;
|
|
356
409
|
}
|
|
357
410
|
|
package/src/browser/errors.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { BrowserConnectError } from '../errors.js';
|
|
9
|
+
import { DEFAULT_DAEMON_PORT } from '../constants.js';
|
|
9
10
|
|
|
10
11
|
export type ConnectFailureKind = 'daemon-not-running' | 'extension-not-connected' | 'command-failed' | 'unknown';
|
|
11
12
|
|
|
@@ -17,7 +18,7 @@ export function formatBrowserConnectError(kind: ConnectFailureKind, detail?: str
|
|
|
17
18
|
(detail ? `\n\n${detail}` : ''),
|
|
18
19
|
'The daemon should start automatically. If it doesn\'t, try:\n' +
|
|
19
20
|
' node dist/daemon.js\n' +
|
|
20
|
-
|
|
21
|
+
`Make sure port ${DEFAULT_DAEMON_PORT} is available.`,
|
|
21
22
|
);
|
|
22
23
|
case 'extension-not-connected':
|
|
23
24
|
return new BrowserConnectError(
|
package/src/browser/index.ts
CHANGED
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export { Page } from './page.js';
|
|
9
|
-
export { BrowserBridge
|
|
9
|
+
export { BrowserBridge } from './mcp.js';
|
|
10
10
|
export { CDPBridge } from './cdp.js';
|
|
11
11
|
export { isDaemonRunning } from './daemon-client.js';
|
|
12
12
|
export { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
|
|
13
|
-
export
|
|
13
|
+
export { generateStealthJs } from './stealth.js';
|
|
14
|
+
export type { DomSnapshotOptions } from './dom-snapshot.js';
|
|
14
15
|
|
|
15
16
|
import { extractTabEntries, diffTabIndexes, appendLimited } from './tabs.js';
|
|
16
17
|
import { __test__ as cdpTest } from './cdp.js';
|
package/src/browser/mcp.ts
CHANGED
|
@@ -9,6 +9,7 @@ import * as fs from 'node:fs';
|
|
|
9
9
|
import type { IPage } from '../types.js';
|
|
10
10
|
import { Page } from './page.js';
|
|
11
11
|
import { isDaemonRunning, isExtensionConnected } from './daemon-client.js';
|
|
12
|
+
import { DEFAULT_DAEMON_PORT } from '../constants.js';
|
|
12
13
|
|
|
13
14
|
const DAEMON_SPAWN_TIMEOUT = 10000; // 10s to wait for daemon + extension
|
|
14
15
|
|
|
@@ -112,10 +113,7 @@ export class BrowserBridge {
|
|
|
112
113
|
throw new Error(
|
|
113
114
|
'Failed to start opencli daemon. Try running manually:\n' +
|
|
114
115
|
` node ${daemonPath}\n` +
|
|
115
|
-
|
|
116
|
+
`Make sure port ${DEFAULT_DAEMON_PORT} is available.`,
|
|
116
117
|
);
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
|
-
|
|
120
|
-
/** @deprecated Use BrowserBridge instead */
|
|
121
|
-
export const PlaywrightMCP = BrowserBridge;
|