mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-04 06:12:10 +03:00
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:
312
.env.example
312
.env.example
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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}`,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
191
tests/unit/check-env-doc-sync.test.ts
Normal file
191
tests/unit/check-env-doc-sync.test.ts
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user