Files
jackwener__opencli/docs/guide/plugins.md
AlexYue 1d39295f4b feat: plugin system (Stage 0-2)
* feat: plugin system (Stage 0-2)

- Stage 0: discoverPlugins() scans ~/.opencli/plugins/ at startup
- Stage 1: demo plugin repos (github-trending, hot-digest)
- Stage 2: opencli plugin install/uninstall/list commands
- package.json exports ./registry for TS plugin peerDep support
- 17 new/updated tests, tsc --noEmit clean

* fix: CDPBridge connect timeout unit mismatch (seconds vs ms)

opts.timeout is passed in seconds from runtime.ts but CDPBridge
was using it as milliseconds, causing instant timeout (30ms).

* feat: add registry-api public entry point for TS plugin peerDep support

- Add src/registry-api.ts: re-exports core registration API (cli, Strategy,
  getRegistry) without transitive side-effects, safe for plugin imports
- Update package.json exports: './registry' -> './dist/registry-api.js'
- Update src/registry.ts: use globalThis shared registry to ensure single
  instance across npm-linked plugin modules
- Update .gitignore for plugin-related artifacts

* fix: symlink host opencli into plugin node_modules on install

After npm install, replace the npm-installed @jackwener/opencli
with a symlink to the running host's package root. This ensures
TS plugins always resolve '@jackwener/opencli/registry' against
the host installation, avoiding version mismatches when the
published npm package lags behind.

* fix: transpile TS plugins to JS on install, deduplicate .ts/.js discovery

- installPlugin: after symlinking host opencli, transpile any .ts files
  to .js using esbuild from the host's node_modules/.bin/
- discoverPluginDir: skip .ts files when a .js sibling exists (production
  node cannot load .ts directly)
- scanPluginCommands: deduplicate basenames via Set to avoid showing
  'aggregate, aggregate' when both .ts and .js exist

* docs: add plugin system user guide

- New docs/guide/plugins.md covering:
  - Installation/uninstallation commands
  - Creating YAML plugins (zero-dep)
  - Creating TS plugins (with peerDep)
  - TS plugin install lifecycle (clone → deps → symlink → transpile)
  - Example plugins and troubleshooting
- Add Plugins to VitePress sidebar (EN + ZH)
- Link from getting-started.md Next Steps

* fix: address review issues in plugin system

- Security: replace execSync with execFileSync to prevent shell injection
- Replace deprecated npm --production with --omit=dev
- Tighten parseSource regex to [\w.-]+ to reject special chars
- Fix ZH sidebar plugin link (/guide/plugins → /zh/guide/plugins)
- Return plugin name from installPlugin() to avoid duplicated logic
- Use execFileSync for esbuild transpilation
- Fix misleading comment in linkHostOpencli

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-03-21 19:41:15 +08:00

3.9 KiB

Plugins

OpenCLI supports community-contributed plugins. Install third-party adapters from GitHub, and they're automatically discovered alongside built-in commands.

Quick Start

# Install a plugin
opencli plugin install github:ByteYue/opencli-plugin-github-trending

# List installed plugins
opencli plugin list

# 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 .yaml, .ts, or .js command files — the same formats used by built-in adapters.

Supported Source Formats

opencli plugin install github:user/repo
opencli plugin install https://github.com/user/repo

The repo name prefix opencli-plugin- is automatically stripped for the local directory name. For example, opencli-plugin-hot-digest becomes hot-digest.

Creating a Plugin

Option 1: YAML Plugin (Simplest)

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]

Option 2: TypeScript Plugin

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,
    }));
  },
});

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 YAML GitHub Trending repositories
opencli-plugin-hot-digest TS Multi-platform trending aggregator (zhihu, weibo, bilibili, v2ex, stackoverflow, reddit, linux-do)

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:

opencli plugin uninstall my-plugin
opencli plugin install github:user/opencli-plugin-my-plugin