loader.go

 1// SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
 2//
 3// SPDX-License-Identifier: AGPL-3.0-or-later
 4
 5package config
 6
 7import (
 8	"fmt"
 9	"os"
10	"path/filepath"
11	"strings"
12
13	"github.com/spf13/viper"
14)
15
16// LoadConfig loads configuration from file, environment variables, and defaults
17func LoadConfig(configPath string) (*Config, error) {
18	cfg := Default()
19
20	v := viper.New()
21
22	// Set config file
23	if configPath != "" {
24		v.SetConfigFile(configPath)
25	} else {
26		// Look for config in current directory and common locations
27		v.SetConfigName("planning-mcp-server")
28		v.SetConfigType("toml")
29		v.AddConfigPath(".")
30		v.AddConfigPath("$HOME/.config/planning-mcp-server")
31		v.AddConfigPath("/etc/planning-mcp-server")
32	}
33
34	// Environment variables
35	v.SetEnvPrefix("PLANNING")
36	v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
37	v.AutomaticEnv()
38
39	// Read config file if it exists
40	if err := v.ReadInConfig(); err != nil {
41		if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
42			return nil, fmt.Errorf("failed to read config file: %w", err)
43		}
44		// Config file not found is OK, we'll use defaults
45	}
46
47	// Unmarshal into config struct
48	if err := v.Unmarshal(cfg); err != nil {
49		return nil, fmt.Errorf("failed to unmarshal config: %w", err)
50	}
51
52	// Validate configuration
53	if err := cfg.Validate(); err != nil {
54		return nil, fmt.Errorf("invalid configuration: %w", err)
55	}
56
57	return cfg, nil
58}
59
60// GenerateExampleConfig generates an example configuration file
61func GenerateExampleConfig(path string) error {
62	cfg := Default()
63
64	// Ensure directory exists
65	dir := filepath.Dir(path)
66	if err := os.MkdirAll(dir, 0o755); err != nil {
67		return fmt.Errorf("failed to create config directory: %w", err)
68	}
69
70	v := viper.New()
71	v.SetConfigType("toml")
72
73	// Set all config values
74	v.Set("server.mode", cfg.Server.Mode)
75	v.Set("server.host", cfg.Server.Host)
76	v.Set("server.port", cfg.Server.Port)
77
78	v.Set("logging.level", cfg.Logging.Level)
79	v.Set("logging.format", cfg.Logging.Format)
80
81	v.Set("planning.max_tasks", cfg.Planning.MaxTasks)
82	v.Set("planning.max_goal_length", cfg.Planning.MaxGoalLength)
83	v.Set("planning.max_task_length", cfg.Planning.MaxTaskLength)
84	v.Set("planning.history_enabled", cfg.Planning.HistoryEnabled)
85
86	if err := v.WriteConfigAs(path); err != nil {
87		return fmt.Errorf("failed to write config file: %w", err)
88	}
89
90	return nil
91}