@jackwener/opencli 1.3.1 → 1.3.3

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 (241) hide show
  1. package/CHANGELOG.md +128 -0
  2. package/README.md +48 -9
  3. package/README.zh-CN.md +48 -9
  4. package/SKILL.md +317 -6
  5. package/TESTING.md +4 -4
  6. package/dist/browser/cdp.js +10 -1
  7. package/dist/browser/daemon-client.js +2 -1
  8. package/dist/browser/discover.js +2 -1
  9. package/dist/browser/errors.d.ts +2 -1
  10. package/dist/browser/errors.js +10 -10
  11. package/dist/browser/index.d.ts +1 -0
  12. package/dist/browser/index.js +1 -0
  13. package/dist/browser/page.js +12 -0
  14. package/dist/browser/stealth.d.ts +18 -0
  15. package/dist/browser/stealth.js +140 -0
  16. package/dist/browser.test.js +47 -1
  17. package/dist/build-manifest.js +1 -3
  18. package/dist/cli-manifest.json +2573 -989
  19. package/dist/cli.js +42 -2
  20. package/dist/clis/bilibili/download.js +20 -65
  21. package/dist/clis/bilibili/utils.js +2 -1
  22. package/dist/clis/chaoxing/assignments.js +2 -1
  23. package/dist/clis/doubao/ask.d.ts +1 -0
  24. package/dist/clis/doubao/ask.js +35 -0
  25. package/dist/clis/doubao/common.d.ts +23 -0
  26. package/dist/clis/doubao/common.js +564 -0
  27. package/dist/clis/doubao/new.d.ts +1 -0
  28. package/dist/clis/doubao/new.js +20 -0
  29. package/dist/clis/doubao/read.d.ts +1 -0
  30. package/dist/clis/doubao/read.js +19 -0
  31. package/dist/clis/doubao/send.d.ts +1 -0
  32. package/dist/clis/doubao/send.js +22 -0
  33. package/dist/clis/doubao/status.d.ts +1 -0
  34. package/dist/clis/doubao/status.js +24 -0
  35. package/dist/clis/doubao-app/ask.d.ts +1 -0
  36. package/dist/clis/doubao-app/ask.js +53 -0
  37. package/dist/clis/doubao-app/common.d.ts +37 -0
  38. package/dist/clis/doubao-app/common.js +110 -0
  39. package/dist/clis/doubao-app/dump.d.ts +1 -0
  40. package/dist/clis/doubao-app/dump.js +24 -0
  41. package/dist/clis/doubao-app/new.d.ts +1 -0
  42. package/dist/clis/doubao-app/new.js +20 -0
  43. package/dist/clis/doubao-app/read.d.ts +1 -0
  44. package/dist/clis/doubao-app/read.js +18 -0
  45. package/dist/clis/doubao-app/screenshot.d.ts +1 -0
  46. package/dist/clis/doubao-app/screenshot.js +18 -0
  47. package/dist/clis/doubao-app/send.d.ts +1 -0
  48. package/dist/clis/doubao-app/send.js +27 -0
  49. package/dist/clis/doubao-app/status.d.ts +1 -0
  50. package/dist/clis/doubao-app/status.js +16 -0
  51. package/dist/clis/hackernews/ask.yaml +38 -0
  52. package/dist/clis/hackernews/best.yaml +38 -0
  53. package/dist/clis/hackernews/jobs.yaml +36 -0
  54. package/dist/clis/hackernews/new.yaml +38 -0
  55. package/dist/clis/hackernews/search.yaml +44 -0
  56. package/dist/clis/hackernews/show.yaml +38 -0
  57. package/dist/clis/hackernews/top.yaml +3 -1
  58. package/dist/clis/hackernews/user.yaml +25 -0
  59. package/dist/clis/twitter/download.js +13 -97
  60. package/dist/clis/twitter/thread.js +2 -1
  61. package/dist/clis/v2ex/member.yaml +29 -0
  62. package/dist/clis/v2ex/node.yaml +34 -0
  63. package/dist/clis/v2ex/nodes.yaml +31 -0
  64. package/dist/clis/v2ex/replies.yaml +32 -0
  65. package/dist/clis/v2ex/user.yaml +34 -0
  66. package/dist/clis/weibo/search.d.ts +1 -0
  67. package/dist/clis/weibo/search.js +73 -0
  68. package/dist/clis/weixin/download.d.ts +12 -0
  69. package/dist/clis/weixin/download.js +183 -0
  70. package/dist/clis/xiaohongshu/download.js +12 -60
  71. package/dist/clis/xiaohongshu/publish.d.ts +18 -0
  72. package/dist/clis/xiaohongshu/publish.js +352 -0
  73. package/dist/clis/xiaohongshu/search.js +47 -15
  74. package/dist/clis/xiaohongshu/search.test.d.ts +1 -0
  75. package/dist/clis/xiaohongshu/search.test.js +114 -0
  76. package/dist/clis/yollomi/background.d.ts +4 -0
  77. package/dist/clis/yollomi/background.js +45 -0
  78. package/dist/clis/yollomi/edit.d.ts +5 -0
  79. package/dist/clis/yollomi/edit.js +56 -0
  80. package/dist/clis/yollomi/face-swap.d.ts +5 -0
  81. package/dist/clis/yollomi/face-swap.js +43 -0
  82. package/dist/clis/yollomi/generate.d.ts +9 -0
  83. package/dist/clis/yollomi/generate.js +100 -0
  84. package/dist/clis/yollomi/models.d.ts +1 -0
  85. package/dist/clis/yollomi/models.js +33 -0
  86. package/dist/clis/yollomi/object-remover.d.ts +4 -0
  87. package/dist/clis/yollomi/object-remover.js +42 -0
  88. package/dist/clis/yollomi/remove-bg.d.ts +4 -0
  89. package/dist/clis/yollomi/remove-bg.js +38 -0
  90. package/dist/clis/yollomi/restore.d.ts +4 -0
  91. package/dist/clis/yollomi/restore.js +38 -0
  92. package/dist/clis/yollomi/try-on.d.ts +4 -0
  93. package/dist/clis/yollomi/try-on.js +46 -0
  94. package/dist/clis/yollomi/upload.d.ts +7 -0
  95. package/dist/clis/yollomi/upload.js +71 -0
  96. package/dist/clis/yollomi/upscale.d.ts +4 -0
  97. package/dist/clis/yollomi/upscale.js +53 -0
  98. package/dist/clis/yollomi/utils.d.ts +45 -0
  99. package/dist/clis/yollomi/utils.js +180 -0
  100. package/dist/clis/yollomi/video.d.ts +5 -0
  101. package/dist/clis/yollomi/video.js +56 -0
  102. package/dist/clis/zhihu/download.d.ts +1 -5
  103. package/dist/clis/zhihu/download.js +20 -126
  104. package/dist/clis/zhihu/download.test.js +7 -5
  105. package/dist/clis/zhihu/question.js +2 -1
  106. package/dist/commanderAdapter.js +4 -6
  107. package/dist/constants.d.ts +2 -0
  108. package/dist/constants.js +2 -0
  109. package/dist/daemon.js +7 -3
  110. package/dist/discovery.js +10 -10
  111. package/dist/doctor.js +2 -1
  112. package/dist/download/article-download.d.ts +59 -0
  113. package/dist/download/article-download.js +178 -0
  114. package/dist/download/media-download.d.ts +49 -0
  115. package/dist/download/media-download.js +112 -0
  116. package/dist/errors.d.ts +23 -2
  117. package/dist/errors.js +58 -2
  118. package/dist/errors.test.d.ts +1 -0
  119. package/dist/errors.test.js +59 -0
  120. package/dist/execution.js +9 -10
  121. package/dist/explore.js +4 -2
  122. package/dist/external.d.ts +15 -0
  123. package/dist/external.js +48 -2
  124. package/dist/external.test.d.ts +1 -0
  125. package/dist/external.test.js +64 -0
  126. package/dist/main.js +10 -0
  127. package/dist/plugin.d.ts +4 -0
  128. package/dist/plugin.js +45 -23
  129. package/dist/plugin.test.js +6 -1
  130. package/dist/record.d.ts +47 -0
  131. package/dist/record.js +545 -0
  132. package/dist/registry.d.ts +7 -2
  133. package/dist/registry.js +2 -6
  134. package/dist/runtime.d.ts +3 -1
  135. package/dist/runtime.js +10 -3
  136. package/dist/validate.js +1 -3
  137. package/docs/.vitepress/config.mts +1 -0
  138. package/docs/adapters/browser/douban.md +18 -8
  139. package/docs/adapters/browser/doubao.md +35 -0
  140. package/docs/adapters/browser/hackernews.md +20 -4
  141. package/docs/adapters/browser/tiktok.md +1 -1
  142. package/docs/adapters/browser/v2ex.md +31 -10
  143. package/docs/adapters/browser/weibo.md +4 -0
  144. package/docs/adapters/browser/weixin.md +33 -0
  145. package/docs/adapters/browser/wikipedia.md +0 -9
  146. package/docs/adapters/browser/xiaohongshu.md +8 -6
  147. package/docs/adapters/browser/yollomi.md +69 -0
  148. package/docs/adapters/desktop/antigravity.md +0 -3
  149. package/docs/adapters/desktop/doubao-app.md +35 -0
  150. package/docs/adapters/index.md +19 -8
  151. package/docs/advanced/download.md +4 -0
  152. package/package.json +3 -1
  153. package/src/browser/cdp.ts +9 -1
  154. package/src/browser/daemon-client.ts +4 -3
  155. package/src/browser/discover.ts +2 -1
  156. package/src/browser/errors.ts +18 -11
  157. package/src/browser/index.ts +1 -0
  158. package/src/browser/page.ts +11 -0
  159. package/src/browser/stealth.ts +142 -0
  160. package/src/browser.test.ts +51 -1
  161. package/src/build-manifest.ts +1 -3
  162. package/src/cli.ts +45 -2
  163. package/src/clis/bilibili/download.ts +25 -83
  164. package/src/clis/bilibili/utils.ts +2 -1
  165. package/src/clis/chaoxing/assignments.ts +2 -1
  166. package/src/clis/doubao/ask.ts +40 -0
  167. package/src/clis/doubao/common.ts +619 -0
  168. package/src/clis/doubao/new.ts +22 -0
  169. package/src/clis/doubao/read.ts +20 -0
  170. package/src/clis/doubao/send.ts +25 -0
  171. package/src/clis/doubao/status.ts +27 -0
  172. package/src/clis/doubao-app/ask.ts +60 -0
  173. package/src/clis/doubao-app/common.ts +116 -0
  174. package/src/clis/doubao-app/dump.ts +28 -0
  175. package/src/clis/doubao-app/new.ts +21 -0
  176. package/src/clis/doubao-app/read.ts +21 -0
  177. package/src/clis/doubao-app/screenshot.ts +19 -0
  178. package/src/clis/doubao-app/send.ts +30 -0
  179. package/src/clis/doubao-app/status.ts +17 -0
  180. package/src/clis/hackernews/ask.yaml +38 -0
  181. package/src/clis/hackernews/best.yaml +38 -0
  182. package/src/clis/hackernews/jobs.yaml +36 -0
  183. package/src/clis/hackernews/new.yaml +38 -0
  184. package/src/clis/hackernews/search.yaml +44 -0
  185. package/src/clis/hackernews/show.yaml +38 -0
  186. package/src/clis/hackernews/top.yaml +3 -1
  187. package/src/clis/hackernews/user.yaml +25 -0
  188. package/src/clis/twitter/download.ts +13 -111
  189. package/src/clis/twitter/thread.ts +2 -1
  190. package/src/clis/v2ex/member.yaml +29 -0
  191. package/src/clis/v2ex/node.yaml +34 -0
  192. package/src/clis/v2ex/nodes.yaml +31 -0
  193. package/src/clis/v2ex/replies.yaml +32 -0
  194. package/src/clis/v2ex/user.yaml +34 -0
  195. package/src/clis/weibo/search.ts +78 -0
  196. package/src/clis/weixin/download.ts +199 -0
  197. package/src/clis/xiaohongshu/download.ts +12 -71
  198. package/src/clis/xiaohongshu/publish.ts +392 -0
  199. package/src/clis/xiaohongshu/search.test.ts +134 -0
  200. package/src/clis/xiaohongshu/search.ts +49 -15
  201. package/src/clis/yollomi/background.ts +48 -0
  202. package/src/clis/yollomi/edit.ts +58 -0
  203. package/src/clis/yollomi/face-swap.ts +45 -0
  204. package/src/clis/yollomi/generate.ts +95 -0
  205. package/src/clis/yollomi/models.ts +38 -0
  206. package/src/clis/yollomi/object-remover.ts +44 -0
  207. package/src/clis/yollomi/remove-bg.ts +40 -0
  208. package/src/clis/yollomi/restore.ts +40 -0
  209. package/src/clis/yollomi/try-on.ts +48 -0
  210. package/src/clis/yollomi/upload.ts +78 -0
  211. package/src/clis/yollomi/upscale.ts +49 -0
  212. package/src/clis/yollomi/utils.ts +202 -0
  213. package/src/clis/yollomi/video.ts +61 -0
  214. package/src/clis/zhihu/download.test.ts +7 -5
  215. package/src/clis/zhihu/download.ts +23 -158
  216. package/src/clis/zhihu/question.ts +2 -1
  217. package/src/commanderAdapter.ts +4 -7
  218. package/src/constants.ts +3 -0
  219. package/src/daemon.ts +7 -3
  220. package/src/discovery.ts +26 -26
  221. package/src/doctor.ts +2 -1
  222. package/src/download/article-download.ts +272 -0
  223. package/src/download/media-download.ts +178 -0
  224. package/src/errors.test.ts +79 -0
  225. package/src/errors.ts +92 -2
  226. package/src/execution.ts +14 -10
  227. package/src/explore.ts +4 -2
  228. package/src/external.test.ts +88 -0
  229. package/src/external.ts +56 -2
  230. package/src/generate.ts +2 -1
  231. package/src/main.ts +10 -0
  232. package/src/plugin.test.ts +7 -1
  233. package/src/plugin.ts +49 -25
  234. package/src/record.ts +617 -0
  235. package/src/registry.ts +9 -5
  236. package/src/runtime.ts +16 -4
  237. package/src/validate.ts +1 -3
  238. package/tests/e2e/browser-auth.test.ts +10 -1
  239. package/tests/e2e/browser-public.test.ts +13 -8
  240. package/tests/e2e/public-commands.test.ts +209 -21
  241. package/tests/smoke/api-health.test.ts +65 -6
