package config import ( "encoding/json" "fmt" "os" "path/filepath" "strings" ) // ResolveHome returns the effective home for config/data paths. // TASK_MASTER_HOME overrides the real user home (useful for demos/dev sandboxes). func ResolveHome() (string, error) { if h := strings.TrimSpace(os.Getenv(envHome)); h != "" { return filepath.Clean(h), nil } home, err := os.UserHomeDir() if err != nil { return "", fmt.Errorf("resolve home directory: %w", err) } return home, nil } // LoadOrCreate loads the config from disk, creating defaults if missing. // It also ensures DataDir and the active project directory exist. func LoadOrCreate() (*Config, error) { home, err := ResolveHome() if err != nil { return nil, err } cfgPath := Path(home) cfgDir := filepath.Dir(cfgPath) if err := os.MkdirAll(cfgDir, 0o755); err != nil { return nil, fmt.Errorf("create config dir %s: %w", cfgDir, err) } cfg, err := readOrCreate(cfgPath, home) if err != nil { return nil, err } if d := strings.TrimSpace(os.Getenv(envDataDir)); d != "" { cfg.DataDir = filepath.Clean(d) } if err := ensureDataDirs(cfg); err != nil { return nil, err } return cfg, nil } func readOrCreate(cfgPath, home string) (*Config, error) { data, err := os.ReadFile(cfgPath) if err != nil { if os.IsNotExist(err) { cfg := Defaults(home) if err := Save(&cfg); err != nil { return nil, fmt.Errorf("write default config: %w", err) } return &cfg, nil } return nil, fmt.Errorf("read config %s: %w", cfgPath, err) } var cfg Config if err := json.Unmarshal(data, &cfg); err != nil { return nil, fmt.Errorf("parse config %s: %w", cfgPath, err) } cfg.ApplyDefaults(home) return &cfg, nil } // Save writes the config to ~/.config/task-master/config.json // (or under TASK_MASTER_HOME when set). func Save(cfg *Config) error { home, err := ResolveHome() if err != nil { return err } cfgPath := Path(home) if err := os.MkdirAll(filepath.Dir(cfgPath), 0o755); err != nil { return fmt.Errorf("create config dir: %w", err) } data, err := json.MarshalIndent(cfg, "", " ") if err != nil { return fmt.Errorf("marshal config: %w", err) } data = append(data, '\n') if err := os.WriteFile(cfgPath, data, 0o644); err != nil { return fmt.Errorf("write config %s: %w", cfgPath, err) } return nil } func ensureDataDirs(cfg *Config) error { if err := os.MkdirAll(cfg.DataDir, 0o755); err != nil { return fmt.Errorf("create data dir %s: %w", cfg.DataDir, err) } projectDir := filepath.Join(cfg.DataDir, "projects", cfg.ActiveProject) if err := os.MkdirAll(projectDir, 0o755); err != nil { return fmt.Errorf("create project dir %s: %w", projectDir, err) } return nil }