| # ============================================ | |
| # World Monitor — Environment Variables | |
| # ============================================ | |
| # Copy this file to .env.local and fill in the values you need. | |
| # All keys are optional — the dashboard works without them, | |
| # but the corresponding features will be disabled. | |
| # | |
| # cp .env.example .env.local | |
| # | |
| # For self-hosted Docker deployments, see SELF_HOSTING.md. | |
| # Use docker-compose.override.yml (gitignored) for local secrets. | |
| # ============================================ | |
| # ------ Local dev server ------ | |
| # Port for the main Vite dev server (`npm run dev`). Defaults to 3000 when unset, | |
| # or when the value is not an integer in 1–65535. (The pro-test dev server keeps its | |
| # own fixed port.) Config-time only (read in vite.config.ts) — intentionally NOT | |
| # VITE_-prefixed so it is never injected into the client bundle. | |
| # DEV_PORT=3000 | |
| # ------ AI Summarization (Vercel) ------ | |
| # Groq API (primary — 14,400 req/day on free tier) | |
| # Get yours at: https://console.groq.com/ | |
| GROQ_API_KEY= | |
| # OpenRouter API (fallback — 50 req/day on free tier) | |
| # Get yours at: https://openrouter.ai/ | |
| OPENROUTER_API_KEY= | |
| # Optional: forecast enrichment model routing | |
| # Defaults stay unchanged unless you set these. | |
| # Precedence: | |
| # combined-stage override -> critical-signal override -> global forecast override -> built-in defaults. | |
| # Examples: | |
| # FORECAST_LLM_COMBINED_PROVIDER_ORDER=openrouter | |
| # FORECAST_LLM_COMBINED_MODEL_OPENROUTER=google/gemini-2.5-pro | |
| # FORECAST_LLM_CRITICAL_PROVIDER_ORDER=openrouter | |
| # FORECAST_LLM_CRITICAL_MODEL_OPENROUTER=anthropic/claude-3.5-haiku | |
| FORECAST_LLM_PROVIDER_ORDER= | |
| FORECAST_LLM_MODEL_OPENROUTER= | |
| FORECAST_LLM_COMBINED_PROVIDER_ORDER= | |
| FORECAST_LLM_COMBINED_MODEL_OPENROUTER= | |
| FORECAST_LLM_CRITICAL_PROVIDER_ORDER= | |
| FORECAST_LLM_CRITICAL_MODEL_OPENROUTER= | |
| FORECAST_LLM_MARKET_IMPLICATIONS_PROVIDER_ORDER= | |
| FORECAST_LLM_MARKET_IMPLICATIONS_MODEL_OPENROUTER= | |
| # ------ Cross-User Cache (Vercel — Upstash Redis) ------ | |
| # Used to deduplicate AI calls and cache risk scores across visitors. | |
| # Create a free Redis database at: https://upstash.com/ | |
| UPSTASH_REDIS_REST_URL= | |
| UPSTASH_REDIS_REST_TOKEN= | |
| # ------ Self-hosted Redis (Docker Compose only) ------ | |
| # REQUIRED when running via docker-compose.yml. Both containers fail to start | |
| # if either variable is unset (no shipped defaults — see #3804). | |
| # Generate fresh values: openssl rand -hex 32 | |
| # | |
| # REDIS_PASSWORD → Redis AUTH password (--requirepass) | |
| # REDIS_TOKEN → Upstash-compatible REST proxy bearer token (SRH_TOKEN) | |
| # | |
| # Not used on Vercel — the Upstash hosted instance uses the UPSTASH_* | |
| # variables above instead. | |
| REDIS_PASSWORD= | |
| REDIS_TOKEN= | |
| # ------ Market Data (Vercel) ------ | |
| # Finnhub (primary stock quotes — free tier available) | |
| # Register at: https://finnhub.io/ | |
| FINNHUB_API_KEY= | |
| # ------ Energy Data (Vercel) ------ | |
| # U.S. Energy Information Administration (oil prices, production, inventory) | |
| # Register at: https://www.eia.gov/opendata/ | |
| EIA_API_KEY= | |
| # ------ Economic Data (Vercel) ------ | |
| # FRED (Federal Reserve Economic Data) | |
| # Register at: https://fred.stlouisfed.org/docs/api/api_key.html | |
| FRED_API_KEY= | |
| # IMF SDMX API (WEO, IRFCL gold reserves, national debt) | |
| # Currently OPTIONAL but recommended — IMF allows unauthenticated requests | |
| # today, but has flipped to hard 401 enforcement intermittently. Setting this | |
| # is forward-compatible. Azure APIM key sent as `Ocp-Apim-Subscription-Key`. | |
| # Register at: https://portal.api.imf.org/ → Products → IMF Data SDMX API → Subscribe | |
| IMF_API_KEY= | |
| # ------ Global Procurement Intelligence (Railway seed) ------ | |
| # Optional for the aggregate, required only for the U.S. SAM.gov adapter. | |
| # Sign in at SAM.gov, open Account Details, enter your account password, and | |
| # request/view a Public API Key. Store it only on the Railway seed service. | |
| # Official guide: https://open.gsa.gov/api/get-opportunities-public-api/ | |
| SAM_GOV_API_KEY= | |
| # ------ Air Quality Intelligence (Railway seed) ------ | |
| # OpenAQ API v3 (required for scripts/seed-health-air-quality.mjs) | |
| # Register at: https://docs.openaq.org/using-the-api/api-key | |
| OPENAQ_API_KEY= | |
| # WAQI API (optional supplement for additional city/station coverage) | |
| # Register at: https://aqicn.org/data-platform/token/ | |
| WAQI_API_KEY= | |
| # ------ Aviation Intelligence (Vercel) ------ | |
| # AviationStack (live flight data, airport flights, carrier ops) | |
| # Register at: https://aviationstack.com/ | |
| AVIATIONSTACK_API= | |
| # AviationStack quota guard (optional). The aviation seeder makes one paid | |
| # AviationStack call per monitored airport (~55) on every cron tick. This sets | |
| # the minimum minutes between paid fetches: when last-good intl delay data is | |
| # younger than this, the seeder serves last-good and skips the fetch, capping | |
| # spend regardless of cron cadence. Default 55. Set 0 to fetch every tick. | |
| # Values above 60 are clamped so freshness stays within the health staleness window. | |
| AVIATIONSTACK_MIN_REFRESH_MIN=55 | |
| # AviationStack monthly call budget (optional, hard ceiling). Every paid call — | |
| # the seeder AND the request-time panel endpoints (flights, flight-status) — | |
| # increments one shared Redis counter and refuses to call upstream once its | |
| # ceiling is hit, so total spend can never exceed your plan. | |
| # AVIATIONSTACK_MONTHLY_BUDGET all calls stop here (default 130000) | |
| # AVIATIONSTACK_REQUEST_BUDGET request-time calls stop earlier (default | |
| # 85000); the gap reserves headroom for the | |
| # curated seeder so panel traffic can't starve | |
| # the map/health data. | |
| # Defaults sum under a 135000-call plan with margin. Set MONTHLY budget to 0 to | |
| # disable the cap. | |
| AVIATIONSTACK_MONTHLY_BUDGET=130000 | |
| AVIATIONSTACK_REQUEST_BUDGET=85000 | |
| # ICAO API (NOTAM airport closures — optional, MENA region) | |
| # Register at: https://applications.icao.int/ | |
| ICAO_API_KEY= | |
| # Travelpayouts (flight price search — optional, demo only) | |
| # Register at: https://www.travelpayouts.com/ | |
| TRAVELPAYOUTS_API_TOKEN= | |
| # ------ Aircraft Tracking (Vercel) ------ | |
| # Wingbits aircraft enrichment (owner, operator, type) | |
| # Contact: https://wingbits.com/ | |
| WINGBITS_API_KEY= | |
| # ------ Conflict & Protest Data (Vercel) ------ | |
| # ACLED (Armed Conflict Location & Event Data — free for researchers) | |
| # Register at: https://acleddata.com/ | |
| # | |
| # RECOMMENDED: Set email + password for automatic OAuth token refresh. | |
| # ACLED access tokens expire every 24 hours; with these credentials, | |
| # the server will automatically exchange them for a fresh token. | |
| # | |
| # SECURITY NOTE: These credentials are stored in plaintext in .env.local. | |
| # This is an acceptable trade-off for a self-hosted dashboard because: | |
| # (a) .env.local is gitignored and never committed, | |
| # (b) ACLED accounts are free and grant read-only API access, | |
| # (c) the alternative (manual token rotation every 24 h) is impractical. | |
| # If this is a concern for your deployment, use ACLED_ACCESS_TOKEN instead | |
| # and manually refresh the token daily. | |
| ACLED_EMAIL= | |
| ACLED_PASSWORD= | |
| # | |
| # LEGACY: Static access token (optional fallback — expires after 24 h). | |
| # Only needed if you prefer not to store email/password above. | |
| # Generate at: https://acleddata.com/ → My Account → API Access | |
| ACLED_ACCESS_TOKEN= | |
| # UCDP (Uppsala Conflict Data Program — access token required since 2025) | |
| # Register at: https://ucdp.uu.se/apidocs/ | |
| UCDP_ACCESS_TOKEN= | |
| # ------ Internet Outages (Vercel) ------ | |
| # Cloudflare Radar API (requires free Cloudflare account with Radar access) | |
| CLOUDFLARE_API_TOKEN= | |
| # Cloudflare R2 account id for seed scripts that read or write R2 objects | |
| CLOUDFLARE_R2_ACCOUNT_ID= | |
| # Cloudflare R2 trace storage for forecast seed review artifacts | |
| # Create R2 access keys in Cloudflare and target the bucket you want to use for forecast traces. | |
| CLOUDFLARE_R2_BUCKET= | |
| CLOUDFLARE_R2_TRACE_BUCKET= | |
| CLOUDFLARE_R2_ACCESS_KEY_ID= | |
| CLOUDFLARE_R2_SECRET_ACCESS_KEY= | |
| CLOUDFLARE_R2_REGION=auto | |
| CLOUDFLARE_R2_TRACE_PREFIX=seed-data/forecast-traces | |
| # Dedicated private R2 bucket for the public bootstrap fast/slow tier objects. | |
| # These names are intentionally separate from CLOUDFLARE_R2_*: the bootstrap | |
| # bucket has two bucket-scoped credentials with different capabilities, and no | |
| # consumer may fall back to the forecast/military object credentials above. | |
| # | |
| # Shared routing values — set in both Railway production (publisher) and Vercel | |
| # production (edge reader). R2_ENDPOINT is optional; when empty, consumers derive | |
| # the standard account endpoint from R2_ACCOUNT_ID. | |
| R2_ACCOUNT_ID= | |
| R2_ENDPOINT= | |
| R2_BOOTSTRAP_BUCKET=worldmonitor-bootstrap | |
| # Tier membership depends on this feature flag. Set the same explicit value in | |
| # the Railway publisher and Vercel edge environments; do not let them drift. | |
| IRAN_EVENTS_ENABLED=false | |
| # Railway production publisher ONLY — bucket-scoped Object Read & Write. | |
| # Never install this write-capable credential in Vercel, preview, or development. | |
| R2_BOOTSTRAP_ACCESS_KEY_ID= | |
| R2_BOOTSTRAP_SECRET_ACCESS_KEY= | |
| # Vercel production edge reader ONLY — bucket-scoped Object Read. | |
| # Never install this credential in Railway, preview, or development. | |
| R2_BOOTSTRAP_READ_KEY_ID= | |
| R2_BOOTSTRAP_READ_SECRET= | |
| # Temporary U3a production-only measurement flag. Keep unset outside Vercel | |
| # production, and disable it before the R2 serving cutover. | |
| BOOTSTRAP_R2_SHADOW_MEASURE= | |
| # ------ Bootstrap tiers on Workers KV (Railway publisher; #5300 KV serving plan) ------ | |
| # The publisher writes each tier envelope to KV alongside R2 (best-effort; absent = skipped). | |
| # Namespace id is config, not a secret. Account id falls back to CLOUDFLARE_R2_ACCOUNT_ID. | |
| # The write token needs Workers KV Storage: Edit; KV's fast READ path is a Worker binding, | |
| # never this REST token. | |
| KV_BOOTSTRAP_NAMESPACE_ID= | |
| KV_BOOTSTRAP_WRITE_TOKEN= | |
| KV_BOOTSTRAP_ACCOUNT_ID= | |
| # ------ Satellite Fire Detection (Vercel) ------ | |
| # NASA FIRMS (Fire Information for Resource Management System) | |
| # Register at: https://firms.modaps.eosdis.nasa.gov/ | |
| # REQUIRED for scripts/seed-fire-detections.mjs — the seed exits non-zero if unset. | |
| NASA_FIRMS_API_KEY= | |
| # ------ Climate Disasters Seed (Railway cron) ------ | |
| # Required ReliefWeb app name for seed-climate-disasters.mjs. | |
| # ReliefWeb now rejects anonymous requests; use a pre-approved app name. | |
| RELIEFWEB_APPNAME= | |
| # ------ Railway Relay (scripts/ais-relay.cjs) ------ | |
| # The relay server handles AIS vessel tracking + OpenSky aircraft data + RSS proxy. | |
| # It can also run the Telegram OSINT poller (stateful MTProto) when configured. | |
| # Deploy on Railway with: node scripts/ais-relay.cjs | |
| # AISStream API key for live vessel positions | |
| # Get yours at: https://aisstream.io/ | |
| AISSTREAM_API_KEY= | |
| # OpenSky Network OAuth2 credentials (higher rate limits for cloud IPs) | |
| # Register at: https://opensky-network.org/ | |
| OPENSKY_CLIENT_ID= | |
| OPENSKY_CLIENT_SECRET= | |
| # Reddit data for the relay (social-velocity + WSB-tickers). The unauthenticated | |
| # www.reddit.com/.../hot.json endpoint is policy-blocked (HTTP 403) regardless of | |
| # IP/UA, and Reddit removed self-serve API-app creation (Responsible Builder | |
| # Policy 2026), so a new OAuth app can't be created. Path precedence in the relay: | |
| # 1. SCRAPECREATORS_API_KEY (PREFERRED) — third-party Reddit vendor, the only | |
| # path that works without a Reddit app. 1 credit/request; same key the | |
| # /last30days skill uses. Get one at scrapecreators.com. | |
| # 2. REDDIT_CLIENT_ID/SECRET — oauth.reddit.com userless client_credentials. | |
| # Usable ONLY with a PRE-policy (pre-~March-2026) "script" app's creds; | |
| # new apps can no longer be created. Set REDDIT_USER_AGENT to your username. | |
| # 3. (none set) → public endpoint, which currently 403s → SEED_ERROR surfaced. | |
| SCRAPECREATORS_API_KEY= | |
| REDDIT_CLIENT_ID= | |
| REDDIT_CLIENT_SECRET= | |
| REDDIT_USER_AGENT= | |
| # ------ Telegram OSINT (Railway relay) ------ | |
| # Telegram MTProto keys (free): https://my.telegram.org/apps | |
| TELEGRAM_API_ID= | |
| TELEGRAM_API_HASH= | |
| # GramJS StringSession generated locally (see: scripts/telegram/session-auth.mjs) | |
| TELEGRAM_SESSION= | |
| # Which curated list bucket to ingest: full | tech | finance | |
| TELEGRAM_CHANNEL_SET=full | |
| # ------ Self-Hosted LLM (Docker — any OpenAI-compatible endpoint) ------ | |
| # Point to your own LLM server (Ollama, vLLM, llama.cpp, etc.) | |
| # Used for intelligence assessments in the correlation engine. | |
| LLM_API_URL= | |
| LLM_API_KEY= | |
| LLM_MODEL= | |
| # Alternative: Ollama-specific URL (used if LLM_API_URL is not set) | |
| OLLAMA_API_URL= | |
| OLLAMA_MODEL= | |
| # ------ Railway Relay Connection (Vercel → Railway) ------ | |
| # Server-side URL (https://) — used by Vercel edge functions to reach the relay | |
| WS_RELAY_URL= | |
| # Optional client-side URL (wss://) — local/dev fallback only | |
| VITE_WS_RELAY_URL= | |
| # REQUIRED: Shared secret used to authenticate requests to the Railway relay | |
| # (scripts/ais-relay.cjs). Without it the relay refuses to start. Generate | |
| # with: openssl rand -hex 32 — then set the SAME value on every host that | |
| # runs the relay AND on every Vercel/server process that calls it. | |
| RELAY_SHARED_SECRET= | |
| # Header name used to send the relay secret (must match on both platforms) | |
| RELAY_AUTH_HEADER=x-relay-key | |
| # Dev-only escape hatch: lets the relay boot WITHOUT a shared secret. When set, | |
| # every non-public route is reachable by anyone who can hit the relay port and | |
| # a loud [SECURITY] line is logged at boot and every 5 minutes. Never set this | |
| # in production or on any internet-reachable host. Keep unset/false otherwise. | |
| # The legacy name ALLOW_UNAUTHENTICATED_RELAY=true is still accepted for back- | |
| # compat but is deprecated — prefer the verbose name below. | |
| I_UNDERSTAND_THIS_DISABLES_AUTH=false | |
| # ALLOW_UNAUTHENTICATED_RELAY=false # deprecated alias of the var above | |
| # Rolling window size (seconds) used by relay /metrics endpoint. | |
| RELAY_METRICS_WINDOW_SECONDS=60 | |
| # ------ Supply Chain Intelligence (Vercel / Railway relay) ------ | |
| # CorridorRisk API (maritime corridor risk scoring — optional) | |
| # Register at: https://corridorrisk.io/ | |
| CORRIDOR_RISK_API_KEY= | |
| # ------ Public Data Sources (no keys required) ------ | |
| # UNHCR (UN Refugee Agency) — public API, no auth (CC BY 4.0) | |
| # Open-Meteo — public API, no auth (processes Copernicus ERA5) | |
| # WorldPop — public API, no auth needed | |
| # ------ Site Configuration ------ | |
| # Site variant: "full" (worldmonitor.app) or "tech" (tech.worldmonitor.app) | |
| VITE_VARIANT=full | |
| # API base URL for web redirect. When set, browser fetch calls to /api/* | |
| # are redirected to this URL. Leave empty for same-domain API (local installs). | |
| # Production: https://api.worldmonitor.app | |
| VITE_WS_API_URL= | |
| # Client-side Sentry DSN (optional). Leave empty to disable error reporting. | |
| VITE_SENTRY_DSN= | |
| # Map interaction mode: | |
| # - "flat" keeps pitch/rotation disabled (2D interaction) | |
| # - "3d" enables pitch/rotation interactions (default) | |
| VITE_MAP_INTERACTION_MODE=3d | |
| # Self-hosted map tiles (optional — PMTiles on Cloudflare R2 or any HTTP server) | |
| # Leave empty to use free OpenFreeMap tiles. Set to your own PMTiles URL for self-hosted tiles. | |
| # See: https://protomaps.com/docs/pmtiles for how to generate PMTiles files. | |
| VITE_PMTILES_URL= | |
| # Public CORS-enabled URL for the same PMTiles file (used by Tauri desktop app). | |
| # If your VITE_PMTILES_URL is behind a reverse proxy without CORS, set this to the | |
| # direct R2/S3 public URL. The desktop app uses this URL; the web app uses VITE_PMTILES_URL. | |
| VITE_PMTILES_URL_PUBLIC= | |
| # ------ Desktop Cloud Fallback (Vercel) ------ | |
| # Comma-separated list of valid API keys for desktop cloud fallback. | |
| # Generate with: openssl rand -hex 24 | sed 's/^/wm_/' | |
| WORLDMONITOR_VALID_KEYS= | |
| # ------ Registration DB (Convex) ------ | |
| # Convex deployment URL for email registration storage. | |
| # Set up at: https://dashboard.convex.dev/ | |
| CONVEX_URL= | |
| # Convex HTTP actions / webhooks base URL (.convex.site host). | |
| # Use for httpRouter endpoints such as internal entitlement fallback and webhooks. | |
| # Example: https://happy-animal-123.convex.site | |
| CONVEX_SITE_URL= | |
| # Shared secret for Vercel gateway/edge + Railway relay -> Convex internal | |
| # service-to-service endpoints (entitlement fallback, /relay/*, internal MCP | |
| # token routes, etc.). Must be set to the same value in every environment. | |
| # Generate: openssl rand -hex 32 | |
| # | |
| # TRUST MODEL: these Convex endpoints accept a `userId` in the request body and | |
| # act on its behalf. That userId is NOT user-controlled — each trusted caller | |
| # (Vercel edge/gateway, Railway relay) authenticates the user first (Clerk JWT / | |
| # WM session / API key) and forwards the *verified* userId. This shared secret is | |
| # the trust boundary that proves "the caller is one of our own services", so guard | |
| # it accordingly: keep it out of logs, rotate periodically, and rotate in every | |
| # environment together (a one-sided rotation breaks the fallback for live users). | |
| CONVEX_SERVER_SHARED_SECRET= | |
| # Vite-exposed Convex URL for frontend entitlement service (VITE_ prefix required for client-side access) | |
| VITE_CONVEX_URL= | |
| # ------ Dodo Payments (Convex + Vercel) ------ | |
| # Dodo Payments API key (test mode or live mode) | |
| # Canonical name: DODO_API_KEY (used by convex/lib/dodo.ts and billing actions) | |
| # Get yours at: https://app.dodopayments.com/ -> Settings -> API Keys | |
| DODO_API_KEY= | |
| # Dodo Payments webhook secret for signature verification | |
| # NOTE: The @dodopayments/convex library reads DODO_PAYMENTS_WEBHOOK_SECRET internally. | |
| # Set BOTH this value AND DODO_PAYMENTS_WEBHOOK_SECRET to the same secret. | |
| # Get it at: https://app.dodopayments.com/ -> Developers -> Webhooks | |
| # Webhook URL to register in Dodo dashboard: | |
| # https://<your-convex-deployment>.convex.site/dodopayments-webhook | |
| DODO_WEBHOOK_SECRET= | |
| DODO_PAYMENTS_WEBHOOK_SECRET= | |
| # HMAC key for signing userId in checkout metadata (identity verification). | |
| # SEPARATE from DODO_PAYMENTS_WEBHOOK_SECRET — never reuse the same value. | |
| # Rotating one must not affect the other. | |
| # Generate: openssl rand -hex 32 | |
| DODO_IDENTITY_SIGNING_SECRET= | |
| # Dodo Payments business ID | |
| # Found at: https://app.dodopayments.com/ -> Settings | |
| DODO_BUSINESS_ID= | |
| # Dodo Payments environment for client-side checkout overlay | |
| # Values: "test_mode" (default) or "live_mode" | |
| VITE_DODO_ENVIRONMENT=test_mode | |
| # ------ Auth (Clerk) ------ | |
| # Clerk publishable key (browser-side, safe to expose) | |
| # Get from: Clerk Dashboard -> API Keys | |
| VITE_CLERK_PUBLISHABLE_KEY= | |
| # Clerk secret key (server-side only, never expose to browser) | |
| # Get from: Clerk Dashboard -> API Keys | |
| CLERK_SECRET_KEY= | |
| # Clerk JWT issuer domain (for Convex auth config) | |
| # Format: https://your-clerk-app.clerk.accounts.dev | |
| CLERK_JWT_ISSUER_DOMAIN= | |
| # ------ Cloud Preferences Sync ------ | |
| # Set to 'true' to enable server-side preferences sync for signed-in users. | |
| # Keep false until Phase 2 is QA-verified in staging. | |
| VITE_CLOUD_PREFS_ENABLED=false | |
| # Followed-countries watchlist primitive (PR A — U2/U3/U4/U5). | |
| # Default ON in dev/preview; set to '0' (literal string zero) to force-disable | |
| # the FollowButton + service entrypoints. Anything else (including 'true', | |
| # 'false', empty, unset) leaves the feature enabled. | |
| VITE_FOLLOW_COUNTRIES_ENABLED=true | |
| # Telegram bot username for deep link generation (without @) | |
| VITE_TELEGRAM_BOT_USERNAME=WorldMonitorBot | |
| # ------ Notification Delivery (Railway notification-relay service) ------ | |
| # @WorldMonitorBot token from BotFather. SEPARATE from TELEGRAM_API_ID/HASH/SESSION (MTProto). | |
| TELEGRAM_BOT_TOKEN= | |
| # Random 256-char secret for X-Telegram-Bot-Api-Secret-Token webhook verification. | |
| # Generate: openssl rand -hex 128 | |
| TELEGRAM_WEBHOOK_SECRET= | |
| # 32-byte base64 AES-256-GCM key for encrypting Slack webhook URLs at rest. | |
| # Generate: openssl rand -base64 32 | |
| # Railway env ONLY — never add to Convex dashboard. | |
| NOTIFICATION_ENCRYPTION_KEY= | |
| # Resend API key for email notification delivery. | |
| # Get from: resend.com/api-keys | |
| RESEND_API_KEY= | |
| # "From" address for email notifications (must be a verified Resend sender domain). | |
| # ALWAYS include a display name in "Name <addr@domain>" form — without it, Gmail | |
| # and Outlook fall back to rendering the local-part ("alerts") as the sender | |
| # name, which reads like a scary alarm even for editorial content. The digest | |
| # path (see RESEND_FROM_BRIEF below) enforces this at runtime via | |
| # scripts/lib/resend-from.cjs; set this var with the wrapper anyway so the | |
| # realtime-alert path (notification-relay.cjs) gets the same protection once | |
| # the relay-side normalizer lands. | |
| # RESEND_FROM_EMAIL is used by notification-relay.cjs for realtime push alerts. | |
| RESEND_FROM_EMAIL=WorldMonitor <alerts@worldmonitor.app> | |
| # "From" address for the daily Intelligence Brief / digest email. Kept | |
| # separate from RESEND_FROM_EMAIL so editorial mail doesn't ship from the | |
| # `alerts@` mailbox (which reads like an incident alarm). Used by | |
| # scripts/seed-digest-notifications.mjs, which normalizes the value via | |
| # scripts/lib/resend-from.cjs — a bare address is coerced to | |
| # "WorldMonitor Brief <addr>" at runtime with a loud warning, so a | |
| # misconfigured env cannot silently re-introduce the bare-local-part bug. | |
| # Falls back to RESEND_FROM_EMAIL if unset, so existing deploys keep working. | |
| RESEND_FROM_BRIEF=WorldMonitor Brief <brief@worldmonitor.app> | |
| # Vite-exposed Convex URL for frontend entitlement service (VITE_ prefix required for client-side access) | |
| VITE_CONVEX_URL= | |
| # ------ Dodo Payments (Convex + Vercel) ------ | |
| # Dodo Payments API key (test mode or live mode) | |
| # Canonical name: DODO_API_KEY (used by convex/lib/dodo.ts and billing actions) | |
| # Get yours at: https://app.dodopayments.com/ -> Settings -> API Keys | |
| DODO_API_KEY= | |
| # Dodo Payments webhook secret for signature verification | |
| # NOTE: The @dodopayments/convex library reads DODO_PAYMENTS_WEBHOOK_SECRET internally. | |
| # Set BOTH this value AND DODO_PAYMENTS_WEBHOOK_SECRET to the same secret. | |
| # Get it at: https://app.dodopayments.com/ -> Developers -> Webhooks | |
| DODO_WEBHOOK_SECRET= | |
| DODO_PAYMENTS_WEBHOOK_SECRET= | |
| # Dodo Payments business ID | |
| # Found at: https://app.dodopayments.com/ -> Settings | |
| DODO_BUSINESS_ID= | |
| # Dodo Payments environment for client-side checkout overlay | |
| # Values: "test_mode" (default) or "live_mode" | |
| VITE_DODO_ENVIRONMENT=test_mode | |
| # ------ Clerk Auth (Gateway JWT verification) ------ | |
| # Clerk JWT issuer domain — enables bearer token auth in the API gateway. | |
| # When set, the gateway verifies Authorization: Bearer <token> headers using | |
| # Clerk's JWKS endpoint and extracts the userId for entitlement checks. | |
| # Example: https://your-app.clerk.accounts.dev | |
| CLERK_JWT_ISSUER_DOMAIN= | |
| # Server-side Clerk publishable key used as an allowed JWT audience for | |
| # standard Clerk session tokens. Use the same value as the browser key, but | |
| # set it as a normal server env var too; the gateway does not read VITE_ vars. | |
| CLERK_PUBLISHABLE_KEY= | |
| # ------ Pro MCP (plan 2026-05-10-001) ------ | |
| # HMAC secret for the apex `/mcp-grant` → api-domain `/oauth/authorize-pro` | |
| # signed-grant bridge. Signs `{nonce, userId, exp, return_to}` server-to-server | |
| # so the api-domain can verify the apex Clerk session was real, atomically | |
| # consume the matching `mcp-grant:<n>` Redis one-shot, and issue an | |
| # `mcpProTokens` row. Required for Pro users to authorize Claude Desktop / | |
| # Cursor / claude.ai without pasting an API key. | |
| # Generate: openssl rand -base64 32 | |
| # MUST be set to the same value in BOTH the apex (worldmonitor.app) and | |
| # api-domain (api.worldmonitor.app) Vercel projects, otherwise grant | |
| # verification fails and Pro sign-in 401s on the bounce-back. | |
| MCP_PRO_GRANT_HMAC_SECRET= | |
| # HMAC secret for the internal MCP→gateway service-auth header | |
| # (`X-WM-MCP-Internal`). The MCP edge handler signs each outbound tool fetch | |
| # with `${ts}:${method}:${pathname}:${queryHash}:${bodyHash}:${userId}` and | |
| # the gateway verifies before setting the trusted-premium markers. Required | |
| # whenever a Pro MCP user calls a tool that internally re-fetches an | |
| # `/api/<x>` endpoint (any RPC tool that goes through the gateway). | |
| # Generate: openssl rand -base64 32 | |
| # Both values above MUST be set for Pro MCP to work end-to-end. Leaving | |
| # either unset makes the Pro path fail-closed: grant bridge rejects (401) | |
| # and tool fetches fail HMAC verify at the gateway (401). | |
| MCP_INTERNAL_HMAC_SECRET= | |
| # ============================================ | |
| # Additional integrations & data sources | |
| # ============================================ | |
| # Everything below is optional. Each key unlocks one feature, seed, or | |
| # integration; the dashboard runs without them (the corresponding feature is | |
| # disabled or falls back to a cached/empty result). Grouped by domain, with the | |
| # advanced runtime-tuning and developer-script knobs documented at the end. | |
| # ------ AI / LLM (additional) ------ | |
| # Anthropic Claude API. Used by scripts/translate-locales.mjs (locale i18n) and | |
| # as an optional forecast/brief LLM provider. | |
| # Get yours at: https://console.anthropic.com/ | |
| ANTHROPIC_API_KEY= | |
| # Auth token for a self-hosted/remote Ollama endpoint (sent as Bearer). Pairs | |
| # with the documented OLLAMA_API_URL / OLLAMA_MODEL. Leave empty for a local, | |
| # unauthenticated Ollama instance. | |
| OLLAMA_API_KEY= | |
| # Optional reasoning-model routing for server/_shared/llm.ts. Overrides the | |
| # provider/model used for "reasoning" LLM calls (brief-why-matters analyst, | |
| # chat-analyst stream, deduct-situation). Reasoning stays enabled for this | |
| # profile; utility calls send reasoning-off. | |
| # Examples: LLM_REASONING_PROVIDER=openrouter | |
| # LLM_REASONING_MODEL=deepseek/deepseek-v4-pro | |
| LLM_REASONING_PROVIDER= | |
| LLM_REASONING_MODEL= | |
| # Optional tool-model routing for server/_shared/llm.ts (callLlmTool profile: | |
| # cheap extraction/parsing tasks — no callers today, config kept valid). | |
| # Examples: LLM_TOOL_PROVIDER=openrouter | |
| # LLM_TOOL_MODEL=deepseek/deepseek-v4-flash | |
| LLM_TOOL_PROVIDER= | |
| LLM_TOOL_MODEL= | |
| # Forecast impact-stage LLM routing (sibling of the FORECAST_LLM_* knobs above). | |
| # Precedence matches the combined/critical overrides. | |
| # Examples: FORECAST_LLM_IMPACT_PROVIDER_ORDER=openrouter | |
| # FORECAST_LLM_IMPACT_MODEL_OPENROUTER=google/gemini-2.5-pro | |
| FORECAST_LLM_IMPACT_PROVIDER_ORDER= | |
| FORECAST_LLM_IMPACT_MODEL_OPENROUTER= | |
| # Caps how many forecasts get a full debug trace written (scripts/seed-forecasts.mjs). | |
| # Diagnostic only; leave unset for the built-in default. | |
| FORECAST_TRACE_MAX_FORECASTS= | |
| # ------ Market & Financial Data (additional) ------ | |
| # Alpha Vantage — fallback/bulk stock + commodity quotes (seed-market-quotes, | |
| # seed-commodity-quotes, seed-gulf-quotes). Free tier available. | |
| # Register at: https://www.alphavantage.co/support/#api-key | |
| ALPHA_VANTAGE_API_KEY= | |
| # CoinGecko — crypto market data (server market RPC + seed-crypto-sectors). | |
| # Optional; unauthenticated requests work at a lower rate limit. | |
| # Register at: https://www.coingecko.com/en/api | |
| # | |
| # Two paid/free tiers use DIFFERENT hosts + auth headers (the CG- key prefix is | |
| # shared, so the tier cannot be auto-detected — set whichever key you have): | |
| # - Pro (paid): COINGECKO_API_KEY -> pro-api.coingecko.com (x-cg-pro-api-key) | |
| # - Demo (free): COINGECKO_DEMO_API_KEY -> api.coingecko.com (x-cg-demo-api-key) | |
| # Pro takes precedence if both are set. A Demo key in COINGECKO_API_KEY fails | |
| # with HTTP 400 — put free Demo keys in COINGECKO_DEMO_API_KEY instead. | |
| COINGECKO_API_KEY= | |
| COINGECKO_DEMO_API_KEY= | |
| # ------ Energy Data (additional) ------ | |
| # GIE AGSI+ — European gas storage levels (seed-gie-gas-storage, | |
| # seed-gas-storage-countries). AGSI_API_KEY is accepted as an alias. | |
| # Register at: https://agsi.gie.eu/account | |
| GIE_API_KEY= | |
| # AGSI_API_KEY= # alias for GIE_API_KEY | |
| # ENTSO-E Transparency Platform — EU electricity prices/load (seed-electricity-prices). | |
| # Request a token at: https://transparency.entsoe.eu/ (Account → Web API) | |
| ENTSO_E_TOKEN= | |
| # ------ Economic & Trade Data (additional) ------ | |
| # UN Comtrade — bilateral trade flows + import-HHI resilience inputs. Accepts a | |
| # COMMA-SEPARATED list of subscription keys; the seeders rotate across them to | |
| # spread the per-key daily quota (see the Import-HHI runbook controls). | |
| # Register at: https://comtradedeveloper.un.org/ | |
| COMTRADE_API_KEYS= | |
| # UCDP conflict events (seed-ucdp-events). UCDP_ACCESS_TOKEN is canonical | |
| # (documented above); UC_DP_KEY is accepted as an alias. | |
| # UC_DP_KEY= # alias for UCDP_ACCESS_TOKEN | |
| # WTO — tariff/trade statistics (server trade RPC). Optional. | |
| # Register at: https://apiportal.wto.org/ | |
| WTO_API_KEY= | |
| # ------ Climate / Disaster / Weather (additional) ------ | |
| # NASA FIRMS fire detections (seed-fire-detections). NASA_FIRMS_API_KEY is the | |
| # canonical name (documented above); FIRMS_API_KEY is accepted as an alias. | |
| # FIRMS_API_KEY= # alias for NASA_FIRMS_API_KEY | |
| # ReliefWeb API "appname" identifier (seed-climate-news). RELIEFWEB_APPNAME is | |
| # canonical (documented above); RELIEFWEB_APP_NAME is accepted as an alias. | |
| # RELIEFWEB_APP_NAME= # alias for RELIEFWEB_APPNAME | |
| # Windy.com webcams API — webcam imagery (server webcam RPC). Optional. | |
| # Register at: https://api.windy.com/ | |
| WINDY_API_KEY= | |
| # ------ Cyber Threat Intelligence ------ | |
| # Feeds for server/worldmonitor/cyber/v1. Each is optional; the corresponding | |
| # threat source is skipped when its key is unset. | |
| # AbuseIPDB — IP reputation. https://www.abuseipdb.com/account/api | |
| ABUSEIPDB_API_KEY= | |
| # AlienVault OTX — open threat exchange pulses. https://otx.alienvault.com/api | |
| OTX_API_KEY= | |
| # abuse.ch URLhaus — malicious URL feed auth key. https://urlhaus.abuse.ch/ | |
| URLHAUS_AUTH_KEY= | |
| # ------ Web Scraping / Search ------ | |
| # Exa neural search — grocery-basket + bigmac price discovery. EXA_API_KEYS | |
| # accepts a comma-separated list (rotated); EXA_API_KEY is the single-key alias. | |
| # Register at: https://exa.ai/ | |
| EXA_API_KEYS= | |
| # EXA_API_KEY= # single-key alias for EXA_API_KEYS | |
| # Firecrawl — page scraping fallback for grocery-basket. Optional. | |
| # Register at: https://www.firecrawl.dev/ | |
| FIRECRAWL_API_KEY= | |
| # Brave Search — used by the relay's news/search loop. Comma-separated keys. | |
| # Register at: https://brave.com/search/api/ | |
| BRAVE_API_KEYS= | |
| # ------ External datasets (Supabase / R2) ------ | |
| # Supabase project that hosts the PIZZINT military-bases dataset | |
| # (scripts/fetch-pizzint-bases.mjs). Read-only anon access. | |
| SUPABASE_URL= | |
| SUPABASE_ANON_KEY= | |
| # Cloudflare R2 bearer token used by scripts/seed-military-bases.mjs to upload | |
| # the bundled bases artifact. Complements the documented CLOUDFLARE_R2_* keys. | |
| CLOUDFLARE_R2_TOKEN= | |
| # ------ OAuth integrations (Discord / Slack) ------ | |
| # Discord OAuth app — connects a user's Discord for notification delivery | |
| # (api/discord/oauth/*). All three are required to enable the Discord connect flow. | |
| # Create an app at: https://discord.com/developers/applications | |
| DISCORD_CLIENT_ID= | |
| DISCORD_CLIENT_SECRET= | |
| DISCORD_REDIRECT_URI= | |
| # Slack OAuth app — connects a user's Slack workspace for notifications | |
| # (api/slack/oauth/*). All three are required to enable the Slack connect flow. | |
| # Create an app at: https://api.slack.com/apps | |
| SLACK_CLIENT_ID= | |
| SLACK_CLIENT_SECRET= | |
| SLACK_REDIRECT_URI= | |
| # ------ Web Push (VAPID) ------ | |
| # Web Push (browser push notifications) keys used by scripts/notification-relay.cjs. | |
| # Generate a key pair with: npx web-push generate-vapid-keys | |
| # VAPID_SUBJECT is a mailto: or https: contact URI for the push service | |
| # (e.g. mailto:you@example.com). | |
| VAPID_PUBLIC_KEY= | |
| VAPID_PRIVATE_KEY= | |
| VAPID_SUBJECT= | |
| # ------ Auth, anti-abuse & signing ------ | |
| # Cloudflare Turnstile secret key — server-side CAPTCHA verification | |
| # (server/_shared/turnstile.ts). Pairs with the public site key in the frontend. | |
| # Register at: https://dash.cloudflare.com/?to=/:account/turnstile | |
| TURNSTILE_SECRET_KEY= | |
| # Secret used to sign the lightweight `wm-session` cookie (api/wm-session). | |
| # Generate: openssl rand -hex 32 | |
| WM_SESSION_SECRET= | |
| # Extra allowed Clerk JWT audience (server/auth-session.ts). Optional; set when | |
| # tokens are minted for a non-default audience. | |
| CLERK_JWT_AUDIENCE= | |
| # Auth keys for the embeddable widget agent endpoint (api/widget-agent.ts). | |
| # PRO_WIDGET_KEY gates Pro widget access; WIDGET_AGENT_KEY authenticates the | |
| # agent backend call. | |
| PRO_WIDGET_KEY= | |
| WIDGET_AGENT_KEY= | |
| # Pepper mixed into the hashed user-agent used for usage analytics | |
| # (server/_shared/usage.ts) so the stored hash isn't reversible. Generate: | |
| # openssl rand -hex 16 | |
| USAGE_UA_PEPPER= | |
| # ------ WorldMonitor internal keys & base URLs ------ | |
| # Premium/read API key used by seed + verification scripts to call the live | |
| # api.worldmonitor.app endpoints. Must be a value present in WORLDMONITOR_VALID_KEYS. | |
| WORLDMONITOR_API_KEY= | |
| # Warm-ping auth for relay/fallback seeds that call Vercel RPC endpoints | |
| # (seed-infra, seed-service-statuses, seed-military-maritime-news). Set to a | |
| # value already present in Vercel's WORLDMONITOR_VALID_KEYS. | |
| WORLDMONITOR_RELAY_KEY= | |
| # Seed-only refresh key for the resilience ranking recompute path | |
| # (get-resilience-ranking?refresh=1). Required by seed-resilience-scores.mjs. | |
| WORLDMONITOR_SEED_REFRESH_KEY= | |
| # API key the consumer-prices-core service requires for its snapshot endpoint | |
| # (consumer-prices-core/src/api/server.ts). | |
| WORLDMONITOR_SNAPSHOT_API_KEY= | |
| # Public base URL used when building shareable brief links (api/brief/share-url.ts). | |
| # Example: https://worldmonitor.app | |
| WORLDMONITOR_PUBLIC_BASE_URL= | |
| # API base-URL overrides. WM_API_BASE_URL / API_BASE_URL point seed + verify | |
| # scripts at a specific gateway; default is the production api domain. | |
| WM_API_BASE_URL= | |
| API_BASE_URL= | |
| # Site URL used to build payment checkout return links (convex/payments/checkout.ts). | |
| # Example: https://worldmonitor.app | |
| SITE_URL= | |
| # Base URL of the AIS relay, used by seeds that warm relay-backed caches | |
| # (e.g. seed-security-advisories). Default targets the deployed relay. | |
| RELAY_URL= | |
| # Optional outbound HTTP(S) proxy for scrapers that need a fixed egress IP | |
| # (scripts/_proxy-utils.cjs). YOUTUBE_PROXY_URL is a dedicated proxy for the | |
| # relay's YouTube live-channel checks. | |
| PROXY_URL= | |
| YOUTUBE_PROXY_URL= | |
| # ------ Intelligence Brief / Digest pipeline ------ | |
| # Master switches for the daily digest + brief generation (default off unless set). | |
| AI_DIGEST_ENABLED= | |
| DIGEST_CRON_ENABLED= | |
| # Notification-relay AI impact scoring toggle. | |
| AI_IMPACT_ENABLED= | |
| # Digest selection tuning (scripts/seed-digest-notifications.mjs + lib/brief-compose.mjs). | |
| # DIGEST_SCORE_MIN: minimum story score to include. | |
| # DIGEST_MAX_STORIES_PER_USER: cap on stories per user per issue. | |
| # DIGEST_ONLY_USER: restrict a run to a single userId (debug/backfill). | |
| # FOLLOWED_BIAS_MULTIPLIER: weight boost for followed-country stories. | |
| DIGEST_SCORE_MIN= | |
| DIGEST_MAX_STORIES_PER_USER= | |
| DIGEST_ONLY_USER= | |
| FOLLOWED_BIAS_MULTIPLIER= | |
| # Brief composition switches + signing secrets. | |
| # BRIEF_COMPOSE_ENABLED / BRIEF_LLM_ENABLED: enable compose + LLM enrichment. | |
| # BRIEF_VALIDATOR_MODE: validation strictness for seed-insights brief output. | |
| # BRIEF_SHARE_SECRET: signs public brief share tokens. | |
| # BRIEF_URL_SIGNING_SECRET (+ _PREV for rotation): signs per-user brief URLs. | |
| # BRIEF_WHY_MATTERS_ENDPOINT_URL: optional endpoint for "why it matters" text. | |
| # BRIEF_WHY_MATTERS_PRIMARY: which path serves whyMatters — 'analyst' (default) | |
| # or 'gemini' (stable fallback prompt); unknown values fall back to gemini. | |
| # BRIEF_WHY_MATTERS_SHADOW: '1' runs BOTH paths on cache miss and records | |
| # shadow pairs for offline comparison. DOUBLES spend while on (#4893) — | |
| # enable only for a bounded A/B window, then unset. | |
| # BRIEF_WHY_MATTERS_SHADOW_SAMPLE_PCT: 0-100 sampling for the shadow run | |
| # (default 100). | |
| BRIEF_COMPOSE_ENABLED= | |
| BRIEF_LLM_ENABLED= | |
| BRIEF_VALIDATOR_MODE= | |
| BRIEF_SHARE_SECRET= | |
| BRIEF_URL_SIGNING_SECRET= | |
| BRIEF_URL_SIGNING_SECRET_PREV= | |
| BRIEF_WHY_MATTERS_ENDPOINT_URL= | |
| BRIEF_WHY_MATTERS_PRIMARY= | |
| BRIEF_WHY_MATTERS_SHADOW= | |
| BRIEF_WHY_MATTERS_SHADOW_SAMPLE_PCT= | |
| # Destination address for contact-form submissions (server leads RPC). | |
| CONTACT_NOTIFY_EMAIL= | |
| # ------ Notification relay tuning (scripts/notification-relay.cjs) ------ | |
| # QUIET_HOURS_BATCH_ENABLED: batch alerts during a user's quiet hours. | |
| # IMPORTANCE_SCORE_LIVE: use live importance scoring vs cached. | |
| # IMPORTANCE_SCORE_MIN: minimum importance to dispatch a push. | |
| # NOTIFY_RELAY_INCLUDE_SNIPPET: include an article snippet in the payload. | |
| QUIET_HOURS_BATCH_ENABLED= | |
| IMPORTANCE_SCORE_LIVE= | |
| IMPORTANCE_SCORE_MIN= | |
| NOTIFY_RELAY_INCLUDE_SNIPPET= | |
| # ------ Seed & forecast feature flags ------ | |
| # CHAIN_FORECAST_SEED_ON_MILITARY: trigger a forecast seed after military-flights. | |
| # DISABLE_RELAY_MARKET_SEED: turn off the relay's inline market seeding loop. | |
| # MARKET_YAHOO_REFRESH_INTERVAL_MS: Yahoo stock refresh cadence in milliseconds | |
| # (default 900000 / 15 minutes; values below the 5-minute relay loop clamp up). | |
| # POLYMARKET_ENABLED: enable the relay's Polymarket prediction-markets loop. | |
| # SEED_FALLBACK_DISPLACEMENT / SEED_FALLBACK_NOTAM: serve a bundled fallback when | |
| # the live source is empty (displacement summary / NOTAM aviation closures). | |
| # FORCE_RESEED: bypass freshness gates and force a full reseed (one-off ops). | |
| # AVIATION_DEMO_PRICES: return demo flight-price data when no provider key is set. | |
| # NEWS_MAX_AGE_HOURS: max age for feed-digest articles. | |
| # LOCAL_API_MODE: relax auth/data-source assumptions for local dev of the API. | |
| CHAIN_FORECAST_SEED_ON_MILITARY= | |
| DISABLE_RELAY_MARKET_SEED= | |
| MARKET_YAHOO_REFRESH_INTERVAL_MS= | |
| POLYMARKET_ENABLED= | |
| SEED_FALLBACK_DISPLACEMENT= | |
| SEED_FALLBACK_NOTAM= | |
| FORCE_RESEED= | |
| AVIATION_DEMO_PRICES= | |
| NEWS_MAX_AGE_HOURS= | |
| LOCAL_API_MODE= | |
| # ------ Country Resilience Index config (server/worldmonitor/resilience) ------ | |
| # Feature gates for resilience methodology variants. Pillar combine and schema v2 | |
| # default on; set either explicitly to false only for rollback. Other gates default off. | |
| RESILIENCE_ENERGY_V2_ENABLED= | |
| RESILIENCE_FIN_SYS_EXPOSURE_ENABLED= | |
| RESILIENCE_PILLAR_COMBINE_ENABLED=true | |
| RESILIENCE_SCHEMA_V2_ENABLED=true | |
| # Override the WHO measles indicator code used by seed-resilience-static. | |
| RESILIENCE_WHO_MEASLES_INDICATOR= | |
| # Import-HHI diagnostics (see docs/railway-seed-consolidation-runbook.md → | |
| # "Import-HHI Comtrade 429 Runbook" for the full operational controls such as | |
| # COMTRADE_API_KEYS, IMPORT_HHI_PER_KEY_DELAY_MS and IMPORT_HHI_MAX_CONCURRENCY). | |
| # IMPORT_HHI_VERBOSE: per-reporter status logging during a force-refresh. | |
| # IMPORT_HHI_VERIFY_REDIS_ONLY / IMPORT_HHI_VERIFY_REPORTERS: scope the | |
| # verify-import-hhi-coverage.mjs check to Redis-only / a reporter subset. | |
| IMPORT_HHI_VERBOSE= | |
| IMPORT_HHI_VERIFY_REDIS_ONLY= | |
| IMPORT_HHI_VERIFY_REPORTERS= | |
| # ------ MCP server ------ | |
| # MCP_PROTOCOL_FLOOR_2025_06_18: kill-switch for the negotiated MCP protocol | |
| # floor. Default (unset) negotiates 2025-06-18 (also serving 2025-03-26 to | |
| # pinned clients). Set to `off` to pin the server back to the legacy | |
| # [2025-03-26]-only floor. | |
| # MCP_TELEMETRY: enable MCP usage telemetry. | |
| MCP_PROTOCOL_FLOOR_2025_06_18= | |
| MCP_TELEMETRY= | |
| # Bearer + endpoint for scripts/capture-mcp-fixture.mjs (dev fixture capture). | |
| WM_MCP_BEARER= | |
| WM_MCP_ENDPOINT= | |
| # ------ Infra / runtime tuning ------ | |
| # Redis client timeouts (server/_shared/redis.ts), in milliseconds. | |
| REDIS_OP_TIMEOUT_MS= | |
| REDIS_PIPELINE_TIMEOUT_MS= | |
| # CORS allow-list overrides. | |
| # CORS_ORIGIN: allowed origin(s) for the consumer-prices-core service. | |
| # ALLOW_VERCEL_PREVIEW_ORIGINS: also allow *.vercel.app preview origins on the relay. | |
| CORS_ORIGIN= | |
| ALLOW_VERCEL_PREVIEW_ORIGINS= | |
| # Observability. | |
| # AXIOM_API_TOKEN: ship usage logs to Axiom (server/_shared/usage.ts). | |
| # USAGE_TELEMETRY: toggle usage telemetry collection. | |
| # LOG_LEVEL: log verbosity for the consumer-prices-core service. | |
| AXIOM_API_TOKEN= | |
| USAGE_TELEMETRY= | |
| LOG_LEVEL= | |
| # ------ Consumer Prices Core service ------ | |
| # Standalone service under consumer-prices-core/ (its own Railway services). | |
| # DATABASE_URL: Postgres connection string. | |
| # CONSUMER_PRICES_CORE_API_KEY / _BASE_URL: how the main app's | |
| # scripts/seed-consumer-prices.mjs reaches this service. | |
| # CONSUMER_PRICES_DEFAULT_MARKET: default market when none is specified. | |
| DATABASE_URL= | |
| CONSUMER_PRICES_CORE_API_KEY= | |
| CONSUMER_PRICES_CORE_BASE_URL= | |
| CONSUMER_PRICES_DEFAULT_MARKET= | |
| # ------ Convex / Payments (additional) ------ | |
| # CONVEX_DEPLOY_KEY: deploy/admin key for Convex CLI + maintenance scripts. | |
| # CONVEX_IS_DEV: marks a dev Convex deployment (convex/lib/auth.ts). | |
| # DODO_PAYMENTS_ENVIRONMENT: server-side Dodo env ("test_mode" | "live_mode"), | |
| # the non-VITE counterpart of VITE_DODO_ENVIRONMENT. | |
| CONVEX_DEPLOY_KEY= | |
| CONVEX_IS_DEV= | |
| DODO_PAYMENTS_ENVIRONMENT= | |
| # ------ Frontend feature flags (Vite, additional) ------ | |
| # Client-side toggles read via import.meta.env at build time (must keep the | |
| # VITE_ prefix to be exposed to the browser). Defaults shown reflect the code's | |
| # fallback when the var is unset. | |
| # VITE_ENABLE_AIS: maritime AIS layer (default ON; set "false" to disable). | |
| # VITE_ENABLE_CYBER_LAYER: cyber-threat map layer (default OFF; set "true" to enable). | |
| # VITE_DIGEST_CRON_ENABLED: show digest cron UI (default ON; set "0" to disable). | |
| # VITE_QUIET_HOURS_BATCH_ENABLED: quiet-hours batching UI (default ON; set "0" to disable). | |
| # VITE_RELAY_GATES_READY: treat relay gates as ready (default OFF; set "1" to enable). | |
| # VITE_RSS_DIRECT_TO_RELAY: route RSS fetches straight to the relay (default OFF; "true" to enable). | |
| VITE_ENABLE_AIS= | |
| VITE_ENABLE_CYBER_LAYER= | |
| VITE_DIGEST_CRON_ENABLED= | |
| VITE_QUIET_HOURS_BATCH_ENABLED= | |
| VITE_RELAY_GATES_READY= | |
| VITE_RSS_DIRECT_TO_RELAY= | |
| # ============================================ | |
| # Advanced: relay runtime tuning | |
| # ============================================ | |
| # These only affect scripts/ais-relay.cjs (the long-running AIS relay service). | |
| # Every one has a sane built-in default — leave unset unless you are tuning the | |
| # relay's memory, throughput, or upstream backpressure behaviour. | |
| # AISStream websocket API key (alias of the documented AISSTREAM_API_KEY). | |
| # VITE_AISSTREAM_API_KEY= | |
| # Vessel store + snapshot cadence. | |
| AIS_MAX_VESSELS= | |
| AIS_MAX_VESSEL_HISTORY= | |
| AIS_SNAPSHOT_INTERVAL_MS= | |
| # Upstream ingest backpressure (queue water marks + drain budget). | |
| AIS_UPSTREAM_QUEUE_HIGH_WATER= | |
| AIS_UPSTREAM_QUEUE_LOW_WATER= | |
| AIS_UPSTREAM_QUEUE_HARD_CAP= | |
| AIS_UPSTREAM_DRAIN_BATCH= | |
| AIS_UPSTREAM_DRAIN_BUDGET_MS= | |
| # Relay process behaviour + rate limits. | |
| RELAY_ENV= | |
| RELAY_GATES_READY= | |
| RELAY_LOG_THROTTLE_MS= | |
| RELAY_MEMORY_CLEANUP_GB= | |
| RELAY_RATE_LIMIT_MAX= | |
| RELAY_RATE_LIMIT_WINDOW_MS= | |
| RELAY_OPENSKY_RATE_LIMIT_MAX= | |
| RELAY_OREF_RATE_LIMIT_MAX= | |
| RELAY_RSS_RATE_LIMIT_MAX= | |
| # Telegram ingest loop tuning (relay-side). | |
| TELEGRAM_POLL_INTERVAL_MS= | |
| TELEGRAM_MAX_FEED_ITEMS= | |
| TELEGRAM_MAX_TEXT_CHARS= | |
| TELEGRAM_RATE_LIMIT_MS= | |
| TELEGRAM_STARTUP_DELAY_MS= | |
| # ------ Advanced: flight tracking (OpenSky / OREF) ------ | |
| # OpenSky request cache + rate-limit shaping (relay + seed-military-flights). | |
| OPENSKY_CACHE_TTL_MS= | |
| OPENSKY_CACHE_MAX_ENTRIES= | |
| OPENSKY_NEGATIVE_CACHE_TTL_MS= | |
| OPENSKY_NEGATIVE_CACHE_MAX_ENTRIES= | |
| OPENSKY_BBOX_QUANT_STEP= | |
| OPENSKY_429_COOLDOWN_MS= | |
| OPENSKY_REQUEST_SPACING_MS= | |
| # Bearer auth for an OpenSky fetch proxy, if you front OpenSky behind one. | |
| OPENSKY_PROXY_AUTH= | |
| # OREF (Israel Home Front Command) alert ingest. | |
| OREF_DATA_DIR= | |
| OREF_POLL_INTERVAL_MS= | |
| OREF_PROXY_AUTH= | |
| # ============================================ | |
| # Platform-injected (do NOT set manually) | |
| # ============================================ | |
| # Provided automatically by the CI / hosting platform; listed for completeness. | |
| # GITHUB_SHA — set by GitHub Actions (build provenance). | |
| # RAILWAY_GIT_COMMIT_SHA — set by Railway (build provenance / forecast trace). | |
| # VERCEL_ENV — set by Vercel ("production" | "preview" | "development"). | |
| # VERCEL_GIT_COMMIT_SHA — set by Vercel (build provenance). | |
| # ============================================ | |
| # Developer / maintenance script knobs | |
| # ============================================ | |
| # These are NOT deployment config. They are per-invocation overrides read by | |
| # one-off audit / capture / verification scripts under scripts/. Documented for | |
| # completeness; you normally pass them inline, e.g. `TOP_N=20 node scripts/...`. | |
| # scripts/audit-resilience-cohorts.mjs — cohort audit run controls: | |
| # API_BASE, FIXTURE, BASELINE, OUT, TOP_N, MOVERS_N, CONCURRENCY, STRICT, | |
| # CONTRIB_TOLERANCE | |
| # scripts/capture-resilience-energy-v2-acceptance.mjs: | |
| # BASELINE_RANKING_SNAPSHOT, POST_FLIP_RANKING_SNAPSHOT, | |
| # RESILIENCE_ENERGY_V2_SAMPLE_COUNTRIES | |
| # scripts/dry-run-resilience-rebalance.mjs: | |
| # PRE_RANKING_KEY | |
| # scripts/freeze-resilience-ranking.mjs: | |
| # RESILIENCE_RANKING_METHODOLOGY_FORMULA, RESILIENCE_RANKING_FORMULA_CHECK_COUNTRIES, | |
| # RESILIENCE_RANKING_FORMULA_TOLERANCE, RESILIENCE_RANKING_OUTPUT_BASENAME, | |
| # RESILIENCE_RANKING_REFRESH, USER_AGENT | |
| # scripts/shadow-score-report.mjs: SHADOW_SCORE_KEY | |
| # scripts/validate-seed-migration.mjs: WORLDMONITOR_KEY (read key) | |
| # scripts/seed-webcams.mjs: KEY_PREFIX (Redis key prefix override) | |
| # scripts/import-gem-pipelines.mjs: GEM_PIPELINES_FILE (input dataset path) | |
| # scripts/_seed-utils.mjs: BUNDLE_RUN_STARTED_AT_MS (set by the | |
| # bundle wrapper; coordinates member skips) | |