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
This commit is contained in:
cyrus@tinyhumans.ai
2026-03-31 16:55:19 +05:30
parent 48272e47d3
commit 06f013719c
9 changed files with 195 additions and 16 deletions
+121
View File
@@ -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=
+13
View File
@@ -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)
+23
View File
@@ -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
+1 -3
View File
@@ -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
// ---------------------------------------------------------------------------
+2 -4
View File
@@ -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);
}
+3 -4
View File
@@ -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
+5 -2
View File
@@ -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', () => ({
+12 -3
View File
@@ -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;
+15
View File
@@ -1,5 +1,20 @@
/// <reference types="vite/client" />
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 {