Files
OmniRoute/bin/cli/commands/launch-codex.mjs
Diego Rodrigues de Sa e Souza c34d37a11e feat(cli): Codex CLI launcher + setup commands (#4270)
* feat(cli): Codex CLI launcher + setup commands

Two new CLI subcommands mirroring the `launch`/`configure` pattern, for driving the
OpenAI Codex CLI against OmniRoute:

- `omniroute launch-codex` — boots OmniRoute (if needed) and launches Codex CLI
  pointed at it (local or remote VPS), with no manual env/config editing.
- `omniroute setup-codex` — generates ~/.codex profile files from OmniRoute's live
  model catalog.

Registered in the command registry; en.json + pt-BR.json locale sections added
(keeps the cli-i18n-catalog top-level parity gate green). CODEX-CLI-CONFIGURATION.md
refreshed. Consolidated from work-in-progress that was uncommitted on the shared
checkout; reconstructed on release/v3.8.30.

* chore(vscode): reduce git repo-detection overhead for nested worktrees

Disable VS Code's git auto-repository-detection / submodule scan / autofetch so the
Source Control view stops indexing the ~44 nested repos (worktrees + _references/*
+ _mono_repo/*), which caused constant "validating" churn. Only the root repo is
tracked. Editor-only settings; no runtime impact.
2026-06-19 09:36:41 -03:00

104 lines
3.5 KiB
JavaScript

import { spawn } from "node:child_process";
import { t } from "../i18n.mjs";
/**
* Health-check an OmniRoute endpoint (local or remote) before launching Codex.
* @param {string} baseUrl
* @param {number} timeoutMs
* @returns {Promise<boolean>}
*/
async function healthCheck(baseUrl, timeoutMs = 3000) {
try {
const res = await fetch(`${baseUrl.replace(/\/v1$/, "")}/api/monitoring/health`, {
signal: AbortSignal.timeout(timeoutMs),
});
return res.ok;
} catch {
return false;
}
}
/**
* Build the env for the Codex child process.
* Injects OMNIROUTE_API_KEY if an explicit api-key was provided on the command line
* (useful when launching against a remote VPS whose key differs from ~/.bashrc).
* @param {Record<string,string>} baseEnv
* @param {string|undefined} apiKey
* @returns {Record<string,string>}
*/
export function buildCodexEnv(baseEnv, apiKey) {
const env = { ...baseEnv };
if (apiKey) env.OMNIROUTE_API_KEY = apiKey;
return env;
}
/**
* @param {{port?:string, remote?:string, profile?:string, apiKey?:string}} opts
* @param {string[]} codexArgs pass-through args for the codex binary
* @returns {Promise<number>} exit code
*/
export async function runLaunchCodexCommand(opts = {}, codexArgs = []) {
const port = Number(opts.port ?? process.env.PORT ?? 20128) || 20128;
const baseUrl = opts.remote ?? `http://localhost:${port}/v1`;
const ok = await healthCheck(baseUrl);
if (!ok) {
const location = opts.remote ?? `port ${port}`;
console.error(
(t("launch.notRunning") || "OmniRoute is not running on port {port}. Start it with 'omniroute serve'.")
.replace("{port}", String(location))
);
return 1;
}
const profile = opts.profile;
const extraArgs = profile ? ["--profile", profile, ...codexArgs] : codexArgs;
const env = buildCodexEnv(process.env, opts.apiKey ?? opts["api-key"]);
return await new Promise((resolve) => {
const child = spawn("codex", extraArgs, { env, stdio: "inherit" });
child.on("error", (err) => {
if (err?.code === "ENOENT") {
console.error(
"The 'codex' CLI was not found in PATH. Install with:\n" +
" npm install -g @openai/codex"
);
resolve(127);
} else {
console.error(String(err?.message || err));
resolve(1);
}
});
child.on("exit", (code) => resolve(code ?? 0));
});
}
export function registerLaunchCodex(program) {
program
.command("launch-codex")
.description(
t("launchCodex.description") ||
"Launch Codex CLI pointed at OmniRoute (local or remote VPS)"
)
.option("--port <port>", "Local OmniRoute port (ignored when --remote is set)", "20128")
.option(
"--remote <url>",
"Remote OmniRoute base URL, e.g. http://100.67.86.91:20128/v1 (overrides --port)"
)
.option("--profile <name>", "Codex profile to activate (passed as --profile <name>)")
.option("-p, --p <name>", "Alias for --profile")
.option(
"--api-key <key>",
"OmniRoute API key (overrides OMNIROUTE_API_KEY env var for this invocation)"
)
.allowUnknownOption(true)
.allowExcessArguments(true)
.argument("[codexArgs...]", "arguments passed through to the codex binary")
.action(async (codexArgs, opts) => {
// -p is an alias for --profile
const merged = { ...opts, profile: opts.profile ?? opts.p };
const exitCode = await runLaunchCodexCommand(merged, codexArgs ?? []);
if (exitCode !== 0) process.exit(exitCode);
});
}