package main import ( "os" "path/filepath" "strings" ) const defaultBalanceURL = "https://api.deepseek.com/user/balance" // Config is the fully-resolved runtime configuration. Values are loaded with // precedence: process environment > .env file > built-in default. type Config struct { APIKey string BalanceURL string // Harness daemon settings; consumed by the harness feature in a later step. Harness harnessConfig } // LoadConfig resolves configuration from the .env file in dir (best-effort), // overlaid with process environment variables, then defaults. func LoadConfig(dir string) Config { dotenv := readDotEnv(filepath.Join(dir, ".env")) cfg := Config{ BalanceURL: defaultBalanceURL, Harness: harnessConfig{ Enabled: true, Command: "npx --yes @deepseek-ai/dsh web", URL: "http://127.0.0.1:3080", StartDelay: 15, LogFile: "harness.log", }, } cfg.APIKey = firstNonEmpty(os.Getenv("DEEPSEEK_API_KEY"), dotenv["DEEPSEEK_API_KEY"]) if v := firstNonEmpty(os.Getenv("DEEPSEEK_BALANCE_URL"), dotenv["DEEPSEEK_BALANCE_URL"]); v != "" { cfg.BalanceURL = v } return cfg } func firstNonEmpty(values ...string) string { for _, v := range values { if v != "" { return v } } return "" } // readDotEnv parses a simple KEY=VALUE file. Missing files yield an empty map. // Lines are trimmed; '#' starts a comment; quotes are stripped from values. func readDotEnv(path string) map[string]string { data, err := os.ReadFile(path) if err != nil { return nil } m := make(map[string]string) for _, raw := range strings.Split(string(data), "\n") { line := strings.TrimSpace(raw) if line == "" || line[0] == '#' { continue } eq := strings.IndexByte(line, '=') if eq < 1 { continue } k := strings.TrimSpace(line[:eq]) v := strings.TrimSpace(line[eq+1:]) m[k] = strings.Trim(v, `"'`) } return m }