@jackwener/opencli 1.0.3 → 1.0.5

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.
Files changed (190) hide show
  1. package/.github/workflows/build-extension.yml +21 -3
  2. package/.github/workflows/docs.yml +52 -0
  3. package/README.md +28 -28
  4. package/README.zh-CN.md +28 -28
  5. package/dist/browser/cdp.d.ts +16 -1
  6. package/dist/browser/cdp.js +124 -80
  7. package/dist/browser/daemon-client.d.ts +3 -1
  8. package/dist/browser/daemon-client.js +4 -0
  9. package/dist/browser/dom-helpers.d.ts +20 -0
  10. package/dist/browser/dom-helpers.js +109 -0
  11. package/dist/browser/mcp.d.ts +1 -0
  12. package/dist/browser/mcp.js +10 -5
  13. package/dist/browser/page.d.ts +7 -0
  14. package/dist/browser/page.js +37 -100
  15. package/dist/browser.test.js +7 -0
  16. package/dist/build-manifest.js +3 -1
  17. package/dist/build-manifest.test.js +34 -0
  18. package/dist/capabilityRouting.d.ts +2 -0
  19. package/dist/capabilityRouting.js +30 -0
  20. package/dist/capabilityRouting.test.d.ts +1 -0
  21. package/dist/capabilityRouting.test.js +42 -0
  22. package/dist/chaoxing.test.js +11 -4
  23. package/dist/cli-manifest.json +635 -1
  24. package/dist/cli.js +48 -8
  25. package/dist/clis/antigravity/serve.d.ts +14 -0
  26. package/dist/clis/antigravity/serve.js +263 -0
  27. package/dist/clis/bilibili/download.js +4 -14
  28. package/dist/clis/boss/resume.d.ts +1 -0
  29. package/dist/clis/boss/resume.js +249 -0
  30. package/dist/clis/hf/top.d.ts +1 -0
  31. package/dist/clis/hf/top.js +119 -0
  32. package/dist/clis/jike/comment.d.ts +1 -0
  33. package/dist/clis/jike/comment.js +107 -0
  34. package/dist/clis/jike/create.d.ts +1 -0
  35. package/dist/clis/jike/create.js +106 -0
  36. package/dist/clis/jike/feed.d.ts +1 -0
  37. package/dist/clis/jike/feed.js +67 -0
  38. package/dist/clis/jike/like.d.ts +1 -0
  39. package/dist/clis/jike/like.js +61 -0
  40. package/dist/clis/jike/notifications.d.ts +1 -0
  41. package/dist/clis/jike/notifications.js +169 -0
  42. package/dist/clis/jike/post.yaml +58 -0
  43. package/dist/clis/jike/repost.d.ts +1 -0
  44. package/dist/clis/jike/repost.js +103 -0
  45. package/dist/clis/jike/search.d.ts +1 -0
  46. package/dist/clis/jike/search.js +67 -0
  47. package/dist/clis/jike/shared.d.ts +19 -0
  48. package/dist/clis/jike/shared.js +25 -0
  49. package/dist/clis/jike/topic.yaml +52 -0
  50. package/dist/clis/jike/user.yaml +51 -0
  51. package/dist/clis/smzdm/search.js +28 -39
  52. package/dist/clis/stackoverflow/bounties.yaml +29 -0
  53. package/dist/clis/stackoverflow/hot.yaml +28 -0
  54. package/dist/clis/stackoverflow/search.yaml +32 -0
  55. package/dist/clis/stackoverflow/unanswered.yaml +28 -0
  56. package/dist/clis/twitter/download.js +6 -16
  57. package/dist/clis/xiaohongshu/download.js +3 -3
  58. package/dist/clis/zhihu/download.js +3 -3
  59. package/dist/doctor.d.ts +7 -0
  60. package/dist/doctor.js +16 -0
  61. package/dist/download/index.d.ts +12 -8
  62. package/dist/download/index.js +11 -3
  63. package/dist/download/index.test.d.ts +1 -0
  64. package/dist/download/index.test.js +14 -0
  65. package/dist/engine.js +5 -5
  66. package/dist/explore.d.ts +1 -0
  67. package/dist/explore.js +3 -3
  68. package/dist/generate.js +1 -0
  69. package/dist/interceptor.js +3 -2
  70. package/dist/output.d.ts +1 -0
  71. package/dist/output.js +3 -1
  72. package/dist/pipeline/executor.test.js +1 -0
  73. package/dist/pipeline/steps/download.js +14 -18
  74. package/dist/registry.d.ts +1 -0
  75. package/dist/registry.js +5 -2
  76. package/dist/runtime.d.ts +4 -1
  77. package/dist/runtime.js +2 -2
  78. package/dist/types.d.ts +12 -0
  79. package/dist/verify.d.ts +6 -1
  80. package/dist/verify.js +54 -2
  81. package/docs/.vitepress/config.mts +193 -0
  82. package/docs/adapters/browser/apple-podcasts.md +28 -0
  83. package/docs/adapters/browser/bbc.md +26 -0
  84. package/docs/adapters/browser/bilibili.md +38 -0
  85. package/docs/adapters/browser/boss.md +28 -0
  86. package/docs/adapters/browser/coupang.md +28 -0
  87. package/docs/adapters/browser/ctrip.md +27 -0
  88. package/docs/adapters/browser/github.md +26 -0
  89. package/docs/adapters/browser/hackernews.md +26 -0
  90. package/docs/adapters/browser/linkedin.md +27 -0
  91. package/docs/adapters/browser/reddit.md +41 -0
  92. package/docs/adapters/browser/reuters.md +27 -0
  93. package/docs/adapters/browser/smzdm.md +27 -0
  94. package/docs/adapters/browser/twitter.md +47 -0
  95. package/docs/adapters/browser/v2ex.md +32 -0
  96. package/docs/adapters/browser/weibo.md +27 -0
  97. package/docs/adapters/browser/xiaohongshu.md +32 -0
  98. package/docs/adapters/browser/xiaoyuzhou.md +28 -0
  99. package/docs/adapters/browser/xueqiu.md +32 -0
  100. package/docs/adapters/browser/yahoo-finance.md +26 -0
  101. package/docs/adapters/browser/youtube.md +29 -0
  102. package/docs/adapters/browser/zhihu.md +30 -0
  103. package/docs/adapters/desktop/antigravity.md +46 -0
  104. package/docs/adapters/desktop/chatgpt.md +43 -0
  105. package/docs/adapters/desktop/chatwise.md +38 -0
  106. package/docs/adapters/desktop/codex.md +32 -0
  107. package/docs/adapters/desktop/cursor.md +33 -0
  108. package/docs/adapters/desktop/discord.md +28 -0
  109. package/docs/adapters/desktop/feishu.md +20 -0
  110. package/docs/adapters/desktop/neteasemusic.md +31 -0
  111. package/docs/adapters/desktop/notion.md +29 -0
  112. package/docs/adapters/desktop/wechat.md +28 -0
  113. package/docs/adapters/index.md +49 -0
  114. package/docs/advanced/cdp.md +103 -0
  115. package/docs/advanced/download.md +63 -0
  116. package/docs/advanced/electron.md +125 -0
  117. package/docs/advanced/remote-chrome.md +72 -0
  118. package/docs/developer/ai-workflow.md +66 -0
  119. package/docs/developer/architecture.md +90 -0
  120. package/docs/developer/contributing.md +136 -0
  121. package/docs/developer/testing.md +237 -0
  122. package/docs/developer/ts-adapter.md +87 -0
  123. package/docs/developer/yaml-adapter.md +108 -0
  124. package/docs/guide/browser-bridge.md +38 -0
  125. package/docs/guide/getting-started.md +56 -0
  126. package/docs/guide/installation.md +37 -0
  127. package/docs/guide/troubleshooting.md +56 -0
  128. package/docs/index.md +35 -0
  129. package/docs/zh/adapters/index.md +5 -0
  130. package/docs/zh/advanced/cdp.md +3 -0
  131. package/docs/zh/developer/contributing.md +24 -0
  132. package/docs/zh/guide/browser-bridge.md +25 -0
  133. package/docs/zh/guide/getting-started.md +40 -0
  134. package/docs/zh/guide/installation.md +37 -0
  135. package/docs/zh/index.md +29 -0
  136. package/extension/dist/background.js +92 -52
  137. package/extension/package-lock.json +1156 -0
  138. package/extension/src/background.test.ts +151 -0
  139. package/extension/src/background.ts +122 -51
  140. package/extension/src/protocol.ts +3 -1
  141. package/package.json +7 -3
  142. package/src/browser/cdp.ts +154 -82
  143. package/src/browser/daemon-client.ts +7 -1
  144. package/src/browser/dom-helpers.ts +116 -0
  145. package/src/browser/mcp.ts +14 -6
  146. package/src/browser/page.ts +45 -100
  147. package/src/browser.test.ts +10 -0
  148. package/src/build-manifest.test.ts +36 -0
  149. package/src/build-manifest.ts +2 -1
  150. package/src/capabilityRouting.test.ts +47 -0
  151. package/src/capabilityRouting.ts +28 -0
  152. package/src/chaoxing.test.ts +12 -4
  153. package/src/cli.ts +30 -8
  154. package/src/clis/antigravity/serve.ts +329 -0
  155. package/src/clis/bilibili/download.ts +4 -15
  156. package/src/clis/boss/resume.ts +262 -0
  157. package/src/clis/hf/top.ts +141 -0
  158. package/src/clis/jike/comment.ts +113 -0
  159. package/src/clis/jike/create.ts +113 -0
  160. package/src/clis/jike/feed.ts +74 -0
  161. package/src/clis/jike/like.ts +65 -0
  162. package/src/clis/jike/notifications.ts +185 -0
  163. package/src/clis/jike/post.yaml +58 -0
  164. package/src/clis/jike/repost.ts +114 -0
  165. package/src/clis/jike/search.ts +74 -0
  166. package/src/clis/jike/shared.ts +36 -0
  167. package/src/clis/jike/topic.yaml +52 -0
  168. package/src/clis/jike/user.yaml +51 -0
  169. package/src/clis/smzdm/search.ts +30 -39
  170. package/src/clis/stackoverflow/bounties.yaml +29 -0
  171. package/src/clis/stackoverflow/hot.yaml +28 -0
  172. package/src/clis/stackoverflow/search.yaml +32 -0
  173. package/src/clis/stackoverflow/unanswered.yaml +28 -0
  174. package/src/clis/twitter/download.ts +6 -17
  175. package/src/clis/xiaohongshu/download.ts +3 -3
  176. package/src/clis/zhihu/download.ts +3 -3
  177. package/src/doctor.ts +18 -2
  178. package/src/download/index.test.ts +16 -0
  179. package/src/download/index.ts +22 -4
  180. package/src/engine.ts +4 -4
  181. package/src/explore.ts +4 -4
  182. package/src/generate.ts +1 -0
  183. package/src/interceptor.ts +3 -2
  184. package/src/output.ts +3 -1
  185. package/src/pipeline/executor.test.ts +1 -0
  186. package/src/pipeline/steps/download.ts +14 -17
  187. package/src/registry.ts +6 -2
  188. package/src/runtime.ts +3 -2
  189. package/src/types.ts +9 -0
  190. 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` | 🔐 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` | 🌐 / 🔐 |
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 |
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` | 🌐 公共 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` | 🔐 浏览器 |
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
 
