package config import ( "context" "fmt" "os" "path/filepath" "gopkg.in/yaml.v3" ) // yamlLoader loads config from YAML files with hierarchical precedence. type yamlLoader struct{} // NewYAMLLoader returns a Loader that reads from YAML files. func NewYAMLLoader() Loader { return &yamlLoader{} } func (l *yamlLoader) Load(ctx context.Context, workdir string) (Config, error) { defaults := LoadDefault() // Load from workdir path := filepath.Join(workdir, "rony.yaml") if _, err := os.Stat(path); err == nil { cfg, err := loadFromFile(path) if err != nil { return Config{}, fmt.Errorf("loading %s: %w", path, err) } // Merge with defaults cfg = merge(defaults, cfg) return cfg, nil } // Load from home directory home, err := os.UserHomeDir() if err == nil { homePath := filepath.Join(home, ".config", "rony", "config.yaml") if _, err := os.Stat(homePath); err == nil { cfg, err := loadFromFile(homePath) if err == nil { return merge(defaults, cfg), nil } } } return defaults, nil } func loadFromFile(path string) (Config, error) { data, err := os.ReadFile(path) if err != nil { return Config{}, err } var cfg Config if err := yaml.Unmarshal(data, &cfg); err != nil { return Config{}, fmt.Errorf("parsing YAML: %w", err) } return cfg, nil } // merge combines two configs, with b taking precedence over a. func merge(a, b Config) Config { if b.Model != "" { a.Model = b.Model } if b.Provider.Type != "" { a.Provider.Type = b.Provider.Type } if b.Provider.Model != "" { a.Provider.Model = b.Provider.Model } if b.Provider.APIKey != "" { a.Provider.APIKey = b.Provider.APIKey } if b.Provider.BaseURL != "" { a.Provider.BaseURL = b.Provider.BaseURL } if b.Provider.MaxTokens > 0 { a.Provider.MaxTokens = b.Provider.MaxTokens } if b.Provider.Temperature > 0 { a.Provider.Temperature = b.Provider.Temperature } if b.Tools.DefaultPermission != "" { a.Tools.DefaultPermission = b.Tools.DefaultPermission } if len(b.Tools.AllowList) > 0 { a.Tools.AllowList = b.Tools.AllowList } if len(b.Tools.DenyList) > 0 { a.Tools.DenyList = b.Tools.DenyList } if b.Logging.Level != "" { a.Logging.Level = b.Logging.Level } if b.Logging.Format != "" { a.Logging.Format = b.Logging.Format } if b.Logging.Output != "" { a.Logging.Output = b.Logging.Output } return a }