@jackwener/opencli 1.5.2 → 1.5.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 (118) hide show
  1. package/.github/workflows/ci.yml +6 -7
  2. package/README.md +21 -362
  3. package/dist/browser/cdp.js +20 -1
  4. package/dist/browser/daemon-client.js +3 -2
  5. package/dist/browser/dom-helpers.d.ts +11 -0
  6. package/dist/browser/dom-helpers.js +42 -0
  7. package/dist/browser/dom-helpers.test.d.ts +1 -0
  8. package/dist/browser/dom-helpers.test.js +92 -0
  9. package/dist/browser/index.d.ts +0 -12
  10. package/dist/browser/index.js +0 -13
  11. package/dist/browser/mcp.js +4 -3
  12. package/dist/browser/page.d.ts +1 -0
  13. package/dist/browser/page.js +14 -1
  14. package/dist/browser.test.js +15 -11
  15. package/dist/clis/36kr/hot.js +1 -1
  16. package/dist/clis/36kr/search.js +1 -1
  17. package/dist/clis/_shared/common.d.ts +8 -0
  18. package/dist/clis/_shared/common.js +10 -0
  19. package/dist/clis/bloomberg/news.js +1 -1
  20. package/dist/clis/douban/utils.js +3 -6
  21. package/dist/clis/medium/utils.js +1 -1
  22. package/dist/clis/producthunt/browse.js +1 -1
  23. package/dist/clis/producthunt/hot.js +1 -1
  24. package/dist/clis/sinablog/utils.js +6 -7
  25. package/dist/clis/substack/utils.js +2 -2
  26. package/dist/clis/twitter/block.js +1 -1
  27. package/dist/clis/twitter/bookmark.js +1 -1
  28. package/dist/clis/twitter/delete.js +1 -1
  29. package/dist/clis/twitter/follow.js +1 -1
  30. package/dist/clis/twitter/followers.js +2 -2
  31. package/dist/clis/twitter/following.js +2 -2
  32. package/dist/clis/twitter/hide-reply.js +1 -1
  33. package/dist/clis/twitter/like.js +1 -1
  34. package/dist/clis/twitter/notifications.js +1 -1
  35. package/dist/clis/twitter/profile.js +1 -1
  36. package/dist/clis/twitter/reply-dm.js +1 -1
  37. package/dist/clis/twitter/reply.js +1 -1
  38. package/dist/clis/twitter/search.js +1 -1
  39. package/dist/clis/twitter/unblock.js +1 -1
  40. package/dist/clis/twitter/unbookmark.js +1 -1
  41. package/dist/clis/twitter/unfollow.js +1 -1
  42. package/dist/clis/xiaohongshu/comments.test.js +1 -0
  43. package/dist/clis/xiaohongshu/creator-note-detail.test.js +1 -0
  44. package/dist/clis/xiaohongshu/creator-notes.test.js +1 -0
  45. package/dist/clis/xiaohongshu/publish.test.js +1 -0
  46. package/dist/clis/xiaohongshu/search.test.js +1 -0
  47. package/dist/download/index.js +39 -33
  48. package/dist/download/index.test.js +15 -1
  49. package/dist/execution.js +3 -2
  50. package/dist/main.js +2 -0
  51. package/dist/node-network.d.ts +10 -0
  52. package/dist/node-network.js +174 -0
  53. package/dist/node-network.test.d.ts +1 -0
  54. package/dist/node-network.test.js +55 -0
  55. package/dist/pipeline/executor.test.js +1 -0
  56. package/dist/pipeline/steps/download.test.js +1 -0
  57. package/dist/pipeline/steps/intercept.js +4 -5
  58. package/dist/types.d.ts +2 -0
  59. package/dist/utils.d.ts +2 -0
  60. package/dist/utils.js +4 -0
  61. package/docs/superpowers/plans/2026-03-28-perf-smart-wait.md +1143 -0
  62. package/docs/superpowers/specs/2026-03-28-perf-smart-wait-design.md +170 -0
  63. package/extension/dist/background.js +1 -1
  64. package/extension/manifest.json +1 -1
  65. package/extension/package-lock.json +2 -2
  66. package/extension/package.json +1 -1
  67. package/extension/src/background.ts +1 -1
  68. package/package.json +2 -1
  69. package/src/browser/cdp.ts +21 -0
  70. package/src/browser/daemon-client.ts +3 -2
  71. package/src/browser/dom-helpers.test.ts +100 -0
  72. package/src/browser/dom-helpers.ts +44 -0
  73. package/src/browser/index.ts +0 -15
  74. package/src/browser/mcp.ts +4 -3
  75. package/src/browser/page.ts +16 -0
  76. package/src/browser.test.ts +16 -12
  77. package/src/clis/36kr/hot.ts +1 -1
  78. package/src/clis/36kr/search.ts +1 -1
  79. package/src/clis/_shared/common.ts +11 -0
  80. package/src/clis/bloomberg/news.ts +1 -1
  81. package/src/clis/douban/utils.ts +3 -7
  82. package/src/clis/medium/utils.ts +1 -1
  83. package/src/clis/producthunt/browse.ts +1 -1
  84. package/src/clis/producthunt/hot.ts +1 -1
  85. package/src/clis/sinablog/utils.ts +6 -7
  86. package/src/clis/substack/utils.ts +2 -2
  87. package/src/clis/twitter/block.ts +1 -1
  88. package/src/clis/twitter/bookmark.ts +1 -1
  89. package/src/clis/twitter/delete.ts +1 -1
  90. package/src/clis/twitter/follow.ts +1 -1
  91. package/src/clis/twitter/followers.ts +2 -2
  92. package/src/clis/twitter/following.ts +2 -2
  93. package/src/clis/twitter/hide-reply.ts +1 -1
  94. package/src/clis/twitter/like.ts +1 -1
  95. package/src/clis/twitter/notifications.ts +1 -1
  96. package/src/clis/twitter/profile.ts +1 -1
  97. package/src/clis/twitter/reply-dm.ts +1 -1
  98. package/src/clis/twitter/reply.ts +1 -1
  99. package/src/clis/twitter/search.ts +1 -1
  100. package/src/clis/twitter/unblock.ts +1 -1
  101. package/src/clis/twitter/unbookmark.ts +1 -1
  102. package/src/clis/twitter/unfollow.ts +1 -1
  103. package/src/clis/xiaohongshu/comments.test.ts +1 -0
  104. package/src/clis/xiaohongshu/creator-note-detail.test.ts +1 -0
  105. package/src/clis/xiaohongshu/creator-notes.test.ts +1 -0
  106. package/src/clis/xiaohongshu/publish.test.ts +1 -0
  107. package/src/clis/xiaohongshu/search.test.ts +1 -0
  108. package/src/download/index.test.ts +19 -1
  109. package/src/download/index.ts +50 -41
  110. package/src/execution.ts +3 -2
  111. package/src/main.ts +3 -0
  112. package/src/node-network.test.ts +93 -0
  113. package/src/node-network.ts +213 -0
  114. package/src/pipeline/executor.test.ts +1 -0
  115. package/src/pipeline/steps/download.test.ts +1 -0
  116. package/src/pipeline/steps/intercept.ts +4 -5
  117. package/src/types.ts +2 -0
  118. package/src/utils.ts +5 -0