package/SKILL.md CHANGED
@@ -1,9 +1,9 @@
1
1
  ---
2
2
  name: opencli
3
- description: "OpenCLI — Make any website or Electron App your CLI. Zero risk, AI-powered, reuse Chrome login. 150+ commands across 30+ sites."
4
- version: 1.1.0
3
+ description: "OpenCLI — Make any website or Electron App your CLI. Zero risk, AI-powered, reuse Chrome login."
4
+ version: 1.3.1
5
5
  author: jackwener
6
- tags: [cli, browser, web, chrome-extension, cdp, bilibili, zhihu, twitter, github, v2ex, hackernews, reddit, xiaohongshu, xueqiu, youtube, boss, coupang, AI, agent]
6
+ tags: [cli, browser, web, chrome-extension, cdp, bilibili, zhihu, twitter, github, v2ex, hackernews, reddit, xiaohongshu, xueqiu, youtube, boss, coupang, yollomi, AI, agent]
7
7
  ---
8
8
 
9
9
  # OpenCLI
@@ -182,7 +182,6 @@ opencli antigravity dump # 导出 DOM 和快照调试信息
182
182
  opencli antigravity extract-code # 自动抽取 AI 回复中的代码块
183
183
  opencli antigravity model claude # 切换底层模型
184
184
  opencli antigravity watch # 流式监听增量消息