@@ -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
- send(method: string, params?: any): Promise<any>;
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;
@@ -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 { resolve, reject } = this._pending.get(msg.id);
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 (e) {
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
- async send(method, params = {}) {
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
- this._pending.set(id, { resolve, reject });
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 new Promise(r => setTimeout(r, 1000));
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 snapshot(opts) {
102
- throw new Error('Method not implemented.');
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
- const safeRef = JSON.stringify(ref);
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
- const safeRef = JSON.stringify(ref);
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
- const code = `
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
- const code = `
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
- async tabs() {
174
- throw new Error('Method not implemented.');
194
+ // ── Implemented methods (P1 fix #2) ──
195
+ async scroll(direction = 'down', amount = 500) {
196
+ await this.evaluate(scrollJs(direction, amount));
175
197
  }
176
- async closeTab(index) {
177
- throw new Error('Method not implemented.');
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 newTab() {
180
- throw new Error('Method not implemented.');
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 selectTab(index) {
183
- throw new Error('Method not implemented.');
219
+ async networkRequests(includeStatic = false) {
220
+ return this.evaluate(networkRequestsJs(includeStatic));
184
221
  }
185
- async networkRequests(includeStatic) {
186
- throw new Error('Method not implemented.');
222
+ async tabs() {
223
+ return [];
187
224
  }
188
- async consoleMessages(level) {
189
- throw new Error('Method not implemented.');
225
+ async closeTab(_index) {
226
+ // Not supported in direct CDP mode
190
227
  }
191
- async scroll(direction, amount) {
192
- throw new Error('Method not implemented.');
228
+ async newTab() {
229
+ await this.bridge.send('Target.createTarget', { url: 'about:blank' });
193
230
  }
194
- async autoScroll(options) {
195
- throw new Error('Method not implemented.');
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
- throw new Error('Method not implemented.');
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
- throw new Error('Method not implemented.');
202
- }
203
- async screenshot(options) {
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;