@@ -39,13 +39,15 @@ jobs:
39
39
  run: npm run build
40
40
 
41
41
  # ── Unit tests (vitest shard) ──
42
+ # PR: ubuntu + Node 22 only (fast feedback, 2 jobs).
43
+ # Push to main/dev: full matrix for cross-platform/cross-version coverage (12 jobs).
42
44
  unit-test:
43
45
  runs-on: ${{ matrix.os }}
44
46
  strategy:
45
47
  fail-fast: false
46
48
  matrix:
47
- os: [ubuntu-latest, macos-latest, windows-latest]
48
- node-version: ['20', '22']
49
+ os: ${{ (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && fromJSON('["ubuntu-latest","macos-latest","windows-latest"]') || fromJSON('["ubuntu-latest"]') }}
50
+ node-version: ${{ (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && fromJSON('["20","22"]') || fromJSON('["22"]') }}
49
51
  shard: [1, 2]
50
52
  steps:
51
53
  - uses: actions/checkout@v6
@@ -82,12 +84,9 @@ jobs:
82
84
  - name: Run unit tests under Bun
83
85
  run: bun vitest run --project unit --reporter=verbose
84
86
 
87
+ # Adapter tests are pure unit tests — OS doesn't affect results.
85
88
  adapter-test:
86
- runs-on: ${{ matrix.os }}
87
- strategy:
88
- fail-fast: false
89
- matrix:
90
- os: [ubuntu-latest, macos-latest, windows-latest]
89
+ runs-on: ubuntu-latest
91
90
  needs: build
92
91
  steps:
93
92
  - uses: actions/checkout@v6
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # OpenCLI
2
2
 
3
- > **Make any website, Electron App, or Local Tool your CLI.**
3
+ > **Make any website, Electron App, or Local Tool your CLI.**
4
4
  > Zero risk · Reuse Chrome login · AI-powered discovery · Universal CLI Hub
5
5
 
6
6
  [![中文文档](https://img.shields.io/badge/docs-%E4%B8%AD%E6%96%87-0F766E?style=flat-square)](./README.zh-CN.md)
@@ -8,384 +8,43 @@
8
8
  [![Node.js Version](https://img.shields.io/node/v/@jackwener/opencli?style=flat-square)](https://nodejs.org)
9
9
  [![License](https://img.shields.io/npm/l/@jackwener/opencli?style=flat-square)](./LICENSE)
10
10
 
11
- A CLI tool that turns **any website**, **Electron app**, or **local CLI tool** into a command-line interface — Bilibili, Zhihu, 小红书, Twitter/X, Reddit, YouTube, Antigravity, `gh`, `docker`, and [many more](#built-in-commands) — powered by browser session reuse and AI-native discovery.
11
+ A CLI tool that turns **any website**, **Electron app**, or **local CLI tool** into a command-line interface — powered by browser session reuse and AI-native discovery.
12
12
 
13
- **Built for AI Agents**: Simply configure an instruction in your global `AGENT.md` or `.cursorrules` guiding the AI to execute `opencli list` via Bash to discover available tools. Register your favorite local CLIs (`opencli register mycli`), and the AI will automatically learn how to invoke all your tools perfectly!
14
-
15
- **CLI All Electron Apps! The Most Powerful Update Has Arrived!**
16
- Turn ANY Electron application into a CLI tool! Recombine, script, and extend applications like Antigravity Ultra seamlessly. Now AI can control itself natively. Unlimited possibilities await!
17
-
18
- ---
19
-
20
- ## Highlights
21
-
22
- - **CLI All Electron** — CLI-ify apps like Antigravity Ultra! Now AI can control itself natively using cc/openclaw!
13
+ - **Zero LLM cost** No tokens consumed at runtime.
23
14
  - **Account-safe** — Reuses Chrome's logged-in state; your credentials never leave the browser.
24
- - **AI Agent ready** — `explore` discovers APIs, `synthesize` generates adapters, `cascade` finds auth strategies.
25
- - **External CLI Hub** — Discover, auto-install, and passthrough commands to any external CLI (gh, obsidian, docker, etc). Zero setup.
26
- - **Self-healing setup** — `opencli doctor` diagnoses and auto-starts the daemon, extension, and live browser connectivity.
27
- - **Dynamic Loader** — Simply drop `.ts` or `.yaml` adapters into the `clis/` folder for auto-registration.
28
- - **Dual-Engine Architecture** — Supports both YAML declarative data pipelines and robust browser runtime TypeScript injections.
29
-
30
- ## Why opencli?
31
-
32
- There are many great browser automation tools. Here's when opencli is the right choice:
33
-
34
- | Your need | Best tool | Why |
35
- |-----------|-----------|-----|
36
- | Scheduled data extraction from specific sites | **opencli** | Pre-built adapters, deterministic JSON, zero LLM cost |
37
- | AI agent needs reliable site operations | **opencli** | Hundreds of commands, structured output, fast deterministic response |
38
- | Explore an unknown website ad-hoc | Browser-Use, Stagehand | LLM-driven general browsing for one-off tasks |
39
- | Large-scale web crawling | Crawl4AI, Scrapy | Purpose-built for throughput and scale |
40
- | Control desktop Electron apps from terminal | **opencli** | CDP + AppleScript — the only CLI tool that does this |
41
-
42
- **What makes opencli different:**
43
-
44
- - **Zero LLM cost** — No tokens consumed at runtime. Run 10,000 times and pay nothing.
45
15
  - **Deterministic** — Same command, same output schema, every time. Pipeable, scriptable, CI-friendly.
46
- - **Broad coverage** — 50+ sites across global and Chinese platforms (Bilibili, Zhihu, Xiaohongshu, Reddit, HackerNews, and more), plus desktop Electron apps via CDP.
47
-
48
- > For a detailed comparison with Browser-Use, Crawl4AI, Firecrawl, and others, see the [Comparison Guide](./docs/comparison.md).
49
-
50
- ## Prerequisites
51
-
52
- - **Node.js**: >= 20.0.0 (or **Bun** >= 1.0 — see [Runtime Support](#runtime-support) below)
53
- - **Chrome** running **and logged into the target site** (e.g. bilibili.com, zhihu.com, xiaohongshu.com).
54
-
55
- > **⚠️ Important**: Browser commands reuse your Chrome login session. You must be logged into the target website in Chrome before running commands. If you get empty data or errors, check your login status first.
56
-
57
- ### Runtime Support
58
-
59
- OpenCLI works with both **Node.js** (≥ 20) and **Bun** (≥ 1.0). All commands and adapters are runtime-agnostic.
60
-
61
- ```bash
62
- # Development with Bun (faster startup)
63
- npm run dev:bun
64
-
65
- # Run the built CLI with Bun
66
- npm run start:bun
67
-
68
- # Run unit tests under Bun
69
- npm run test:bun
70
-
71
- # Run E2E tests with Bun as the runtime
72
- OPENCLI_TEST_RUNTIME=bun npm run test:e2e
73
- ```
74
-
75
- Use `opencli doctor` to check your current runtime — it displays the active engine (e.g. `node v22.13.0` or `bun 1.1.42`).
76
-
77
- OpenCLI connects to your browser through a lightweight **Browser Bridge** Chrome Extension + micro-daemon (zero config, auto-start).
78
-
79
- ### Browser Bridge Extension Setup
80
-
81
- You can install the extension via either method:
82
-
83
- **Method 1: Download Pre-built Release (Recommended)**
84
- 1. Go to the GitHub [Releases page](https://github.com/jackwener/opencli/releases) and download the latest `opencli-extension.zip`.
85
- 2. Unzip the file and open `chrome://extensions`, enable **Developer mode** (top-right toggle).
86
- 3. Click **Load unpacked** and select the unzipped folder.
87
-
88
- **Method 2: Load Source (For Developers)**
89
- 1. Open `chrome://extensions` and enable **Developer mode**.
90
- 2. Click **Load unpacked** and select the `extension/` directory from this repository.
91
-
92
- That's it! The daemon auto-starts when you run any browser command. No tokens, no manual configuration.
93
-
94
- > **Tip**: Use `opencli doctor` for ongoing diagnosis:
95
- > ```bash
96
- > opencli doctor # Check extension + daemon connectivity
97
- > ```
16
+ - **AI Agent ready** — `explore` discovers APIs, `synthesize` generates adapters, `cascade` finds auth strategies.
17
+ - **65+ built-in adapters** — Global and Chinese platforms, plus desktop Electron apps via CDP.
98
18
 
99
19
  ## Quick Start
100
20
 
101
- ### Install via npm (recommended)
102
-
103
21
  ```bash
104
22
  npm install -g @jackwener/opencli
23
+ opencli doctor # Check setup
24
+ opencli list # See all commands
25
+ opencli hackernews top --limit 5 # Public API, no browser needed
26
+ opencli bilibili hot -f json # Browser command, JSON output
105
27
  ```
106
28
 
107
- Then use directly:
108
-
109
- ```bash
110
- opencli list # See all commands
111
- opencli list -f yaml # List commands as YAML
112
- opencli hackernews top --limit 5 # Public API, no browser
113
- opencli bilibili hot --limit 5 # Browser command
114
- opencli zhihu hot -f json # JSON output
115
- opencli zhihu hot -f yaml # YAML output
116
- ```
117
-
118
- ### Install from source (for developers)
119
-
120
- ```bash
121
- git clone git@github.com:jackwener/opencli.git
122
- cd opencli
123
- npm install
124
- npm run build
125
- npm link # Link binary globally
126
- opencli list # Now you can use it anywhere!
127
- ```
128
-
129
- ### Update
130
-
131
- ```bash
132
- npm install -g @jackwener/opencli@latest
133
- ```
134
-
135
- ## Built-in Commands
136
-
137
- Run `opencli list` for the live registry.
138
-
139
- | Site | Commands | Mode |
140
- |------|----------|------|
141
- | **twitter** | `trending` `bookmarks` `profile` `search` `timeline` `thread` `following` `followers` `notifications` `post` `reply` `delete` `like` `article` `follow` `unfollow` `bookmark` `unbookmark` `download` `accept` `reply-dm` `block` `unblock` `hide-reply` | Browser |
142
- | **reddit** | `hot` `frontpage` `popular` `search` `subreddit` `read` `user` `user-posts` `user-comments` `upvote` `save` `comment` `subscribe` `saved` `upvoted` | Browser |
143
- | **cursor** | `status` `send` `read` `new` `dump` `composer` `model` `extract-code` `ask` `screenshot` `history` `export` | Desktop |
144
- | **bilibili** | `hot` `search` `me` `favorite` `history` `feed` `subtitle` `dynamic` `ranking` `following` `user-videos` `download` | Browser |
145
- | **codex** | `status` `send` `read` `new` `dump` `extract-diff` `model` `ask` `screenshot` `history` `export` | Desktop |
146
- | **chatwise** | `status` `new` `send` `read` `ask` `model` `history` `export` `screenshot` | Desktop |
147
- | **doubao** | `status` `new` `send` `read` `ask` | Browser |
148
- | **doubao-app** | `status` `new` `send` `read` `ask` `screenshot` `dump` | Desktop |
149
- | **notion** | `status` `search` `read` `new` `write` `sidebar` `favorites` `export` | Desktop |
150
- | **discord-app** | `status` `send` `read` `channels` `servers` `search` `members` | Desktop |
151
- | **v2ex** | `hot` `latest` `topic` `node` `user` `member` `replies` `nodes` `daily` `me` `notifications` | Public / Browser |
152
- | **xueqiu** | `feed` `hot-stock` `hot` `search` `stock` `watchlist` `earnings-date` `fund-holdings` `fund-snapshot` | Browser |
153
- | **antigravity** | `status` `send` `read` `new` `dump` `extract-code` `model` `watch` | Desktop |
154
- | **chatgpt** | `status` `new` `send` `read` `ask` `model` | Desktop |
155
- | **xiaohongshu** | `search` `notifications` `feed` `user` `download` `publish` `creator-notes` `creator-note-detail` `creator-notes-summary` `creator-profile` `creator-stats` | Browser |
156
- | **apple-podcasts** | `search` `episodes` `top` | Public |
157
- | **xiaoyuzhou** | `podcast` `podcast-episodes` `episode` | Public |
158
- | **zhihu** | `hot` `search` `question` `download` | Browser |
159
- | **weixin** | `download` | Browser |
160
- | **youtube** | `search` `video` `transcript` | Browser |
161
- | **boss** | `search` `detail` `recommend` `joblist` `greet` `batchgreet` `send` `chatlist` `chatmsg` `invite` `mark` `exchange` `resume` `stats` | Browser |
162
- | **coupang** | `search` `add-to-cart` | Browser |
163
- | **bbc** | `news` | Public |
164
- | **bloomberg** | `main` `markets` `economics` `industries` `tech` `politics` `businessweek` `opinions` `feeds` `news` | Public / Browser |
165
- | **ctrip** | `search` | Browser |
166
- | **devto** | `top` `tag` `user` | Public |
167
- | **dictionary** | `search` `synonyms` `examples` | Public |
168
- | **arxiv** | `search` `paper` | Public |
169
- | **paperreview** | `submit` `review` `feedback` | Public |
170
- | **wikipedia** | `search` `summary` `random` `trending` | Public |
171
- | **hackernews** | `top` `new` `best` `ask` `show` `jobs` `search` `user` | Public |
172
- | **jd** | `item` | Browser |
173
- | **linkedin** | `search` `timeline` | Browser |
174
- | **reuters** | `search` | Browser |
175
- | **smzdm** | `search` | Browser |
176
- | **web** | `read` | Browser |
177
- | **weibo** | `hot` `search` | Browser |
178
- | **yahoo-finance** | `quote` | Browser |
179
- | **sinafinance** | `news` | 🌐 Public |
180
- | **barchart** | `quote` `options` `greeks` `flow` | Browser |
181
- | **chaoxing** | `assignments` `exams` | Browser |
182
- | **grok** | `ask` | Browser |
183
- | **hf** | `top` | Public |
184
- | **jike** | `feed` `search` `create` `like` `comment` `repost` `notifications` `post` `topic` `user` | Browser |
185
- | **jimeng** | `generate` `history` | Browser |
186
- | **yollomi** | `generate` `video` `edit` `upload` `models` `remove-bg` `upscale` `face-swap` `restore` `try-on` `background` `object-remover` | Browser |
187
- | **linux-do** | `feed` `categories` `tags` `search` `topic` `user-topics` `user-posts` | Browser |
188
- | **stackoverflow** | `hot` `search` `bounties` `unanswered` | Public |
189
- | **steam** | `top-sellers` | Public |
190
- | **weread** | `shelf` `search` `book` `highlights` `notes` `notebooks` `ranking` | Browser |
191
- | **douban** | `search` `top250` `subject` `photos` `download` `marks` `reviews` `movie-hot` `book-hot` | Browser |
192
- | **facebook** | `feed` `profile` `search` `friends` `groups` `events` `notifications` `memories` `add-friend` `join-group` | Browser |
193
- | **google** | `news` `search` `suggest` `trends` | Public |
194
- | **36kr** | `news` `hot` `search` `article` | Public / Browser |
195
- | **imdb** | `search` `title` `top` `trending` `person` `reviews` | Public |
196
- | **producthunt** | `posts` `today` `hot` `browse` | Public / Browser |
197
- | **instagram** | `explore` `profile` `search` `user` `followers` `following` `follow` `unfollow` `like` `unlike` `comment` `save` `unsave` `saved` | Browser |
198
- | **lobsters** | `hot` `newest` `active` `tag` | Public |
199
- | **medium** | `feed` `search` `user` | Browser |
200
- | **sinablog** | `hot` `search` `article` `user` | Browser |
201
- | **substack** | `feed` `search` `publication` | Browser |
202
- | **pixiv** | `ranking` `search` `user` `illusts` `detail` `download` | Browser |
203
- | **tiktok** | `explore` `search` `profile` `user` `following` `follow` `unfollow` `like` `unlike` `comment` `save` `unsave` `live` `notifications` `friends` | Browser |
204
-
205
-
206
- ### External CLI Hub
207
-
208
- OpenCLI acts as a universal hub for your existing command-line tools. It provides unified discovery, automatic installation, and pure passthrough execution.
209
-
210
- | External CLI | Description | Commands Example |
211
- |--------------|-------------|------------------|
212
- | **gh** | GitHub CLI | `opencli gh pr list --limit 5` |
213
- | **obsidian** | Obsidian vault management | `opencli obsidian search query="AI"` |
214
- | **docker** | Docker command-line interface | `opencli docker ps` |
215
- | **readwise** | Readwise & Reader CLI | `opencli readwise login` |
216
- | **gws** | Google Workspace CLI — Docs, Sheets, Drive, Gmail, Calendar | `opencli gws docs list` |
217
-
218
- **Zero Configuration**: OpenCLI purely passes your inputs to the underlying binary via standard I/O streams. The external CLI works exactly as it naturally would, maintaining its standard output formats.
219
-
220
- **Auto-Installation**: If you run `opencli gh ...` and `gh` is not installed on your system, OpenCLI will automatically try to install it using your system's package manager (e.g., `brew install gh`) before seamlessly re-running the command.
221
-
222
- **Register Your Own**:
223
- Add any local CLI to your OpenCLI registry so AI agents can automatically discover it via the `opencli list` command.
224
- ```bash
225
- opencli register mycli
226
- ```
227
-
228
- ### Desktop App Adapters
229
-
230
- Each desktop adapter has its own detailed documentation with commands reference, setup guide, and examples:
231
-
232
- If you want to add support for a new Electron desktop app, start with [docs/guide/electron-app-cli.md](./docs/guide/electron-app-cli.md) and the deeper [Electron guide](./docs/advanced/electron.md).
233
-
234
- | App | Description | Doc |
235
- |-----|-------------|-----|
236
- | **Cursor** | Control Cursor IDE — Composer, chat, code extraction | [Doc](./docs/adapters/desktop/cursor.md) |
237
- | **Codex** | Drive OpenAI Codex CLI agent headlessly | [Doc](./docs/adapters/desktop/codex.md) |
238
- | **Antigravity** | Control Antigravity Ultra from terminal | [Doc](./docs/adapters/desktop/antigravity.md) |
239
- | **ChatGPT** | Automate ChatGPT macOS desktop app | [Doc](./docs/adapters/desktop/chatgpt.md) |
240
- | **ChatWise** | Multi-LLM client (GPT-4, Claude, Gemini) | [Doc](./docs/adapters/desktop/chatwise.md) |
241
- | **Notion** | Search, read, write Notion pages | [Doc](./docs/adapters/desktop/notion.md) |
242
- | **Discord** | Discord Desktop — messages, channels, servers | [Doc](./docs/adapters/desktop/discord.md) |
243
- | **Doubao** | Control Doubao AI desktop app via CDP | [Doc](./docs/adapters/desktop/doubao-app.md) |
244
-
245
- ## Download Support
246
-
247
- OpenCLI supports downloading images, videos, and articles from supported platforms.
248
-
249
- ### Supported Platforms
250
-
251
- | Platform | Content Types | Notes |
252
- |----------|---------------|-------|
253
- | **xiaohongshu** | Images, Videos | Downloads all media from a note |
254
- | **bilibili** | Videos | Requires `yt-dlp` installed |
255
- | **twitter** | Images, Videos | Downloads from user media tab or single tweet |
256
- | **douban** | Images | Downloads poster / still image lists from movie subjects |
257
- | **pixiv** | Images | Downloads original-quality illustrations, supports multi-page works |
258
- | **zhihu** | Articles (Markdown) | Exports articles with optional image download |
259
- | **weixin** | Articles (Markdown) | Exports WeChat Official Account articles |
260
-
261
- ### Prerequisites
262
-
263
- For video downloads from streaming platforms, you need to install `yt-dlp`:
264
-
265
- ```bash
266
- # Install yt-dlp
267
- pip install yt-dlp
268
- # or
269
- brew install yt-dlp
270
- ```
271
-
272
- ### Usage Examples
29
+ **[→ Full documentation](./docs/index.md)**
273
30
 
274
- ```bash
275
- # Download images/videos from Xiaohongshu note
276
- opencli xiaohongshu download abc123 --output ./xhs
277
-
278
- # Download Bilibili video (requires yt-dlp)
279
- opencli bilibili download BV1xxx --output ./bilibili
280
- opencli bilibili download BV1xxx --quality 1080p # Specify quality
281
-
282
- # Download Twitter media from user
283
- opencli twitter download elonmusk --limit 20 --output ./twitter
284
-
285
- # Download single tweet media
286
- opencli twitter download --tweet-url "https://x.com/user/status/123" --output ./twitter
287
-
288
- # Download Douban posters / stills
289
- opencli douban download 30382501 --output ./douban
290
-
291
- # Export Zhihu article to Markdown
292
- opencli zhihu download "https://zhuanlan.zhihu.com/p/xxx" --output ./zhihu
293
-
294
- # Export with local images
295
- opencli zhihu download "https://zhuanlan.zhihu.com/p/xxx" --download-images
296
-
297
- # Export WeChat article to Markdown
298
- opencli weixin download --url "https://mp.weixin.qq.com/s/xxx" --output ./weixin
299
- ```
300
-
301
-
302
-
303
- ## Output Formats
304
-
305
- All built-in commands support `--format` / `-f` with `table`, `json`, `yaml`, `md`, and `csv`.
306
- The `list` command supports the same format options, and keeps `--json` for backward compatibility.
307
-
308
- ```bash
309
- opencli list -f yaml # Command registry as YAML
310
- opencli bilibili hot -f table # Default: rich terminal table
311
- opencli bilibili hot -f json # JSON (pipe to jq or LLMs)
312
- opencli bilibili hot -f yaml # YAML (human-readable structured output)
313
- opencli bilibili hot -f md # Markdown
314
- opencli bilibili hot -f csv # CSV
315
- opencli bilibili hot -v # Verbose: show pipeline debug steps
316
- ```
317
-
318
- ## Plugins
319
-
320
- Extend OpenCLI with community-contributed adapters. Plugins use the same YAML/TS format as built-in commands and are automatically discovered at startup.
321
-
322
- ```bash
323
- opencli plugin install github:user/opencli-plugin-my-tool # Install
324
- opencli plugin list # List installed
325
- opencli plugin update my-tool # Update to latest
326
- opencli plugin update --all # Update all installed plugins
327
- opencli plugin uninstall my-tool # Remove
328
- ```
329
-
330
- `opencli plugin list` also shows the tracked short commit hash when a plugin version is recorded in `~/.opencli/plugins.lock.json`.
331
-
332
- | Plugin | Type | Description |
333
- |--------|------|-------------|
334
- | [opencli-plugin-github-trending](https://github.com/ByteYue/opencli-plugin-github-trending) | YAML | GitHub Trending repositories |
335
- | [opencli-plugin-hot-digest](https://github.com/ByteYue/opencli-plugin-hot-digest) | TS | Multi-platform trending aggregator |
336
- | [opencli-plugin-juejin](https://github.com/Astro-Han/opencli-plugin-juejin) | YAML | 稀土掘金 (Juejin) hot articles |
337
-
338
- See [Plugins Guide](./docs/guide/plugins.md) for creating your own plugin.
339
-
340
- ## For AI Agents (Developer Guide)
341
-
342
- If you are an AI assistant tasked with creating a new command adapter for `opencli`, please follow the AI Agent workflow below:
343
-
344
- > **Quick mode**: To generate a single command for a specific page URL, see [CLI-ONESHOT.md](./CLI-ONESHOT.md) — just a URL + one-line goal, 4 steps done.
345
-
346
- > **Full mode**: Before writing any adapter code, read [CLI-EXPLORER.md](./CLI-EXPLORER.md). It contains the complete browser exploration workflow, the 5-tier authentication strategy decision tree, and debugging guide.
347
-
348
- ```bash
349
- # 1. Deep Explore — discover APIs, infer capabilities, detect framework
350
- opencli explore https://example.com --site mysite
351
-
352
- # 2. Synthesize — generate YAML adapters from explore artifacts
353
- opencli synthesize mysite
354
-
355
- # 3. Generate — one-shot: explore → synthesize → register
356
- opencli generate https://example.com --goal "hot"
357
-
358
- # 4. Strategy Cascade — auto-probe: PUBLIC → COOKIE → HEADER
359
- opencli cascade https://api.example.com/data
360
- ```
361
-
362
- Explore outputs to `.opencli/explore/<site>/` (manifest.json, endpoints.json, capabilities.json, auth.json).
363
-
364
- ## Testing
365
-
366
- See **[TESTING.md](./TESTING.md)** for how to run and write tests.
367
-
368
- ## Troubleshooting
369
-
370
- - **"Extension not connected"**
371
- - Ensure the opencli Browser Bridge extension is installed and **enabled** in `chrome://extensions`.
372
- - **"attach failed: Cannot access a chrome-extension:// URL"**
373
- - Another Chrome extension (e.g. youmind, New Tab Override, or AI assistant extensions) may be interfering. Try **disabling other extensions** temporarily, then retry.
374
- - **Empty data returns or 'Unauthorized' error**
375
- - Your login session in Chrome might have expired. Open a normal Chrome tab, navigate to the target site, and log in or refresh the page.
376
- - **Node API errors**
377
- - Make sure you are using Node.js >= 20. Some dependencies require modern Node APIs.
378
- - **Daemon issues**
379
- - Check daemon status: `curl localhost:19825/status`
380
- - View extension logs: `curl localhost:19825/logs`
31
+ ## Documentation
381
32
 
33
+ | Topic | Link |
34
+ |-------|------|
35
+ | Installation & Setup | [docs/guide/installation.md](./docs/guide/installation.md) |
36
+ | Getting Started | [docs/guide/getting-started.md](./docs/guide/getting-started.md) |
37
+ | Built-in Commands | [docs/adapters/index.md](./docs/adapters/index.md) |
38
+ | Desktop App Adapters | [docs/adapters/desktop](./docs/adapters/desktop) |
39
+ | Plugins | [docs/guide/plugins.md](./docs/guide/plugins.md) |
40
+ | Electron App Guide | [docs/guide/electron-app-cli.md](./docs/guide/electron-app-cli.md) |
41
+ | Troubleshooting | [docs/guide/troubleshooting.md](./docs/guide/troubleshooting.md) |
42
+ | Comparison with other tools | [docs/comparison.md](./docs/comparison.md) |
382
43
 
383
44
  ## Star History
384
45
 
385
46
  [![Star History Chart](https://api.star-history.com/svg?repos=jackwener/opencli&type=Date)](https://star-history.com/#jackwener/opencli&Date)
386
47
 
387
-
388
-
389
48
  ## License
390
49
 
391
50
  [Apache-2.0](./LICENSE)
@@ -13,7 +13,7 @@ import { request as httpsRequest } from 'node:https';
13
13
  import { wrapForEval } from './utils.js';
14
14
  import { generateSnapshotJs, scrollToRefJs, getFormStateJs } from './dom-snapshot.js';
15
15
  import { generateStealthJs } from './stealth.js';
16
- import { clickJs, typeTextJs, pressKeyJs, waitForTextJs, scrollJs, autoScrollJs, networkRequestsJs, waitForDomStableJs, } from './dom-helpers.js';
16
+ import { clickJs, typeTextJs, pressKeyJs, waitForTextJs, scrollJs, autoScrollJs, networkRequestsJs, waitForDomStableJs, waitForCaptureJs, waitForSelectorJs, } from './dom-helpers.js';
17
17
  import { isRecord, saveBase64ToFile } from '../utils.js';
18
18
  const CDP_SEND_TIMEOUT = 30_000;
19
19
  export class CDPBridge {
@@ -201,6 +201,16 @@ class CDPPage {
201
201
  }
202
202
  async wait(options) {
203
203
  if (typeof options === 'number') {
204
+ if (options >= 1) {
205
+ try {
206
+ const maxMs = options * 1000;
207
+ await this.evaluate(waitForDomStableJs(maxMs, Math.min(500, maxMs)));
208
+ return;
209
+ }
210
+ catch {
211
+ // Fallback: fixed sleep
212
+ }
213
+ }
204
214
  await new Promise((resolve) => setTimeout(resolve, options * 1000));
205
215
  return;
206
216
  }
@@ -209,6 +219,11 @@ class CDPPage {
209
219
  await new Promise((resolve) => setTimeout(resolve, waitTime * 1000));
210
220
  return;
211
221
  }
222
+ if (options.selector) {
223
+ const timeout = (options.timeout ?? 10) * 1000;
224
+ await this.evaluate(waitForSelectorJs(options.selector, timeout));
225
+ return;
226
+ }
212
227
  if (options.text) {
213
228
  const timeout = (options.timeout ?? 30) * 1000;
214
229
  await this.evaluate(waitForTextJs(options.text, timeout));
@@ -268,6 +283,10 @@ class CDPPage {
268
283
  const result = await this.evaluate(generateReadInterceptedJs('__opencli_xhr'));
269
284
  return Array.isArray(result) ? result : [];
270
285
  }
286
+ async waitForCapture(timeout = 10) {
287
+ const maxMs = timeout * 1000;
288
+ await this.evaluate(waitForCaptureJs(maxMs));
289
+ }
271
290
  }
272
291
  function isCookie(value) {
273
292
  return isRecord(value)
@@ -4,6 +4,7 @@
4
4
  * Provides a typed send() function that posts a Command and returns a Result.
5
5
  */
6
6
  import { DEFAULT_DAEMON_PORT } from '../constants.js';
7
+ import { sleep } from '../utils.js';
7
8
  const DAEMON_PORT = parseInt(process.env.OPENCLI_DAEMON_PORT ?? String(DEFAULT_DAEMON_PORT), 10);
8
9
  const DAEMON_URL = `http://127.0.0.1:${DAEMON_PORT}`;
9
10
  let _idCounter = 0;
@@ -80,7 +81,7 @@ export async function sendCommand(action, params = {}) {
80
81
  || errMsg.includes('no longer exists');
81
82
  if (isTransient && attempt < maxRetries) {
82
83
  // Longer delay for extension recovery (service worker restart)
83
- await new Promise(r => setTimeout(r, 1500));
84
+ await sleep(1500);
84
85
  continue;
85
86
  }
86
87
  throw new Error(result.error ?? 'Daemon command failed');
@@ -91,7 +92,7 @@ export async function sendCommand(action, params = {}) {
91
92
  const isRetryable = err instanceof TypeError // fetch network error
92
93
  || (err instanceof Error && err.name === 'AbortError');
93
94
  if (isRetryable && attempt < maxRetries) {
94
- await new Promise(r => setTimeout(r, 500));
95
+ await sleep(500);
95
96
  continue;
96
97
  }
97
98
  throw err;
@@ -26,3 +26,14 @@ export declare function networkRequestsJs(includeStatic: boolean): string;
26
26
  * If document.body is not available, falls back to a fixed sleep of maxMs.
27
27
  */
28
28
  export declare function waitForDomStableJs(maxMs: number, quietMs: number): string;
29
+ /**
30
+ * Generate JS to wait until window.__opencli_xhr has ≥1 captured response.
31
+ * Polls every 100ms. Resolves 'captured' on success; rejects after maxMs.
32
+ * Used after installInterceptor() + goto() instead of a fixed sleep.
33
+ */
34
+ export declare function waitForCaptureJs(maxMs: number): string;
35
+ /**
36
+ * Generate JS to wait until document.querySelector(selector) returns a match.
37
+ * Uses MutationObserver for near-instant resolution; falls back to reject after timeoutMs.
38
+ */
39
+ export declare function waitForSelectorJs(selector: string, timeoutMs: number): string;
@@ -171,3 +171,45 @@ export function waitForDomStableJs(maxMs, quietMs) {
171
171
  })
172
172
  `;
173
173
  }
174
+ /**
175
+ * Generate JS to wait until window.__opencli_xhr has ≥1 captured response.
176
+ * Polls every 100ms. Resolves 'captured' on success; rejects after maxMs.
177
+ * Used after installInterceptor() + goto() instead of a fixed sleep.
178
+ */
179
+ export function waitForCaptureJs(maxMs) {
180
+ return `
181
+ new Promise((resolve, reject) => {
182
+ const deadline = Date.now() + ${maxMs};
183
+ const check = () => {
184
+ if ((window.__opencli_xhr || []).length > 0) return resolve('captured');
185
+ if (Date.now() > deadline) return reject(new Error('No network capture within ${maxMs / 1000}s'));
186
+ setTimeout(check, 100);
187
+ };
188
+ check();
189
+ })
190
+ `;
191
+ }
192
+ /**
193
+ * Generate JS to wait until document.querySelector(selector) returns a match.
194
+ * Uses MutationObserver for near-instant resolution; falls back to reject after timeoutMs.
195
+ */
196
+ export function waitForSelectorJs(selector, timeoutMs) {
197
+ return `
198
+ new Promise((resolve, reject) => {
199
+ const sel = ${JSON.stringify(selector)};
200
+ if (document.querySelector(sel)) return resolve('found');
201
+ const cap = setTimeout(() => {
202
+ obs.disconnect();
203
+ reject(new Error('Selector not found: ' + sel));
204
+ }, ${timeoutMs});
205
+ const obs = new MutationObserver(() => {
206
+ if (document.querySelector(sel)) {
207
+ clearTimeout(cap);
208
+ obs.disconnect();
209
+ resolve('found');
210
+ }
211
+ });
212
+ obs.observe(document.body || document.documentElement, { childList: true, subtree: true });
213
+ })
214
+ `;
215
+ }
@@ -0,0 +1 @@
1
+ export {};