mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-31 04:12:10 +03:00
* fix(doctor): resolve two false-positive WARNs (#6162) The `omniroute doctor` command reported two warnings on healthy installs even though the underlying checks actually passed. Both came from the doctor probing state that already worked; they looked like bugs but users couldn't tell without manual digging. Issue 1 — Server liveness HTTP 401 /api/health and /api/health/degradation both require the management token. Doctor called them without auth → 401 → WARN, even when the Next.js server was clearly alive and listening. Fix: probe the configured health endpoint first; on 401/403, fall back to a publicly served static asset (/favicon.ico) to confirm the server is alive. WARN now only fires when both probes fail. Issue 2 — CLI Tools '@/shared' import tool-detector.ts (and 3 other cli-helper files) import @/shared/... aliases that resolve via tsconfig.json paths. The CLI ships raw TS source (no compile step) and runs through tsx, but tsx does not honor tsconfig paths at runtime, and tsconfig-paths only hooks CJS Module._resolveFilename while doctor uses ESM `import()`. Fix: replace @/shared/... with relative imports in the 4 cli-helper files. This is the same pattern these files already use for ./config- generator/* imports. No new dependency, no architectural change, and the fix doesn't regress Next.js itself which keeps using @/shared. Verified on v3.8.43 (Node v24.17, Windows 11): Before: 7 ok, 2 warning(s), 0 failure(s) After: 8 ok, N warning(s), 0 failure(s) where N accurately reflects which CLI tools are installed and configured for OmniRoute (e.g. Hermes Agent installed but not pointed at 20128 → 2 real warnings, not 1 false-positive). Refs #6162 * fix(doctor): derive fallback URL from primary URL via new URL() Per Gemini code-assist review feedback: the previous fallback constructed the /favicon.ico URL from defaults (127.0.0.1:PORT) which ignored custom host/port/protocol configurations supplied via: - OMNIROUTE_DOCTOR_LIVENESS_URL - OMNIROUTE_DOCTOR_HOST - --liveness-url / --host CLI flags Parse the primary URL with new URL() to preserve protocol, host, port, and subpaths. The previous default-based fallback remains as a catch-all for invalid primary URLs. * test(doctor): add regression tests for #6162 fixes Two new test files lock the fix and satisfy the PR Test Policy gate ("production code change without tests"): - tests/unit/cli-helper-tool-detector-paths-6162.test.ts Locks the @/shared → relative imports fix across all 4 cli-helper files. Asserts (a) no @/shared alias remains in the cli-helper sources, and (b) each file is importable at runtime via tsx/ESM, which would have thrown "Cannot find package '@/shared'" before the fix. - tests/unit/cli-doctor-liveness-fallback-6162.test.ts Locks the /favicon.ico fallback in doctor.mjs. Asserts the fallback probe exists, derives its URL from the primary URL via new URL() (per Gemini review feedback), and that the buggy 'Server responded with HTTP 401' WARN path is gone. Both tests use only node:test + node:assert/strict so they slot into the existing 'test' and 'test:unit' scripts with no extra config. * test(doctor): fix primary.ok regex in fallback test The earlier regex /primary\.ok\s*\?/ required a '?' immediately after, but the actual doctor.mjs code uses a multi-line if-block: if (primary.ok) { return ok(...); } Use /\bprimary\.ok\b/ instead so the assertion matches the existing branching. --------- Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
bin/cli — OmniRoute CLI internals
This directory contains the CLI runtime, helpers, and commands for the omniroute binary.
Structure
bin/cli/
├── CONVENTIONS.md ← normative design rules (read this first)
├── README.md ← this file
├── program.mjs ← Commander setup — global flags, registerCommands()
├── api.mjs ← apiFetch() — all HTTP calls + retry/backoff
├── runtime.mjs ← withRuntime() — server-first / DB-fallback
├── i18n.mjs ← t() — i18n helper + locale detection
├── output.mjs ← emit() — table/json/jsonl/csv + printSuccess/printError
├── io.mjs ← ask() / askSecret() — interactive prompts
├── data-dir.mjs ← resolveDataDir() / resolveStoragePath()
├── sqlite.mjs ← openOmniRouteDb() — DB bootstrap
├── encryption.mjs ← encrypt/decrypt credentials
├── provider-catalog.mjs ← static provider catalog
├── provider-store.mjs ← DB CRUD for provider_connections
├── provider-test.mjs ← testProviderApiKey()
├── settings-store.mjs ← DB CRUD for key_value settings
├── locales/
│ ├── en.json ← English strings (source of truth, 42+ locales)
│ ├── pt-BR.json ← Portuguese (Brazil) — fully translated
│ └── {locale}.json ← 40 additional locales (ar, az, de, es, fr, ja, zh-CN, …)
├── scripts/
│ └── generate-locales.mjs ← scaffold new locale files from config/i18n.json
└── commands/
├── setup.mjs
├── doctor.mjs
├── providers.mjs
├── config.mjs ← includes `config lang get/set/list`
├── status.mjs
├── logs.mjs
└── update.mjs
Key helpers
apiFetch(path, opts) — api.mjs
All HTTP calls to the OmniRoute server must go through this wrapper.
import { apiFetch } from "./api.mjs";
const res = await apiFetch("/api/health");
if (!res.ok) await res.assertOk(); // throws ApiError with mapped exit code
const data = await res.json();
Options:
baseUrl— override base URL (default:OMNIROUTE_BASE_URLenv orlocalhost:20128)apiKey— override API key (default:OMNIROUTE_API_KEY)method,body,headers— standard fetch optionstimeout— per-attempt ms (default:30000)retry—falseto disable (default: enabled)retryMax— total attempts (default:3)verbose— log retry attempts to stderr
withRuntime(fn, opts) — runtime.mjs
Provides server-first / DB-fallback transparently.
import { withRuntime } from "./runtime.mjs";
await withRuntime(async (ctx) => {
if (ctx.kind === "http") {
const res = await ctx.api("/v1/providers");
return res.json();
}
return ctx.db.prepare("SELECT * FROM provider_connections").all();
});
opts.requireServer = true— throwsServerOfflineError(exit 3) if offlineopts.preferDb = true— always use DB (skip server check)
t(key, vars) — i18n.mjs
Internationalized strings. Catalog loaded from locales/{locale}.json.
import { t } from "./i18n.mjs";
console.log(t("common.serverOffline"));
console.log(t("setup.testFailed", { error: err.message }));
Locale detection order: OMNIROUTE_LANG → LC_ALL → LC_MESSAGES → LANG → en.
emit(data, opts) — output.mjs
Format-aware output. Reads opts.output to select table/json/jsonl/csv.
import { emit, printError, EXIT_CODES } from "./output.mjs";
emit(providers, { output: opts.output ?? "table" });
printError("Something went wrong");
process.exit(EXIT_CODES.SERVER_OFFLINE);
Locale selection
The CLI displays text in the user's language. Detection order:
--lang <code>flag on the command lineOMNIROUTE_LANGenvironment variable- System env:
LC_ALL→LC_MESSAGES→LANG - Fallback:
en
Set permanently:
omniroute config lang set pt-BR # saves to ~/.omniroute/.env
omniroute config lang list # show all 42 available locales
omniroute config lang get # show currently active locale
One-time override:
omniroute --lang de providers list # run in German, not persisted
OMNIROUTE_LANG=ja omniroute status # same effect via env
Adding a new locale: add entry to config/i18n.json, then run:
node bin/cli/scripts/generate-locales.mjs
Adding a new command
- Create
bin/cli/commands/your-command.mjs - Export
registerYourCommand(program)following the Commander pattern - Register in
bin/cli/commands/registry.mjs - Add strings to
locales/en.jsonandlocales/pt-BR.json - Write test in
tests/unit/cli-your-command.test.ts
See CONVENTIONS.md for exit codes, flag naming, output format, and destructive-action rules.