185
- opencli antigravity serve --port 8082 # 启动 Anthropic 兼容代理
186
185
 
187
186
  # Barchart (browser)
188
187
  opencli barchart quote --symbol AAPL # 股票行情
@@ -222,6 +221,14 @@ opencli weread ranking --limit 10 # 排行榜
222
221
  opencli jimeng generate --prompt "描述" # AI 生图
223
222
  opencli jimeng history --limit 10 # 生成历史
224
223
 
224
+ # Yollomi yollomi.com (browser — 需在 Chrome 登录 yollomi.com,复用站点 session)
225
+ opencli yollomi models --type image # 列出图像模型与积分
226
+ opencli yollomi generate "提示词" --model z-image-turbo # 文生图
227
+ opencli yollomi video "提示词" --model kling-2-1 # 视频
228
+ opencli yollomi upload ./photo.jpg # 上传得 URL,供 img2img / 工具链使用
229
+ opencli yollomi remove-bg <image-url> # 去背景(免费)
230
+ opencli yollomi edit <image-url> "改成油画风格" # Qwen 图像编辑
231
+
225
232
  # Grok (default + explicit web)
226
233
  opencli grok ask --prompt "问题" # 提问 Grok(兼容默认路径)
227
234
  opencli grok ask --prompt "问题" --web # 显式 grok.com consumer web UI 路径
@@ -232,6 +239,175 @@ opencli hf top --limit 10 # 热门模型
232
239
  # 超星学习通 (browser)
233
240
  opencli chaoxing assignments # 作业列表
234
241
  opencli chaoxing exams # 考试列表
