diff --git a/cmd_profile.go b/cmd_profile.go index 4939310..5be1714 100644 --- a/cmd_profile.go +++ b/cmd_profile.go @@ -38,6 +38,7 @@ func (c *KVSClient) handleProfile(args []string) { BaseURL: baseURL, } fmt.Println(green("Profile added:"), args[1]) + c.saveProfiles() case "use": if len(args) < 2 { @@ -54,6 +55,7 @@ func (c *KVSClient) handleProfile(args []string) { c.baseURL = profile.BaseURL c.activeProfile = args[1] fmt.Println(green("Switched to profile:"), args[1]) + c.saveProfiles() case "remove": if len(args) < 2 { @@ -65,5 +67,29 @@ func (c *KVSClient) handleProfile(args []string) { c.activeProfile = "" } fmt.Println(green("Profile removed:"), args[1]) + c.saveProfiles() + + case "save": + if err := c.saveProfiles(); err != nil { + fmt.Println(red("Error saving profiles:"), err) + return + } + fmt.Println(green("Profiles saved to ~/.kvs/config.json")) + + case "load": + config, err := LoadConfig() + if err != nil { + fmt.Println(red("Error loading profiles:"), err) + return + } + c.syncConfigToClient(config) + fmt.Println(green("Profiles loaded from ~/.kvs/config.json")) + if c.activeProfile != "" { + fmt.Println(cyan("Active profile:"), c.activeProfile) + } + + default: + fmt.Println(red("Unknown profile command:"), subCmd) + fmt.Println("Available commands: add, use, remove, save, load") } } diff --git a/cmd_system.go b/cmd_system.go index 02e934d..b569671 100644 --- a/cmd_system.go +++ b/cmd_system.go @@ -27,10 +27,12 @@ KVS Interactive Shell - Available Commands: Connection & Authentication: connect - Connect to KVS server auth - Set authentication token + profile - List all profiles profile add [url] - Add user profile profile use - Switch to user profile profile remove - Remove user profile - profile - List all profiles + profile save - Save profiles to ~/.kvs/config.json + profile load - Load profiles from ~/.kvs/config.json Key-Value Operations: get - Retrieve value for key diff --git a/config.go b/config.go new file mode 100644 index 0000000..63162ee --- /dev/null +++ b/config.go @@ -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 +} diff --git a/main.go b/main.go index ca87df3..661fbae 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,11 @@ import ( func main() { client := NewKVSClient("http://localhost:8090") + // Load saved profiles + if config, err := LoadConfig(); err == nil { + client.syncConfigToClient(config) + } + // Setup readline rl, err := readline.NewEx(&readline.Config{ Prompt: cyan("kvs> "), @@ -26,6 +31,9 @@ func main() { fmt.Println(magenta("KVS Interactive Shell")) fmt.Println("Type 'help' for available commands") + if client.activeProfile != "" { + fmt.Println(green("Active profile:"), client.activeProfile) + } fmt.Println() for { diff --git a/utils.go b/utils.go index d1b9718..2871a37 100644 --- a/utils.go +++ b/utils.go @@ -99,6 +99,8 @@ var completer = readline.NewPrefixCompleter( readline.PcItem("add"), readline.PcItem("use"), readline.PcItem("remove"), + readline.PcItem("save"), + readline.PcItem("load"), ), readline.PcItem("get"), readline.PcItem("put"),