@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
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# 其他垂直源
|
|
2
|
+
|
|
3
|
+
用于求职、金融、书籍、词典等不适合归入前述分类的场景。
|
|
4
|
+
|
|
5
|
+
## 站点
|
|
6
|
+
|
|
7
|
+
### linkedin
|
|
8
|
+
|
|
9
|
+
- 适用:全球职位、英文岗位、跨国公司招聘
|
|
10
|
+
- 使用前先运行:`opencli linkedin -h`
|
|
11
|
+
|
|
12
|
+
### boss
|
|
13
|
+
|
|
14
|
+
- 适用:国内职位搜索、招聘与岗位信息
|
|
15
|
+
- 使用前先运行:`opencli boss -h`
|
|
16
|
+
|
|
17
|
+
### xueqiu
|
|
18
|
+
|
|
19
|
+
- 适用:股票、金融讨论、行情相关线索
|
|
20
|
+
- 使用前先运行:`opencli xueqiu -h`
|
|
21
|
+
|
|
22
|
+
### weread
|
|
23
|
+
|
|
24
|
+
- 适用:中文书籍搜索与书单线索
|
|
25
|
+
- 使用前先运行:`opencli weread -h`
|
|
26
|
+
|
|
27
|
+
### dictionary
|
|
28
|
+
|
|
29
|
+
- 适用:英文词义、基础词典查询
|
|
30
|
+
- 使用前先运行:`opencli dictionary -h`
|
|
31
|
+
|
|
32
|
+
### sinablog
|
|
33
|
+
|
|
34
|
+
- 适用:较旧的中文博客内容
|
|
35
|
+
- 使用前先运行:`opencli sinablog -h`
|
|
36
|
+
|
|
37
|
+
## 路由提示
|
|
38
|
+
|
|
39
|
+
- 求职:全球优先 `linkedin`,国内优先 `boss`
|
|
40
|
+
- 金融:优先 `xueqiu`
|
|
41
|
+
- 书籍:中文优先 `weread`
|
|
42
|
+
- 英文单词定义:优先 `dictionary`
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# 购物
|
|
2
|
+
|
|
3
|
+
用于商品搜索、价格、好价、口碑与区域电商结果。
|
|
4
|
+
|
|
5
|
+
## 站点
|
|
6
|
+
|
|
7
|
+
### amazon
|
|
8
|
+
|
|
9
|
+
- 适用:全球商品搜索、价格参考、英文电商
|
|
10
|
+
- 使用前先运行:`opencli amazon -h`
|
|
11
|
+
|
|
12
|
+
### smzdm
|
|
13
|
+
|
|
14
|
+
- 适用:国内好价、优惠、导购、商品讨论
|
|
15
|
+
- 使用前先运行:`opencli smzdm -h`
|
|
16
|
+
|
|
17
|
+
### coupang
|
|
18
|
+
|
|
19
|
+
- 适用:韩国电商商品搜索
|
|
20
|
+
- 使用前先运行:`opencli coupang -h`
|
|
21
|
+
|
|
22
|
+
### douban
|
|
23
|
+
|
|
24
|
+
- 适用:图书、影视、音乐类口碑补充,不是标准电商,但可用于消费决策补充
|
|
25
|
+
- 使用前先运行:`opencli douban -h`
|
|
26
|
+
|
|
27
|
+
## 路由提示
|
|
28
|
+
|
|
29
|
+
- 用户指定平台时直接使用该平台
|
|
30
|
+
- 未指定平台时,全球商品优先 `amazon`,国内好价优先 `smzdm`
|
|
31
|
+
- 若先用 AI 做产品调研,后续可补电商站点拿实际商品结果
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# 社交媒体
|
|
2
|
+
|
|
3
|
+
用于需要原始帖子、原始用户结果、中文或英文社区讨论时。
|
|
4
|
+
|
|
5
|
+
## 站点
|
|
6
|
+
|
|
7
|
+
### twitter
|
|
8
|
+
|
|
9
|
+
- 适用:Twitter/X 原始帖子、作者、实时讨论
|
|
10
|
+
- 使用前先运行:`opencli twitter -h`
|
|
11
|
+
|
|
12
|
+
### weibo
|
|
13
|
+
|
|
14
|
+
- 适用:微博热点、话题、中文舆论
|
|
15
|
+
- 使用前先运行:`opencli weibo -h`
|
|
16
|
+
|
|
17
|
+
### xiaohongshu
|
|
18
|
+
|
|
19
|
+
- 适用:生活方式、穿搭、美妆、旅行、真实体验
|
|
20
|
+
- 使用前先运行:`opencli xiaohongshu -h`
|
|
21
|
+
|
|
22
|
+
### zhihu
|
|
23
|
+
|
|
24
|
+
- 适用:中文深度问答、专业解释、行业经验
|
|
25
|
+
- 使用前先运行:`opencli zhihu -h`
|
|
26
|
+
|
|
27
|
+
### tieba
|
|
28
|
+
|
|
29
|
+
- 适用:兴趣圈子、历史帖子、粉丝社区
|
|
30
|
+
- 使用前先运行:`opencli tieba -h`
|
|
31
|
+
|
|
32
|
+
### instagram
|
|
33
|
+
|
|
34
|
+
- 适用:账号搜索、图片社交线索
|
|
35
|
+
- 使用前先运行:`opencli instagram -h`
|
|
36
|
+
|
|
37
|
+
### facebook
|
|
38
|
+
|
|
39
|
+
- 适用:主页、人物、帖子线索
|
|
40
|
+
- 使用前先运行:`opencli facebook -h`
|
|
41
|
+
|
|
42
|
+
### bluesky
|
|
43
|
+
|
|
44
|
+
- 适用:Bluesky 用户与账号搜索
|
|
45
|
+
- 使用前先运行:`opencli bluesky -h`
|
|
46
|
+
|
|
47
|
+
## 路由提示
|
|
48
|
+
|
|
49
|
+
- 用户明确指定某个平台时,直接用该平台
|
|
50
|
+
- 用户只说“社交媒体上怎么看”时,可先选 `grok` 或 `doubao`
|
|
51
|
+
- AI 只给摘要而没有原始帖子时,再切到对应社交站点
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# 技术 / 学术
|
|
2
|
+
|
|
3
|
+
用于技术问题、研究论文、开发者讨论、开源社区信息。
|
|
4
|
+
|
|
5
|
+
## 站点
|
|
6
|
+
|
|
7
|
+
### arxiv
|
|
8
|
+
|
|
9
|
+
- 适用:论文、研究、模型、算法、学术背景
|
|
10
|
+
- 使用前先运行:`opencli arxiv -h`
|
|
11
|
+
|
|
12
|
+
### stackoverflow
|
|
13
|
+
|
|
14
|
+
- 适用:具体报错、API 用法、代码模式、实现细节
|
|
15
|
+
- 使用前先运行:`opencli stackoverflow -h`
|
|
16
|
+
|
|
17
|
+
### hackernews
|
|
18
|
+
|
|
19
|
+
- 适用:技术社区讨论、开发者观点、创业和产品话题
|
|
20
|
+
- 使用前先运行:`opencli hackernews -h`
|
|
21
|
+
|
|
22
|
+
### reddit
|
|
23
|
+
|
|
24
|
+
- 适用:英文社区问答、经验贴、推荐、对比讨论
|
|
25
|
+
- 使用前先运行:`opencli reddit -h`
|
|
26
|
+
|
|
27
|
+
### linux-do
|
|
28
|
+
|
|
29
|
+
- 适用:人工智能、开源工具、中文技术社区
|
|
30
|
+
- 使用前先运行:`opencli linux-do -h`
|
|
31
|
+
|
|
32
|
+
### v2ex
|
|
33
|
+
|
|
34
|
+
- 适用:中文技术社区
|
|
35
|
+
- 使用前先运行:`opencli linux-do
|
|
36
|
+
|
|
37
|
+
## 路由提示
|
|
38
|
+
|
|
39
|
+
- 用户明确说“论文”“研究”时,优先 `arxiv`
|
|
40
|
+
- 用户明确说“报错”“API 怎么用”时,优先 `stackoverflow`
|
|
41
|
+
- 用户要“社区讨论”“开发者观点”时,优先 `hackernews` 或 `reddit`
|
|
42
|
+
- 若用户没有明确指定站点,可先选 `gemini` 或 `grok`,内容不足时再补这些专用源
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 旅游
|
|
2
|
+
|
|
3
|
+
用于目的地、景区、酒店联想和旅行线索。
|
|
4
|
+
|
|
5
|
+
## 站点
|
|
6
|
+
|
|
7
|
+
### ctrip
|
|
8
|
+
|
|
9
|
+
- 适用:目的地、景区、酒店、旅行联想搜索
|
|
10
|
+
- 使用前先运行:`opencli ctrip -h`
|
|
11
|
+
|
|
12
|
+
### xiaohongshu
|
|
13
|
+
|
|
14
|
+
- 适用:生活方式、穿搭、美妆、旅行、真实体验
|
|
15
|
+
- 使用前先运行:`opencli xiaohongshu -h`
|
|
16
|
+
|
|
17
|
+
## 路由提示
|
|
18
|
+
|
|
19
|
+
- 用户明确说“携程”时直接使用 `ctrip`
|
|
20
|
+
- 用户只说“旅行/目的地/景区”时,可先选 `doubao` 做中文语境粗检索,再补 `ctrip`
|
package/src/browser/base-page.ts
CHANGED
|
@@ -27,6 +27,8 @@ import { formatSnapshot } from '../snapshotFormatter.js';
|
|
|
27
27
|
|
|
28
28
|
export abstract class BasePage implements IPage {
|
|
29
29
|
protected _lastUrl: string | null = null;
|
|
30
|
+
/** Cached previous snapshot hashes for incremental diff marking */
|
|
31
|
+
private _prevSnapshotHashes: string | null = null;
|
|
30
32
|
|
|
31
33
|
// ── Transport-specific methods (must be implemented by subclasses) ──
|
|
32
34
|
|
|
@@ -35,14 +37,34 @@ export abstract class BasePage implements IPage {
|
|
|
35
37
|
abstract getCookies(opts?: { domain?: string; url?: string }): Promise<BrowserCookie[]>;
|
|
36
38
|
abstract screenshot(options?: ScreenshotOptions): Promise<string>;
|
|
37
39
|
abstract tabs(): Promise<unknown[]>;
|
|
38
|
-
abstract closeTab(index?: number): Promise<void>;
|
|
39
|
-
abstract newTab(): Promise<void>;
|
|
40
40
|
abstract selectTab(index: number): Promise<void>;
|
|
41
41
|
|
|
42
42
|
// ── Shared DOM helper implementations ──
|
|
43
43
|
|
|
44
44
|
async click(ref: string): Promise<void> {
|
|
45
|
-
await this.evaluate(clickJs(ref))
|
|
45
|
+
const result = await this.evaluate(clickJs(ref)) as
|
|
46
|
+
| string
|
|
47
|
+
| { status: string; x?: number; y?: number; w?: number; h?: number; error?: string }
|
|
48
|
+
| null;
|
|
49
|
+
|
|
50
|
+
// Backwards compat: old format returned 'clicked' string
|
|
51
|
+
if (typeof result === 'string' || result == null) return;
|
|
52
|
+
|
|
53
|
+
// JS click succeeded
|
|
54
|
+
if (result.status === 'clicked') return;
|
|
55
|
+
|
|
56
|
+
// JS click failed — try CDP native click if coordinates available
|
|
57
|
+
if (result.x != null && result.y != null) {
|
|
58
|
+
const success = await this.tryNativeClick(result.x, result.y);
|
|
59
|
+
if (success) return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
throw new Error(`Click failed: ${result.error ?? 'JS click and CDP fallback both failed'}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Override in subclasses with CDP native click support */
|
|
66
|
+
protected async tryNativeClick(_x: number, _y: number): Promise<boolean> {
|
|
67
|
+
return false;
|
|
46
68
|
}
|
|
47
69
|
|
|
48
70
|
async typeText(ref: string, text: string): Promise<void> {
|
|
@@ -111,17 +133,30 @@ export abstract class BasePage implements IPage {
|
|
|
111
133
|
|
|
112
134
|
async snapshot(opts: SnapshotOptions = {}): Promise<unknown> {
|
|
113
135
|
const snapshotJs = generateSnapshotJs({
|
|
114
|
-
viewportExpand: opts.viewportExpand ??
|
|
136
|
+
viewportExpand: opts.viewportExpand ?? 2000,
|
|
115
137
|
maxDepth: Math.max(1, Math.min(Number(opts.maxDepth) || 50, 200)),
|
|
116
138
|
interactiveOnly: opts.interactive ?? false,
|
|
117
139
|
maxTextLength: opts.maxTextLength ?? 120,
|
|
118
140
|
includeScrollInfo: true,
|
|
119
141
|
bboxDedup: true,
|
|
142
|
+
previousHashes: this._prevSnapshotHashes,
|
|
120
143
|
});
|
|
121
144
|
|
|
122
145
|
try {
|
|
123
|
-
|
|
124
|
-
|
|
146
|
+
const result = await this.evaluate(snapshotJs);
|
|
147
|
+
// Read back the hashes stored by the snapshot for next diff
|
|
148
|
+
try {
|
|
149
|
+
const hashes = await this.evaluate('window.__opencli_prev_hashes') as string | null;
|
|
150
|
+
this._prevSnapshotHashes = typeof hashes === 'string' ? hashes : null;
|
|
151
|
+
} catch {
|
|
152
|
+
// Non-fatal: diff is best-effort
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
} catch (err) {
|
|
156
|
+
// Log snapshot failure for debugging, then fallback to basic accessibility tree
|
|
157
|
+
if (process.env.DEBUG_SNAPSHOT) {
|
|
158
|
+
console.error('[snapshot] DOM snapshot failed, falling back to accessibility tree:', (err as Error)?.message?.slice(0, 200));
|
|
159
|
+
}
|
|
125
160
|
return this._basicSnapshot(opts);
|
|
126
161
|
}
|
|
127
162
|
}
|
package/src/browser/bridge.ts
CHANGED
|
@@ -9,7 +9,7 @@ import * as fs from 'node:fs';
|
|
|
9
9
|
import type { IPage } from '../types.js';
|
|
10
10
|
import type { IBrowserFactory } from '../runtime.js';
|
|
11
11
|
import { Page } from './page.js';
|
|
12
|
-
import {
|
|
12
|
+
import { fetchDaemonStatus, isExtensionConnected } from './daemon-client.js';
|
|
13
13
|
import { DEFAULT_DAEMON_PORT } from '../constants.js';
|
|
14
14
|
|
|
15
15
|
const DAEMON_SPAWN_TIMEOUT = 10000; // 10s to wait for daemon + extension
|
|
@@ -60,14 +60,17 @@ export class BrowserBridge implements IBrowserFactory {
|
|
|
60
60
|
const effectiveSeconds = (timeoutSeconds && timeoutSeconds > 0) ? timeoutSeconds : Math.ceil(DAEMON_SPAWN_TIMEOUT / 1000);
|
|
61
61
|
const timeoutMs = effectiveSeconds * 1000;
|
|
62
62
|
|
|
63
|
+
// Single status check instead of two separate fetchDaemonStatus() calls
|
|
64
|
+
const status = await fetchDaemonStatus();
|
|
65
|
+
|
|
63
66
|
// Fast path: extension already connected
|
|
64
|
-
if (
|
|
67
|
+
if (status?.extensionConnected) return;
|
|
65
68
|
|
|
66
69
|
// Daemon running but no extension — wait for extension with progress
|
|
67
|
-
if (
|
|
70
|
+
if (status !== null) {
|
|
68
71
|
if (process.env.OPENCLI_VERBOSE || process.stderr.isTTY) {
|
|
69
|
-
process.stderr.write('⏳ Waiting for Chrome extension to connect...\n');
|
|
70
|
-
process.stderr.write(' Make sure Chrome is open and the OpenCLI extension is enabled.\n');
|
|
72
|
+
process.stderr.write('⏳ Waiting for Chrome/Chromium extension to connect...\n');
|
|
73
|
+
process.stderr.write(' Make sure Chrome or Chromium is open and the OpenCLI extension is enabled.\n');
|
|
71
74
|
}
|
|
72
75
|
const deadline = Date.now() + timeoutMs;
|
|
73
76
|
while (Date.now() < deadline) {
|
|
@@ -76,7 +79,7 @@ export class BrowserBridge implements IBrowserFactory {
|
|
|
76
79
|
}
|
|
77
80
|
throw new Error(
|
|
78
81
|
'Daemon is running but the Browser Extension is not connected.\n' +
|
|
79
|
-
'Please install and enable the opencli Browser Bridge extension in Chrome.',
|
|
82
|
+
'Please install and enable the opencli Browser Bridge extension in Chrome or Chromium.',
|
|
80
83
|
);
|
|
81
84
|
}
|
|
82
85
|
|
|
@@ -110,10 +113,10 @@ export class BrowserBridge implements IBrowserFactory {
|
|
|
110
113
|
if (await isExtensionConnected()) return;
|
|
111
114
|
}
|
|
112
115
|
|
|
113
|
-
if (await
|
|
116
|
+
if ((await fetchDaemonStatus()) !== null) {
|
|
114
117
|
throw new Error(
|
|
115
118
|
'Daemon is running but the Browser Extension is not connected.\n' +
|
|
116
|
-
'Please install and enable the opencli Browser Bridge extension in Chrome.',
|
|
119
|
+
'Please install and enable the opencli Browser Bridge extension in Chrome or Chromium.',
|
|
117
120
|
);
|
|
118
121
|
}
|
|
119
122
|
|
package/src/browser/cdp.ts
CHANGED
|
@@ -223,14 +223,6 @@ class CDPPage extends BasePage {
|
|
|
223
223
|
return [];
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
-
async closeTab(_index?: number): Promise<void> {
|
|
227
|
-
// Not supported in direct CDP mode
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
async newTab(): Promise<void> {
|
|
231
|
-
await this.bridge.send('Target.createTarget', { url: 'about:blank' });
|
|
232
|
-
}
|
|
233
|
-
|
|
234
226
|
async selectTab(_index: number): Promise<void> {
|
|
235
227
|
// Not supported in direct CDP mode
|
|
236
228
|
}
|
|
@@ -274,6 +266,7 @@ function scoreCDPTarget(target: CDPTarget, preferredPattern?: RegExp): number {
|
|
|
274
266
|
|
|
275
267
|
if (!haystack.trim() && !type) return Number.NEGATIVE_INFINITY;
|
|
276
268
|
if (haystack.includes('devtools')) return Number.NEGATIVE_INFINITY;
|
|
269
|
+
if (type === 'background_page' || type === 'service_worker') return Number.NEGATIVE_INFINITY;
|
|
277
270
|
|
|
278
271
|
let score = 0;
|
|
279
272
|
|
|
@@ -21,7 +21,7 @@ function generateId(): string {
|
|
|
21
21
|
|
|
22
22
|
export interface DaemonCommand {
|
|
23
23
|
id: string;
|
|
24
|
-
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'cdp';
|
|
24
|
+
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions' | 'set-file-input' | 'insert-text' | 'bind-current' | 'network-capture-start' | 'network-capture-read' | 'cdp';
|
|
25
25
|
tabId?: number;
|
|
26
26
|
code?: string;
|
|
27
27
|
workspace?: string;
|
|
@@ -29,6 +29,8 @@ export interface DaemonCommand {
|
|
|
29
29
|
op?: string;
|
|
30
30
|
index?: number;
|
|
31
31
|
domain?: string;
|
|
32
|
+
matchDomain?: string;
|
|
33
|
+
matchPathPrefix?: string;
|
|
32
34
|
format?: 'png' | 'jpeg';
|
|
33
35
|
quality?: number;
|
|
34
36
|
fullPage?: boolean;
|
|
@@ -37,6 +39,10 @@ export interface DaemonCommand {
|
|
|
37
39
|
files?: string[];
|
|
38
40
|
/** CSS selector for file input element (set-file-input action) */
|
|
39
41
|
selector?: string;
|
|
42
|
+
/** Raw text payload for insert-text action */
|
|
43
|
+
text?: string;
|
|
44
|
+
/** URL substring filter pattern for network capture */
|
|
45
|
+
pattern?: string;
|
|
40
46
|
cdpMethod?: string;
|
|
41
47
|
cdpParams?: Record<string, unknown>;
|
|
42
48
|
}
|
|
@@ -163,3 +169,7 @@ export async function listSessions(): Promise<BrowserSessionInfo[]> {
|
|
|
163
169
|
const result = await sendCommand('sessions');
|
|
164
170
|
return Array.isArray(result) ? result : [];
|
|
165
171
|
}
|
|
172
|
+
|
|
173
|
+
export async function bindCurrentTab(workspace: string, opts: { matchDomain?: string; matchPathPrefix?: string } = {}): Promise<unknown> {
|
|
174
|
+
return sendCommand('bind-current', { workspace, ...opts });
|
|
175
|
+
}
|
|
@@ -5,64 +5,76 @@
|
|
|
5
5
|
* to eliminate code duplication for click, type, press, wait, scroll, etc.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
const safeRef = JSON.stringify(ref);
|
|
8
|
+
/** Shared element lookup JS fragment (4-strategy resolution) */
|
|
9
|
+
function resolveElementJs(safeRef: string, selectorSet: string): string {
|
|
11
10
|
return `
|
|
12
|
-
(() => {
|
|
13
11
|
const ref = ${safeRef};
|
|
14
|
-
// 1. data-opencli-ref (set by snapshot engine)
|
|
15
12
|
let el = document.querySelector('[data-opencli-ref="' + ref + '"]');
|
|
16
|
-
// 2. data-ref (legacy)
|
|
17
13
|
if (!el) el = document.querySelector('[data-ref="' + ref + '"]');
|
|
18
|
-
// 3. CSS selector
|
|
19
14
|
if (!el && ref.match(/^[a-zA-Z#.\\[]/)) {
|
|
20
15
|
try { el = document.querySelector(ref); } catch {}
|
|
21
16
|
}
|
|
22
|
-
// 4. Numeric index into interactive elements
|
|
23
17
|
if (!el) {
|
|
24
18
|
const idx = parseInt(ref, 10);
|
|
25
19
|
if (!isNaN(idx)) {
|
|
26
|
-
el = document.querySelectorAll('
|
|
20
|
+
el = document.querySelectorAll('${selectorSet}')[idx];
|
|
27
21
|
}
|
|
28
|
-
}
|
|
22
|
+
}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Generate JS to click an element by ref.
|
|
26
|
+
* Returns { status, x, y, w, h } for CDP fallback when JS click fails. */
|
|
27
|
+
export function clickJs(ref: string): string {
|
|
28
|
+
const safeRef = JSON.stringify(ref);
|
|
29
|
+
return `
|
|
30
|
+
(() => {
|
|
31
|
+
${resolveElementJs(safeRef, 'a, button, input, select, textarea, [role="button"], [tabindex]:not([tabindex="-1"])')}
|
|
29
32
|
if (!el) throw new Error('Element not found: ' + ref);
|
|
30
33
|
el.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
31
|
-
el.
|
|
32
|
-
|
|
34
|
+
const rect = el.getBoundingClientRect();
|
|
35
|
+
const x = Math.round(rect.left + rect.width / 2);
|
|
36
|
+
const y = Math.round(rect.top + rect.height / 2);
|
|
37
|
+
try {
|
|
38
|
+
el.click();
|
|
39
|
+
return { status: 'clicked', x, y, w: Math.round(rect.width), h: Math.round(rect.height) };
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return { status: 'js_failed', x, y, w: Math.round(rect.width), h: Math.round(rect.height), error: e.message };
|
|
42
|
+
}
|
|
33
43
|
})()
|
|
34
44
|
`;
|
|
35
45
|
}
|
|
36
46
|
|
|
37
|
-
/** Generate JS to type text into an element by ref
|
|
47
|
+
/** Generate JS to type text into an element by ref.
|
|
48
|
+
* Uses native setter for React compat + execCommand for contenteditable. */
|
|
38
49
|
export function typeTextJs(ref: string, text: string): string {
|
|
39
50
|
const safeRef = JSON.stringify(ref);
|
|
40
51
|
const safeText = JSON.stringify(text);
|
|
41
52
|
return `
|
|
42
53
|
(() => {
|
|
43
|
-
|
|
44
|
-
// 1. data-opencli-ref (set by snapshot engine)
|
|
45
|
-
let el = document.querySelector('[data-opencli-ref="' + ref + '"]');
|
|
46
|
-
// 2. data-ref (legacy)
|
|
47
|
-
if (!el) el = document.querySelector('[data-ref="' + ref + '"]');
|
|
48
|
-
// 3. CSS selector
|
|
49
|
-
if (!el && ref.match(/^[a-zA-Z#.\\[]/)) {
|
|
50
|
-
try { el = document.querySelector(ref); } catch {}
|
|
51
|
-
}
|
|
52
|
-
// 4. Numeric index into typeable elements
|
|
53
|
-
if (!el) {
|
|
54
|
-
const idx = parseInt(ref, 10);
|
|
55
|
-
if (!isNaN(idx)) {
|
|
56
|
-
el = document.querySelectorAll('input, textarea, [contenteditable="true"]')[idx];
|
|
57
|
-
}
|
|
58
|
-
}
|
|
54
|
+
${resolveElementJs(safeRef, 'input, textarea, [contenteditable="true"]')}
|
|
59
55
|
if (!el) throw new Error('Element not found: ' + ref);
|
|
60
56
|
el.focus();
|
|
61
57
|
if (el.isContentEditable) {
|
|
62
|
-
|
|
58
|
+
// Select all content + delete, then insert (supports undo, works with rich text editors)
|
|
59
|
+
const sel = window.getSelection();
|
|
60
|
+
const range = document.createRange();
|
|
61
|
+
range.selectNodeContents(el);
|
|
62
|
+
sel.removeAllRanges();
|
|
63
|
+
sel.addRange(range);
|
|
64
|
+
document.execCommand('delete', false);
|
|
65
|
+
document.execCommand('insertText', false, ${safeText});
|
|
63
66
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
64
67
|
} else {
|
|
65
|
-
|
|
68
|
+
// Use native setter for React/framework compatibility (match element type)
|
|
69
|
+
const proto = el instanceof HTMLTextAreaElement
|
|
70
|
+
? HTMLTextAreaElement.prototype
|
|
71
|
+
: HTMLInputElement.prototype;
|
|
72
|
+
const nativeSetter = Object.getOwnPropertyDescriptor(proto, 'value')?.set;
|
|
73
|
+
if (nativeSetter) {
|
|
74
|
+
nativeSetter.call(el, ${safeText});
|
|
75
|
+
} else {
|
|
76
|
+
el.value = ${safeText};
|
|
77
|
+
}
|
|
66
78
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
67
79
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
68
80
|
}
|
|
@@ -377,6 +377,8 @@ export function generateSnapshotJs(opts: DomSnapshotOptions = {}): string {
|
|
|
377
377
|
if (role && INTERACTIVE_ROLES.has(role)) return true;
|
|
378
378
|
if (el.hasAttribute('onclick') || el.hasAttribute('onmousedown') || el.hasAttribute('ontouchstart')) return true;
|
|
379
379
|
if (el.hasAttribute('tabindex') && el.getAttribute('tabindex') !== '-1') return true;
|
|
380
|
+
// Framework event listener detection (React/Vue/Angular onClick)
|
|
381
|
+
if (hasFrameworkListener(el)) return true;
|
|
380
382
|
try { if (window.getComputedStyle(el).cursor === 'pointer') return true; } catch {}
|
|
381
383
|
if (el.isContentEditable && el.getAttribute('contenteditable') !== 'false') return true;
|
|
382
384
|
// Search element heuristic detection
|
|
@@ -384,9 +386,29 @@ export function generateSnapshotJs(opts: DomSnapshotOptions = {}): string {
|
|
|
384
386
|
return false;
|
|
385
387
|
}
|
|
386
388
|
|
|
389
|
+
function hasFrameworkListener(el) {
|
|
390
|
+
try {
|
|
391
|
+
// React: __reactProps$xxx / __reactEvents$xxx with onClick/onMouseDown
|
|
392
|
+
for (const key of Object.keys(el)) {
|
|
393
|
+
if (key.startsWith('__reactProps$') || key.startsWith('__reactEvents$')) {
|
|
394
|
+
const props = el[key];
|
|
395
|
+
if (props && (props.onClick || props.onMouseDown || props.onPointerDown)) return true;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
// Vue 3: _vei (Vue Event Invoker) with onClick
|
|
399
|
+
if (el._vei && (el._vei.onClick || el._vei.click || el._vei.onMousedown)) return true;
|
|
400
|
+
// Vue 2: __vue__ instance with $listeners
|
|
401
|
+
if (el.__vue__?.$listeners?.click) return true;
|
|
402
|
+
// Angular: ng-reflect-click binding
|
|
403
|
+
if (el.hasAttribute('ng-reflect-click')) return true;
|
|
404
|
+
} catch { /* ignore errors from cross-origin or frozen objects */ }
|
|
405
|
+
return false;
|
|
406
|
+
}
|
|
407
|
+
|
|
387
408
|
function isSearchElement(el) {
|
|
388
409
|
// Check class names for search indicators
|
|
389
|
-
|
|
410
|
+
// Note: SVG elements have className as SVGAnimatedString (not a string), use baseVal
|
|
411
|
+
const className = (typeof el.className === 'string' ? el.className : el.className?.baseVal || '').toLowerCase();
|
|
390
412
|
const classes = className.split(/\\s+/).filter(Boolean);
|
|
391
413
|
for (const cls of classes) {
|
|
392
414
|
const cleaned = cls.replace(/[^a-z0-9-]/g, '');
|