242
+
243
+ # Douban 豆瓣 (browser)
244
+ opencli douban search "三体" # 搜索 (query positional)
245
+ opencli douban top250 # 豆瓣 Top 250
246
+ opencli douban subject 1234567 # 条目详情 (id positional)
247
+ opencli douban marks --limit 10 # 我的标记
248
+ opencli douban reviews --limit 10 # 短评
249
+
250
+ # Facebook (browser)
251
+ opencli facebook feed --limit 10 # 动态流
252
+ opencli facebook profile username # 用户资料 (id positional)
253
+ opencli facebook search "AI" # 搜索 (query positional)
254
+ opencli facebook friends # 好友列表
255
+ opencli facebook groups # 群组
256
+ opencli facebook events # 活动
257
+ opencli facebook notifications # 通知
258
+ opencli facebook memories # 回忆
259
+ opencli facebook add-friend username # 添加好友 (id positional)
260
+ opencli facebook join-group groupid # 加入群组 (id positional)
261
+
262
+ # Instagram (browser)
263
+ opencli instagram explore # 探索
264
+ opencli instagram profile username # 用户资料 (id positional)
265
+ opencli instagram search "AI" # 搜索 (query positional)
266
+ opencli instagram user username # 用户详情 (id positional)
267
+ opencli instagram followers username # 粉丝 (id positional)
268
+ opencli instagram following username # 关注 (id positional)
269
+ opencli instagram follow username # 关注用户 (id positional)
270
+ opencli instagram unfollow username # 取消关注 (id positional)
271
+ opencli instagram like postid # 点赞 (id positional)
272
+ opencli instagram unlike postid # 取消点赞 (id positional)
273
+ opencli instagram comment postid "评论" # 评论 (id + text positional)
274
+ opencli instagram save postid # 收藏 (id positional)
275
+ opencli instagram unsave postid # 取消收藏 (id positional)
276
+ opencli instagram saved # 已收藏列表
277
+
278
+ # TikTok (browser)
279
+ opencli tiktok explore # 探索
280
+ opencli tiktok search "AI" # 搜索 (query positional)
281
+ opencli tiktok profile username # 用户资料 (id positional)
282
+ opencli tiktok user username # 用户详情 (id positional)
283
+ opencli tiktok following username # 关注列表 (id positional)
284
+ opencli tiktok follow username # 关注 (id positional)
285
+ opencli tiktok unfollow username # 取消关注 (id positional)
286
+ opencli tiktok like videoid # 点赞 (id positional)
287
+ opencli tiktok unlike videoid # 取消点赞 (id positional)
288
+ opencli tiktok comment videoid "评论" # 评论 (id + text positional)
289
+ opencli tiktok save videoid # 收藏 (id positional)
290
+ opencli tiktok unsave videoid # 取消收藏 (id positional)
291
+ opencli tiktok live # 直播
292
+ opencli tiktok notifications # 通知
293
+ opencli tiktok friends # 朋友
294
+
295
+ # Medium (browser)
296
+ opencli medium feed --limit 10 # 动态流
297
+ opencli medium search "AI" # 搜索 (query positional)
298
+ opencli medium user username # 用户主页 (id positional)
299
+
300
+ # Substack (browser)
301
+ opencli substack feed --limit 10 # 订阅动态
302
+ opencli substack search "AI" # 搜索 (query positional)
303
+ opencli substack publication name # 出版物详情 (id positional)
304
+
305
+ # Sinablog 新浪博客 (browser)
306
+ opencli sinablog hot --limit 10 # 热门
307
+ opencli sinablog search "AI" # 搜索 (query positional)
308
+ opencli sinablog article url # 文章详情
309
+ opencli sinablog user username # 用户主页 (id positional)
310
+
311
+ # Lobsters (public)
312
+ opencli lobsters hot --limit 10 # 热门
313
+ opencli lobsters newest --limit 10 # 最新
314
+ opencli lobsters active --limit 10 # 活跃
315
+ opencli lobsters tag rust # 按标签筛选 (tag positional)
316
+
317
+ # Google (public)
318
+ opencli google news --limit 10 # 新闻
319
+ opencli google search "AI" # 搜索 (query positional)
320
+ opencli google suggest "AI" # 搜索建议 (query positional)
321
+ opencli google trends # 趋势
322
+
323
+ # DEV.to (public)
324
+ opencli devto top --limit 10 # 热门文章
325
+ opencli devto tag javascript --limit 10 # 按标签 (tag positional)
326
+ opencli devto user username # 用户文章 (username positional)
327
+
328
+ # Steam (public)
329
+ opencli steam top-sellers --limit 10 # 热销游戏
330
+
331
+ # Wikipedia (public)
332
+ opencli wikipedia search "AI" # 搜索 (query positional)
333
+ opencli wikipedia summary "Python" # 摘要 (title positional)
334
+ ```
335
+
336
+ ### Desktop Adapter Commands
337
+
338
+ ```bash
339
+ # Cursor (desktop — CDP via Electron)
340
+ opencli cursor status # 检查连接
341
+ opencli cursor send "message" # 发送消息
342
+ opencli cursor read # 读取回复
343
+ opencli cursor new # 新建对话
344
+ opencli cursor dump # 导出 DOM 调试信息
345
+ opencli cursor composer # Composer 模式
346
+ opencli cursor model claude # 切换模型
347
+ opencli cursor extract-code # 提取代码块
348
+ opencli cursor ask "question" # 一键提问并等回复
349
+ opencli cursor screenshot # 截图
350
+ opencli cursor history # 对话历史
351
+ opencli cursor export # 导出对话
352
+
353
+ # Codex (desktop — headless CLI agent)
354
+ opencli codex status # 检查连接
355
+ opencli codex send "message" # 发送消息
356
+ opencli codex read # 读取回复
357
+ opencli codex new # 新建对话
358
+ opencli codex dump # 导出调试信息
359
+ opencli codex extract-diff # 提取 diff
360
+ opencli codex model gpt-4 # 切换模型
361
+ opencli codex ask "question" # 一键提问并等回复
362
+ opencli codex screenshot # 截图
363
+ opencli codex history # 对话历史
364
+ opencli codex export # 导出对话
365
+
366
+ # ChatGPT (desktop — macOS AppleScript/CDP)
367
+ opencli chatgpt status # 检查应用状态
368
+ opencli chatgpt new # 新建对话
369
+ opencli chatgpt send "message" # 发送消息
370
+ opencli chatgpt read # 读取回复
371
+ opencli chatgpt ask "question" # 一键提问并等回复
372
+
373
+ # ChatWise (desktop — multi-LLM client)
374
+ opencli chatwise status # 检查连接
375
+ opencli chatwise new # 新建对话
376
+ opencli chatwise send "message" # 发送消息
377
+ opencli chatwise read # 读取回复
378
+ opencli chatwise ask "question" # 一键提问并等回复
379
+ opencli chatwise model claude # 切换模型
380
+ opencli chatwise history # 对话历史
381
+ opencli chatwise export # 导出对话
382
+ opencli chatwise screenshot # 截图
383
+
384
+ # Notion (desktop — CDP via Electron)
385
+ opencli notion status # 检查连接
386
+ opencli notion search "keyword" # 搜索页面
387
+ opencli notion read # 读取当前页面
388
+ opencli notion new # 新建页面
389
+ opencli notion write "content" # 写入内容
390
+ opencli notion sidebar # 侧边栏导航
391
+ opencli notion favorites # 收藏列表
392
+ opencli notion export # 导出
393
+
394
+ # Discord App (desktop — CDP via Electron)
395
+ opencli discord-app status # 检查连接
396
+ opencli discord-app send "message" # 发送消息
397
+ opencli discord-app read # 读取消息
398
+ opencli discord-app channels # 频道列表
399
+ opencli discord-app servers # 服务器列表
400
+ opencli discord-app search "keyword" # 搜索
401
+ opencli discord-app members # 成员列表
402
+
403
+ # Doubao App 豆包桌面版 (desktop — CDP via Electron)
404
+ opencli doubao-app status # 检查连接
405
+ opencli doubao-app new # 新建对话
406
+ opencli doubao-app send "message" # 发送消息
407
+ opencli doubao-app read # 读取回复
408
+ opencli doubao-app ask "question" # 一键提问并等回复
409
+ opencli doubao-app screenshot # 截图
410
+ opencli doubao-app dump # 导出 DOM 调试信息
235
411
  ```
236
412
 
237
413
  ### Management Commands
@@ -259,14 +435,26 @@ opencli synthesize <site>
259
435
  # Generate: one-shot explore → synthesize → register
260
436
  opencli generate <url> --goal "hot"
261
437
 
438
+ # Record: YOU operate the page, opencli captures every API call → YAML candidates
439
+ # Opens the URL in automation window, injects fetch/XHR interceptor into ALL tabs,
440
+ # polls every 2s, auto-stops after 60s (or press Enter to stop early).
441
+ opencli record <url> # 录制,site name 从域名推断
442
+ opencli record <url> --site mysite # 指定 site name
443
+ opencli record <url> --timeout 120000 # 自定义超时(毫秒,默认 60000)
444
+ opencli record <url> --poll 1000 # 缩短轮询间隔(毫秒,默认 2000)
445
+ opencli record <url> --out .opencli/record/x # 自定义输出目录
446
+ # Output:
447
+ # .opencli/record/<site>/captured.json ← 原始捕获数据(带 url/method/body)
448
+ # .opencli/record/<site>/candidates/*.yaml ← 高置信度候选适配器(score ≥ 8,有 array 结果)
449
+
262
450
  # Strategy Cascade: auto-probe PUBLIC → COOKIE → HEADER
263
451
  opencli cascade <api-url>
264
452
 
265
453
  # Explore with interactive fuzzing (click buttons to trigger lazy APIs)
266
454
  opencli explore <url> --auto --click "字幕,CC,评论"
267
455
 
268
- # Verify: validate adapter definitions
269
- opencli verify
456
+ # Validate: validate adapter definitions
457
+ opencli validate
270
458
  ```
