@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
package/extension/src/cdp.ts
CHANGED
|
@@ -8,6 +8,27 @@
|
|
|
8
8
|
|
|
9
9
|
const attached = new Set<number>();
|
|
10
10
|
|
|
11
|
+
type NetworkCaptureEntry = {
|
|
12
|
+
kind: 'cdp';
|
|
13
|
+
url: string;
|
|
14
|
+
method: string;
|
|
15
|
+
requestHeaders?: Record<string, string>;
|
|
16
|
+
requestBodyKind?: string;
|
|
17
|
+
requestBodyPreview?: string;
|
|
18
|
+
responseStatus?: number;
|
|
19
|
+
responseContentType?: string;
|
|
20
|
+
responseHeaders?: Record<string, string>;
|
|
21
|
+
responsePreview?: string;
|
|
22
|
+
timestamp: number;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type NetworkCaptureState = {
|
|
26
|
+
patterns: string[];
|
|
27
|
+
entries: NetworkCaptureEntry[];
|
|
28
|
+
requestToIndex: Map<string, number>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const networkCaptures = new Map<number, NetworkCaptureState>();
|
|
11
32
|
/** Check if a URL can be attached via CDP — only allow http(s) and blank pages. */
|
|
12
33
|
function isDebuggableUrl(url?: string): boolean {
|
|
13
34
|
if (!url) return true; // empty/undefined = tab still loading, allow it
|
|
@@ -79,6 +100,16 @@ export async function ensureAttached(tabId: number, aggressiveRetry: boolean = f
|
|
|
79
100
|
}
|
|
80
101
|
|
|
81
102
|
if (lastError) {
|
|
103
|
+
// Log detailed diagnostics for debugging extension conflicts
|
|
104
|
+
let finalUrl = 'unknown';
|
|
105
|
+
let finalWindowId = 'unknown';
|
|
106
|
+
try {
|
|
107
|
+
const tab = await chrome.tabs.get(tabId);
|
|
108
|
+
finalUrl = tab.url ?? 'undefined';
|
|
109
|
+
finalWindowId = String(tab.windowId);
|
|
110
|
+
} catch { /* tab gone */ }
|
|
111
|
+
console.warn(`[opencli] attach failed for tab ${tabId}: url=${finalUrl}, windowId=${finalWindowId}, error=${lastError}`);
|
|
112
|
+
|
|
82
113
|
const hint = lastError.includes('chrome-extension://')
|
|
83
114
|
? '. Tip: another Chrome extension may be interfering — try disabling other extensions'
|
|
84
115
|
: '';
|
|
@@ -121,11 +152,14 @@ export async function evaluate(tabId: number, expression: string, aggressiveRetr
|
|
|
121
152
|
} catch (e) {
|
|
122
153
|
const msg = e instanceof Error ? e.message : String(e);
|
|
123
154
|
// Only retry on attach/debugger errors, not on JS eval errors
|
|
124
|
-
const
|
|
125
|
-
|
|
155
|
+
const isNavigateError = msg.includes('Inspected target navigated') || msg.includes('Target closed');
|
|
156
|
+
const isAttachError = isNavigateError || msg.includes('attach failed') || msg.includes('Debugger is not attached')
|
|
157
|
+
|| msg.includes('chrome-extension://');
|
|
126
158
|
if (isAttachError && attempt < MAX_EVAL_RETRIES) {
|
|
127
159
|
attached.delete(tabId); // Force re-attach on next attempt
|
|
128
|
-
|
|
160
|
+
// SPA navigations recover quickly; debugger detach needs longer
|
|
161
|
+
const retryMs = isNavigateError ? 200 : 500;
|
|
162
|
+
await new Promise(resolve => setTimeout(resolve, retryMs));
|
|
129
163
|
continue;
|
|
130
164
|
}
|
|
131
165
|
throw e;
|
|
@@ -228,18 +262,100 @@ export async function setFileInputFiles(
|
|
|
228
262
|
});
|
|
229
263
|
}
|
|
230
264
|
|
|
265
|
+
export async function insertText(
|
|
266
|
+
tabId: number,
|
|
267
|
+
text: string,
|
|
268
|
+
): Promise<void> {
|
|
269
|
+
await ensureAttached(tabId);
|
|
270
|
+
await chrome.debugger.sendCommand({ tabId }, 'Input.insertText', { text });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function normalizeCapturePatterns(pattern?: string): string[] {
|
|
274
|
+
return String(pattern || '')
|
|
275
|
+
.split('|')
|
|
276
|
+
.map((part) => part.trim())
|
|
277
|
+
.filter(Boolean);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function shouldCaptureUrl(url: string | undefined, patterns: string[]): boolean {
|
|
281
|
+
if (!url) return false;
|
|
282
|
+
if (!patterns.length) return true;
|
|
283
|
+
return patterns.some((pattern) => url.includes(pattern));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function normalizeHeaders(headers: unknown): Record<string, string> {
|
|
287
|
+
if (!headers || typeof headers !== 'object') return {};
|
|
288
|
+
const out: Record<string, string> = {};
|
|
289
|
+
for (const [key, value] of Object.entries(headers as Record<string, unknown>)) {
|
|
290
|
+
out[String(key)] = String(value);
|
|
291
|
+
}
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function getOrCreateNetworkCaptureEntry(tabId: number, requestId: string, fallback?: {
|
|
296
|
+
url?: string;
|
|
297
|
+
method?: string;
|
|
298
|
+
requestHeaders?: Record<string, string>;
|
|
299
|
+
}): NetworkCaptureEntry | null {
|
|
300
|
+
const state = networkCaptures.get(tabId);
|
|
301
|
+
if (!state) return null;
|
|
302
|
+
const existingIndex = state.requestToIndex.get(requestId);
|
|
303
|
+
if (existingIndex !== undefined) {
|
|
304
|
+
return state.entries[existingIndex] || null;
|
|
305
|
+
}
|
|
306
|
+
const url = fallback?.url || '';
|
|
307
|
+
if (!shouldCaptureUrl(url, state.patterns)) return null;
|
|
308
|
+
const entry: NetworkCaptureEntry = {
|
|
309
|
+
kind: 'cdp',
|
|
310
|
+
url,
|
|
311
|
+
method: fallback?.method || 'GET',
|
|
312
|
+
requestHeaders: fallback?.requestHeaders || {},
|
|
313
|
+
timestamp: Date.now(),
|
|
314
|
+
};
|
|
315
|
+
state.entries.push(entry);
|
|
316
|
+
state.requestToIndex.set(requestId, state.entries.length - 1);
|
|
317
|
+
return entry;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export async function startNetworkCapture(
|
|
321
|
+
tabId: number,
|
|
322
|
+
pattern?: string,
|
|
323
|
+
): Promise<void> {
|
|
324
|
+
await ensureAttached(tabId);
|
|
325
|
+
await chrome.debugger.sendCommand({ tabId }, 'Network.enable');
|
|
326
|
+
networkCaptures.set(tabId, {
|
|
327
|
+
patterns: normalizeCapturePatterns(pattern),
|
|
328
|
+
entries: [],
|
|
329
|
+
requestToIndex: new Map(),
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export async function readNetworkCapture(tabId: number): Promise<NetworkCaptureEntry[]> {
|
|
334
|
+
const state = networkCaptures.get(tabId);
|
|
335
|
+
if (!state) return [];
|
|
336
|
+
const entries = state.entries.slice();
|
|
337
|
+
state.entries = [];
|
|
338
|
+
state.requestToIndex.clear();
|
|
339
|
+
return entries;
|
|
340
|
+
}
|
|
341
|
+
|
|
231
342
|
export async function detach(tabId: number): Promise<void> {
|
|
232
343
|
if (!attached.has(tabId)) return;
|
|
233
344
|
attached.delete(tabId);
|
|
345
|
+
networkCaptures.delete(tabId);
|
|
234
346
|
try { await chrome.debugger.detach({ tabId }); } catch { /* ignore */ }
|
|
235
347
|
}
|
|
236
348
|
|
|
237
349
|
export function registerListeners(): void {
|
|
238
350
|
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
239
351
|
attached.delete(tabId);
|
|
352
|
+
networkCaptures.delete(tabId);
|
|
240
353
|
});
|
|
241
354
|
chrome.debugger.onDetach.addListener((source) => {
|
|
242
|
-
if (source.tabId)
|
|
355
|
+
if (source.tabId) {
|
|
356
|
+
attached.delete(source.tabId);
|
|
357
|
+
networkCaptures.delete(source.tabId);
|
|
358
|
+
}
|
|
243
359
|
});
|
|
244
360
|
// Invalidate attached cache when tab URL changes to non-debuggable
|
|
245
361
|
chrome.tabs.onUpdated.addListener(async (tabId, info) => {
|
|
@@ -247,4 +363,78 @@ export function registerListeners(): void {
|
|
|
247
363
|
await detach(tabId);
|
|
248
364
|
}
|
|
249
365
|
});
|
|
366
|
+
chrome.debugger.onEvent.addListener(async (source, method, params) => {
|
|
367
|
+
const tabId = source.tabId;
|
|
368
|
+
if (!tabId) return;
|
|
369
|
+
const state = networkCaptures.get(tabId);
|
|
370
|
+
if (!state) return;
|
|
371
|
+
|
|
372
|
+
if (method === 'Network.requestWillBeSent') {
|
|
373
|
+
const requestId = String(params?.requestId || '');
|
|
374
|
+
const request = params?.request as {
|
|
375
|
+
url?: string;
|
|
376
|
+
method?: string;
|
|
377
|
+
headers?: Record<string, unknown>;
|
|
378
|
+
postData?: string;
|
|
379
|
+
hasPostData?: boolean;
|
|
380
|
+
} | undefined;
|
|
381
|
+
const entry = getOrCreateNetworkCaptureEntry(tabId, requestId, {
|
|
382
|
+
url: request?.url,
|
|
383
|
+
method: request?.method,
|
|
384
|
+
requestHeaders: normalizeHeaders(request?.headers),
|
|
385
|
+
});
|
|
386
|
+
if (!entry) return;
|
|
387
|
+
entry.requestBodyKind = request?.hasPostData ? 'string' : 'empty';
|
|
388
|
+
entry.requestBodyPreview = String(request?.postData || '').slice(0, 4000);
|
|
389
|
+
try {
|
|
390
|
+
const postData = await chrome.debugger.sendCommand({ tabId }, 'Network.getRequestPostData', { requestId }) as { postData?: string };
|
|
391
|
+
if (postData?.postData) {
|
|
392
|
+
entry.requestBodyKind = 'string';
|
|
393
|
+
entry.requestBodyPreview = postData.postData.slice(0, 4000);
|
|
394
|
+
}
|
|
395
|
+
} catch {
|
|
396
|
+
// Optional; some requests do not expose postData.
|
|
397
|
+
}
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (method === 'Network.responseReceived') {
|
|
402
|
+
const requestId = String(params?.requestId || '');
|
|
403
|
+
const response = params?.response as {
|
|
404
|
+
url?: string;
|
|
405
|
+
mimeType?: string;
|
|
406
|
+
status?: number;
|
|
407
|
+
headers?: Record<string, unknown>;
|
|
408
|
+
} | undefined;
|
|
409
|
+
const entry = getOrCreateNetworkCaptureEntry(tabId, requestId, {
|
|
410
|
+
url: response?.url,
|
|
411
|
+
});
|
|
412
|
+
if (!entry) return;
|
|
413
|
+
entry.responseStatus = response?.status;
|
|
414
|
+
entry.responseContentType = response?.mimeType || '';
|
|
415
|
+
entry.responseHeaders = normalizeHeaders(response?.headers);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (method === 'Network.loadingFinished') {
|
|
420
|
+
const requestId = String(params?.requestId || '');
|
|
421
|
+
const stateEntryIndex = state.requestToIndex.get(requestId);
|
|
422
|
+
if (stateEntryIndex === undefined) return;
|
|
423
|
+
const entry = state.entries[stateEntryIndex];
|
|
424
|
+
if (!entry) return;
|
|
425
|
+
try {
|
|
426
|
+
const body = await chrome.debugger.sendCommand({ tabId }, 'Network.getResponseBody', { requestId }) as {
|
|
427
|
+
body?: string;
|
|
428
|
+
base64Encoded?: boolean;
|
|
429
|
+
};
|
|
430
|
+
if (typeof body?.body === 'string') {
|
|
431
|
+
entry.responsePreview = body.base64Encoded
|
|
432
|
+
? `base64:${body.body.slice(0, 4000)}`
|
|
433
|
+
: body.body.slice(0, 4000);
|
|
434
|
+
}
|
|
435
|
+
} catch {
|
|
436
|
+
// Optional; bodies are unavailable for some requests (e.g. uploads).
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
});
|
|
250
440
|
}
|
|
@@ -5,7 +5,20 @@
|
|
|
5
5
|
* Everything else is just JS code sent via 'exec'.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export type Action =
|
|
8
|
+
export type Action =
|
|
9
|
+
| 'exec'
|
|
10
|
+
| 'navigate'
|
|
11
|
+
| 'tabs'
|
|
12
|
+
| 'cookies'
|
|
13
|
+
| 'screenshot'
|
|
14
|
+
| 'close-window'
|
|
15
|
+
| 'sessions'
|
|
16
|
+
| 'set-file-input'
|
|
17
|
+
| 'insert-text'
|
|
18
|
+
| 'bind-current'
|
|
19
|
+
| 'network-capture-start'
|
|
20
|
+
| 'network-capture-read'
|
|
21
|
+
| 'cdp';
|
|
9
22
|
|
|
10
23
|
export interface Command {
|
|
11
24
|
/** Unique request ID */
|
|
@@ -26,6 +39,10 @@ export interface Command {
|
|
|
26
39
|
index?: number;
|
|
27
40
|
/** Cookie domain filter */
|
|
28
41
|
domain?: string;
|
|
42
|
+
/** Optional hostname/domain to require for current-tab binding */
|
|
43
|
+
matchDomain?: string;
|
|
44
|
+
/** Optional pathname prefix to require for current-tab binding */
|
|
45
|
+
matchPathPrefix?: string;
|
|
29
46
|
/** Screenshot format: png (default) or jpeg */
|
|
30
47
|
format?: 'png' | 'jpeg';
|
|
31
48
|
/** JPEG quality (0-100), only for jpeg format */
|
|
@@ -36,6 +53,10 @@ export interface Command {
|
|
|
36
53
|
files?: string[];
|
|
37
54
|
/** CSS selector for file input element (set-file-input action) */
|
|
38
55
|
selector?: string;
|
|
56
|
+
/** Raw text payload for insert-text action */
|
|
57
|
+
text?: string;
|
|
58
|
+
/** URL substring filter pattern for network capture actions */
|
|
59
|
+
pattern?: string;
|
|
39
60
|
/** CDP method name for 'cdp' action (e.g. 'Accessibility.getFullAXTree') */
|
|
40
61
|
cdpMethod?: string;
|
|
41
62
|
/** CDP method params for 'cdp' action */
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jackwener/opencli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
7
|
-
"description": "Make any website your CLI. AI-powered.",
|
|
7
|
+
"description": "Make any website or Electron App your CLI. AI-powered.",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=20.0.0"
|
|
10
10
|
},
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"postinstall": "node scripts/postinstall.js || true",
|
|
31
31
|
"typecheck": "tsc --noEmit",
|
|
32
32
|
"lint": "tsc --noEmit",
|
|
33
|
+
"prepare": "[ -d src ] && npm run build || true",
|
|
33
34
|
"prepublishOnly": "npm run build",
|
|
34
35
|
"test": "vitest run --project unit",
|
|
35
36
|
"test:bun": "bun vitest run --project unit",
|
package/scripts/postinstall.js
CHANGED
|
@@ -217,7 +217,7 @@ function main() {
|
|
|
217
217
|
console.log(' \x1b[1mNext step — Browser Bridge setup\x1b[0m');
|
|
218
218
|
console.log(' Browser commands (bilibili, zhihu, twitter...) require the extension:');
|
|
219
219
|
console.log(' 1. Download: https://github.com/jackwener/opencli/releases');
|
|
220
|
-
console.log(' 2.
|
|
220
|
+
console.log(' 2. In Chrome or Chromium, open chrome://extensions → enable Developer Mode → Load unpacked');
|
|
221
221
|
console.log('');
|
|
222
222
|
console.log(' Then run \x1b[36mopencli doctor\x1b[0m to verify.');
|
|
223
223
|
console.log('');
|
|
@@ -10,7 +10,7 @@ tags: [opencli, adapter, browser, api-discovery, cli, web-scraping, automation]
|
|
|
10
10
|
> 从零到发布,覆盖 API 发现、方案选择、适配器编写、测试验证全流程。
|
|
11
11
|
|
|
12
12
|
> [!TIP]
|
|
13
|
-
> **只想为一个具体页面快速生成一个命令?** 看 [
|
|
13
|
+
> **只想为一个具体页面快速生成一个命令?** 看 [opencli-oneshot skill](../opencli-oneshot/SKILL.md)(~150 行,4 步搞定)。
|
|
14
14
|
> 本文档适合从零探索一个新站点的完整流程。
|
|
15
15
|
|
|
16
16
|
---
|
|
@@ -7,7 +7,7 @@ tags: [opencli, adapter, quick-start, yaml, cli, one-shot, automation]
|
|
|
7
7
|
# CLI-ONESHOT — 单点快速 CLI 生成
|
|
8
8
|
|
|
9
9
|
> 给一个 URL + 一句话描述,4 步生成一个 CLI 命令。
|
|
10
|
-
> 完整探索式开发请看 [
|
|
10
|
+
> 完整探索式开发请看 [opencli-explorer skill](../opencli-explorer/SKILL.md)。
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
@@ -219,4 +219,4 @@ opencli mysite mycommand --limit 3 -v # 实际运行
|
|
|
219
219
|
|
|
220
220
|
## 就这样,没了
|
|
221
221
|
|
|
222
|
-
写完文件 → build → run → 提交。有问题再看 [
|
|
222
|
+
写完文件 → build → run → 提交。有问题再看 [opencli-explorer skill](../opencli-explorer/SKILL.md)。
|
|
@@ -4,7 +4,7 @@ description: Make websites accessible for AI agents. Navigate, click, type, extr
|
|
|
4
4
|
allowed-tools: Bash(opencli:*), Read, Edit, Write
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# OpenCLI —
|
|
7
|
+
# OpenCLI Operate — Browser Automation for AI Agents
|
|
8
8
|
|
|
9
9
|
Control Chrome step-by-step via CLI. Reuses existing login sessions — no passwords needed.
|
|
10
10
|
|
|
@@ -16,25 +16,61 @@ opencli doctor # Verify extension + daemon connectivity
|
|
|
16
16
|
|
|
17
17
|
Requires: Chrome running + OpenCLI Browser Bridge extension installed.
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Critical Rules
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
1. **ALWAYS use `state` to inspect the page, NEVER use `screenshot`** — `state` returns structured DOM with `[N]` element indices, is instant and costs zero tokens. `screenshot` requires vision processing and is slow. Only use `screenshot` when the user explicitly asks to save a visual.
|
|
22
|
+
2. **ALWAYS use `click`/`type`/`select` for interaction, NEVER use `eval` to click or type** — `eval "el.click()"` bypasses scrollIntoView and CDP click pipeline, causing failures on off-screen elements. Use `state` to find the `[N]` index, then `click <N>`.
|
|
23
|
+
3. **Verify inputs with `get value`, not screenshots** — after `type`, run `get value <index>` to confirm.
|
|
24
|
+
4. **Run `state` after every page change** — after `open`, `click` (on links), `scroll`, always run `state` to see the new elements and their indices. Never guess indices.
|
|
25
|
+
5. **Chain commands aggressively with `&&`** — combine `open + state`, multiple `type` calls, and `type + get value` into single `&&` chains. Each tool call has overhead; chaining cuts it.
|
|
26
|
+
6. **`eval` is read-only** — use `eval` ONLY for data extraction (`JSON.stringify(...)`), never for clicking, typing, or navigating. Always wrap in IIFE to avoid variable conflicts: `eval "(function(){ const x = ...; return JSON.stringify(x); })()"`.
|
|
27
|
+
7. **Minimize total tool calls** — plan your sequence before acting. A good task completion uses 3-5 tool calls, not 15-20. Combine `open + state` as one call. Combine `type + type + click` as one call. Only run `state` separately when you need to discover new indices.
|
|
28
|
+
8. **Prefer `network` to discover APIs** — most sites have JSON APIs. API-based adapters are more reliable than DOM scraping.
|
|
22
29
|
|
|
23
|
-
##
|
|
30
|
+
## Command Cost Guide
|
|
24
31
|
|
|
32
|
+
| Cost | Commands | When to use |
|
|
33
|
+
|------|----------|-------------|
|
|
34
|
+
| **Free & instant** | `state`, `get *`, `eval`, `network`, `scroll`, `keys` | Default — use these |
|
|
35
|
+
| **Free but changes page** | `open`, `click`, `type`, `select`, `back` | Interaction — run `state` after |
|
|
36
|
+
| **Expensive (vision tokens)** | `screenshot` | ONLY when user needs a saved image |
|
|
37
|
+
|
|
38
|
+
## Action Chaining Rules
|
|
39
|
+
|
|
40
|
+
Commands can be chained with `&&`. The browser persists via daemon, so chaining is safe.
|
|
41
|
+
|
|
42
|
+
**Always chain when possible** — fewer tool calls = faster completion:
|
|
25
43
|
```bash
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
44
|
+
# GOOD: open + inspect in one call (saves 1 round trip)
|
|
45
|
+
opencli operate open https://example.com && opencli operate state
|
|
46
|
+
|
|
47
|
+
# GOOD: fill form in one call (saves 2 round trips)
|
|
48
|
+
opencli operate type 3 "hello" && opencli operate type 4 "world" && opencli operate click 7
|
|
49
|
+
|
|
50
|
+
# GOOD: type + verify in one call
|
|
51
|
+
opencli operate type 5 "test@example.com" && opencli operate get value 5
|
|
52
|
+
|
|
53
|
+
# GOOD: click + wait + state in one call (for page-changing clicks)
|
|
54
|
+
opencli operate click 12 && opencli operate wait time 1 && opencli operate state
|
|
55
|
+
|
|
56
|
+
# BAD: separate calls for each action (wasteful)
|
|
57
|
+
opencli operate type 3 "hello" # Don't do this
|
|
58
|
+
opencli operate type 4 "world" # when you can chain
|
|
59
|
+
opencli operate click 7 # all three together
|
|
29
60
|
```
|
|
30
61
|
|
|
62
|
+
**Page-changing — always put last** in a chain (subsequent commands see stale indices):
|
|
63
|
+
- `open <url>`, `back`, `click <link/button that navigates>`
|
|
64
|
+
|
|
65
|
+
**Rule**: Chain when you already know the indices. Run `state` separately when you need to discover indices first.
|
|
66
|
+
|
|
31
67
|
## Core Workflow
|
|
32
68
|
|
|
33
69
|
1. **Navigate**: `opencli operate open <url>`
|
|
34
|
-
2. **Inspect**: `opencli operate state` →
|
|
70
|
+
2. **Inspect**: `opencli operate state` → elements with `[N]` indices
|
|
35
71
|
3. **Interact**: use indices — `click`, `type`, `select`, `keys`
|
|
36
|
-
4. **Wait
|
|
37
|
-
5. **Verify**: `opencli operate
|
|
72
|
+
4. **Wait** (if needed): `opencli operate wait selector ".loaded"` or `wait text "Success"`
|
|
73
|
+
5. **Verify**: `opencli operate state` or `opencli operate get value <N>`
|
|
38
74
|
6. **Repeat**: browser stays open between commands
|
|
39
75
|
7. **Save**: write a TS adapter to `~/.opencli/clis/<site>/<command>.ts`
|
|
40
76
|
|
|
@@ -43,26 +79,26 @@ opencli operate open https://example.com # 3. Go!
|
|
|
43
79
|
### Navigation
|
|
44
80
|
|
|
45
81
|
```bash
|
|
46
|
-
opencli operate open <url> # Open URL
|
|
47
|
-
opencli operate back # Go back
|
|
82
|
+
opencli operate open <url> # Open URL (page-changing)
|
|
83
|
+
opencli operate back # Go back (page-changing)
|
|
48
84
|
opencli operate scroll down # Scroll (up/down, --amount N)
|
|
49
85
|
opencli operate scroll up --amount 1000
|
|
50
86
|
```
|
|
51
87
|
|
|
52
|
-
### Inspect
|
|
88
|
+
### Inspect (free & instant)
|
|
53
89
|
|
|
54
90
|
```bash
|
|
55
|
-
opencli operate state #
|
|
56
|
-
opencli operate screenshot [path.png] #
|
|
91
|
+
opencli operate state # Structured DOM with [N] indices — PRIMARY tool
|
|
92
|
+
opencli operate screenshot [path.png] # Save visual to file — ONLY for user deliverables
|
|
57
93
|
```
|
|
58
94
|
|
|
59
|
-
### Get (
|
|
95
|
+
### Get (free & instant)
|
|
60
96
|
|
|
61
97
|
```bash
|
|
62
98
|
opencli operate get title # Page title
|
|
63
99
|
opencli operate get url # Current URL
|
|
64
100
|
opencli operate get text <index> # Element text content
|
|
65
|
-
opencli operate get value <index> # Input/textarea value
|
|
101
|
+
opencli operate get value <index> # Input/textarea value (use to verify after type)
|
|
66
102
|
opencli operate get html # Full page HTML
|
|
67
103
|
opencli operate get html --selector "h1" # Scoped HTML
|
|
68
104
|
opencli operate get attributes <index> # Element attributes
|
|
@@ -79,18 +115,37 @@ opencli operate keys "Enter" # Press key (Enter, Escape, Tab, Control
|
|
|
79
115
|
|
|
80
116
|
### Wait
|
|
81
117
|
|
|
118
|
+
Three variants — use the right one for the situation:
|
|
119
|
+
|
|
82
120
|
```bash
|
|
83
|
-
opencli operate wait
|
|
84
|
-
opencli operate wait selector ".
|
|
85
|
-
opencli operate wait
|
|
86
|
-
opencli operate wait
|
|
121
|
+
opencli operate wait time 3 # Wait N seconds (fixed delay)
|
|
122
|
+
opencli operate wait selector ".loaded" # Wait until element appears in DOM
|
|
123
|
+
opencli operate wait selector ".spinner" --timeout 5000 # With timeout (default 30s)
|
|
124
|
+
opencli operate wait text "Success" # Wait until text appears on page
|
|
87
125
|
```
|
|
88
126
|
|
|
89
|
-
|
|
127
|
+
**When to wait**: After `open` on SPAs, after `click` that triggers async loading, before `eval` on dynamically rendered content.
|
|
128
|
+
|
|
129
|
+
### Extract (free & instant, read-only)
|
|
130
|
+
|
|
131
|
+
Use `eval` ONLY for reading data. Never use it to click, type, or navigate.
|
|
90
132
|
|
|
91
133
|
```bash
|
|
92
134
|
opencli operate eval "document.title"
|
|
93
135
|
opencli operate eval "JSON.stringify([...document.querySelectorAll('h2')].map(e => e.textContent))"
|
|
136
|
+
|
|
137
|
+
# IMPORTANT: wrap complex logic in IIFE to avoid "already declared" errors
|
|
138
|
+
opencli operate eval "(function(){ const items = [...document.querySelectorAll('.item')]; return JSON.stringify(items.map(e => e.textContent)); })()"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Selector safety**: Always use fallback selectors — `querySelector` returns `null` on miss:
|
|
142
|
+
```bash
|
|
143
|
+
# BAD: crashes if selector misses
|
|
144
|
+
opencli operate eval "document.querySelector('.title').textContent"
|
|
145
|
+
|
|
146
|
+
# GOOD: fallback with || or ?.
|
|
147
|
+
opencli operate eval "(document.querySelector('.title') || document.querySelector('h1') || {textContent:''}).textContent"
|
|
148
|
+
opencli operate eval "document.querySelector('.title')?.textContent ?? 'not found'"
|
|
94
149
|
```
|
|
95
150
|
|
|
96
151
|
### Network (API Discovery)
|
|
@@ -104,10 +159,14 @@ opencli operate network --all # Include static resources
|
|
|
104
159
|
### Sedimentation (Save as CLI)
|
|
105
160
|
|
|
106
161
|
```bash
|
|
107
|
-
opencli operate init hn/top # Generate adapter scaffold
|
|
108
|
-
opencli operate verify hn/top # Test the adapter
|
|
162
|
+
opencli operate init hn/top # Generate adapter scaffold at ~/.opencli/clis/hn/top.ts
|
|
163
|
+
opencli operate verify hn/top # Test the adapter (adds --limit 3 only if `limit` arg is defined)
|
|
109
164
|
```
|
|
110
165
|
|
|
166
|
+
- `init` auto-detects the domain from the active browser session (no need to specify it)
|
|
167
|
+
- `init` creates the file + populates `site`, `name`, `domain`, and `columns` from current page
|
|
168
|
+
- `verify` runs the adapter end-to-end and prints output; if no `limit` arg exists in the adapter, it won't pass `--limit 3`
|
|
169
|
+
|
|
111
170
|
### Session
|
|
112
171
|
|
|
113
172
|
```bash
|
|
@@ -128,8 +187,7 @@ opencli operate close
|
|
|
128
187
|
```bash
|
|
129
188
|
opencli operate open https://httpbin.org/forms/post
|
|
130
189
|
opencli operate state # See [3] input "Customer Name", [4] input "Telephone"
|
|
131
|
-
opencli operate type 3 "OpenCLI"
|
|
132
|
-
opencli operate type 4 "555-0100"
|
|
190
|
+
opencli operate type 3 "OpenCLI" && opencli operate type 4 "555-0100"
|
|
133
191
|
opencli operate get value 3 # Verify: "OpenCLI"
|
|
134
192
|
opencli operate close
|
|
135
193
|
```
|
|
@@ -204,10 +262,45 @@ Save to `~/.opencli/clis/<site>/<command>.ts` → immediately available as `open
|
|
|
204
262
|
|
|
205
263
|
**Always prefer API over UI** — if you discovered an API during browsing, use `fetch()` directly.
|
|
206
264
|
|
|
265
|
+
## Tips
|
|
266
|
+
|
|
267
|
+
1. **Always `state` first** — never guess element indices, always inspect first
|
|
268
|
+
2. **Sessions persist** — browser stays open between commands, no need to re-open
|
|
269
|
+
3. **Use `eval` for data extraction** — `eval "JSON.stringify(...)"` is faster than multiple `get` calls
|
|
270
|
+
4. **Use `network` to find APIs** — JSON APIs are more reliable than DOM scraping
|
|
271
|
+
5. **Alias**: `opencli op` is shorthand for `opencli operate`
|
|
272
|
+
|
|
273
|
+
## Common Pitfalls
|
|
274
|
+
|
|
275
|
+
1. **`form.submit()` fails in automation** — Don't use `form.submit()` or `eval` to submit forms. Navigate directly to the search URL instead:
|
|
276
|
+
```bash
|
|
277
|
+
# BAD: form.submit() often silently fails
|
|
278
|
+
opencli operate eval "document.querySelector('form').submit()"
|
|
279
|
+
# GOOD: construct the URL and navigate
|
|
280
|
+
opencli operate open "https://github.com/search?q=opencli&type=repositories"
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
2. **GitHub DOM changes frequently** — Prefer `data-testid` attributes when available; they are more stable than class names or tag structure.
|
|
284
|
+
|
|
285
|
+
3. **SPA pages need `wait` before extraction** — After `open` or `click` on single-page apps, the DOM isn't ready immediately. Always `wait selector` or `wait text` before `eval`.
|
|
286
|
+
|
|
287
|
+
4. **Use `state` before clicking** — Run `opencli operate state` to inspect available interactive elements and their indices. Never guess indices from memory.
|
|
288
|
+
|
|
289
|
+
5. **`evaluate` runs in browser context** — `page.evaluate()` in adapters executes inside the browser. Node.js APIs (`fs`, `path`, `process`) are NOT available. Use `fetch()` for network calls, DOM APIs for page data.
|
|
290
|
+
|
|
291
|
+
6. **Backticks in `page.evaluate` break JSON storage** — When writing adapters that will be stored/transported as JSON, avoid template literals inside `page.evaluate`. Use string concatenation or function-style evaluate:
|
|
292
|
+
```typescript
|
|
293
|
+
// BAD: template literal backticks break when adapter is in JSON
|
|
294
|
+
page.evaluate(`document.querySelector("${selector}")`)
|
|
295
|
+
// GOOD: function-style evaluate
|
|
296
|
+
page.evaluate((sel) => document.querySelector(sel), selector)
|
|
297
|
+
```
|
|
298
|
+
|
|
207
299
|
## Troubleshooting
|
|
208
300
|
|
|
209
301
|
| Error | Fix |
|
|
210
302
|
|-------|-----|
|
|
211
303
|
| "Browser not connected" | Run `opencli doctor` |
|
|
212
304
|
| "attach failed: chrome-extension://" | Disable 1Password temporarily |
|
|
213
|
-
| Element not found | `opencli operate scroll down
|
|
305
|
+
| Element not found | `opencli operate scroll down && opencli operate state` |
|
|
306
|
+
| Stale indices after page change | Run `opencli operate state` again to get fresh indices |
|