chore: import upstream snapshot with attribution
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# Browser Bridge Setup
|
||||
|
||||
> **⚠️ Important**: Browser commands reuse your Chrome login session. You must be logged into the target website in Chrome before running commands.
|
||||
|
||||
OpenCLI connects to your browser through a lightweight **Browser Bridge** Chrome Extension + micro-daemon (zero config, auto-start).
|
||||
|
||||
## Extension Installation
|
||||
|
||||
### Method 1: Download Pre-built Release (Recommended)
|
||||
|
||||
1. Go to the GitHub [Releases page](https://github.com/jackwener/opencli/releases) and download the latest `opencli-extension-v{version}.zip`.
|
||||
2. Unzip the file and open `chrome://extensions`, enable **Developer mode** (top-right toggle).
|
||||
3. Click **Load unpacked** and select the unzipped folder.
|
||||
|
||||
### Method 2: Load Unpacked Source (For Developers)
|
||||
|
||||
1. Open `chrome://extensions` and enable **Developer mode**.
|
||||
2. Click **Load unpacked** and select the `extension/` directory from the repository.
|
||||
|
||||
## Verification
|
||||
|
||||
That's it! The daemon auto-starts when you run any browser command. No tokens, no manual configuration.
|
||||
|
||||
```bash
|
||||
opencli doctor # Check extension + daemon connectivity
|
||||
```
|
||||
|
||||
## Tab Targeting
|
||||
|
||||
Browser commands require an explicit `<session>` positional immediately after `browser`. Use the same session name for a multi-step flow, and use different names to isolate parallel work.
|
||||
|
||||
```bash
|
||||
opencli browser baidu open https://www.baidu.com/
|
||||
opencli browser baidu tab list
|
||||
opencli browser baidu tab new https://www.baidu.com/
|
||||
opencli browser baidu eval --tab <targetId> 'document.title'
|
||||
opencli browser baidu tab select <targetId>
|
||||
opencli browser baidu get title
|
||||
opencli browser baidu tab close <targetId>
|
||||
```
|
||||
|
||||
Key rules:
|
||||
|
||||
- `opencli browser <session> open <url>` and `opencli browser <session> tab new [url]` return a `targetId`.
|
||||
- `opencli browser <session> tab list` prints the `targetId` values of tabs that already exist.
|
||||
- `--tab <targetId>` routes a single browser command to that specific tab.
|
||||
- `tab new` creates a new tab but does not change the default browser target.
|
||||
- `tab select <targetId>` makes that tab the default target for later untargeted `opencli browser ...` commands.
|
||||
- `tab close <targetId>` removes the tab; if it was the current default target, the stored default is cleared.
|
||||
|
||||
## Session Lifecycle
|
||||
|
||||
Use a stable session name when you want multiple `opencli browser` commands to keep operating on the same page:
|
||||
|
||||
```bash
|
||||
opencli browser my-session open https://example.com
|
||||
opencli browser my-session state
|
||||
opencli browser my-session extract "main"
|
||||
```
|
||||
|
||||
Owned browser sessions use an interactive tab lease with a 10-minute idle timeout. Release it explicitly when done:
|
||||
|
||||
```bash
|
||||
opencli browser my-session close
|
||||
```
|
||||
|
||||
Use `opencli browser <session> bind` when you want to attach OpenCLI to a Chrome tab you already opened manually. Bound sessions do not have the owned-session idle close timer; they stay attached until `unbind`, tab close, window close, or daemon restart. For owned sessions, use `--window foreground` to watch OpenCLI work in a visible automation window, or `--window background` to keep that automation window out of the way.
|
||||
|
||||
The `OpenCLI Browser` and `OpenCLI Adapter` tab groups are extension-managed automation containers; avoid putting your own long-lived tabs in them or renaming them.
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
┌─────────────┐ WebSocket ┌──────────────┐ Chrome API ┌─────────┐
|
||||
│ opencli │ ◄──────────────► │ micro-daemon │ ◄──────────────► │ Chrome │
|
||||
│ (Node.js) │ localhost:19825 │ (auto-start) │ Extension │ Browser │
|
||||
└─────────────┘ └──────────────┘ └─────────┘
|
||||
```
|
||||
|
||||
The daemon manages the WebSocket connection between your CLI commands and the Chrome extension. The extension executes JavaScript in the context of web pages, with access to the logged-in session.
|
||||
|
||||
## Daemon Lifecycle
|
||||
|
||||
The daemon auto-starts on first browser command and stays alive persistently.
|
||||
|
||||
```bash
|
||||
opencli daemon stop # Graceful shutdown
|
||||
```
|
||||
|
||||
The daemon is persistent — it stays alive until you explicitly stop it (`opencli daemon stop`) or uninstall the package.
|
||||
|
||||
## Running OpenCLI from a remote machine
|
||||
|
||||
If you need to run `opencli` on a remote server (CI runner, agent host) but keep the browser session on your local machine, see [Remote Orchestration](/guide/remote-orchestration). It walks through the SSH reverse-tunnel pattern so the daemon never leaves localhost.
|
||||
@@ -0,0 +1,199 @@
|
||||
---
|
||||
description: How to turn a new Electron desktop app into an OpenCLI adapter
|
||||
---
|
||||
|
||||
# Add a New Electron App CLI
|
||||
|
||||
This guide is the **fast entry point** for turning a new Electron desktop application into an OpenCLI adapter.
|
||||
|
||||
If you want the full background and deeper SOP, read:
|
||||
- [CLI-ifying Electron Applications](/advanced/electron)
|
||||
- [Chrome DevTools Protocol](/advanced/cdp)
|
||||
- [TypeScript Adapter Guide](/developer/ts-adapter)
|
||||
|
||||
## When to use this guide
|
||||
|
||||
Use this workflow when the target app:
|
||||
- is built with **Electron**, or at least exposes a working **Chrome DevTools Protocol (CDP)** endpoint
|
||||
- can be launched with `--remote-debugging-port=<port>`
|
||||
- should be automated through its real UI instead of a public HTTP API
|
||||
|
||||
If the app is **not** Electron and does **not** expose CDP, use the native desktop automation pattern instead. See [CLI-ifying Electron Applications](/advanced/electron#non-electron-pattern-applescript).
|
||||
|
||||
## The shortest path
|
||||
|
||||
### 1. Confirm the app is Electron
|
||||
|
||||
Typical macOS check:
|
||||
|
||||
```bash
|
||||
ls /Applications/AppName.app/Contents/Frameworks/Electron\ Framework.framework
|
||||
```
|
||||
|
||||
If Electron is present, the next step is usually to launch the app with a debugging port.
|
||||
|
||||
### 2. Launch it with CDP enabled
|
||||
|
||||
```bash
|
||||
/Applications/AppName.app/Contents/MacOS/AppName --remote-debugging-port=<unique-port>
|
||||
```
|
||||
|
||||
Then point OpenCLI at that CDP endpoint:
|
||||
|
||||
```bash
|
||||
export OPENCLI_CDP_ENDPOINT="http://127.0.0.1:<unique-port>"
|
||||
```
|
||||
|
||||
### 3. Start with the 5-command pattern
|
||||
|
||||
For a new Electron adapter, implement these commands first in `clis/<app>/`:
|
||||
|
||||
- `status.js` — verify the app is reachable through CDP
|
||||
- `dump.js` — inspect DOM and snapshot structure before guessing selectors
|
||||
- `read.js` — extract the visible context you actually need
|
||||
- `send.js` — inject text and submit through the real editor
|
||||
- `new.js` — create a new session, tab, thread, or document
|
||||
|
||||
This is the standard baseline because it gives you:
|
||||
- a connection check
|
||||
- a reverse-engineering tool
|
||||
- one read path
|
||||
- one write path
|
||||
- one session reset path
|
||||
|
||||
The full rationale and examples are in [CLI-ifying Electron Applications](/advanced/electron).
|
||||
|
||||
## Recommended implementation workflow
|
||||
|
||||
### Step 1: Build `status`
|
||||
|
||||
Goal: prove CDP connectivity before touching app-specific logic.
|
||||
|
||||
Typical checks:
|
||||
- current URL
|
||||
- document title
|
||||
- app shell presence
|
||||
|
||||
If `status` is unstable, stop there and fix connectivity first.
|
||||
|
||||
### Step 2: Build `dump`
|
||||
|
||||
Do **not** guess selectors from the rendered UI.
|
||||
|
||||
Dump:
|
||||
- `document.body.innerHTML`
|
||||
- accessibility snapshot
|
||||
- any stable attributes such as `data-testid`, `role`, `aria-*`, framework-specific markers
|
||||
|
||||
Use the dump to identify real containers, buttons, composers, and conversation regions.
|
||||
|
||||
### Step 3: Build `read`
|
||||
|
||||
Target only the app region that matters.
|
||||
|
||||
Good targets:
|
||||
- message list
|
||||
- editor history
|
||||
- visible thread content
|
||||
- selected document panel
|
||||
|
||||
Avoid dumping the entire page text into the final command output.
|
||||
|
||||
### Step 4: Build `send`
|
||||
|
||||
Most Electron apps use React-style controlled editors, so direct `.value = ...` assignments are often ignored.
|
||||
|
||||
Prefer editor-aware input patterns such as:
|
||||
- focus the editable region
|
||||
- use `document.execCommand('insertText', false, text)` when applicable
|
||||
- use real key presses like `Enter`, `Meta+Enter`, or app-specific shortcuts
|
||||
|
||||
### Step 5: Build `new`
|
||||
|
||||
Many desktop apps rely on keyboard shortcuts for “new chat”, “new tab”, or “new note”.
|
||||
|
||||
Typical pattern:
|
||||
|
||||
```ts
|
||||
const isMac = process.platform === 'darwin';
|
||||
await page.pressKey(isMac ? 'Meta+N' : 'Control+N');
|
||||
await page.wait(1);
|
||||
```
|
||||
|
||||
## Where to put files
|
||||
|
||||
For a desktop adapter, the usual layout is:
|
||||
|
||||
```text
|
||||
clis/<app>/status.js
|
||||
clis/<app>/dump.js
|
||||
clis/<app>/read.js
|
||||
clis/<app>/send.js
|
||||
clis/<app>/new.js
|
||||
clis/<app>/utils.js
|
||||
```
|
||||
|
||||
If the app grows beyond the baseline, add higher-level commands such as:
|
||||
- `ask`
|
||||
- `history`
|
||||
- `model`
|
||||
- `screenshot`
|
||||
- `export`
|
||||
|
||||
## What to document when you add a new app
|
||||
|
||||
When the adapter is ready, also add:
|
||||
|
||||
- an adapter doc under `docs/adapters/desktop/`
|
||||
- command list and examples
|
||||
- launch instructions with `--remote-debugging-port`
|
||||
- any required environment variables
|
||||
- platform-specific caveats
|
||||
|
||||
Examples to study:
|
||||
- `docs/adapters/desktop/codex.md`
|
||||
- `docs/adapters/desktop/chatwise.md`
|
||||
- `docs/adapters/desktop/discord.md`
|
||||
|
||||
## Common failure modes
|
||||
|
||||
### CDP endpoint exists, but commands are flaky
|
||||
|
||||
Usually one of these:
|
||||
- the wrong window/tab is selected
|
||||
- the app has not finished rendering
|
||||
- selectors were guessed instead of discovered from `dump`
|
||||
- the editor is controlled and ignores direct value assignment
|
||||
|
||||
### The app is Chromium-based but not truly controllable
|
||||
|
||||
Some desktop apps embed Chromium but do not expose a usable CDP surface.
|
||||
In that case, switch to the non-Electron desktop automation approach instead of forcing the Electron pattern.
|
||||
|
||||
### You already have a browser workflow and wonder whether to reuse it
|
||||
|
||||
If the app exposes a normal web URL and the browser flow is enough, a browser adapter is usually simpler.
|
||||
Use an Electron adapter only when the desktop app is the real integration surface.
|
||||
|
||||
## Recommended reading order
|
||||
|
||||
If you are starting from zero:
|
||||
|
||||
1. This page
|
||||
2. [CLI-ifying Electron Applications](/advanced/electron)
|
||||
3. [Chrome DevTools Protocol](/advanced/cdp)
|
||||
4. [TypeScript Adapter Guide](/developer/ts-adapter)
|
||||
5. One concrete desktop adapter doc under `docs/adapters/desktop/`
|
||||
|
||||
## Practical rule
|
||||
|
||||
Do not start with a large feature surface.
|
||||
|
||||
Start with:
|
||||
- `status`
|
||||
- `dump`
|
||||
- `read`
|
||||
- `send`
|
||||
- `new`
|
||||
|
||||
Once those are stable, extend outward.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Exit Codes
|
||||
|
||||
`opencli` follows Unix `sysexits.h` conventions so it integrates naturally with shell pipelines and CI scripts.
|
||||
|
||||
| Code | Meaning | When |
|
||||
|------|---------|------|
|
||||
| `0` | Success | Command completed normally |
|
||||
| `1` | Generic error | Unexpected / unclassified failure |
|
||||
| `2` | Usage error | Bad arguments or unknown command |
|
||||
| `66` | Empty result | No data returned (`EX_NOINPUT`) |
|
||||
| `69` | Service unavailable | Browser Bridge not connected (`EX_UNAVAILABLE`) |
|
||||
| `75` | Temporary failure | Command timed out — retry (`EX_TEMPFAIL`) |
|
||||
| `77` | Auth required | Not logged in to target site (`EX_NOPERM`) |
|
||||
| `78` | Config error | Missing credentials or bad config (`EX_CONFIG`) |
|
||||
| `130` | Interrupted | Ctrl-C / SIGINT |
|
||||
|
||||
## Example: branch on exit code
|
||||
|
||||
```bash
|
||||
opencli spotify status || echo "exit $?" # 69 if browser not running
|
||||
|
||||
opencli gh issue list 2>/dev/null
|
||||
[ $? -eq 77 ] && opencli gh auth login # auto-auth if not logged in
|
||||
```
|
||||
@@ -0,0 +1,134 @@
|
||||
# Extending OpenCLI
|
||||
|
||||
OpenCLI has five extension paths. Pick the path based on where you want the source code to live and how you want commands to be shared.
|
||||
|
||||
| Goal | Use | Source location | Command surface |
|
||||
|------|-----|-----------------|-----------------|
|
||||
| Build a personal website command in your own Git repo | Local plugin | Your project directory, symlinked into `~/.opencli/plugins/` | `opencli <plugin> <command>` |
|
||||
| Quickly draft a private adapter on this machine | User adapter | `~/.opencli/clis/<site>/<command>.js` | `opencli <site> <command>` |
|
||||
| Edit an official adapter locally | Adapter override | `~/.opencli/clis/<site>/` | `opencli <site> <command>` |
|
||||
| Publish or install third-party commands | Plugin | Git repo, installed into `~/.opencli/plugins/` | `opencli <plugin> <command>` |
|
||||
| Wrap an existing local binary | External CLI | `~/.opencli/external-clis.yaml` | `opencli <tool> ...` |
|
||||
|
||||
## Personal commands in your own Git repo
|
||||
|
||||
Use a local plugin when you want the code to stay in a normal project directory and be managed by Git.
|
||||
|
||||
```bash
|
||||
opencli plugin create my-cnn
|
||||
cd my-cnn
|
||||
git init
|
||||
opencli plugin install file://$(pwd)
|
||||
opencli my-cnn hello
|
||||
```
|
||||
|
||||
`plugin install file://...` creates a symlink under `~/.opencli/plugins/`. Your source files stay in your project directory, so edits and commits happen there.
|
||||
|
||||
This is the recommended path for custom commands you own long-term.
|
||||
|
||||
## Private adapters in `~/.opencli/clis`
|
||||
|
||||
Use a user adapter when you want the fastest local adapter loop and do not need a separate project directory.
|
||||
|
||||
```bash
|
||||
opencli browser init cnn/top
|
||||
# edit ~/.opencli/clis/cnn/top.js
|
||||
opencli browser verify cnn/top
|
||||
opencli cnn top
|
||||
```
|
||||
|
||||
User adapters are loaded from:
|
||||
|
||||
```text
|
||||
~/.opencli/clis/<site>/<command>.js
|
||||
```
|
||||
|
||||
This path is convenient for quick local automation. For code you want to version, review, or share, prefer a plugin.
|
||||
|
||||
If the command takes required positional args and no fixture exists yet, seed the first verify run explicitly:
|
||||
|
||||
```bash
|
||||
opencli browser verify instagram/collection-create --write-fixture --seed-args opencli-verify
|
||||
opencli browser verify example/detail --write-fixture --seed-args '["https://example.com/item/1", "--limit", 3]'
|
||||
```
|
||||
|
||||
`--seed-args` is only used when the fixture has no `args`. Once the fixture is written, `opencli browser verify` reads args from `~/.opencli/sites/<site>/verify/<command>.json`.
|
||||
|
||||
`browser verify` also enforces row shape before fixture checks: each row should
|
||||
stay compact (at most 12 top-level keys), avoid nesting deeper than one level,
|
||||
and keep id-shaped fields such as `id` / `user_id` at the top level.
|
||||
|
||||
## Local overrides for official adapters
|
||||
|
||||
Use `adapter eject` when you want to customize an existing official adapter.
|
||||
|
||||
```bash
|
||||
opencli adapter eject twitter
|
||||
# edit ~/.opencli/clis/twitter/*.js
|
||||
opencli adapter reset twitter
|
||||
```
|
||||
|
||||
Files in `~/.opencli/clis/<site>/<command>.js` override packaged adapters with the same `site/command` on this machine. `opencli browser verify <site>/<command>` also runs the local override, so a passing local verify does not prove that the packaged adapter was changed.
|
||||
|
||||
The packaged `cli-manifest.json` only describes bundled adapters. User adapters are discovered at runtime and do not need manifest entries.
|
||||
|
||||
After copying a local fix into the repository for a PR, remove the local copy or run `opencli adapter reset <site>` after merge. Otherwise the local file keeps shadowing future package updates. `opencli doctor` warns when it detects this shadowing.
|
||||
|
||||
## Plugins for sharing commands
|
||||
|
||||
Plugins are third-party command packages. They can be installed from GitHub, any git-cloneable URL, or a local directory.
|
||||
|
||||
```bash
|
||||
opencli plugin install github:user/opencli-plugin-my-tool
|
||||
opencli plugin install https://github.com/user/opencli-plugin-my-tool
|
||||
opencli plugin install file:///absolute/path/to/plugin
|
||||
opencli plugin list
|
||||
opencli plugin update --all
|
||||
opencli plugin uninstall my-tool
|
||||
```
|
||||
|
||||
Each plugin directory is scanned for `.ts` and `.js` command files. TypeScript plugins are transpiled during install.
|
||||
|
||||
See [Plugins](./plugins.md) for manifest fields, TypeScript examples, update behavior, and monorepo publishing.
|
||||
|
||||
## Multiple custom sites in one repo
|
||||
|
||||
For a Git-hosted plugin collection, declare sub-plugins in `opencli-plugin.json` and install from GitHub:
|
||||
|
||||
```json
|
||||
{
|
||||
"plugins": {
|
||||
"cnn": { "path": "packages/cnn" },
|
||||
"reuters": { "path": "packages/reuters" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
opencli plugin install github:user/opencli-plugins
|
||||
opencli plugin install github:user/opencli-plugins/cnn
|
||||
```
|
||||
|
||||
For local development, install each sub-plugin directory directly:
|
||||
|
||||
```bash
|
||||
opencli plugin install file:///absolute/path/opencli-plugins/packages/cnn
|
||||
opencli plugin install file:///absolute/path/opencli-plugins/packages/reuters
|
||||
```
|
||||
|
||||
Local `file://` installs expect the target directory itself to be a valid plugin with command files. For a monorepo root, push it to GitHub and install it with the GitHub monorepo flow.
|
||||
|
||||
## External CLI passthrough
|
||||
|
||||
Use external CLI registration when the command already exists as a binary on your machine and you want it available through `opencli`.
|
||||
|
||||
```bash
|
||||
opencli external register my-tool \
|
||||
--binary my-tool \
|
||||
--install "npm i -g my-tool" \
|
||||
--desc "My internal CLI"
|
||||
|
||||
opencli my-tool --help
|
||||
```
|
||||
|
||||
External CLIs pass stdio and exit codes through to the underlying binary.
|
||||
@@ -0,0 +1,81 @@
|
||||
# Getting Started
|
||||
|
||||
> **Make any website or Electron App your CLI.**
|
||||
> Zero risk · Reuse Chrome login · AI-powered discovery · Browser + Desktop automation
|
||||
|
||||
[](https://www.npmjs.com/package/@jackwener/opencli)
|
||||
[](https://nodejs.org)
|
||||
[](https://github.com/jackwener/opencli/blob/main/LICENSE)
|
||||
|
||||
OpenCLI turns **any website** or **Electron app** into a command-line interface — Bilibili, Zhihu, 小红书, Twitter/X, Reddit, YouTube, Antigravity, and [many more](/adapters/) — powered by browser session reuse and AI-native discovery.
|
||||
|
||||
## Highlights
|
||||
|
||||
- **Desktop App Control** — Drive Electron apps (Cursor, Codex, ChatGPT, etc.) directly from the terminal via CDP.
|
||||
- **Browser Automation** — `browser` gives AI agents direct browser control: click, type/fill, extract, screenshot — fully scriptable.
|
||||
- **Website → CLI** — Turn any website into a deterministic CLI: 100+ site surfaces are already registered, or author your own with the `opencli-adapter-author` skill.
|
||||
- **Account-safe** — Reuses Chrome's logged-in state; your credentials never leave the browser.
|
||||
- **AI Agent ready** — `opencli browser *` primitives (`open` / `network` / `state` / `eval` / `init` / `verify`) drive the adapter-authoring loop.
|
||||
- **Zero LLM cost** — No tokens consumed at runtime. Run 10,000 times and pay nothing.
|
||||
- **Deterministic** — Same command, same output schema, every time. Pipeable, scriptable, CI-friendly.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Install via npm
|
||||
|
||||
```bash
|
||||
npm install -g @jackwener/opencli
|
||||
```
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```bash
|
||||
opencli list # See all commands
|
||||
opencli hackernews top --limit 5 # Public API, no browser
|
||||
opencli bilibili hot --limit 5 # Browser command
|
||||
opencli zhihu hot -f json # JSON output
|
||||
```
|
||||
|
||||
### Output Formats
|
||||
|
||||
All built-in commands support `--format` / `-f`:
|
||||
|
||||
```bash
|
||||
opencli bilibili hot -f table # Default: rich terminal table
|
||||
opencli bilibili hot -f json # JSON (pipe to jq or LLMs)
|
||||
opencli bilibili hot -f yaml # YAML (human-readable)
|
||||
opencli bilibili hot -f md # Markdown
|
||||
opencli bilibili hot -f csv # CSV
|
||||
opencli bilibili hot -v # Verbose: show pipeline debug
|
||||
```
|
||||
|
||||
### Tab Completion
|
||||
|
||||
OpenCLI supports intelligent tab completion to speed up command input:
|
||||
|
||||
```bash
|
||||
# Add shell completion to your startup config
|
||||
echo 'eval "$(opencli completion zsh)"' >> ~/.zshrc # Zsh
|
||||
echo 'eval "$(opencli completion bash)"' >> ~/.bashrc # Bash
|
||||
echo 'opencli completion fish | source' >> ~/.config/fish/config.fish # Fish
|
||||
|
||||
# Restart your shell, then press Tab to complete:
|
||||
opencli [Tab] # Complete site names (bilibili, zhihu, twitter...)
|
||||
opencli bilibili [Tab] # Complete commands (hot, search, me, download...)
|
||||
```
|
||||
|
||||
The completion includes:
|
||||
- All available sites and adapters
|
||||
- Built-in commands (list, validate, verify, browser, doctor, plugin...)
|
||||
- Command aliases
|
||||
- Real-time updates as you add new adapters
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Installation details](/guide/installation)
|
||||
- [Browser Bridge setup](/guide/browser-bridge)
|
||||
- [Extending OpenCLI — custom commands, plugins, and external CLIs](/guide/extending-opencli)
|
||||
- [Plugins — extend with community adapters](/guide/plugins)
|
||||
- [All available adapters](/adapters/)
|
||||
- [For developers / AI agents](/developer/contributing)
|
||||
- [Add a new Electron app CLI](/guide/electron-app-cli)
|
||||
@@ -0,0 +1,52 @@
|
||||
# Installation
|
||||
|
||||
## Requirements
|
||||
|
||||
- **Node.js**: >= 21.0.0, or **Bun** >= 1.0
|
||||
- **Chrome** running and logged into the target site (for browser commands)
|
||||
|
||||
## Install via npm (Recommended)
|
||||
|
||||
```bash
|
||||
npm install -g @jackwener/opencli
|
||||
```
|
||||
|
||||
## Install from Source
|
||||
|
||||
```bash
|
||||
git clone git@github.com:jackwener/opencli.git
|
||||
cd opencli
|
||||
npm install
|
||||
npm run build
|
||||
npm link # Link binary globally
|
||||
opencli list # Now you can use it anywhere!
|
||||
```
|
||||
|
||||
## Update
|
||||
|
||||
```bash
|
||||
npm install -g @jackwener/opencli@latest
|
||||
|
||||
# If you use the packaged OpenCLI skills, refresh them too
|
||||
npx skills add jackwener/opencli
|
||||
```
|
||||
|
||||
Or refresh only the skills you actually use:
|
||||
|
||||
```bash
|
||||
npx skills add jackwener/opencli --skill opencli-adapter-author
|
||||
npx skills add jackwener/opencli --skill opencli-autofix
|
||||
npx skills add jackwener/opencli --skill opencli-browser
|
||||
npx skills add jackwener/opencli --skill opencli-browser-sitemap
|
||||
npx skills add jackwener/opencli --skill opencli-sitemap-author
|
||||
npx skills add jackwener/opencli --skill opencli-usage
|
||||
npx skills add jackwener/opencli --skill smart-search
|
||||
```
|
||||
|
||||
## Verify Installation
|
||||
|
||||
```bash
|
||||
opencli --version # Check version
|
||||
opencli list # List all commands
|
||||
opencli doctor # Diagnose connectivity
|
||||
```
|
||||
@@ -0,0 +1,226 @@
|
||||
# Plugins
|
||||
|
||||
OpenCLI supports community-contributed plugins. Install third-party adapters from GitHub, and they're automatically discovered alongside built-in commands.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Install a plugin
|
||||
opencli plugin install github:ByteYue/opencli-plugin-github-trending
|
||||
|
||||
# List installed plugins
|
||||
opencli plugin list
|
||||
|
||||
# Update one plugin
|
||||
opencli plugin update github-trending
|
||||
|
||||
# Update all installed plugins
|
||||
opencli plugin update --all
|
||||
|
||||
# Use the plugin (it's just a regular command)
|
||||
opencli github-trending repos --limit 10
|
||||
|
||||
# Remove a plugin
|
||||
opencli plugin uninstall github-trending
|
||||
```
|
||||
|
||||
## How Plugins Work
|
||||
|
||||
Plugins live in `~/.opencli/plugins/<name>/`. Each subdirectory is scanned at startup for `.ts` or `.js` command files — the same formats used by built-in adapters.
|
||||
|
||||
### Supported Source Formats
|
||||
|
||||
```bash
|
||||
# GitHub shorthand
|
||||
opencli plugin install github:user/repo
|
||||
opencli plugin install github:user/repo/subplugin # install specific sub-plugin from monorepo
|
||||
opencli plugin install https://github.com/user/repo
|
||||
|
||||
# Any git-cloneable URL
|
||||
opencli plugin install https://gitlab.example.com/team/repo.git
|
||||
opencli plugin install ssh://git@gitlab.example.com/team/repo.git
|
||||
opencli plugin install git@gitlab.example.com:team/repo.git
|
||||
|
||||
# Local plugin (for development)
|
||||
opencli plugin install file:///path/to/plugin
|
||||
opencli plugin install /path/to/plugin
|
||||
```
|
||||
|
||||
The repo name prefix `opencli-plugin-` is automatically stripped for the local directory name. For example, `opencli-plugin-hot-digest` becomes `hot-digest`.
|
||||
|
||||
## Plugin Manifest (`opencli-plugin.json`)
|
||||
|
||||
Plugins can include an `opencli-plugin.json` manifest file at the repo root to declare metadata:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-plugin",
|
||||
"version": "1.0.0",
|
||||
"opencli": ">=1.0.0",
|
||||
"description": "My awesome plugin"
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Description |
|
||||
|-------|-------------|
|
||||
| `name` | Plugin name (overrides repo-derived name) |
|
||||
| `version` | Semantic version |
|
||||
| `opencli` | Required opencli version range (e.g. `>=1.0.0`, `^1.2.0`) |
|
||||
| `description` | Human-readable description |
|
||||
| `plugins` | Monorepo sub-plugin declarations (see below) |
|
||||
|
||||
The manifest is optional — plugins without one continue to work exactly as before.
|
||||
|
||||
## Monorepo Plugins
|
||||
|
||||
A single repository can contain multiple plugins by declaring a `plugins` field in `opencli-plugin.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"opencli": ">=1.0.0",
|
||||
"description": "My plugin collection",
|
||||
"plugins": {
|
||||
"polymarket": {
|
||||
"path": "packages/polymarket",
|
||||
"description": "Prediction market analysis",
|
||||
"version": "1.2.0"
|
||||
},
|
||||
"defi": {
|
||||
"path": "packages/defi",
|
||||
"description": "DeFi protocol data",
|
||||
"version": "0.8.0",
|
||||
"opencli": ">=1.2.0"
|
||||
},
|
||||
"experimental": {
|
||||
"path": "packages/experimental",
|
||||
"disabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Installing
|
||||
|
||||
```bash
|
||||
# Install ALL enabled sub-plugins from a monorepo
|
||||
opencli plugin install github:user/opencli-plugins
|
||||
|
||||
# Install a SPECIFIC sub-plugin
|
||||
opencli plugin install github:user/opencli-plugins/polymarket
|
||||
```
|
||||
|
||||
### How It Works
|
||||
|
||||
- The monorepo is cloned once to `~/.opencli/monorepos/<repo>/`
|
||||
- Each sub-plugin gets a symlink in `~/.opencli/plugins/<name>/` pointing to its subdirectory
|
||||
- Command discovery works transparently — symlinks are scanned just like regular directories
|
||||
- Disabled sub-plugins (with `"disabled": true`) are skipped during install
|
||||
- Sub-plugins can specify their own `opencli` compatibility range
|
||||
|
||||
### Updating
|
||||
|
||||
Updating any sub-plugin from a monorepo pulls the entire repo and refreshes all sub-plugins:
|
||||
|
||||
```bash
|
||||
opencli plugin update polymarket # updates the monorepo, refreshes all
|
||||
```
|
||||
|
||||
### Uninstalling
|
||||
|
||||
```bash
|
||||
opencli plugin uninstall polymarket # removes just this sub-plugin's symlink
|
||||
```
|
||||
|
||||
When the last sub-plugin from a monorepo is uninstalled, the monorepo clone is automatically cleaned up.
|
||||
|
||||
## Version Tracking
|
||||
|
||||
OpenCLI records installed plugin versions in `~/.opencli/plugins.lock.json`. Each entry stores the plugin source, current git commit hash, install time, and last update time. `opencli plugin list` shows the short commit hash when version metadata is available.
|
||||
|
||||
## Creating a Plugin
|
||||
|
||||
### Creating a TypeScript Plugin
|
||||
|
||||
```
|
||||
my-plugin/
|
||||
├── package.json
|
||||
├── my-command.ts
|
||||
└── README.md
|
||||
```
|
||||
|
||||
`package.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "opencli-plugin-my-plugin",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"peerDependencies": {
|
||||
"@jackwener/opencli": ">=1.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`my-command.ts`:
|
||||
|
||||
```typescript
|
||||
import { cli, Strategy } from '@jackwener/opencli/registry';
|
||||
|
||||
cli({
|
||||
site: 'my-plugin',
|
||||
name: 'my-command',
|
||||
description: 'My custom command',
|
||||
access: 'read', // 'read' | 'write'
|
||||
example: 'opencli my-plugin my-command -f yaml',
|
||||
strategy: Strategy.PUBLIC,
|
||||
browser: false,
|
||||
args: [
|
||||
{ name: 'limit', type: 'int', default: 10, help: 'Number of items' },
|
||||
],
|
||||
columns: ['title', 'score'],
|
||||
func: async (kwargs) => {
|
||||
const res = await fetch('https://api.example.com/data');
|
||||
const data = await res.json();
|
||||
return data.items.slice(0, kwargs.limit).map((item: any, i: number) => ({
|
||||
title: item.title,
|
||||
score: item.score,
|
||||
}));
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### TS Plugin Install Lifecycle
|
||||
|
||||
When you run `opencli plugin install`, TS plugins are automatically set up:
|
||||
|
||||
1. **Clone** — `git clone --depth 1` from GitHub
|
||||
2. **npm install** — Resolves regular dependencies
|
||||
3. **Host symlink** — Links the running `@jackwener/opencli` into the plugin's `node_modules/` so `import from '@jackwener/opencli/registry'` always resolves against the host
|
||||
4. **Transpile** — Compiles `.ts` → `.js` via `esbuild` (production `node` cannot load `.ts` directly)
|
||||
|
||||
On startup, if both `my-command.ts` and `my-command.js` exist, the `.js` version is loaded to avoid duplicate registration.
|
||||
|
||||
## Example Plugins
|
||||
|
||||
| Repo | Type | Description |
|
||||
|------|------|-------------|
|
||||
| [opencli-plugin-github-trending](https://github.com/ByteYue/opencli-plugin-github-trending) | TS | GitHub Trending repositories |
|
||||
| [opencli-plugin-hot-digest](https://github.com/ByteYue/opencli-plugin-hot-digest) | TS | Multi-platform trending aggregator (zhihu, weibo, bilibili, v2ex, stackoverflow, reddit, linux-do) |
|
||||
| [opencli-plugin-juejin](https://github.com/Astro-Han/opencli-plugin-juejin) | TS | 稀土掘金 (Juejin) hot articles, categories, and article feed |
|
||||
| [opencli-plugin-rubysec](https://github.com/nullptrKey/opencli-plugin-rubysec) | TS | RubySec advisory archive and advisory article reader |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Command not found after install
|
||||
|
||||
Restart opencli (or open a new terminal) — plugins are discovered at startup.
|
||||
|
||||
### TS plugin import errors
|
||||
|
||||
If you see `Cannot find module '@jackwener/opencli/registry'`, the host symlink may be broken. Reinstall the plugin:
|
||||
|
||||
```bash
|
||||
opencli plugin uninstall my-plugin
|
||||
opencli plugin install github:user/opencli-plugin-my-plugin
|
||||
```
|
||||
@@ -0,0 +1,116 @@
|
||||
# Remote Orchestration
|
||||
|
||||
Run an OpenCLI command from a remote machine (a CI runner, an agent server, a sandbox) while the **browser session stays on your local laptop**. The remote command sees `localhost:19825` like usual; behind the scenes, traffic is tunneled back to the daemon and Chrome on your machine.
|
||||
|
||||
## When you need this
|
||||
|
||||
- An autonomous agent (OpenClaw, a CI job, a server-side script) needs to drive a logged-in browser session, but only your local Chrome has the cookies.
|
||||
- Target sites do IP-based throttling or risk control — you want web traffic to leave from your home network, not from the agent's data center.
|
||||
- The remote machine has no display and no Chrome installed.
|
||||
|
||||
## What not to do (and why)
|
||||
|
||||
The first instinct is "let the extension connect to a remote daemon" — type a public host into the popup, expose port 19825 with frp, done. **Don't.** The daemon's WebSocket protocol has no built-in authentication. Anything that can reach the port can:
|
||||
|
||||
- Read cookies for every site you're logged into
|
||||
- Execute arbitrary JavaScript in any of your tabs
|
||||
- Take screenshots, send arbitrary HTTP requests, dump page content
|
||||
|
||||
Treat the daemon port the way you'd treat your unlocked desktop: never put it on a network you don't fully trust. Native "extension talks to remote daemon" support was [proposed in #636](https://github.com/jackwener/OpenCLI/pull/636) and deferred until daemon authentication exists.
|
||||
|
||||
## The pattern: reverse-tunnel a localhost daemon
|
||||
|
||||
Keep Chrome, the extension, and the daemon **all on your local machine**. Use a reverse port forward so the remote process can reach your daemon by connecting to its own `localhost:19825`. The daemon itself never leaves localhost.
|
||||
|
||||
```
|
||||
┌─ Local ─────────────────────────────────┐ ┌─ Remote ──────────┐
|
||||
│ Chrome ↔ Extension ↔ Daemon (127.0.0.1) │ ←┐ │ opencli-cli │
|
||||
└──────────────────────────────────────────┘ │ │ (talks to │
|
||||
│ │ localhost:19825)│
|
||||
reverse tunnel ────────┘ └───────────────────┘
|
||||
(SSH -R / frpc / VPN)
|
||||
```
|
||||
|
||||
The remote `opencli` process needs **no flags, no env vars, no extension changes** — it connects to its own loopback, which the tunnel forwards to your laptop.
|
||||
|
||||
## Option A — SSH reverse port forward (recommended)
|
||||
|
||||
From your local machine, SSH to the remote with `-R` to expose your local daemon to the remote's loopback:
|
||||
|
||||
```bash
|
||||
ssh -R 19825:127.0.0.1:19825 user@remote-server
|
||||
```
|
||||
|
||||
While that session is open, anything on the remote connecting to `localhost:19825` is forwarded back to your local daemon. The remote-side workflow is unchanged:
|
||||
|
||||
```bash
|
||||
# On the remote server
|
||||
opencli twitter feed
|
||||
opencli browser open https://example.com
|
||||
```
|
||||
|
||||
::: tip
|
||||
Use `127.0.0.1` (not `localhost`) on the `-R` clause to avoid IPv6 resolution stalls.
|
||||
:::
|
||||
|
||||
For long-lived agent runs, use `autossh` so the tunnel reconnects automatically:
|
||||
|
||||
```bash
|
||||
autossh -M 0 -N -R 19825:127.0.0.1:19825 user@remote-server
|
||||
```
|
||||
|
||||
Or as a systemd unit / launchd plist on the local side.
|
||||
|
||||
### Why this is safe
|
||||
|
||||
- The daemon stays bound to `127.0.0.1` on your machine.
|
||||
- The tunnel rides on SSH's authenticated transport — no new auth surface.
|
||||
- If the SSH session drops, the tunnel drops; the remote `opencli` simply fails to connect rather than reaching some stale endpoint.
|
||||
|
||||
## Option B — frp reverse TCP proxy
|
||||
|
||||
If SSH from your local machine to the remote isn't an option (NAT, firewalls), use [frp](https://github.com/fatedier/frp) to expose the local daemon through a public relay. **This is more complex than SSH and has more failure modes — prefer Option A unless you have a hard reason not to.**
|
||||
|
||||
1. Run **frps** on a public relay you control.
|
||||
2. Run **frpc** on your local machine, exposing daemon port 19825:
|
||||
|
||||
```toml
|
||||
# ~/frpc.toml on your local machine
|
||||
serverAddr = "<public-relay-ip>"
|
||||
serverPort = 7000
|
||||
auth.method = "token"
|
||||
auth.token = "<long-random-token>"
|
||||
|
||||
[[proxies]]
|
||||
name = "opencli-daemon"
|
||||
type = "tcp"
|
||||
localIP = "127.0.0.1"
|
||||
localPort = 19825
|
||||
remotePort = 19825
|
||||
```
|
||||
|
||||
3. On the remote server, run a second **frpc** that maps the relay's exposed port back to the remote's `localhost:19825` (a `stcp` visitor or a plain `tcp` client+visitor pair). This way `opencli` on the remote keeps talking to its own loopback.
|
||||
|
||||
::: warning
|
||||
- Always set a strong `auth.token` on frps and frpc. Without it, anyone who learns the relay address has full control of your browser.
|
||||
- Bind the relay's exposed port to a private interface or restrict it with iptables / security groups. A daemon port on the public internet is the same risk as the rejected #636 design.
|
||||
- If you find yourself debugging frp auth, revisit Option A — it's cheaper and safer.
|
||||
:::
|
||||
|
||||
## Verification
|
||||
|
||||
After setting up the tunnel, confirm the remote sees the daemon:
|
||||
|
||||
```bash
|
||||
# On the remote server
|
||||
curl -sf http://127.0.0.1:19825/ping && echo "daemon reachable"
|
||||
opencli doctor
|
||||
```
|
||||
|
||||
`opencli doctor` from the remote should report the same extension version your local Chrome is running.
|
||||
|
||||
## Caveats
|
||||
|
||||
- **Local Chrome must be running** for the duration of the remote command. Closing Chrome closes the extension's WebSocket; remote `opencli` calls will fail with a "no daemon" error until Chrome is reopened.
|
||||
- **Tunnel latency adds to every call**. Each browser command crosses the tunnel twice; expect 50–200ms overhead per call on a typical SSH link, more on transcontinental links.
|
||||
- **One tunnel per local daemon**. If you start multiple SSH sessions all forwarding 19825, only the first wins; the rest log a "remote port already in use" warning.
|
||||
@@ -0,0 +1,69 @@
|
||||
# Troubleshooting
|
||||
|
||||
## Common Issues
|
||||
|
||||
### "Extension not connected"
|
||||
|
||||
- Ensure the opencli Browser Bridge extension is installed and **enabled** in `chrome://extensions`.
|
||||
- Run `opencli doctor` to diagnose connectivity.
|
||||
|
||||
### Empty data or 'Unauthorized' error
|
||||
|
||||
- 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.
|
||||
- Some sites have geographic restrictions (e.g., Bilibili, Zhihu from outside China).
|
||||
|
||||
### Browser command opens the page but still cannot read context
|
||||
|
||||
- A healthy Browser Bridge connection does not guarantee that the current page target exposes the data your adapter expects.
|
||||
- Some browser adapters are sensitive to the active host or page context.
|
||||
- Example: `opencli 1688 item` may fail with `did not expose product context` if the target is too broad.
|
||||
- Retry on a real item page, refresh the page in Chrome, and if needed narrow the target, for example:
|
||||
|
||||
```bash
|
||||
OPENCLI_CDP_TARGET=detail.1688.com opencli 1688 item 841141931191 -f json
|
||||
```
|
||||
|
||||
### Node API errors
|
||||
|
||||
- Make sure you are using **Node.js >= 20**. Run `node --version` to verify.
|
||||
|
||||
### Daemon issues
|
||||
|
||||
```bash
|
||||
# View extension logs
|
||||
curl localhost:19825/logs
|
||||
|
||||
# Stop the daemon
|
||||
opencli daemon stop
|
||||
|
||||
# Full diagnostics
|
||||
opencli doctor
|
||||
```
|
||||
|
||||
> The daemon is persistent and stays alive until explicitly stopped (`opencli daemon stop`) or the package is uninstalled.
|
||||
|
||||
> When the CLI detects a stale daemon (version mismatch after `npm install -g @jackwener/opencli@latest`), it first asks the daemon to shut down via `/shutdown`, then falls back to `SIGKILL` if the daemon does not release the port within 3 seconds. Manual `opencli daemon stop` is only needed if SIGKILL itself is rejected (cross-user owner / cross-machine PID file).
|
||||
|
||||
### Desktop adapter connection issues
|
||||
|
||||
For Electron/CDP-based adapters (Cursor, Codex, etc.):
|
||||
|
||||
1. Make sure the app is launched with `--remote-debugging-port=XXXX`
|
||||
2. Verify the endpoint is set: `echo $OPENCLI_CDP_ENDPOINT`
|
||||
3. Test the endpoint: `curl http://127.0.0.1:XXXX/json/version`
|
||||
|
||||
### Build errors
|
||||
|
||||
```bash
|
||||
# Clean rebuild
|
||||
rm -rf dist/
|
||||
npm run build
|
||||
|
||||
# Type check
|
||||
npx tsc --noEmit
|
||||
```
|
||||
|
||||
## Getting Help
|
||||
|
||||
- [GitHub Issues](https://github.com/jackwener/opencli/issues) — Bug reports and feature requests
|
||||
- Run `opencli doctor` for comprehensive diagnostics
|
||||
Reference in New Issue
Block a user