@jackwener/opencli 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-extension.yml +21 -3
- package/.github/workflows/docs.yml +52 -0
- package/README.md +28 -28
- package/README.zh-CN.md +28 -28
- package/dist/browser/cdp.d.ts +16 -1
- package/dist/browser/cdp.js +124 -80
- package/dist/browser/daemon-client.d.ts +3 -1
- package/dist/browser/daemon-client.js +4 -0
- package/dist/browser/dom-helpers.d.ts +20 -0
- package/dist/browser/dom-helpers.js +109 -0
- package/dist/browser/mcp.d.ts +1 -0
- package/dist/browser/mcp.js +10 -5
- package/dist/browser/page.d.ts +7 -0
- package/dist/browser/page.js +37 -100
- package/dist/browser.test.js +7 -0
- package/dist/build-manifest.js +3 -1
- package/dist/build-manifest.test.js +34 -0
- package/dist/capabilityRouting.d.ts +2 -0
- package/dist/capabilityRouting.js +30 -0
- package/dist/capabilityRouting.test.d.ts +1 -0
- package/dist/capabilityRouting.test.js +42 -0
- package/dist/chaoxing.test.js +11 -4
- package/dist/cli-manifest.json +635 -1
- package/dist/cli.js +45 -8
- package/dist/clis/antigravity/serve.d.ts +14 -0
- package/dist/clis/antigravity/serve.js +263 -0
- package/dist/clis/bilibili/download.js +4 -14
- package/dist/clis/boss/resume.d.ts +1 -0
- package/dist/clis/boss/resume.js +249 -0
- package/dist/clis/hf/top.d.ts +1 -0
- package/dist/clis/hf/top.js +119 -0
- package/dist/clis/jike/comment.d.ts +1 -0
- package/dist/clis/jike/comment.js +107 -0
- package/dist/clis/jike/create.d.ts +1 -0
- package/dist/clis/jike/create.js +106 -0
- package/dist/clis/jike/feed.d.ts +1 -0
- package/dist/clis/jike/feed.js +67 -0
- package/dist/clis/jike/like.d.ts +1 -0
- package/dist/clis/jike/like.js +61 -0
- package/dist/clis/jike/notifications.d.ts +1 -0
- package/dist/clis/jike/notifications.js +169 -0
- package/dist/clis/jike/post.yaml +58 -0
- package/dist/clis/jike/repost.d.ts +1 -0
- package/dist/clis/jike/repost.js +103 -0
- package/dist/clis/jike/search.d.ts +1 -0
- package/dist/clis/jike/search.js +67 -0
- package/dist/clis/jike/shared.d.ts +19 -0
- package/dist/clis/jike/shared.js +25 -0
- package/dist/clis/jike/topic.yaml +52 -0
- package/dist/clis/jike/user.yaml +51 -0
- package/dist/clis/smzdm/search.js +28 -39
- package/dist/clis/stackoverflow/bounties.yaml +29 -0
- package/dist/clis/stackoverflow/hot.yaml +28 -0
- package/dist/clis/stackoverflow/search.yaml +32 -0
- package/dist/clis/stackoverflow/unanswered.yaml +28 -0
- package/dist/clis/twitter/download.js +6 -16
- package/dist/clis/xiaohongshu/download.js +3 -3
- package/dist/clis/zhihu/download.js +3 -3
- package/dist/doctor.d.ts +7 -0
- package/dist/doctor.js +16 -0
- package/dist/download/index.d.ts +12 -8
- package/dist/download/index.js +11 -3
- package/dist/download/index.test.d.ts +1 -0
- package/dist/download/index.test.js +14 -0
- package/dist/engine.js +5 -5
- package/dist/explore.d.ts +1 -0
- package/dist/explore.js +3 -3
- package/dist/generate.js +1 -0
- package/dist/interceptor.js +3 -2
- package/dist/output.d.ts +1 -0
- package/dist/output.js +3 -1
- package/dist/pipeline/executor.test.js +1 -0
- package/dist/pipeline/steps/download.js +14 -18
- package/dist/registry.d.ts +1 -0
- package/dist/registry.js +5 -2
- package/dist/runtime.d.ts +4 -1
- package/dist/runtime.js +2 -2
- package/dist/types.d.ts +12 -0
- package/dist/verify.d.ts +6 -1
- package/dist/verify.js +54 -2
- package/docs/.vitepress/config.mts +193 -0
- package/docs/adapters/browser/apple-podcasts.md +28 -0
- package/docs/adapters/browser/bbc.md +26 -0
- package/docs/adapters/browser/bilibili.md +38 -0
- package/docs/adapters/browser/boss.md +28 -0
- package/docs/adapters/browser/coupang.md +28 -0
- package/docs/adapters/browser/ctrip.md +27 -0
- package/docs/adapters/browser/github.md +26 -0
- package/docs/adapters/browser/hackernews.md +26 -0
- package/docs/adapters/browser/linkedin.md +27 -0
- package/docs/adapters/browser/reddit.md +41 -0
- package/docs/adapters/browser/reuters.md +27 -0
- package/docs/adapters/browser/smzdm.md +27 -0
- package/docs/adapters/browser/twitter.md +47 -0
- package/docs/adapters/browser/v2ex.md +32 -0
- package/docs/adapters/browser/weibo.md +27 -0
- package/docs/adapters/browser/xiaohongshu.md +32 -0
- package/docs/adapters/browser/xiaoyuzhou.md +28 -0
- package/docs/adapters/browser/xueqiu.md +32 -0
- package/docs/adapters/browser/yahoo-finance.md +26 -0
- package/docs/adapters/browser/youtube.md +29 -0
- package/docs/adapters/browser/zhihu.md +30 -0
- package/docs/adapters/desktop/antigravity.md +46 -0
- package/docs/adapters/desktop/chatgpt.md +43 -0
- package/docs/adapters/desktop/chatwise.md +38 -0
- package/docs/adapters/desktop/codex.md +32 -0
- package/docs/adapters/desktop/cursor.md +33 -0
- package/docs/adapters/desktop/discord.md +28 -0
- package/docs/adapters/desktop/feishu.md +20 -0
- package/docs/adapters/desktop/neteasemusic.md +31 -0
- package/docs/adapters/desktop/notion.md +29 -0
- package/docs/adapters/desktop/wechat.md +28 -0
- package/docs/adapters/index.md +49 -0
- package/docs/advanced/cdp.md +103 -0
- package/docs/advanced/download.md +63 -0
- package/docs/advanced/electron.md +125 -0
- package/docs/advanced/remote-chrome.md +72 -0
- package/docs/developer/ai-workflow.md +66 -0
- package/docs/developer/architecture.md +90 -0
- package/docs/developer/contributing.md +136 -0
- package/docs/developer/testing.md +237 -0
- package/docs/developer/ts-adapter.md +87 -0
- package/docs/developer/yaml-adapter.md +108 -0
- package/docs/guide/browser-bridge.md +38 -0
- package/docs/guide/getting-started.md +56 -0
- package/docs/guide/installation.md +37 -0
- package/docs/guide/troubleshooting.md +56 -0
- package/docs/index.md +35 -0
- package/docs/zh/adapters/index.md +5 -0
- package/docs/zh/advanced/cdp.md +3 -0
- package/docs/zh/developer/contributing.md +24 -0
- package/docs/zh/guide/browser-bridge.md +25 -0
- package/docs/zh/guide/getting-started.md +40 -0
- package/docs/zh/guide/installation.md +37 -0
- package/docs/zh/index.md +29 -0
- package/extension/dist/background.js +92 -52
- package/extension/package-lock.json +1156 -0
- package/extension/src/background.test.ts +151 -0
- package/extension/src/background.ts +122 -51
- package/extension/src/protocol.ts +3 -1
- package/package.json +7 -3
- package/src/browser/cdp.ts +154 -82
- package/src/browser/daemon-client.ts +7 -1
- package/src/browser/dom-helpers.ts +116 -0
- package/src/browser/mcp.ts +14 -6
- package/src/browser/page.ts +45 -100
- package/src/browser.test.ts +10 -0
- package/src/build-manifest.test.ts +36 -0
- package/src/build-manifest.ts +2 -1
- package/src/capabilityRouting.test.ts +47 -0
- package/src/capabilityRouting.ts +28 -0
- package/src/chaoxing.test.ts +12 -4
- package/src/cli.ts +28 -8
- package/src/clis/antigravity/serve.ts +329 -0
- package/src/clis/bilibili/download.ts +4 -15
- package/src/clis/boss/resume.ts +262 -0
- package/src/clis/hf/top.ts +141 -0
- package/src/clis/jike/comment.ts +113 -0
- package/src/clis/jike/create.ts +113 -0
- package/src/clis/jike/feed.ts +74 -0
- package/src/clis/jike/like.ts +65 -0
- package/src/clis/jike/notifications.ts +185 -0
- package/src/clis/jike/post.yaml +58 -0
- package/src/clis/jike/repost.ts +114 -0
- package/src/clis/jike/search.ts +74 -0
- package/src/clis/jike/shared.ts +36 -0
- package/src/clis/jike/topic.yaml +52 -0
- package/src/clis/jike/user.yaml +51 -0
- package/src/clis/smzdm/search.ts +30 -39
- package/src/clis/stackoverflow/bounties.yaml +29 -0
- package/src/clis/stackoverflow/hot.yaml +28 -0
- package/src/clis/stackoverflow/search.yaml +32 -0
- package/src/clis/stackoverflow/unanswered.yaml +28 -0
- package/src/clis/twitter/download.ts +6 -17
- package/src/clis/xiaohongshu/download.ts +3 -3
- package/src/clis/zhihu/download.ts +3 -3
- package/src/doctor.ts +18 -2
- package/src/download/index.test.ts +16 -0
- package/src/download/index.ts +22 -4
- package/src/engine.ts +4 -4
- package/src/explore.ts +4 -4
- package/src/generate.ts +1 -0
- package/src/interceptor.ts +3 -2
- package/src/output.ts +3 -1
- package/src/pipeline/executor.test.ts +1 -0
- package/src/pipeline/steps/download.ts +14 -17
- package/src/registry.ts +6 -2
- package/src/runtime.ts +3 -2
- package/src/types.ts +9 -0
- package/src/verify.ts +64 -3
|
@@ -21,10 +21,28 @@ jobs:
|
|
|
21
21
|
uses: actions/setup-node@v4
|
|
22
22
|
with:
|
|
23
23
|
node-version: 20
|
|
24
|
+
cache: 'npm'
|
|
25
|
+
cache-dependency-path: extension/package-lock.json
|
|
26
|
+
|
|
27
|
+
- name: Install extension dependencies
|
|
28
|
+
run: npm ci
|
|
29
|
+
working-directory: extension
|
|
30
|
+
|
|
31
|
+
- name: Build extension
|
|
32
|
+
run: npm run build
|
|
33
|
+
working-directory: extension
|
|
34
|
+
|
|
35
|
+
- name: Prepare extension package
|
|
36
|
+
run: |
|
|
37
|
+
rm -rf extension-package
|
|
38
|
+
mkdir -p extension-package
|
|
39
|
+
cp extension/manifest.json extension-package/
|
|
40
|
+
cp -R extension/dist extension-package/
|
|
41
|
+
cp -R extension/icons extension-package/
|
|
24
42
|
|
|
25
43
|
- name: Create Extension ZIP
|
|
26
44
|
run: |
|
|
27
|
-
cd extension
|
|
45
|
+
cd extension-package
|
|
28
46
|
zip -r ../opencli-extension.zip .
|
|
29
47
|
|
|
30
48
|
- name: Create Extension CRX
|
|
@@ -33,11 +51,11 @@ jobs:
|
|
|
33
51
|
if [ -n "${{ secrets.CRX_PRIVATE_KEY }}" ]; then
|
|
34
52
|
echo "Found CRX_PRIVATE_KEY, signing extension..."
|
|
35
53
|
echo "${{ secrets.CRX_PRIVATE_KEY }}" > crx-key.pem
|
|
36
|
-
crx3 pack extension -o opencli-extension.crx -p crx-key.pem
|
|
54
|
+
crx3 pack extension-package -o opencli-extension.crx -p crx-key.pem
|
|
37
55
|
rm crx-key.pem
|
|
38
56
|
else
|
|
39
57
|
echo "No CRX_PRIVATE_KEY configured. Generating CRX with a temporary random key..."
|
|
40
|
-
crx3 pack extension -o opencli-extension.crx
|
|
58
|
+
crx3 pack extension-package -o opencli-extension.crx
|
|
41
59
|
fi
|
|
42
60
|
|
|
43
61
|
- name: Upload Artifacts (Action Run)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
name: Deploy Docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
paths: ['docs/**']
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
pages: write
|
|
12
|
+
id-token: write
|
|
13
|
+
|
|
14
|
+
concurrency:
|
|
15
|
+
group: pages
|
|
16
|
+
cancel-in-progress: false
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
build:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
with:
|
|
24
|
+
fetch-depth: 0
|
|
25
|
+
|
|
26
|
+
- uses: actions/setup-node@v4
|
|
27
|
+
with:
|
|
28
|
+
node-version: 20
|
|
29
|
+
cache: npm
|
|
30
|
+
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: npm ci
|
|
33
|
+
|
|
34
|
+
- name: Build docs
|
|
35
|
+
run: npm run docs:build
|
|
36
|
+
|
|
37
|
+
- uses: actions/configure-pages@v4
|
|
38
|
+
|
|
39
|
+
- uses: actions/upload-pages-artifact@v3
|
|
40
|
+
with:
|
|
41
|
+
path: docs/.vitepress/dist
|
|
42
|
+
|
|
43
|
+
deploy:
|
|
44
|
+
environment:
|
|
45
|
+
name: github-pages
|
|
46
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
47
|
+
needs: build
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
steps:
|
|
50
|
+
- name: Deploy to GitHub Pages
|
|
51
|
+
id: deployment
|
|
52
|
+
uses: actions/deploy-pages@v4
|
package/README.md
CHANGED
|
@@ -115,34 +115,34 @@ Run `opencli list` for the live registry.
|
|
|
115
115
|
|
|
116
116
|
| Site | Commands | Mode |
|
|
117
117
|
|------|----------|------|
|
|
118
|
-
| **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` `download` `accept` `reply-dm` |
|
|
119
|
-
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` |
|
|
120
|
-
| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` `ask` `screenshot` `history` `export` |
|
|
121
|
-
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` `download` |
|
|
122
|
-
| **codex** | `status` `send` `read` `new` `extract-diff` `model` `ask` `screenshot` `history` `export` |
|
|
123
|
-
| **chatwise** | `status` `new` `send` `read` `ask` `model` `history` `export` `screenshot` |
|
|
124
|
-
| **notion** | `status` `search` `read` `new` `write` `sidebar` `favorites` `export` |
|
|
125
|
-
| **discord-app** | `status` `send` `read` `channels` `servers` `search` `members` |
|
|
126
|
-
| **v2ex** | `hot` `latest` `topic` `daily` `me` `notifications` |
|
|
127
|
-
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` |
|
|
128
|
-
| **antigravity** | `status` `send` `read` `new` `dump` `extract-code` `model` `watch` |
|
|
129
|
-
| **chatgpt** | `status` `new` `send` `read` `ask` |
|
|
130
|
-
| **xiaohongshu** | `search` `notifications` `feed` `me` `user` `download` |
|
|
131
|
-
| **apple-podcasts** | `search` `episodes` `top` |
|
|
132
|
-
| **xiaoyuzhou** | `podcast` `podcast-episodes` `episode` |
|
|
133
|
-
| **zhihu** | `hot` `search` `question` `download` |
|
|
134
|
-
| **youtube** | `search` `video` `transcript` |
|
|
135
|
-
| **boss** | `search` `detail` |
|
|
136
|
-
| **coupang** | `search` `add-to-cart` |
|
|
137
|
-
| **bbc** | `news` |
|
|
138
|
-
| **ctrip** | `search` |
|
|
139
|
-
| **github** | `search` |
|
|
140
|
-
| **hackernews** | `top` |
|
|
141
|
-
| **linkedin** | `search` |
|
|
142
|
-
| **reuters** | `search` |
|
|
143
|
-
| **smzdm** | `search` |
|
|
144
|
-
| **weibo** | `hot` |
|
|
145
|
-
| **yahoo-finance** | `quote` |
|
|
118
|
+
| **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` `download` `accept` `reply-dm` | Browser |
|
|
119
|
+
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | Browser |
|
|
120
|
+
| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` `ask` `screenshot` `history` `export` | Desktop |
|
|
121
|
+
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` `download` | Browser |
|
|
122
|
+
| **codex** | `status` `send` `read` `new` `extract-diff` `model` `ask` `screenshot` `history` `export` | Desktop |
|
|
123
|
+
| **chatwise** | `status` `new` `send` `read` `ask` `model` `history` `export` `screenshot` | Desktop |
|
|
124
|
+
| **notion** | `status` `search` `read` `new` `write` `sidebar` `favorites` `export` | Desktop |
|
|
125
|
+
| **discord-app** | `status` `send` `read` `channels` `servers` `search` `members` | Desktop |
|
|
126
|
+
| **v2ex** | `hot` `latest` `topic` `daily` `me` `notifications` | Public / Browser |
|
|
127
|
+
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | Browser |
|
|
128
|
+
| **antigravity** | `status` `send` `read` `new` `dump` `extract-code` `model` `watch` | Desktop |
|
|
129
|
+
| **chatgpt** | `status` `new` `send` `read` `ask` | Desktop |
|
|
130
|
+
| **xiaohongshu** | `search` `notifications` `feed` `me` `user` `download` | Browser |
|
|
131
|
+
| **apple-podcasts** | `search` `episodes` `top` | Public |
|
|
132
|
+
| **xiaoyuzhou** | `podcast` `podcast-episodes` `episode` | Public |
|
|
133
|
+
| **zhihu** | `hot` `search` `question` `download` | Browser |
|
|
134
|
+
| **youtube** | `search` `video` `transcript` | Browser |
|
|
135
|
+
| **boss** | `search` `detail` | Browser |
|
|
136
|
+
| **coupang** | `search` `add-to-cart` | Browser |
|
|
137
|
+
| **bbc** | `news` | Public |
|
|
138
|
+
| **ctrip** | `search` | Browser |
|
|
139
|
+
| **github** | `search` | Public |
|
|
140
|
+
| **hackernews** | `top` | Public |
|
|
141
|
+
| **linkedin** | `search` | Browser |
|
|
142
|
+
| **reuters** | `search` | Browser |
|
|
143
|
+
| **smzdm** | `search` | Browser |
|
|
144
|
+
| **weibo** | `hot` | Browser |
|
|
145
|
+
| **yahoo-finance** | `quote` | Browser |
|
|
146
146
|
|
|
147
147
|
### Desktop App Adapters
|
|
148
148
|
|
package/README.zh-CN.md
CHANGED
|
@@ -116,34 +116,34 @@ npm install -g @jackwener/opencli@latest
|
|
|
116
116
|
|
|
117
117
|
| 站点 | 命令 | 模式 |
|
|
118
118
|
|------|------|------|
|
|
119
|
-
| **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` `download` `accept` `reply-dm` |
|
|
120
|
-
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` |
|
|
121
|
-
| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` `ask` `screenshot` `history` `export` |
|
|
122
|
-
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` `download` |
|
|
123
|
-
| **codex** | `status` `send` `read` `new` `extract-diff` `model` `ask` `screenshot` `history` `export` |
|
|
124
|
-
| **chatwise** | `status` `new` `send` `read` `ask` `model` `history` `export` `screenshot` |
|
|
125
|
-
| **notion** | `status` `search` `read` `new` `write` `sidebar` `favorites` `export` |
|
|
126
|
-
| **discord-app** | `status` `send` `read` `channels` `servers` `search` `members` |
|
|
127
|
-
| **v2ex** | `hot` `latest` `topic` `daily` `me` `notifications` |
|
|
128
|
-
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` |
|
|
129
|
-
| **antigravity** | `status` `send` `read` `new` `dump` `extract-code` `model` `watch` |
|
|
130
|
-
| **chatgpt** | `status` `new` `send` `read` `ask` |
|
|
131
|
-
| **xiaohongshu** | `search` `notifications` `feed` `me` `user` `download` |
|
|
132
|
-
| **apple-podcasts** | `search` `episodes` `top` |
|
|
133
|
-
| **xiaoyuzhou** | `podcast` `podcast-episodes` `episode` |
|
|
134
|
-
| **zhihu** | `hot` `search` `question` `download` |
|
|
135
|
-
| **youtube** | `search` `video` `transcript` |
|
|
136
|
-
| **boss** | `search` `detail` |
|
|
137
|
-
| **coupang** | `search` `add-to-cart` |
|
|
138
|
-
| **bbc** | `news` |
|
|
139
|
-
| **ctrip** | `search` |
|
|
140
|
-
| **github** | `search` |
|
|
141
|
-
| **hackernews** | `top` |
|
|
142
|
-
| **linkedin** | `search` |
|
|
143
|
-
| **reuters** | `search` |
|
|
144
|
-
| **smzdm** | `search` |
|
|
145
|
-
| **weibo** | `hot` |
|
|
146
|
-
| **yahoo-finance** | `quote` |
|
|
119
|
+
| **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` `download` `accept` `reply-dm` | 浏览器 |
|
|
120
|
+
| **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | 浏览器 |
|
|
121
|
+
| **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` `ask` `screenshot` `history` `export` | 桌面端 |
|
|
122
|
+
| **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` `download` | 浏览器 |
|
|
123
|
+
| **codex** | `status` `send` `read` `new` `extract-diff` `model` `ask` `screenshot` `history` `export` | 桌面端 |
|
|
124
|
+
| **chatwise** | `status` `new` `send` `read` `ask` `model` `history` `export` `screenshot` | 桌面端 |
|
|
125
|
+
| **notion** | `status` `search` `read` `new` `write` `sidebar` `favorites` `export` | 桌面端 |
|
|
126
|
+
| **discord-app** | `status` `send` `read` `channels` `servers` `search` `members` | 桌面端 |
|
|
127
|
+
| **v2ex** | `hot` `latest` `topic` `daily` `me` `notifications` | 公开 / 浏览器 |
|
|
128
|
+
| **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` | 浏览器 |
|
|
129
|
+
| **antigravity** | `status` `send` `read` `new` `dump` `extract-code` `model` `watch` | 桌面端 |
|
|
130
|
+
| **chatgpt** | `status` `new` `send` `read` `ask` | 桌面端 |
|
|
131
|
+
| **xiaohongshu** | `search` `notifications` `feed` `me` `user` `download` | 浏览器 |
|
|
132
|
+
| **apple-podcasts** | `search` `episodes` `top` | 公开 |
|
|
133
|
+
| **xiaoyuzhou** | `podcast` `podcast-episodes` `episode` | 公开 |
|
|
134
|
+
| **zhihu** | `hot` `search` `question` `download` | 浏览器 |
|
|
135
|
+
| **youtube** | `search` `video` `transcript` | 浏览器 |
|
|
136
|
+
| **boss** | `search` `detail` | 浏览器 |
|
|
137
|
+
| **coupang** | `search` `add-to-cart` | 浏览器 |
|
|
138
|
+
| **bbc** | `news` | 公共 API |
|
|
139
|
+
| **ctrip** | `search` | 浏览器 |
|
|
140
|
+
| **github** | `search` | 公共 API |
|
|
141
|
+
| **hackernews** | `top` | 公共 API |
|
|
142
|
+
| **linkedin** | `search` | 浏览器 |
|
|
143
|
+
| **reuters** | `search` | 浏览器 |
|
|
144
|
+
| **smzdm** | `search` | 浏览器 |
|
|
145
|
+
| **weibo** | `hot` | 浏览器 |
|
|
146
|
+
| **yahoo-finance** | `quote` | 浏览器 |
|
|
147
147
|
|
|
148
148
|
### 桌面应用适配器
|
|
149
149
|
|
package/dist/browser/cdp.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CDP client — implements IPage by connecting directly to a Chrome/Electron CDP WebSocket.
|
|
3
|
+
*
|
|
4
|
+
* Fixes applied:
|
|
5
|
+
* - send() now has a 30s timeout guard (P0 #4)
|
|
6
|
+
* - goto() waits for Page.loadEventFired instead of hardcoded 1s sleep (P1 #3)
|
|
7
|
+
* - Implemented scroll, autoScroll, screenshot, networkRequests (P1 #2)
|
|
8
|
+
* - Shared DOM helper methods extracted to reduce duplication with Page (P1 #5)
|
|
3
9
|
*/
|
|
4
10
|
import type { IPage } from '../types.js';
|
|
5
11
|
export interface CDPTarget {
|
|
@@ -12,11 +18,20 @@ export declare class CDPBridge {
|
|
|
12
18
|
private _ws;
|
|
13
19
|
private _idCounter;
|
|
14
20
|
private _pending;
|
|
21
|
+
private _eventListeners;
|
|
15
22
|
connect(opts?: {
|
|
16
23
|
timeout?: number;
|
|
24
|
+
workspace?: string;
|
|
17
25
|
}): Promise<IPage>;
|
|
18
26
|
close(): Promise<void>;
|
|
19
|
-
|
|
27
|
+
/** Send a CDP command with timeout guard (P0 fix #4) */
|
|
28
|
+
send(method: string, params?: any, timeoutMs?: number): Promise<any>;
|
|
29
|
+
/** Listen for a CDP event */
|
|
30
|
+
on(event: string, handler: (params: any) => void): void;
|
|
31
|
+
/** Remove a CDP event listener */
|
|
32
|
+
off(event: string, handler: (params: any) => void): void;
|
|
33
|
+
/** Wait for a CDP event to fire (one-shot) */
|
|
34
|
+
waitForEvent(event: string, timeoutMs?: number): Promise<any>;
|
|
20
35
|
}
|
|
21
36
|
declare function selectCDPTarget(targets: CDPTarget[]): CDPTarget | undefined;
|
|
22
37
|
declare function scoreCDPTarget(target: CDPTarget, preferredPattern?: RegExp): number;
|
package/dist/browser/cdp.js
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CDP client — implements IPage by connecting directly to a Chrome/Electron CDP WebSocket.
|
|
3
|
+
*
|
|
4
|
+
* Fixes applied:
|
|
5
|
+
* - send() now has a 30s timeout guard (P0 #4)
|
|
6
|
+
* - goto() waits for Page.loadEventFired instead of hardcoded 1s sleep (P1 #3)
|
|
7
|
+
* - Implemented scroll, autoScroll, screenshot, networkRequests (P1 #2)
|
|
8
|
+
* - Shared DOM helper methods extracted to reduce duplication with Page (P1 #5)
|
|
3
9
|
*/
|
|
4
10
|
import { WebSocket } from 'ws';
|
|
5
11
|
import { wrapForEval } from './utils.js';
|
|
12
|
+
import { clickJs, typeTextJs, pressKeyJs, waitForTextJs, scrollJs, autoScrollJs, networkRequestsJs, } from './dom-helpers.js';
|
|
13
|
+
const CDP_SEND_TIMEOUT = 30_000; // 30s per command
|
|
6
14
|
export class CDPBridge {
|
|
7
15
|
_ws = null;
|
|
8
16
|
_idCounter = 0;
|
|
9
17
|
_pending = new Map();
|
|
18
|
+
_eventListeners = new Map();
|
|
10
19
|
async connect(opts) {
|
|
11
20
|
const endpoint = process.env.OPENCLI_CDP_ENDPOINT;
|
|
12
21
|
if (!endpoint)
|
|
@@ -39,18 +48,28 @@ export class CDPBridge {
|
|
|
39
48
|
ws.on('message', (data) => {
|
|
40
49
|
try {
|
|
41
50
|
const msg = JSON.parse(data.toString());
|
|
51
|
+
// Handle command responses
|
|
42
52
|
if (msg.id && this._pending.has(msg.id)) {
|
|
43
|
-
const
|
|
53
|
+
const entry = this._pending.get(msg.id);
|
|
54
|
+
clearTimeout(entry.timer);
|
|
44
55
|
this._pending.delete(msg.id);
|
|
45
56
|
if (msg.error) {
|
|
46
|
-
reject(new Error(msg.error.message));
|
|
57
|
+
entry.reject(new Error(msg.error.message));
|
|
47
58
|
}
|
|
48
59
|
else {
|
|
49
|
-
resolve(msg.result);
|
|
60
|
+
entry.resolve(msg.result);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Handle CDP events
|
|
64
|
+
if (msg.method) {
|
|
65
|
+
const listeners = this._eventListeners.get(msg.method);
|
|
66
|
+
if (listeners) {
|
|
67
|
+
for (const fn of listeners)
|
|
68
|
+
fn(msg.params);
|
|
50
69
|
}
|
|
51
70
|
}
|
|
52
71
|
}
|
|
53
|
-
catch
|
|
72
|
+
catch {
|
|
54
73
|
// ignore parsing errors
|
|
55
74
|
}
|
|
56
75
|
});
|
|
@@ -62,29 +81,68 @@ export class CDPBridge {
|
|
|
62
81
|
this._ws = null;
|
|
63
82
|
}
|
|
64
83
|
for (const p of this._pending.values()) {
|
|
84
|
+
clearTimeout(p.timer);
|
|
65
85
|
p.reject(new Error('CDP connection closed'));
|
|
66
86
|
}
|
|
67
87
|
this._pending.clear();
|
|
88
|
+
this._eventListeners.clear();
|
|
68
89
|
}
|
|
69
|
-
|
|
90
|
+
/** Send a CDP command with timeout guard (P0 fix #4) */
|
|
91
|
+
async send(method, params = {}, timeoutMs = CDP_SEND_TIMEOUT) {
|
|
70
92
|
if (!this._ws || this._ws.readyState !== WebSocket.OPEN) {
|
|
71
93
|
throw new Error('CDP connection is not open');
|
|
72
94
|
}
|
|
73
95
|
const id = ++this._idCounter;
|
|
74
96
|
return new Promise((resolve, reject) => {
|
|
75
|
-
|
|
97
|
+
const timer = setTimeout(() => {
|
|
98
|
+
this._pending.delete(id);
|
|
99
|
+
reject(new Error(`CDP command '${method}' timed out after ${timeoutMs / 1000}s`));
|
|
100
|
+
}, timeoutMs);
|
|
101
|
+
this._pending.set(id, { resolve, reject, timer });
|
|
76
102
|
this._ws.send(JSON.stringify({ id, method, params }));
|
|
77
103
|
});
|
|
78
104
|
}
|
|
105
|
+
/** Listen for a CDP event */
|
|
106
|
+
on(event, handler) {
|
|
107
|
+
let set = this._eventListeners.get(event);
|
|
108
|
+
if (!set) {
|
|
109
|
+
set = new Set();
|
|
110
|
+
this._eventListeners.set(event, set);
|
|
111
|
+
}
|
|
112
|
+
set.add(handler);
|
|
113
|
+
}
|
|
114
|
+
/** Remove a CDP event listener */
|
|
115
|
+
off(event, handler) {
|
|
116
|
+
this._eventListeners.get(event)?.delete(handler);
|
|
117
|
+
}
|
|
118
|
+
/** Wait for a CDP event to fire (one-shot) */
|
|
119
|
+
waitForEvent(event, timeoutMs = 15_000) {
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
const timer = setTimeout(() => {
|
|
122
|
+
this.off(event, handler);
|
|
123
|
+
reject(new Error(`Timed out waiting for CDP event '${event}'`));
|
|
124
|
+
}, timeoutMs);
|
|
125
|
+
const handler = (params) => {
|
|
126
|
+
clearTimeout(timer);
|
|
127
|
+
this.off(event, handler);
|
|
128
|
+
resolve(params);
|
|
129
|
+
};
|
|
130
|
+
this.on(event, handler);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
79
133
|
}
|
|
80
134
|
class CDPPage {
|
|
81
135
|
bridge;
|
|
82
136
|
constructor(bridge) {
|
|
83
137
|
this.bridge = bridge;
|
|
84
138
|
}
|
|
139
|
+
/** Navigate with proper load event waiting (P1 fix #3) */
|
|
85
140
|
async goto(url) {
|
|
141
|
+
await this.bridge.send('Page.enable');
|
|
142
|
+
const loadPromise = this.bridge.waitForEvent('Page.loadEventFired', 30_000)
|
|
143
|
+
.catch(() => { }); // Don't fail if event times out
|
|
86
144
|
await this.bridge.send('Page.navigate', { url });
|
|
87
|
-
await
|
|
145
|
+
await loadPromise;
|
|
88
146
|
}
|
|
89
147
|
async evaluate(js) {
|
|
90
148
|
const expression = wrapForEval(js);
|
|
@@ -98,52 +156,26 @@ class CDPPage {
|
|
|
98
156
|
}
|
|
99
157
|
return result.result?.value;
|
|
100
158
|
}
|
|
101
|
-
async
|
|
102
|
-
|
|
159
|
+
async getCookies(opts = {}) {
|
|
160
|
+
const result = await this.bridge.send('Network.getCookies', opts.url ? { urls: [opts.url] } : {});
|
|
161
|
+
const cookies = Array.isArray(result?.cookies) ? result.cookies : [];
|
|
162
|
+
return opts.domain
|
|
163
|
+
? cookies.filter((cookie) => typeof cookie.domain === 'string' && cookie.domain.includes(opts.domain))
|
|
164
|
+
: cookies;
|
|
103
165
|
}
|
|
166
|
+
async snapshot(_opts) {
|
|
167
|
+
// CDP doesn't have a built-in accessibility tree equivalent without additional setup
|
|
168
|
+
return '(snapshot not available in CDP mode)';
|
|
169
|
+
}
|
|
170
|
+
// ── Shared DOM operations (P1 fix #5 — using dom-helpers.ts) ──
|
|
104
171
|
async click(ref) {
|
|
105
|
-
|
|
106
|
-
const code = `
|
|
107
|
-
(() => {
|
|
108
|
-
const ref = ${safeRef};
|
|
109
|
-
const el = document.querySelector('[data-ref="' + ref + '"]')
|
|
110
|
-
|| document.querySelectorAll('a, button, input, [role="button"], [tabindex]')[parseInt(ref, 10) || 0];
|
|
111
|
-
if (!el) throw new Error('Element not found: ' + ref);
|
|
112
|
-
el.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
113
|
-
el.click();
|
|
114
|
-
return 'clicked';
|
|
115
|
-
})()
|
|
116
|
-
`;
|
|
117
|
-
await this.evaluate(code);
|
|
172
|
+
await this.evaluate(clickJs(ref));
|
|
118
173
|
}
|
|
119
174
|
async typeText(ref, text) {
|
|
120
|
-
|
|
121
|
-
const safeText = JSON.stringify(text);
|
|
122
|
-
const code = `
|
|
123
|
-
(() => {
|
|
124
|
-
const ref = ${safeRef};
|
|
125
|
-
const el = document.querySelector('[data-ref="' + ref + '"]')
|
|
126
|
-
|| document.querySelectorAll('input, textarea, [contenteditable]')[parseInt(ref, 10) || 0];
|
|
127
|
-
if (!el) throw new Error('Element not found: ' + ref);
|
|
128
|
-
el.focus();
|
|
129
|
-
el.value = ${safeText};
|
|
130
|
-
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
131
|
-
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
132
|
-
return 'typed';
|
|
133
|
-
})()
|
|
134
|
-
`;
|
|
135
|
-
await this.evaluate(code);
|
|
175
|
+
await this.evaluate(typeTextJs(ref, text));
|
|
136
176
|
}
|
|
137
177
|
async pressKey(key) {
|
|
138
|
-
|
|
139
|
-
(() => {
|
|
140
|
-
const el = document.activeElement || document.body;
|
|
141
|
-
el.dispatchEvent(new KeyboardEvent('keydown', { key: ${JSON.stringify(key)}, bubbles: true }));
|
|
142
|
-
el.dispatchEvent(new KeyboardEvent('keyup', { key: ${JSON.stringify(key)}, bubbles: true }));
|
|
143
|
-
return 'pressed';
|
|
144
|
-
})()
|
|
145
|
-
`;
|
|
146
|
-
await this.evaluate(code);
|
|
178
|
+
await this.evaluate(pressKeyJs(key));
|
|
147
179
|
}
|
|
148
180
|
async wait(options) {
|
|
149
181
|
if (typeof options === 'number') {
|
|
@@ -156,54 +188,66 @@ class CDPPage {
|
|
|
156
188
|
}
|
|
157
189
|
if (options.text) {
|
|
158
190
|
const timeout = (options.timeout ?? 30) * 1000;
|
|
159
|
-
|
|
160
|
-
new Promise((resolve, reject) => {
|
|
161
|
-
const deadline = Date.now() + ${timeout};
|
|
162
|
-
const check = () => {
|
|
163
|
-
if (document.body.innerText.includes(${JSON.stringify(options.text)})) return resolve('found');
|
|
164
|
-
if (Date.now() > deadline) return reject(new Error('Text not found: ' + ${JSON.stringify(options.text)}));
|
|
165
|
-
setTimeout(check, 200);
|
|
166
|
-
};
|
|
167
|
-
check();
|
|
168
|
-
})
|
|
169
|
-
`;
|
|
170
|
-
await this.evaluate(code);
|
|
191
|
+
await this.evaluate(waitForTextJs(options.text, timeout));
|
|
171
192
|
}
|
|
172
193
|
}
|
|
173
|
-
|
|
174
|
-
|
|
194
|
+
// ── Implemented methods (P1 fix #2) ──
|
|
195
|
+
async scroll(direction = 'down', amount = 500) {
|
|
196
|
+
await this.evaluate(scrollJs(direction, amount));
|
|
175
197
|
}
|
|
176
|
-
async
|
|
177
|
-
|
|
198
|
+
async autoScroll(options) {
|
|
199
|
+
const times = options?.times ?? 3;
|
|
200
|
+
const delayMs = options?.delayMs ?? 2000;
|
|
201
|
+
await this.evaluate(autoScrollJs(times, delayMs));
|
|
178
202
|
}
|
|
179
|
-
async
|
|
180
|
-
|
|
203
|
+
async screenshot(options = {}) {
|
|
204
|
+
const result = await this.bridge.send('Page.captureScreenshot', {
|
|
205
|
+
format: options.format ?? 'png',
|
|
206
|
+
quality: options.format === 'jpeg' ? (options.quality ?? 80) : undefined,
|
|
207
|
+
captureBeyondViewport: options.fullPage ?? false,
|
|
208
|
+
});
|
|
209
|
+
const base64 = result.data;
|
|
210
|
+
if (options.path) {
|
|
211
|
+
const fs = await import('node:fs');
|
|
212
|
+
const path = await import('node:path');
|
|
213
|
+
const dir = path.dirname(options.path);
|
|
214
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
215
|
+
await fs.promises.writeFile(options.path, Buffer.from(base64, 'base64'));
|
|
216
|
+
}
|
|
217
|
+
return base64;
|
|
181
218
|
}
|
|
182
|
-
async
|
|
183
|
-
|
|
219
|
+
async networkRequests(includeStatic = false) {
|
|
220
|
+
return this.evaluate(networkRequestsJs(includeStatic));
|
|
184
221
|
}
|
|
185
|
-
async
|
|
186
|
-
|
|
222
|
+
async tabs() {
|
|
223
|
+
return [];
|
|
187
224
|
}
|
|
188
|
-
async
|
|
189
|
-
|
|
225
|
+
async closeTab(_index) {
|
|
226
|
+
// Not supported in direct CDP mode
|
|
190
227
|
}
|
|
191
|
-
async
|
|
192
|
-
|
|
228
|
+
async newTab() {
|
|
229
|
+
await this.bridge.send('Target.createTarget', { url: 'about:blank' });
|
|
193
230
|
}
|
|
194
|
-
async
|
|
195
|
-
|
|
231
|
+
async selectTab(_index) {
|
|
232
|
+
// Not supported in direct CDP mode
|
|
233
|
+
}
|
|
234
|
+
async consoleMessages(_level) {
|
|
235
|
+
return [];
|
|
196
236
|
}
|
|
197
237
|
async installInterceptor(pattern) {
|
|
198
|
-
|
|
238
|
+
const { generateInterceptorJs } = await import('../interceptor.js');
|
|
239
|
+
await this.evaluate(generateInterceptorJs(JSON.stringify(pattern), {
|
|
240
|
+
arrayName: '__opencli_xhr',
|
|
241
|
+
patchGuard: '__opencli_interceptor_patched',
|
|
242
|
+
}));
|
|
199
243
|
}
|
|
200
244
|
async getInterceptedRequests() {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
throw new Error('Method not implemented.');
|
|
245
|
+
const { generateReadInterceptedJs } = await import('../interceptor.js');
|
|
246
|
+
const result = await this.evaluate(generateReadInterceptedJs('__opencli_xhr'));
|
|
247
|
+
return result || [];
|
|
205
248
|
}
|
|
206
249
|
}
|
|
250
|
+
// ── CDP target selection (unchanged) ──
|
|
207
251
|
function selectCDPTarget(targets) {
|
|
208
252
|
const preferredPattern = compilePreferredPattern(process.env.OPENCLI_CDP_TARGET);
|
|
209
253
|
const ranked = targets
|
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export interface DaemonCommand {
|
|
7
7
|
id: string;
|
|
8
|
-
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window';
|
|
8
|
+
action: 'exec' | 'navigate' | 'tabs' | 'cookies' | 'screenshot' | 'close-window' | 'sessions';
|
|
9
9
|
tabId?: number;
|
|
10
10
|
code?: string;
|
|
11
|
+
workspace?: string;
|
|
11
12
|
url?: string;
|
|
12
13
|
op?: string;
|
|
13
14
|
index?: number;
|
|
@@ -35,3 +36,4 @@ export declare function isExtensionConnected(): Promise<boolean>;
|
|
|
35
36
|
* Retries up to 3 times with 500ms delay for transient failures.
|
|
36
37
|
*/
|
|
37
38
|
export declare function sendCommand(action: DaemonCommand['action'], params?: Omit<DaemonCommand, 'id' | 'action'>): Promise<unknown>;
|
|
39
|
+
export declare function listSessions(): Promise<any[]>;
|
|
@@ -80,3 +80,7 @@ export async function sendCommand(action, params = {}) {
|
|
|
80
80
|
// Unreachable — the loop always returns or throws
|
|
81
81
|
throw new Error('sendCommand: max retries exhausted');
|
|
82
82
|
}
|
|
83
|
+
export async function listSessions() {
|
|
84
|
+
const result = await sendCommand('sessions');
|
|
85
|
+
return Array.isArray(result) ? result : [];
|
|
86
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared DOM operation JS generators.
|
|
3
|
+
*
|
|
4
|
+
* Used by both Page (daemon mode) and CDPPage (direct CDP mode)
|
|
5
|
+
* to eliminate code duplication for click, type, press, wait, scroll, etc.
|
|
6
|
+
*/
|
|
7
|
+
/** Generate JS to click an element by ref */
|
|
8
|
+
export declare function clickJs(ref: string): string;
|
|
9
|
+
/** Generate JS to type text into an element by ref */
|
|
10
|
+
export declare function typeTextJs(ref: string, text: string): string;
|
|
11
|
+
/** Generate JS to press a keyboard key */
|
|
12
|
+
export declare function pressKeyJs(key: string): string;
|
|
13
|
+
/** Generate JS to wait for text to appear in the page */
|
|
14
|
+
export declare function waitForTextJs(text: string, timeoutMs: number): string;
|
|
15
|
+
/** Generate JS for scroll */
|
|
16
|
+
export declare function scrollJs(direction: string, amount: number): string;
|
|
17
|
+
/** Generate JS for auto-scroll with lazy-load detection */
|
|
18
|
+
export declare function autoScrollJs(times: number, delayMs: number): string;
|
|
19
|
+
/** Generate JS to read performance resource entries as network requests */
|
|
20
|
+
export declare function networkRequestsJs(includeStatic: boolean): string;
|