Merge FASE 2: env audit

Resolves conflict in scripts/check/check-env-doc-sync.mjs (FASE 1 moved it from scripts/ to scripts/check/, FASE 2 modified it at the old path). Applies FASE 2's strict checker version at the new path, fixes __dirname-based REPO_ROOT to traverse two levels up, and updates the unit test import to the new path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
diegosouzapw
2026-05-13 16:02:44 -03:00
8 changed files with 983 additions and 226 deletions

View File

@@ -91,6 +91,12 @@ OMNIROUTE_USE_TURBOPACK=1
# Used by: src/lib/runtime/ports.ts — preserves canonical port in Electron.
# OMNIROUTE_PORT=20128
# Hostname/bind address for the Next.js server.
# Used by: scripts/run-next.mjs (HOST), Playwright runner (HOSTNAME).
# Default: 0.0.0.0 (HOST) / 127.0.0.1 (HOSTNAME inside tests).
#HOST=0.0.0.0
#HOSTNAME=127.0.0.1
# Environment mode — affects Next.js behavior, logging verbosity, and caching.
# Values: production | development | Default: production
NODE_ENV=production
@@ -147,6 +153,10 @@ ALLOW_API_KEY_REVEAL=false
# Default: false (blocked) | Set true to enable local providers.
# OMNIROUTE_ALLOW_PRIVATE_PROVIDER_URLS=true
# Legacy alias toggling the SSRF guard. Used by: src/shared/network/outboundUrlGuard.ts
# When unset, OmniRoute uses the per-feature defaults. Set to "false"/"0" to disable.
# OUTBOUND_SSRF_GUARD_ENABLED=true
# ═══════════════════════════════════════════════════════════════════════════════
# 5. INPUT SANITIZATION & PII PROTECTION (FASE-01)
@@ -250,6 +260,20 @@ NEXT_PUBLIC_CLOUD_URL=
# Legacy alias — fallback for NEXT_PUBLIC_BASE_URL in sync schedulers.
# NEXT_PUBLIC_APP_URL=http://localhost:20128
# Public callback URL for asynchronous image/audio jobs (kie.ai, etc.).
# Used by: open-sse/utils/kieTask.ts — overrides callbackUrlFromBaseUrl().
# Honor order: KIE_CALLBACK_URL → OMNIROUTE_KIE_CALLBACK_URL → OMNIROUTE_PUBLIC_URL.
#KIE_CALLBACK_URL=
#OMNIROUTE_KIE_CALLBACK_URL=
#OMNIROUTE_PUBLIC_URL=
# Upstream quota endpoints used by the Usage page. Override only for
# debugging or when routing through a corporate mirror. Used by:
# open-sse/services/usage.ts.
#OMNIROUTE_CROF_USAGE_URL=https://crof.ai/usage_api/
#OMNIROUTE_GEMINI_CLI_USAGE_URL=https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist
#OMNIROUTE_CODEWHISPERER_BASE_URL=https://codewhisperer.us-east-1.amazonaws.com
# ═══════════════════════════════════════════════════════════════════════════════
# 8. OUTBOUND PROXY (Upstream Provider Calls)
@@ -332,6 +356,17 @@ NEXT_PUBLIC_ENABLE_SOCKS5_PROXY=true
# Full list: admin, combos, health, models, routing, budget, metrics, pricing, memory, skills
# OMNIROUTE_MCP_SCOPES=admin,combos,health
# Compress MCP tool descriptions before serializing the manifest.
# Used by: open-sse/mcp-server/descriptionCompressor.ts — reduces token spend
# for clients that read the full tool catalog.
# Accepted disabling values: 0, false, off. Default: enabled.
# OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS=1
# Algorithm/profile used when description compression is enabled.
# Used by: open-sse/mcp-server/descriptionCompressor.ts
# Set to 0/false/off to skip compression entirely. Default: rtk
# OMNIROUTE_MCP_DESCRIPTION_COMPRESSION=rtk
# Model catalog sync interval in hours.
# Used by: src/shared/services/modelSyncScheduler.ts — periodic model refresh.
# Default: 24
@@ -347,6 +382,48 @@ PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES=70
# Useful for: CI builds, test environments, or resource-constrained containers.
# OMNIROUTE_DISABLE_BACKGROUND_SERVICES=false
# Force runtime background tasks (healthchecks/sync) even under automated test
# detection. Used by: src/lib/config/runtimeSettings.ts — overrides the test
# heuristic in instrumentation-node.ts. Default: unset (tests skip background).
#OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS=1
# Background job interval for budget reset checks (ms). Default: 600000 (10m).
# Used by: src/lib/jobs/budgetResetJob.ts. Floor: 10000.
#OMNIROUTE_BUDGET_RESET_JOB_INTERVAL_MS=600000
# Reasoning cache cleanup cadence (ms). Default: 1800000 (30m). Floor: 60000.
# Used by: src/lib/jobs/reasoningCacheCleanupJob.ts.
#OMNIROUTE_REASONING_CACHE_CLEANUP_INTERVAL_MS=1800000
# Spend write batcher cadence (ms) and buffer size before forced flush.
# Used by: src/lib/spend/batchWriter.ts. Defaults: 60000 ms / 1000 entries.
#OMNIROUTE_SPEND_FLUSH_INTERVAL_MS=60000
#OMNIROUTE_SPEND_MAX_BUFFER_SIZE=1000
# Config hot-reload polling interval (ms). Default: 5000.
# Used by: src/lib/config/hotReload.ts. Lower than 1000ms is rejected.
#OMNIROUTE_CONFIG_HOT_RELOAD_MS=5000
# Override the migrations directory used by src/lib/db/migrationRunner.ts.
# Default: <repo>/src/lib/db/migrations.
#OMNIROUTE_MIGRATIONS_DIR=
# Trust user-managed RTK project filter rules without strict signature checks.
# Used by: open-sse/services/compression/engines/rtk/filterLoader.ts. Default: 0.
#OMNIROUTE_RTK_TRUST_PROJECT_FILTERS=0
# Force a DB healthcheck regardless of cadence. Default: 0.
# Used by: src/lib/db/core.ts::shouldRunDbHealthCheck().
#OMNIROUTE_FORCE_DB_HEALTHCHECK=0
# DB healthcheck cadence override (ms). Default: 21600000 (6h).
# Used by: src/lib/db/core.ts::getDbHealthCheckIntervalMs().
#OMNIROUTE_DB_HEALTHCHECK_INTERVAL_MS=21600000
# Skip the Redis-backed auth cache used by API key lookups (forces DB reads).
# Used by: src/lib/db/apiKeys.ts. Set to 1 to disable. Default: enabled.
#OMNIROUTE_DISABLE_REDIS_AUTH_CACHE=0
# Flag set by bootstrap script after initial setup is complete.
# Used by: src/app/(dashboard)/dashboard/page.tsx — shows setup wizard vs. dashboard.
# OMNIROUTE_BOOTSTRAPPED=false
@@ -355,6 +432,11 @@ PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES=70
# Used by: open-sse/executors/antigravity.ts — escape hatch for multi-project setups.
# OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE=0
# Adjust how Antigravity advertises remaining credits. Used by:
# open-sse/services/antigravityCredits.ts — accepts forced override strings.
# Default: empty (use upstream-reported credits).
#ANTIGRAVITY_CREDITS=
# ═══════════════════════════════════════════════════════════════════════════════
# 11. OAUTH PROVIDER CREDENTIALS
@@ -406,6 +488,14 @@ WINDSURF_FIREBASE_API_KEY=AIzaSyBpLTEGSt59AUPKxBb7lIWjSE2ZXQH7mgU
# Required scopes: api, read_user, openid, profile, email
# GITLAB_DUO_OAUTH_CLIENT_ID=***
# GITLAB_DUO_OAUTH_CLIENT_SECRET=*** # optional — PKCE flow does not require a secret
#
# Self-managed GitLab Duo instance overrides.
# Used by: src/lib/oauth/gitlab.ts and src/lib/oauth/constants/oauth.ts —
# fall back to these when the _DUO_ variants above are unset.
#GITLAB_DUO_BASE_URL=https://gitlab.com
#GITLAB_BASE_URL=https://gitlab.com
#GITLAB_OAUTH_CLIENT_ID=
#GITLAB_OAUTH_CLIENT_SECRET=
# ── Qoder ──
QODER_OAUTH_CLIENT_SECRET=4Z3YjXycVsQvyGF1etiNlIBB4RsqSDtW
@@ -477,6 +567,10 @@ QWEN_USER_AGENT="QwenCode/0.15.9 (linux; x64)"
CURSOR_USER_AGENT="Cursor/3.3"
GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
# Override Codex client version sent in headers independently of the
# CODEX_USER_AGENT string. Used by: open-sse/config/codexClient.ts.
# CODEX_CLIENT_VERSION=0.130.0
# ═══════════════════════════════════════════════════════════════════════════════
# 13. CLI FINGERPRINT COMPATIBILITY (Anti-Detection)
@@ -491,7 +585,6 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
# CLI_COMPAT_CLAUDE=1
# CLI_COMPAT_GITHUB=1
# CLI_COMPAT_ANTIGRAVITY=1
# CLI_COMPAT_KIRO=1
# CLI_COMPAT_CURSOR=1
# CLI_COMPAT_KIMI_CODING=1
# CLI_COMPAT_KILOCODE=1
@@ -501,6 +594,12 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
# Or enable for all providers at once:
# CLI_COMPAT_ALL=1
# ── Kimi Coding CLI identity overrides ──
# Used by: src/lib/oauth/providers/kimi-coding.ts — sent in OAuth + API headers.
# Leave unset to use the captured defaults baked into the OmniRoute build.
#KIMI_CLI_VERSION=1.36.0
#KIMI_CODING_DEVICE_ID=
# ═══════════════════════════════════════════════════════════════════════════════
# 14. API KEY PROVIDERS
@@ -509,20 +608,20 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
# Preferred setup: Dashboard → Providers → Add API Key.
# Setting here is an alternative for Docker/headless deployments.
# Static API keys for direct-authentication providers wired through the runtime.
# OmniRoute loads provider credentials from the encrypted database or
# data/provider-credentials.json. The variables below are documented escape
# hatches that are referenced in code today.
# DEEPSEEK_API_KEY=
# GROQ_API_KEY=
# XAI_API_KEY=
# MISTRAL_API_KEY=
# PERPLEXITY_API_KEY=
# TOGETHER_API_KEY=
# FIREWORKS_API_KEY=
# CEREBRAS_API_KEY=
# COHERE_API_KEY=
# NVIDIA_API_KEY=
# Windsurf / Devin CLI direct API key.
# Used by: open-sse/executors/devin-cli.ts — bypasses OAuth when set.
# WINDSURF_API_KEY=
# Embedding Providers (optional — used by /v1/embeddings)
# NEBIUS_API_KEY=
# Provider keys above (OpenAI, Mistral, Together, Fireworks, NVIDIA) also work for embeddings.
# OpenAI/Mistral/Together/Fireworks/NVIDIA configured via Dashboard → Providers
# also work for embeddings.
# ═══════════════════════════════════════════════════════════════════════════════
@@ -546,6 +645,29 @@ GEMINI_CLI_USER_AGENT="google-api-nodejs-client/10.3.0"
# FETCH_CONNECT_TIMEOUT_MS=30000 # TCP connection establishment (default: 30s)
# FETCH_KEEPALIVE_TIMEOUT_MS=4000 # Keep-alive socket idle timeout (default: 4s)
# Default timeout (ms) for src/shared/utils/fetchTimeout.ts. Acts as the
# fallback when FETCH_TIMEOUT_MS is unset. Default: 120000 (2 min).
# OMNIROUTE_DEFAULT_FETCH_TIMEOUT_MS=120000
# ── ChatGPT TLS sidecar (Firefox-fingerprinted client) ──
# Used by: open-sse/services/chatgptTlsClient.ts — wire-level timeout for
# the bogdanfinn/tls-client koffi binding and the JS-side grace window
# layered on top of it when the native library is wedged.
# OMNIROUTE_CHATGPT_TLS_TIMEOUT_MS=60000
# OMNIROUTE_CHATGPT_TLS_GRACE_MS=10000
# ── Circuit breaker thresholds and reset windows ──
# Used by: open-sse/config/constants.ts → src/lib/resilience/settings.ts.
# Defaults match historical PROVIDER_PROFILES values (post-scaling for
# 500+ connections). Lower the threshold to react faster, raise it to
# tolerate more transient failures before short-circuiting.
# OMNIROUTE_CIRCUIT_BREAKER_OAUTH_THRESHOLD=8
# OMNIROUTE_CIRCUIT_BREAKER_OAUTH_RESET_MS=60000
# OMNIROUTE_CIRCUIT_BREAKER_API_KEY_THRESHOLD=12
# OMNIROUTE_CIRCUIT_BREAKER_API_KEY_RESET_MS=30000
# OMNIROUTE_CIRCUIT_BREAKER_LOCAL_THRESHOLD=2
# OMNIROUTE_CIRCUIT_BREAKER_LOCAL_RESET_MS=15000
# ── Stream idle detection ──
# STREAM_IDLE_TIMEOUT_MS=600000 # Max silence between SSE chunks (default: 600000)
# # Extended-thinking models rarely pause >90s.
@@ -718,6 +840,13 @@ APP_LOG_TO_FILE=true
# NANOBANANA_POLL_TIMEOUT_MS=120000 # Max wait for job completion (default: 120s)
# NANOBANANA_POLL_INTERVAL_MS=2500 # Poll frequency (default: 2.5s)
# ── AWS Bedrock (Kiro / Audio) ──
# Region used to construct AWS Bedrock endpoints. Used by:
# src/lib/providers/validation.ts and open-sse/handlers/audioSpeech.ts.
# AWS_REGION takes precedence over AWS_DEFAULT_REGION when both are set.
# AWS_REGION=us-east-1
# AWS_DEFAULT_REGION=us-east-1
# ── Cloudflare Workers AI ──
# Account ID override for Cloudflare Workers AI executor.
# Used by: open-sse/executors/cloudflare-ai.ts
@@ -777,21 +906,49 @@ APP_LOG_TO_FILE=true
# Used by: open-sse/services/rateLimitManager.ts
# RATE_LIMIT_MAX_WAIT_MS=120000
# Force the auto-enable rate limit safety net on/off regardless of the persisted
# Dashboard setting. Used by: open-sse/services/rateLimitManager.ts.
# Accepted values: true|1|on (force on), false|0|off (force off), unset (use Dashboard).
# RATE_LIMIT_AUTO_ENABLE=
# Stagger interval (ms) between provider token healthchecks at startup.
# Used by: src/lib/tokenHealthCheck.ts. Default: 3000.
# HEALTHCHECK_STAGGER_MS=3000
# ═══════════════════════════════════════════════════════════════════════════════
# 22. DEBUGGING
# ═══════════════════════════════════════════════════════════════════════════════
# These variables enable verbose debugging output. NEVER enable in production.
# Dump Cursor protobuf decode/encode details to console.
# CURSOR_PROTOBUF_DEBUG=1
# Dump raw Cursor SSE stream data to console.
# Cursor executor verbose debug (decoded SSE chunks, etc.).
# CURSOR_STREAM_DEBUG is kept as a backward-compatible alias.
# Used by: open-sse/executors/cursor.ts
# CURSOR_DEBUG=1
# CURSOR_STREAM_DEBUG=1
# When CURSOR_DEBUG=1, also append raw decoded chunks to this file path.
# CURSOR_DUMP_FILE=/tmp/cursor-stream.log
# Cursor stream idle timeout (ms). Default: 300000 (5 min).
# Used by: open-sse/executors/cursor.ts.
# CURSOR_STREAM_TIMEOUT_MS=300000
# Cursor state DB path override (for cursor version detection).
# Used by: open-sse/utils/cursorVersionDetector.ts. Default: probed automatically.
# CURSOR_STATE_DB_PATH=
# Direct Cursor bearer token used by scripts/cursor-tap.cjs (developer tooling).
# CURSOR_TOKEN=
# Log Responses API SSE-to-JSON translation details.
# DEBUG_RESPONSES_SSE_TO_JSON=true
# Log request shape (content-type + content-length) for large chat payloads.
# Used by: src/app/api/v1/chat/completions/route.ts. Set to "0" to silence.
# Default: enabled.
# OMNIROUTE_LOG_REQUEST_SHAPE=1
# Write raw (untruncated) request/response JSON in call log artifacts.
# When enabled, serializeArtifactForStorage skips size-based truncation.
# Also enabled automatically when APP_LOG_LEVEL=debug.
@@ -813,3 +970,128 @@ APP_LOG_TO_FILE=true
# GitHub Personal Access Token with issues:write scope.
# GITHUB_ISSUES_TOKEN=ghp_xxxx
# Generic GitHub access token consumed by issue triage / agent helpers.
# Used by: src/app/api/v1/issues/* and src/lib/cloudAgent/* — falls back to
# GITHUB_ISSUES_TOKEN when unset.
# GITHUB_TOKEN=
# ═══════════════════════════════════════════════════════════════════════════════
# 24. PROVIDER QUOTAS, TUNNELS & SANDBOXED SKILLS
# ═══════════════════════════════════════════════════════════════════════════════
# Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug
# proxy), 1Proxy egress pool, skills sandbox runtime, and miscellaneous CLI
# binaries referenced by the executor layer or the dashboard runtime.
# ── Alibaba (Bailian) coding plan quota ──
# Host/full URL override used by: open-sse/services/bailianQuotaFetcher.ts.
# When unset the fetcher uses the production Alibaba endpoints.
# ALIBABA_CODING_PLAN_HOST=
# ALIBABA_CODING_PLAN_QUOTA_URL=
# ── Context window tuning ──
# Tokens reserved for completion output when computing prompt budgets.
# Used by: open-sse/services/contextManager.ts. Default: 1024.
# CONTEXT_RESERVE_TOKENS=1024
# ── Model alias rewriting (legacy compatibility) ──
# Toggle the legacy model-alias compatibility layer used by older clients.
# Used by: open-sse/services/model.ts. Default: enabled.
# MODEL_ALIAS_COMPAT_ENABLED=true
# ── Devin CLI binary path ──
# Used by: open-sse/executors/devin-cli.ts. Default: looked up via PATH.
# CLI_DEVIN_BIN=devin
# ── Command Code (custom CLI) callback ──
# Local port used for OAuth-style callbacks from the Command Code CLI helper.
# Used by: src/app/api/providers/command-code/auth/shared.ts.
# COMMAND_CODE_CALLBACK_PORT=
# ── MITM debug proxy (development only) ──
# Used by: src/mitm/server.cjs — captures upstream traffic for inspection.
# MITM_LOCAL_PORT=443
# MITM_DISABLE_TLS_VERIFY=0
# ── 1Proxy egress pool ──
# Used by: src/lib/oneproxySync.ts — fetches proxy nodes from the OmniRoute
# CrofAI 1Proxy service. Disable, override URL, or tune the import quality.
# ONEPROXY_ENABLED=true
# ONEPROXY_API_URL=https://1proxy-api.aitradepulse.com
# ONEPROXY_MAX_PROXIES=500
# ONEPROXY_MIN_QUALITY_THRESHOLD=50
# ── Tailscale tunnel binaries ──
# Optional explicit paths to tailscale/tailscaled binaries used by the
# dashboard's tunnel manager. Used by: src/lib/tailscaleTunnel.ts.
# TAILSCALE_BIN=/usr/local/bin/tailscale
# TAILSCALED_BIN=/usr/local/bin/tailscaled
# ── Ngrok tunnel ──
# Used by: src/lib/ngrokTunnel.ts — authenticates outbound tunnels.
# NGROK_AUTHTOKEN=
# ── Database backups ──
# Used by: src/lib/db/backup.ts.
# DB_BACKUP_MAX_FILES=20
# DB_BACKUP_RETENTION_DAYS=0
# ── TLS sidecar override ──
# Used by: open-sse/services/chatgptTlsClient.ts tests. Production deployments
# should leave this unset; the sidecar is auto-managed.
# OMNIROUTE_TLS_PROXY_URL=
# ── Skills sandbox (experimental) ──
# Used by: src/lib/skills/builtins.ts. All values support comma lists where
# noted in the source.
# SKILLS_MAX_FILE_BYTES=1048576
# SKILLS_MAX_HTTP_RESPONSE_BYTES=256000
# SKILLS_MAX_SANDBOX_OUTPUT_CHARS=100000
# SKILLS_SANDBOX_TIMEOUT_MS=10000
# SKILLS_SANDBOX_NETWORK_ENABLED=0
# SKILLS_ALLOWED_SANDBOX_IMAGES=
# ═══════════════════════════════════════════════════════════════════════════════
# 25. TEST & E2E
# ═══════════════════════════════════════════════════════════════════════════════
# Used by scripts/run-next-playwright.mjs, scripts/smoke-electron-packaged.mjs,
# scripts/run-ecosystem-tests.mjs and scripts/uninstall.mjs.
# Production deployments should leave every value below unset.
# E2E bootstrap mode for the Playwright runner. Accepted: auth | fresh | reuse.
# Default (when unset): auth.
# OMNIROUTE_E2E_BOOTSTRAP_MODE=auth
# Admin password injected into the Playwright test environment.
# Falls back to INITIAL_PASSWORD when unset.
# OMNIROUTE_E2E_PASSWORD=
# Disable the local healthcheck poll during Playwright runs (default: true).
# OMNIROUTE_DISABLE_LOCAL_HEALTHCHECK=true
# Disable the OAuth token healthcheck loop during tests (default: true).
# OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK=true
# Silence healthcheck noise in Playwright stdout (default: true).
# OMNIROUTE_HIDE_HEALTHCHECK_LOGS=true
# Skip the Next.js production build before Playwright starts (CI optimization).
# OMNIROUTE_PLAYWRIGHT_SKIP_BUILD=0
# Skip the OmniRoute uninstall hook (used by CI to keep node_modules intact).
# OMNIROUTE_SKIP_UNINSTALL_HOOK=0
# Ecosystem/protocol test orchestrators wait this long (ms) for the server to
# become healthy. Default: 180000.
# ECOSYSTEM_SERVER_WAIT_MS=180000
# Electron smoke harness (used by scripts/smoke-electron-packaged.mjs).
# ELECTRON_SMOKE_URL=http://127.0.0.1:20128/login
# ELECTRON_SMOKE_TIMEOUT_MS=45000
# ELECTRON_SMOKE_SETTLE_MS=2000
# ELECTRON_SMOKE_APP_EXECUTABLE=
# ELECTRON_SMOKE_DATA_DIR=
# ELECTRON_SMOKE_KEEP_DATA=0
# ELECTRON_SMOKE_STREAM_LOGS=0

