diff --git a/LICENSE b/LICENSE index aa95b5f..b2e6ea4 100644 --- a/LICENSE +++ b/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file diff --git a/cmd/cli/createMsg.go b/cmd/cli/createMsg.go new file mode 100644 index 0000000..63c8c26 --- /dev/null +++ b/cmd/cli/createMsg.go @@ -0,0 +1,140 @@ +package cmd + +import ( + "log" + "os" + + "github.com/atotto/clipboard" + "github.com/dfanso/commit-msg/cmd/cli/store" + "github.com/dfanso/commit-msg/internal/chatgpt" + "github.com/dfanso/commit-msg/internal/claude" + "github.com/dfanso/commit-msg/internal/display" + "github.com/dfanso/commit-msg/internal/gemini" + "github.com/dfanso/commit-msg/internal/git" + "github.com/dfanso/commit-msg/internal/grok" + "github.com/dfanso/commit-msg/internal/stats" + "github.com/dfanso/commit-msg/pkg/types" + "github.com/pterm/pterm" +) + + +func CreateCommitMsg () { + + // Validate COMMIT_LLM and required API keys + useLLM,err := store.DefaultLLMKey() + if err != nil { + log.Fatal(err) + } + + commitLLM := useLLM.LLM + apiKey := useLLM.APIKey + + + // Get current directory + currentDir, err := os.Getwd() + if err != nil { + log.Fatalf("Failed to get current directory: %v", err) + } + + // Check if current directory is a git repository + if !git.IsRepository(currentDir) { + log.Fatalf("Current directory is not a Git repository: %s", currentDir) + } + + // Create a minimal config for the API + config := &types.Config{ + GrokAPI: "https://api.x.ai/v1/chat/completions", + } + + // Create a repo config for the current directory + repoConfig := types.RepoConfig{ + Path: currentDir, + } + + // Get file statistics before fetching changes + fileStats, err := stats.GetFileStatistics(&repoConfig) + if err != nil { + log.Fatalf("Failed to get file statistics: %v", err) + } + + // Display header + pterm.DefaultHeader.WithFullWidth(). + WithBackgroundStyle(pterm.NewStyle(pterm.BgDarkGray)). + WithTextStyle(pterm.NewStyle(pterm.FgLightWhite)). + Println("🚀 Commit Message Generator") + + pterm.Println() + + // Display file statistics with icons + display.ShowFileStatistics(fileStats) + + if fileStats.TotalFiles == 0 { + pterm.Warning.Println("No changes detected in the Git repository.") + return + } + + // Get the changes + changes, err := git.GetChanges(&repoConfig) + if err != nil { + log.Fatalf("Failed to get Git changes: %v", err) + } + + if len(changes) == 0 { + pterm.Warning.Println("No changes detected in the Git repository.") + return + } + + pterm.Println() + + // Show generating spinner + spinnerGenerating, err := pterm.DefaultSpinner. + WithSequence("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"). + Start("🤖 Generating commit message...") + if err != nil { + log.Fatalf("Failed to start spinner: %v", err) + } + + var commitMsg string + + switch commitLLM { + + case "Gemini": + commitMsg, err = gemini.GenerateCommitMessage(config, changes, apiKey) + + case "OpenAI": + commitMsg, err = chatgpt.GenerateCommitMessage(config, changes, apiKey) + + case "Claude": + commitMsg, err = claude.GenerateCommitMessage(config, changes, apiKey) + + default: + commitMsg, err = grok.GenerateCommitMessage(config, changes, apiKey) + } + + + if err != nil { + spinnerGenerating.Fail("Failed to generate commit message") + log.Fatalf("Error: %v", err) + } + + spinnerGenerating.Success("✅ Commit message generated successfully!") + + pterm.Println() + + // Display the commit message in a styled panel + display.ShowCommitMessage(commitMsg) + + // Copy to clipboard + err = clipboard.WriteAll(commitMsg) + if err != nil { + pterm.Warning.Printf("⚠️ Could not copy to clipboard: %v\n", err) + } else { + pterm.Success.Println("📋 Commit message copied to clipboard!") + } + + pterm.Println() + + // Display changes preview + display.ShowChangesPreview(fileStats) + +} \ No newline at end of file diff --git a/cmd/cli/llmSetup.go b/cmd/cli/llmSetup.go new file mode 100644 index 0000000..e4f40e9 --- /dev/null +++ b/cmd/cli/llmSetup.go @@ -0,0 +1,122 @@ +package cmd + +import ( + "errors" + "fmt" + + "github.com/dfanso/commit-msg/cmd/cli/store" + "github.com/manifoldco/promptui" +) + + +func SetupLLM() error { + + providers := []string{"OpenAI", "Claude", "Gemini", "Grok"} + prompt := promptui.Select{ + Label: "Select LLM", + Items: providers, + } + + _, model, err := prompt.Run() + if err != nil { + return fmt.Errorf("prompt failed") + } + + apiKeyPrompt := promptui.Prompt{ + Label: "Enter API Key", + Mask: '*', + + } + + apiKey, err := apiKeyPrompt.Run() + if err != nil { + return fmt.Errorf("failed to read API Key: %w", err) + } + + LLMConfig := store.LLMProvider{ + LLM: model, + APIKey: apiKey, + } + + + + err = store.Save(LLMConfig) + if err != nil { + return err + } + + fmt.Println("LLM model added") + return nil +} + +func UpdateLLM() error { + + SavedModels, err := store.ListSavedModels() + if err != nil { + return err + } + + if len(SavedModels.LLMProviders) == 0 { + return errors.New("no model exists, Please add atleast one model Run: 'commit llm setup'") + + } + + models := []string{} + options := []string{"Set Default", "Change API Key", "Delete"} + + for _, p := range SavedModels.LLMProviders { + models = append(models, p.LLM) + } + + prompt := promptui.Select{ + Label: "Select from saved models", + Items: models, + } + + _,model,err := prompt.Run() + if err != nil { + return err + } + + + prompt = promptui.Select{ + Label: "Select Option", + Items: options, + } + opNo,_,err := prompt.Run() + if err != nil { + return err + } + + apiKeyprompt := promptui.Prompt { + Label: "Enter API Key", + } + + + switch opNo { + case 0: + err := store.ChangeDefault(model) + if err != nil { + return err + } + fmt.Printf("%s set as default", model) + case 1: + apiKey, err := apiKeyprompt.Run() + if err != nil { + return err + } + err = store.UpdateAPIKey(model, apiKey) + if err != nil { + return err + } + fmt.Printf("%s API Key Updated", model) + case 2: + err := store.DeleteModel(model) + if err != nil { + return err + } + fmt.Printf("%s model deleted", model) + } + + return nil +} diff --git a/cmd/cli/root.go b/cmd/cli/root.go new file mode 100644 index 0000000..8b3d565 --- /dev/null +++ b/cmd/cli/root.go @@ -0,0 +1,76 @@ +/* +Copyright © 2025 NAME HERE +*/ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "commit", + Short: "CLI tool to write commit message", + Long: `Write a commit message with AI of your choice`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +var llmCmd = &cobra.Command{ + Use: "llm", + Short: "Manage LLM configuration", +} + +var llmSetupCmd = &cobra.Command{ + Use: "setup", + Short: "Setup your LLM provider and API key", + RunE: func(cmd *cobra.Command, args []string) error { + return SetupLLM() + }, +} + +var llmUpdateCmd = &cobra.Command{ + Use: "update", + Short: "Update or Delete LLM Model", + RunE: func(cmd *cobra.Command, args []string) error { + return UpdateLLM() + }, +} + +var creatCommitMsg = &cobra.Command{ + Use: ".", + Short: "Create Commit Message", + RunE: func(cmd *cobra.Command, args []string) error { + CreateCommitMsg() + return nil + }, +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.commit-msg.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + rootCmd.AddCommand(creatCommitMsg) + rootCmd.AddCommand(llmCmd) + llmCmd.AddCommand(llmSetupCmd) + llmCmd.AddCommand(llmUpdateCmd) +} + diff --git a/cmd/cli/store/store.go b/cmd/cli/store/store.go new file mode 100644 index 0000000..35a2502 --- /dev/null +++ b/cmd/cli/store/store.go @@ -0,0 +1,358 @@ +package store + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "runtime" +) + +type LLMProvider struct { + LLM string `json:"model"` + APIKey string `json:"api_key"` +} + +type Config struct { + Default string `json:"default"` + LLMProviders []LLMProvider `json:"models"` +} + +func Save(LLMConfig LLMProvider) error { + + cfg := Config{ + LLMConfig.LLM, + []LLMProvider{LLMConfig}, + } + + + configPath, err := getConfigPath() + if err != nil { + return err + } + + isConfigExists := checkConfig(configPath) + if !isConfigExists { + err := createConfigFile(configPath) + if err != nil { + return err + } + } + + + data, err := os.ReadFile(configPath) + if errors.Is(err, os.ErrNotExist){ + data = []byte("{}") + } else if err != nil { + return err + } + + + if len(data) > 0 { + err = json.Unmarshal(data, &cfg) + if err != nil { + return err + } + } + + + updated := false + for i, p := range cfg.LLMProviders { + if p.LLM == LLMConfig.LLM { + cfg.LLMProviders[i] = LLMConfig + updated = true + break + } + } + + if !updated { + cfg.LLMProviders = append(cfg.LLMProviders, LLMConfig) + } + + cfg.Default = LLMConfig.LLM + + + data, err = json.MarshalIndent(cfg, "", " ") + if err != nil { + return err + } + + return os.WriteFile(configPath, data, 0600) +} + + +func checkConfig(configPath string) bool { + + _,err := os.Stat(configPath) + if err != nil ||os.IsNotExist(err) { + return false + } + + return true +} + + +func createConfigFile(configPath string) error { + + err := os.MkdirAll(filepath.Dir(configPath), 0700) + if err != nil { + return err + } + + return nil + +} + +func getConfigPath() (string, error) { + + appName := "commit-msg" + + switch runtime.GOOS { + + case "windows": + localAppData := os.Getenv("LOCALAPPDATA") + if localAppData == "" { + localAppData = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Local") + } + + return filepath.Join(localAppData, appName, "config.json"), nil + + case "darwin": + + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + + return filepath.Join(home, "Library", "Application Support", appName, "config.json"), nil + + default: + + configHome := os.Getenv("XDG_CONFIG_HOME") + if configHome == "" { + home, err := os.UserHomeDir() + if err != nil { + return "", err + } + + configHome = filepath.Join(home, ".config") + } + + return filepath.Join(configHome, appName, "config.json"), nil + } + +} + +func DefaultLLMKey() (*LLMProvider, error) { + + var cfg Config + var useModel LLMProvider + + configPath, err := getConfigPath() + if err != nil { + return nil, err + } + + isConfigExists := checkConfig(configPath) + if !isConfigExists { + return nil, errors.New("config file Not exists") + } + + + data, err := os.ReadFile(configPath) + if err != nil { + return nil, err + } + + fmt.Println(len(data)) + if len(data) > 2 { + err = json.Unmarshal(data, &cfg) + if err != nil { + return nil, err + } + } else { + return nil, errors.New("config file is empty, Please add atlead one LLM Key") + } + + + + defaultLLM := cfg.Default + + for i, p := range cfg.LLMProviders { + if p.LLM == defaultLLM { + useModel= cfg.LLMProviders[i] + return &useModel, nil + } + } + return nil, errors.New("not found default model in config") +} + + +func ListSavedModels() (*Config, error){ + + var cfg Config + + configPath, err := getConfigPath() + if err != nil { + return nil, err + } + + isConfigExists := checkConfig(configPath) + if !isConfigExists { + return nil, errors.New("config file not exists") + } + + data, err := os.ReadFile(configPath) + if err != nil { + return nil, err + } + + if len(data) > 0 { + err = json.Unmarshal(data, &cfg) + if err != nil { + return nil, err + } + } else { + return nil, errors.New("config file is empty, Please add atlead one LLM Key") + } + + + return &cfg, nil + +} + + +func ChangeDefault(Model string) error { + + var cfg Config + + configPath, err := getConfigPath() + if err != nil { + return err + } + + isConfigExists := checkConfig(configPath) + if !isConfigExists { + return errors.New("config file not exists") + } + + data, err := os.ReadFile(configPath) + if err != nil { + return err + } + + if len(data) > 0 { + err = json.Unmarshal(data, &cfg) + if err != nil { + return err + } + } + + cfg.Default = Model + + data, err = json.MarshalIndent(cfg, "", " ") + if err != nil { + return err + } + + return os.WriteFile(configPath, data, 0600) +} + + +func DeleteModel(Model string) error { + + var cfg Config + var newCfg Config + + configPath, err := getConfigPath() + if err != nil { + return err + } + + isConfigExists := checkConfig(configPath) + if !isConfigExists { + return errors.New("config file not exists") + } + + data, err := os.ReadFile(configPath) + if err != nil { + return err + } + + if len(data) > 0 { + err = json.Unmarshal(data, &cfg) + if err != nil { + return err + } + } + + + if Model == cfg.Default { + if len(cfg.LLMProviders) > 1 { + return fmt.Errorf("cannot delete %s while it is default, set other model default first", Model) + } else { + return os.WriteFile(configPath, []byte("{}"), 0600) + } + } else { + + for _,p := range cfg.LLMProviders { + + if p.LLM != Model { + newCfg.LLMProviders = append(newCfg.LLMProviders, p) + } + } + + newCfg.Default = cfg.Default + + data, err = json.MarshalIndent(newCfg, "", " ") + if err != nil { + return err + } + return os.WriteFile(configPath, data, 0600) + + } +} + + +func UpdateAPIKey(Model, APIKey string) error { + + var cfg Config + + + configPath, err := getConfigPath() + if err != nil { + return err + } + + isConfigExists := checkConfig(configPath) + if !isConfigExists { + return errors.New("config file not exists") + } + + data, err := os.ReadFile(configPath) + if err != nil { + return err + } + + if len(data) > 0 { + err = json.Unmarshal(data, &cfg) + if err != nil { + return err + } + } + + for i, p := range cfg.LLMProviders { + if p.LLM == Model { + cfg.LLMProviders[i].APIKey = APIKey + } + } + + data, err = json.MarshalIndent(cfg, "", " ") + if err != nil { + return err + } + + return os.WriteFile(configPath, data, 0600) + +} \ No newline at end of file diff --git a/cmd/commit-msg/main.go b/cmd/commit-msg/main.go index caafa7f..8486c8a 100644 --- a/cmd/commit-msg/main.go +++ b/cmd/commit-msg/main.go @@ -1,178 +1,8 @@ package main -import ( - "log" - "os" - - "github.com/atotto/clipboard" - "github.com/dfanso/commit-msg/internal/chatgpt" - "github.com/dfanso/commit-msg/internal/claude" - "github.com/dfanso/commit-msg/internal/display" - "github.com/dfanso/commit-msg/internal/gemini" - "github.com/dfanso/commit-msg/internal/git" - "github.com/dfanso/commit-msg/internal/grok" - "github.com/dfanso/commit-msg/internal/groq" - "github.com/dfanso/commit-msg/internal/ollama" - "github.com/dfanso/commit-msg/internal/stats" - "github.com/dfanso/commit-msg/pkg/types" - "github.com/joho/godotenv" - "github.com/pterm/pterm" -) +import cmd "github.com/dfanso/commit-msg/cmd/cli" // main is the entry point of the commit message generator func main() { - // Load the .env file - // Try to load .env file, but don't fail if it doesn't exist - // System environment variables will be used as fallback - _ = godotenv.Load() - - // Validate COMMIT_LLM and required API keys - commitLLM := os.Getenv("COMMIT_LLM") - var apiKey string - - switch commitLLM { - case "gemini": - apiKey = os.Getenv("GEMINI_API_KEY") - if apiKey == "" { - log.Fatalf("GEMINI_API_KEY is not set") - } - case "grok": - apiKey = os.Getenv("GROK_API_KEY") - if apiKey == "" { - log.Fatalf("GROK_API_KEY is not set") - } - case "groq": - apiKey = os.Getenv("GROQ_API_KEY") - if apiKey == "" { - log.Fatalf("GROQ_API_KEY is not set") - } - case "chatgpt": - apiKey = os.Getenv("OPENAI_API_KEY") - if apiKey == "" { - log.Fatalf("OPENAI_API_KEY is not set") - } - case "claude": - apiKey = os.Getenv("CLAUDE_API_KEY") - if apiKey == "" { - log.Fatalf("CLAUDE_API_KEY is not set") - } - case "ollama": - // No API key required to run a local LLM - apiKey = "" - default: - log.Fatalf("Invalid COMMIT_LLM value: %s", commitLLM) - } - - // Get current directory - currentDir, err := os.Getwd() - if err != nil { - log.Fatalf("Failed to get current directory: %v", err) - } - - // Check if current directory is a git repository - if !git.IsRepository(currentDir) { - log.Fatalf("Current directory is not a Git repository: %s", currentDir) - } - - // Create a minimal config for the API - config := &types.Config{ - GrokAPI: "https://api.x.ai/v1/chat/completions", - } - - // Create a repo config for the current directory - repoConfig := types.RepoConfig{ - Path: currentDir, - } - - // Get file statistics before fetching changes - fileStats, err := stats.GetFileStatistics(&repoConfig) - if err != nil { - log.Fatalf("Failed to get file statistics: %v", err) - } - - // Display header - pterm.DefaultHeader.WithFullWidth(). - WithBackgroundStyle(pterm.NewStyle(pterm.BgDarkGray)). - WithTextStyle(pterm.NewStyle(pterm.FgLightWhite)). - Println("🚀 Commit Message Generator") - - pterm.Println() - - // Display file statistics with icons - display.ShowFileStatistics(fileStats) - - if fileStats.TotalFiles == 0 { - pterm.Warning.Println("No changes detected in the Git repository.") - return - } - - // Get the changes - changes, err := git.GetChanges(&repoConfig) - if err != nil { - log.Fatalf("Failed to get Git changes: %v", err) - } - - if len(changes) == 0 { - pterm.Warning.Println("No changes detected in the Git repository.") - return - } - - pterm.Println() - - // Show generating spinner - spinnerGenerating, err := pterm.DefaultSpinner. - WithSequence("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"). - Start("🤖 Generating commit message...") - if err != nil { - log.Fatalf("Failed to start spinner: %v", err) - } - - var commitMsg string - switch commitLLM { - case "gemini": - commitMsg, err = gemini.GenerateCommitMessage(config, changes, apiKey) - case "chatgpt": - commitMsg, err = chatgpt.GenerateCommitMessage(config, changes, apiKey) - case "claude": - commitMsg, err = claude.GenerateCommitMessage(config, changes, apiKey) - case "ollama": - url := os.Getenv("OLLAMA_URL") - if url == "" { - url = "http://localhost:11434/api/generate" - } - model := os.Getenv("OLLAMA_MODEL") - if model == "" { - model = "llama3:latest" - } - commitMsg, err = ollama.GenerateCommitMessage(config, changes, url, model) - case "groq": - commitMsg, err = groq.GenerateCommitMessage(config, changes, apiKey) - default: - commitMsg, err = grok.GenerateCommitMessage(config, changes, apiKey) - } - - if err != nil { - spinnerGenerating.Fail("Failed to generate commit message") - log.Fatalf("Error: %v", err) - } - - spinnerGenerating.Success("✅ Commit message generated successfully!") - - pterm.Println() - - // Display the commit message in a styled panel - display.ShowCommitMessage(commitMsg) - - // Copy to clipboard - err = clipboard.WriteAll(commitMsg) - if err != nil { - pterm.Warning.Printf("⚠️ Could not copy to clipboard: %v\n", err) - } else { - pterm.Success.Println("📋 Commit message copied to clipboard!") - } - - pterm.Println() - - // Display changes preview - display.ShowChangesPreview(fileStats) + cmd.Execute() } diff --git a/go.mod b/go.mod index 0c29269..99570d6 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,10 @@ toolchain go1.24.7 require ( github.com/atotto/clipboard v0.1.4 github.com/google/generative-ai-go v0.19.0 + github.com/manifoldco/promptui v0.9.0 github.com/openai/openai-go/v3 v3.0.1 github.com/pterm/pterm v0.12.80 + github.com/spf13/cobra v1.10.1 google.golang.org/api v0.223.0 ) @@ -22,6 +24,7 @@ require ( cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect cloud.google.com/go/compute/metadata v0.6.0 // indirect cloud.google.com/go/longrunning v0.5.7 // indirect + github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/containerd/console v1.0.5 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -31,10 +34,11 @@ require ( github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/googleapis/gax-go/v2 v2.14.1 // indirect github.com/gookit/color v1.5.4 // indirect - github.com/joho/godotenv v1.5.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/spf13/pflag v1.0.9 // indirect github.com/tidwall/gjson v1.14.4 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect diff --git a/go.sum b/go.sum index fb0f184..fe4be9b 100644 --- a/go.sum +++ b/go.sum @@ -30,9 +30,16 @@ github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -61,8 +68,8 @@ github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQ github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= @@ -73,6 +80,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= +github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA= +github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= @@ -92,8 +101,13 @@ github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaK github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -151,6 +165,7 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=