271
459
 
272
460
  ## Output Formats
@@ -289,6 +477,129 @@ opencli bilibili hot -f csv # CSV
289
477
  opencli bilibili hot -v # Show each pipeline step and data flow
290
478
  ```
291
479
 
480
+ ## Record Workflow
481
+
482
+ `record` 是为「无法用 `explore` 自动发现」的页面(需要登录操作、复杂交互、SPA 内路由)准备的手动录制方案。
483
+
484
+ ### 工作原理
485
+
486
+ ```
487
+ opencli record <url>
488
+ → 打开 automation window 并导航到目标 URL
489
+ → 向所有 tab 注入 fetch/XHR 拦截器(幂等,可重复注入)
490
+ → 每 2s 轮询一次:发现新 tab 自动注入,drain 所有 tab 的捕获缓冲区
491
+ → 超时(默认 60s)或按 Enter 停止
492
+ → 分析捕获到的 JSON 请求:去重 → 评分 → 生成候选 YAML
493
+ ```
494
+
495
+ **拦截器特性**:
496
+ - 同时 patch `window.fetch` 和 `XMLHttpRequest`
497
+ - 只捕获 `Content-Type: application/json` 的响应
498
+ - 过滤纯对象少于 2 个 key 的响应(避免 tracking/ping)
499
+ - 跨 tab 隔离:每个 tab 独立缓冲区,轮询时分别 drain
500
+ - 幂等注入:同一 tab 二次注入时先 restore 原始函数再重新 patch,不丢失已捕获数据
501
+
502
+ ### 使用步骤
503
+
504
+ ```bash
505
+ # 1. 启动录制(建议 --timeout 给足操作时间)
506
+ opencli record "https://example.com/page" --timeout 120000
507
+
508
+ # 2. 在弹出的 automation window 里正常操作页面:
509
+ # - 打开列表、搜索、点击条目、切换 Tab
510
+ # - 凡是触发网络请求的操作都会被捕获
511
+
512
+ # 3. 完成操作后按 Enter 停止(或等超时自动停止)
513
+
514
+ # 4. 查看结果
515
+ cat .opencli/record/<site>/captured.json # 原始捕获
516
+ ls .opencli/record/<site>/candidates/ # 候选 YAML
517
+ ```
518
+
519
+ ### 页面类型与捕获预期
520
+
521
+ | 页面类型 | 预期捕获量 | 说明 |
522
+ |---------|-----------|------|
523
+ | 列表/搜索页 | 多(5~20+) | 每次搜索/翻页都会触发新请求 |
524
+ | 详情页(只读) | 少(1~5) | 首屏数据一次性返回,后续操作走 form/redirect |
525
+ | SPA 内路由跳转 | 中等 | 路由切换会触发新接口,但首屏请求在注入前已发出 |
526
+ | 需要登录的页面 | 视操作而定 | 确保 Chrome 已登录目标网站 |
527
+
528
+ > **注意**:如果页面在导航完成前就发出了大部分请求(服务端渲染 / SSR 注水),拦截器会错过这些请求。
529
+ > 解决方案:在页面加载完成后,手动触发能产生新请求的操作(搜索、翻页、切 Tab、展开折叠项等)。
530
+
531
+ ### 候选 YAML → TS CLI 转换
532
+
533
+ 生成的候选 YAML 是起点,通常需要转换为 TypeScript(尤其是 tae 等内部系统):
534
+
535
+ **候选 YAML 结构**(自动生成):
536
+ ```yaml
537
+ site: tae
538
+ name: getList # 从 URL path 推断的名称
539
+ strategy: cookie
540
+ browser: true
541
+ pipeline:
542
+ - navigate: https://...
543
+ - evaluate: |
544
+ (async () => {
545
+ const res = await fetch('/approval/getList.json?procInsId=...', { credentials: 'include' });
546
+ const data = await res.json();
547
+ return (data?.content?.operatorRecords || []).map(item => ({ ... }));
548
+ })()
549
+ ```
550
+
551
+ **转换为 TS CLI**(参考 `src/clis/tae/add-expense.ts` 风格):
552
+ ```typescript
553
+ import { cli, Strategy } from '../../registry.js';
554
+
555
+ cli({
556
+ site: 'tae',
557
+ name: 'get-approval',
558
+ description: '查看报销单审批流程和操作记录',
559
+ domain: 'tae.alibaba-inc.com',
560
+ strategy: Strategy.COOKIE,
561
+ browser: true,
562
+ args: [
563
+ { name: 'proc_ins_id', type: 'string', required: true, positional: true, help: '流程实例 ID(procInsId)' },
564
+ ],
565
+ columns: ['step', 'operator', 'action', 'time'],
566
+ func: async (page, kwargs) => {
567
+ await page.goto('https://tae.alibaba-inc.com/expense/pc.html?_authType=SAML');
568
+ await page.wait(2);
569
+ const result = await page.evaluate(`(async () => {
570
+ const res = await fetch('/approval/getList.json?taskId=&procInsId=${kwargs.proc_ins_id}', {
571
+ credentials: 'include'
572
+ });
573
+ const data = await res.json();
574
+ return data?.content?.operatorRecords || [];
575
+ })()`);
576
+ return (result as any[]).map((r, i) => ({
577
+ step: i + 1,
578
+ operator: r.operatorName || r.userId,
579
+ action: r.operationType,
580
+ time: r.operateTime,
581
+ }));
582
+ },
583
+ });
584
+ ```
585
+
586
+ **转换要点**:
587
+ 1. URL 中的动态 ID(`procInsId`、`taskId` 等)提取为 `args`
588
+ 2. `captured.json` 里的真实 body 结构用于确定正确的数据路径(如 `content.operatorRecords`)
589
+ 3. tae 系统统一用 `{ success, content, errorCode, errorMsg }` 外层包裹,取数据要走 `content.*`
590
+ 4. 认证方式:cookie(`credentials: 'include'`),不需要额外 header
591
+ 5. 文件放入 `src/clis/<site>/`,无需手动注册,`npm run build` 后自动发现
592
+
593
+ ### 故障排查
594
+
595
+ | 现象 | 原因 | 解法 |
596
+ |------|------|------|
597
+ | 捕获 0 条请求 | 拦截器注入失败,或页面无 JSON API | 检查 daemon 是否运行:`curl localhost:19825/status` |
598
+ | 捕获量少(1~3 条) | 页面是只读详情页,首屏数据已在注入前发出 | 手动操作触发更多请求(搜索/翻页),或换用列表页 |
599
+ | 候选 YAML 为 0 | 捕获到的 JSON 都没有 array 结构 | 直接看 `captured.json` 手写 TS CLI |
600
+ | 新开的 tab 没有被拦截 | 轮询间隔内 tab 已关闭 | 缩短 `--poll 500` |
601
+ | 二次运行 record 时数据不连续 | 正常,每次 `record` 启动都是新的 automation window | 无需处理 |
602
+
292
603
  ## Creating Adapters
293
604
 
294
605
  > [!TIP]
package/TESTING.md CHANGED
@@ -30,12 +30,12 @@ tests/
30
30
  ├── smoke/
31
31
  │ └── api-health.test.ts # 外部 API、adapter 定义、命令注册健康检查
32
32
  src/
33
- └── **/*.test.ts # 单元测试(当前 31 个文件)
33
+ └── **/*.test.ts # 单元测试(当前 32 个文件)
34
34
  ```
