byob-go-cli

Layered config with provenance: env > file > default

byob-config.2 config

Problem: mytool config get foo returns bar — from where? The env? The file? The default? Without provenance, users can't debug config issues.

Idea: resolve values through a documented layer order: (1) environment variable override (MYTOOL_FOO) (2) project/host config file (3) built-in default

Each resolved value carries its source ("env:MYTOOL_FOO", "file:/path.toml", "default"). A --show-source flag on config get prints the source.

Tradeoffs: slightly more machinery than a flat struct. Alternative: track only values, lose debuggability. Rule: if you support config files AND env overrides AND defaults, you owe users provenance.

Design

type Value[T any] struct {
    V      T
    Source string // "env:MYTOOL_FOO" | "file" | "default"
}

func ResolveString(envKey, fileVal, def string) Value[string] {
    if v := os.Getenv(envKey); v != "" {
        return Value[string]{v, "env:" + envKey}
    }
    if fileVal != "" {
        return Value[string]{fileVal, "file"}
    }
    return Value[string]{def, "default"}
}

// usage:
endpoint := ResolveString("MYTOOL_ENDPOINT", fileCfg.Endpoint, "https://api.example.com")
fmt.Printf("%s (from %s)\n", endpoint.V, endpoint.Source)