From be96fb916b1e2fb3f9b325655ccd011ecd0cba73 Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Wed, 1 Jul 2026 01:29:33 -0300 Subject: [PATCH] feat(cli): show version in startup banner (integrates #5752) (#5769) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(cli): show version in startup banner Print dim 'v' line below ASCII art logo in omniroute serve. Uses readFileSync (same pattern as program.mjs) to read package.json. Closes #5749. * test(cli): guard startup-banner version line (#5752) Source-inspection test (same pattern as cli-serve-port.test.ts) asserting serve.mjs parses the version from package.json and prints v${_pkg.version} in the startup banner — satisfies Hard Rule #8 for the bin/ change. Co-authored-by: diegosouzapw * docs(changelog): credit #5752 startup-banner version line (thanks @chirag127) --------- Co-authored-by: Chirag Singhal <76880977+chirag127@users.noreply.github.com> --- CHANGELOG.md | 2 ++ bin/cli/commands/serve.mjs | 14 ++++---- tests/unit/cli-serve-version-banner.test.ts | 38 +++++++++++++++++++++ 3 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 tests/unit/cli-serve-version-banner.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 03123eb488..c03c7dfe2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - **providers (CLI profile auto-sync):** opt-in toggles to auto-regenerate CLI tool profiles after a provider model sync. When enabled, a model-catalog change (re)writes that tool's profile files from the live catalog — Codex (`~/.codex/*.config.toml`) and now **Claude Code** (`~/.claude/profiles//settings.json`, via an extracted `syncClaudeProfilesFromModels` + a new `claudeProfileAutoSync.ts` mirroring the Codex path). Both are **off by default** and never touch the active/default CLI config; they are backed by the `OMNIROUTE_AUTO_SYNC_CODEX_PROFILES` / `OMNIROUTE_AUTO_SYNC_CLAUDE_PROFILES` feature flags (DB/dashboard override > env > default "false") and additionally gated behind the existing `CLI_ALLOW_CONFIG_WRITES` write-guard. A **"CLI profile auto-sync"** card on the providers dashboard toggles each. Regression guards: `tests/unit/claude-profile-auto-sync-gate.test.ts`, `tests/unit/codex-profile-auto-sync-gate.test.ts`, `tests/unit/cli/setup-claude.test.ts` (follow-up to #5737). +- **cli (startup banner):** the `serve` startup banner now prints the running OmniRoute version (`v3.8.x`) beneath the ASCII logo, so the active version is visible at a glance without a separate `--version` call. Regression guard: `tests/unit/cli-serve-version-banner.test.ts`. Thanks [@chirag127](https://github.com/chirag127) ([#5752](https://github.com/diegosouzapw/OmniRoute/pull/5752)). + - **analytics (subscription cost):** flat-rate providers now show **$0** in cost analytics instead of an inflated per-token estimate. Subscription / coding-plan providers (every cookie-web provider — ChatGPT Web, grok-web, … — plus the dedicated **Minimax Coding**, **Kimi Coding**, **GLM Coding**, **Alibaba Coding Plan**, and **Xiaomi MiMo** plans) bill a flat fee, not per token, yet still carry per-token pricing rows used for estimates — so the analytics dashboard over-reported their cost. A new flat-rate classifier (`src/lib/usage/flatRateProviders.ts`) is consulted by the analytics surfaces (analytics route, usage stats, usage analytics) via an opt-in `flatRateAsZero` cost option, so those providers read $0 while **budget / quota / routing keep estimating unchanged**. Deliberately NOT zeroed: `codex`/`cx` (OmniRoute actively tracks Codex token cost — Fast-tier multipliers, GPT-5.x pricing — and Codex can be a metered account), `byteplus` (metered ModelArk), `minimax-cn` (metered China API). Regression guard: `tests/unit/flat-rate-cost-5552.test.ts`. ([#5552](https://github.com/diegosouzapw/OmniRoute/issues/5552)) - **mcp (RTK):** expose the RTK tool-output **learn/discover** workflow as two new MCP tools so an agent can grow the RTK filter catalog without leaving the protocol. `omniroute_rtk_discover` analyzes recently captured raw tool output (`discoverRepeatedNoise` / `suggestFilter`) and returns candidate noise patterns plus a suggested filter; `omniroute_rtk_learn` lists the captured command samples (`listRtkCommandSamples`) and resolves a command to its RTK filter id (`commandToId`). Both are read-only (scope `read:compression`), wrap the existing RTK discovery primitives (no new logic in the engine), and log to the MCP audit trail. Regression guard: `tests/unit/compression/rtk-mcp-tools.test.ts` (4). gaps v3.8.42 — T07. diff --git a/bin/cli/commands/serve.mjs b/bin/cli/commands/serve.mjs index 2260127a38..32c898d077 100644 --- a/bin/cli/commands/serve.mjs +++ b/bin/cli/commands/serve.mjs @@ -1,5 +1,5 @@ import { spawn } from "node:child_process"; -import { existsSync } from "node:fs"; +import { existsSync, readFileSync } from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; import { platform, totalmem } from "node:os"; @@ -16,6 +16,7 @@ import { import { resolveTlsOptions } from "../../../scripts/dev/tls-options.mjs"; const __dirname = dirname(fileURLToPath(import.meta.url)); +const _pkg = JSON.parse(readFileSync(join(__dirname, "..", "..", "..", "package.json"), "utf8")); // URL scheme for the "OmniRoute is running" banner — flipped to https when // opt-in TLS (#5242) is active. Process-scoped: one `serve` run = one scheme. @@ -48,11 +49,13 @@ export function registerServe(program) { .option("--no-tray", t("serve.no_tray") || "Disable system tray icon") .option( "--tls-cert ", - t("serve.tls_cert") || "Path to a TLS certificate (PEM) to serve HTTPS (also OMNIROUTE_TLS_CERT)" + t("serve.tls_cert") || + "Path to a TLS certificate (PEM) to serve HTTPS (also OMNIROUTE_TLS_CERT)" ) .option( "--tls-key ", - t("serve.tls_key") || "Path to the TLS private key (PEM) to serve HTTPS (also OMNIROUTE_TLS_KEY)" + t("serve.tls_key") || + "Path to the TLS private key (PEM) to serve HTTPS (also OMNIROUTE_TLS_KEY)" ) .action(async (opts) => { await runServe(opts); @@ -78,6 +81,7 @@ export async function runServe(opts = {}) { | |__| | | | | | | | | | | | \\ \\ (_) | |_| | || __/ \\____/|_| |_| |_|_| |_|_|_| \\_\\___/ \\__,_|\\__\\___| \x1b[0m`); + console.log(`\x1b[2m v${_pkg.version}\x1b[0m\n`); const nodeSupport = getNodeRuntimeSupport(); if (!nodeSupport.nodeCompatible) { @@ -366,9 +370,7 @@ async function maybeStartTray(port, apiPort, supervisor) { } catch (err) { // tray is optional — do not fail the server, but surface why it failed so // "--tray shows nothing" is diagnosable instead of silent (#4605). - process.stderr.write( - `[omniroute][tray] failed to start: ${err?.message ?? String(err)}\n` - ); + process.stderr.write(`[omniroute][tray] failed to start: ${err?.message ?? String(err)}\n`); } } diff --git a/tests/unit/cli-serve-version-banner.test.ts b/tests/unit/cli-serve-version-banner.test.ts new file mode 100644 index 0000000000..5c7d84e2bb --- /dev/null +++ b/tests/unit/cli-serve-version-banner.test.ts @@ -0,0 +1,38 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; + +/** + * Regression guard for the startup-banner version line (#5752). + * + * `runServe` prints `v` under the ASCII banner. The version is parsed + * once at module load from the repo-root package.json. These source-inspection + * assertions (same technique as cli-serve-port.test.ts) ensure the banner never + * silently loses the version again — mirroring how the CLI is exercised without + * spawning a real server. + */ +const serveSource = fs.readFileSync( + path.resolve(import.meta.dirname, "../../bin/cli/commands/serve.mjs"), + "utf-8", +); + +test("serve banner: version is parsed from package.json at module load", () => { + assert.match( + serveSource, + /_pkg\s*=\s*JSON\.parse\(\s*readFileSync\(/, + "serve.mjs should parse the version from package.json into _pkg", + ); + assert.ok( + serveSource.includes("package.json"), + "serve.mjs should reference package.json for the version source", + ); +}); + +test("serve banner: startup banner prints v", () => { + assert.match( + serveSource, + /v\$\{_pkg\.version\}/, + "serve.mjs should print v${_pkg.version} in the startup banner", + ); +});