@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.
- package/.github/workflows/ci.yml +6 -7
- package/README.md +21 -362
- package/dist/browser/cdp.js +20 -1
- package/dist/browser/daemon-client.js +3 -2
- package/dist/browser/dom-helpers.d.ts +11 -0
- package/dist/browser/dom-helpers.js +42 -0
- package/dist/browser/dom-helpers.test.d.ts +1 -0
- package/dist/browser/dom-helpers.test.js +92 -0
- package/dist/browser/index.d.ts +0 -12
- package/dist/browser/index.js +0 -13
- package/dist/browser/mcp.js +4 -3
- package/dist/browser/page.d.ts +1 -0
- package/dist/browser/page.js +14 -1
- package/dist/browser.test.js +15 -11
- package/dist/clis/36kr/hot.js +1 -1
- package/dist/clis/36kr/search.js +1 -1
- package/dist/clis/_shared/common.d.ts +8 -0
- package/dist/clis/_shared/common.js +10 -0
- package/dist/clis/bloomberg/news.js +1 -1
- package/dist/clis/douban/utils.js +3 -6
- package/dist/clis/medium/utils.js +1 -1
- package/dist/clis/producthunt/browse.js +1 -1
- package/dist/clis/producthunt/hot.js +1 -1
- package/dist/clis/sinablog/utils.js +6 -7
- package/dist/clis/substack/utils.js +2 -2
- package/dist/clis/twitter/block.js +1 -1
- package/dist/clis/twitter/bookmark.js +1 -1
- package/dist/clis/twitter/delete.js +1 -1
- package/dist/clis/twitter/follow.js +1 -1
- package/dist/clis/twitter/followers.js +2 -2
- package/dist/clis/twitter/following.js +2 -2
- package/dist/clis/twitter/hide-reply.js +1 -1
- package/dist/clis/twitter/like.js +1 -1
- package/dist/clis/twitter/notifications.js +1 -1
- package/dist/clis/twitter/profile.js +1 -1
- package/dist/clis/twitter/reply-dm.js +1 -1
- package/dist/clis/twitter/reply.js +1 -1
- package/dist/clis/twitter/search.js +1 -1
- package/dist/clis/twitter/unblock.js +1 -1
- package/dist/clis/twitter/unbookmark.js +1 -1
- package/dist/clis/twitter/unfollow.js +1 -1
- package/dist/clis/xiaohongshu/comments.test.js +1 -0
- package/dist/clis/xiaohongshu/creator-note-detail.test.js +1 -0
- package/dist/clis/xiaohongshu/creator-notes.test.js +1 -0
- package/dist/clis/xiaohongshu/publish.test.js +1 -0
- package/dist/clis/xiaohongshu/search.test.js +1 -0
- package/dist/download/index.js +39 -33
- package/dist/download/index.test.js +15 -1
- package/dist/execution.js +3 -2
- package/dist/main.js +2 -0
- package/dist/node-network.d.ts +10 -0
- package/dist/node-network.js +174 -0
- package/dist/node-network.test.d.ts +1 -0
- package/dist/node-network.test.js +55 -0
- package/dist/pipeline/executor.test.js +1 -0
- package/dist/pipeline/steps/download.test.js +1 -0
- package/dist/pipeline/steps/intercept.js +4 -5
- package/dist/types.d.ts +2 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +4 -0
- package/docs/superpowers/plans/2026-03-28-perf-smart-wait.md +1143 -0
- package/docs/superpowers/specs/2026-03-28-perf-smart-wait-design.md +170 -0
- package/extension/dist/background.js +1 -1
- package/extension/manifest.json +1 -1
- package/extension/package-lock.json +2 -2
- package/extension/package.json +1 -1
- package/extension/src/background.ts +1 -1
- package/package.json +2 -1
- package/src/browser/cdp.ts +21 -0
- package/src/browser/daemon-client.ts +3 -2
- package/src/browser/dom-helpers.test.ts +100 -0
- package/src/browser/dom-helpers.ts +44 -0
- package/src/browser/index.ts +0 -15
- package/src/browser/mcp.ts +4 -3
- package/src/browser/page.ts +16 -0
- package/src/browser.test.ts +16 -12
- package/src/clis/36kr/hot.ts +1 -1
- package/src/clis/36kr/search.ts +1 -1
- package/src/clis/_shared/common.ts +11 -0
- package/src/clis/bloomberg/news.ts +1 -1
- package/src/clis/douban/utils.ts +3 -7
- package/src/clis/medium/utils.ts +1 -1
- package/src/clis/producthunt/browse.ts +1 -1
- package/src/clis/producthunt/hot.ts +1 -1
- package/src/clis/sinablog/utils.ts +6 -7
- package/src/clis/substack/utils.ts +2 -2
- package/src/clis/twitter/block.ts +1 -1
- package/src/clis/twitter/bookmark.ts +1 -1
- package/src/clis/twitter/delete.ts +1 -1
- package/src/clis/twitter/follow.ts +1 -1
- package/src/clis/twitter/followers.ts +2 -2
- package/src/clis/twitter/following.ts +2 -2
- package/src/clis/twitter/hide-reply.ts +1 -1
- package/src/clis/twitter/like.ts +1 -1
- package/src/clis/twitter/notifications.ts +1 -1
- package/src/clis/twitter/profile.ts +1 -1
- package/src/clis/twitter/reply-dm.ts +1 -1
- package/src/clis/twitter/reply.ts +1 -1
- package/src/clis/twitter/search.ts +1 -1
- package/src/clis/twitter/unblock.ts +1 -1
- package/src/clis/twitter/unbookmark.ts +1 -1
- package/src/clis/twitter/unfollow.ts +1 -1
- package/src/clis/xiaohongshu/comments.test.ts +1 -0
- package/src/clis/xiaohongshu/creator-note-detail.test.ts +1 -0
- package/src/clis/xiaohongshu/creator-notes.test.ts +1 -0
- package/src/clis/xiaohongshu/publish.test.ts +1 -0
- package/src/clis/xiaohongshu/search.test.ts +1 -0
- package/src/download/index.test.ts +19 -1
- package/src/download/index.ts +50 -41
- package/src/execution.ts +3 -2
- package/src/main.ts +3 -0
- package/src/node-network.test.ts +93 -0
- package/src/node-network.ts +213 -0
- package/src/pipeline/executor.test.ts +1 -0
- package/src/pipeline/steps/download.test.ts +1 -0
- package/src/pipeline/steps/intercept.ts +4 -5
- package/src/types.ts +2 -0
- package/src/utils.ts +5 -0
package/.github/workflows/ci.yml
CHANGED
|
@@ -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,
|
|
48
|
-
node-version: [
|
|
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:
|
|
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
|
[](./README.zh-CN.md)
|
|
@@ -8,384 +8,43 @@
|
|
|
8
8
|
[](https://nodejs.org)
|
|
9
9
|
[](./LICENSE)
|
|
10
10
|
|
|
11
|
-
A CLI tool that turns **any website**, **Electron app**, or **local CLI tool** into a command-line interface —
|
|
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
|
-
**
|
|
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
|
-
- **
|
|
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
|
-
|
|
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
|
-
|
|
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
|
[](https://star-history.com/#jackwener/opencli&Date)
|
|
386
47
|
|
|
387
|
-
|
|
388
|
-
|
|
389
48
|
## License
|
|
390
49
|
|
|
391
50
|
[Apache-2.0](./LICENSE)
|
package/dist/browser/cdp.js
CHANGED
|
@@ -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
|
|
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
|
|
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 {};
|