feat: add persistent profile storage to ~/.kvs/config.json
Implemented roadmap #1: Configuration file for persistent profiles. Features: - Auto-saves profiles to ~/.kvs/config.json on add/use/remove - Auto-loads profiles on shell startup - File created with 0600 permissions for token security - Shows active profile in welcome message - Added 'profile save' and 'profile load' commands for manual control Technical details: - Created config.go with LoadConfig/SaveConfig functions - Profile changes automatically trigger persistence - ~/.kvs directory created with 0700 permissions if missing - Gracefully handles missing config file on first run 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
120
config.go
Normal file
120
config.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Config holds the persistent configuration
|
||||
type Config struct {
|
||||
Profiles map[string]Profile `json:"profiles"`
|
||||
ActiveProfile string `json:"active_profile"`
|
||||
}
|
||||
|
||||
// getConfigPath returns the path to the config file
|
||||
func getConfigPath() (string, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
kvsDir := filepath.Join(homeDir, ".kvs")
|
||||
// Create .kvs directory if it doesn't exist
|
||||
if err := os.MkdirAll(kvsDir, 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(kvsDir, "config.json"), nil
|
||||
}
|
||||
|
||||
// LoadConfig loads the configuration from disk
|
||||
func LoadConfig() (*Config, error) {
|
||||
configPath, err := getConfigPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If config doesn't exist, return empty config
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
return &Config{
|
||||
Profiles: make(map[string]Profile),
|
||||
}, nil
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config Config
|
||||
if err := json.Unmarshal(data, &config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.Profiles == nil {
|
||||
config.Profiles = make(map[string]Profile)
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// SaveConfig saves the configuration to disk with 0600 permissions
|
||||
func SaveConfig(config *Config) error {
|
||||
configPath, err := getConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(config, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write with restrictive permissions (0600) to protect tokens
|
||||
if err := os.WriteFile(configPath, data, 0600); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// syncConfigToClient updates client state from config
|
||||
func (c *KVSClient) syncConfigToClient(config *Config) {
|
||||
c.profiles = config.Profiles
|
||||
c.activeProfile = config.ActiveProfile
|
||||
|
||||
// If there's an active profile, apply it
|
||||
if c.activeProfile != "" {
|
||||
if profile, ok := c.profiles[c.activeProfile]; ok {
|
||||
c.currentToken = profile.Token
|
||||
c.currentUser = profile.UserUUID
|
||||
c.baseURL = profile.BaseURL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// syncClientToConfig updates config from client state
|
||||
func (c *KVSClient) syncClientToConfig(config *Config) {
|
||||
config.Profiles = c.profiles
|
||||
config.ActiveProfile = c.activeProfile
|
||||
}
|
||||
|
||||
// saveProfiles is a convenience method to save current client state
|
||||
func (c *KVSClient) saveProfiles() error {
|
||||
config, err := LoadConfig()
|
||||
if err != nil {
|
||||
// If we can't load, create new config
|
||||
config = &Config{Profiles: make(map[string]Profile)}
|
||||
}
|
||||
|
||||
c.syncClientToConfig(config)
|
||||
|
||||
if err := SaveConfig(config); err != nil {
|
||||
fmt.Println(yellow("Warning: Failed to save profiles:"), err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user