From 06f013719c2f9c478538b9815f98b036ed6d02ff Mon Sep 17 00:00:00 2001 From: "cyrus@tinyhumans.ai" Date: Tue, 31 Mar 2026 16:55:19 +0530 Subject: [PATCH] refactor(config): centralize env vars, add .env.example files, remove stale config - Centralize all VITE_* env var reads in app/src/utils/config.ts (SENTRY_DSN, BACKEND_URL, DEV_JWT_TOKEN) - Update consumers (analytics.ts, backendUrl.ts, store/index.ts) to import from config.ts - Add TypeScript type declarations for ImportMetaEnv in vite-env.d.ts - Remove dead import.meta.env.OPENHUMAN_CORE_RPC_URL fallback (non-VITE prefix, never exposed by Vite) - Remove unused TELEGRAM_BOT_USERNAME/TELEGRAM_BOT_ID from test mocks - Create .env.example (root) documenting ~30 Rust/Tauri env vars with [required]/[optional] tags - Create app/.env.example documenting 6 frontend VITE_* vars - Add Configuration section to CLAUDE.md Closes #61 --- .env.example | 121 +++++++++++++++++++++++++++++++++ CLAUDE.md | 13 ++++ app/.env.example | 23 +++++++ app/src/services/analytics.ts | 4 +- app/src/services/backendUrl.ts | 6 +- app/src/store/index.ts | 7 +- app/src/test/setup.ts | 7 +- app/src/utils/config.ts | 15 +++- app/src/vite-env.d.ts | 15 ++++ 9 files changed, 195 insertions(+), 16 deletions(-) create mode 100644 .env.example create mode 100644 app/.env.example diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..fc2cfcd82 --- /dev/null +++ b/.env.example @@ -0,0 +1,121 @@ +# Root environment variables — Rust core, Tauri shell, and shared settings. +# Copy to .env and fill in values as needed. +# Loaded via: source scripts/load-dotenv.sh +# +# Tags: [required] must be set, [optional] has a sensible default or can be blank + +# --------------------------------------------------------------------------- +# Backend API +# --------------------------------------------------------------------------- +# [required] Primary backend URL (read by Rust core and QuickJS skills sandbox) +BACKEND_URL=https://staging-api.alphahuman.xyz +# [required] Also read by Vite frontend (VITE_ prefix required for browser exposure) +VITE_BACKEND_URL=https://staging-api.alphahuman.xyz + +# --------------------------------------------------------------------------- +# Core process +# --------------------------------------------------------------------------- +# [optional] Default: 7788 +OPENHUMAN_CORE_PORT=7788 +# [optional] Default: http://127.0.0.1:7788/rpc +OPENHUMAN_CORE_RPC_URL=http://127.0.0.1:7788/rpc +# [optional] Run mode: child (default, spawns sidecar) | inprocess +OPENHUMAN_CORE_RUN_MODE=child +# [optional] Override path to openhuman core binary (leave blank for auto-detection) +OPENHUMAN_CORE_BIN= + +# --------------------------------------------------------------------------- +# Config overrides (override config.toml values at runtime) +# --------------------------------------------------------------------------- +# [required] API key for the LLM provider +OPENHUMAN_API_KEY= +# [optional] Default model to use +OPENHUMAN_MODEL= +# [optional] Workspace directory (default: ~/.openhuman) +OPENHUMAN_WORKSPACE= +# [optional] Default: 0.7 +OPENHUMAN_TEMPERATURE=0.7 + +# --------------------------------------------------------------------------- +# Runtime flags +# --------------------------------------------------------------------------- +# [optional] Default: 0 +OPENHUMAN_BROWSER_ALLOW_ALL=0 +# [optional] Default: 0 +OPENHUMAN_LOG_PROMPTS=0 +# [optional] Enable reasoning mode +OPENHUMAN_REASONING_ENABLED= + +# --------------------------------------------------------------------------- +# Web search +# --------------------------------------------------------------------------- +# [optional] Default: false +OPENHUMAN_WEB_SEARCH_ENABLED=false +# [optional] Search provider name +OPENHUMAN_WEB_SEARCH_PROVIDER= +# [optional] Required if web search provider is Brave +OPENHUMAN_BRAVE_API_KEY= +# [optional] Default: 5 +OPENHUMAN_WEB_SEARCH_MAX_RESULTS=5 +# [optional] Default: 10 +OPENHUMAN_WEB_SEARCH_TIMEOUT_SECS=10 + +# --------------------------------------------------------------------------- +# Storage +# --------------------------------------------------------------------------- +# [optional] Storage backend (default: SQLite) +OPENHUMAN_STORAGE_PROVIDER= +# [optional] Database connection URL +OPENHUMAN_STORAGE_DB_URL= +# [optional] Connection timeout in seconds +OPENHUMAN_STORAGE_CONNECT_TIMEOUT_SECS= + +# --------------------------------------------------------------------------- +# Proxy +# --------------------------------------------------------------------------- +# [optional] Default: false +OPENHUMAN_PROXY_ENABLED=false +# [optional] HTTP proxy URL +OPENHUMAN_HTTP_PROXY= +# [optional] HTTPS proxy URL +OPENHUMAN_HTTPS_PROXY= +# [optional] Catch-all proxy URL +OPENHUMAN_ALL_PROXY= +# [optional] Comma-separated hosts to bypass proxy +OPENHUMAN_NO_PROXY= +# [optional] Proxy scope +OPENHUMAN_PROXY_SCOPE= +# [optional] Comma-separated services to proxy +OPENHUMAN_PROXY_SERVICES= + +# --------------------------------------------------------------------------- +# Local AI binary overrides +# --------------------------------------------------------------------------- +# [optional] Override path to whisper binary +WHISPER_BIN= +# [optional] Override path to piper binary +PIPER_BIN= +# [optional] Override path to ollama binary +OLLAMA_BIN= + +# --------------------------------------------------------------------------- +# Skills +# --------------------------------------------------------------------------- +# [optional] Override skills registry URL +SKILLS_REGISTRY_URL= + +# --------------------------------------------------------------------------- +# Logging +# --------------------------------------------------------------------------- +# [optional] Default: info +RUST_LOG=info +# [optional] Default: 0 (set to 1 for full backtraces) +RUST_BACKTRACE=1 + +# --------------------------------------------------------------------------- +# Testing (do not set in production) +# --------------------------------------------------------------------------- +# [optional] Enable mock service mode +# OPENHUMAN_SERVICE_MOCK=0 +# [optional] Path to mock state file +# OPENHUMAN_SERVICE_MOCK_STATE_FILE= diff --git a/CLAUDE.md b/CLAUDE.md index 035a428dc..155c8d47c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -75,6 +75,19 @@ cargo check --manifest-path app/src-tauri/Cargo.toml --- +## Configuration + +Environment variables are documented in two `.env.example` files: + +- **[`.env.example`](.env.example)** (repo root) — Rust core, Tauri shell, backend URL, logging, proxy, storage, web search, local AI binary overrides. Loaded via `source scripts/load-dotenv.sh`. +- **[`app/.env.example`](app/.env.example)** — Frontend `VITE_*` vars (core RPC URL, backend URL, Sentry DSN, skills repo, dev helpers). Copy to `app/.env.local` for local overrides. + +**Frontend config** is centralized in [`app/src/utils/config.ts`](app/src/utils/config.ts). All `VITE_*` env vars should be read there and re-exported — do not read `import.meta.env` directly in other files. + +**Rust config** uses a TOML-based `Config` struct (`src/openhuman/config/schema/types.rs`) with env var overrides applied in `src/openhuman/config/schema/load.rs`. Env vars override config file values at runtime (e.g. `OPENHUMAN_API_KEY` overrides `config.api_key`). + +--- + ## Testing Guide (Unit + E2E) ### Unit tests (Vitest) diff --git a/app/.env.example b/app/.env.example new file mode 100644 index 000000000..e681b986d --- /dev/null +++ b/app/.env.example @@ -0,0 +1,23 @@ +# Frontend (Vite) environment variables +# Copy to app/.env.local and fill in values as needed. +# Only VITE_-prefixed vars are exposed to the browser. +# +# Tags: [required] must be set, [optional] has a sensible default or can be blank + +# [optional] Core RPC endpoint (Tauri sets this automatically; override for web-only dev) +VITE_OPENHUMAN_CORE_RPC_URL=http://127.0.0.1:7788/rpc + +# [required] Backend API URL (web fallback when core RPC is unavailable) +VITE_BACKEND_URL=https://staging-api.alphahuman.xyz + +# [optional] Skills GitHub repository slug (default: tinyhumansai/openhuman-skills) +VITE_SKILLS_GITHUB_REPO=tinyhumansai/openhuman-skills + +# [optional] Sentry DSN for error reporting (leave blank to disable) +VITE_SENTRY_DSN= + +# [optional] Dev-only: auto-inject JWT token to skip login flow +VITE_DEV_JWT_TOKEN= + +# [optional] Dev-only: force onboarding flow to always show +VITE_DEV_FORCE_ONBOARDING=false diff --git a/app/src/services/analytics.ts b/app/src/services/analytics.ts index 480ed9986..be8dd3992 100644 --- a/app/src/services/analytics.ts +++ b/app/src/services/analytics.ts @@ -21,11 +21,9 @@ import * as Sentry from '@sentry/react'; import { store } from '../store'; -import { IS_DEV } from '../utils/config'; +import { IS_DEV, SENTRY_DSN } from '../utils/config'; import { enqueueError, registerSentrySender, type SanitizedSentryEvent } from './errorReportQueue'; -const SENTRY_DSN = import.meta.env.VITE_SENTRY_DSN as string | undefined; - // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- diff --git a/app/src/services/backendUrl.ts b/app/src/services/backendUrl.ts index 8f3334f73..28a712bc3 100644 --- a/app/src/services/backendUrl.ts +++ b/app/src/services/backendUrl.ts @@ -1,5 +1,6 @@ import { isTauri as coreIsTauri } from '@tauri-apps/api/core'; +import { BACKEND_URL } from '../utils/config'; import { callCoreRpc } from './coreRpcClient'; let resolvedBackendUrl: string | null = null; @@ -10,10 +11,7 @@ function normalizeBaseUrl(url: string): string { } function webFallbackBackendUrl(): string { - const fromVite = - typeof import.meta.env.VITE_BACKEND_URL === 'string' - ? import.meta.env.VITE_BACKEND_URL.trim() - : ''; + const fromVite = typeof BACKEND_URL === 'string' ? BACKEND_URL.trim() : ''; if (fromVite) { return normalizeBaseUrl(fromVite); } diff --git a/app/src/store/index.ts b/app/src/store/index.ts index c935eb7a9..4ffd00000 100644 --- a/app/src/store/index.ts +++ b/app/src/store/index.ts @@ -12,7 +12,7 @@ import { } from 'redux-persist'; import storage from 'redux-persist/lib/storage'; -import { IS_DEV } from '../utils/config'; +import { DEV_JWT_TOKEN, IS_DEV } from '../utils/config'; import { logout as clearRustSession, storeSession, @@ -147,9 +147,8 @@ export const store = configureStore({ export const persistor = persistStore(store, null, () => { // Dev-only: auto-inject JWT token for local testing without login flow. - const devToken = import.meta.env.VITE_DEV_JWT_TOKEN; - if (devToken && !store.getState().auth.token) { - store.dispatch(setToken(devToken)); + if (DEV_JWT_TOKEN && !store.getState().auth.token) { + store.dispatch(setToken(DEV_JWT_TOKEN)); console.log('[dev] Auto-injected JWT token from VITE_DEV_JWT_TOKEN'); // Auto-mark user as onboarded once their profile is fetched diff --git a/app/src/test/setup.ts b/app/src/test/setup.ts index 0d9db568d..48a1a226e 100644 --- a/app/src/test/setup.ts +++ b/app/src/test/setup.ts @@ -57,10 +57,13 @@ vi.mock('../utils/tauriCommands', () => ({ // Mock the config module vi.mock('../utils/config', () => ({ - TELEGRAM_BOT_USERNAME: 'test_bot', - TELEGRAM_BOT_ID: '12345', + CORE_RPC_URL: 'http://127.0.0.1:7788/rpc', IS_DEV: true, + DEV_FORCE_ONBOARDING: false, SKILLS_GITHUB_REPO: 'test/skills', + SENTRY_DSN: undefined, + BACKEND_URL: 'http://localhost:5005', + DEV_JWT_TOKEN: undefined, })); vi.mock('../services/backendUrl', () => ({ diff --git a/app/src/utils/config.ts b/app/src/utils/config.ts index 06f638f8c..f3859c5eb 100644 --- a/app/src/utils/config.ts +++ b/app/src/utils/config.ts @@ -1,7 +1,5 @@ export const CORE_RPC_URL = - import.meta.env.OPENHUMAN_CORE_RPC_URL || - import.meta.env.VITE_OPENHUMAN_CORE_RPC_URL || - 'http://127.0.0.1:7788/rpc'; + import.meta.env.VITE_OPENHUMAN_CORE_RPC_URL || 'http://127.0.0.1:7788/rpc'; export const IS_DEV = import.meta.env.DEV; @@ -11,3 +9,14 @@ export const DEV_FORCE_ONBOARDING = export const SKILLS_GITHUB_REPO = import.meta.env.VITE_SKILLS_GITHUB_REPO || 'tinyhumansai/openhuman-skills'; + +/** Sentry DSN for error reporting. Leave blank to disable. */ +export const SENTRY_DSN = import.meta.env.VITE_SENTRY_DSN as string | undefined; + +/** Backend API URL (web fallback when core RPC is unavailable). */ +export const BACKEND_URL = import.meta.env.VITE_BACKEND_URL as string | undefined; + +/** Dev only: auto-inject JWT token to skip login flow. */ +export const DEV_JWT_TOKEN = import.meta.env.DEV + ? (import.meta.env.VITE_DEV_JWT_TOKEN as string | undefined) + : undefined; diff --git a/app/src/vite-env.d.ts b/app/src/vite-env.d.ts index 29c058a0d..19161aae7 100644 --- a/app/src/vite-env.d.ts +++ b/app/src/vite-env.d.ts @@ -1,5 +1,20 @@ /// +interface ImportMetaEnv { + readonly VITE_OPENHUMAN_CORE_RPC_URL?: string; + readonly VITE_BACKEND_URL?: string; + readonly VITE_SKILLS_GITHUB_REPO?: string; + readonly VITE_SENTRY_DSN?: string; + readonly VITE_DEV_JWT_TOKEN?: string; + readonly VITE_DEV_FORCE_ONBOARDING?: string; + readonly DEV: boolean; + readonly MODE: string; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +} + // Node.js polyfills for browser declare global { interface Window {