@jackwener/opencli 1.6.0 → 1.6.2
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/CHANGELOG.md +8 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +27 -45
- package/README.zh-CN.md +32 -34
- package/autoresearch/browse-tasks.json +18 -20
- package/autoresearch/commands/debug.ts +163 -0
- package/autoresearch/commands/fix.ts +145 -0
- package/autoresearch/commands/plan.ts +88 -0
- package/autoresearch/commands/run.ts +138 -0
- package/autoresearch/config.ts +82 -0
- package/autoresearch/engine.ts +359 -0
- package/autoresearch/eval-all.ts +127 -0
- package/autoresearch/eval-browse.ts +1 -1
- package/autoresearch/eval-publish.ts +238 -0
- package/autoresearch/eval-save.ts +249 -0
- package/autoresearch/eval-skill.ts +14 -8
- package/autoresearch/eval-v2ex.ts +220 -0
- package/autoresearch/eval-zhihu.ts +230 -0
- package/autoresearch/logger.ts +69 -0
- package/autoresearch/presets/combined-reliability.ts +27 -0
- package/autoresearch/presets/index.ts +23 -0
- package/autoresearch/presets/operate-reliability.ts +24 -0
- package/autoresearch/presets/save-reliability.ts +26 -0
- package/autoresearch/presets/skill-quality.ts +20 -0
- package/autoresearch/presets/v2ex-reliability.ts +24 -0
- package/autoresearch/presets/zhihu-reliability.ts +25 -0
- package/autoresearch/publish-tasks.json +345 -0
- package/autoresearch/run-save.sh +11 -0
- package/autoresearch/save-adapters/xhs-explore-deep.ts +64 -0
- package/autoresearch/save-adapters/xhs-note-comments.ts +61 -0
- package/autoresearch/save-adapters/xhs-search-full.ts +62 -0
- package/autoresearch/save-adapters/zhihu-hot-detail.ts +52 -0
- package/autoresearch/save-adapters/zhihu-question-full.ts +57 -0
- package/autoresearch/save-adapters/zhihu-search-detail.ts +53 -0
- package/autoresearch/save-tasks.json +281 -0
- package/autoresearch/v2ex-tasks.json +899 -0
- package/autoresearch/zhihu-tasks.json +848 -0
- package/bun.lock +615 -0
- package/dist/browser/base-page.d.ts +4 -2
- package/dist/browser/base-page.js +37 -4
- package/dist/browser/bridge.js +10 -8
- package/dist/browser/cdp.js +2 -6
- package/dist/browser/daemon-client.d.ts +11 -1
- package/dist/browser/daemon-client.js +3 -0
- package/dist/browser/dom-helpers.d.ts +4 -2
- package/dist/browser/dom-helpers.js +42 -31
- package/dist/browser/dom-snapshot.js +23 -1
- package/dist/browser/page.d.ts +7 -2
- package/dist/browser/page.js +112 -30
- package/dist/browser.test.js +1 -1
- package/dist/build-manifest.d.ts +1 -0
- package/dist/build-manifest.js +1 -0
- package/dist/cli-manifest.json +1133 -182
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +48 -7
- package/dist/cli.test.d.ts +1 -0
- package/dist/cli.test.js +88 -0
- package/dist/clis/1688/item.d.ts +70 -0
- package/dist/clis/1688/item.js +187 -0
- package/dist/clis/1688/item.test.d.ts +1 -0
- package/dist/clis/1688/item.test.js +67 -0
- package/dist/clis/1688/search.d.ts +56 -0
- package/dist/clis/1688/search.js +309 -0
- package/dist/clis/1688/search.test.d.ts +1 -0
- package/dist/clis/1688/search.test.js +75 -0
- package/dist/clis/1688/shared.d.ts +112 -0
- package/dist/clis/1688/shared.js +514 -0
- package/dist/clis/1688/shared.test.d.ts +1 -0
- package/dist/clis/1688/shared.test.js +57 -0
- package/dist/clis/1688/store.d.ts +45 -0
- package/dist/clis/1688/store.js +226 -0
- package/dist/clis/1688/store.test.d.ts +1 -0
- package/dist/clis/1688/store.test.js +62 -0
- package/dist/clis/amazon/bestsellers.d.ts +0 -20
- package/dist/clis/amazon/bestsellers.js +6 -129
- package/dist/clis/amazon/bestsellers.test.js +12 -3
- package/dist/clis/amazon/movers-shakers.d.ts +1 -0
- package/dist/clis/amazon/movers-shakers.js +7 -0
- package/dist/clis/amazon/new-releases.d.ts +1 -0
- package/dist/clis/amazon/new-releases.js +7 -0
- package/dist/clis/amazon/rankings.d.ts +59 -0
- package/dist/clis/amazon/rankings.js +226 -0
- package/dist/clis/amazon/rankings.test.d.ts +1 -0
- package/dist/clis/amazon/rankings.test.js +41 -0
- package/dist/clis/amazon/shared.d.ts +11 -0
- package/dist/clis/amazon/shared.js +121 -11
- package/dist/clis/amazon/shared.test.js +11 -0
- package/dist/clis/bilibili/comments.js +2 -2
- package/dist/clis/bilibili/comments.test.js +3 -2
- package/dist/clis/bilibili/download.js +2 -1
- package/dist/clis/bilibili/subtitle.js +4 -3
- package/dist/clis/bilibili/subtitle.test.js +2 -1
- package/dist/clis/bilibili/utils.d.ts +5 -0
- package/dist/clis/bilibili/utils.js +30 -0
- package/dist/clis/bilibili/utils.test.d.ts +1 -0
- package/dist/clis/bilibili/utils.test.js +17 -0
- package/dist/clis/douban/marks.js +1 -1
- package/dist/clis/douban/subject.yaml +50 -19
- package/dist/clis/doubao/utils.js +32 -12
- package/dist/clis/douyin/_shared/browser-fetch.test.js +0 -1
- package/dist/clis/douyin/_shared/transcode.test.js +0 -2
- package/dist/clis/douyin/draft.test.js +0 -2
- package/dist/clis/facebook/search.test.js +0 -2
- package/dist/clis/gemini/ask.js +9 -3
- package/dist/clis/gemini/ask.test.d.ts +1 -0
- package/dist/clis/gemini/ask.test.js +100 -0
- package/dist/clis/gemini/reply-state.test.d.ts +1 -0
- package/dist/clis/gemini/reply-state.test.js +641 -0
- package/dist/clis/gemini/utils.d.ts +44 -1
- package/dist/clis/gemini/utils.js +528 -61
- package/dist/clis/gemini/utils.test.js +149 -2
- package/dist/clis/hupu/detail.d.ts +1 -0
- package/dist/clis/hupu/detail.js +72 -0
- package/dist/clis/hupu/hot.yaml +43 -0
- package/dist/clis/hupu/like.d.ts +1 -0
- package/dist/clis/hupu/like.js +75 -0
- package/dist/clis/hupu/reply.d.ts +1 -0
- package/dist/clis/hupu/reply.js +71 -0
- package/dist/clis/hupu/search.d.ts +1 -0
- package/dist/clis/hupu/search.js +59 -0
- package/dist/clis/hupu/unlike.d.ts +1 -0
- package/dist/clis/hupu/unlike.js +75 -0
- package/dist/clis/hupu/utils.d.ts +20 -0
- package/dist/clis/hupu/utils.js +319 -0
- package/dist/clis/instagram/_shared/private-publish.d.ts +138 -0
- package/dist/clis/instagram/_shared/private-publish.js +1030 -0
- package/dist/clis/instagram/_shared/private-publish.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/private-publish.test.js +705 -0
- package/dist/clis/instagram/_shared/protocol-capture.d.ts +26 -0
- package/dist/clis/instagram/_shared/protocol-capture.js +282 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.d.ts +1 -0
- package/dist/clis/instagram/_shared/protocol-capture.test.js +114 -0
- package/dist/clis/instagram/_shared/runtime-info.d.ts +9 -0
- package/dist/clis/instagram/_shared/runtime-info.js +81 -0
- package/dist/clis/instagram/note.d.ts +1 -0
- package/dist/clis/instagram/note.js +222 -0
- package/dist/clis/instagram/note.test.d.ts +1 -0
- package/dist/clis/instagram/note.test.js +81 -0
- package/dist/clis/instagram/post.d.ts +4 -0
- package/dist/clis/instagram/post.js +1496 -0
- package/dist/clis/instagram/post.test.d.ts +1 -0
- package/dist/clis/instagram/post.test.js +1647 -0
- package/dist/clis/instagram/reel.d.ts +1 -0
- package/dist/clis/instagram/reel.js +826 -0
- package/dist/clis/instagram/reel.test.d.ts +1 -0
- package/dist/clis/instagram/reel.test.js +167 -0
- package/dist/clis/instagram/story.d.ts +1 -0
- package/dist/clis/instagram/story.js +115 -0
- package/dist/clis/instagram/story.test.d.ts +1 -0
- package/dist/clis/instagram/story.test.js +167 -0
- package/dist/clis/sinafinance/stock-rank.d.ts +4 -0
- package/dist/clis/sinafinance/stock-rank.js +65 -0
- package/dist/clis/substack/utils.test.js +0 -2
- package/dist/clis/twitter/post.js +72 -45
- package/dist/clis/twitter/post.test.d.ts +1 -0
- package/dist/clis/twitter/post.test.js +116 -0
- package/dist/clis/twitter/reply.d.ts +12 -0
- package/dist/clis/twitter/reply.js +257 -35
- package/dist/clis/twitter/reply.test.d.ts +1 -0
- package/dist/clis/twitter/reply.test.js +151 -0
- package/dist/clis/twitter/search.js +67 -5
- package/dist/clis/twitter/search.test.js +83 -5
- package/dist/clis/xianyu/chat.d.ts +7 -0
- package/dist/clis/xianyu/chat.js +146 -0
- package/dist/clis/xianyu/chat.test.d.ts +1 -0
- package/dist/clis/xianyu/chat.test.js +15 -0
- package/dist/clis/xianyu/item.d.ts +7 -0
- package/dist/clis/xianyu/item.js +152 -0
- package/dist/clis/xianyu/item.test.d.ts +1 -0
- package/dist/clis/xianyu/item.test.js +56 -0
- package/dist/clis/xianyu/search.d.ts +10 -0
- package/dist/clis/xianyu/search.js +134 -0
- package/dist/clis/xianyu/search.test.d.ts +1 -0
- package/dist/clis/xianyu/search.test.js +17 -0
- package/dist/clis/xianyu/utils.d.ts +1 -0
- package/dist/clis/xianyu/utils.js +8 -0
- package/dist/clis/xiaoe/catalog.yaml +129 -0
- package/dist/clis/xiaoe/content.yaml +43 -0
- package/dist/clis/xiaoe/courses.yaml +73 -0
- package/dist/clis/xiaoe/detail.yaml +39 -0
- package/dist/clis/xiaoe/play-url.yaml +124 -0
- package/dist/clis/xiaohongshu/comments.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +0 -2
- package/dist/clis/xiaohongshu/creator-notes.test.js +0 -2
- package/dist/clis/xiaohongshu/download.test.js +0 -2
- package/dist/clis/xiaohongshu/note.test.js +0 -2
- package/dist/clis/xiaohongshu/publish.test.js +0 -2
- package/dist/clis/xiaohongshu/search.js +29 -20
- package/dist/clis/xiaohongshu/search.test.js +56 -48
- package/dist/clis/yuanbao/ask.d.ts +21 -0
- package/dist/clis/yuanbao/ask.js +427 -0
- package/dist/clis/yuanbao/ask.test.d.ts +1 -0
- package/dist/clis/yuanbao/ask.test.js +124 -0
- package/dist/clis/yuanbao/new.d.ts +1 -0
- package/dist/clis/yuanbao/new.js +70 -0
- package/dist/clis/yuanbao/new.test.d.ts +1 -0
- package/dist/clis/yuanbao/new.test.js +30 -0
- package/dist/clis/yuanbao/shared.d.ts +13 -0
- package/dist/clis/yuanbao/shared.js +49 -0
- package/dist/clis/zhihu/question.js +30 -19
- package/dist/clis/zhihu/question.test.js +34 -16
- package/dist/commanderAdapter.js +8 -4
- package/dist/commanderAdapter.test.js +42 -0
- package/dist/completion.js +3 -1
- package/dist/completion.test.d.ts +1 -0
- package/dist/completion.test.js +23 -0
- package/dist/doctor.js +1 -1
- package/dist/electron-apps.d.ts +2 -0
- package/dist/electron-apps.js +7 -1
- package/dist/errors.js +1 -1
- package/dist/execution.js +25 -35
- package/dist/explore.js +1 -1
- package/dist/launcher.d.ts +4 -0
- package/dist/launcher.js +64 -8
- package/dist/launcher.test.js +88 -7
- package/dist/output.d.ts +2 -0
- package/dist/output.js +10 -1
- package/dist/output.test.d.ts +0 -3
- package/dist/output.test.js +59 -92
- package/dist/pipeline/executor.test.js +0 -2
- package/dist/pipeline/steps/download.test.js +0 -2
- package/dist/registry.d.ts +2 -0
- package/dist/serialization.d.ts +1 -0
- package/dist/serialization.js +1 -0
- package/dist/types.d.ts +9 -2
- package/docs/.vitepress/config.mts +4 -0
- package/docs/adapters/browser/1688.md +52 -0
- package/docs/adapters/browser/36kr.md +2 -1
- package/docs/adapters/browser/doubao.md +5 -1
- package/docs/adapters/browser/hupu.md +53 -0
- package/docs/adapters/browser/sinafinance.md +32 -2
- package/docs/adapters/browser/weibo.md +6 -1
- package/docs/adapters/browser/wikipedia.md +2 -0
- package/docs/adapters/browser/xianyu.md +42 -0
- package/docs/adapters/browser/xiaoe.md +44 -0
- package/docs/adapters/browser/yuanbao.md +64 -0
- package/docs/adapters/index.md +14 -5
- package/docs/comparison.md +1 -1
- package/docs/developer/ai-workflow.md +2 -2
- package/docs/developer/contributing.md +1 -1
- package/docs/developer/testing.md +2 -0
- package/docs/guide/plugins.md +1 -0
- package/docs/guide/troubleshooting.md +11 -0
- package/docs/superpowers/specs/2026-04-03-v2ex-autoresearch-design.md +41 -0
- package/docs/zh/guide/plugins.md +1 -0
- package/extension/dist/background.js +1127 -0
- package/extension/src/background.test.ts +39 -0
- package/extension/src/background.ts +223 -34
- package/extension/src/cdp.ts +194 -4
- package/extension/src/protocol.ts +22 -1
- package/package.json +3 -2
- package/scripts/postinstall.js +1 -1
- package/skills/opencli-explorer/SKILL.md +1 -1
- package/skills/opencli-oneshot/SKILL.md +2 -2
- package/skills/opencli-operate/SKILL.md +120 -27
- package/skills/opencli-usage/SKILL.md +31 -20
- package/skills/opencli-usage/browser.md +114 -16
- package/skills/opencli-usage/public-api.md +32 -3
- package/skills/smart-search/SKILL.md +156 -0
- package/skills/smart-search/references/sources-ai.md +74 -0
- package/skills/smart-search/references/sources-info.md +43 -0
- package/skills/smart-search/references/sources-media.md +50 -0
- package/skills/smart-search/references/sources-other.md +42 -0
- package/skills/smart-search/references/sources-shopping.md +31 -0
- package/skills/smart-search/references/sources-social.md +51 -0
- package/skills/smart-search/references/sources-tech.md +42 -0
- package/skills/smart-search/references/sources-travel.md +20 -0
- package/src/browser/base-page.ts +41 -6
- package/src/browser/bridge.ts +11 -8
- package/src/browser/cdp.ts +1 -8
- package/src/browser/daemon-client.ts +11 -1
- package/src/browser/dom-helpers.ts +43 -31
- package/src/browser/dom-snapshot.ts +23 -1
- package/src/browser/page.ts +115 -31
- package/src/browser.test.ts +1 -1
- package/src/build-manifest.ts +2 -0
- package/src/cli.test.ts +133 -0
- package/src/cli.ts +73 -11
- package/src/clis/1688/item.test.ts +69 -0
- package/src/clis/1688/item.ts +282 -0
- package/src/clis/1688/search.test.ts +81 -0
- package/src/clis/1688/search.ts +402 -0
- package/src/clis/1688/shared.test.ts +75 -0
- package/src/clis/1688/shared.ts +623 -0
- package/src/clis/1688/store.test.ts +69 -0
- package/src/clis/1688/store.ts +300 -0
- package/src/clis/amazon/bestsellers.test.ts +12 -3
- package/src/clis/amazon/bestsellers.ts +6 -178
- package/src/clis/amazon/movers-shakers.ts +8 -0
- package/src/clis/amazon/new-releases.ts +8 -0
- package/src/clis/amazon/rankings.test.ts +47 -0
- package/src/clis/amazon/rankings.ts +312 -0
- package/src/clis/amazon/shared.test.ts +16 -0
- package/src/clis/amazon/shared.ts +134 -12
- package/src/clis/bilibili/comments.test.ts +4 -3
- package/src/clis/bilibili/comments.ts +2 -2
- package/src/clis/bilibili/download.ts +2 -1
- package/src/clis/bilibili/subtitle.test.ts +2 -1
- package/src/clis/bilibili/subtitle.ts +4 -3
- package/src/clis/bilibili/utils.test.ts +21 -0
- package/src/clis/bilibili/utils.ts +27 -0
- package/src/clis/douban/marks.ts +1 -1
- package/src/clis/douban/subject.yaml +50 -19
- package/src/clis/doubao/utils.ts +32 -12
- package/src/clis/douyin/_shared/browser-fetch.test.ts +0 -1
- package/src/clis/douyin/_shared/transcode.test.ts +0 -2
- package/src/clis/douyin/draft.test.ts +0 -2
- package/src/clis/facebook/search.test.ts +0 -2
- package/src/clis/gemini/ask.test.ts +116 -0
- package/src/clis/gemini/ask.ts +10 -3
- package/src/clis/gemini/reply-state.test.ts +708 -0
- package/src/clis/gemini/utils.test.ts +184 -2
- package/src/clis/gemini/utils.ts +588 -60
- package/src/clis/hupu/detail.ts +126 -0
- package/src/clis/hupu/hot.yaml +43 -0
- package/src/clis/hupu/like.ts +76 -0
- package/src/clis/hupu/reply.ts +76 -0
- package/src/clis/hupu/search.ts +95 -0
- package/src/clis/hupu/unlike.ts +76 -0
- package/src/clis/hupu/utils.ts +381 -0
- package/src/clis/instagram/_shared/private-publish.test.ts +827 -0
- package/src/clis/instagram/_shared/private-publish.ts +1303 -0
- package/src/clis/instagram/_shared/protocol-capture.test.ts +148 -0
- package/src/clis/instagram/_shared/protocol-capture.ts +321 -0
- package/src/clis/instagram/_shared/runtime-info.ts +91 -0
- package/src/clis/instagram/note.test.ts +96 -0
- package/src/clis/instagram/note.ts +254 -0
- package/src/clis/instagram/post.test.ts +1716 -0
- package/src/clis/instagram/post.ts +1620 -0
- package/src/clis/instagram/reel.test.ts +191 -0
- package/src/clis/instagram/reel.ts +886 -0
- package/src/clis/instagram/story.test.ts +191 -0
- package/src/clis/instagram/story.ts +151 -0
- package/src/clis/sinafinance/stock-rank.ts +68 -0
- package/src/clis/substack/utils.test.ts +0 -2
- package/src/clis/twitter/post.test.ts +157 -0
- package/src/clis/twitter/post.ts +82 -48
- package/src/clis/twitter/reply.test.ts +177 -0
- package/src/clis/twitter/reply.ts +285 -39
- package/src/clis/twitter/search.test.ts +88 -5
- package/src/clis/twitter/search.ts +68 -5
- package/src/clis/xianyu/chat.test.ts +20 -0
- package/src/clis/xianyu/chat.ts +175 -0
- package/src/clis/xianyu/item.test.ts +67 -0
- package/src/clis/xianyu/item.ts +172 -0
- package/src/clis/xianyu/search.test.ts +22 -0
- package/src/clis/xianyu/search.ts +151 -0
- package/src/clis/xianyu/utils.ts +9 -0
- package/src/clis/xiaoe/catalog.yaml +129 -0
- package/src/clis/xiaoe/content.yaml +43 -0
- package/src/clis/xiaoe/courses.yaml +73 -0
- package/src/clis/xiaoe/detail.yaml +39 -0
- package/src/clis/xiaoe/play-url.yaml +124 -0
- package/src/clis/xiaohongshu/comments.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +0 -2
- package/src/clis/xiaohongshu/creator-notes.test.ts +0 -2
- package/src/clis/xiaohongshu/download.test.ts +0 -2
- package/src/clis/xiaohongshu/note.test.ts +0 -2
- package/src/clis/xiaohongshu/publish.test.ts +0 -2
- package/src/clis/xiaohongshu/search.test.ts +59 -48
- package/src/clis/xiaohongshu/search.ts +31 -21
- package/src/clis/yuanbao/ask.test.ts +156 -0
- package/src/clis/yuanbao/ask.ts +522 -0
- package/src/clis/yuanbao/new.test.ts +36 -0
- package/src/clis/yuanbao/new.ts +81 -0
- package/src/clis/yuanbao/shared.ts +57 -0
- package/src/clis/zhihu/question.test.ts +42 -17
- package/src/clis/zhihu/question.ts +31 -26
- package/src/commanderAdapter.test.ts +51 -0
- package/src/commanderAdapter.ts +8 -4
- package/src/completion.test.ts +30 -0
- package/src/completion.ts +3 -1
- package/src/doctor.ts +1 -1
- package/src/electron-apps.ts +9 -1
- package/src/errors.ts +1 -1
- package/src/execution.ts +26 -30
- package/src/explore.ts +1 -1
- package/src/launcher.test.ts +121 -7
- package/src/launcher.ts +87 -9
- package/src/output.test.ts +50 -90
- package/src/output.ts +10 -1
- package/src/pipeline/executor.test.ts +0 -2
- package/src/pipeline/steps/download.test.ts +0 -2
- package/src/registry.ts +2 -0
- package/src/serialization.ts +2 -0
- package/src/types.ts +9 -2
- package/tests/e2e/browser-auth.test.ts +9 -0
- package/CLI-EXPLORER.md +0 -724
- package/CLI-ONESHOT.md +0 -216
- package/SKILL.md +0 -59
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import type { BrowserCookie, IPage, ScreenshotOptions, SnapshotOptions, WaitOptions } from '../types.js';
|
|
12
12
|
export declare abstract class BasePage implements IPage {
|
|
13
13
|
protected _lastUrl: string | null;
|
|
14
|
+
/** Cached previous snapshot hashes for incremental diff marking */
|
|
15
|
+
private _prevSnapshotHashes;
|
|
14
16
|
abstract goto(url: string, options?: {
|
|
15
17
|
waitUntil?: 'load' | 'none';
|
|
16
18
|
settleMs?: number;
|
|
@@ -22,10 +24,10 @@ export declare abstract class BasePage implements IPage {
|
|
|
22
24
|
}): Promise<BrowserCookie[]>;
|
|
23
25
|
abstract screenshot(options?: ScreenshotOptions): Promise<string>;
|
|
24
26
|
abstract tabs(): Promise<unknown[]>;
|
|
25
|
-
abstract closeTab(index?: number): Promise<void>;
|
|
26
|
-
abstract newTab(): Promise<void>;
|
|
27
27
|
abstract selectTab(index: number): Promise<void>;
|
|
28
28
|
click(ref: string): Promise<void>;
|
|
29
|
+
/** Override in subclasses with CDP native click support */
|
|
30
|
+
protected tryNativeClick(_x: number, _y: number): Promise<boolean>;
|
|
29
31
|
typeText(ref: string, text: string): Promise<void>;
|
|
30
32
|
pressKey(key: string): Promise<void>;
|
|
31
33
|
scrollTo(ref: string): Promise<unknown>;
|
|
@@ -13,9 +13,28 @@ import { clickJs, typeTextJs, pressKeyJs, waitForTextJs, waitForCaptureJs, waitF
|
|
|
13
13
|
import { formatSnapshot } from '../snapshotFormatter.js';
|
|
14
14
|
export class BasePage {
|
|
15
15
|
_lastUrl = null;
|
|
16
|
+
/** Cached previous snapshot hashes for incremental diff marking */
|
|
17
|
+
_prevSnapshotHashes = null;
|
|
16
18
|
// ── Shared DOM helper implementations ──
|
|
17
19
|
async click(ref) {
|
|
18
|
-
await this.evaluate(clickJs(ref));
|
|
20
|
+
const result = await this.evaluate(clickJs(ref));
|
|
21
|
+
// Backwards compat: old format returned 'clicked' string
|
|
22
|
+
if (typeof result === 'string' || result == null)
|
|
23
|
+
return;
|
|
24
|
+
// JS click succeeded
|
|
25
|
+
if (result.status === 'clicked')
|
|
26
|
+
return;
|
|
27
|
+
// JS click failed — try CDP native click if coordinates available
|
|
28
|
+
if (result.x != null && result.y != null) {
|
|
29
|
+
const success = await this.tryNativeClick(result.x, result.y);
|
|
30
|
+
if (success)
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
throw new Error(`Click failed: ${result.error ?? 'JS click and CDP fallback both failed'}`);
|
|
34
|
+
}
|
|
35
|
+
/** Override in subclasses with CDP native click support */
|
|
36
|
+
async tryNativeClick(_x, _y) {
|
|
37
|
+
return false;
|
|
19
38
|
}
|
|
20
39
|
async typeText(ref, text) {
|
|
21
40
|
await this.evaluate(typeTextJs(ref, text));
|
|
@@ -75,17 +94,31 @@ export class BasePage {
|
|
|
75
94
|
}
|
|
76
95
|
async snapshot(opts = {}) {
|
|
77
96
|
const snapshotJs = generateSnapshotJs({
|
|
78
|
-
viewportExpand: opts.viewportExpand ??
|
|
97
|
+
viewportExpand: opts.viewportExpand ?? 2000,
|
|
79
98
|
maxDepth: Math.max(1, Math.min(Number(opts.maxDepth) || 50, 200)),
|
|
80
99
|
interactiveOnly: opts.interactive ?? false,
|
|
81
100
|
maxTextLength: opts.maxTextLength ?? 120,
|
|
82
101
|
includeScrollInfo: true,
|
|
83
102
|
bboxDedup: true,
|
|
103
|
+
previousHashes: this._prevSnapshotHashes,
|
|
84
104
|
});
|
|
85
105
|
try {
|
|
86
|
-
|
|
106
|
+
const result = await this.evaluate(snapshotJs);
|
|
107
|
+
// Read back the hashes stored by the snapshot for next diff
|
|
108
|
+
try {
|
|
109
|
+
const hashes = await this.evaluate('window.__opencli_prev_hashes');
|
|
110
|
+
this._prevSnapshotHashes = typeof hashes === 'string' ? hashes : null;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
// Non-fatal: diff is best-effort
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
87
116
|
}
|
|
88
|
-
catch {
|
|
117
|
+
catch (err) {
|
|
118
|
+
// Log snapshot failure for debugging, then fallback to basic accessibility tree
|
|
119
|
+
if (process.env.DEBUG_SNAPSHOT) {
|
|
120
|
+
console.error('[snapshot] DOM snapshot failed, falling back to accessibility tree:', err?.message?.slice(0, 200));
|
|
121
|
+
}
|
|
89
122
|
return this._basicSnapshot(opts);
|
|
90
123
|
}
|
|
91
124
|
}
|
package/dist/browser/bridge.js
CHANGED
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
6
6
|
import * as path from 'node:path';
|
|
7
7
|
import * as fs from 'node:fs';
|
|
8
8
|
import { Page } from './page.js';
|
|
9
|
-
import {
|
|
9
|
+
import { fetchDaemonStatus, isExtensionConnected } from './daemon-client.js';
|
|
10
10
|
import { DEFAULT_DAEMON_PORT } from '../constants.js';
|
|
11
11
|
const DAEMON_SPAWN_TIMEOUT = 10000; // 10s to wait for daemon + extension
|
|
12
12
|
/**
|
|
@@ -52,14 +52,16 @@ export class BrowserBridge {
|
|
|
52
52
|
async _ensureDaemon(timeoutSeconds) {
|
|
53
53
|
const effectiveSeconds = (timeoutSeconds && timeoutSeconds > 0) ? timeoutSeconds : Math.ceil(DAEMON_SPAWN_TIMEOUT / 1000);
|
|
54
54
|
const timeoutMs = effectiveSeconds * 1000;
|
|
55
|
+
// Single status check instead of two separate fetchDaemonStatus() calls
|
|
56
|
+
const status = await fetchDaemonStatus();
|
|
55
57
|
// Fast path: extension already connected
|
|
56
|
-
if (
|
|
58
|
+
if (status?.extensionConnected)
|
|
57
59
|
return;
|
|
58
60
|
// Daemon running but no extension — wait for extension with progress
|
|
59
|
-
if (
|
|
61
|
+
if (status !== null) {
|
|
60
62
|
if (process.env.OPENCLI_VERBOSE || process.stderr.isTTY) {
|
|
61
|
-
process.stderr.write('⏳ Waiting for Chrome extension to connect...\n');
|
|
62
|
-
process.stderr.write(' Make sure Chrome is open and the OpenCLI extension is enabled.\n');
|
|
63
|
+
process.stderr.write('⏳ Waiting for Chrome/Chromium extension to connect...\n');
|
|
64
|
+
process.stderr.write(' Make sure Chrome or Chromium is open and the OpenCLI extension is enabled.\n');
|
|
63
65
|
}
|
|
64
66
|
const deadline = Date.now() + timeoutMs;
|
|
65
67
|
while (Date.now() < deadline) {
|
|
@@ -68,7 +70,7 @@ export class BrowserBridge {
|
|
|
68
70
|
return;
|
|
69
71
|
}
|
|
70
72
|
throw new Error('Daemon is running but the Browser Extension is not connected.\n' +
|
|
71
|
-
'Please install and enable the opencli Browser Bridge extension in Chrome.');
|
|
73
|
+
'Please install and enable the opencli Browser Bridge extension in Chrome or Chromium.');
|
|
72
74
|
}
|
|
73
75
|
// No daemon — spawn one
|
|
74
76
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -96,9 +98,9 @@ export class BrowserBridge {
|
|
|
96
98
|
if (await isExtensionConnected())
|
|
97
99
|
return;
|
|
98
100
|
}
|
|
99
|
-
if (await
|
|
101
|
+
if ((await fetchDaemonStatus()) !== null) {
|
|
100
102
|
throw new Error('Daemon is running but the Browser Extension is not connected.\n' +
|
|
101
|
-
'Please install and enable the opencli Browser Bridge extension in Chrome.');
|
|
103
|
+
'Please install and enable the opencli Browser Bridge extension in Chrome or Chromium.');
|
|
102
104
|
}
|
|
103
105
|
throw new Error('Failed to start opencli daemon. Try running manually:\n' +
|
|
104
106
|
` node ${daemonPath}\n` +
|
package/dist/browser/cdp.js
CHANGED
|
@@ -189,12 +189,6 @@ class CDPPage extends BasePage {
|
|
|
189
189
|
async tabs() {
|
|
190
190
|
return [];
|
|
191
191
|
}
|
|
192
|
-
async closeTab(_index) {
|
|
193
|
-
// Not supported in direct CDP mode
|
|
194
|
-
}
|
|
195
|
-
async newTab() {
|
|
196
|
-
await this.bridge.send('Target.createTarget', { url: 'about:blank' });
|
|
197
|
-
}
|
|
198
192
|
async selectTab(_index) {
|
|
199
193
|
// Not supported in direct CDP mode
|
|
200
194
|
}
|
|
@@ -234,6 +228,8 @@ function scoreCDPTarget(target, preferredPattern) {
|
|
|
234
228
|
return Number.NEGATIVE_INFINITY;
|
|
235
229
|
if (haystack.includes('devtools'))
|
|
236
230
|
return Number.NEGATIVE_INFINITY;
|
|
231
|
+
if (type === 'background_page' || type === 'service_worker')
|
|
232
|
+
return Number.NEGATIVE_INFINITY;
|
|
237
233
|
let score = 0;
|
|
238
234
|
if (preferredPattern && preferredPattern.test(haystack))
|
|
239
235
|
score += 1000;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import type { BrowserSessionInfo } from '../types.js';
|
|
7
7
|
export interface DaemonCommand {
|
|
8
8
|
id: string;
|
|
9
|
-
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'cdp';
|
|
9
|
+
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'insert-text' | 'bind-current' | 'network-capture-start' | 'network-capture-read' | 'cdp';
|
|
10
10
|
tabId?: number;
|
|
11
11
|
code?: string;
|
|
12
12
|
workspace?: string;
|
|
@@ -14,6 +14,8 @@ export interface DaemonCommand {
|
|
|
14
14
|
op?: string;
|
|
15
15
|
index?: number;
|
|
16
16
|
domain?: string;
|
|
17
|
+
matchDomain?: string;
|
|
18
|
+
matchPathPrefix?: string;
|
|
17
19
|
format?: 'png' | 'jpeg';
|
|
18
20
|
quality?: number;
|
|
19
21
|
fullPage?: boolean;
|
|
@@ -21,6 +23,10 @@ export interface DaemonCommand {
|
|
|
21
23
|
files?: string[];
|
|
22
24
|
/** CSS selector for file input element (set-file-input action) */
|
|
23
25
|
selector?: string;
|
|
26
|
+
/** Raw text payload for insert-text action */
|
|
27
|
+
text?: string;
|
|
28
|
+
/** URL substring filter pattern for network capture */
|
|
29
|
+
pattern?: string;
|
|
24
30
|
cdpMethod?: string;
|
|
25
31
|
cdpParams?: Record<string, unknown>;
|
|
26
32
|
}
|
|
@@ -62,3 +68,7 @@ export declare function isExtensionConnected(): Promise<boolean>;
|
|
|
62
68
|
*/
|
|
63
69
|
export declare function sendCommand(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<unknown>;
|
|
64
70
|
export declare function listSessions(): Promise<BrowserSessionInfo[]>;
|
|
71
|
+
export declare function bindCurrentTab(workspace: string, opts?: {
|
|
72
|
+
matchDomain?: string;
|
|
73
|
+
matchPathPrefix?: string;
|
|
74
|
+
}): Promise<unknown>;
|
|
@@ -108,3 +108,6 @@ export async function listSessions() {
|
|
|
108
108
|
const result = await sendCommand('sessions');
|
|
109
109
|
return Array.isArray(result) ? result : [];
|
|
110
110
|
}
|
|
111
|
+
export async function bindCurrentTab(workspace, opts = {}) {
|
|
112
|
+
return sendCommand('bind-current', { workspace, ...opts });
|
|
113
|
+
}
|
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
* Used by both Page (daemon mode) and CDPPage (direct CDP mode)
|
|
5
5
|
* to eliminate code duplication for click, type, press, wait, scroll, etc.
|
|
6
6
|
*/
|
|
7
|
-
/** Generate JS to click an element by ref
|
|
7
|
+
/** Generate JS to click an element by ref.
|
|
8
|
+
* Returns { status, x, y, w, h } for CDP fallback when JS click fails. */
|
|
8
9
|
export declare function clickJs(ref: string): string;
|
|
9
|
-
/** Generate JS to type text into an element by ref
|
|
10
|
+
/** Generate JS to type text into an element by ref.
|
|
11
|
+
* Uses native setter for React compat + execCommand for contenteditable. */
|
|
10
12
|
export declare function typeTextJs(ref: string, text: string): string;
|
|
11
13
|
/** Generate JS to press a keyboard key */
|
|
12
14
|
export declare function pressKeyJs(key: string): string;
|
|
@@ -4,63 +4,74 @@
|
|
|
4
4
|
* Used by both Page (daemon mode) and CDPPage (direct CDP mode)
|
|
5
5
|
* to eliminate code duplication for click, type, press, wait, scroll, etc.
|
|
6
6
|
*/
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
const safeRef = JSON.stringify(ref);
|
|
7
|
+
/** Shared element lookup JS fragment (4-strategy resolution) */
|
|
8
|
+
function resolveElementJs(safeRef, selectorSet) {
|
|
10
9
|
return `
|
|
11
|
-
(() => {
|
|
12
10
|
const ref = ${safeRef};
|
|
13
|
-
// 1. data-opencli-ref (set by snapshot engine)
|
|
14
11
|
let el = document.querySelector('[data-opencli-ref="' + ref + '"]');
|
|
15
|
-
// 2. data-ref (legacy)
|
|
16
12
|
if (!el) el = document.querySelector('[data-ref="' + ref + '"]');
|
|
17
|
-
// 3. CSS selector
|
|
18
13
|
if (!el && ref.match(/^[a-zA-Z#.\\[]/)) {
|
|
19
14
|
try { el = document.querySelector(ref); } catch {}
|
|
20
15
|
}
|
|
21
|
-
// 4. Numeric index into interactive elements
|
|
22
16
|
if (!el) {
|
|
23
17
|
const idx = parseInt(ref, 10);
|
|
24
18
|
if (!isNaN(idx)) {
|
|
25
|
-
el = document.querySelectorAll('
|
|
19
|
+
el = document.querySelectorAll('${selectorSet}')[idx];
|
|
26
20
|
}
|
|
27
|
-
}
|
|
21
|
+
}`;
|
|
22
|
+
}
|
|
23
|
+
/** Generate JS to click an element by ref.
|
|
24
|
+
* Returns { status, x, y, w, h } for CDP fallback when JS click fails. */
|
|
25
|
+
export function clickJs(ref) {
|
|
26
|
+
const safeRef = JSON.stringify(ref);
|
|
27
|
+
return `
|
|
28
|
+
(() => {
|
|
29
|
+
${resolveElementJs(safeRef, 'a, button, input, select, textarea, [role="button"], [tabindex]:not([tabindex="-1"])')}
|
|
28
30
|
if (!el) throw new Error('Element not found: ' + ref);
|
|
29
31
|
el.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
30
|
-
el.
|
|
31
|
-
|
|
32
|
+
const rect = el.getBoundingClientRect();
|
|
33
|
+
const x = Math.round(rect.left + rect.width / 2);
|
|
34
|
+
const y = Math.round(rect.top + rect.height / 2);
|
|
35
|
+
try {
|
|
36
|
+
el.click();
|
|
37
|
+
return { status: 'clicked', x, y, w: Math.round(rect.width), h: Math.round(rect.height) };
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return { status: 'js_failed', x, y, w: Math.round(rect.width), h: Math.round(rect.height), error: e.message };
|
|
40
|
+
}
|
|
32
41
|
})()
|
|
33
42
|
`;
|
|
34
43
|
}
|
|
35
|
-
/** Generate JS to type text into an element by ref
|
|
44
|
+
/** Generate JS to type text into an element by ref.
|
|
45
|
+
* Uses native setter for React compat + execCommand for contenteditable. */
|
|
36
46
|
export function typeTextJs(ref, text) {
|
|
37
47
|
const safeRef = JSON.stringify(ref);
|
|
38
48
|
const safeText = JSON.stringify(text);
|
|
39
49
|
return `
|
|
40
50
|
(() => {
|
|
41
|
-
|
|
42
|
-
// 1. data-opencli-ref (set by snapshot engine)
|
|
43
|
-
let el = document.querySelector('[data-opencli-ref="' + ref + '"]');
|
|
44
|
-
// 2. data-ref (legacy)
|
|
45
|
-
if (!el) el = document.querySelector('[data-ref="' + ref + '"]');
|
|
46
|
-
// 3. CSS selector
|
|
47
|
-
if (!el && ref.match(/^[a-zA-Z#.\\[]/)) {
|
|
48
|
-
try { el = document.querySelector(ref); } catch {}
|
|
49
|
-
}
|
|
50
|
-
// 4. Numeric index into typeable elements
|
|
51
|
-
if (!el) {
|
|
52
|
-
const idx = parseInt(ref, 10);
|
|
53
|
-
if (!isNaN(idx)) {
|
|
54
|
-
el = document.querySelectorAll('input, textarea, [contenteditable="true"]')[idx];
|
|
55
|
-
}
|
|
56
|
-
}
|
|
51
|
+
${resolveElementJs(safeRef, 'input, textarea, [contenteditable="true"]')}
|
|
57
52
|
if (!el) throw new Error('Element not found: ' + ref);
|
|
58
53
|
el.focus();
|
|
59
54
|
if (el.isContentEditable) {
|
|
60
|
-
|
|
55
|
+
// Select all content + delete, then insert (supports undo, works with rich text editors)
|
|
56
|
+
const sel = window.getSelection();
|
|
57
|
+
const range = document.createRange();
|
|
58
|
+
range.selectNodeContents(el);
|
|
59
|
+
sel.removeAllRanges();
|
|
60
|
+
sel.addRange(range);
|
|
61
|
+
document.execCommand('delete', false);
|
|
62
|
+
document.execCommand('insertText', false, ${safeText});
|
|
61
63
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
62
64
|
} else {
|
|
63
|
-
|
|
65
|
+
// Use native setter for React/framework compatibility (match element type)
|
|
66
|
+
const proto = el instanceof HTMLTextAreaElement
|
|
67
|
+
? HTMLTextAreaElement.prototype
|
|
68
|
+
: HTMLInputElement.prototype;
|
|
69
|
+
const nativeSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
|
|
70
|
+
if (nativeSetter) {
|
|
71
|
+
nativeSetter.call(el, ${safeText});
|
|
72
|
+
} else {
|
|
73
|
+
el.value = ${safeText};
|
|
74
|
+
}
|
|
64
75
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
65
76
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
66
77
|
}
|
|
@@ -336,6 +336,8 @@ export function generateSnapshotJs(opts = {}) {
|
|
|
336
336
|
if (role && INTERACTIVE_ROLES.has(role)) return true;
|
|
337
337
|
if (el.hasAttribute('onclick') || el.hasAttribute('onmousedown') || el.hasAttribute('ontouchstart')) return true;
|
|
338
338
|
if (el.hasAttribute('tabindex') && el.getAttribute('tabindex') !== '-1') return true;
|
|
339
|
+
// Framework event listener detection (React/Vue/Angular onClick)
|
|
340
|
+
if (hasFrameworkListener(el)) return true;
|
|
339
341
|
try { if (window.getComputedStyle(el).cursor === 'pointer') return true; } catch {}
|
|
340
342
|
if (el.isContentEditable && el.getAttribute('contenteditable') !== 'false') return true;
|
|
341
343
|
// Search element heuristic detection
|
|
@@ -343,9 +345,29 @@ export function generateSnapshotJs(opts = {}) {
|
|
|
343
345
|
return false;
|
|
344
346
|
}
|
|
345
347
|
|
|
348
|
+
function hasFrameworkListener(el) {
|
|
349
|
+
try {
|
|
350
|
+
// React: __reactProps$xxx / __reactEvents$xxx with onClick/onMouseDown
|
|
351
|
+
for (const key of Object.keys(el)) {
|
|
352
|
+
if (key.startsWith('__reactProps$') || key.startsWith('__reactEvents$')) {
|
|
353
|
+
const props = el[key];
|
|
354
|
+
if (props && (props.onClick || props.onMouseDown || props.onPointerDown)) return true;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
// Vue 3: _vei (Vue Event Invoker) with onClick
|
|
358
|
+
if (el._vei && (el._vei.onClick || el._vei.click || el._vei.onMousedown)) return true;
|
|
359
|
+
// Vue 2: __vue__ instance with $listeners
|
|
360
|
+
if (el.__vue__?.$listeners?.click) return true;
|
|
361
|
+
// Angular: ng-reflect-click binding
|
|
362
|
+
if (el.hasAttribute('ng-reflect-click')) return true;
|
|
363
|
+
} catch { /* ignore errors from cross-origin or frozen objects */ }
|
|
364
|
+
return false;
|
|
365
|
+
}
|
|
366
|
+
|
|
346
367
|
function isSearchElement(el) {
|
|
347
368
|
// Check class names for search indicators
|
|
348
|
-
|
|
369
|
+
// Note: SVG elements have className as SVGAnimatedString (not a string), use baseVal
|
|
370
|
+
const className = (typeof el.className === 'string' ? el.className : el.className?.baseVal || '').toLowerCase();
|
|
349
371
|
const classes = className.split(/\\s+/).filter(Boolean);
|
|
350
372
|
for (const cls of classes) {
|
|
351
373
|
const cleaned = cls.replace(/[^a-z0-9-]/g, '');
|
package/dist/browser/page.d.ts
CHANGED
|
@@ -37,20 +37,25 @@ export declare class Page extends BasePage {
|
|
|
37
37
|
/** Close the automation window in the extension */
|
|
38
38
|
closeWindow(): Promise<void>;
|
|
39
39
|
tabs(): Promise<unknown[]>;
|
|
40
|
-
closeTab(index?: number): Promise<void>;
|
|
41
|
-
newTab(): Promise<void>;
|
|
42
40
|
selectTab(index: number): Promise<void>;
|
|
43
41
|
/**
|
|
44
42
|
* Capture a screenshot via CDP Page.captureScreenshot.
|
|
45
43
|
*/
|
|
46
44
|
screenshot(options?: ScreenshotOptions): Promise<string>;
|
|
45
|
+
startNetworkCapture(pattern?: string): Promise<void>;
|
|
46
|
+
readNetworkCapture(): Promise<unknown[]>;
|
|
47
47
|
/**
|
|
48
48
|
* Set local file paths on a file input element via CDP DOM.setFileInputFiles.
|
|
49
49
|
* Chrome reads the files directly from the local filesystem, avoiding the
|
|
50
50
|
* payload size limits of base64-in-evaluate.
|
|
51
51
|
*/
|
|
52
52
|
setFileInput(files: string[], selector?: string): Promise<void>;
|
|
53
|
+
insertText(text: string): Promise<void>;
|
|
53
54
|
cdp(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
55
|
+
/** CDP native click fallback — called when JS el.click() fails */
|
|
56
|
+
protected tryNativeClick(x: number, y: number): Promise<boolean>;
|
|
57
|
+
/** Precise click using DOM.getContentQuads/getBoxModel for inline elements */
|
|
58
|
+
clickWithQuads(ref: string): Promise<void>;
|
|
54
59
|
nativeClick(x: number, y: number): Promise<void>;
|
|
55
60
|
nativeType(text: string): Promise<void>;
|
|
56
61
|
nativeKeyPress(key: string, modifiers?: string[]): Promise<void>;
|
package/dist/browser/page.js
CHANGED
|
@@ -52,26 +52,17 @@ export class Page extends BasePage {
|
|
|
52
52
|
this._tabId = result.tabId;
|
|
53
53
|
}
|
|
54
54
|
this._lastUrl = url;
|
|
55
|
-
// Inject stealth
|
|
56
|
-
|
|
57
|
-
await sendCommand('exec', {
|
|
58
|
-
code: generateStealthJs(),
|
|
59
|
-
...this._cmdOpts(),
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
// Non-fatal: stealth is best-effort
|
|
64
|
-
}
|
|
65
|
-
// Smart settle: use DOM stability detection instead of fixed sleep.
|
|
66
|
-
// settleMs is now a timeout cap (default 1000ms), not a fixed wait.
|
|
55
|
+
// Inject stealth + settle in a single round-trip instead of two sequential exec calls.
|
|
56
|
+
// The stealth guard flag prevents double-injection; settle uses DOM stability detection.
|
|
67
57
|
if (options?.waitUntil !== 'none') {
|
|
68
58
|
const maxMs = options?.settleMs ?? 1000;
|
|
69
|
-
const
|
|
70
|
-
|
|
59
|
+
const combinedCode = `${generateStealthJs()};\n${waitForDomStableJs(maxMs, Math.min(500, maxMs))}`;
|
|
60
|
+
const combinedOpts = {
|
|
61
|
+
code: combinedCode,
|
|
71
62
|
...this._cmdOpts(),
|
|
72
63
|
};
|
|
73
64
|
try {
|
|
74
|
-
await sendCommand('exec',
|
|
65
|
+
await sendCommand('exec', combinedOpts);
|
|
75
66
|
}
|
|
76
67
|
catch (err) {
|
|
77
68
|
if (!isRetryableSettleError(err))
|
|
@@ -81,17 +72,26 @@ export class Page extends BasePage {
|
|
|
81
72
|
// to load, then retry the settle probe once.
|
|
82
73
|
try {
|
|
83
74
|
await new Promise((r) => setTimeout(r, 200));
|
|
84
|
-
await sendCommand('exec',
|
|
75
|
+
await sendCommand('exec', combinedOpts);
|
|
85
76
|
}
|
|
86
77
|
catch (retryErr) {
|
|
87
78
|
if (!isRetryableSettleError(retryErr))
|
|
88
79
|
throw retryErr;
|
|
89
|
-
// Retry also failed — give up silently. Settle is best-effort
|
|
90
|
-
// after successful navigation; the next real command will surface
|
|
91
|
-
// any persistent target error immediately.
|
|
92
80
|
}
|
|
93
81
|
}
|
|
94
82
|
}
|
|
83
|
+
else {
|
|
84
|
+
// Even with waitUntil='none', still inject stealth (best-effort)
|
|
85
|
+
try {
|
|
86
|
+
await sendCommand('exec', {
|
|
87
|
+
code: generateStealthJs(),
|
|
88
|
+
...this._cmdOpts(),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Non-fatal: stealth is best-effort
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
95
|
}
|
|
96
96
|
getActiveTabId() {
|
|
97
97
|
return this._tabId;
|
|
@@ -120,22 +120,15 @@ export class Page extends BasePage {
|
|
|
120
120
|
catch {
|
|
121
121
|
// Window may already be closed or daemon may be down
|
|
122
122
|
}
|
|
123
|
+
finally {
|
|
124
|
+
this._tabId = undefined;
|
|
125
|
+
this._lastUrl = null;
|
|
126
|
+
}
|
|
123
127
|
}
|
|
124
128
|
async tabs() {
|
|
125
129
|
const result = await sendCommand('tabs', { op: 'list', ...this._wsOpt() });
|
|
126
130
|
return Array.isArray(result) ? result : [];
|
|
127
131
|
}
|
|
128
|
-
async closeTab(index) {
|
|
129
|
-
await sendCommand('tabs', { op: 'close', ...this._wsOpt(), ...(index !== undefined ? { index } : {}) });
|
|
130
|
-
// Invalidate cached tabId — the closed tab might have been our active one.
|
|
131
|
-
// We can't know for sure (close-by-index doesn't return tabId), so reset.
|
|
132
|
-
this._tabId = undefined;
|
|
133
|
-
}
|
|
134
|
-
async newTab() {
|
|
135
|
-
const result = await sendCommand('tabs', { op: 'new', ...this._wsOpt() });
|
|
136
|
-
if (result?.tabId)
|
|
137
|
-
this._tabId = result.tabId;
|
|
138
|
-
}
|
|
139
132
|
async selectTab(index) {
|
|
140
133
|
const result = await sendCommand('tabs', { op: 'select', index, ...this._wsOpt() });
|
|
141
134
|
if (result?.selected)
|
|
@@ -156,6 +149,18 @@ export class Page extends BasePage {
|
|
|
156
149
|
}
|
|
157
150
|
return base64;
|
|
158
151
|
}
|
|
152
|
+
async startNetworkCapture(pattern = '') {
|
|
153
|
+
await sendCommand('network-capture-start', {
|
|
154
|
+
pattern,
|
|
155
|
+
...this._cmdOpts(),
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async readNetworkCapture() {
|
|
159
|
+
const result = await sendCommand('network-capture-read', {
|
|
160
|
+
...this._cmdOpts(),
|
|
161
|
+
});
|
|
162
|
+
return Array.isArray(result) ? result : [];
|
|
163
|
+
}
|
|
159
164
|
/**
|
|
160
165
|
* Set local file paths on a file input element via CDP DOM.setFileInputFiles.
|
|
161
166
|
* Chrome reads the files directly from the local filesystem, avoiding the
|
|
@@ -171,6 +176,15 @@ export class Page extends BasePage {
|
|
|
171
176
|
throw new Error('setFileInput returned no count — command may not be supported by the extension');
|
|
172
177
|
}
|
|
173
178
|
}
|
|
179
|
+
async insertText(text) {
|
|
180
|
+
const result = await sendCommand('insert-text', {
|
|
181
|
+
text,
|
|
182
|
+
...this._cmdOpts(),
|
|
183
|
+
});
|
|
184
|
+
if (!result?.inserted) {
|
|
185
|
+
throw new Error('insertText returned no inserted flag — command may not be supported by the extension');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
174
188
|
async cdp(method, params = {}) {
|
|
175
189
|
return sendCommand('cdp', {
|
|
176
190
|
cdpMethod: method,
|
|
@@ -178,6 +192,74 @@ export class Page extends BasePage {
|
|
|
178
192
|
...this._cmdOpts(),
|
|
179
193
|
});
|
|
180
194
|
}
|
|
195
|
+
/** CDP native click fallback — called when JS el.click() fails */
|
|
196
|
+
async tryNativeClick(x, y) {
|
|
197
|
+
try {
|
|
198
|
+
await this.nativeClick(x, y);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
/** Precise click using DOM.getContentQuads/getBoxModel for inline elements */
|
|
206
|
+
async clickWithQuads(ref) {
|
|
207
|
+
const safeRef = JSON.stringify(ref);
|
|
208
|
+
const cssSelector = `[data-opencli-ref="${ref.replace(/"/g, '\\"')}"]`;
|
|
209
|
+
// Scroll element into view first
|
|
210
|
+
await this.evaluate(`
|
|
211
|
+
(() => {
|
|
212
|
+
const el = document.querySelector('[data-opencli-ref="' + ${safeRef} + '"]');
|
|
213
|
+
if (el) el.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
214
|
+
return !!el;
|
|
215
|
+
})()
|
|
216
|
+
`);
|
|
217
|
+
try {
|
|
218
|
+
// Find DOM node via CDP
|
|
219
|
+
const doc = await this.cdp('DOM.getDocument', {});
|
|
220
|
+
const result = await this.cdp('DOM.querySelectorAll', {
|
|
221
|
+
nodeId: doc.root.nodeId,
|
|
222
|
+
selector: cssSelector,
|
|
223
|
+
});
|
|
224
|
+
if (!result.nodeIds?.length)
|
|
225
|
+
throw new Error('DOM node not found');
|
|
226
|
+
const nodeId = result.nodeIds[0];
|
|
227
|
+
// Try getContentQuads first (precise for inline elements)
|
|
228
|
+
try {
|
|
229
|
+
const quads = await this.cdp('DOM.getContentQuads', { nodeId });
|
|
230
|
+
if (quads.quads?.length) {
|
|
231
|
+
const q = quads.quads[0];
|
|
232
|
+
const cx = (q[0] + q[2] + q[4] + q[6]) / 4;
|
|
233
|
+
const cy = (q[1] + q[3] + q[5] + q[7]) / 4;
|
|
234
|
+
await this.nativeClick(Math.round(cx), Math.round(cy));
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch { /* fallthrough */ }
|
|
239
|
+
// Try getBoxModel
|
|
240
|
+
try {
|
|
241
|
+
const box = await this.cdp('DOM.getBoxModel', { nodeId });
|
|
242
|
+
if (box.model?.content) {
|
|
243
|
+
const c = box.model.content;
|
|
244
|
+
const cx = (c[0] + c[2] + c[4] + c[6]) / 4;
|
|
245
|
+
const cy = (c[1] + c[3] + c[5] + c[7]) / 4;
|
|
246
|
+
await this.nativeClick(Math.round(cx), Math.round(cy));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch { /* fallthrough */ }
|
|
251
|
+
}
|
|
252
|
+
catch { /* fallthrough */ }
|
|
253
|
+
// Final fallback: regular click
|
|
254
|
+
await this.evaluate(`
|
|
255
|
+
(() => {
|
|
256
|
+
const el = document.querySelector('[data-opencli-ref="' + ${safeRef} + '"]');
|
|
257
|
+
if (!el) throw new Error('Element not found: ' + ${safeRef});
|
|
258
|
+
el.click();
|
|
259
|
+
return 'clicked';
|
|
260
|
+
})()
|
|
261
|
+
`);
|
|
262
|
+
}
|
|
181
263
|
async nativeClick(x, y) {
|
|
182
264
|
await this.cdp('Input.dispatchMouseEvent', {
|
|
183
265
|
type: 'mousePressed',
|
package/dist/browser.test.js
CHANGED
|
@@ -106,7 +106,7 @@ describe('BrowserBridge state', () => {
|
|
|
106
106
|
});
|
|
107
107
|
it('fails fast when daemon is running but extension is disconnected', async () => {
|
|
108
108
|
vi.spyOn(daemonClient, 'isExtensionConnected').mockResolvedValue(false);
|
|
109
|
-
vi.spyOn(daemonClient, '
|
|
109
|
+
vi.spyOn(daemonClient, 'fetchDaemonStatus').mockResolvedValue({ extensionConnected: false });
|
|
110
110
|
const bridge = new BrowserBridge();
|
|
111
111
|
await expect(bridge.connect({ timeout: 0.1 })).rejects.toThrow('Browser Extension is not connected');
|
|
112
112
|
});
|
package/dist/build-manifest.d.ts
CHANGED
package/dist/build-manifest.js
CHANGED