package config import ( "os" "path/filepath" "github.com/spf13/viper" ) type Config struct { DoksStorageDir string `json:"doksStorageDir" mapstructure:"doksStorageDir"` DefaultEditor string `json:"defaultEditor" mapstructure:"defaultEditor"` Search SearchConfig `json:"search" mapstructure:"search"` Embeddings EmbedConfig `json:"embeddings" mapstructure:"embeddings"` Display DisplayConfig `json:"display" mapstructure:"display"` } type SearchConfig struct { Engine string `json:"engine" mapstructure:"engine"` VectorEnabled bool `json:"vectorEnabled" mapstructure:"vectorEnabled"` } type EmbedConfig struct { Provider string `json:"provider" mapstructure:"provider"` BaseURL string `json:"baseUrl" mapstructure:"baseUrl"` APIKey string `json:"apiKey" mapstructure:"apiKey"` Model string `json:"model" mapstructure:"model"` } type DisplayConfig struct { ContextLines int `json:"contextLines" mapstructure:"contextLines"` MaxResults int `json:"maxResults" mapstructure:"maxResults"` } func DefaultConfig() *Config { homeDir, _ := os.UserHomeDir() return &Config{ DoksStorageDir: filepath.Join(homeDir, "doks"), DefaultEditor: "vim", Search: SearchConfig{ Engine: "text", VectorEnabled: false, }, Embeddings: EmbedConfig{ Provider: "openai", BaseURL: "https://api.openai.com/v1", APIKey: "", Model: "text-embedding-3-small", }, Display: DisplayConfig{ ContextLines: 3, MaxResults: 20, }, } } func Load() (*Config, error) { homeDir, err := os.UserHomeDir() if err != nil { return nil, err } configDir := filepath.Join(homeDir, ".config", "doks") if err := os.MkdirAll(configDir, 0755); err != nil { return nil, err } viper.SetConfigName("config") viper.SetConfigType("json") viper.AddConfigPath(configDir) // Set defaults defaults := DefaultConfig() viper.SetDefault("doksStorageDir", defaults.DoksStorageDir) viper.SetDefault("defaultEditor", defaults.DefaultEditor) viper.SetDefault("search.engine", defaults.Search.Engine) viper.SetDefault("search.vectorEnabled", defaults.Search.VectorEnabled) viper.SetDefault("embeddings.provider", defaults.Embeddings.Provider) viper.SetDefault("embeddings.baseUrl", defaults.Embeddings.BaseURL) viper.SetDefault("embeddings.apiKey", defaults.Embeddings.APIKey) viper.SetDefault("embeddings.model", defaults.Embeddings.Model) viper.SetDefault("display.contextLines", defaults.Display.ContextLines) viper.SetDefault("display.maxResults", defaults.Display.MaxResults) if err := viper.ReadInConfig(); err != nil { if _, ok := err.(viper.ConfigFileNotFoundError); ok { // Config file not found, create with defaults configPath := filepath.Join(configDir, "config.json") if err := viper.SafeWriteConfigAs(configPath); err != nil { return nil, err } } else { return nil, err } } var cfg Config if err := viper.Unmarshal(&cfg); err != nil { return nil, err } // Expand ~ in storage dir cfg.DoksStorageDir = expandPath(cfg.DoksStorageDir) // Ensure storage directory exists if err := os.MkdirAll(cfg.DoksStorageDir, 0755); err != nil { return nil, err } // Ensure doksFiles subdirectory exists filesDir := filepath.Join(cfg.DoksStorageDir, "doksFiles") if err := os.MkdirAll(filesDir, 0755); err != nil { return nil, err } return &cfg, nil } func (c *Config) RegistryPath() string { return filepath.Join(c.DoksStorageDir, "doksRegistry.db") } func (c *Config) FilesDir() string { return filepath.Join(c.DoksStorageDir, "doksFiles") } func (c *Config) FilePath(filename string) string { return filepath.Join(c.FilesDir(), filename) } func (c *Config) VectorsDir() string { return filepath.Join(c.DoksStorageDir, "vectors") } func expandPath(path string) string { if len(path) > 0 && path[0] == '~' { homeDir, err := os.UserHomeDir() if err != nil { return path } return filepath.Join(homeDir, path[1:]) } return path }