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}