package config import ( "encoding/json" "fmt" "os" "path/filepath" ) const ( AppConfigVersion = 1 ConfigDirName = "verstak" AppConfigFileName = "config.json" ) // AppConfig is the global application config stored at ~/.config/verstak/config.json. type AppConfig struct { Version int `json:"version"` VaultPath string `json:"vault_path"` Theme string `json:"theme"` Language string `json:"language"` EnabledTemplates []string `json:"enabled_templates"` EnabledPlugins []string `json:"enabled_plugins"` FirstRunCompleted bool `json:"first_run_completed"` Window WindowConfig `json:"window,omitempty"` Vault VaultAppConfig `json:"vault,omitempty"` } type WindowConfig struct { Width int `json:"width,omitempty"` Height int `json:"height,omitempty"` } // VaultAppConfig holds per-vault settings in the global config. type VaultAppConfig struct { VaultID string `json:"vault_id,omitempty"` CreatedAt string `json:"created_at,omitempty"` Sync SyncSettings `json:"sync,omitempty"` } // SyncSettings holds sync configuration for the current vault. type SyncSettings struct { Enabled bool `json:"enabled"` ServerURL string `json:"server_url"` DeviceID string `json:"device_id"` DeviceName string `json:"device_name"` SyncInterval int `json:"sync_interval"` LastStatus string `json:"last_status"` LastSyncAt string `json:"last_sync_at"` LastError string `json:"last_error,omitempty"` } func DefaultAppConfig() *AppConfig { return &AppConfig{ Version: AppConfigVersion, Theme: "system", Language: "ru", EnabledTemplates: []string{"folder", "project", "client", "document", "recipe"}, EnabledPlugins: []string{}, } } // ConfigDir returns ~/.config/verstak (or platform equivalent). func ConfigDir() (string, error) { cfgDir, err := os.UserConfigDir() if err != nil { return "", err } return filepath.Join(cfgDir, ConfigDirName), nil } // AppConfigPath returns the path to config.json. func AppConfigPath() (string, error) { dir, err := ConfigDir() if err != nil { return "", err } return filepath.Join(dir, AppConfigFileName), nil } // EnsureConfigDir creates the config directory if it doesn't exist. func EnsureConfigDir() (string, error) { dir, err := ConfigDir() if err != nil { return "", err } if err := os.MkdirAll(dir, 0o750); err != nil { return "", err } return dir, nil } // LoadAppConfig reads the global config from disk. Returns nil if the file doesn't exist. func LoadAppConfig() (*AppConfig, error) { path, err := AppConfigPath() if err != nil { return nil, err } data, err := os.ReadFile(path) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, fmt.Errorf("read config: %w", err) } if len(data) == 0 { return nil, nil } var cfg AppConfig if err := json.Unmarshal(data, &cfg); err != nil { return nil, fmt.Errorf("parse config: %w", err) } return &cfg, nil } // SaveAppConfig writes the global config to disk. func SaveAppConfig(cfg *AppConfig) error { path, err := AppConfigPath() if err != nil { return err } if _, err := EnsureConfigDir(); err != nil { return err } data, err := json.MarshalIndent(cfg, "", " ") if err != nil { return err } return os.WriteFile(path, data, 0o640) } // DefaultVaultPath returns the default vault location. func DefaultVaultPath() (string, error) { dir, err := ConfigDir() if err != nil { return "", err } return filepath.Join(dir, "vault"), nil }