// Package config loads GitManager's runtime configuration from the environment // (and an optional .env file). See AGENT.md §1.5 — every setting is env-driven; // nothing is hardcoded. package config import ( "os" "strconv" "strings" "time" "github.com/joho/godotenv" ) // Config is the typed application configuration. type Config struct { ListenAddr string // address the HTTP server binds to RepoRoots []string // roots to scan for git repositories GitBin string // path to the git binary ScanInterval time.Duration // scanner refresh interval ScanMaxDepth int // max discovery depth under each root ScanIgnore []string // directory names to skip during discovery ScanFetchEnabled bool // allow the scanner to run `git fetch` Dev bool // readable console logging vs structured JSON LogFile string // optional file to also append logs to GitHubToken string // optional forge token (AGENT.md §8) GitLabToken string // optional forge token (AGENT.md §8) } // Load reads .env (if present) then the environment, applying defaults. // A missing .env is not an error — the environment may be set another way. func Load() (Config, error) { _ = godotenv.Load() c := Config{ ListenAddr: env("LISTEN_ADDR", "127.0.0.1:8080"), RepoRoots: splitList(env("GIT_REPO_ROOTS", "")), GitBin: env("GIT_BIN", "git"), ScanMaxDepth: envInt("SCAN_MAX_DEPTH", 4), ScanIgnore: splitList(env("SCAN_IGNORE", "node_modules,vendor,.cache")), ScanFetchEnabled: envBool("SCAN_FETCH_ENABLED", false), Dev: strings.EqualFold(env("APP_ENV", "dev"), "dev"), LogFile: env("LOG_FILE", ""), GitHubToken: env("GITHUB_TOKEN", ""), GitLabToken: env("GITLAB_TOKEN", ""), } interval, err := time.ParseDuration(env("SCAN_INTERVAL", "30s")) if err != nil { return Config{}, err } c.ScanInterval = interval return c, nil } func env(key, def string) string { if v, ok := os.LookupEnv(key); ok && v != "" { return v } return def } func envInt(key string, def int) int { if v, ok := os.LookupEnv(key); ok { if n, err := strconv.Atoi(strings.TrimSpace(v)); err == nil { return n } } return def } func envBool(key string, def bool) bool { if v, ok := os.LookupEnv(key); ok { if b, err := strconv.ParseBool(strings.TrimSpace(v)); err == nil { return b } } return def } // splitList splits a comma-separated value, trimming spaces and dropping empties. func splitList(v string) []string { var out []string for _, part := range strings.Split(v, ",") { if p := strings.TrimSpace(part); p != "" { out = append(out, p) } } return out }