mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-18 21:22:28 +03:00
feat(cli): relay-like CLI closure — target manifest, Codex TOML, Gemini launcher, guards
- canonical executable manifest (bin/cli/cli-manifest.mjs): run/configure/completion derive targets, aliases and --model wiring from one table; drift test cross-checks manifest x cliRuntime x UI catalog (tests/unit/cli/cli-manifest-drift.test.ts) - dashboard Codex generator converged to ~/.codex/config.toml (modern Codex v0.137+, verified against codex-cli 0.147.0): conservative merge, env_key auth (key never written), refuses invalid TOML, reports legacy config.yaml as migration note - omniroute run gemini: launcher over OmniRoute's /v1beta surface via GOOGLE_GEMINI_BASE_URL + isolated GEMINI_CLI_HOME forcing gemini-api-key auth (contract proven against @google/gemini-cli 0.50.0); ACP registration kept distinct - opt-in real smoke harness for upstream CLIs (RUN_CLI_SMOKE=1, credential by env NAME, redacted output): tests/integration/upstream-cli-smoke.int.test.ts - container-guard homologation for POST /api/cli-tools/apply (422 in container, dry-run preview allowed, host write passes) + docs; guard untouched - typecheck: omniglyphAdapter union narrowing, usageTracking typed signatures (UsageLike, no any), models.ts isValidModel params — typecheck:core and typecheck:noimplicit:core now clean - relay core (prior session of this effort): omniroute run for 6 CLIs, configure picker with per-context favorites/recents, contexts with optional keychain + 0600 fallback, provider CRUD with recursive redaction, completion updates, docs
This commit is contained in:
@@ -28,7 +28,7 @@ const PASSTHROUGH_PROVIDERS = new Set(
|
||||
);
|
||||
|
||||
// Wrap isValidModel with passthrough providers
|
||||
export function isValidModel(aliasOrId, modelId) {
|
||||
export function isValidModel(aliasOrId: string, modelId: string) {
|
||||
if (isOpenAICompatibleProvider(aliasOrId)) return true;
|
||||
if (isAnthropicCompatibleProvider(aliasOrId)) return true;
|
||||
if (PASSTHROUGH_PROVIDERS.has(aliasOrId)) return true;
|
||||
|
||||
@@ -198,6 +198,34 @@ const CLI_TOOLS: Record<string, any> = {
|
||||
env: ".qwen/.env",
|
||||
},
|
||||
},
|
||||
aider: {
|
||||
defaultCommand: "aider",
|
||||
envBinKey: "CLI_AIDER_BIN",
|
||||
requiresBinary: true,
|
||||
healthcheckTimeoutMs: 12000,
|
||||
paths: {
|
||||
config: ".aider.conf.yml",
|
||||
},
|
||||
},
|
||||
goose: {
|
||||
defaultCommand: "goose",
|
||||
envBinKey: "CLI_GOOSE_BIN",
|
||||
requiresBinary: true,
|
||||
healthcheckTimeoutMs: 12000,
|
||||
paths: {
|
||||
config: ".config/goose/config.yaml",
|
||||
},
|
||||
},
|
||||
gemini: {
|
||||
defaultCommand: "gemini",
|
||||
envBinKey: "CLI_GEMINI_BIN",
|
||||
requiresBinary: true,
|
||||
// gemini-cli cold start (bundle + extension discovery) can exceed 4s.
|
||||
healthcheckTimeoutMs: 15000,
|
||||
paths: {
|
||||
settings: ".gemini/settings.json",
|
||||
},
|
||||
},
|
||||
// ── Plan 14 — new "custom" configType tools ───────────────────────────────
|
||||
forge: {
|
||||
defaultCommand: "forge",
|
||||
@@ -286,6 +314,33 @@ const CLI_TOOLS: Record<string, any> = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Compatibility aliases accepted by CLI/API callers.
|
||||
*
|
||||
* The runtime catalog keeps one canonical id per executable. Older surfaces
|
||||
* exposed a binary name (notably `kilocode`) or launcher aliases instead of
|
||||
* that id, so normalize them at the boundary rather than duplicating entries.
|
||||
*/
|
||||
export const CLI_TOOL_ALIASES: Readonly<Record<string, string>> = {
|
||||
kilocode: "kilo",
|
||||
"kilo-code": "kilo",
|
||||
kilo_cli: "kilo",
|
||||
cc: "claude",
|
||||
"claude-code": "claude",
|
||||
"openai-codex": "codex",
|
||||
openai: "codex",
|
||||
cn: "continue",
|
||||
qodercli: "qoder",
|
||||
};
|
||||
|
||||
/** Resolve a user-facing or legacy id to the canonical runtime id. */
|
||||
export const normalizeCliToolId = (toolId: string): string => {
|
||||
const normalized = String(toolId || "")
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
return CLI_TOOL_ALIASES[normalized] || normalized;
|
||||
};
|
||||
|
||||
const isWindows = () => process.platform === "win32";
|
||||
|
||||
/**
|
||||
@@ -568,6 +623,7 @@ const getExtraPaths = () =>
|
||||
* Works on all platforms — Windows checks .cmd wrappers, Linux/macOS checks bare names.
|
||||
*/
|
||||
export const getKnownToolPaths = (toolId: string): string[] => {
|
||||
toolId = normalizeCliToolId(toolId);
|
||||
const home = os.homedir();
|
||||
const paths: string[] = [];
|
||||
|
||||
@@ -730,7 +786,7 @@ export const getLookupEnv = () => {
|
||||
};
|
||||
|
||||
const resolveToolCommands = (toolId: string): string[] => {
|
||||
const tool = CLI_TOOLS[toolId];
|
||||
const tool = CLI_TOOLS[normalizeCliToolId(toolId)];
|
||||
if (!tool) return [];
|
||||
const envCommand = String(process.env[tool.envBinKey] || "").trim();
|
||||
if (envCommand) return [envCommand];
|
||||
@@ -740,6 +796,16 @@ const resolveToolCommands = (toolId: string): string[] => {
|
||||
return tool.defaultCommand ? [tool.defaultCommand] : [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Return command candidates without probing the filesystem.
|
||||
*
|
||||
* Lightweight consumers (config status and CLI inventory) use this to build
|
||||
* a version probe while getCliRuntimeStatus() remains the authoritative
|
||||
* health/runnability check.
|
||||
*/
|
||||
export const getCliToolCommandCandidates = (toolId: string): string[] =>
|
||||
resolveToolCommands(toolId);
|
||||
|
||||
const checkExplicitPath = async (commandPath: string) => {
|
||||
// Reject paths that look like injection attempts
|
||||
if (!isSafePath(commandPath)) {
|
||||
@@ -781,13 +847,13 @@ export const locateCommand = async (command: string, env: Record<string, string
|
||||
// and a .cmd wrapper. We must prefer the Windows executable extension.
|
||||
const lines = located.stdout
|
||||
.split(/\r?\n/)
|
||||
.map((l) => l.trim())
|
||||
.map((l: string) => l.trim())
|
||||
.filter(Boolean);
|
||||
if (lines.length === 0) {
|
||||
return { installed: false, commandPath: null, reason: "not_found" };
|
||||
}
|
||||
const winExt = /\.(cmd|exe|bat|com)$/i;
|
||||
const preferred = lines.find((l) => winExt.test(l)) || lines[0];
|
||||
const preferred = lines.find((l: string) => winExt.test(l)) || lines[0];
|
||||
return { installed: true, commandPath: normalizeMsys2Path(preferred), reason: null };
|
||||
}
|
||||
return { installed: false, commandPath: null, reason: "not_found" };
|
||||
@@ -1025,6 +1091,7 @@ export const resolveOpencodeConfigPath = (
|
||||
export const getOpenCodeConfigPath = () => resolveOpencodeConfigPath();
|
||||
|
||||
export const getCliConfigPaths = (toolId: string) => {
|
||||
toolId = normalizeCliToolId(toolId);
|
||||
const tool = CLI_TOOLS[toolId];
|
||||
if (!tool) return null;
|
||||
|
||||
@@ -1071,6 +1138,7 @@ export const getCliPrimaryConfigPath = (toolId: string) => {
|
||||
};
|
||||
|
||||
export const getCliRuntimeStatus = async (toolId: string) => {
|
||||
toolId = normalizeCliToolId(toolId);
|
||||
const tool = CLI_TOOLS[toolId];
|
||||
const runtimeMode = getRuntimeMode();
|
||||
if (!tool) {
|
||||
|
||||
Reference in New Issue
Block a user