// Package config loads smartrund's runtime configuration from environment // variables (a single-user local app has no need for a config file/flags // beyond this). package config import ( "fmt" "os" "strconv" "time" ) // Config holds everything smartrund needs to start. type Config struct { // Addr is the HTTP listen address, e.g. ":8080". Addr string // DBPath is the SQLite database file path. DBPath string // GarminPythonPath is mcp-garmin's venv python executable. GarminPythonPath string // GarminServerPath is mcp-garmin's server.py. GarminServerPath string GarminEmail string GarminPassword string // GarminTokenStore, if set, overrides mcp-garmin's default ~/.garth // session cache location. GarminTokenStore string MaxHR float64 MinConfidence float64 BackfillHorizonDays int IncrementalSyncEvery time.Duration } // Load reads configuration from environment variables, applying defaults // for anything optional. Returns an error if a required variable is unset. func Load() (Config, error) { cfg := Config{ Addr: getEnvDefault("SMARTRUN_ADDR", ":8080"), DBPath: getEnvDefault("SMARTRUN_DB_PATH", "smartrun.db"), GarminPythonPath: os.Getenv("MCP_GARMIN_PYTHON"), GarminServerPath: os.Getenv("MCP_GARMIN_SERVER"), GarminEmail: os.Getenv("GARMIN_EMAIL"), GarminPassword: os.Getenv("GARMIN_PASSWORD"), GarminTokenStore: os.Getenv("GARMIN_TOKENSTORE"), MaxHR: getEnvFloat("SMARTRUN_MAX_HR", 190), MinConfidence: getEnvFloat("SMARTRUN_MIN_CONFIDENCE", 0.6), BackfillHorizonDays: getEnvInt("SMARTRUN_BACKFILL_HORIZON_DAYS", 3*365), IncrementalSyncEvery: getEnvDuration("SMARTRUN_INCREMENTAL_SYNC_EVERY", 6*time.Hour), } if cfg.GarminPythonPath == "" { return cfg, fmt.Errorf("MCP_GARMIN_PYTHON is required (path to mcp-garmin's venv python)") } if cfg.GarminServerPath == "" { return cfg, fmt.Errorf("MCP_GARMIN_SERVER is required (path to mcp-garmin's server.py)") } if cfg.GarminEmail == "" || cfg.GarminPassword == "" { return cfg, fmt.Errorf("GARMIN_EMAIL and GARMIN_PASSWORD are required") } return cfg, nil } func getEnvDefault(key, def string) string { if v := os.Getenv(key); v != "" { return v } return def } func getEnvFloat(key string, def float64) float64 { if v := os.Getenv(key); v != "" { if f, err := strconv.ParseFloat(v, 64); err == nil { return f } } return def } func getEnvInt(key string, def int) int { if v := os.Getenv(key); v != "" { if n, err := strconv.Atoi(v); err == nil { return n } } return def } func getEnvDuration(key string, def time.Duration) time.Duration { if v := os.Getenv(key); v != "" { if d, err := time.ParseDuration(v); err == nil { return d } } return def }