OmniRoute/bin/cli/plugins.mjs
diegosouzapw b3e5ee3333 feat(cli): fase 9.4 — plugin system (omniroute-cmd-*)
Adds plugin discovery, loading, and management to the omniroute CLI.

- bin/cli/plugins.mjs: discoverPlugins / loadPlugins / buildPluginContext
- bin/cli/commands/plugin.mjs: list / install / remove / info / search / update / scaffold
- examples/omniroute-cmd-hello/: minimal working plugin example
- docs/dev/plugins.md: plugin API contract and authoring guide
- .env.example + ENVIRONMENT.md: document OMNIROUTE_PLUGIN_PATH
2026-05-15 05:11:18 -03:00

90 lines
2.5 KiB
JavaScript

import { readdirSync, existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { homedir } from "node:os";
import { pathToFileURL } from "node:url";
const PLUGIN_PREFIX_RE = /^(@[^/]+\/)?omniroute-cmd-/;
function getPluginDirs() {
return [join(homedir(), ".omniroute", "plugins"), process.env.OMNIROUTE_PLUGIN_PATH].filter(
Boolean
);
}
export async function discoverPlugins() {
const found = [];
for (const dir of getPluginDirs()) {
if (!existsSync(dir)) continue;
let entries;
try {
entries = readdirSync(dir, { withFileTypes: true });
} catch {
continue;
}
for (const entry of entries) {
if (!entry.isDirectory()) continue;
const pkgPath = join(dir, entry.name, "package.json");
if (!existsSync(pkgPath)) continue;
let pkg;
try {
pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
} catch {
continue;
}
if (!pkg.name || !PLUGIN_PREFIX_RE.test(pkg.name)) continue;
found.push({
name: pkg.name,
version: pkg.version || "0.0.0",
description: pkg.description || "",
dir: join(dir, entry.name),
pkg,
});
}
}
return found;
}
export function buildPluginContext(opts = {}) {
return {
apiFetch: async (...args) => {
const { apiFetch } = await import("./api.mjs");
return apiFetch(...args);
},
emit: async (...args) => {
const { emit } = await import("./output.mjs");
return emit(...args);
},
t: async (...args) => {
const { t } = await import("./i18n.mjs");
return t(...args);
},
withSpinner: async (...args) => {
const { withSpinner } = await import("./spinner.mjs");
return withSpinner(...args);
},
baseUrl: opts.baseUrl,
apiKey: opts.apiKey,
};
}
export async function loadPlugins(program, ctx = {}) {
const plugins = await discoverPlugins();
for (const p of plugins) {
try {
const entryPath = join(p.dir, p.pkg.main || "index.mjs");
if (!existsSync(entryPath)) {
process.stderr.write(`[plugin] ${p.name}: entry file not found (${entryPath})\n`);
continue;
}
const mod = await import(pathToFileURL(entryPath).href);
if (typeof mod.register === "function") {
mod.register(program, buildPluginContext(ctx));
} else {
process.stderr.write(`[plugin] ${p.name}: no register() export — skipping\n`);
}
} catch (err) {
process.stderr.write(`[plugin] Failed to load ${p.name}: ${err.message}\n`);
}
}
return plugins.length;
}