35
35
 
36
36
  | 层 | 位置 | 当前文件数 | 运行方式 | 用途 |
37
37
  |---|---|---:|---|---|
38
- | 单元测试 | `src/**/*.test.ts` | 31 | `npx vitest run src/` | 内部模块、pipeline、adapter 工具函数 |
38
+ | 单元测试 | `src/**/*.test.ts` | 32 | `npx vitest run src/` | 内部模块、pipeline、adapter 工具函数 |
39
39
  | E2E 测试 | `tests/e2e/*.test.ts` | 5 | `npx vitest run tests/e2e/` | 真实 CLI 命令执行 |
40
40
  | 烟雾测试 | `tests/smoke/*.test.ts` | 1 | `npx vitest run tests/smoke/` | 外部 API 与注册完整性 |
41
41
 
@@ -43,13 +43,13 @@ src/
43
43
 
44
44
  ## 当前覆盖范围
45
45
 
46
- ### 单元测试(31 个文件)
46
+ ### 单元测试(32 个文件)
47
47
 
48
48
  | 领域 | 文件 |
49
49
  |---|---|
50
50
  | 核心运行时与输出 | `src/browser.test.ts`, `src/browser/dom-snapshot.test.ts`, `src/build-manifest.test.ts`, `src/capabilityRouting.test.ts`, `src/doctor.test.ts`, `src/engine.test.ts`, `src/interceptor.test.ts`, `src/output.test.ts`, `src/plugin.test.ts`, `src/registry.test.ts`, `src/snapshotFormatter.test.ts` |