View File

@@ -3,6 +3,12 @@
> Complete reference for every environment variable recognized by OmniRoute.
> For a quick-start template, see [`.env.example`](../.env.example).
> [!IMPORTANT]
> Every variable documented here must also appear in `.env.example`, and
> every variable in `.env.example` must appear here. `npm run check:env-doc-sync`
> enforces this on commit and in CI. To omit a variable on purpose, add it to
> the allowlist inside `scripts/check-env-doc-sync.mjs`.
---
## Table of Contents
@@ -93,17 +99,19 @@ OmniRoute uses **SQLite** (via `better-sqlite3`) for all persistence. These vari
## 3. Network & Ports
| Variable | Default | Source File | Description |
| ------------------------- | ------------------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). |
| `API_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the `/v1/*` proxy API on this separate port. |
| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | Bind address for the API port. |
| `DASHBOARD_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the Dashboard UI on this separate port. |
| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Host-side published port for the Dashboard in Docker production mode. |
| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Host-side published port for the API in Docker production mode. |
| `OMNIROUTE_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | Takes precedence over `PORT` when running inside Electron or other wrappers. |
| `NODE_ENV` | `production` | Next.js core | Controls logging verbosity, caching, error detail exposure, and Next.js optimizations. |
| `OMNIROUTE_USE_TURBOPACK` | `1` (default in `.env.example`) | `package.json` / Next.js 16 | Toggles the Next.js 16 Turbopack bundler in `npm run dev` and `npm run build`. Set to `0` on Windows or when running into native binding incompatibilities. |
| Variable | Default | Source File | Description |
| ------------------------- | ------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PORT` | `20128` | `src/lib/runtime/ports.ts` | Primary port for both Dashboard UI and API endpoints (single-port mode). |
| `API_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the `/v1/*` proxy API on this separate port. |
| `API_HOST` | `0.0.0.0` | `src/lib/runtime/ports.ts` | Bind address for the API port. |
| `DASHBOARD_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | When set, serves the Dashboard UI on this separate port. |
| `PROD_DASHBOARD_PORT` | `20130` | `docker-compose.prod.yml` | Host-side published port for the Dashboard in Docker production mode. |
| `PROD_API_PORT` | `20131` | `docker-compose.prod.yml` | Host-side published port for the API in Docker production mode. |
| `OMNIROUTE_PORT` | _(unset)_ | `src/lib/runtime/ports.ts` | Takes precedence over `PORT` when running inside Electron or other wrappers. |
| `NODE_ENV` | `production` | Next.js core | Controls logging verbosity, caching, error detail exposure, and Next.js optimizations. |
| `OMNIROUTE_USE_TURBOPACK` | `1` (default in `.env.example`) | `package.json` / Next.js 16 | Toggles the Next.js 16 Turbopack bundler in `npm run dev` and `npm run build`. Set to `0` on Windows or when running into native binding incompatibilities. |
| `HOST` | `0.0.0.0` | `scripts/run-next.mjs` | Bind address for the Next.js dev/start server. Overrides the default `0.0.0.0` when set. |
| `HOSTNAME` | `127.0.0.1` | `scripts/run-next-playwright.mjs` | Bind address used by the Playwright runner when launching Next.js. Defaults to `127.0.0.1` for hermetic tests. |
### Port Modes
@@ -190,25 +198,33 @@ OmniRoute provides a two-layer defense: request-side injection scanning and resp
## 6. Tool & Routing Policies
| Variable | Default | Source File | Description |
| ------------------ | ---------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `TOOL_POLICY_MODE` | `disabled` | `src/lib/toolPolicy.ts` | Controls LLM tool/function-calling access. `allowlist` = only listed tools, `denylist` = all except listed, `disabled` = no restrictions. |
| Variable | Default | Source File | Description |
| ----------------------------------- | ---------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `TOOL_POLICY_MODE` | `disabled` | `src/lib/toolPolicy.ts` | Controls LLM tool/function-calling access. `allowlist` = only listed tools, `denylist` = all except listed, `disabled` = no restrictions. |
| `OMNIROUTE_PAYLOAD_RULES_PATH` | `./config/payloadRules.json` | `open-sse/services/payloadRules.ts` | Path to payload manipulation rules JSON file (per-model/protocol upstream tweaks). |
| `OMNIROUTE_PAYLOAD_RULES_RELOAD_MS` | `5000` | `open-sse/services/payloadRules.ts` | Reload interval (ms) for hot-reloading the payload rules file. Minimum `1000`. |
---
## 7. URLs & Cloud Sync
| Variable | Default | Source File | Description |
| --------------------------------------- | ------------------------ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BASE_URL` | `http://localhost:20128` | `src/lib/cloudSync.ts` | Server-side URL for internal sync jobs to call `/api/sync/cloud`. |
| `CLOUD_URL` | _(empty)_ | `src/lib/cloudSync.ts` | Cloud relay endpoint URL (premium feature). |
| `CLOUD_SYNC_TIMEOUT_MS` | `12000` | `src/lib/cloudSync.ts` | HTTP timeout for cloud sync requests. |
| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth, Dashboard, sync | Public-facing URL for OAuth redirect_uri, Dashboard links. **Must match your public URL behind reverse proxy.** |
| `NEXT_PUBLIC_CLOUD_URL` | _(empty)_ | Client-side | Client-side mirror of `CLOUD_URL`. |
| `NEXT_PUBLIC_APP_URL` | _(unset)_ | `src/shared/services/cloudSyncScheduler.ts` | Legacy fallback for `NEXT_PUBLIC_BASE_URL`. |
| `OMNIROUTE_PUBLIC_BASE_URL` | _(unset)_ | `open-sse/executors/chatgpt-web.ts` | Browser-facing OmniRoute origin used for image URLs in API responses (e.g., `/v1/chatgpt-web/image/<id>`). Set this when OpenWebUI or another relay reaches OmniRoute by an internal URL but the user's browser must fetch images from a LAN, tunnel, or public origin. Do **not** include `/v1`. |
| `OMNIROUTE_CGPT_WEB_IMAGE_TIMEOUT_MS` | `180000` (3 min) | `open-sse/executors/chatgpt-web.ts` | Max wait time for an async chatgpt-web image to land via the celsius WebSocket. Increase during upstream queue-deep windows. |
| `OMNIROUTE_CGPT_WEB_IMAGE_CACHE_MAX_MB` | `256` | `open-sse/services/chatgptImageCache.ts` | Total in-memory byte budget (MB) for the chatgpt-web image cache serving `/v1/chatgpt-web/image/<id>`. Lower on memory-constrained hosts; raise if image generation is heavy and clients race the 30-minute TTL. |
| Variable | Default | Source File | Description |
| --------------------------------------- | --------------------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BASE_URL` | `http://localhost:20128` | `src/lib/cloudSync.ts` | Server-side URL for internal sync jobs to call `/api/sync/cloud`. |
| `CLOUD_URL` | _(empty)_ | `src/lib/cloudSync.ts` | Cloud relay endpoint URL (premium feature). |
| `CLOUD_SYNC_TIMEOUT_MS` | `12000` | `src/lib/cloudSync.ts` | HTTP timeout for cloud sync requests. |
| `NEXT_PUBLIC_BASE_URL` | `http://localhost:20128` | OAuth, Dashboard, sync | Public-facing URL for OAuth redirect_uri, Dashboard links. **Must match your public URL behind reverse proxy.** |
| `NEXT_PUBLIC_CLOUD_URL` | _(empty)_ | Client-side | Client-side mirror of `CLOUD_URL`. |
| `NEXT_PUBLIC_APP_URL` | _(unset)_ | `src/shared/services/cloudSyncScheduler.ts` | Legacy fallback for `NEXT_PUBLIC_BASE_URL`. |
| `OMNIROUTE_PUBLIC_BASE_URL` | _(unset)_ | `open-sse/executors/chatgpt-web.ts` | Browser-facing OmniRoute origin used for image URLs in API responses (e.g., `/v1/chatgpt-web/image/<id>`). Set this when OpenWebUI or another relay reaches OmniRoute by an internal URL but the user's browser must fetch images from a LAN, tunnel, or public origin. Do **not** include `/v1`. |
| `OMNIROUTE_CGPT_WEB_IMAGE_TIMEOUT_MS` | `180000` (3 min) | `open-sse/executors/chatgpt-web.ts` | Max wait time for an async chatgpt-web image to land via the celsius WebSocket. Increase during upstream queue-deep windows. |
| `OMNIROUTE_CGPT_WEB_IMAGE_CACHE_MAX_MB` | `256` | `open-sse/services/chatgptImageCache.ts` | Total in-memory byte budget (MB) for the chatgpt-web image cache serving `/v1/chatgpt-web/image/<id>`. Lower on memory-constrained hosts; raise if image generation is heavy and clients race the 30-minute TTL. |
| `KIE_CALLBACK_URL` | _(unset)_ | `open-sse/utils/kieTask.ts` | Public callback URL for asynchronous kie.ai jobs. Highest-priority override before `OMNIROUTE_KIE_CALLBACK_URL` and `OMNIROUTE_PUBLIC_URL`. |
| `OMNIROUTE_KIE_CALLBACK_URL` | _(unset)_ | `open-sse/utils/kieTask.ts` | Alternate spelling of `KIE_CALLBACK_URL`. Falls back when the primary variable is unset. |
| `OMNIROUTE_PUBLIC_URL` | _(unset)_ | `open-sse/utils/kieTask.ts` | Public origin used to compose async callback URLs. Lowest-priority fallback for kie.ai callbacks; also used as a generic public URL for other relays. |
| `OMNIROUTE_CROF_USAGE_URL` | `https://crof.ai/usage_api/` | `open-sse/services/usage.ts` | CrofAI quota lookup endpoint used by the Usage page. Override for relays / test fixtures. |
| `OMNIROUTE_GEMINI_CLI_USAGE_URL` | `https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist` | `open-sse/services/usage.ts` | Gemini CLI quota lookup endpoint. Override for relays / test fixtures. |
| `OMNIROUTE_CODEWHISPERER_BASE_URL` | `https://codewhisperer.us-east-1.amazonaws.com` | `open-sse/services/usage.ts` | CodeWhisperer (AWS Kiro) usage limits endpoint. Override for relays / test fixtures. |
> [!IMPORTANT]
> When deploying behind a reverse proxy (nginx, Caddy), `NEXT_PUBLIC_BASE_URL` **must** be set to your public URL (e.g., `https://omniroute.example.com`). Without this, OAuth callbacks will fail because the redirect_uri won't match.
@@ -257,6 +273,7 @@ Controls how OmniRoute discovers and launches CLI sidecars (Claude Code, Codex,
| `CLI_CLINE_BIN` | `cline` | `src/shared/services/cliRuntime.ts` | Custom path to Cline CLI binary. |
| `CLI_CONTINUE_BIN` | `cn` | `src/shared/services/cliRuntime.ts` | Custom path to Continue CLI binary. |
| `CLI_QODER_BIN` | `qoder` | `src/shared/services/cliRuntime.ts` | Custom path to Qoder CLI binary. |
| `CLI_QWEN_BIN` | `qwen` | `src/shared/services/cliRuntime.ts` | Custom path to the Qwen Code CLI binary. |
| `CLI_DEVIN_BIN` | `devin` | `open-sse/executors/devin-cli.ts` | Custom path to the Devin CLI binary (v3.8.0). Used by the Windsurf/Devin executor. |
### Docker Example
@@ -273,19 +290,28 @@ CLI_CLAUDE_BIN=/host-cli/bin/claude
## 10. Internal Agent & MCP Integrations
| Variable | Default | Source File | Description |
| --------------------------------------- | ----------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `OMNIROUTE_BASE_URL` | auto-detect | `open-sse/mcp-server/server.ts` | Explicit URL for MCP/A2A tools to reach OmniRoute. Overrides localhost auto-detection. |
| `OMNIROUTE_API_KEY` | _(unset)_ | MCP/A2A modules | API key for internal MCP tool and A2A skill calls. |
| `OMNIROUTE_API_KEY_ID` | _(unset)_ | `open-sse/mcp-server/audit.ts` | Key ID for MCP audit log attribution. |
| `ROUTER_API_KEY` | _(unset)_ | Legacy | Legacy alias for `OMNIROUTE_API_KEY`. |
| `OMNIROUTE_MCP_ENFORCE_SCOPES` | `false` | `open-sse/mcp-server/server.ts` | Enforce scope-based access control on MCP tool calls. |
| `OMNIROUTE_MCP_SCOPES` | _(all)_ | `open-sse/mcp-server/server.ts` | Comma-separated scopes: `admin`, `combos`, `health`, `models`, `routing`, `budget`, `metrics`, `pricing`, `memory`, `skills`. |
| `MODEL_SYNC_INTERVAL_HOURS` | `24` | `src/shared/services/modelSyncScheduler.ts` | Model catalog sync interval in hours. |
| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | `src/server-init.ts` | Provider rate-limit and quota polling interval. |
| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | `false` | `src/instrumentation-node.ts` | Disable all background services (sync, pricing, model refresh). Useful for CI/test. |
| `OMNIROUTE_BOOTSTRAPPED` | `false` | `src/app/(dashboard)/dashboard/page.tsx` | Set `true` by bootstrap script after initial setup. Controls setup wizard visibility. |
| `OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE` | `0` | `open-sse/executors/antigravity.ts` | Escape hatch: allow request body to override the Antigravity project field. |
| Variable | Default | Source File | Description |
| ----------------------------------------------- | ----------- | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `OMNIROUTE_BASE_URL` | auto-detect | `open-sse/mcp-server/server.ts` | Explicit URL for MCP/A2A tools to reach OmniRoute. Overrides localhost auto-detection. |
| `OMNIROUTE_API_KEY` | _(unset)_ | MCP/A2A modules | API key for internal MCP tool and A2A skill calls. |
| `OMNIROUTE_API_KEY_ID` | _(unset)_ | `open-sse/mcp-server/audit.ts` | Key ID for MCP audit log attribution. |
| `ROUTER_API_KEY` | _(unset)_ | Legacy | Legacy alias for `OMNIROUTE_API_KEY`. |
| `OMNIROUTE_MCP_ENFORCE_SCOPES` | `false` | `open-sse/mcp-server/server.ts` | Enforce scope-based access control on MCP tool calls. |
| `OMNIROUTE_MCP_SCOPES` | _(all)_ | `open-sse/mcp-server/server.ts` | Comma-separated scopes: `admin`, `combos`, `health`, `models`, `routing`, `budget`, `metrics`, `pricing`, `memory`, `skills`. |
| `OMNIROUTE_MCP_COMPRESS_DESCRIPTIONS` | enabled | `open-sse/mcp-server/descriptionCompressor.ts` | Compress MCP tool descriptions before serializing the manifest. Disable values: `0`, `false`, `off`. |
| `OMNIROUTE_MCP_DESCRIPTION_COMPRESSION` | `rtk` | `open-sse/mcp-server/descriptionCompressor.ts` | Compression algorithm/profile. Disable values: `0`, `false`, `off`. |
| `MODEL_SYNC_INTERVAL_HOURS` | `24` | `src/shared/services/modelSyncScheduler.ts` | Model catalog sync interval in hours. |
| `PROVIDER_LIMITS_SYNC_INTERVAL_MINUTES` | `70` | `src/server-init.ts` | Provider rate-limit and quota polling interval. |
| `OMNIROUTE_DISABLE_BACKGROUND_SERVICES` | `false` | `src/instrumentation-node.ts` | Disable all background services (sync, pricing, model refresh). Useful for CI/test. |
| `OMNIROUTE_ENABLE_RUNTIME_BACKGROUND_TASKS` | _(unset)_ | `src/lib/config/runtimeSettings.ts` | Force background tasks on under automated test detection. Set `1` to override the test heuristic. |
| `OMNIROUTE_BUDGET_RESET_JOB_INTERVAL_MS` | `600000` | `src/lib/jobs/budgetResetJob.ts` | Budget reset check cadence (ms). Floor `10000`. |
| `OMNIROUTE_REASONING_CACHE_CLEANUP_INTERVAL_MS` | `1800000` | `src/lib/jobs/reasoningCacheCleanupJob.ts` | Reasoning cache cleanup cadence (ms). Floor `60000`. |
| `OMNIROUTE_CONFIG_HOT_RELOAD_MS` | `5000` | `src/lib/config/hotReload.ts` | Polling interval (ms) for config hot-reload. Lower than `1000` is rejected. |
| `OMNIROUTE_DISABLE_REDIS_AUTH_CACHE` | _(enabled)_ | `src/lib/db/apiKeys.ts` | Set `1` to bypass the Redis-backed API-key auth cache (forces DB reads). |
| `OMNIROUTE_RTK_TRUST_PROJECT_FILTERS` | `0` | `open-sse/services/compression/engines/rtk/filterLoader.ts` | Trust user-managed RTK project filter rules without strict signature checks. |
| `OMNIROUTE_BOOTSTRAPPED` | `false` | `src/app/(dashboard)/dashboard/page.tsx` | Set `true` by bootstrap script after initial setup. Controls setup wizard visibility. |
| `OMNIROUTE_ALLOW_BODY_PROJECT_OVERRIDE` | `0` | `open-sse/executors/antigravity.ts` | Escape hatch: allow request body to override the Antigravity project field. |
| `ANTIGRAVITY_CREDITS` | _(unset)_ | `open-sse/services/antigravityCredits.ts` | Override Antigravity's advertised remaining credits (testing / forced values). |
### OAuth CLI Bridge (Internal)
@@ -324,6 +350,9 @@ Built-in credentials for **localhost development**. For remote deployments, regi
| `GITLAB_DUO_OAUTH_CLIENT_ID` | GitLab Duo (v3.8) | OAuth client ID for GitLab Duo. Register an app at `https://gitlab.com/-/profile/applications` with redirect URI `<NEXT_PUBLIC_BASE_URL>/callback` and scopes `api, read_user, openid, profile, email`. Falls back to `GITLAB_OAUTH_CLIENT_ID`. |
| `GITLAB_DUO_OAUTH_CLIENT_SECRET` | GitLab Duo (v3.8) | OAuth client secret for GitLab Duo. Optional — PKCE flow does not require a secret. Falls back to `GITLAB_OAUTH_CLIENT_SECRET`. |
| `GITLAB_DUO_BASE_URL` | GitLab Duo (v3.8) | Override GitLab base URL (self-hosted GitLab). Defaults to `https://gitlab.com`. Falls back to `GITLAB_BASE_URL`. |
| `GITLAB_BASE_URL` | GitLab Duo (v3.8) | Legacy fallback for `GITLAB_DUO_BASE_URL`. Used when the `_DUO_` variant is unset. |
| `GITLAB_OAUTH_CLIENT_ID` | GitLab Duo (v3.8) | Legacy fallback for `GITLAB_DUO_OAUTH_CLIENT_ID` consumed by `src/lib/oauth/constants/oauth.ts`. |
| `GITLAB_OAUTH_CLIENT_SECRET` | GitLab Duo (v3.8) | Legacy fallback for `GITLAB_DUO_OAUTH_CLIENT_SECRET` consumed by `src/lib/oauth/constants/oauth.ts`. |
| `QODER_OAUTH_CLIENT_SECRET` | Qoder | — |
| `QODER_OAUTH_AUTHORIZE_URL` | Qoder | Set to enable Qoder OAuth. |
| `QODER_OAUTH_TOKEN_URL` | Qoder | — |
@@ -379,24 +408,30 @@ When enabled, OmniRoute reorders HTTP headers and JSON body fields to match the
### Per-Provider
| Variable | Effect |
| -------------------------- | --------------------------------------- |
| `CLI_COMPAT_CODEX=1` | Mimics Codex CLI request signature |
| `CLI_COMPAT_CLAUDE=1` | Mimics Claude Code request signature |
| `CLI_COMPAT_GITHUB=1` | Mimics GitHub Copilot request signature |
| `CLI_COMPAT_ANTIGRAVITY=1` | Mimics Antigravity request signature |
| `CLI_COMPAT_KIRO=1` | Mimics Kiro IDE request signature |
| `CLI_COMPAT_CURSOR=1` | Mimics Cursor request signature |
| `CLI_COMPAT_KIMI_CODING=1` | Mimics Kimi Coding request signature |
| `CLI_COMPAT_KILOCODE=1` | Mimics Kilo Code request signature |
| `CLI_COMPAT_CLINE=1` | Mimics Cline request signature |
| `CLI_COMPAT_QWEN=1` | Mimics Qwen Code request signature |
| Variable | Activation | Effect |
| ------------------------ | ---------- | --------------------------------------- |
| `CLI_COMPAT_CODEX` | `=1` | Mimics Codex CLI request signature |
| `CLI_COMPAT_CLAUDE` | `=1` | Mimics Claude Code request signature |
| `CLI_COMPAT_GITHUB` | `=1` | Mimics GitHub Copilot request signature |
| `CLI_COMPAT_ANTIGRAVITY` | `=1` | Mimics Antigravity request signature |
| `CLI_COMPAT_CURSOR` | `=1` | Mimics Cursor request signature |
| `CLI_COMPAT_KIMI_CODING` | `=1` | Mimics Kimi Coding request signature |
| `CLI_COMPAT_KILOCODE` | `=1` | Mimics Kilo Code request signature |
| `CLI_COMPAT_CLINE` | `=1` | Mimics Cline request signature |
| `CLI_COMPAT_QWEN` | `=1` | Mimics Qwen Code request signature |
### Global
| Variable | Effect |
| ------------------ | --------------------------------------------------------------- |
| `CLI_COMPAT_ALL=1` | Enable fingerprint compatibility for **all** providers at once. |
| Variable | Activation | Effect |
| ---------------- | ---------- | --------------------------------------------------------------- |
| `CLI_COMPAT_ALL` | `=1` | Enable fingerprint compatibility for **all** providers at once. |
### Kimi Coding CLI identity overrides
| Variable | Default | Source File | Description |
| ----------------------- | -------------------- | ---------------------------------------- | ------------------------------------------------------------ |
| `KIMI_CLI_VERSION` | `1.36.0` | `src/lib/oauth/providers/kimi-coding.ts` | Override the Kimi CLI version sent during OAuth/API calls. |
| `KIMI_CODING_DEVICE_ID` | _(captured default)_ | `src/lib/oauth/providers/kimi-coding.ts` | Override the captured Kimi device ID used in client headers. |
> [!NOTE]
> This feature works alongside the User-Agent overrides (§12). The fingerprint system handles header ordering and body field ordering, while User-Agent overrides handle the specific UA string. Both can be enabled independently.
@@ -411,20 +446,13 @@ Setting via environment variables is an alternative for Docker or headless deplo
Recognized pattern: `{PROVIDER_ID}_API_KEY`
| Variable | Provider |
| -------------------- | ------------------- |
| `DEEPSEEK_API_KEY` | DeepSeek |
| `GROQ_API_KEY` | Groq |
| `XAI_API_KEY` | xAI (Grok) |
| `MISTRAL_API_KEY` | Mistral AI |
| `PERPLEXITY_API_KEY` | Perplexity |
| `TOGETHER_API_KEY` | Together AI |
| `FIREWORKS_API_KEY` | Fireworks AI |
| `CEREBRAS_API_KEY` | Cerebras |
| `COHERE_API_KEY` | Cohere |
| `NVIDIA_API_KEY` | NVIDIA NIM |
| `NEBIUS_API_KEY` | Nebius (embeddings) |
| `QIANFAN_API_KEY` | Baidu Qianfan |
| Variable | Provider |
| ------------------ | ---------- |
| `DEEPSEEK_API_KEY` | DeepSeek |
| `NVIDIA_API_KEY` | NVIDIA NIM |
> [!NOTE]
> Static `${PROVIDER}_API_KEY` entries for Groq, xAI, Mistral, Perplexity, Together AI, Fireworks, Cerebras, Cohere, Nebius, and Qianfan were removed in v3.8.0 because the runtime no longer reads them — those providers rely exclusively on Dashboard / `data/provider-credentials.json` / the encrypted DB. See the _Audit: Removed / Dead Variables_ section at the bottom of this document for the migration path.
> [!TIP]
> Keys set via the Dashboard are stored encrypted in SQLite and take precedence over environment variables.
@@ -469,6 +497,22 @@ REQUEST_TIMEOUT_MS (global override)
| `API_BRIDGE_SERVER_KEEPALIVE_TIMEOUT_MS` | `5000` | Bridge keep-alive idle timeout. |
| `API_BRIDGE_SERVER_SOCKET_TIMEOUT_MS` | `0` | Raw socket timeout (0 = disabled). |
| `SHUTDOWN_TIMEOUT_MS` | `30000` | Grace period on SIGTERM/SIGINT before force-exit. |
| `OMNIROUTE_DEFAULT_FETCH_TIMEOUT_MS` | `120000` | Fallback used by `src/shared/utils/fetchTimeout.ts` when `FETCH_TIMEOUT_MS` is unset. |
| `OMNIROUTE_CHATGPT_TLS_TIMEOUT_MS` | `60000` | Wire-level timeout for the bogdanfinn/tls-client koffi binding (`chatgptTlsClient.ts`). |
| `OMNIROUTE_CHATGPT_TLS_GRACE_MS` | `10000` | JS-side grace added on top of the wire timeout when the native binding is wedged. |
### Circuit Breaker Thresholds
Provider-level circuit breaker tuning. Defaults reflect the scaled values used since v3.6 for 500+ connections.
| Variable | Default | Source File | Description |
| --------------------------------------------- | ------- | ------------------------------ | --------------------------------------------------------------------------- |
| `OMNIROUTE_CIRCUIT_BREAKER_OAUTH_THRESHOLD` | `8` | `open-sse/config/constants.ts` | Consecutive failure threshold for OAuth providers before the breaker trips. |
| `OMNIROUTE_CIRCUIT_BREAKER_OAUTH_RESET_MS` | `60000` | `open-sse/config/constants.ts` | Reset window (ms) for OAuth provider breaker. |
| `OMNIROUTE_CIRCUIT_BREAKER_API_KEY_THRESHOLD` | `12` | `open-sse/config/constants.ts` | Consecutive failure threshold for API-key providers. |
| `OMNIROUTE_CIRCUIT_BREAKER_API_KEY_RESET_MS` | `30000` | `open-sse/config/constants.ts` | Reset window (ms) for API-key provider breaker. |
| `OMNIROUTE_CIRCUIT_BREAKER_LOCAL_THRESHOLD` | `2` | `open-sse/config/constants.ts` | Consecutive failure threshold for local providers (Ollama, LM Studio, ...). |
| `OMNIROUTE_CIRCUIT_BREAKER_LOCAL_RESET_MS` | `15000` | `open-sse/config/constants.ts` | Reset window (ms) for local provider breaker. |
### Scenarios
@@ -484,21 +528,27 @@ REQUEST_TIMEOUT_MS (global override)
The logging system writes to both stdout and rotated log files. All configuration is read by `src/lib/logEnv.ts`.
| Variable | Default | Description |
| ----------------------------------------- | -------------------------- | -------------------------------------------------------------------------------- |
| `APP_LOG_LEVEL` | `info` | Minimum log level: `debug`, `info`, `warn`, `error`. |
| `APP_LOG_FORMAT` | `text` | Output format: `text` (human-readable) or `json` (structured). |
| `APP_LOG_TO_FILE` | `true` | Write logs to file alongside stdout. |
| `APP_LOG_FILE_PATH` | `logs/application/app.log` | Log file path (relative to project root or `DATA_DIR`). |
| `APP_LOG_MAX_FILE_SIZE` | `50M` | Max file size before rotation. Accepts: `50M`, `1G`, `512K`, or plain bytes. |
| `APP_LOG_RETENTION_DAYS` | `7` | Days to keep rotated application log files. |
| `APP_LOG_MAX_FILES` | `20` | Maximum rotated log file backups. |
| `CALL_LOG_RETENTION_DAYS` | `7` | Days to keep request/call log entries in the database. |
| `CALL_LOG_MAX_ENTRIES` | `10000` | Max call log entries in the in-memory buffer. |
| `CALL_LOGS_TABLE_MAX_ROWS` | `100000` | Max rows in the `call_logs` SQLite table before pruning. |
| `CALL_LOG_PIPELINE_CAPTURE_STREAM_CHUNKS` | `true` | Store stream chunks in pipeline artifacts when `call_log_pipeline_enabled=true`. |
| `CALL_LOG_PIPELINE_MAX_SIZE_KB` | `512` | Max pipeline call log artifact size in KB when `call_log_pipeline_enabled=true`. |
| `PROXY_LOGS_TABLE_MAX_ROWS` | `100000` | Max rows in the `proxy_logs` SQLite table before pruning. |
| Variable | Default | Description |
| ----------------------------------------- | -------------------------- | --------------------------------------------------------------------------------- |
| `APP_LOG_LEVEL` | `info` | Minimum log level: `debug`, `info`, `warn`, `error`. |
| `APP_LOG_FORMAT` | `text` | Output format: `text` (human-readable) or `json` (structured). |
| `APP_LOG_TO_FILE` | `true` | Write logs to file alongside stdout. |
| `APP_LOG_FILE_PATH` | `logs/application/app.log` | Log file path (relative to project root or `DATA_DIR`). |
| `APP_LOG_MAX_FILE_SIZE` | `50M` | Max file size before rotation. Accepts: `50M`, `1G`, `512K`, or plain bytes. |
| `APP_LOG_RETENTION_DAYS` | `7` | Days to keep rotated application log files. |
| `APP_LOG_MAX_FILES` | `20` | Maximum rotated log file backups. |
| `CALL_LOG_RETENTION_DAYS` | `7` | Days to keep request/call log entries in the database. |
| `CALL_LOG_MAX_ENTRIES` | `10000` | Max call log entries in the in-memory buffer. |
| `CALL_LOGS_TABLE_MAX_ROWS` | `100000` | Max rows in the `call_logs` SQLite table before pruning. |
| `CALL_LOG_PIPELINE_CAPTURE_STREAM_CHUNKS` | `true` | Store stream chunks in pipeline artifacts when `call_log_pipeline_enabled=true`. |
| `CALL_LOG_PIPELINE_MAX_SIZE_KB` | `512` | Max pipeline call log artifact size in KB when `call_log_pipeline_enabled=true`. |
| `PROXY_LOGS_TABLE_MAX_ROWS` | `100000` | Max rows in the `proxy_logs` SQLite table before pruning. |
| `APP_LOG_ROTATION_CHECK_INTERVAL_MS` | `60000` (1 min) | How often `src/lib/logRotation.ts` re-checks the active log file size. |
| `CHAT_LOG_TEXT_LIMIT` | `65536` | Max string length retained in chat log artifacts (default 64 KB). |
| `CHAT_LOG_ARRAY_TAIL_ITEMS` | `24` | Number of array items retained from the tail when truncating chat log payloads. |
| `CHAT_LOG_MAX_DEPTH` | `6` | Max nesting depth before chat log payloads are truncated. |
| `CHAT_LOG_MAX_OBJECT_KEYS` | `80` | Max object keys retained in chat log payloads (0 = unlimited). |
| `CHAT_DEBUG_FILE` | `false` | When true, `serializeArtifactForStorage` skips size-based truncation. Debug only. |
---
@@ -558,20 +608,22 @@ Automatic model pricing data synchronization from external sources.
## 20. Provider-Specific Settings
| Variable | Default | Source File | Description |
| ----------------------------------------- | ------------------ | ------------------------------------------ | ------------------------------------------------------------------------------------- |
| `OPENROUTER_CATALOG_TTL_MS` | `86400000` (24h) | `src/lib/catalog/openrouterCatalog.ts` | OpenRouter model catalog cache TTL. |
| `NANOBANANA_POLL_TIMEOUT_MS` | `120000` | `open-sse/handlers/imageGeneration.ts` | Max wait for NanoBanana image generation jobs. |
| `NANOBANANA_POLL_INTERVAL_MS` | `2500` | `open-sse/handlers/imageGeneration.ts` | NanoBanana job polling frequency. |
| `CLOUDFLARE_ACCOUNT_ID` | _(unset)_ | `open-sse/executors/cloudflare-ai.ts` | Account ID for Cloudflare Workers AI. |
| `CLOUDFLARED_BIN` | auto-detect | `src/lib/cloudflaredTunnel.ts` | Custom path to `cloudflared` binary. |
| `SEARCH_CACHE_TTL_MS` | `300000` (5 min) | `open-sse/services/searchCache.ts` | TTL for search API (Perplexity, Brave, etc.) response caching. |
| `ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE` | `false` | `src/app/api/providers/route.ts` | Allow multiple simultaneous connections per OpenAI-compatible provider. |
| `ENABLE_CC_COMPATIBLE_PROVIDER` | `false` | `src/shared/utils/featureFlags.ts` | Reveal the experimental CC-compatible provider UI for Claude Code-only relays. |
| `CLIPROXYAPI_HOST` | `127.0.0.1` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge host (legacy integration). |
| `CLIPROXYAPI_PORT` | `5544` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge port. |
| `CLIPROXYAPI_CONFIG_DIR` | `~/.cli-proxy-api` | `src/lib/versionManager/processManager.ts` | CLIProxyAPI config directory. |
| `LOCAL_HOSTNAMES` | _(empty)_ | `open-sse/config/providerRegistry.ts` | Comma-separated additional hostnames treated as "local" (Docker service names, etc.). |
| Variable | Default | Source File | Description |
| ----------------------------------------- | ------------------ | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| `OPENROUTER_CATALOG_TTL_MS` | `86400000` (24h) | `src/lib/catalog/openrouterCatalog.ts` | OpenRouter model catalog cache TTL. |
| `NANOBANANA_POLL_TIMEOUT_MS` | `120000` | `open-sse/handlers/imageGeneration.ts` | Max wait for NanoBanana image generation jobs. |
| `NANOBANANA_POLL_INTERVAL_MS` | `2500` | `open-sse/handlers/imageGeneration.ts` | NanoBanana job polling frequency. |
| `AWS_REGION` | _(unset)_ | `src/lib/providers/validation.ts`, `open-sse/handlers/audioSpeech.ts` | Region used to construct AWS Bedrock endpoints (Kiro, audio). |
| `AWS_DEFAULT_REGION` | _(unset)_ | `src/lib/providers/validation.ts`, `open-sse/handlers/audioSpeech.ts` | Fallback when `AWS_REGION` is not set. |
| `CLOUDFLARE_ACCOUNT_ID` | _(unset)_ | `open-sse/executors/cloudflare-ai.ts` | Account ID for Cloudflare Workers AI. |
| `CLOUDFLARED_BIN` | auto-detect | `src/lib/cloudflaredTunnel.ts` | Custom path to `cloudflared` binary. |
| `SEARCH_CACHE_TTL_MS` | `300000` (5 min) | `open-sse/services/searchCache.ts` | TTL for search API (Perplexity, Brave, etc.) response caching. |
| `ALLOW_MULTI_CONNECTIONS_PER_COMPAT_NODE` | `false` | `src/app/api/providers/route.ts` | Allow multiple simultaneous connections per OpenAI-compatible provider. |
| `ENABLE_CC_COMPATIBLE_PROVIDER` | `false` | `src/shared/utils/featureFlags.ts` | Reveal the experimental CC-compatible provider UI for Claude Code-only relays. |
| `CLIPROXYAPI_HOST` | `127.0.0.1` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge host (legacy integration). |
| `CLIPROXYAPI_PORT` | `5544` | `open-sse/executors/cliproxyapi.ts` | CLIProxyAPI bridge port. |
| `CLIPROXYAPI_CONFIG_DIR` | `~/.cli-proxy-api` | `src/lib/versionManager/processManager.ts` | CLIProxyAPI config directory. |
| `LOCAL_HOSTNAMES` | _(empty)_ | `open-sse/config/providerRegistry.ts` | Comma-separated additional hostnames treated as "local" (Docker service names, etc.). |
`ENABLE_CC_COMPATIBLE_PROVIDER` is only for third-party relays that accept Claude Code clients
exclusively. OmniRoute rewrites requests so those relays accept them. If you only want to use
@@ -582,14 +634,15 @@ Anthropic-compatible provider instead.
## 21. Proxy Health
| Variable | Default | Source File | Description |
| ---------------------------- | ---------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PROXY_FAST_FAIL_TIMEOUT_MS` | `2000` | `src/lib/proxyHealth.ts` | Fast-fail health check timeout. |
| `PROXY_HEALTH_CACHE_TTL_MS` | `30000` | `src/lib/proxyHealth.ts` | Health check result cache TTL. |
| `RATE_LIMIT_MAX_WAIT_MS` | `120000` (2 min) | `open-sse/services/rateLimitManager.ts` | Max time to wait on a 429 before failing the request. |
| `RATE_LIMIT_AUTO_ENABLE` | `false` | `open-sse/services/rateLimitManager.ts` | When `true`/`1`, automatically engages the rate-limit manager on first observed upstream 429 (without requiring manual toggle). Accepts `true`/`1`/`yes`. |
| `REQUEST_RETRY` | `2` | `src/sse/services/cooldownAwareRetry.ts` | Number of automatic retries on model-scoped cooldown responses before returning error to client. |
| `MAX_RETRY_INTERVAL_SEC` | `30` | `src/sse/services/cooldownAwareRetry.ts` | Max backoff interval (seconds) between cooldown retries. Capped by this value regardless of upstream `Retry-After`. |
| Variable | Default | Source File | Description |
| ---------------------------- | ---------------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PROXY_FAST_FAIL_TIMEOUT_MS` | `2000` | `src/lib/proxyHealth.ts` | Fast-fail health check timeout. |
| `PROXY_HEALTH_CACHE_TTL_MS` | `30000` | `src/lib/proxyHealth.ts` | Health check result cache TTL. |
| `RATE_LIMIT_MAX_WAIT_MS` | `120000` (2 min) | `open-sse/services/rateLimitManager.ts` | Max time to wait on a 429 before failing the request. |
| `RATE_LIMIT_AUTO_ENABLE` | _(unset)_ | `open-sse/services/rateLimitManager.ts` | Force the auto-enable rate limit safety net on/off regardless of the persisted Dashboard setting. Accepts `true`/`1`/`on` to force on, `false`/`0`/`off` to force off. |
| `HEALTHCHECK_STAGGER_MS` | `3000` | `src/lib/tokenHealthCheck.ts` | Stagger interval (ms) between provider token healthchecks at startup. |
| `REQUEST_RETRY` | `2` | `src/sse/services/cooldownAwareRetry.ts` | Number of automatic retries on model-scoped cooldown responses before returning error to client. |
| `MAX_RETRY_INTERVAL_SEC` | `30` | `src/sse/services/cooldownAwareRetry.ts` | Max backoff interval (seconds) between cooldown retries. Capped by this value regardless of upstream `Retry-After`. |
---
@@ -598,12 +651,17 @@ Anthropic-compatible provider instead.
> [!CAUTION]
> These variables produce **verbose output** and may leak sensitive data. **Never enable in production.**
| Variable | Default | Source File | Description |
| -------------------------------- | --------- | ----------------------------------------- | -------------------------------------------------------------- |
| `CURSOR_PROTOBUF_DEBUG` | _(unset)_ | `open-sse/utils/cursorProtobuf.ts` | Set `1` to dump Cursor protobuf decode/encode details. |
| `CURSOR_STREAM_DEBUG` | _(unset)_ | `open-sse/executors/cursor.ts` | Set `1` to dump raw Cursor SSE stream data. |
| `DEBUG_RESPONSES_SSE_TO_JSON` | _(unset)_ | `open-sse/handlers/responseTranslator.ts` | Set `true` to log Responses API SSE→JSON translation details. |
| `NEXT_PUBLIC_OMNIROUTE_E2E_MODE` | _(unset)_ | E2E test harness | Set `true` to enable E2E test mode (relaxed auth, test hooks). |
| Variable | Default | Source File | Description |
| -------------------------------- | ------------------- | ------------------------------------------ | --------------------------------------------------------------------------------- |
| `CURSOR_DEBUG` | _(unset)_ | `open-sse/executors/cursor.ts` | Set `1` to enable verbose Cursor executor logs (decoded SSE chunks, etc.). |
| `CURSOR_STREAM_DEBUG` | _(unset)_ | `open-sse/executors/cursor.ts` | Backward-compatible alias of `CURSOR_DEBUG`. |
| `CURSOR_DUMP_FILE` | _(unset)_ | `open-sse/executors/cursor.ts` | Optional file path that receives raw decoded Cursor chunks when `CURSOR_DEBUG=1`. |
| `CURSOR_STREAM_TIMEOUT_MS` | `300000` | `open-sse/executors/cursor.ts` | Stream idle timeout (ms) for the Cursor executor. |
| `CURSOR_STATE_DB_PATH` | _(probed)_ | `open-sse/utils/cursorVersionDetector.ts` | Override the Cursor state DB lookup used for version detection. |
| `CURSOR_TOKEN` | _(unset)_ | `scripts/cursor-tap.cjs` | Direct Cursor bearer token used by developer tooling. |
| `OMNIROUTE_LOG_REQUEST_SHAPE` | enabled (`!== "0"`) | `src/app/api/v1/chat/completions/route.ts` | Log content-type/length markers for large chat payloads. Set `"0"` to silence. |
| `DEBUG_RESPONSES_SSE_TO_JSON` | _(unset)_ | `open-sse/handlers/responseTranslator.ts` | Set `true` to log Responses API SSE→JSON translation details. |
| `NEXT_PUBLIC_OMNIROUTE_E2E_MODE` | _(unset)_ | E2E test harness | Set `true` to enable E2E test mode (relaxed auth, test hooks). |
---
@@ -611,10 +669,11 @@ Anthropic-compatible provider instead.
Allow users to report issues directly from the Dashboard.
| Variable | Default | Source File | Description |
| --------------------- | --------- | --------------------------------------- | ------------------------------------------------------- |
| `GITHUB_ISSUES_REPO` | _(unset)_ | `src/app/api/v1/issues/report/route.ts` | Repository in `owner/repo` format. |
| `GITHUB_ISSUES_TOKEN` | _(unset)_ | `src/app/api/v1/issues/report/route.ts` | GitHub Personal Access Token with `issues:write` scope. |
| Variable | Default | Source File | Description |
| --------------------- | --------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `GITHUB_ISSUES_REPO` | _(unset)_ | `src/app/api/v1/issues/report/route.ts` | Repository in `owner/repo` format. |
| `GITHUB_ISSUES_TOKEN` | _(unset)_ | `src/app/api/v1/issues/report/route.ts` | GitHub Personal Access Token with `issues:write` scope. |
| `GITHUB_TOKEN` | _(unset)_ | issue triage / cloud agent helpers | Generic GitHub access token used as fallback for `GITHUB_ISSUES_TOKEN` and consumed by cloud agent helpers in `src/lib/cloudAgent/*`. |
---
@@ -698,19 +757,77 @@ Limits and safety knobs applied when the Skills framework (`src/lib/skills/`) ex
---
## 25. Provider Quotas, Tunnels, Backups & Misc Runtime
Provider quota endpoints, network tunnels (Tailscale, Ngrok, MITM debug proxy), the 1Proxy egress pool, database backups and small per-feature overrides referenced by the executor layer or scripts.
| Variable | Default | Source File | Description |
| -------------------------------- | ------------------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------- |
| `REDIS_URL` | `redis://localhost:6379` | `src/shared/utils/rateLimiter.ts` | Redis connection string for the rate limiter backend. |
| `ALIBABA_CODING_PLAN_HOST` | _(production host)_ | `open-sse/services/bailianQuotaFetcher.ts` | Override the host used to fetch Alibaba Bailian coding-plan quotas. |
| `ALIBABA_CODING_PLAN_QUOTA_URL` | derived from host | `open-sse/services/bailianQuotaFetcher.ts` | Full quota URL override for Alibaba Bailian. |
| `CONTEXT_RESERVE_TOKENS` | `1024` | `open-sse/services/contextManager.ts` | Tokens reserved for completion output when computing prompt budgets. |
| `MODEL_ALIAS_COMPAT_ENABLED` | enabled | `open-sse/services/model.ts` | Toggle the legacy model-alias compatibility layer used by older clients. |
| `COMMAND_CODE_CALLBACK_PORT` | _(unset)_ | `src/app/api/providers/command-code/auth/shared.ts` | Local port used for OAuth-style callbacks from the Command Code CLI helper. |
| `MITM_LOCAL_PORT` | `443` | `src/mitm/server.cjs` | Local bind port for the MITM debug proxy. |
| `MITM_DISABLE_TLS_VERIFY` | `0` | `src/mitm/server.cjs` | Set `1` to disable upstream TLS verification (development only). |
| `ONEPROXY_ENABLED` | `true` | `src/lib/oneproxySync.ts` | Enable the 1Proxy egress pool sync. |
| `ONEPROXY_API_URL` | `https://1proxy-api.aitradepulse.com` | `src/lib/oneproxySync.ts` | 1Proxy service API URL override. |
| `ONEPROXY_MAX_PROXIES` | `500` | `src/lib/oneproxySync.ts` | Maximum proxies imported per sync. |
| `ONEPROXY_MIN_QUALITY_THRESHOLD` | `50` | `src/lib/oneproxySync.ts` | Minimum quality score for imported proxies. |
| `TAILSCALE_BIN` | _(auto-detect)_ | `src/lib/tailscaleTunnel.ts` | Explicit path to the `tailscale` binary. |
| `TAILSCALED_BIN` | _(auto-detect)_ | `src/lib/tailscaleTunnel.ts` | Explicit path to the `tailscaled` daemon binary. |
| `NGROK_AUTHTOKEN` | _(unset)_ | `src/lib/ngrokTunnel.ts` | Authenticates outbound ngrok tunnels. |
| `DB_BACKUP_MAX_FILES` | `20` | `src/lib/db/backup.ts` | Maximum SQLite backup files retained on disk. |
| `DB_BACKUP_RETENTION_DAYS` | `0` | `src/lib/db/backup.ts` | Maximum age (days) of retained backups. `0` disables age-based pruning. |
| `OMNIROUTE_TLS_PROXY_URL` | _(unset)_ | `open-sse/services/chatgptTlsClient.ts` | Override the TLS sidecar URL for tests. Production should leave unset. |
---
## 26. Test & E2E Harness
Used by `scripts/run-next-playwright.mjs`, `scripts/smoke-electron-packaged.mjs`,
`scripts/run-ecosystem-tests.mjs`, and `scripts/uninstall.mjs`. Leave every
value below unset in production deployments.
| Variable | Default | Source File | Description |
| ------------------------------------- | -------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------- |
| `OMNIROUTE_E2E_BOOTSTRAP_MODE` | `auth` | `scripts/run-next-playwright.mjs` | E2E bootstrap mode (`auth`, `fresh`, `reuse`) for the Playwright runner. |
| `OMNIROUTE_E2E_PASSWORD` | falls back to `INITIAL_PASSWORD` | `scripts/run-next-playwright.mjs` | Admin password injected into the Playwright environment. |
| `OMNIROUTE_DISABLE_LOCAL_HEALTHCHECK` | `true` | `scripts/run-next-playwright.mjs` | Disable the local healthcheck poll during Playwright runs. |
| `OMNIROUTE_DISABLE_TOKEN_HEALTHCHECK` | `true` | `scripts/run-next-playwright.mjs` | Disable the OAuth token healthcheck loop during tests. |
| `OMNIROUTE_HIDE_HEALTHCHECK_LOGS` | `true` | `scripts/run-next-playwright.mjs` | Silence healthcheck noise in Playwright stdout. |
| `OMNIROUTE_PLAYWRIGHT_SKIP_BUILD` | `0` | `scripts/run-next-playwright.mjs` | Skip the Next.js production build before Playwright starts (CI optimization). |
| `OMNIROUTE_SKIP_UNINSTALL_HOOK` | `0` | `scripts/uninstall.mjs` | Skip the OmniRoute uninstall hook (used by CI to keep `node_modules` intact). |
| `ECOSYSTEM_SERVER_WAIT_MS` | `180000` | `scripts/run-ecosystem-tests.mjs` | Wait time (ms) for the server to become healthy before running ecosystem/protocol tests. |
| `ELECTRON_SMOKE_URL` | `http://127.0.0.1:20128/login` | `scripts/smoke-electron-packaged.mjs` | URL the Electron smoke harness expects the packaged app to serve. |
| `ELECTRON_SMOKE_TIMEOUT_MS` | `45000` | `scripts/smoke-electron-packaged.mjs` | Total timeout (ms) before the smoke harness gives up. |
| `ELECTRON_SMOKE_SETTLE_MS` | `2000` | `scripts/smoke-electron-packaged.mjs` | Settle window (ms) after the page loads. |
| `ELECTRON_SMOKE_APP_EXECUTABLE` | _(auto)_ | `scripts/smoke-electron-packaged.mjs` | Explicit path to the packaged Electron executable. |
| `ELECTRON_SMOKE_DATA_DIR` | _(tmpdir)_ | `scripts/smoke-electron-packaged.mjs` | Data directory for the Electron smoke run. |
| `ELECTRON_SMOKE_KEEP_DATA` | `0` | `scripts/smoke-electron-packaged.mjs` | Set `1` to preserve the smoke data directory after the run. |
| `ELECTRON_SMOKE_STREAM_LOGS` | `0` | `scripts/smoke-electron-packaged.mjs` | Set `1` to stream Electron logs to stdout during the run. |
| `CLI_DEVIN_BIN` | _(PATH lookup)_ | `open-sse/executors/devin-cli.ts` | Override the Devin CLI binary path. |
---
## Audit: Removed / Dead Variables
The following variables appeared in previous versions of `.env.example` but have **no runtime references** in the current codebase. They have been removed:
| Variable | Reason |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| `STORAGE_DRIVER=sqlite` | Never read by any source file. SQLite is the only supported driver — no selection needed. |
| `INSTANCE_NAME=omniroute` | Present in old docs/env templates but unused at runtime. May return in a future multi-instance feature. |
| `SQLITE_MAX_SIZE_MB=2048` | Not referenced in source code. Database size is not artificially limited. |
| `SQLITE_CLEAN_LEGACY_FILES=true` | Not referenced in source code. Legacy cleanup was likely removed. |
| `CLI_ROO_BIN` | Not registered in `src/shared/services/cliRuntime.ts`. |
| `CLI_KIMI_CODING_BIN` | Not registered in `src/shared/services/cliRuntime.ts` (Kimi Coding uses OAuth, not a CLI binary). |
| `IFLOW_OAUTH_CLIENT_ID` / `IFLOW_OAUTH_CLIENT_SECRET` | Not referenced anywhere in source code. |
| Variable | Reason |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `STORAGE_DRIVER=sqlite` | Never read by any source file. SQLite is the only supported driver — no selection needed. |
| `INSTANCE_NAME=omniroute` | Present in old docs/env templates but unused at runtime. May return in a future multi-instance feature. |
| `SQLITE_MAX_SIZE_MB=2048` | Not referenced in source code. Database size is not artificially limited. |
| `SQLITE_CLEAN_LEGACY_FILES=true` | Not referenced in source code. Legacy cleanup was likely removed. |
| `CLI_ROO_BIN` | Not registered in `src/shared/services/cliRuntime.ts`. |
| `CLI_KIMI_CODING_BIN` | Not registered in `src/shared/services/cliRuntime.ts` (Kimi Coding uses OAuth, not a CLI binary). |
| `IFLOW_OAUTH_CLIENT_ID` / `IFLOW_OAUTH_CLIENT_SECRET` | Not referenced anywhere in source code. |
| `CEREBRAS_API_KEY` / `COHERE_API_KEY` / `FIREWORKS_API_KEY` / `GROQ_API_KEY` / `MISTRAL_API_KEY` / `NEBIUS_API_KEY` / `PERPLEXITY_API_KEY` / `TOGETHER_API_KEY` / `XAI_API_KEY` | Removed in v3.8.0. The runtime no longer reads these env vars — credentials come from Dashboard / `data/provider-credentials.json` / encrypted DB. |
| `CURSOR_PROTOBUF_DEBUG` | Removed in v3.8.0. Cursor executor uses `CURSOR_DEBUG` / `CURSOR_STREAM_DEBUG` (see §22). |
| `CLI_COMPAT_KIRO` | Removed in v3.8.0. Kiro is in `CLI_COMPAT_OMITTED_PROVIDER_IDS` — its toggle has no effect. |
| `QIANFAN_API_KEY` | Removed alongside other unused provider API key stubs in v3.8.0. |
### Default Value Corrections

View File

@@ -142,13 +142,23 @@ export const RateLimitReason = {
// ─── Provider Resilience Profiles ───────────────────────────────────────────
// Separate behavior for OAuth (low-limit, session-based) vs API Key (high-limit, metered)
// Circuit-breaker thresholds and reset windows are overridable via
// OMNIROUTE_CIRCUIT_BREAKER_* env vars so operators can dampen or harden
// behavior without recompiling.
function envInt(name: string, fallback: number): number {
const raw = process.env[name];
if (raw === undefined || raw === null || raw === "") return fallback;
const parsed = Number(raw);
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
}
export const PROVIDER_PROFILES = {
oauth: {
transientCooldown: 5000, // 5s (session tokens — short recovery)
rateLimitCooldown: 60000, // 60s default when no retry-after header
maxBackoffLevel: 8, // Higher ceiling (sessions may stay bad longer)
circuitBreakerThreshold: 8, // Scaled for 500+ connections (was 3)
circuitBreakerReset: 60000, // 1min reset
circuitBreakerThreshold: envInt("OMNIROUTE_CIRCUIT_BREAKER_OAUTH_THRESHOLD", 8),
circuitBreakerReset: envInt("OMNIROUTE_CIRCUIT_BREAKER_OAUTH_RESET_MS", 60000),
// Provider-level circuit breaker (entire provider cooldown after repeated failures)
providerFailureThreshold: 10, // Scaled for 500+ connections (was 3)
providerFailureWindowMs: 900000, // 15min window (was 10min)
@@ -158,8 +168,8 @@ export const PROVIDER_PROFILES = {
transientCooldown: 3000, // 3s (API providers recover faster)
rateLimitCooldown: 0, // 0 = respect retry-after header from provider
maxBackoffLevel: 5, // Lower ceiling (API quotas reset at known intervals)
circuitBreakerThreshold: 12, // Scaled for 500+ connections (was 5)
circuitBreakerReset: 30000, // 30s reset
circuitBreakerThreshold: envInt("OMNIROUTE_CIRCUIT_BREAKER_API_KEY_THRESHOLD", 12),
circuitBreakerReset: envInt("OMNIROUTE_CIRCUIT_BREAKER_API_KEY_RESET_MS", 30000),
// Provider-level circuit breaker (entire provider cooldown after repeated failures)
providerFailureThreshold: 15, // Scaled for 500+ connections (was 5)
providerFailureWindowMs: 1800000, // 30min window (was 20min)
@@ -172,8 +182,8 @@ export const PROVIDER_PROFILES = {
transientCooldown: 2000, // 2s (local — very fast recovery)
rateLimitCooldown: 5000, // 5s (local — no real rate limits)
maxBackoffLevel: 3, // Low ceiling (local either works or doesn't)
circuitBreakerThreshold: 2, // Opens fast (if local is down, it's down)
circuitBreakerReset: 15000, // 15s reset (check again quickly)
circuitBreakerThreshold: envInt("OMNIROUTE_CIRCUIT_BREAKER_LOCAL_THRESHOLD", 2),
circuitBreakerReset: envInt("OMNIROUTE_CIRCUIT_BREAKER_LOCAL_RESET_MS", 15000),
// Provider-level circuit breaker (entire provider cooldown after repeated failures)
providerFailureThreshold: 2, // 2 failures trigger provider cooldown
providerFailureWindowMs: 300000, // 5min window for counting failures

View File

@@ -21,14 +21,16 @@ let clientPromise: Promise<unknown> | null = null;
let exitHookInstalled = false;
const CHATGPT_PROFILE = "firefox_148"; // matches the Firefox 148 UA we send
const DEFAULT_TIMEOUT_MS = 60_000;
const DEFAULT_TIMEOUT_MS =
Number.parseInt(process.env.OMNIROUTE_CHATGPT_TLS_TIMEOUT_MS || "", 10) || 60_000;
// Grace period added to the binding's wire-level timeout before our JS-level
// hard timeout fires. Under healthy operation `tls-client-node` honors
// `timeoutMilliseconds` and rejects on its own; the JS-level race only wins
// when the koffi-loaded native library is wedged (which the binding's own
// timer can't escape). Keep the grace small so users don't wait noticeably
// longer than the configured timeout when the binding is dead.
const HARD_TIMEOUT_GRACE_MS = 10_000;
const HARD_TIMEOUT_GRACE_MS =
Number.parseInt(process.env.OMNIROUTE_CHATGPT_TLS_GRACE_MS || "", 10) || 10_000;
function installExitHook(): void {
if (exitHookInstalled) return;

View File

@@ -3,6 +3,14 @@
*/
import { PROVIDERS } from "../config/constants.ts";
// Quota / usage upstream URLs (overridable for testing or relays).
const CROF_USAGE_URL = process.env.OMNIROUTE_CROF_USAGE_URL ?? "https://crof.ai/usage_api/";
const GEMINI_CLI_USAGE_URL =
process.env.OMNIROUTE_GEMINI_CLI_USAGE_URL ??
"https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist";
const CODEWHISPERER_BASE_URL =
process.env.OMNIROUTE_CODEWHISPERER_BASE_URL ?? "https://codewhisperer.us-east-1.amazonaws.com";
import {
getAntigravityFetchAvailableModelsUrls,
ANTIGRAVITY_BASE_URLS,
@@ -482,7 +490,7 @@ async function getCrofUsage(apiKey: string) {
let response: Response;
try {
response = await fetch("https://crof.ai/usage_api/", {
response = await fetch(CROF_USAGE_URL, {
method: "GET",
headers: {
Authorization: `Bearer ${apiKey}`,
@@ -1400,7 +1408,7 @@ async function getGeminiCliSubscriptionInfoCached(accessToken: string): Promise<
*/
async function getGeminiCliSubscriptionInfo(accessToken: string): Promise<unknown | null> {
try {
const response = await fetch("https://cloudcode-pa.googleapis.com/v1internal:loadCodeAssist", {
const response = await fetch(GEMINI_CLI_USAGE_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
@@ -2212,7 +2220,7 @@ async function getKiroUsage(accessToken?: string, providerSpecificData?: JsonRec
resourceType: "AGENTIC_REQUEST",
};
const response = await fetch("https://codewhisperer.us-east-1.amazonaws.com", {
const response = await fetch(CODEWHISPERER_BASE_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,

View File

@@ -1,10 +1,27 @@
#!/usr/bin/env node
// Validates that env vars referenced in code appear in .env.example AND in docs/ENVIRONMENT.md.
// Exits 0 on success, 1 on missing entries. Designed for use in pre-commit / CI.
//
// Run: node scripts/check/check-env-doc-sync.mjs
// Strict mode: node scripts/check/check-env-doc-sync.mjs --strict
// In strict mode, missing entries cause failure. In default mode, only summary is printed.
/**
* Strict environment variable contract checker.
*
* Enforces that every env var referenced in OmniRoute source code appears in
* both `.env.example` and `docs/ENVIRONMENT.md`, and that the two files agree
* on the documented var set. Falls back to a small allowlist for variables
* that are intentionally documented but not literally referenced (legacy
* aliases, future-supported hooks) or vice versa.
*
* Usage:
* node scripts/check/check-env-doc-sync.mjs # strict (CI mode)
* node scripts/check/check-env-doc-sync.mjs --lenient # legacy report-only mode
*
* Strict mode exits non-zero if any of these are non-empty:
* - vars in code but missing from .env.example
* - vars in .env.example but missing from ENVIRONMENT.md
* - vars in ENVIRONMENT.md but missing from .env.example
*
* Programmatic API:
* Other Node tests can `import { runEnvDocSync } from "./check-env-doc-sync.mjs"`
* and pass `{ root, envExample, envDoc, codeVars, ignore, docOnlyAllowlist,
* envOnlyAllowlist }` to drive the checker against fixtures.
*/
import fs from "node:fs";
import path from "node:path";
@@ -12,14 +29,12 @@ import { fileURLToPath } from "node:url";
import { execSync } from "node:child_process";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, "..", "..");
const ENV_EXAMPLE = path.join(ROOT, ".env.example");
const ENV_DOC = path.join(ROOT, "docs", "ENVIRONMENT.md");
const REPO_ROOT = path.resolve(__dirname, "..", "..");
const STRICT = process.argv.includes("--strict");
// Vars that are intentionally not documented or detected in code via dynamic patterns.
const IGNORE = new Set([
// ─── Allowlists ────────────────────────────────────────────────────────────
// Env vars referenced in code that should NOT trigger documentation drift.
// These are usually system/process vars or harness-only knobs.
const IGNORE_FROM_CODE = new Set([
"NODE_ENV",
"PATH",
"HOME",
@@ -33,99 +48,230 @@ const IGNORE = new Set([
"CI",
"GITHUB_ACTIONS",
"RUNNER_OS",
// Add false positives here as discovered.
// OS / Node internals frequently surfaced by indirect dependencies.
"APPDATA",
"LOCALAPPDATA",
"XDG_CONFIG_HOME",
"USERPROFILE",
"PREFIX",
// Next.js / Node test runners — these are framework-managed.
"NEXT_DIST_DIR",
"NEXT_PHASE",
"NEXT_RUNTIME",
"VITEST",
// CI providers (set by the runner).
"GITHUB_BASE_REF",
"GITHUB_BASE_SHA",
// Aliases for documented vars handled via fallback ordering.
"API_KEY",
"APP_URL",
"PUBLIC_URL",
"ANTHROPIC_API_URL",
"OPENAI_API_URL",
"LOG_LEVEL",
// Internal QA helpers used only by scripts/ and Playwright.
"QA_BASE_URL",
"QA_LOCALES",
"QA_REPORT_SUFFIX",
"QA_ROUTES",
// Doctor diagnostic flags (no runtime behavior yet — placeholders).
"OMNIROUTE_DOCTOR_HOST",
"OMNIROUTE_DOCTOR_LIVENESS_URL",
"OMNIROUTE_PROVIDER_CATALOG_PATH",
"OMNIROUTE_PROVIDER_TEST_MODEL",
// Source typo / placeholder.
"OMNIROUT",
// Static config alias path (the canonical var is OMNIROUTE_PAYLOAD_RULES_PATH).
"PAYLOAD_RULES_PATH",
]);
function readEnvExampleVars() {
if (!fs.existsSync(ENV_EXAMPLE)) {
console.error(`${ENV_EXAMPLE} not found`);
process.exit(2);
}
const txt = fs.readFileSync(ENV_EXAMPLE, "utf8");
// Vars documented in ENVIRONMENT.md but intentionally absent from .env.example.
// Used for past-tense documentation (Audit / Dead vars section), legacy aliases
// with no runtime hook, and section anchors that look like vars to the regex.
const DOC_ONLY_ALLOWLIST = new Set([
// Audit history (Removed / Dead Variables section).
"CEREBRAS_API_KEY",
"COHERE_API_KEY",
"FIREWORKS_API_KEY",
"GROQ_API_KEY",
"MISTRAL_API_KEY",
"NEBIUS_API_KEY",
"PERPLEXITY_API_KEY",
"TOGETHER_API_KEY",
"XAI_API_KEY",
"QIANFAN_API_KEY",
"CURSOR_PROTOBUF_DEBUG",
"CLI_COMPAT_KIRO",
"CLI_KIMI_CODING_BIN",
"CLI_ROO_BIN",
"IFLOW_OAUTH_CLIENT_ID",
"IFLOW_OAUTH_CLIENT_SECRET",
// Source-code constants accidentally captured by the doc regex.
"CLI_COMPAT_OMITTED_PROVIDER_IDS",
// Sample default values that look like SHOUTY_NAMES (not env vars).
"CHANGEME",
// Legacy aliases — present in docs as "would be aliases" but read-only
// through their canonical names today.
"OMNIROUTE_CRYPT_KEY",
"OMNIROUTE_API_KEY_BASE64",
// Future-supported hooks: documented but currently hardcoded constants.
"MAX_RETRY_INTERVAL_SEC",
"REQUEST_RETRY",
"SKILLS_EXECUTION_TIMEOUT_MS",
"SKILLS_SANDBOX_DOCKER_IMAGE",
]);
// Vars present in .env.example but intentionally absent from ENVIRONMENT.md.
// Empty today — kept for forward compatibility / explicit exemption.
const ENV_ONLY_ALLOWLIST = new Set([]);
// ─── Parsing helpers ───────────────────────────────────────────────────────
/**
* Extract VAR= entries from a `.env`-style file (handles commented examples).
*/
export function parseEnvExampleVars(text) {
const vars = new Set();
for (const line of txt.split("\n")) {
// Match both "VAR=value" and "# VAR=value" (commented-out examples are still documented)
for (const line of String(text ?? "").split("\n")) {
const m = line.match(/^#?\s*([A-Z][A-Z0-9_]+)\s*=/);
if (m) vars.add(m[1]);
}
return vars;
}
function readEnvDocVars() {
if (!fs.existsSync(ENV_DOC)) {
console.error(`${ENV_DOC} not found`);
process.exit(2);
}
const txt = fs.readFileSync(ENV_DOC, "utf8");
/**
* Extract `VARNAME` tokens from a markdown doc — matches anything in backticks
* that looks like an env var (uppercase + digit + underscore).
*/
export function parseEnvDocVars(text) {
const vars = new Set();
// Match `VAR_NAME` in inline code or table cells.
for (const m of txt.matchAll(/`([A-Z][A-Z0-9_]{2,})`/g)) {
for (const m of String(text ?? "").matchAll(/`([A-Z][A-Z0-9_]{2,})`/g)) {
vars.add(m[1]);
}
return vars;
}
function readCodeVars() {
/**
* Collect environment variable references in source code via grep against
* the `process.env` member access pattern.
*/
function scanCodeVars({ cwd } = {}) {
const repoRoot = cwd ?? REPO_ROOT;
const stdout = execSync(
"grep -rhoE 'process\\.env\\.[A-Z][A-Z0-9_]+' " +
"src/ open-sse/ bin/ scripts/ electron/main.js electron/preload.js 2>/dev/null || true",
{ cwd: repoRoot, encoding: "utf8", maxBuffer: 20 * 1024 * 1024 }
);
const vars = new Set();
let stdout;
try {
stdout = execSync(
"grep -rhoE 'process\\.env\\.[A-Z][A-Z0-9_]+' src/ open-sse/ bin/ scripts/ 2>/dev/null || true",
{ cwd: ROOT, encoding: "utf8", maxBuffer: 20 * 1024 * 1024 }
);
} catch (e) {
console.error(`✗ grep failed: ${e.message}`);
process.exit(2);
}
for (const line of stdout.split("\n")) {
const m = line.match(/^process\.env\.([A-Z][A-Z0-9_]+)$/);
if (m && !IGNORE.has(m[1])) vars.add(m[1]);
if (m) vars.add(m[1]);
}
return vars;
}
/**
* Diff helper.
*/
function diff(set, against) {
return [...set].filter((v) => !against.has(v)).sort();
}
function main() {
const codeVars = readCodeVars();
const exampleVars = readEnvExampleVars();
const docVars = readEnvDocVars();
// ─── Programmatic entry point ──────────────────────────────────────────────
const inCodeMissingExample = diff(codeVars, exampleVars);
const inCodeMissingDoc = diff(codeVars, docVars);
const inExampleMissingDoc = diff(exampleVars, docVars);
const inExampleMissingCode = diff(exampleVars, codeVars);
/**
* Run the contract checker. All inputs are overridable for tests.
*
* Returns `{ ok: boolean, summary, problems: { codeMissingEnv, envMissingDoc,
* docMissingEnv } }`.
*/
export function runEnvDocSync(options = {}) {
const ignore = options.ignore ?? IGNORE_FROM_CODE;
const docOnly = options.docOnlyAllowlist ?? DOC_ONLY_ALLOWLIST;
const envOnly = options.envOnlyAllowlist ?? ENV_ONLY_ALLOWLIST;
console.log("Env var sync report");
console.log("===================");
console.log(`Code references: ${codeVars.size} unique vars`);
console.log(`In .env.example: ${exampleVars.size} unique vars`);
console.log(`In docs/ENVIRONMENT.md: ${docVars.size} unique vars (heuristic)`);
console.log();
const envExampleText =
options.envExampleText ??
(options.envExamplePath
? fs.readFileSync(options.envExamplePath, "utf8")
: fs.readFileSync(path.join(REPO_ROOT, ".env.example"), "utf8"));
const envDocText =
options.envDocText ??
(options.envDocPath
? fs.readFileSync(options.envDocPath, "utf8")
: fs.readFileSync(path.join(REPO_ROOT, "docs", "ENVIRONMENT.md"), "utf8"));
function printList(label, list) {
if (list.length === 0) {
console.log(`${label}: none`);
} else {
console.log(`${label}: ${list.length}`);
for (const v of list.slice(0, 30)) console.log(` - ${v}`);
if (list.length > 30) console.log(` ... and ${list.length - 30} more`);
}
}
const envVars = parseEnvExampleVars(envExampleText);
const docVars = parseEnvDocVars(envDocText);
printList("In code but missing from .env.example", inCodeMissingExample);
printList("In code but missing from ENVIRONMENT.md", inCodeMissingDoc);
printList("In .env.example but missing from ENVIRONMENT.md", inExampleMissingDoc);
printList("In .env.example but not referenced in code (dead?)", inExampleMissingCode);
const codeVars = new Set(
[...(options.codeVars ?? scanCodeVars({ cwd: options.root }))].filter((v) => !ignore.has(v))
);
const errors = inCodeMissingExample.length + inExampleMissingDoc.length;
if (STRICT && errors > 0) {
console.error(`\n${errors} drift(s) detected (strict mode)`);
process.exit(1);
}
console.log(`\n${errors === 0 ? "✓" : "⚠"} Done.`);
const codeMissingEnv = diff(codeVars, envVars);
const envMissingDoc = diff(envVars, docVars).filter((v) => !envOnly.has(v));
const docMissingEnv = diff(docVars, envVars).filter((v) => !docOnly.has(v));
const ok =
codeMissingEnv.length === 0 && envMissingDoc.length === 0 && docMissingEnv.length === 0;
return {
ok,
summary: {
code: codeVars.size,
envExample: envVars.size,
doc: docVars.size,
},
problems: {
codeMissingEnv,
envMissingDoc,
docMissingEnv,
},
};
}
main();
// ─── CLI ───────────────────────────────────────────────────────────────────
function printList(label, list, marker) {
if (list.length === 0) {
console.log(` ${marker || "✓"} ${label}: none`);
return;
}
console.log(`${label}: ${list.length}`);
for (const v of list.slice(0, 50)) console.log(` - ${v}`);
if (list.length > 50) console.log(` ... and ${list.length - 50} more`);
}
function main() {
const lenient = process.argv.includes("--lenient");
const result = runEnvDocSync();
console.log("Env var contract sync report");
console.log("============================");
console.log(`Code references: ${result.summary.code} unique vars`);
console.log(`In .env.example: ${result.summary.envExample} unique vars`);
console.log(`In docs/ENVIRONMENT.md: ${result.summary.doc} unique vars`);
console.log();
printList("In code but missing from .env.example", result.problems.codeMissingEnv);
printList("In .env.example but missing from ENVIRONMENT.md", result.problems.envMissingDoc);
printList("In ENVIRONMENT.md but missing from .env.example", result.problems.docMissingEnv);
if (result.ok) {
console.log("\n✓ Env / docs contract is in sync.");
process.exit(0);
}
if (lenient) {
console.log("\n⚠ Drift detected (lenient mode — exit 0).");
process.exit(0);
}
console.log("\n✗ Env / docs contract is out of sync. Update .env.example, docs/ENVIRONMENT.md,");
console.log(" or the allowlists in scripts/check/check-env-doc-sync.mjs and try again.");
process.exit(1);
}
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}

View File

@@ -7,7 +7,8 @@
* @module shared/utils/fetchTimeout
*/
const DEFAULT_TIMEOUT_MS = 120000; // 2 minutes
const DEFAULT_TIMEOUT_MS =
parseInt(process.env.OMNIROUTE_DEFAULT_FETCH_TIMEOUT_MS || "", 10) || 120000; // 2 minutes
const FETCH_TIMEOUT_MS = parseInt(process.env.FETCH_TIMEOUT_MS || "", 10) || DEFAULT_TIMEOUT_MS;
interface FetchTimeoutOptions extends RequestInit {

View File

@@ -0,0 +1,191 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
parseEnvExampleVars,
parseEnvDocVars,
runEnvDocSync,
} from "../../scripts/check/check-env-doc-sync.mjs";
test("parseEnvExampleVars: extracts uncommented assignments", () => {
const text = `
JWT_SECRET=abc123
FOO_BAR=baz
# SKIPPED_VAR=leading whitespace before hash is rejected
`;
const vars = parseEnvExampleVars(text);
assert.ok(vars.has("JWT_SECRET"));
assert.ok(vars.has("FOO_BAR"));
// The regex anchors `^#?` so leading whitespace before `#` prevents a match.
assert.equal(vars.has("SKIPPED_VAR"), false);
});
test("parseEnvExampleVars: extracts commented examples", () => {
const text = `
#OPTIONAL_KEY=value
# OPTIONAL_OTHER=value-with-space
ACTIVE=1
`;
const vars = parseEnvExampleVars(text);
assert.ok(vars.has("OPTIONAL_KEY"));
assert.ok(vars.has("OPTIONAL_OTHER"));
assert.ok(vars.has("ACTIVE"));
});
test("parseEnvExampleVars: ignores prose", () => {
const text = `
# This file documents env vars. Don't put values here.
Some narrative paragraph mentions PORT and DATA_DIR in passing.
PORT=20128
`;
const vars = parseEnvExampleVars(text);
assert.deepEqual([...vars].sort(), ["PORT"]);
});
test("parseEnvDocVars: extracts SHOUTY_NAMES from inline backticks", () => {
const md = "Set `FOO_BAR` to enable the thing. Defaults to `BAR_BAZ_QUX`.";
const vars = parseEnvDocVars(md);
assert.deepEqual([...vars].sort(), ["BAR_BAZ_QUX", "FOO_BAR"]);
});
test("parseEnvDocVars: ignores values like `7s` or two-letter codes", () => {
const md = "TTL is `60s` and the type is `JSON`. Real var: `OMNIROUTE_TTL_MS`.";
const vars = parseEnvDocVars(md);
assert.deepEqual([...vars].sort(), ["JSON", "OMNIROUTE_TTL_MS"]);
});
test("runEnvDocSync: matched fixture passes", () => {
const envExampleText = `
JWT_SECRET=secret
#OPTIONAL_VAR=value
ANOTHER_VAR=1
`;
const envDocText = `
# Reference
| Variable | Default | Description |
| --- | --- | --- |
| \`JWT_SECRET\` | _(none)_ | required |
| \`OPTIONAL_VAR\` | _(unset)_ | optional |
| \`ANOTHER_VAR\` | 1 | enabled by default |
`;
const codeVars = new Set(["JWT_SECRET", "OPTIONAL_VAR", "ANOTHER_VAR"]);
const result = runEnvDocSync({
envExampleText,
envDocText,
codeVars,
ignore: new Set(),
docOnlyAllowlist: new Set(),
envOnlyAllowlist: new Set(),
});
assert.equal(result.ok, true);
assert.deepEqual(result.problems.codeMissingEnv, []);
assert.deepEqual(result.problems.envMissingDoc, []);
assert.deepEqual(result.problems.docMissingEnv, []);
});
test("runEnvDocSync: drift in code is flagged", () => {
const envExampleText = `JWT_SECRET=secret\n`;
const envDocText = "| `JWT_SECRET` | _(none)_ | required |";
const result = runEnvDocSync({
envExampleText,
envDocText,
codeVars: new Set(["JWT_SECRET", "NEW_VAR"]),
ignore: new Set(),
docOnlyAllowlist: new Set(),
envOnlyAllowlist: new Set(),
});
assert.equal(result.ok, false);
assert.deepEqual(result.problems.codeMissingEnv, ["NEW_VAR"]);
});
test("runEnvDocSync: drift in doc is flagged", () => {
const envExampleText = `JWT_SECRET=secret\nNEW_VAR=value\n`;
const envDocText = "| `JWT_SECRET` | _(none)_ | required |";
const result = runEnvDocSync({
envExampleText,
envDocText,
codeVars: new Set(["JWT_SECRET", "NEW_VAR"]),
ignore: new Set(),
docOnlyAllowlist: new Set(),
envOnlyAllowlist: new Set(),
});
assert.equal(result.ok, false);
assert.deepEqual(result.problems.envMissingDoc, ["NEW_VAR"]);
});
test("runEnvDocSync: drift in env (doc-only var) is flagged", () => {
const envExampleText = `JWT_SECRET=secret\n`;
const envDocText = `
| \`JWT_SECRET\` | _(none)_ | required |
| \`OBSOLETE_VAR\` | _(unset)_ | docs-only legacy |
`;
const result = runEnvDocSync({
envExampleText,
envDocText,
codeVars: new Set(["JWT_SECRET"]),
ignore: new Set(),
docOnlyAllowlist: new Set(),
envOnlyAllowlist: new Set(),
});
assert.equal(result.ok, false);
assert.deepEqual(result.problems.docMissingEnv, ["OBSOLETE_VAR"]);
});
test("runEnvDocSync: docOnlyAllowlist absolves doc-only entries", () => {
const envExampleText = `JWT_SECRET=secret\n`;
const envDocText = `
| \`JWT_SECRET\` | _(none)_ | required |
| \`LEGACY_ALIAS\` | _(unset)_ | documented but not in env.example |
`;
const result = runEnvDocSync({
envExampleText,
envDocText,
codeVars: new Set(["JWT_SECRET"]),
ignore: new Set(),
docOnlyAllowlist: new Set(["LEGACY_ALIAS"]),
envOnlyAllowlist: new Set(),
});
assert.equal(result.ok, true);
assert.deepEqual(result.problems.docMissingEnv, []);
});
test("runEnvDocSync: envOnlyAllowlist absolves env-only entries", () => {
const envExampleText = `JWT_SECRET=secret\nFOO_BAR=bar\n`;
const envDocText = "| `JWT_SECRET` | _(none)_ | required |";
const result = runEnvDocSync({
envExampleText,
envDocText,
codeVars: new Set(["JWT_SECRET", "FOO_BAR"]),
ignore: new Set(),
docOnlyAllowlist: new Set(),
envOnlyAllowlist: new Set(["FOO_BAR"]),
});
assert.equal(result.ok, true);
assert.deepEqual(result.problems.envMissingDoc, []);
});
test("runEnvDocSync: ignore set skips a code-referenced var", () => {
const envExampleText = `JWT_SECRET=secret\n`;
const envDocText = "| `JWT_SECRET` | _(none)_ | required |";
const result = runEnvDocSync({
envExampleText,
envDocText,
codeVars: new Set(["JWT_SECRET", "PATH", "HOME"]),
ignore: new Set(["PATH", "HOME"]),
docOnlyAllowlist: new Set(),
envOnlyAllowlist: new Set(),
});
assert.equal(result.ok, true);
});
test("repository contract is in sync (live data)", () => {
// Uses the real .env.example, docs/ENVIRONMENT.md, and the bundled
// allowlists. This is the same check that runs in pre-commit / CI.
const result = runEnvDocSync();
if (!result.ok) {
const summary = JSON.stringify(result.problems, null, 2);
assert.fail(`Env/docs contract drift detected:\n${summary}`);
}
assert.equal(result.ok, true);
});