OpenCLI supports community-contributed plugins. Install third-party adapters from GitHub, and they're automatically discovered alongside built-in commands.
# 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-trendingPlugins live in ~/.opencli/plugins/<name>/. Each subdirectory is scanned at startup for .yaml, .ts, or .js command files — the same formats used by built-in adapters.
# 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/pluginThe repo name prefix opencli-plugin- is automatically stripped for the local directory name. For example, opencli-plugin-hot-digest becomes hot-digest.
Plugins can include an opencli-plugin.json manifest file at the repo root to declare metadata:
{
"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.
A single repository can contain multiple plugins by declaring a plugins field in opencli-plugin.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
}
}
}# 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- 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
openclicompatibility range
Updating any sub-plugin from a monorepo pulls the entire repo and refreshes all sub-plugins:
opencli plugin update polymarket # updates the monorepo, refreshes allopencli plugin uninstall polymarket # removes just this sub-plugin's symlinkWhen the last sub-plugin from a monorepo is uninstalled, the monorepo clone is automatically cleaned up.
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.
Zero dependencies, no build step. Just create a .yaml file:
my-plugin/
├── my-command.yaml
└── README.md
Example my-command.yaml:
site: my-plugin
name: my-command
description: My custom command
strategy: public
browser: false
args:
limit:
type: int
default: 10
pipeline:
- fetch:
url: https://api.example.com/data
- map:
title: ${{ item.title }}
score: ${{ item.score }}
- limit: ${{ args.limit }}
columns: [title, score]For richer logic (multi-source aggregation, custom transformations, etc.):
my-plugin/
├── package.json
├── my-command.ts
└── README.md
package.json:
{
"name": "opencli-plugin-my-plugin",
"version": "0.1.0",
"type": "module",
"peerDependencies": {
"@jackwener/opencli": ">=1.0.0"
}
}my-command.ts:
import { cli, Strategy } from '@jackwener/opencli/registry';
cli({
site: 'my-plugin',
name: 'my-command',
description: 'My custom command',
strategy: Strategy.PUBLIC,
browser: false,
args: [
{ name: 'limit', type: 'int', default: 10, help: 'Number of items' },
],
columns: ['title', 'score'],
func: async (_page, 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,
}));
},
});When you run opencli plugin install, TS plugins are automatically set up:
- Clone —
git clone --depth 1from GitHub - npm install — Resolves regular dependencies
- Host symlink — Links the running
@jackwener/opencliinto the plugin'snode_modules/soimport from '@jackwener/opencli/registry'always resolves against the host - Transpile — Compiles
.ts→.jsviaesbuild(productionnodecannot load.tsdirectly)
On startup, if both my-command.ts and my-command.js exist, the .js version is loaded to avoid duplicate registration.
| Repo | Type | Description |
|---|---|---|
| opencli-plugin-github-trending | YAML | GitHub Trending repositories |
| opencli-plugin-hot-digest | TS | Multi-platform trending aggregator (zhihu, weibo, bilibili, v2ex, stackoverflow, reddit, linux-do) |
| opencli-plugin-juejin | YAML | 稀土掘金 (Juejin) hot articles, categories, and article feed |
Restart opencli (or open a new terminal) — plugins are discovered at startup.
If you see Cannot find module '@jackwener/opencli/registry', the host symlink may be broken. Reinstall the plugin:
opencli plugin uninstall my-plugin
opencli plugin install github:user/opencli-plugin-my-plugin