51
51
  | pipeline 与下载 | `src/download/index.test.ts`, `src/pipeline/executor.test.ts`, `src/pipeline/template.test.ts`, `src/pipeline/transform.test.ts` |
52
- | 站点 / adapter 逻辑 | `src/clis/apple-podcasts/commands.test.ts`, `src/clis/apple-podcasts/utils.test.ts`, `src/clis/bloomberg/utils.test.ts`, `src/clis/chaoxing/utils.test.ts`, `src/clis/coupang/utils.test.ts`, `src/clis/google/utils.test.ts`, `src/clis/grok/ask.test.ts`, `src/clis/twitter/timeline.test.ts`, `src/clis/weread/utils.test.ts`, `src/clis/xiaohongshu/creator-note-detail.test.ts`, `src/clis/xiaohongshu/creator-notes-summary.test.ts`, `src/clis/xiaohongshu/creator-notes.test.ts`, `src/clis/xiaohongshu/user-helpers.test.ts`, `src/clis/xiaoyuzhou/utils.test.ts`, `src/clis/youtube/transcript-group.test.ts`, `src/clis/zhihu/download.test.ts` |
52
+ | 站点 / adapter 逻辑 | `src/clis/apple-podcasts/commands.test.ts`, `src/clis/apple-podcasts/utils.test.ts`, `src/clis/bloomberg/utils.test.ts`, `src/clis/chaoxing/utils.test.ts`, `src/clis/coupang/utils.test.ts`, `src/clis/google/utils.test.ts`, `src/clis/grok/ask.test.ts`, `src/clis/twitter/timeline.test.ts`, `src/clis/weread/utils.test.ts`, `src/clis/xiaohongshu/creator-note-detail.test.ts`, `src/clis/xiaohongshu/creator-notes-summary.test.ts`, `src/clis/xiaohongshu/creator-notes.test.ts`, `src/clis/xiaohongshu/search.test.ts`, `src/clis/xiaohongshu/user-helpers.test.ts`, `src/clis/xiaoyuzhou/utils.test.ts`, `src/clis/youtube/transcript-group.test.ts`, `src/clis/zhihu/download.test.ts` |
53
53
 
54
54
  这些测试覆盖的重点包括:
55
55
 
@@ -10,6 +10,7 @@
10
10
  import { WebSocket } from 'ws';
11
11
  import { wrapForEval } from './utils.js';
12
12
  import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
13
+ import { generateStealthJs } from './stealth.js';
13
14
  import { clickJs, typeTextJs, pressKeyJs, waitForTextJs, scrollJs, autoScrollJs, networkRequestsJs, waitForDomStableJs, } from './dom-helpers.js';
14
15
  const CDP_SEND_TIMEOUT = 30_000; // 30s per command
