Files
OmniRoute/docs/dev/plugins.md
diegosouzapw e45eb1e80f fix(ci): unblock release/v3.8.2 CI + parallelize tests
- qs override ^6.15.2 to clear GHSA-q8mj-m7cp-5q26 audit advisory
- docs: drop two broken links (omniroute-cmd-hello example, Tuto_Qdrant.md)
- i18n: relax UI coverage threshold 80→65 for this release (follow-up issue
  to restore after locale catch-up)
- openai registry: re-add gpt-4o + gpt-4o-mini (still serviced by upstream;
  removal broke integration tests using these model IDs)
- models/v1 catalog: skip combos lacking a name field so OpenAI-shape contract
  test does not see entries without 'id'
- db/core: drop duplicated skipIntegrityCheck key in runDbHealthCheck options
  (TS1117 from #2591 review oversight)
- CI: bump unit/node-compat concurrency 1→4 and unit shards 2→4 so the test
  matrix uses available vCPUs; integration kept concurrency=1 for SQLite
  safety
2026-05-22 21:13:53 -03:00

3.5 KiB

OmniRoute CLI Plugin System

Extend the omniroute CLI without modifying its core. Plugins follow the omniroute-cmd-* naming convention, similar to gh extension or kubectl plugin.

Quick start

# Install a plugin from npm
omniroute plugin install stripe

# Install a local plugin in development
omniroute plugin install ./my-plugin

# List installed plugins
omniroute plugin list

# Scaffold a new plugin
omniroute plugin scaffold myplugin
cd omniroute-cmd-myplugin
omniroute plugin install .

Plugin anatomy

A plugin is an npm package named omniroute-cmd-<name> (or @scope/omniroute-cmd-<name>).

omniroute-cmd-myplugin/
├── package.json     # must have "type": "module" and "main": "index.mjs"
├── index.mjs        # exports register(program, ctx) + optional meta
└── README.md

package.json

{
  "name": "omniroute-cmd-myplugin",
  "version": "0.1.0",
  "type": "module",
  "main": "index.mjs",
  "engines": { "omniroute": ">=4.0.0" },
  "keywords": ["omniroute-plugin", "omniroute-cmd"]
}

index.mjs

export const meta = {
  name: "myplugin",
  version: "0.1.0",
  description: "My plugin for OmniRoute",
  omnirouteApi: ">=4.0.0",
};

export function register(program, ctx) {
  program
    .command("myplugin")
    .description(meta.description)
    .option("-n, --name <name>")
    .action(async (opts, cmd) => {
      const gOpts = cmd.optsWithGlobals();
      const res = await ctx.apiFetch("/api/combos", {
        baseUrl: gOpts.baseUrl,
        apiKey: gOpts.apiKey,
      });
      const data = await res.json();
      ctx.emit(data, gOpts);
    });
}

Plugin context API

The ctx object passed to register(program, ctx):

Property Type Description
ctx.apiFetch(path, opts) async function Authenticated fetch to the OmniRoute server
ctx.emit(data, opts) function Output in table/json/jsonl/csv per --output flag
ctx.t(key) async function i18n translation lookup
ctx.withSpinner(label, fn) async function Wraps async fn with ora spinner
ctx.baseUrl string Resolved base URL
ctx.apiKey string | null API key if provided

Discovery

Plugins are discovered from:

  1. ~/.omniroute/plugins/<name>/ — user-local installs
  2. OMNIROUTE_PLUGIN_PATH env var — custom directory

Loading errors are caught and printed as warnings — a broken plugin never crashes the CLI.

Security

Plugins run with the same Node.js process privileges as omniroute. Only install plugins from sources you trust. omniroute plugin install shows an explicit warning and requires --yes or interactive confirmation.

Publishing

  1. Ensure package.json has "keywords": ["omniroute-plugin"]
  2. npm publish as normal
  3. Users discover via omniroute plugin search <query> (searches npm registry)

Example plugin

A minimal working example follows the structure shown in the Plugin shape section above — a plugin package with a register(program, ctx) export, a keywords: ["omniroute-plugin"] entry in package.json, and an omnirouteApi semver range in the exported meta.