# God's Eye View — environment variables # Copy to .env and fill in your keys. On macOS the launcher can also read keys # from the Keychain (see README); on Linux/Windows use this file or env vars. # # Easiest path: don't edit anything. Run the app and paste keys into the # in-app Provider Settings panel (the POWER UP chip, bottom-right) — it writes # this checkout's .env for you (owner-only permissions) and restarts the dev # server. Under the Pinokio launcher it writes pinokio/ENVIRONMENT instead. # Keys you supply yourself (shell env, Keychain) are shown as configured # externally and never touched. This file remains the reference for headless # and self-hosted setups. # # NOTE ON CLIENT-EXPOSED KEYS: GOOGLE_MAPS_API_KEY and CESIUM_ION_TOKEN are # injected into the browser bundle by design (they're used client-side) and # WILL be visible in devtools. Restrict/scope them rather than trying to hide # them (see SECURITY.md). All other keys below stay server-side. # Optional: direct Google Photorealistic 3D Tiles and GEV place search. # Without it, a Cesium ion token can still load ion-hosted Google 3D; with # neither credential, the app starts on keyless Esri World Imagery with OSM # available in the map tray and as the automatic provider-failure fallback. # CLIENT-EXPOSED — restrict it (HTTP referrer + API restriction) in Google Cloud. GOOGLE_MAPS_API_KEY= # Optional: opt-in per-IP rate limit for the Google Places cost endpoint # (/api/google/nearby-places), in requests per minute. # DEFAULT IS UNLIMITED — unset (or 0) means no throttling, unchanged behavior. # Set a positive integer to cap per-IP Places quota spend when self-hosting. # Recommended whenever HOST is not localhost. NOTE: this is a per-IP, # process-local guard (in-memory, resets on restart; over-limit requests get a # sanitized 429) — it is NOT a billing cap. For hard spend protection set # provider-side budget alerts: Google Cloud Console -> Billing -> Budgets & alerts # (plus per-API quotas under APIs & Services -> Quotas). # GEV_RATELIMIT_GOOGLE_PER_MIN=60 # Optional: Google Photorealistic 3D Tiles through Cesium ion, Bing world # imagery, and Cesium World Terrain. The free Community plan is for eligible # personal/non-commercial use and has quotas; check current Cesium terms. A # direct Google key above is still required for GEV place search. # CLIENT-EXPOSED — use a public assets:read token with URL restrictions. CESIUM_ION_TOKEN= # Optional: OpenAI Realtime voice control. Do not prefix with VITE_. OPENAI_API_KEY= OPENAI_REALTIME_MODEL=gpt-realtime-2 # Cheaper voice tier, used when the GEV MIC panel's MINI toggle is on. # Model ids and prices are external facts that drift — if OpenAI moves this id, # override it here rather than editing src/voice/voiceCost.js. OPENAI_REALTIME_MODEL_MINI=gpt-realtime-2.1-mini OPENAI_REALTIME_VOICE=marin OPENAI_REALTIME_REASONING_EFFORT=low # Keep only a short conversational window. Current map state is fetched live. OPENAI_REALTIME_CONTEXT_TOKENS=3000 OPENAI_REALTIME_CONTEXT_RETENTION=0.5 OPENAI_HUD_SUMMARY_MODEL=gpt-5-nano # Optional: opt-in per-IP rate limit for the OpenAI cost endpoints # (/api/realtime/token + /api/openai/hud-summary), in requests per minute. # DEFAULT IS UNLIMITED — unset (or 0) means no throttling, unchanged behavior. # Set a positive integer to cap per-IP spend when self-hosting on a network. # Recommended whenever HOST is not localhost. NOTE: this is a per-IP, # process-local guard (in-memory, resets on restart; over-limit requests get a # sanitized 429) — it is NOT a billing cap. For hard spend protection set a # provider-side budget: OpenAI platform -> Settings -> Limits (usage limits). # GEV_RATELIMIT_OPENAI_PER_MIN=30 # Optional: OpenSky auth mode (oauth | basic | auto | anon) # Default: oauth — requires client credentials below # Set to "anon" to skip aircraft auth (rate-limited but works) OPENSKY_AUTH_MODE=oauth # Optional: OpenSky OAuth credentials (from opensky-network.org account dashboard) OPENSKY_CLIENT_ID= OPENSKY_CLIENT_SECRET= # Optional: higher Launch Library 2 request allowance. Public access works # without a token. LL2_API_TOKEN= # Optional: OpenSky credentials JSON file path (alternative to above) # OPENSKY_CREDENTIALS_FILE=/path/to/credentials.json # Optional: dev server config PORT=4173 # Network binding. Default is localhost — the server is reachable ONLY from # this machine. This is the safe default: the dev server brokers your # configured API keys, so anyone who can reach it can spend your quota. # LAN opt-in: set HOST=0.0.0.0 to share on your local network. WARNING: that # exposes every key-brokering proxy to everyone on the network — use only on # networks you trust, and consider the GEV_RATELIMIT_* throttles above. # HOST=localhost # HOST=0.0.0.0 # Optional: NASA FIRMS live active fires. SERVER-SIDE ONLY (never reaches the # browser — the client polls the /api/firms proxy). Free key: # https://firms.modaps.eosdis.nasa.gov/api/map_key/ # Without a key the FIRMS Active Fires layer is empty ("KEY REQUIRED"). # FIRMS_MAP_KEY= # Optional: Live AIS vessels layer # Browser fetch path. Keep same-origin so the private stream key stays server-side. VITE_AIS_LIVE_API_URL=/api/ais-live # Client render cap. The server can keep a larger AISStream cache, but drawing # every global vessel at once can overwhelm WebGL on laptops. VITE_AIS_LIVE_MAX_ROWS=12000 # Active label cap after screen-space clustering. Labels exist for all rendered # ships, but only clustered winners are shown to keep text readable. VITE_AIS_LIVE_LABEL_MAX_ROWS=900 # Private AISStream key for the server-side websocket. Do not prefix with VITE_. AISSTREAM_API_KEY= # Optional AISStream subscription filters. # AISSTREAM_BOUNDING_BOXES=[[[-90,-180],[90,180]]] # AISSTREAM_MESSAGE_TYPES=PositionReport,StandardClassBPositionReport,ExtendedClassBPositionReport,ShipStaticData,StaticDataReport # Feed watchdog: silence (ms) after which the feed is REPORTED stale; the socket # is recycled at 2.5x that. Default 120000. The watchdog self-arms only for the # default worldwide subscription — a narrow filter above can be legitimately # quiet, so set this explicitly (sized for your filter) to opt back in. # 0 disables the silence watch entirely. # AISSTREAM_SILENCE_TIMEOUT_MS=120000 # Optional: TomTom live traffic flow (BYOK freemium — free tier is ~50,000 tile # requests/day, get a key at https://developer.tomtom.com). SERVER-SIDE ONLY: # the browser fetches same-origin /api/tomtom/* and never sees the key. # Keyless fallback: the traffic layer runs its built-in simulation (white dots, # hardcoded per-road-class speeds) — no key required for the layer to work. # TOMTOM_API_KEY= # Optional soft cap on upstream tile fetches per UTC day (default 40000) — a # configurable application safety ceiling, not a guarantee of staying within # TomTom's free allowance (currently 200K tile requests/month: # https://docs.tomtom.com/pricing/). Over the cap the proxy serves cached/stale # tiles instead of hitting upstream. # TOMTOM_DAILY_TILE_BUDGET=40000 # Optional: CCTV layer tuning (advanced). Defaults are sensible — leave unset # unless you're customizing the camera source pack. # CCTV_SOURCES_FILE=config/cctv_sources.austin.json # path to a source-pack JSON # CCTV_SOURCES_JSON= # inline source-pack JSON (overrides file) # CCTV_AUSTIN_ROWS_URL= # override the Austin Open Data catalog URL # CCTV_AUSTIN_MAX_SOURCES=36 # cap cameras loaded from the Austin catalog # CCTV_MAX_SOURCES=48 # overall cap on registered cameras # CCTV_PREFER_AUSTIN=1 # prioritize Austin cameras in the registry # CCTV_FORCE_AUSTIN= # force Austin-only sources # CCTV_AUTO_CALIBRATE=1 # auto-calibrate camera projection (client flag) # CCTV_DRAPE_MESH=1 # drape frames onto 3D geometry (client flag)