15
16
  export class CDPBridge {
@@ -38,9 +39,17 @@ export class CDPBridge {
38
39
  const ws = new WebSocket(wsUrl);
39
40
  const timeoutMs = (opts?.timeout ?? 10) * 1000; // opts.timeout is in seconds
40
41
  const timeout = setTimeout(() => reject(new Error('CDP connect timeout')), timeoutMs);
41
- ws.on('open', () => {
42
+ ws.on('open', async () => {
42
43
  clearTimeout(timeout);
43
44
  this._ws = ws;
45
+ // Register stealth script to run before any page JS on every navigation.
46
+ try {
47
+ await this.send('Page.enable');
48
+ await this.send('Page.addScriptToEvaluateOnNewDocument', { source: generateStealthJs() });
49
+ }
50
+ catch {
51
+ // Non-fatal: stealth is best-effort
52
+ }
44
53
  resolve(new CDPPage(this));
45
54
  });
46
55
  ws.on('error', (err) => {
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Provides a typed send() function that posts a Command and returns a Result.
5
5
  */
6
- const DAEMON_PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? '19825', 10);
6
+ import { DEFAULT_DAEMON_PORT } from '../constants.js';
7
+ const DAEMON_PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
7
8
  const DAEMON_URL = `http://127.0.0.1:${DAEMON_PORT}`;
8
9
  let _idCounter = 0;
9
10
  function generateId() {
@@ -4,6 +4,7 @@
4
4
  * Only needs to check if the daemon is running. No more file system
5
5
  * scanning for @playwright/mcp locations.
6
6
  */
7
+ import { DEFAULT_DAEMON_PORT } from '../constants.js';
7
8
  import { isDaemonRunning } from './daemon-client.js';
8
9
  export { isDaemonRunning };
9
10
  /**
@@ -11,7 +12,7 @@ export { isDaemonRunning };
11
12
  */
12
13
  export async function checkDaemonStatus() {
13
14
  try {
14
- const port = parseInt(process.env.OPENCLI_DAEMON_PORT ?? '19825', 10);
15
+ const port = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
15
16
  const res = await fetch(`http://127.0.0.1:${port}/status`, {
16
17
  headers: { 'X-OpenCLI': '1' },
17
18
  });
@@ -4,5 +4,6 @@
4
4
  * Simplified — no more token/extension/CDP classification.
5
5
  * The daemon architecture has a single failure mode: daemon not reachable or extension not connected.
6
6
  */
7
+ import { BrowserConnectError } from '../errors.js';
7
8
  export type ConnectFailureKind = 'daemon-not-running' | 'extension-not-connected' | 'command-failed' | 'unknown';
8
- export declare function formatBrowserConnectError(kind: ConnectFailureKind, detail?: string): Error;
9
+ export declare function formatBrowserConnectError(kind: ConnectFailureKind, detail?: string): BrowserConnectError;
@@ -4,25 +4,25 @@
4
4
  * Simplified — no more token/extension/CDP classification.
5
5
  * The daemon architecture has a single failure mode: daemon not reachable or extension not connected.
6
6
  */
7
+ import { BrowserConnectError } from '../errors.js';
8
+ import { DEFAULT_DAEMON_PORT } from '../constants.js';
7
9
  export function formatBrowserConnectError(kind, detail) {
8
10
  switch (kind) {
9
11
  case 'daemon-not-running':
10
- return new Error('Cannot connect to opencli daemon.\n\n' +
11
- 'The daemon should start automatically. If it doesn\'t, try:\n' +
12
+ return new BrowserConnectError('Cannot connect to opencli daemon.' +
13
+ (detail ? `\n\n${detail}` : ''), 'The daemon should start automatically. If it doesn\'t, try:\n' +
12
14
  ' node dist/daemon.js\n' +
13
- 'Make sure port 19825 is available.' +
14
- (detail ? `\n\n${detail}` : ''));
15
+ `Make sure port ${DEFAULT_DAEMON_PORT} is available.`);
15
16
  case 'extension-not-connected':
16
- return new Error('opencli Browser Bridge extension is not connected.\n\n' +
17
- 'Please install the extension:\n' +
17
+ return new BrowserConnectError('opencli Browser Bridge extension is not connected.' +
18
+ (detail ? `\n\n${detail}` : ''), 'Please install the extension:\n' +
18
19
  ' 1. Download from GitHub Releases\n' +
19
20
  ' 2. Open chrome://extensions/ → Enable Developer Mode\n' +
20
21
  ' 3. Click "Load unpacked" → select the extension folder\n' +
21
- ' 4. Make sure Chrome is running' +
22
- (detail ? `\n\n${detail}` : ''));
22
+ ' 4. Make sure Chrome is running');
23
23
  case 'command-failed':
24
- return new Error(`Browser command failed: ${detail ?? 'unknown error'}`);
24
+ return new BrowserConnectError(`Browser command failed: ${detail ?? 'unknown error'}`);
25
25
  default:
26
- return new Error(detail ?? 'Failed to connect to browser');
26
+ return new BrowserConnectError(detail ?? 'Failed to connect to browser');
27
27
  }
28
28
  }
@@ -9,6 +9,7 @@ export { BrowserBridge, BrowserBridge as PlaywrightMCP } from './mcp.js';
9
9
  export { CDPBridge } from './cdp.js';
10
10
  export { isDaemonRunning } from './daemon-client.js';
11
11
  export { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
12
+ export { generateStealthJs } from './stealth.js';
12
13
  export type { SnapshotOptions } from './dom-snapshot.js';
13
14
  import { extractTabEntries, diffTabIndexes, appendLimited } from './tabs.js';
14
15
  import { withTimeoutMs } from '../runtime.js';
@@ -9,6 +9,7 @@ export { BrowserBridge, BrowserBridge as PlaywrightMCP } from './mcp.js';
9
9
  export { CDPBridge } from './cdp.js';
10
10
  export { isDaemonRunning } from './daemon-client.js';
11
11
  export { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
12
+ export { generateStealthJs } from './stealth.js';
12
13
  import { extractTabEntries, diffTabIndexes, appendLimited } from './tabs.js';
13
14
  import { __test__ as cdpTest } from './cdp.js';
14
15
  import { withTimeoutMs } from '../runtime.js';
@@ -13,6 +13,7 @@ import { formatSnapshot } from '../snapshotFormatter.js';
13
13
  import { sendCommand } from './daemon-client.js';
14
14
  import { wrapForEval } from './utils.js';
15
15
  import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
16
+ import { generateStealthJs } from './stealth.js';
16
17
  import { clickJs, typeTextJs, pressKeyJs, waitForTextJs, scrollJs, autoScrollJs, networkRequestsJs, waitForDomStableJs, } from './dom-helpers.js';
17
18
  /**
18
19
  * Page — implements IPage by talking to the daemon via HTTP.
@@ -41,6 +42,17 @@ export class Page {
41
42
  if (result?.tabId) {
42
43
  this._tabId = result.tabId;
43
44
  }
45
+ // Inject stealth anti-detection patches (guard flag prevents double-injection).
46
+ try {
47
+ await sendCommand('exec', {
48
+ code: generateStealthJs(),
49
+ ...this._workspaceOpt(),
50
+ ...this._tabOpt(),
51
+ });
52
+ }
53
+ catch {
54
+ // Non-fatal: stealth is best-effort
55
+ }
44
56
  // Smart settle: use DOM stability detection instead of fixed sleep.
45
57
  // settleMs is now a timeout cap (default 1000ms), not a fixed wait.
46
58
  if (options?.waitUntil !== 'none') {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Stealth anti-detection module.
3
+ *
4
+ * Generates JS code that patches browser globals to hide automation
5
+ * fingerprints (e.g. navigator.webdriver, missing chrome object, empty
6
+ * plugin list). Injected before page scripts run so that websites cannot
7
+ * detect CDP / extension-based control.
8
+ *
9
+ * Inspired by puppeteer-extra-plugin-stealth.
10
+ */
11
+ /** Guard flag set on `window` to prevent double-injection. */
12
+ export declare const STEALTH_GUARD = "__opencli_stealth_applied";
13
+ /**
14
+ * Return a self-contained JS string that, when evaluated in a page context,
15
+ * applies all stealth patches. Safe to call multiple times — the guard flag
16
+ * ensures patches are applied only once.
17
+ */
18
+ export declare function generateStealthJs(): string;