From e06c42c5f7f6d465341e53bf00fca8e2f951a202 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 13:49:43 +0530 Subject: [PATCH 01/30] use grok to generate commit msg --- .env | 1 + README.md | 9 ++ commit-helper.json | 6 ++ src/config/config.go | 33 +++++++ src/grok/grok.go | 77 +++++++++++++++ src/main.go | 221 ++++++++++++++++++++++++++++++++++++++++++- src/types/types.go | 24 +++++ 7 files changed, 368 insertions(+), 3 deletions(-) create mode 100644 .env create mode 100644 commit-helper.json create mode 100644 src/config/config.go create mode 100644 src/grok/grok.go create mode 100644 src/types/types.go diff --git a/.env b/.env new file mode 100644 index 0000000..6dcd17b --- /dev/null +++ b/.env @@ -0,0 +1 @@ +GROK_API_KEY=your-grok-api-key \ No newline at end of file diff --git a/README.md b/README.md index 38b86dd..8d3ed3e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,11 @@ # commit-msg + ```bash +./commit-helper --setup --path="/F:/Git/bible-marker --api-key="your-grok-api-key" +``` + +## Usage + +```bash +./commit-helper +``` \ No newline at end of file diff --git a/commit-helper.json b/commit-helper.json new file mode 100644 index 0000000..6d2188e --- /dev/null +++ b/commit-helper.json @@ -0,0 +1,6 @@ +{ + "path": "F:/Git/bible-marker", + "grok_api": "https://api.grok.ai/v1/chat/completions", + "api_key": "your-grok-api-key", + "last_run": "2025-03-02T13:46:14+05:30" +} \ No newline at end of file diff --git a/src/config/config.go b/src/config/config.go new file mode 100644 index 0000000..b535aab --- /dev/null +++ b/src/config/config.go @@ -0,0 +1,33 @@ +package config + +import ( + "encoding/json" + "os" + + "github.com/dfanso/commit-msg/src/types" +) + +// Load configuration from file +func LoadConfig(configFile string) (*types.Config, error) { + data, err := os.ReadFile(configFile) + if err != nil { + return nil, err + } + + var config types.Config + if err := json.Unmarshal(data, &config); err != nil { + return nil, err + } + + return &config, nil +} + +// Save configuration to file +func SaveConfig(configFile string, config *types.Config) error { + data, err := json.MarshalIndent(config, "", " ") + if err != nil { + return err + } + + return os.WriteFile(configFile, data, 0644) +} diff --git a/src/grok/grok.go b/src/grok/grok.go new file mode 100644 index 0000000..a3c7a21 --- /dev/null +++ b/src/grok/grok.go @@ -0,0 +1,77 @@ +package grok + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "time" + + "github.com/dfanso/commit-msg/src/types" +) + +func GenerateCommitMessage(config *types.Config, changes string) (string, error) { + // Prepare request to Grok API + prompt := fmt.Sprintf(` +I need a concise git commit message based on the following changes from my Git repository. +Please generate a commit message that: +1. Starts with a verb in the present tense (e.g., "Add", "Fix", "Update") +2. Is clear and descriptive +3. Focuses on the "what" and "why" of the changes +4. Is no longer than 50-72 characters for the first line +5. Can include a more detailed description after a blank line if needed + +Here are the changes: + +%s +`, changes) + + request := types.GrokRequest{ + Messages: []types.Message{ + { + Role: "user", + Content: prompt, + }, + }, + } + + requestBody, err := json.Marshal(request) + if err != nil { + return "", err + } + + // Create HTTP request + req, err := http.NewRequest("POST", config.GrokAPI, bytes.NewBuffer(requestBody)) + if err != nil { + return "", err + } + + // Set headers + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", config.APIKey)) + + // Send request + client := &http.Client{ + Timeout: 30 * time.Second, + } + resp, err := client.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + // Check response status + if resp.StatusCode != http.StatusOK { + bodyBytes, _ := ioutil.ReadAll(resp.Body) + return "", fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(bodyBytes)) + } + + // Parse response + var grokResponse types.GrokResponse + if err := json.NewDecoder(resp.Body).Decode(&grokResponse); err != nil { + return "", err + } + + return grokResponse.Message.Content, nil +} diff --git a/src/main.go b/src/main.go index 29a4e10..eaa84bd 100644 --- a/src/main.go +++ b/src/main.go @@ -1,7 +1,222 @@ package main -import "fmt" +import ( + "flag" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + "time" + "github.com/dfanso/commit-msg/src/config" + "github.com/dfanso/commit-msg/src/grok" + "github.com/dfanso/commit-msg/src/types" +) + +// Main function func main() { - fmt.Println("Hello, Commit Message Project!") -} \ No newline at end of file + // Define command line flags + configFile := flag.String("config", "commit-helper.json", "Path to config file") + setupMode := flag.Bool("setup", false, "Setup configuration") + path := flag.String("path", "", "Path to monitor (for setup)") + apiKey := flag.String("api-key", "", "Grok API key (for setup)") + apiEndpoint := flag.String("api-endpoint", "https://api.grok.ai/v1/chat/completions", "Grok API endpoint (for setup)") + flag.Parse() + + // Initialize or load configuration + cfg, err := config.LoadConfig(*configFile) + if err != nil { + if *setupMode { + newConfig := &types.Config{ + Path: *path, + GrokAPI: *apiEndpoint, + APIKey: *apiKey, + LastRun: time.Now().Format(time.RFC3339), + } + if err := config.SaveConfig(*configFile, newConfig); err != nil { + log.Fatalf("Failed to save configuration: %v", err) + } + fmt.Println("Configuration saved successfully.") + return + } else { + log.Fatalf("Failed to load configuration: %v", err) + } + } + + // Validate configuration + if cfg.Path == "" { + log.Fatalf("Path not configured. Run with --setup and provide a path.") + } + if cfg.GrokAPI == "" { + log.Fatalf("Grok API endpoint not configured. Run with --setup and provide an API endpoint.") + } + if cfg.APIKey == "" { + log.Fatalf("API key not configured. Run with --setup and provide an API key.") + } + + // Ensure the path is a Git repository + if !isGitRepository(cfg.Path) { + log.Fatalf("The specified path is not a Git repository: %s", cfg.Path) + } + + // Get the changes + changes, err := getGitChanges(cfg) + if err != nil { + log.Fatalf("Failed to get Git changes: %v", err) + } + + if len(changes) == 0 { + fmt.Println("No changes detected in the Git repository.") + return + } + + // Generate commit message using Grok API + commitMsg, err := grok.GenerateCommitMessage(cfg, changes) + if err != nil { + log.Fatalf("Failed to generate commit message: %v", err) + } + + // Display the commit message + fmt.Println("Suggested commit message:") + fmt.Println(commitMsg) + + // Update the last run time + cfg.LastRun = time.Now().Format(time.RFC3339) + if err := config.SaveConfig(*configFile, cfg); err != nil { + log.Printf("Warning: Failed to update last run time: %v", err) + } +} + +// Check if directory is a git repository +func isGitRepository(path string) bool { + cmd := exec.Command("git", "-C", path, "rev-parse", "--is-inside-work-tree") + output, err := cmd.CombinedOutput() + if err != nil { + return false + } + return strings.TrimSpace(string(output)) == "true" +} + +// Get changes using Git +func getGitChanges(config *types.Config) (string, error) { + var changes strings.Builder + + // 1. Check for unstaged changes + cmd := exec.Command("git", "-C", config.Path, "diff", "--name-status") + output, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("git diff failed: %v", err) + } + + if len(output) > 0 { + changes.WriteString("Unstaged changes:\n") + changes.WriteString(string(output)) + changes.WriteString("\n\n") + + // Get the content of these changes + diffCmd := exec.Command("git", "-C", config.Path, "diff") + diffOutput, err := diffCmd.Output() + if err != nil { + return "", fmt.Errorf("git diff content failed: %v", err) + } + + changes.WriteString("Unstaged diff content:\n") + changes.WriteString(string(diffOutput)) + changes.WriteString("\n\n") + } + + // 2. Check for staged changes + stagedCmd := exec.Command("git", "-C", config.Path, "diff", "--name-status", "--cached") + stagedOutput, err := stagedCmd.Output() + if err != nil { + return "", fmt.Errorf("git diff --cached failed: %v", err) + } + + if len(stagedOutput) > 0 { + changes.WriteString("Staged changes:\n") + changes.WriteString(string(stagedOutput)) + changes.WriteString("\n\n") + + // Get the content of these changes + stagedDiffCmd := exec.Command("git", "-C", config.Path, "diff", "--cached") + stagedDiffOutput, err := stagedDiffCmd.Output() + if err != nil { + return "", fmt.Errorf("git diff --cached content failed: %v", err) + } + + changes.WriteString("Staged diff content:\n") + changes.WriteString(string(stagedDiffOutput)) + changes.WriteString("\n\n") + } + + // 3. Check for untracked files + untrackedCmd := exec.Command("git", "-C", config.Path, "ls-files", "--others", "--exclude-standard") + untrackedOutput, err := untrackedCmd.Output() + if err != nil { + return "", fmt.Errorf("git ls-files failed: %v", err) + } + + if len(untrackedOutput) > 0 { + changes.WriteString("Untracked files:\n") + changes.WriteString(string(untrackedOutput)) + changes.WriteString("\n\n") + + // Try to get content of untracked files (limited to text files and smaller size) + untrackedFiles := strings.Split(strings.TrimSpace(string(untrackedOutput)), "\n") + for _, file := range untrackedFiles { + if file == "" { + continue + } + + fullPath := filepath.Join(config.Path, file) + if isTextFile(fullPath) && isSmallFile(fullPath) { + fileContent, err := os.ReadFile(fullPath) + if err == nil { + changes.WriteString(fmt.Sprintf("Content of new file %s:\n", file)) + changes.WriteString(string(fileContent)) + changes.WriteString("\n\n") + } + } + } + } + + // 4. Get recent commits for context + recentCommitsCmd := exec.Command("git", "-C", config.Path, "log", "--oneline", "-n", "3") + recentCommitsOutput, err := recentCommitsCmd.Output() + if err == nil && len(recentCommitsOutput) > 0 { + changes.WriteString("Recent commits for context:\n") + changes.WriteString(string(recentCommitsOutput)) + changes.WriteString("\n") + } + + return changes.String(), nil +} + +// Check if a file is likely to be a text file +func isTextFile(filename string) bool { + // List of common text file extensions + textExtensions := []string{".txt", ".md", ".go", ".js", ".py", ".java", ".c", ".cpp", ".h", ".html", ".css", ".json", ".xml", ".yaml", ".yml", ".sh", ".bash", ".ts", ".tsx", ".jsx", ".php", ".rb", ".rs", ".dart"} + + ext := strings.ToLower(filepath.Ext(filename)) + for _, textExt := range textExtensions { + if ext == textExt { + return true + } + } + + return false +} + +// Check if a file is small enough to include in context +func isSmallFile(filename string) bool { + const maxSize = 10 * 1024 // 10KB max + + info, err := os.Stat(filename) + if err != nil { + return false + } + + return info.Size() <= maxSize +} diff --git a/src/types/types.go b/src/types/types.go new file mode 100644 index 0000000..3a23dd2 --- /dev/null +++ b/src/types/types.go @@ -0,0 +1,24 @@ +package types + +// Configuration structure +type Config struct { + Path string `json:"path"` + GrokAPI string `json:"grok_api"` + APIKey string `json:"api_key"` + LastRun string `json:"last_run"` +} + +// Grok API request structure +type GrokRequest struct { + Messages []Message `json:"messages"` +} + +type Message struct { + Role string `json:"role"` + Content string `json:"content"` +} + +// Grok API response structure +type GrokResponse struct { + Message Message `json:"message"` +} From b361b64058eadcc8ae5123e883b809a8ae749a1f Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 13:53:57 +0530 Subject: [PATCH 02/30] Delete .env --- .env | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 6dcd17b..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -GROK_API_KEY=your-grok-api-key \ No newline at end of file From d733ac6698fbf7e912a25f41eb4664baeb2d0736 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 14:16:37 +0530 Subject: [PATCH 03/30] Delete commit-helper.json --- commit-helper.json | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 commit-helper.json diff --git a/commit-helper.json b/commit-helper.json deleted file mode 100644 index 6d2188e..0000000 --- a/commit-helper.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "path": "F:/Git/bible-marker", - "grok_api": "https://api.grok.ai/v1/chat/completions", - "api_key": "your-grok-api-key", - "last_run": "2025-03-02T13:46:14+05:30" -} \ No newline at end of file From 1d66fd13afe8b946a6a2f6e6384485f8a1547130 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 14:24:32 +0530 Subject: [PATCH 04/30] Update setup and API integration Improve setup process, switch to X.AI API, and enhance error handling and configuration management. --- .gitignore | 2 + README.md | 6 ++- commit-helper.json | 12 +++-- go.mod | 2 + go.sum | 2 + src/grok/grok.go | 39 ++++++++++++-- src/main.go | 129 +++++++++++++++++++++++++++++++++++---------- src/types/types.go | 38 ++++++++++--- 8 files changed, 187 insertions(+), 43 deletions(-) create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index 3b735ec..9fc3390 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ # Go workspace file go.work +.env +commit-helper.json diff --git a/README.md b/README.md index 8d3ed3e..648f56c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # commit-msg + + ## Setup ```bash -./commit-helper --setup --path="/F:/Git/bible-marker --api-key="your-grok-api-key" +go run src/main.go --setup --path="/F:/Git/bible-marker --name="bible-marker" --api-key="your-grok-api-key" ``` ## Usage ```bash -./commit-helper +go run src/main.go ``` \ No newline at end of file diff --git a/commit-helper.json b/commit-helper.json index 6d2188e..a58da0f 100644 --- a/commit-helper.json +++ b/commit-helper.json @@ -1,6 +1,10 @@ { - "path": "F:/Git/bible-marker", - "grok_api": "https://api.grok.ai/v1/chat/completions", - "api_key": "your-grok-api-key", - "last_run": "2025-03-02T13:46:14+05:30" + "grok_api": "https://api.x.ai/v1/chat/completions", + "repos": { + "commit-msg": { + "path": "F:/Git/commit-msg", + "last_run": "2025-03-02T14:24:15+05:30" + } + }, + "api_key": "xai-aHqouEe5EoEf4u8VwlSd024yDJUhjQjwvWzQ1jcJ91bBp8YTgbmuODYS6ED6PgcKeSzaUNm64doNlTrq" } \ No newline at end of file diff --git a/go.mod b/go.mod index 6442e95..af97a06 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module github.com/dfanso/commit-msg go 1.23.4 + +require github.com/joho/godotenv v1.5.1 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d61b19e --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= diff --git a/src/grok/grok.go b/src/grok/grok.go index a3c7a21..3e1c932 100644 --- a/src/grok/grok.go +++ b/src/grok/grok.go @@ -2,17 +2,19 @@ package grok import ( "bytes" + "crypto/tls" "encoding/json" "fmt" "io/ioutil" "net/http" + "os" "time" "github.com/dfanso/commit-msg/src/types" ) func GenerateCommitMessage(config *types.Config, changes string) (string, error) { - // Prepare request to Grok API + // Prepare request to X.AI (Grok) API prompt := fmt.Sprintf(` I need a concise git commit message based on the following changes from my Git repository. Please generate a commit message that: @@ -34,6 +36,9 @@ Here are the changes: Content: prompt, }, }, + Model: "grok-2-latest", // Add the model parameter + Stream: false, + Temperature: 0, } requestBody, err := json.Marshal(request) @@ -47,13 +52,34 @@ Here are the changes: return "", err } + // Get API key from config or environment variable + apiKey := config.APIKey + if apiKey == "" { + apiKey = os.Getenv("GROK_API_KEY") + if apiKey == "" { + return "", fmt.Errorf("GROK_API_KEY not found in config or environment variables") + } + } + // Set headers req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", config.APIKey)) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey)) + + // Configure HTTP client with improved TLS settings + transport := &http.Transport{ + TLSHandshakeTimeout: 10 * time.Second, + MaxIdleConns: 10, + IdleConnTimeout: 30 * time.Second, + DisableCompression: true, + // Add TLS config to handle server name mismatch + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: false, // Keep this false for security + }, + } - // Send request client := &http.Client{ - Timeout: 30 * time.Second, + Timeout: 30 * time.Second, + Transport: transport, } resp, err := client.Do(req) if err != nil { @@ -73,5 +99,10 @@ Here are the changes: return "", err } + // Check if the response follows the expected structure + if grokResponse.Message.Content == "" && grokResponse.Choices != nil && len(grokResponse.Choices) > 0 { + return grokResponse.Choices[0].Message.Content, nil + } + return grokResponse.Message.Content, nil } diff --git a/src/main.go b/src/main.go index eaa84bd..4e63f83 100644 --- a/src/main.go +++ b/src/main.go @@ -13,56 +13,130 @@ import ( "github.com/dfanso/commit-msg/src/config" "github.com/dfanso/commit-msg/src/grok" "github.com/dfanso/commit-msg/src/types" + "github.com/joho/godotenv" ) +// Normalize path to handle both forward and backslashes +func normalizePath(path string) string { + // Replace backslashes with forward slashes + normalized := strings.ReplaceAll(path, "\\", "/") + // Remove any trailing slash + normalized = strings.TrimSuffix(normalized, "/") + return normalized +} + // Main function func main() { // Define command line flags configFile := flag.String("config", "commit-helper.json", "Path to config file") setupMode := flag.Bool("setup", false, "Setup configuration") path := flag.String("path", "", "Path to monitor (for setup)") - apiKey := flag.String("api-key", "", "Grok API key (for setup)") - apiEndpoint := flag.String("api-endpoint", "https://api.grok.ai/v1/chat/completions", "Grok API endpoint (for setup)") + repoName := flag.String("name", "", "Repository name (for setup)") + apiEndpoint := flag.String("api-endpoint", "https://api.x.ai/v1/chat/completions", "X.AI API endpoint (for setup)") flag.Parse() + // Load environment variables from .env file + if err := godotenv.Load(); err != nil { + log.Printf("Warning: Error loading .env file: %v", err) + } + + // Get API key from environment variables + apiKey := os.Getenv("GROK_API_KEY") + if apiKey == "" { + log.Fatalf("GROK_API_KEY not found in environment variables") + } + + // If in setup mode, create a new configuration + if *setupMode { + if *path == "" || *repoName == "" { + log.Fatalf("Both --path and --name are required in setup mode") + } + + // Normalize the path + normalizedPath := normalizePath(*path) + + // Create new config with default endpoint + newConfig := &types.Config{ + GrokAPI: *apiEndpoint, + APIKey: apiKey, // Store API key in config + Repos: make(map[string]types.RepoConfig), + } + + newConfig.Repos[*repoName] = types.RepoConfig{ + Path: normalizedPath, + LastRun: time.Now().Format(time.RFC3339), + } + + if err := config.SaveConfig(*configFile, newConfig); err != nil { + log.Fatalf("Failed to save configuration: %v", err) + } + + fmt.Printf("Repository '%s' configured successfully.\n", *repoName) + return + } + // Initialize or load configuration cfg, err := config.LoadConfig(*configFile) if err != nil { - if *setupMode { - newConfig := &types.Config{ - Path: *path, - GrokAPI: *apiEndpoint, - APIKey: *apiKey, - LastRun: time.Now().Format(time.RFC3339), - } - if err := config.SaveConfig(*configFile, newConfig); err != nil { - log.Fatalf("Failed to save configuration: %v", err) - } - fmt.Println("Configuration saved successfully.") + log.Fatalf("Failed to load configuration: %v", err) + } + + // Make sure the API key is in the config + if cfg.APIKey == "" { + cfg.APIKey = apiKey + if err := config.SaveConfig(*configFile, cfg); err != nil { + log.Printf("Warning: Failed to update API key in config: %v", err) + } + } + + // If no path provided, list available repositories + if *path == "" { + fmt.Println("Available repositories:") + if len(cfg.Repos) == 0 { + fmt.Println("No repositories configured. Use --setup to add a repository.") return - } else { - log.Fatalf("Failed to load configuration: %v", err) } + for name, repo := range cfg.Repos { + fmt.Printf("- %s: %s (Last run: %s)\n", name, repo.Path, repo.LastRun) + } + return } // Validate configuration - if cfg.Path == "" { - log.Fatalf("Path not configured. Run with --setup and provide a path.") - } if cfg.GrokAPI == "" { - log.Fatalf("Grok API endpoint not configured. Run with --setup and provide an API endpoint.") + cfg.GrokAPI = *apiEndpoint // Use default endpoint if not set + if err := config.SaveConfig(*configFile, cfg); err != nil { + log.Printf("Warning: Failed to update API endpoint in config: %v", err) + } } - if cfg.APIKey == "" { - log.Fatalf("API key not configured. Run with --setup and provide an API key.") + + // Normalize the input path + repoPath := normalizePath(*path) + var selectedRepo string + var repoConfig types.RepoConfig + + // Find the repository configuration by path + for name, repo := range cfg.Repos { + // Normalize the stored path for comparison + storedPath := normalizePath(repo.Path) + if storedPath == repoPath { + selectedRepo = name + repoConfig = repo + break + } + } + + if selectedRepo == "" { + log.Fatalf("No repository configured for path: %s", repoPath) } // Ensure the path is a Git repository - if !isGitRepository(cfg.Path) { - log.Fatalf("The specified path is not a Git repository: %s", cfg.Path) + if !isGitRepository(repoConfig.Path) { + log.Fatalf("The specified path is not a Git repository: %s", repoConfig.Path) } // Get the changes - changes, err := getGitChanges(cfg) + changes, err := getGitChanges(&repoConfig) if err != nil { log.Fatalf("Failed to get Git changes: %v", err) } @@ -72,7 +146,7 @@ func main() { return } - // Generate commit message using Grok API + // Generate commit message using X.AI API commitMsg, err := grok.GenerateCommitMessage(cfg, changes) if err != nil { log.Fatalf("Failed to generate commit message: %v", err) @@ -83,7 +157,8 @@ func main() { fmt.Println(commitMsg) // Update the last run time - cfg.LastRun = time.Now().Format(time.RFC3339) + repoConfig.LastRun = time.Now().Format(time.RFC3339) + cfg.Repos[selectedRepo] = repoConfig if err := config.SaveConfig(*configFile, cfg); err != nil { log.Printf("Warning: Failed to update last run time: %v", err) } @@ -100,7 +175,7 @@ func isGitRepository(path string) bool { } // Get changes using Git -func getGitChanges(config *types.Config) (string, error) { +func getGitChanges(config *types.RepoConfig) (string, error) { var changes strings.Builder // 1. Check for unstaged changes diff --git a/src/types/types.go b/src/types/types.go index 3a23dd2..111c02c 100644 --- a/src/types/types.go +++ b/src/types/types.go @@ -2,15 +2,23 @@ package types // Configuration structure type Config struct { + GrokAPI string `json:"grok_api"` + Repos map[string]RepoConfig `json:"repos"` + APIKey string `json:"api_key"` +} + +// Repository configuration +type RepoConfig struct { Path string `json:"path"` - GrokAPI string `json:"grok_api"` - APIKey string `json:"api_key"` LastRun string `json:"last_run"` } -// Grok API request structure +// Grok/X.AI API request structure type GrokRequest struct { - Messages []Message `json:"messages"` + Messages []Message `json:"messages"` + Model string `json:"model"` + Stream bool `json:"stream"` + Temperature float64 `json:"temperature"` } type Message struct { @@ -18,7 +26,25 @@ type Message struct { Content string `json:"content"` } -// Grok API response structure +// Grok/X.AI API response structure type GrokResponse struct { - Message Message `json:"message"` + Message Message `json:"message,omitempty"` + Choices []Choice `json:"choices,omitempty"` + Id string `json:"id,omitempty"` + Object string `json:"object,omitempty"` + Created int64 `json:"created,omitempty"` + Model string `json:"model,omitempty"` + Usage UsageInfo `json:"usage,omitempty"` +} + +type Choice struct { + Message Message `json:"message"` + Index int `json:"index"` + FinishReason string `json:"finish_reason"` +} + +type UsageInfo struct { + PromptTokens int `json:"prompt_tokens"` + CompletionTokens int `json:"completion_tokens"` + TotalTokens int `json:"total_tokens"` } From 450065c5430c8f5c47aa4c43ec1cff95d7fc93d2 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 14:28:08 +0530 Subject: [PATCH 05/30] Update: README and grok.go for path consistency Improve setup and usage examples in README.md and update grok.go to reflect new path requirements for better project consistency. --- README.md | 4 ++-- src/grok/grok.go | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 648f56c..931ba89 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ ## Setup ```bash -go run src/main.go --setup --path="/F:/Git/bible-marker --name="bible-marker" --api-key="your-grok-api-key" +go run src/main.go --setup --path F:/Git/commit-msg --name commit-msg ``` ## Usage ```bash -go run src/main.go +go run src/main.go --path F:/Git/commit-msg ``` \ No newline at end of file diff --git a/src/grok/grok.go b/src/grok/grok.go index 3e1c932..f011378 100644 --- a/src/grok/grok.go +++ b/src/grok/grok.go @@ -18,11 +18,12 @@ func GenerateCommitMessage(config *types.Config, changes string) (string, error) prompt := fmt.Sprintf(` I need a concise git commit message based on the following changes from my Git repository. Please generate a commit message that: -1. Starts with a verb in the present tense (e.g., "Add", "Fix", "Update") +1. Starts with a verb in the present tense (e.g., "Add: ", "Fix: ", "Update: ", "Remove: ","Feat" etc.) 2. Is clear and descriptive 3. Focuses on the "what" and "why" of the changes 4. Is no longer than 50-72 characters for the first line 5. Can include a more detailed description after a blank line if needed +6. Oonly include commit msg dont say anyhtiing else Here are the changes: From 82b19e326660933c838b68acb31f4b85b37d290f Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 14:34:19 +0530 Subject: [PATCH 06/30] Feat: Add auto-setup for current directory and new repos This commit introduces functionality to automatically set up the current directory as a repository when "." is provided as an argument. It also includes auto-configuration for new repositories not found in the config, ensuring unique names and saving the new configuration. --- src/main.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/src/main.go b/src/main.go index 4e63f83..ed5f6f4 100644 --- a/src/main.go +++ b/src/main.go @@ -46,6 +46,23 @@ func main() { log.Fatalf("GROK_API_KEY not found in environment variables") } + // Check for "." argument which means use current directory + if len(flag.Args()) > 0 && flag.Args()[0] == "." { + // Get current directory + currentDir, err := os.Getwd() + if err != nil { + log.Fatalf("Failed to get current directory: %v", err) + } + + // Override path with current directory + *path = currentDir + + // Check if current directory is a git repository + if !isGitRepository(currentDir) { + log.Fatalf("Current directory is not a Git repository: %s", currentDir) + } + } + // If in setup mode, create a new configuration if *setupMode { if *path == "" || *repoName == "" { @@ -78,7 +95,12 @@ func main() { // Initialize or load configuration cfg, err := config.LoadConfig(*configFile) if err != nil { - log.Fatalf("Failed to load configuration: %v", err) + // If config doesn't exist yet, create a new one + cfg = &types.Config{ + GrokAPI: *apiEndpoint, + APIKey: apiKey, + Repos: make(map[string]types.RepoConfig), + } } // Make sure the API key is in the config @@ -126,8 +148,38 @@ func main() { } } + // Auto-setup if repository not found in config if selectedRepo == "" { - log.Fatalf("No repository configured for path: %s", repoPath) + // Generate a repository name from the path + dirName := filepath.Base(repoPath) + if dirName == "" || dirName == "." || dirName == "/" || dirName == "\\" { + dirName = "auto-repo" + } + + // Make sure the name is unique + baseName := dirName + counter := 1 + for { + if _, exists := cfg.Repos[dirName]; !exists { + break + } + dirName = fmt.Sprintf("%s-%d", baseName, counter) + counter++ + } + + // Add the repository to the configuration + selectedRepo = dirName + repoConfig = types.RepoConfig{ + Path: repoPath, + LastRun: time.Now().Format(time.RFC3339), + } + cfg.Repos[selectedRepo] = repoConfig + + if err := config.SaveConfig(*configFile, cfg); err != nil { + log.Printf("Warning: Failed to save auto-configured repository: %v", err) + } else { + fmt.Printf("Repository '%s' auto-configured successfully.\n", selectedRepo) + } } // Ensure the path is a Git repository From f0c7a63d69ddb7506c39833cab6f74dfd54c9ceb Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 14:35:18 +0530 Subject: [PATCH 07/30] Update: Clarify commit message guidelines in grok.go - Correct typo in guideline 6 - Add new guideline 7 for using '-' in sentences --- src/grok/grok.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/grok/grok.go b/src/grok/grok.go index f011378..4c3c397 100644 --- a/src/grok/grok.go +++ b/src/grok/grok.go @@ -23,7 +23,8 @@ Please generate a commit message that: 3. Focuses on the "what" and "why" of the changes 4. Is no longer than 50-72 characters for the first line 5. Can include a more detailed description after a blank line if needed -6. Oonly include commit msg dont say anyhtiing else +6. only include commit msg dont say anyhtiing else +7. use '-' for sentences Here are the changes: From 10b44d4fdaa9a7d3af7c488c6d5f8ade8b6d301a Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 14:41:45 +0530 Subject: [PATCH 08/30] Update: Simplify API key handling and add to README Simplified API key management by removing it from the Config struct and passing it directly to GenerateCommitMessage. Updated README to include instructions for setting the GROK_API_KEY environment variable. --- README.md | 3 ++- src/grok/grok.go | 18 +++--------------- src/main.go | 14 ++------------ src/types/types.go | 1 - 4 files changed, 7 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 931ba89..d274666 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # commit-msg - +## Add GROK_API_KEY to system variables + ## Setup ```bash diff --git a/src/grok/grok.go b/src/grok/grok.go index 4c3c397..4d8890d 100644 --- a/src/grok/grok.go +++ b/src/grok/grok.go @@ -7,24 +7,21 @@ import ( "fmt" "io/ioutil" "net/http" - "os" "time" "github.com/dfanso/commit-msg/src/types" ) -func GenerateCommitMessage(config *types.Config, changes string) (string, error) { +func GenerateCommitMessage(config *types.Config, changes string, apiKey string) (string, error) { // Prepare request to X.AI (Grok) API prompt := fmt.Sprintf(` I need a concise git commit message based on the following changes from my Git repository. Please generate a commit message that: -1. Starts with a verb in the present tense (e.g., "Add: ", "Fix: ", "Update: ", "Remove: ","Feat" etc.) +1. Starts with a verb in the present tense (e.g., "Add", "Fix", "Update") 2. Is clear and descriptive 3. Focuses on the "what" and "why" of the changes 4. Is no longer than 50-72 characters for the first line 5. Can include a more detailed description after a blank line if needed -6. only include commit msg dont say anyhtiing else -7. use '-' for sentences Here are the changes: @@ -38,7 +35,7 @@ Here are the changes: Content: prompt, }, }, - Model: "grok-2-latest", // Add the model parameter + Model: "grok-2-latest", Stream: false, Temperature: 0, } @@ -54,15 +51,6 @@ Here are the changes: return "", err } - // Get API key from config or environment variable - apiKey := config.APIKey - if apiKey == "" { - apiKey = os.Getenv("GROK_API_KEY") - if apiKey == "" { - return "", fmt.Errorf("GROK_API_KEY not found in config or environment variables") - } - } - // Set headers req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey)) diff --git a/src/main.go b/src/main.go index ed5f6f4..c35408b 100644 --- a/src/main.go +++ b/src/main.go @@ -75,7 +75,6 @@ func main() { // Create new config with default endpoint newConfig := &types.Config{ GrokAPI: *apiEndpoint, - APIKey: apiKey, // Store API key in config Repos: make(map[string]types.RepoConfig), } @@ -98,19 +97,10 @@ func main() { // If config doesn't exist yet, create a new one cfg = &types.Config{ GrokAPI: *apiEndpoint, - APIKey: apiKey, Repos: make(map[string]types.RepoConfig), } } - // Make sure the API key is in the config - if cfg.APIKey == "" { - cfg.APIKey = apiKey - if err := config.SaveConfig(*configFile, cfg); err != nil { - log.Printf("Warning: Failed to update API key in config: %v", err) - } - } - // If no path provided, list available repositories if *path == "" { fmt.Println("Available repositories:") @@ -198,8 +188,8 @@ func main() { return } - // Generate commit message using X.AI API - commitMsg, err := grok.GenerateCommitMessage(cfg, changes) + // Pass API key to GenerateCommitMessage + commitMsg, err := grok.GenerateCommitMessage(cfg, changes, apiKey) if err != nil { log.Fatalf("Failed to generate commit message: %v", err) } diff --git a/src/types/types.go b/src/types/types.go index 111c02c..4e6f309 100644 --- a/src/types/types.go +++ b/src/types/types.go @@ -4,7 +4,6 @@ package types type Config struct { GrokAPI string `json:"grok_api"` Repos map[string]RepoConfig `json:"repos"` - APIKey string `json:"api_key"` } // Repository configuration From 81734403a1801fc09b1de4457ef71808ea13839a Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 14:42:38 +0530 Subject: [PATCH 09/30] Update README with new command examples for current directory This commit adds new command examples to the README.md file, demonstrating how to run the main.go script in the current directory. The new examples enhance the documentation by showing an alternative usage of the tool. --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index d274666..385187f 100644 --- a/README.md +++ b/README.md @@ -11,4 +11,8 @@ go run src/main.go --setup --path F:/Git/commit-msg --name commit-msg ```bash go run src/main.go --path F:/Git/commit-msg +``` + +```bash +go run src/main.go . ``` \ No newline at end of file From 52ce7a2bbf84b5b84a54a24cbe570d143da143fd Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 14:48:26 +0530 Subject: [PATCH 10/30] Update: Clarify README and commit message guidelines Add new command example to README and update commit message guidelines in grok.go to include more action verbs and a sample message. --- .github/workflows/build-and-release.yml | 143 ++++++++++++++++++++++++ README.md | 2 +- src/grok/grok.go | 5 +- 3 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build-and-release.yml diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml new file mode 100644 index 0000000..df23604 --- /dev/null +++ b/.github/workflows/build-and-release.yml @@ -0,0 +1,143 @@ +name: Build and Release + +on: + push: + tags: + - 'v*' + branches: + - main + pull_request: + branches: + - main + +jobs: + build: + name: Build Go Binary + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + include: + - os: ubuntu-latest + artifact_name: commit + asset_name: commit-linux-amd64 + - os: windows-latest + artifact_name: commit.exe + asset_name: commit-windows-amd64.exe + - os: macos-latest + artifact_name: commit + asset_name: commit-macos-amd64 + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' # Use the appropriate Go version for your project + + - name: Get dependencies + run: go mod download + + - name: Build + run: | + if [ "${{ matrix.os }}" = "windows-latest" ]; then + go build -v -o ${{ matrix.artifact_name }} -ldflags="-s -w" ./src + else + go build -v -o ${{ matrix.artifact_name }} -ldflags="-s -w" ./src + fi + shell: bash + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.asset_name }} + path: ${{ matrix.artifact_name }} + + release: + needs: build + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Download all artifacts + uses: actions/download-artifact@v3 + + - name: Create Release + id: create_release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: | + commit-linux-amd64/commit + commit-windows-amd64.exe/commit.exe + commit-macos-amd64/commit + draft: false + prerelease: false + generate_release_notes: true + + publish: + name: Publish to GitHub Packages + needs: build + if: startsWith(github.ref, 'refs/tags/v') + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.22' + + - name: Download Linux artifact + uses: actions/download-artifact@v3 + with: + name: commit-linux-amd64 + path: ./dist/linux-amd64 + + - name: Download Windows artifact + uses: actions/download-artifact@v3 + with: + name: commit-windows-amd64.exe + path: ./dist/windows-amd64 + + - name: Download macOS artifact + uses: actions/download-artifact@v3 + with: + name: commit-macos-amd64 + path: ./dist/darwin-amd64 + + - name: Make binaries executable + run: | + chmod +x ./dist/linux-amd64/commit + chmod +x ./dist/darwin-amd64/commit + + - name: Prepare release assets + run: | + mkdir -p ./release + + # Linux package + tar -C ./dist/linux-amd64 -czf ./release/commit-linux-amd64.tar.gz commit + + # Windows package + zip -j ./release/commit-windows-amd64.zip ./dist/windows-amd64/commit.exe + + # macOS package + tar -C ./dist/darwin-amd64 -czf ./release/commit-macos-amd64.tar.gz commit + + - name: Upload release packages to GitHub Releases + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: | + ./release/commit-linux-amd64.tar.gz + ./release/commit-windows-amd64.zip + ./release/commit-macos-amd64.tar.gz \ No newline at end of file diff --git a/README.md b/README.md index 385187f..bc3186d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ go run src/main.go --setup --path F:/Git/commit-msg --name commit-msg ```bash go run src/main.go --path F:/Git/commit-msg ``` - +### this will run the commit-msg in the current directory ```bash go run src/main.go . ``` \ No newline at end of file diff --git a/src/grok/grok.go b/src/grok/grok.go index 4d8890d..aef8525 100644 --- a/src/grok/grok.go +++ b/src/grok/grok.go @@ -17,12 +17,15 @@ func GenerateCommitMessage(config *types.Config, changes string, apiKey string) prompt := fmt.Sprintf(` I need a concise git commit message based on the following changes from my Git repository. Please generate a commit message that: -1. Starts with a verb in the present tense (e.g., "Add", "Fix", "Update") +1. Starts with a verb in the present tense (e.g., "Add", "Fix", "Update", "Feat", "Refactor", etc.) 2. Is clear and descriptive 3. Focuses on the "what" and "why" of the changes 4. Is no longer than 50-72 characters for the first line 5. Can include a more detailed description after a blank line if needed +6. Dont say any other stuff only include the commit msg +here is a sample commit msgs: +'feat: improve performance with lazy load implementation for images' Here are the changes: %s From 08e8bce2c1a1f6ccfd1fae0567e584c68b9df607 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 14:50:48 +0530 Subject: [PATCH 11/30] Update: Upgrade GitHub Actions to latest versions for better reliability and performance Improve CI/CD workflow by updating actions/checkout to v4, actions/setup-go to v5, actions/upload-artifact to v4, and actions/download-artifact to v4. Also, remove unnecessary log message in main.go. --- .github/workflows/build-and-release.yml | 20 ++++++++++---------- src/main.go | 1 - 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index df23604..8da43e0 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -30,10 +30,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '1.22' # Use the appropriate Go version for your project @@ -50,7 +50,7 @@ jobs: shell: bash - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.asset_name }} path: ${{ matrix.artifact_name }} @@ -63,10 +63,10 @@ jobs: contents: write steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Download all artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 - name: Create Release id: create_release @@ -89,27 +89,27 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '1.22' - name: Download Linux artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: commit-linux-amd64 path: ./dist/linux-amd64 - name: Download Windows artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: commit-windows-amd64.exe path: ./dist/windows-amd64 - name: Download macOS artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: commit-macos-amd64 path: ./dist/darwin-amd64 diff --git a/src/main.go b/src/main.go index c35408b..437b556 100644 --- a/src/main.go +++ b/src/main.go @@ -195,7 +195,6 @@ func main() { } // Display the commit message - fmt.Println("Suggested commit message:") fmt.Println(commitMsg) // Update the last run time From 11f1c4e66ba9631653eccabff46e898397544990 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 14:55:34 +0530 Subject: [PATCH 12/30] Update: Enhance CI/CD with auto-tagging and improved artifact handling Add auto-tagging job, refine build and release processes, and improve error handling for artifact creation and packaging. --- .github/workflows/build-and-release.yml | 86 +++++++++++++++++++++---- 1 file changed, 75 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 8da43e0..062cee2 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -2,18 +2,49 @@ name: Build and Release on: push: - tags: - - 'v*' branches: - main + tags: + - 'v*' pull_request: branches: - main jobs: + auto-tag: + name: Auto Tag + runs-on: ubuntu-latest + # Only run on main branch pushes, not on tag pushes or PRs + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + outputs: + new_tag: ${{ steps.tag_version.outputs.new_tag }} + tag_created: ${{ steps.tag_version.outputs.tag_created }} + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Auto Tag + id: tag_version + uses: mathieudutour/github-tag-action@v6.1 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + default_bump: patch + create_annotated_tag: true + tag_prefix: v + + - name: Print Tag Info + run: | + echo "New tag: ${{ steps.tag_version.outputs.new_tag }}" + echo "Tag created: ${{ steps.tag_version.outputs.tag_created }}" + build: name: Build Go Binary runs-on: ${{ matrix.os }} + needs: auto-tag strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] @@ -31,6 +62,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + with: + ref: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.auto-tag.outputs.tag_created == 'true' && needs.auto-tag.outputs.new_tag || github.ref }} - name: Set up Go uses: actions/setup-go@v5 @@ -56,8 +89,9 @@ jobs: path: ${{ matrix.artifact_name }} release: - needs: build - if: startsWith(github.ref, 'refs/tags/v') + needs: [auto-tag, build] + # Run if a tag was created or if this was triggered by a tag push + if: (needs.auto-tag.outputs.tag_created == 'true') || (github.event_name == 'push' && contains(github.ref, 'refs/tags/')) runs-on: ubuntu-latest permissions: contents: write @@ -68,12 +102,21 @@ jobs: - name: Download all artifacts uses: actions/download-artifact@v4 + - name: Debug info + run: | + echo "GitHub ref: ${{ github.ref }}" + echo "Event name: ${{ github.event_name }}" + echo "Auto tag created: ${{ needs.auto-tag.outputs.tag_created }}" + echo "New tag: ${{ needs.auto-tag.outputs.new_tag }}" + ls -la + - name: Create Release id: create_release uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: + tag_name: ${{ needs.auto-tag.outputs.tag_created == 'true' && needs.auto-tag.outputs.new_tag || github.ref_name }} files: | commit-linux-amd64/commit commit-windows-amd64.exe/commit.exe @@ -84,13 +127,21 @@ jobs: publish: name: Publish to GitHub Packages - needs: build - if: startsWith(github.ref, 'refs/tags/v') + needs: [auto-tag, build] + # Run if a tag was created or if this was triggered by a tag push + if: (needs.auto-tag.outputs.tag_created == 'true') || (github.event_name == 'push' && contains(github.ref, 'refs/tags/')) runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 + - name: Debug info + run: | + echo "GitHub ref: ${{ github.ref }}" + echo "Event name: ${{ github.event_name }}" + echo "Auto tag created: ${{ needs.auto-tag.outputs.tag_created }}" + echo "New tag: ${{ needs.auto-tag.outputs.new_tag }}" + - name: Set up Go uses: actions/setup-go@v5 with: @@ -114,29 +165,42 @@ jobs: name: commit-macos-amd64 path: ./dist/darwin-amd64 + - name: List artifacts + run: | + echo "Linux artifacts:" + ls -la ./dist/linux-amd64/ || echo "Directory not found" + echo "Windows artifacts:" + ls -la ./dist/windows-amd64/ || echo "Directory not found" + echo "macOS artifacts:" + ls -la ./dist/darwin-amd64/ || echo "Directory not found" + - name: Make binaries executable run: | - chmod +x ./dist/linux-amd64/commit - chmod +x ./dist/darwin-amd64/commit + chmod +x ./dist/linux-amd64/commit || echo "Linux binary not found" + chmod +x ./dist/darwin-amd64/commit || echo "macOS binary not found" - name: Prepare release assets run: | mkdir -p ./release # Linux package - tar -C ./dist/linux-amd64 -czf ./release/commit-linux-amd64.tar.gz commit + tar -C ./dist/linux-amd64 -czf ./release/commit-linux-amd64.tar.gz commit || echo "Failed to create Linux package" # Windows package - zip -j ./release/commit-windows-amd64.zip ./dist/windows-amd64/commit.exe + zip -j ./release/commit-windows-amd64.zip ./dist/windows-amd64/commit.exe || echo "Failed to create Windows package" # macOS package - tar -C ./dist/darwin-amd64 -czf ./release/commit-macos-amd64.tar.gz commit + tar -C ./dist/darwin-amd64 -czf ./release/commit-macos-amd64.tar.gz commit || echo "Failed to create macOS package" + + echo "Release packages:" + ls -la ./release/ - name: Upload release packages to GitHub Releases uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: + tag_name: ${{ needs.auto-tag.outputs.tag_created == 'true' && needs.auto-tag.outputs.new_tag || github.ref_name }} files: | ./release/commit-linux-amd64.tar.gz ./release/commit-windows-amd64.zip From 49cb38945c4f9386fc0bf77d0e1a0b1bd83fbd26 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 15:11:57 +0530 Subject: [PATCH 13/30] Refactor: Simplify CI/CD workflow and improve tag handling Improvements include: - Streamline auto-tag job output - Ensure build job runs even if auto-tag is skipped - Simplify release and publish job conditions - Update file paths for consistency - Enhance debug logging with 'find' command --- .github/workflows/build-and-release.yml | 56 ++++++++++--------------- 1 file changed, 22 insertions(+), 34 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 062cee2..ef8035f 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -18,7 +18,7 @@ jobs: if: github.event_name == 'push' && github.ref == 'refs/heads/main' outputs: new_tag: ${{ steps.tag_version.outputs.new_tag }} - tag_created: ${{ steps.tag_version.outputs.tag_created }} + tag_created: ${{ steps.tag_version.outputs.new_tag != '' }} permissions: contents: write steps: @@ -39,12 +39,14 @@ jobs: - name: Print Tag Info run: | echo "New tag: ${{ steps.tag_version.outputs.new_tag }}" - echo "Tag created: ${{ steps.tag_version.outputs.tag_created }}" + echo "Tag created: ${{ steps.tag_version.outputs.new_tag != '' }}" build: name: Build Go Binary runs-on: ${{ matrix.os }} needs: auto-tag + # Always run build, even if auto-tag was skipped (e.g., for tag pushes) + if: always() && (needs.auto-tag.result == 'success' || needs.auto-tag.result == 'skipped') strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] @@ -62,8 +64,6 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 - with: - ref: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && needs.auto-tag.outputs.tag_created == 'true' && needs.auto-tag.outputs.new_tag || github.ref }} - name: Set up Go uses: actions/setup-go@v5 @@ -89,9 +89,10 @@ jobs: path: ${{ matrix.artifact_name }} release: + name: Create Release needs: [auto-tag, build] - # Run if a tag was created or if this was triggered by a tag push - if: (needs.auto-tag.outputs.tag_created == 'true') || (github.event_name == 'push' && contains(github.ref, 'refs/tags/')) + # Run if a tag was created by auto-tag or this is a tag push + if: (needs.auto-tag.outputs.new_tag != '') || (github.event_name == 'push' && contains(github.ref, 'refs/tags/')) runs-on: ubuntu-latest permissions: contents: write @@ -106,9 +107,9 @@ jobs: run: | echo "GitHub ref: ${{ github.ref }}" echo "Event name: ${{ github.event_name }}" - echo "Auto tag created: ${{ needs.auto-tag.outputs.tag_created }}" echo "New tag: ${{ needs.auto-tag.outputs.new_tag }}" - ls -la + echo "Directory contents:" + find . -type f | sort - name: Create Release id: create_release @@ -116,11 +117,12 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: ${{ needs.auto-tag.outputs.tag_created == 'true' && needs.auto-tag.outputs.new_tag || github.ref_name }} + tag_name: ${{ needs.auto-tag.outputs.new_tag != '' && needs.auto-tag.outputs.new_tag || github.ref_name }} + name: Release ${{ needs.auto-tag.outputs.new_tag != '' && needs.auto-tag.outputs.new_tag || github.ref_name }} files: | - commit-linux-amd64/commit - commit-windows-amd64.exe/commit.exe - commit-macos-amd64/commit + ./commit-linux-amd64/commit + ./commit-windows-amd64.exe/commit.exe + ./commit-macos-amd64/commit draft: false prerelease: false generate_release_notes: true @@ -128,25 +130,15 @@ jobs: publish: name: Publish to GitHub Packages needs: [auto-tag, build] - # Run if a tag was created or if this was triggered by a tag push - if: (needs.auto-tag.outputs.tag_created == 'true') || (github.event_name == 'push' && contains(github.ref, 'refs/tags/')) + # Run if a tag was created by auto-tag or this is a tag push + if: (needs.auto-tag.outputs.new_tag != '') || (github.event_name == 'push' && contains(github.ref, 'refs/tags/')) runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout code uses: actions/checkout@v4 - - name: Debug info - run: | - echo "GitHub ref: ${{ github.ref }}" - echo "Event name: ${{ github.event_name }}" - echo "Auto tag created: ${{ needs.auto-tag.outputs.tag_created }}" - echo "New tag: ${{ needs.auto-tag.outputs.new_tag }}" - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.22' - - name: Download Linux artifact uses: actions/download-artifact@v4 with: @@ -165,14 +157,10 @@ jobs: name: commit-macos-amd64 path: ./dist/darwin-amd64 - - name: List artifacts + - name: Debug info run: | - echo "Linux artifacts:" - ls -la ./dist/linux-amd64/ || echo "Directory not found" - echo "Windows artifacts:" - ls -la ./dist/windows-amd64/ || echo "Directory not found" - echo "macOS artifacts:" - ls -la ./dist/darwin-amd64/ || echo "Directory not found" + echo "Directory contents:" + find ./dist -type f | sort - name: Make binaries executable run: | @@ -200,7 +188,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - tag_name: ${{ needs.auto-tag.outputs.tag_created == 'true' && needs.auto-tag.outputs.new_tag || github.ref_name }} + tag_name: ${{ needs.auto-tag.outputs.new_tag != '' && needs.auto-tag.outputs.new_tag || github.ref_name }} files: | ./release/commit-linux-amd64.tar.gz ./release/commit-windows-amd64.zip From 965782ff4e624f84e62876db3ef81667821b6485 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 15:15:22 +0530 Subject: [PATCH 14/30] Update: Upgrade Go to 1.23 and streamline CI/CD workflow This commit updates the Go version used in the CI/CD pipeline from 1.22 to 1.23, which should bring performance improvements and new features. Additionally, the workflow has been streamlined by reorganizing the job structure, renaming jobs for clarity, and optimizing the artifact handling and packaging process. The release job now depends on the new 'package' job, ensuring that binaries are properly packaged before release creation. --- .github/workflows/build-and-release.yml | 134 +++++++++++------------- 1 file changed, 62 insertions(+), 72 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index ef8035f..e60aa7b 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -68,7 +68,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.22' # Use the appropriate Go version for your project + go-version: '1.23' # Use the appropriate Go version for your project - name: Get dependencies run: go mod download @@ -88,108 +88,98 @@ jobs: name: ${{ matrix.asset_name }} path: ${{ matrix.artifact_name }} - release: - name: Create Release + package: + name: Package Binaries needs: [auto-tag, build] - # Run if a tag was created by auto-tag or this is a tag push - if: (needs.auto-tag.outputs.new_tag != '') || (github.event_name == 'push' && contains(github.ref, 'refs/tags/')) runs-on: ubuntu-latest - permissions: - contents: write + # Always run after build + if: always() && needs.build.result == 'success' steps: - name: Checkout code uses: actions/checkout@v4 - - name: Download all artifacts - uses: actions/download-artifact@v4 - - - name: Debug info - run: | - echo "GitHub ref: ${{ github.ref }}" - echo "Event name: ${{ github.event_name }}" - echo "New tag: ${{ needs.auto-tag.outputs.new_tag }}" - echo "Directory contents:" - find . -type f | sort - - - name: Create Release - id: create_release - uses: softprops/action-gh-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ needs.auto-tag.outputs.new_tag != '' && needs.auto-tag.outputs.new_tag || github.ref_name }} - name: Release ${{ needs.auto-tag.outputs.new_tag != '' && needs.auto-tag.outputs.new_tag || github.ref_name }} - files: | - ./commit-linux-amd64/commit - ./commit-windows-amd64.exe/commit.exe - ./commit-macos-amd64/commit - draft: false - prerelease: false - generate_release_notes: true - - publish: - name: Publish to GitHub Packages - needs: [auto-tag, build] - # Run if a tag was created by auto-tag or this is a tag push - if: (needs.auto-tag.outputs.new_tag != '') || (github.event_name == 'push' && contains(github.ref, 'refs/tags/')) - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Download Linux artifact + - name: Download Linux Binary uses: actions/download-artifact@v4 with: name: commit-linux-amd64 - path: ./dist/linux-amd64 + path: ./binaries/linux - - name: Download Windows artifact + - name: Download Windows Binary uses: actions/download-artifact@v4 with: name: commit-windows-amd64.exe - path: ./dist/windows-amd64 + path: ./binaries/windows - - name: Download macOS artifact + - name: Download macOS Binary uses: actions/download-artifact@v4 with: name: commit-macos-amd64 - path: ./dist/darwin-amd64 + path: ./binaries/macos - - name: Debug info + - name: Show downloaded files run: | - echo "Directory contents:" - find ./dist -type f | sort - + find ./binaries -type f + - name: Make binaries executable run: | - chmod +x ./dist/linux-amd64/commit || echo "Linux binary not found" - chmod +x ./dist/darwin-amd64/commit || echo "macOS binary not found" + chmod +x ./binaries/linux/commit + chmod +x ./binaries/macos/commit - - name: Prepare release assets + - name: Create packages run: | - mkdir -p ./release + mkdir -p ./packages - # Linux package - tar -C ./dist/linux-amd64 -czf ./release/commit-linux-amd64.tar.gz commit || echo "Failed to create Linux package" + # Package Linux binary + tar -C ./binaries/linux -czf ./packages/commit-linux-amd64.tar.gz commit - # Windows package - zip -j ./release/commit-windows-amd64.zip ./dist/windows-amd64/commit.exe || echo "Failed to create Windows package" + # Package Windows binary + zip -j ./packages/commit-windows-amd64.zip ./binaries/windows/commit.exe - # macOS package - tar -C ./dist/darwin-amd64 -czf ./release/commit-macos-amd64.tar.gz commit || echo "Failed to create macOS package" + # Package macOS binary + tar -C ./binaries/macos -czf ./packages/commit-macos-amd64.tar.gz commit - echo "Release packages:" - ls -la ./release/ + # List created packages + ls -la ./packages/ + + - name: Upload packages as artifacts + uses: actions/upload-artifact@v4 + with: + name: release-packages + path: ./packages/* + + release: + name: Create Release + needs: [auto-tag, build, package] + # Run if a tag was created by auto-tag or this is a tag push + if: (needs.auto-tag.outputs.new_tag != '') || (github.event_name == 'push' && contains(github.ref, 'refs/tags/')) + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout code + uses: actions/checkout@v4 - - name: Upload release packages to GitHub Releases + - name: Download packages + uses: actions/download-artifact@v4 + with: + name: release-packages + path: ./packages + + - name: List package files + run: | + ls -la ./packages/ + + - name: Create GitHub Release uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: ${{ needs.auto-tag.outputs.new_tag != '' && needs.auto-tag.outputs.new_tag || github.ref_name }} + name: Release ${{ needs.auto-tag.outputs.new_tag != '' && needs.auto-tag.outputs.new_tag || github.ref_name }} files: | - ./release/commit-linux-amd64.tar.gz - ./release/commit-windows-amd64.zip - ./release/commit-macos-amd64.tar.gz \ No newline at end of file + ./packages/commit-linux-amd64.tar.gz + ./packages/commit-windows-amd64.zip + ./packages/commit-macos-amd64.tar.gz + draft: false + prerelease: false + generate_release_notes: true \ No newline at end of file From 146c2e5eafc201776b7e3dc39dd3373fadf53f53 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sun, 2 Mar 2025 15:22:42 +0530 Subject: [PATCH 15/30] Update: Enhance README with detailed setup, usage, and sample output sections Improve clarity and provide comprehensive guidance for users. --- README.md | 46 +++++++++++++++++++++++++++++++++++++++++----- image.png | Bin 0 -> 27302 bytes 2 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 image.png diff --git a/README.md b/README.md index bc3186d..ac046b9 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,54 @@ + # commit-msg -## Add GROK_API_KEY to system variables +![Commit-msg Screenshot](image.png) + +## Add `GROK_API_KEY` to System Variables + +Before running the application, ensure you have set the `GROK_API_KEY` as a system environment variable. + +--- - ## Setup - ```bash +## Setup + +To set up `commit-msg`, run the following command: + +```bash go run src/main.go --setup --path F:/Git/commit-msg --name commit-msg ``` +--- + ## Usage +To run `commit-msg`, use: + ```bash go run src/main.go --path F:/Git/commit-msg ``` -### this will run the commit-msg in the current directory + +This will execute `commit-msg` in the current directory: + ```bash go run src/main.go . -``` \ No newline at end of file +``` + +--- + +## Sample Output + +Below is a sample execution of `commit-msg`: + +```bash +# Example output here (replace with actual example) +``` + +--- + +## Screenshot + +Hereโ€™s an image of the application in action: + + + + diff --git a/image.png b/image.png new file mode 100644 index 0000000000000000000000000000000000000000..4c59a1f7223bcedff5f077ad30f5eb3eaa35061a GIT binary patch literal 27302 zcmce;cT^K!7d9F?iuB&4C@4|{r3fTSRZ#)yMM6`GROvNbT8m;zu;7#ve?E^&Vk=Ul2 z`h|UMYS**hSguu9ymJ1SvbLwtcdybku0BO?c=G97O0fQs(rkEaRaX!fd|W7_%AU7C z_5HeJIC(a{+rjVHZmS@I1%$$cYmAi4KHY1WNk6d;Yn|PmkgbQ@<4pKx`~Gql#jNws z_FGM^nUnqR4F}*|Nc^XCcofs_KV@}*-{^wG|Jg);#i;*%Cwm5~PiJAc)OcD@B%axv zY+HD~e*>)-aD;=czgdC83;9PYRDue`4t-sIsAA0juH}9U)9!uH_A0^u$^FC#Ul&Jd z0m%DL4-h#ATT?}DuF|{rwv+ohT878<-iH8I!j|UUZQ4Qr;=(^ITgZ&)HL6detc7@b zj0aH(pz{VYiE;t(2^>j2S-_iUOxC6Q*%*ydC2WcSu0i3YLRUX&jEBpaSxud`{@zS| zf2T>|H|gR6_I4q4&$*{P-Q*A9kp<-s%v^;*fggk`EM~8USZ?hM_f_EqdZkEdDpDU! zG)e+UX=@dy0e0iA1+~n_m$ai$GANzyH+}2hI#!skH40mwx_eXC!lFET-LJ(3gXI3| z%nf{rjVn3tB1RA3`Ub7g!9P8ZtJ`b}bGR|-o|~4-c4j`Mv)QTo)~LMT_))jff*V$8 zcccvfXs&an_R?YY=(#CLQ zCjGtGC$#tyk*K&$_mby?$1b7%*Ta4jZ2H!>gz~2S6+kkoSw?lg-_e6cLDpBQk66O} zSn4fY2CRM{q)FWXhr$(QqTW&}l9*tdnxLO=iu#(%->{D8RLXr|@39c*d~_qumrU$H zYN%v|%gQ^>dZLRVZoGWIjuc^G-wmVv)w8$e)ZK(KRr409 z(t9|ic{{REzhcMG1E;Q->7@oCwfq)IDHsnGZB~!W6AlBnCVscm^uY&mhGN`%QeW9? ze7+I5-BOKy_Do`+@woFyxbhVMAXkTtWf%}=f-$WkK9j;hRCH_6BD_wy<7Ci^|*!G9)Xf+P? z^ClAudzzFuqBG-tPZr2b6K@>zz*xSuILn1M690eN$CG9Q%`V0&!z+OlCjV&Zbd@ua`l>DC|k* zO!7{iKhv3$JmX`0WAYR$9MP2uI?`T&#hyolwHD+WTh${wKUcF`Ap~XM3=$zdAp5nw zT`h#9%o3r=Tz}26e`3!<-o-!uTZb4?&0k#vwLIg94a3tII0$$v9_ z)axBuzo@cNEy5~M1VXdBfM*3n^@R;@pl8Uw7lYJI&mDfvk7DpnFR!A^rhO=N4tv55 zNyQX__E?=Do#fw~RY)PPsB8^&|LA+0Z# zz>BHR=}=tng5-Z~oqC~$E1T7U$4lO546kPG3_2`kr8gQ|Eq@O?hmPmvR1QyF;cQ47 zoe7@6EN+2dsbme~Jv59ghlXBnba8Q$cG#Gr*R~E8R`#Scy2@&5qAd_2NUCZ!yf~LP5X7 z2tM;C^YXI?4X^B6drwQ7&`{K~B!bB`0pk}Uz099t?=;srstHK77qziG`7XH-vifM2 zcf`BFVq#+ztBij%YtfeMy*{L$IamRHbFqyIfA+%gqRe9frKR4c%!@_7eLRn$F9yf4 z+A7b<^W7Vf<8ghW#EKg){xXB*+}ad-Z%7agKlJhTCu3n_Td9smjKOToY8jOiWKFKc zyQANZz}y>wdoQOqM+tmQaf>f@MWH&?kHGJIgN1f2ceM`Hz23aV)xIM4J08s)^M~fG z_&n`Df8px4l#8v%>lY%(R0uf;Uz0L{4?)^bi!7xEyfbc}`UrnBI*x1$t;hk)R)KXq zT{)gC_{sYa&$>5&x%YBD?q0dP4bnK~m-JBp-n8HrQA2s=E7+6P+V5yomVDA)muQ;J zempX0`6}!0NiGP-eUPykJ9Gqe5P#Ba9OqN}!|+hb;#YVVa!d09<01)670R=%+w}Ii z1GMhlh2#TJF^pNMr0YIyTg41ymf?548_%;?N8PM{lM+ zJMy_N>ciTS8_qJZ=T&U8nX3>HPFMN%IS|6$-pEz5zDw3^`XRXSwSrpn3b6pL^7M@= zt?~qTY-@^~yt}dJuGYS}1nDVETpn<_b=kwgJP#7I_?T7Vo`Q;qwl0x14bwp{1;!0H(wu%8@S4+1ryi$I}F_kW}yR!7M|b>U=?7I(S9R$s?=m zG``Y`Za9tlA!50DZ!4}kX*exnc((jhj=J&?#No%(G#KjCWy`7TrRa1Z)9%Y3*pp`Z z19}hJm*Le{+}pmNtP+Oo(`-_hUOlr{UF{F=leze7@U0~V{}#I<^DbyFhjGf_M3-ng zKfK60jVgqB@9DimdEK)s|K0j4H$ves8?fz5= zE&`1rSQbg(2~99;ivJ}Jjp0KC)=#@I;xNAxdUVqoHd$q`&#kis}23M z{&h`l6GD)>;?}33QJ0BpmF7%Ni@*w<6JU`n(^~i5t;|96umJv8FC8}ZZk}1ag;U5 z%Q*|)_AD4z+h-${nna_#-HnnvHbe#DUd9Q82ewi*w~EGuy)ix+wgRM|m;FI)vwAB< zrXV$CWgVT2ba7w`W8_aO+brv?u)8qPP3tGmsS$FMAMn<|9UynqRs3Ump1hCW#V+oJ zEh|UOV;0e^2pK4-{CMKVlG)mTpd}LPOwuZJPnpIIKl!%C@mBh}Lsx>ReGalg$@2#* zrfbGE2F|>pU$xWtWR>~Kpeufc&YH9p!{5~HRgCq~1kOGaguVI6XcH2YrOLAqc49t7 zuJH09jw4!#CxQN3njEPmP%|KTfj8>oPUd!KN0l3n?7fUb{~b`tQTm_eW>fymII-F+ zZB#dpba*cGF6@;qqVR$fdbkHa_8V=2lnu=ifj^QL>Y}`~s&ye+qnUvixwvdZFf$oV zUR6ir?_RYLyWEke=U8s8i-1GIDM3=iGuqsOc~aaQJ?9X`h*v^rwgWA%bdQ0?=(l$0 z0TB+HkF|Rdky{#F?wZj_p-NgY98~AD$)b72^Y;3v_ROourT)C}rq#hGr5ENXq5p2V z^u*=XsNZN#A#ii3RiS;>Z(QrAwZI8tmo+nZjsDl`>k&o|r-awSypb6$UiMpIP1fzm zcBD>;??`J+dZN-b5Zhn#4)+R8cOp+_&_ze#m_zLo=m;1r(!Zf-Z|vFd)4fDTb%WVJyt zpytyc1Lsgpaia*p-M-yZ&&LShf#Ick%Ka$}bE~F5Ep$ChbExL%8Ij{VP$3^MzavjI zODXq!RaF0cz&j)-&+jkQ1@X|YFKXt4km*mSEe!dg&3G8 zzPIQ>qH-|yEcwA+m;S|}P-OsTQpM=X9hr5`{YAsbXfNMxUrJzj zyb6ljSzeM&URUuh61?Ji&(*5SgU}!0bE1{9D+RkbQIpZ!P9T3f&IG)^O9f~l4kxK6 zo@t)2?Qlca*lcfDeOQ`M&)EfOQ2?Y?DAk6SRssc54ueeh=mFBz8u44iE;iJ{Gm+H* zb6V0yS)PMA&+vp}CRZubr_NXQLsW~86bd~I+JxQaK zG2M>CLdAAHbjK1x4dLaj`vnX)My)L)L;{{f5D!Fm%)e^d_VrWAc!c;-N zj|(}-$!F9yT#pI+@qH~Q6QL&$SPK&drJKUI-m^QPg9`Sp8a@lrve_d*2wf>lSCy~$ z6hOKta3~YM2LLx4LJQH%U0(UdzHJv+CBltC5_U^=@km&M!)Ih6?#`XmjTgI=Nb~ON zreqo)b-j?)p(O-KxH`Q)jKp%X0xDtHg*TOC%WGvHH*hc7-j0=QcL*U-MCMIV#O6vtz^l76!mkp_q_)sQt*5 zo=1MGBGJ2~KK7#)G^^~Y;55w#0_bDVq4f$;-f^njV*lQII>v-q?YdU&@Or1 zJVfw$;P*~^2Y>+i_t!;PO$lzC&lWV)cC*W1w|-~_75$dL&1BV!s{+YC&lAuxf+AK{ zsXstwP7fY(UOu)i*t3RsxgECldyAh_K6m;8xlReYV-0{|$@iPrm#c;FB;irDjw4eP z0gUL%Ogn@fAk`~Js^235KNu$NrA>$~ref0Q+)B>PjJ`9oku7go-?KdKcnGT;wTx!s zA@reH@M{($1az_p78OG$)3mgTQ(u5BChn4}SyC?@QiAarSRdQZ#9iH)v8*p#jo0_O zI@TU7d~F?hOuDUyOew9Aw>*oF$#9LO3yNIEhQu*|nHH2-Qsv(xM^s$K4l^$YT>fXk z7q#&4a?xDO-HG$Y|lzph=qz)*Q1Q>nWzfu!h*8bb7h-R35F z)@P%+>xK_#lL9)98VoYaK>+(7vO-XA?VmEBZ=VqroEV*wtH=xC>fd5^c|o^EW!0zD zH{8!A>ot8!Mh*A%!eYH4YJ8~(65U#_!PLjUJ(MTbqjS+FXpO)mKm&NkAOPWuQ)ez^ z{GNq(ANo*)u4!jW{qSjP!}pM-CHw8IQ3O9YTuQA1@${SKg&?QUDtj^W=i|s!)W#u% zm*5^o!x2hzQz8@*#uTv?mJEq5TGcmP9s$v4p4G7NImySm_)1~rH++k47i#_W1i#It ztbJfFyh<&`4ue#tS{bhcg`)dnjozhD*tVUIk2~81JFci8zfFr^3%$uNWB$fYgB$z* zCg6(qs6WczzwkGeCN3WFgK4)mBVscDuUjbl@3*`1N)?hoD~e(>)PTJiBbz@TqB1vE!kuhFI6xp zP$_hJ7<)5B-LZs)(eSXCc}x9s>#MTXU!>W3Q=KUhIALbw9qESy0pAGfzUD#A(^qk_ zy>QoWZDl-B-hQ(T#U;&zUIWm+nVkRIV;++bh{u0+Z95+Fod`GLIZNDCIbu?laQn z@07ROrVz|Rq(41J_8+QF=Fp2>Vwe7Dd~PuI+PRv--uy#5{UWn0mUDv(QJ;&3t2@6Y zt`vN=Jb8_T7uCJYRio_$3_Ve@uj^d9-Qw?~$g}N3xcN_KN*0ARK?JD${mAdabPKuH zX1RvWxkVE2SB||iz#fJ}lsfAbEJ285g0OD0yFl!vx=crJ1z&mLdskE6mha)8#cD}bM zsmXofojYM;i-Oz}tc_pV^9udg@T_YN<6p#W6;HIgp5HyiV%Lr|JmwCRc|258=f zb`F36VHmY-m@SPdxby9SM6)TgWz1gg-D(Zy2VHK!-)J- zR0rX`m`}pfj!l*?v7F~-^0RPQmXOPrE6{%2&c9}#>-ol`=I7M>!&-%)t4~;_ZtsZ# zSeGm(LTGi*i+#ZjTKzL0H}K_Qf2N{xyw$|wabj<{?NBTPX#ScGu5q%hap?zq-;|aOFB1B@k;lfQH6g9lV$`aUSrl*Bs!2G)p z%Kl4Y_bH1Ebj5SJ?_-BF0+I^eF-peH8U-#J1SFX_lL59JAINgJkRP%g6p z;z{=7by_nDp#IBg1@LH6LwrW7Ck6BAbo<1*eneGwG^#iC_UR)lP-M>rs8osw;w`}# z0y)&j#id4^MVd(LKd-JrK3nt*cvi;C#UIbmh3|B1jc&0)nbtZuLA$W*4jaagUfj*K zbRAK-*0aj#tc9__RHk_wk~YXA-+zDvXvDP&cdBNZ5?`y$YIvU$!5%Oskv=@YfGW`? zRnHG<-?f}RsX*qxFnpcJer62R>%daqnYmgp$n0eCC zJNS|eZkI2l!FhQv=hNbv5)Eyb)4Z)>1KguekogOSV-`#01B<$3>eN6bInQ8&eOVO8l&_|G<5a{_) zFVHB>)O|1+*eEV)C2>#sXE;~xWFp6t2LjY4KUq><&)Sj&8AYzLDB)!`ZPqoTuOhQY zVbwR%DwGA~3N^;AChtTdVWg|Yu2yj+JFHCj#%nHeB89Uf$vYg#vac*p4JrrZ-TOe% zSG{=Wsm>YdYt?Xb>kCn?;<{^97llgpOlj?Z*Bxdoy;`7q?_lc4R>dn9FpWe%?iIoO z^Hmh_^Zx4q5KlQHP6)u)AL!m4eE#wQ|5uyZJ)+Q1xKW{F zN(~9n=d!Mff<6x!9noLfWwzW1W;m=FVxB!233ndq?+j}_{8RdhsNf6jW&%HECdqf;lgG!|XMDZ6?)Cv8(m{Dj$ga+h+6?9fKd>S_miRCBtsiJ z8VPysn#kx^ocaYemw{NNsuET$37Z|p!Ljukpc4PFgp5Mi;MeIy>QG`u&y3{I&|*r& z?utP%S+`IAS&y9`sT(hShJ%RoCA1`hc1{huP8SDjg2(B%=i8~0;Sah{gz)V!7vRGw z))!Cta=vz2twqQot6tYcOs)EvM0!3$>YImJU!#6Hy6m(LmJ5DMCQ0xbi}FSPUPfg# z#y$4>))A0N5peyU)$A2o@zLwsUL(Wo!v}iwWpcQ#L`sS7Rq}0psTHLjj+zP`f1IyT zdC)(d{y)R^-6W1@;r>OX8018TX$0R_o3p-W*!eWeR?iG%4dL?g?6p=iG_EZAvKV(G z?E}?-SN&+dh@9=hWYSr-K9(PGV*7CAnGiUHNB6%jeX=XAqEuHk^d62Y`w|>JVw_gJ z(8_M$ch+aRAm&eOuWrmJ`; z{fSWIyT+Z$j0F|%0wMh*%nb-2?3o>W`3A<^>V>7s-s8`HA(!VA<*18XZ|*#vA@(_j ztlg%yc-x0g5}2VCGbUd~XQxo*vf5YXV>vLe+gfY%_YAKsqt28r8ct!lH+XZU4fhR> z(xoGH(Pdx==g^vh<>e)lN-;*EUc`zr4My27d1-d-ND8< zOd>qJt(K4bX(#3fs}X8pZICd3!|dq`wQ_k5Ku_EWRZRCw008a4*r4(&s|U-~8*lBt zW^%jHwr6*WA$vw*2k}B1Kfz9S-hDw%Xs$XOIP}0=6qRF#b4Ka^&jI~rrbrWjsHeSo z#6I?d8;zXFORA|`BT~|tFFD+9MYvJ%c*tx0tSz+kF~Vdy_VS0fc}nnjs>PVVSsZ&A z){R8@;@%els<_1<1|rIyUt_FijU&Wa^Bbk1M5s)Bdiq}c=|Q{YsqvWspn6O|MUtCx+0nI%0G+#? zYVzY~PQN%}bx!|*bGQ(P5cD?Oq!@|BcbYfy*7^Mj!R9S%man-#KFr+)S0A2SH#qx6 zBot7tnzZn=>I`$U9gfaj_W9M-UfE!8z(rC(b71|2jAxOcw?ESXd(0r54L>XNxFg0o zz)@-VI9nQ`UaO%=YrJ+jYn%A6@HdEC#aMKKwYHh}gl_8H7e&S*?dYui$nt9Qm3!{t zsGfM4!P(xtw~SdH5Mg{J;6YXI4-30qgK8nT8Nh-GOgWNzse{udS< z#4XbWBD6xJJCL_e>gLLS5jh( zI!^-b;Iw;rHB*g^M+I(E{Yd909r=p6+YE@m^FN{ItEx{Rz_%wsR_CsCp$*fE%S}VB z*uWxmc4QDhM?(kFzy%7&Is^r{kSb9pAM547X@WHcv_@D>hrOFlTNflS0hqP+P-b2=NWbU5|_%d`t9KBe|fv+eG(W62Xj z64LMkiS%KyW96VJS4t$DGHR6s@%QZopS2p)7*8;wDQpWpKi$t?6G>qH@~-i;o`9W9 zVU2$&Hk+6Kya_787`FM~N&rr4Ch5RWTE;s{IBGGJWyYQlCZt=Q1;b z&dE0mW@O~2e?CLQ?(R%AX#L`lSMCPT#mN6X8{DS-pycVLg^YND(||m~4MY5P&-Nag zm+Q&zHKfb388&#znyZnZSDKpiMPPP}^~9}BQF~a5_%sSr;&CrzhbGH(-kya&Wqg9? zmr-QSk@}BC-&F8T5a~t>>7oAsLhjU1_hA-u?f`4K31M5@eT#;zfG^tUDR-m!jF_tP zl^9ubQ;Y9cf?H=U0&VNP`LJb?e?^HLOuMIp=bvOE4S1;DK>+V(P^X~y-_q)L8(kbf zBC~n&A4~@C$5t^bt5ZNhUA979aEv59b zer(*jjqSBxaA{2_7DnmX%O9v{1A)dV6;nL4QuLkXLG0ztXKeOw(lQ`=Fr&X((N+-x zb?c8;N+rPG3RKSwinLN|A3C)!H?M`HDv{&8!s1VZhkRw zR(zsdW+<3qH2Dj)4lqiooAIDv6WtbMA!5^QoY4cgYfbaH=DbG7YDCB8M|RoG$~?AL zdB20>gX^7a{)=Sp`L00~BpC3=>(b!Ujcbfdh@TWIs^V0OTtt0InG&51SdqWEt-3up z+SzwMzcx|_luJVwyCdNW-aH;RH}}-FC(P!YK4$5RXinVXcI^jUR zzuRZ1?Gw9)&iFEZuGUH%iYcst=;{#Vk7tuAu?j(#IfUWy`p3`d0xc`1!dkXO*>4f} zs~~j(lk-t8zh849Un3t#YUoXo#nPZo&K>?{Mv-Sc;f9Lzd4fhZMel7+^(816P3)qy zr;uoTEH(!`M)f@FwVSHNe!5v@5Hj+!YI42Y!H)X}n!O3h2 zrFvlAIR_FjZWJhJ>XJfEn5`b?qWen$lA1&f4J;3BA&EEF5ccN?RXaeuxb7C= z0qLF|znB$~~zk4OeeVx7fzdQ5;n*vY5eeKWSbc7V&sqe|~vN-M3Bo zTuJ^G)U^xOo+dx#@t`{STh`i9u=?G=?H-YIp3k(zr{~Fues2)!AdVBNW_dGQ2(G@f z^4J8#IDbW1lJO=;61M;9kcg4pt6`X475Y~CF5`P>5HHJ)bgx+Iz^@eeNImHcCm_@pAzy;mGXA0FI24q*A*H4WCf37kKXr4!b*&QgEBS+O@ zF@)4<#2)P`@4*4~h}-6>3_&R8UgRT4#=;;WqW?SAjkT~n{C^hpXi==`S5;>~Ck}u6 z1Bw43I6QJp<8%YI@d_v`F~|COKY39{>MqmVX9vg=bx^Z>R`G`#&F0yq7K# z4QgJuK&1{lB9Ux7KUj-U>Ah*FmU+gc6z$kjDsY6Sh3MQ}$0sbxqk_*FIy7R6VINIe zJE}}!vsl+~Ga?K%{OFYjf&aS4wh13pAX+p$Ksa~Ht0J?tt%?h#7s5#vo&pLBBXUUN zd~EEW-jHR>t2%z7uuqmvo;d2o2ut~qqd=`5GXo%LMHbfBr!l-I|T zhgc-x?K96U`%JRa0O#=IiKp6{-F?6pXo;?|l9sdCUZ@Lq_gqPzAY15FEbVI}qFF=* zlBgR_OAK$Z0@w&}%6qB7lVA znFD>e(@q1h59R@e`zlaR%~SpQ;gIsWiIe|ptHLKQ2@$aQ=cSVDo*`yS#mU$&p8uh& zC#xtn{Qsa=c0B%6a8-c2`mz1yoq*z;G&$HY?lE%o=~Y7q83ErKal<>j%-LJlyjVA2j29bB7@YkyclKP@3L*EU(O2Pgc{x+% zGY6yJ7hgbizdAcqbfRUGYj!Q459%Qa;}BHiSN~sUhL)Dxd8Hr)2+ohm%1)%3^XsFS z(#Vuctfdq%-!`d`N9VbbwpxS>4%=m;o%g7rQpk_qk~&bhuAeAx+#OJ_}lQF`5>@Gz*gR6&kwnWhL{llF>cEIrL(N20yv#F!xyIF$EMLkXhVC? zk7l^|H%OOpfRW7b>tubYMuhXvrK2sEn4dyXH#0-SEXK!I_nHqu%Cg+X4P|H)XC`{1 zr#}z!{%no|XN`hgyUWZ6QP^+9@n#$PQT>(fVZxqQt?;*fe@R*!W8a?IS-A|5kcz&O z&`fsFWom_kpSauJcaG0Mj=+BNDf|#V3TbqiqzY@hmU8!87pALpByjhNihX+Jcl~Kb z&b!K@+>J%*N>a?xA3YfQ?s=qD|BqA}EFDB6ut!0}rV+n|>Qo3dxdWwIHRsXne{i$) zN@G~D6I>s@?@ZcoMeL9^+YX&v(2KhiN)my2KE{GPQD1_f?=Z4{?wq=-RLqe%^$}PE zf+Wmakb}u=kD$7nJNgTe2+v(OWPMXq@q=GqlN5UO2E+}tyeoboOy-NDTw{phT^#cO z6&GEpy=gGOqrt+cK0;PG;)6z!>Jid2dXLJ@{@gg}i3{)rQo_oLQA9Z3O7%_`ioAQp zS;qn6F|Ye;M6IHK&fA<$Cpf$oe98SVMW2`Fyu$;Q95^JpVFJhesa-t&~ z8kcU4Ctj9(uUn3)9^N2a`2^J}BhnEops)o(IWIzSry?jk-=DQw^pq%H1uZ8Z%^Mc| zB*pDPdsk;bc4MGdGpy}6{Wv@FRt{_5Ib?~(wI)p)*R@AwTi^8negYtKmVG!NJ z)_dW}11{HqI#))ky1YeO5)t7dahI+_It?ZeI^b8x3N=OqT^WC!`Rjf zbjf~YjyExOglE2oP)FB#SY~94InH^j430bw;)Lk+!8S;&sKeGxNW1@Q^o3$8P$HJ6 zmz8$aJ9Ft1adnsVXk~DbP)dw#mGm28)5+$wcN?)YJx^V-UawJP`7hmWk&q)U=122E z>WvRZhdoV1rt4!%Bp-2wV^eC3LrLYljO@AT(GziqK6}+0dWV5x9N%8>4^ZF;{gpdR z&oPJMLYjdB4PqrP8@zZpC_goM&}IKIm>Z=0kf~IqwnWIZG8FJ;|RVyyF zAMOs_Qx8ki^wYVEzK7^exG27MsLgw%Y-#VSmfr8I=M#S8)hAYoAOkVEk8_|4e!za_*2tY<4-61Jcjug9A)ccwcs7$|R6?7d0j%C(OZ(h^;rn2L+Rj zYk6$tiQj5xSwz?O-nx=9w?I3{)Q0M9Tmuq5hKqMu?k4^LN;irDI;ry%@R~gu|8pSo zUNKG^LP)Mo7yrX=Iwa570 z3yb7Tg&30))~YG7MBZDzU(#g$SXSX!dlj7P)f=F#X+y_IE=;u-iQ}+jm)PWtN2RhF z;}bScV9k-mqzP}(Excd{geMnF4!%XcZ3}W<6V=p1!Z!%w1K?_+bG7IjNqF@%PRt#Y zNa7scQ$$x<087wf5}L~A8lOV?-u}2e86W46H`rjtC0hK~lHna@Giu*pujgXulc z>_DJ>;0Qb58|k4vvYJ6+`IbHTj(vFemVd#QU+rM#o{gfLADzbGb`dOi`5F6`)w9Q0 zqPKi76A5QK5Fso(NAj4hph`xZO;UkdgVaIIot2MN>5pXkwpFrD3qS2XZ_9q3p&_Ii zu6X8B2G3+}$LcneE{AbDf9>H&k`lK0Goua2EK5%WG07H1>gV{gMMio{QIg{4dBid!%05dK=*lJ4G zAuuJ(F(??3AMhabEZ>U6Nc(Z8?f#)#A#V<$9%MIB@A`<?iv>K$sSLY(#!!qy|v`RogHR;-!;)9@(Upc!7{mNz}vEH`Lii4~0AwwgfA zGiZM=1Pw;lu>((8UM1z18*as0E+pe)+>NaGW5qLeNhB6L+rNL0)1cG9(ygWF<0=gJGNQqhP|?Xyc(fr|Y*31YsG z(y+E|!e>tCDG?FQ<6WpraxuI8Krr^40ac@Qbk?LR?eyDjFL0ZHelanNc(eyTS~1M+ z!DgdG3(?C1ih)-@NKs!9w1Y=hEMR%ZWhun?`;B^-G$~w>6i@{#Ch?Za;I>lasfed{ z`kEdP7zgEDA&k4MIt#<#IH*sPm3K1%u&P4y%CIIY%dC?JcviF{^Eu|{sF;%nhg|L; zsozhLS~hBU_h6lS=0G8A#rCu6#|#pMZPt_qTiDmN+>={X89&G+3>)UPglqd&F5+DH z0Y;$;tlT+VLj_mOa48!Mb^dtiou_F+@u^=cvJBU5TH2p*c`u17K!fC@E1glN2+cv}RLGQYfX`D8D8YGlUw{*ZMvu_Su6*;%#T@Z=W-H}Lx zfcIU9MftFD($0D!gDBwg1E)+W6>4CyCb@qc*by-|10~uRM^fl#<{FwBo6(}rwm4H; zKU^kg7^m$;2;$+F`5=DAWq(ca1aW%d?uqTXyXem8CBrTdNV6L)eAex|Pe{`6Q>h9g zPnNC-B7+32if^L;V=9B=^DE_*z@3Hr>K(cZ_1+bD^%p0&c(f1Va@S#B?)FB2tEdw3LvL&VKm4BtT z06hixu;qRlPPlu|PcmsmJ8Rc_1xM9_-XxQ%7kmZQJx?SyH^Zpji);49%q5k`LFl$2 za~NJYRBjoYy(PMXUS?CI74-AlKc1j*kY7wJg0*bvUDnc6 z|8#ZnXL{wM?g#y<&~^AeH>}ED$QoCAI1NAShIm20pZu*@w+zMkSY#>EC z!nqI{`9+K_$^dbNR6fX~<}2ZuSw_-}ugO>a%Jo9Y0KSbumf|$}u!_*v_lMzwUR?h82eNM4Te47ny2@OKUO?Jm(~2(sRL6*x zL;TTce=n9hKf8C$!me+c31#ZraxzyN(e{Zf`5Dp9T>;r`VfKROmO`#^tRUI=Cqr91 zD1u7kdDW|ojV;MHd+*Q2Fo>&SFIE{4)IR3rAA1Hs)&=Kgc-d7}{pQK1Gb)gYw+KQd zpG5^#UFoVBV7WFzBUJyI|KQ#p&bSfM{<{RfH$6w9C4a{a?0r#gTlb` zAe|tg*SK5pqo-iRGN*-+=qcbsnXW|5LCX9Le`pZ-ah5G5jW-qioH+6M^xBir!5#zs zjwm%>jvNrzyQOY};)6t#%S&g%om7bM+zi;5O!=i$w=1#!%z*q)Qpeg1-N}BQs$>tG zlFkLV2>8GTAH6zYur6v-%PqS0K)2Yb_)PjX1JibmdD95Iog>TCnw>8*3BHnjEAN#W zOTY20o2B@pBe&=5?iF2Dx!w&bTBb0bi?Nay)s9omOWkBXR%zRZ3p`QI1yLj&jG45r z@P0mgVChKTTu#=55ORvy&0D}DW&zgF$n-L0hcam!yR$&RcY;`FOsG9;19- zQgPU~N_8C+`F^DO&M|?sLPm>neRLO1KJfE^%;cj?KQGk_JdJFp+A>RqU!w3@p)ta} z^Jjeq778o|LAE-Z2Mei&$Q>l&l=oL?B^}K}sPA$<7c1y-^OO3 zAW@9ApX$UVFA9_cX$n5aG(fHudt6toCh6wW6Vc<15jF?}e;Fi#-OUCVKu)gmK3j?F z#$uSp@)12pczutVL>QnJStAc(5LEH(J7p(MwkEjcAP|Qo%@Ahv25G*qan%pMCrY#9 zR8OXACvm#jfP!}RV0f1jcwT{vWPxztv8np0-JbQNd{Ng5QrUhv50TK#$ z+(2aGe%x33gE|*!9UMlU>WaW`a>AhrB<%HB(BPk`z~}f9PcMP9YdA%rd?}kBnQAU_13pWK$ZCH|EA{636})SjV)vD~q$rDw z&6#=Rb(v~@mHyoKr8w;;fSTnW(KEGE#xRQZbzUt@=t+AkQ=NTx>5t@c{ll1_6!A-y z&+4Ork83QmvXYUF@r%%Mvhn*zq_ty<1B)Lcdl8F2NqTsTqKHU#W1P;L^q@x2XI0pV zETV6wU)*#kFZI2T%!r7zN49f4;dDnn4Gf($jF;}f49a_n60yAOP3hkLOV|f*a$bDy zSwkeUQe-3=s_^J^I4q?Su>W0El6I!ZLwl>)^WuOgfSNNh`g$77p|V=zd6073=D0db z!#e0IX#!~17?FOSY_FJeFL=(1)%Hfp)kC?vh2ZoB91)H{LRJbcV6CzS<0dayW}cnQ z_4G66O#rUbu4sw$JhEQ6u=qKJ624-m<}Q5vblg9DxDn`T@4;;9^^g8Q-a+ zPA>}~mQG1j!Do$&Zbn$KA6yt*+&0DBA2Qs2o|HttYUq=ZR^B48%9Ik@gEaAnxMXdd zii@Kld6T~ z8U=Q8-h(wH_Qls8kMEJM8k&FIuDD-K)_FgNNIx0XOSfl)ZC(&7tWyN0hh>EsyL=KpF-Tw#HjYNvGf}uPj?4fg7=MoLj;T-X#+efBq%I zWbgwE2Um0~o8W+4GbAYUWe+-oNaJIL8sxemmU{Y_lI~c3V^ws0`RaIi+bxCO?q3F|>erphNCZYhR_>(7kZw@8y(HEt1Kv}ph% zdd-KzCyEgEpE$$hBXs0tSbZ8027|+5DZ2#Npo`?c<-OuJsgF`3Cc2Cc zDtK$UlBLq;UcitD2He(hdM6>mm-wR)F*m4fh4t~`yKDD4Ibapl(|BWo@Od6aA7QZv z`+u4{^LVKK|Ls$Ws4NjeQz?WDEtF-Z#Zsvl6lEDAvXo?vF(VSPWr?y)S)#-!YhuQd z#=cY<`;@_0LSqawX1mX{{Jwo2-_P&9|God?pE=H)_c`zLx~}JSDPWp~KU>kcg380k z%5)O1v*=fmeb>s6_cwKWSFjBkA~6Zl1#$|Yb-8r0f@(oUn$8G!9d&FlGPPFp$Mi{> z^iW_j6C6W7*nXN@5tX2h*5n|1p|!xa$jSA~(>;oEVlvIiHn)^9HDkRJ;+5#VopxIM zjRkVA#}!wl526m~a%By2ii%C!#vZ(Scs1_Hlfnyo4yr4LNNp7050h>D<|I_gR!lpW z&Qw$C?y;9+CPrfkJF^m3ky25`nTN`zE&cZ_;-ax;thKFyOPF>~_|x1s>ro}=42hQe z1}f;2TW!6fM5my6_-+3FZh>_PjN%cTZE9=a?5&m7fV#tGfWV)Hey6_z5)VbU0P>bC zEUx+H_Tfe*ICoJB^6ej1A>wa~^8aBZ-VFfq5%Gzaygy0;KGOh)f3|Dl+51lUpKM}O zvE_t}!5Dn#5TwpMVnrPk8hB@02zKhc?9d@j#2(T^F$`j8E-P4_otHg%COYwy%fnz% zBIUVvt*Y4qXehRCWpSFsE-=93pluc2Zkedw6Z1rUh`|%C%TRUk^f{`fxr@BS9%hja zb0qKu7i)Ue{sX)?QBg_vD)QlE8>>$$=#a6lFE^<8P*>uD-^$XR@pm%N1sfjUFy%mV z#d%`pilf1^J&d-HoC-#y&5~W^U$xHNBXtpAVnSp zv&H79RegOushO&M9lhjUZN{7i(lDT)ogrBA9oZpe&fU*OnG_BN;LFcWe%eWvJd=?& zsoul$;ZfniqVSJ%nTx&j zqtYoegRDX#*4{X((`-no`*QdZ-w!Wzn2mw?zzN@Xb9&oB_oVr}H+;cn6O-R2utKWeIK2@yonuXfUvHC=oK7OHl*vHqpjA ztIvb4xw_9%JSjEKkSn$x0<%4iB_y&IHQ?pyf}j2qXzjWL@ukGlJE-H?BETPe=VJgb zes%bTj&^39255h!iMd2_IP4pLS--=HzLm;><;xMi=)atrH$sc*9j9Q~ihUAMZLv8f z!Cs)pB^Qqde^;_lGRzyV+^lXe(|OX8m#uj@e7E`ePmjf}2Sk-0yK+pXf7nhs@y2N^UU1mx){VaR^b=2r1gr`My$FE`rS1pmnO1&yxp%V;~xJ{L_qUN)F8 z)X~xk?@!i9;nKg$>x&bmUFWt)W*412d1XWQPaZvW`4SoPC#rLn8N24jvs|{^;H0Ey zssyxEGw77015mY6NA7#C3DcdJK;-GqW}kX5w|2;fJMy`f%a#o~`tG&YJt`{_a2SsV zY%O;*{4=UH$Kigcv!b1L#f|m#96~(&3`u&@@#p zK9#B>xHUVnu2XPg+-#hc%QBku^hKI6%^Ic|*T&)HY|Tw`f%s=thlS3>9B)gwYNM3z z6gICumbI!YT%w@Q>4i`*vc!)$JSV5&uCivxJKgg*#6dPVcNEvP+))@Imb{~suJT2inkfgX1?Tfz=1!wmp|=k_u&WBuht|A6?|iZ%O~x3Y zj*kbyxG_WUR9tC54eE>HAx=d%l;;hed_b9QTDKSQk(cP;vVgVBqelQ2FGpbP2}kKd zrEaDipjbOFAAG1Dq3F`mRnRIB!-^s4gOi^WeNbN)DF-Z(C9F^h_}2Y{wn_cm%?=qH zrFP)p^3a=8g7tnVIDA+h(+9mmlb-#`ICI~DeQn&#n{~m#%`3D$lL;DC{)JelXnkGc ze>G|mZ5rJ$eTyzg&#R_Dktb31>_! zXigI^dr;409(~!hH~)gi;GH1dg+vo07hIF`>hrf`g?P68z2jj#%k<&ELYUqQwi9!6t!tIf^PaeO>;;yx= zdo}t-nye*cxxKOiSAPLdBWH@Eil!#A#~?*|SS?eNno;gC7#c_PYo~ zxkw|GW#2s9O^?Oexk~+IA_d8yPhRVy0eJRDxv-btb2(8Cth1+EN5W=eltb~|ADbN62=G#%h0M(vNy>aW}~r;Lfo; z6XE|GhuzK`(Dh?Aaw!-&;vQbMgkyiS0;&Mx%l$Cl^o-q3ZvO6aJQo4ToqY-*iDl9$ zJIE+qEZz)c2Q@OJZEL~wF$G~)-5OCBaca$O{HbJ_jx`=JW5MeMlmkkedmb+0ezXKmKRf^*p!nz`~f>q4dE=ySKIZy-+6%naY z!&$=}V;ZpTr8rp9t&*BaR)6Tj>f4&hQR}13a{gVcjxk|od!aroMC!qW_$Gl!7xxYT zi?4@3m5b08DBHoUo!zkZD9i12tHpP_ft9Q1oJu+}TsXcNqM_FG#q1HWDC^CZ_fg=g z3g}ZJ6ac!*W@PyEBl+EL4A(C^?mVl&1u?&?zS>lBs_lN|0BJx;cV{mYuqA?j0`E!G z5uTsYHfni_kf28emv3I)1-Zzmp)$n1)`2G(hq*AP-=kYqOt?n~-l+X$6hcJOk6mTl{0WAUU%Oik*9BOx7g!x} z@Lv(ch?z9C0ZZhD#(pzJRXUOn9`KzW!6m_=SOFeFUVHR$Quq@!lXa08-J!FL^V%*Q zg`c>4)<~!7tK}pp&#ZZiwHpnoMb0L@K)bSb;7|`|QC)P&O7sY=ua!PUyL?;;Tk*jx zp@s!4N-b`5dm#W^bSN7YvW+G!69^ALB$8!yXr_A((UFx)niHMwf{riE;LVE58~(Nk zobz^}V9bCZ?ulMxuPir#saHQ2Q50%KO`ql%r;cg(O$ox-`9ed))0`%f0Of!O%89cF ziAAO59^&}*zPEvjk}V!D#2Hvv%s4?L&#JvGmYIr|yD5LB`TP>2si z#Yb19(lGl#)w<1W>E#YgChi;E1D1A3+8Kzx&Ad;%juh+Sma=?8`)@$gIejI#oNi6% z%)^(!*D9-k3**o_em!mZ8(izZEv@H!?5NFuoM%NGyDhIhRcZlwvfjz(d8PulvRWSQ z8ORxoRnP3FBuewC;9{n1Hioq;6#9$>K$CN@(#KUlIs-cW(OH(sOUX=y%5KnuTdYrc zDyk(ZMd;+4%Q9Qq*zf>d*-GmV|HmXCU(k($Lzgay!@w5T^_ zE_O1iqIC%cczvd?*gQ3Mjw}*PCM-hpCf49!q|w>-myRN!NKs;2_z!_bjAN;f zbQ-oc+%$-Kb9>U#r{qK}Vu%eMS3-&6qpT1?_7E+!9n`<|vzZ<;ehgYO1JYq*q|w;| zn4X2U_vui4B8LSGo0;_GTbzf_Mmf1l$F}zAk4@84^Fom%%CPJ#Mu$6{ZgvoP^1h?< z&8=g|;9cJf^lZC8xvcmqu-|ZxHN&$S^i+p4S^+Pfi^yEQG_RI}GhEflU0!)gfab29 z5wE}jR!1Ts(Hq8$rRsb*k6?V`7=FfOkfE0&J$%I$=AbgWYhud#k6+gplXE$7gEo`q zd^l#x!kV zZ(rbs>HRn1&J(j31(Y!!6EQ_IO{hgQ2?Xx;ls!<)iZbw6ufeaRwaPZQiSbRy8>P*LMNq-VfaPc=y74R( zcwdTY9FU^daEDU<-Nrb=$v<;nnL-dBO2uR|fl2`HbjolfLlGPA@rjF^IFF!LAdof4 z+NI~n{HD2bCGYW$LXt^C=2B&0w*v$<_no%t&-gq@*X5QrkV$Yij>IT2`lFse_S&7i z^GvxeI5`N0Fs;O0;1F*}!)!reU*8MeyHZ?MS-Z7yx3jp{>)hI=a`@WZK|nB1BMSDAN+2f{B*O5O`b>+|#m_a0Q+s4_&M9T2g+T`x_A`99B-@{63WiqQK-Xes} zr`r^n>s(Vh(8rcV!)i;mqHDr=Dm4$+f8_RXuG}|vwlKeX{5fLifluWQP1`FR$=&)@ zW*vI(|C4T*kxTGJ7g6Y&#FBVn6Y~%moZWNQ<=91`S#n@5=g`?wt`SAe;(xIy6hB+9 zf0yTQb2iFKz0*GK$*KNf+Vx5B9wG{$I#2UvPxHC?53OioNx%-=5X*ow@;)jPz*}(K zaobYFm~RSBnUvQ|I+5sw5N%D>1B&vsMCnk92eBG;YpZ#Q zv*4Y$u~M@;_=fP1-wB8lwRyX@xlT9|Ir4bZnOL8dj4gMyUUV-5lKX~Q*WHQ-We<@a z?Ye;0mU&jx*gv(pmA|1-(EQS{C=BLmr+VPl8O^g+P?O+usylrTpi6|^>^lmb|4T`} zB7r&5AS}{EB4uTMv{cmh!2P$ikX}`wJ`{(Jv}@!j6e=)V0NdfpT9EkY+MW0J9=^}l zp8TXd3(echgiGRdcm6RU@>t)J*p7DaWjJ*ua!b1jMw~fQ=vH9c}=7dLWeWrE(gl_fI}ccPQ5`mcxK|5 z6j(&CoS4LuF9-3)EAC|aEhvQl?OdTXspA$qZVXy652|KkwxJJe|-B>-P{VhnvKWHQ!gOdc|{frt-MUTAS;Z@}{?!#8Qty&0%`lhnP)u=aT{DaDlk zbnx1p{NqX}A7;RH?vYO(&}UWigtaxGH`1T^fDG=o$=Z<}HI5{L6%Zk!7L7b5^n8x!hdsSIK>XuunJN)ZB(!o;gF zi$T69M?fNb&yb4+pSeO&%mNLqz?ly4mU%M##@wPdvT}$JY0X70pI{r22m@=WfveDQ z@cDipAZ@XC;zG--z(A+%d<_q%A#l7B-h0=ImtOe9cb^15IgLjSh6aZnG%$?;Z~521 zss8p*mJgsozxsRKII^o}JY`9DuV~4MGk3kYl15fIp3d$xYWeuhZMo-sVI>>)=>=R( zoVe%oFGB52>bi`KwQ;)UZ~NKVO5iARu8w$80S&V5MU zWafN6-1$>Mv&^9$F6Ej2wh{w~RPJ11HL2q|JB|o#UB)>eR4azeaBRH(<^JTaX&g!& zBvvL79C?Y+GGENq5gcD?>jGVw^c}^^jW5Nt#+#Ke7IVu&jpACoL;B(96+o6j+p#JJ zY74|7E9x00)KqAo0#OB=Ut6`UmOYlo+5jQ5c68XvIk zPAZIk5u0yRWkLDnZt$~Daq#t|9t_31Ec9A*yIXYQQlKAV+5sA>iH-ZxBfToRum=54 zT>*_mWi~ai98XJwpnIB@WMAQ6O-q~TR}pLXfDIl-OV0+L0f%IuO{~m2gV>Myoq&1Z zQu7XSG-ilEoXK5cWEwIyYh~cR200-_5~QD-{+MHzi$-^Q-@AqU3?$GfB|dfP-W}vk zs49#TqzJ8nB4bWyBRBVmt^+>O-JZKS2Y#P8r3_**`);v~@PNJBsw1+XFw@8gTD115 zsXewn7IINJ0Qp~$n6*rmi;DwB{*!A*(nnopqL?QS6d6>C_j`qKUU3Tlw!kaNWBa+opK}s^0B^M_D6{B8f)ShkYEBl^kdMZ6SD@oxTk=bX1D_L zUNC=6>6<;D;exM@d)k_YUA~y3F!N3fZ7)Ff{uO)4T}%(KxrM$*d#B_0&72nC2-s&f zkQ-&e0@}yzGchS`=01S7x?v^^Bf+O{Sx^;I7{Mu?00w zH|THCc*w*Ajn)Sg;PuYD4hWJcDOoqE_Tit9KmrG`E)lGDY4P) z%>KyppHhPJiYbG~g7#p+onPcsbYYSADul9n051pqV%18P$HSiz+uY!#y#m>olfrSBxv+7hV8f*l0i$8U)lY6)y1gu1)te{2i3@43|1w)n59+WTupHa< z2p4^uqj1#gEKdnO?$6=D;YTVMk9O$5UcFqM@MoNUVGy1y25u*2p*p1y7CY}JDXujE z#V2pV0m$)dOPXIc^;9`YK9Bur& z{pg(qp(5rkeKVj&MpT#0{h4M(F|Vq;mD^fN)|FZCROki(>4W}|)Lt+X+^P@I>h{`r z{iy0oTa9Vjgvgo`HZ|^$jaJzB7sV~HwL8<{{ixjK|MY>hcLU`N-PsTL`$H%lErOBw zQ&%~?o^YFS%{zV6(~0xz%PWOV5=RZ&?_Ib0xuInj_U_$56w3znEB%vo6hqHTk-zYwoWVdt%FKUsV6(vkz{xgM?MyOduS9@|Ex7o9f*yBa7UKyIdu{2-n>0 zG~}g@Fh3)m4Gl<0Moo^z+hkaVl}|~!lw?L4ngsu+g~9&9uNDSJSMC>pIKF}e?C{i; z{sqtzD)=8{`IT%y0xn|ZQs-*Tpuae1e5iC12= zXv9g_V|Qf#4C#nNXi^44GWme?>yXMf0~#r~G1hJm;;$x*+w!PiEg1Q2ALWR(WKd3| z=;|UMB+f*y6FYJfNP${IK(5MqiG)}%3^APp564lZ6~KTy%~EWK8v;SJVUud)pGyM( zroRJlvsr%9BPJh~BTByW{i$f^oo%zf>uJLjcgOXetsQcL%C~O>&4kac%a3MS3K(PrM$h~X9r=u z;=}JjCVrEz*O}y4n-@b$2P^Xq;6p4pu2oRy)uwC}b#P>Wms@yudnv zq#9Nn$@?j_=i?2xEW8NGR2kye1MWynBSK}1`4D`{#=e! zzwIL_;$Hk-0qKOq&qLWL?L{^1xh?6Sr*+#1l)a8KjrvwL7Mi{vtR@fTf;u&3u#V*p z?u=(Fr3`|^HCYW@*TNea%X=jBP9xZhPCcn)@$3AZNu)BZz(-4M@*7WbF6U*Udo^kszMf}@1WxSc4y4a^4ZXT}6!k5ah|&cNJADJ%moINFm; zt&R}|p^0S1oIG%w0bI2*3iTsD)bM{QHg21^HP-(m_VzWs-KSrbh+i~cxxytsl)q-u zwsKD0zb@JFQ_PM;^eri^%naP;t;e~1r%R1u8TG7R`o{o6b^WAe*vWM35C2+~FrJaP zv3W9G4IRD8&db6{>EJ!di>Z+A~o{UT=?D&n`l9-H{IPcc{*M#4q)t6!WSG z5t|C55;CA$bX*n&w%xN*It>k?tMZtmL=~sQXxBMvTV7y zrwtgi_x>XOu4GS-Rc^F#IuV5EV3vKdR1_QVD0Fq0Agccq1dQJE$i4?V?ELE@bo72fVg{g&WyXdOIHx4cOH5r}-ZrG_*?Q3+`wB523dY;ni{p@mNnPCSmypfHgL3 z_4MyJb{M!TY7iEFYbIzCOY22YCoRZUs=Ib4<_RvbJ@I#~feHLWrGq?knXlc0xm&e8 z&a^@3x4T%4JPz^<7Ya?-jdEF)@EQRqLE4bkzQ z6UCwT$kOh^$eSe7ls;-KFiK0I!%6$r;3$sx1o(3mn>Jp3ppmg}me9nenRswkbdeba zS831tZK!iwY`DJuJAOBf_-_~$Se>$7Cuv`L>ITE%)qqc5tTHQLoU~0xEUT8!^O8nm z27nwatQDkZ1dOwrR5uiAG(H<;3{*ow+Ya~_SualcbA419SFxko|_|ul(dezbf-cx9b24NCkRp z14EFzE~M#G0@jX4A-v5;wW8xB5raK_@;ybs+ zw~KI&MG-qdzu~6qb$WKG8jdwAhn&C0u`R1+Kb!l$1fQ_gMS(lOo@09N9$KvO@3apx z&(1fkVl{CN*+88*T@_QJjSPTZ`3XNwVq?D?s9zD!hTnfjPo9bZh^L<;8J9Pb#nKr; zPHaSjIg9=^==b)pr0^{~#CSz@v+z2;L*dQMVs=aub-+bF+pj0Vcsv*W21?gM?HBFF zhu|JG_jk$KEWgl&!3$Mzd}PtzAF*c*(id8A&eQ*Bh>M;>2EOH>2j~$2d)%ntESpGcYx`1o$N{b&yyme&5`N#L%XHOf;uEBmVij$p04&1Yfy=YmyhGz#jSC!2g!w OJ7aXgu=J#3`2PW7V3*|p literal 0 HcmV?d00001 From a10c56c149947223e079bd677af9aba2c97d80a9 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Mon, 3 Mar 2025 10:40:52 +0530 Subject: [PATCH 16/30] Update README.md --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ac046b9..b52331b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # commit-msg +## Screenshot + +Hereโ€™s an image of the application in action: + ![Commit-msg Screenshot](image.png) ## Add `GROK_API_KEY` to System Variables @@ -45,9 +49,7 @@ Below is a sample execution of `commit-msg`: --- -## Screenshot -Hereโ€™s an image of the application in action: From 146b4465674c59cd2d5302e97a81ec1aca119d84 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Wed, 5 Mar 2025 00:38:08 +0530 Subject: [PATCH 17/30] Feat: Add Gemini LLM support for commit messages - Adds Gemini as an alternative LLM for generating commit - messages, configurable via the COMMIT_LLM environment - variable. Also updates dependencies and README. --- README.md | 28 +++--- go.mod | 42 ++++++++- go.sum | 209 +++++++++++++++++++++++++++++++++++++++++++ src/gemini/gemini.go | 63 +++++++++++++ src/main.go | 31 +++++-- 5 files changed, 346 insertions(+), 27 deletions(-) create mode 100644 src/gemini/gemini.go diff --git a/README.md b/README.md index ac046b9..40b446e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,19 @@ # commit-msg +## Screenshot + +Below is a sample execution of `commit-msg`: + ![Commit-msg Screenshot](image.png) -## Add `GROK_API_KEY` to System Variables +## You can Use Gemini or Grok as the LLM to Generate Commit Messages + +### Add `COMMIT_LLM` value are `gemini` or `grok` +### Add `GROK_API_KEY` to System Variables (if use grok) +### Add `GEMINI_API_KEY` to System Variables (if use gemini) -Before running the application, ensure you have set the `GROK_API_KEY` as a system environment variable. +Before running the application, ensure you have set the system environment variables. --- @@ -33,22 +41,6 @@ This will execute `commit-msg` in the current directory: go run src/main.go . ``` ---- - -## Sample Output - -Below is a sample execution of `commit-msg`: - -```bash -# Example output here (replace with actual example) -``` - ---- - -## Screenshot - -Hereโ€™s an image of the application in action: - diff --git a/go.mod b/go.mod index af97a06..e313679 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,44 @@ module github.com/dfanso/commit-msg go 1.23.4 -require github.com/joho/godotenv v1.5.1 // indirect +require ( + github.com/google/generative-ai-go v0.19.0 + github.com/joho/godotenv v1.5.1 +) + +require ( + cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go/ai v0.8.0 // indirect + cloud.google.com/go/auth v0.15.0 // indirect + 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/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/s2a-go v0.1.9 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect + go.opentelemetry.io/otel v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.34.0 // indirect + go.opentelemetry.io/otel/trace v1.34.0 // indirect + golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.35.0 // indirect + golang.org/x/oauth2 v0.26.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + golang.org/x/time v0.10.0 // indirect + google.golang.org/api v0.223.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect + google.golang.org/grpc v1.70.0 // indirect + google.golang.org/protobuf v1.36.5 // indirect +) diff --git a/go.sum b/go.sum index d61b19e..bb22aa5 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,211 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= +cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w= +cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE= +cloud.google.com/go/auth v0.6.0 h1:5x+d6b5zdezZ7gmLWD1m/xNjnaQ2YDhmIz/HH3doy1g= +cloud.google.com/go/auth v0.6.0/go.mod h1:b4acV+jLQDyjwm4OXHYjNvRi4jvGBzHWJRtJcy+2P4g= +cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= +cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= +cloud.google.com/go/auth/oauth2adapt v0.2.2 h1:+TTV8aXpjeChS9M+aTtN/TjdQnzJvmzKFt//oWu7HX4= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= +github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/generative-ai-go v0.19.0 h1:R71szggh8wHMCUlEMsW2A/3T+5LdEIkiaHSYgSpUgdg= +github.com/google/generative-ai-go v0.19.0/go.mod h1:JYolL13VG7j79kM5BtHz4qwONHkeJQzOCkKXnpqtS/E= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= +github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBYGmXdxA= +github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0 h1:A3SayB3rNyt+1S6qpI9mHPkeHTZbD7XILEqWnYZb2l0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= +go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30= +go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= +golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= +golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= +golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.186.0 h1:n2OPp+PPXX0Axh4GuSsL5QL8xQCTb2oDwyzPnQvqUug= +google.golang.org/api v0.186.0/go.mod h1:hvRbBmgoje49RV3xqVXrmP6w93n6ehGgIVPYrGtBFFc= +google.golang.org/api v0.223.0 h1:JUTaWEriXmEy5AhvdMgksGGPEFsYfUKaPEYXd4c3Wvc= +google.golang.org/api v0.223.0/go.mod h1:C+RS7Z+dDwds2b+zoAk5hN/eSfsiCn0UDrYof/M4d2M= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4 h1:MuYw1wJzT+ZkybKfaOXKp5hJiZDn2iHaXRw0mRYdHSc= +google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= +google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4 h1:Di6ANFilr+S60a4S61ZM00vLdw0IrQOSMS2/6mrnOU0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240617180043-68d350f18fd4/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 h1:DMTIbak9GhdaSxEjvVzAeNZvyc03I61duqNbnm3SU0M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= +google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/src/gemini/gemini.go b/src/gemini/gemini.go new file mode 100644 index 0000000..350142a --- /dev/null +++ b/src/gemini/gemini.go @@ -0,0 +1,63 @@ +package gemini + +import ( + "context" + "fmt" + + "github.com/google/generative-ai-go/genai" + "google.golang.org/api/option" + + "github.com/dfanso/commit-msg/src/types" +) + +func GenerateCommitMessage(config *types.Config, changes string, apiKey string) (string, error) { + // Prepare request to Gemini API + prompt := fmt.Sprintf(` +I need a concise git commit message based on the following changes from my Git repository. +Please generate a commit message that: +1. Starts with a verb in the present tense (e.g., "Add", "Fix", "Update", "Feat", "Refactor", etc.) +2. Is clear and descriptive +3. Focuses on the "what" and "why" of the changes +4. Is no longer than 50-72 characters for the first line +5. Can include a more detailed description after a blank line if needed +6. Dont say any other stuff only include the commit msg + +here is a sample commit msgs: +'Feat: Add Gemini LLM support for commit messages + +- Adds Gemini as an alternative LLM for generating commit +- messages, configurable via the COMMIT_LLM environment +- variable. Also updates dependencies and README.' +Here are the changes: + +%s +`, changes) + + // Create context and client + ctx := context.Background() + client, err := genai.NewClient(ctx, option.WithAPIKey(apiKey)) + if err != nil { + return "", err + } + defer client.Close() + + // Create a GenerativeModel with appropriate settings + model := client.GenerativeModel("gemini-2.0-flash") + model.SetTemperature(0.2) // Lower temperature for more focused responses + + // Generate content using the prompt + resp, err := model.GenerateContent(ctx, genai.Text(prompt)) + if err != nil { + return "", err + } + + // Check if we got a valid response + if len(resp.Candidates) == 0 || len(resp.Candidates[0].Content.Parts) == 0 { + return "", fmt.Errorf("no response generated") + } + + // Extract the commit message from the response + commitMsg := fmt.Sprintf("%v", resp.Candidates[0].Content.Parts[0]) + + return commitMsg, nil +} diff --git a/src/main.go b/src/main.go index 437b556..eb8e1d2 100644 --- a/src/main.go +++ b/src/main.go @@ -11,9 +11,9 @@ import ( "time" "github.com/dfanso/commit-msg/src/config" + "github.com/dfanso/commit-msg/src/gemini" "github.com/dfanso/commit-msg/src/grok" "github.com/dfanso/commit-msg/src/types" - "github.com/joho/godotenv" ) // Normalize path to handle both forward and backslashes @@ -36,14 +36,24 @@ func main() { flag.Parse() // Load environment variables from .env file - if err := godotenv.Load(); err != nil { - log.Printf("Warning: Error loading .env file: %v", err) - } + // if err := godotenv.Load(); err != nil { + // log.Printf("Warning: Error loading .env file: %v", err) + // } // Get API key from environment variables - apiKey := os.Getenv("GROK_API_KEY") - if apiKey == "" { - log.Fatalf("GROK_API_KEY not found in environment variables") + var apiKey string + if os.Getenv("COMMIT_LLM") == "google" { + apiKey = os.Getenv("GOOGLE_API_KEY") + if apiKey == "" { + log.Fatalf("GOOGLE_API_KEY is not set") + } + } else if os.Getenv("COMMIT_LLM") == "grok" { + apiKey = os.Getenv("GROK_API_KEY") + if apiKey == "" { + log.Fatalf("GROK_API_KEY is not set") + } + } else { + log.Fatalf("Invalid COMMIT_LLM value: %s", os.Getenv("COMMIT_LLM")) } // Check for "." argument which means use current directory @@ -189,7 +199,12 @@ func main() { } // Pass API key to GenerateCommitMessage - commitMsg, err := grok.GenerateCommitMessage(cfg, changes, apiKey) + var commitMsg string + if os.Getenv("COMMIT_LLM") == "google" { + commitMsg, err = gemini.GenerateCommitMessage(cfg, changes, apiKey) + } else { + commitMsg, err = grok.GenerateCommitMessage(cfg, changes, apiKey) + } if err != nil { log.Fatalf("Failed to generate commit message: %v", err) } From a7ab61e3ce9e36ff2a7255fa1f06ba7443423832 Mon Sep 17 00:00:00 2001 From: Leo Gavin Date: Wed, 5 Mar 2025 00:40:56 +0530 Subject: [PATCH 18/30] Refactor: Move commit prompt to types package - Moves the commit message prompt to a `types.CommitPrompt` variable - for better organization and maintainability. --- src/gemini/gemini.go | 21 +-------------------- src/grok/grok.go | 18 ++---------------- src/types/promt.go | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 36 deletions(-) create mode 100644 src/types/promt.go diff --git a/src/gemini/gemini.go b/src/gemini/gemini.go index 350142a..e3cdf0e 100644 --- a/src/gemini/gemini.go +++ b/src/gemini/gemini.go @@ -12,26 +12,7 @@ import ( func GenerateCommitMessage(config *types.Config, changes string, apiKey string) (string, error) { // Prepare request to Gemini API - prompt := fmt.Sprintf(` -I need a concise git commit message based on the following changes from my Git repository. -Please generate a commit message that: -1. Starts with a verb in the present tense (e.g., "Add", "Fix", "Update", "Feat", "Refactor", etc.) -2. Is clear and descriptive -3. Focuses on the "what" and "why" of the changes -4. Is no longer than 50-72 characters for the first line -5. Can include a more detailed description after a blank line if needed -6. Dont say any other stuff only include the commit msg - -here is a sample commit msgs: -'Feat: Add Gemini LLM support for commit messages - -- Adds Gemini as an alternative LLM for generating commit -- messages, configurable via the COMMIT_LLM environment -- variable. Also updates dependencies and README.' -Here are the changes: - -%s -`, changes) + prompt := fmt.Sprintf("%s\n\n%s", types.CommitPrompt, changes) // Create context and client ctx := context.Background() diff --git a/src/grok/grok.go b/src/grok/grok.go index aef8525..665f76c 100644 --- a/src/grok/grok.go +++ b/src/grok/grok.go @@ -14,24 +14,10 @@ import ( func GenerateCommitMessage(config *types.Config, changes string, apiKey string) (string, error) { // Prepare request to X.AI (Grok) API - prompt := fmt.Sprintf(` -I need a concise git commit message based on the following changes from my Git repository. -Please generate a commit message that: -1. Starts with a verb in the present tense (e.g., "Add", "Fix", "Update", "Feat", "Refactor", etc.) -2. Is clear and descriptive -3. Focuses on the "what" and "why" of the changes -4. Is no longer than 50-72 characters for the first line -5. Can include a more detailed description after a blank line if needed -6. Dont say any other stuff only include the commit msg - -here is a sample commit msgs: -'feat: improve performance with lazy load implementation for images' -Here are the changes: - -%s -`, changes) + prompt := fmt.Sprintf("%s\n\n%s", types.CommitPrompt, changes) request := types.GrokRequest{ + Messages: []types.Message{ { Role: "user", diff --git a/src/types/promt.go b/src/types/promt.go new file mode 100644 index 0000000..5b9a2f5 --- /dev/null +++ b/src/types/promt.go @@ -0,0 +1,19 @@ +package types + +var CommitPrompt = `I need a concise git commit message based on the following changes from my Git repository. +Please generate a commit message that: +1. Starts with a verb in the present tense (e.g., "Add", "Fix", "Update", "Feat", "Refactor", etc.) +2. Is clear and descriptive +3. Focuses on the "what" and "why" of the changes +4. Is no longer than 50-72 characters for the first line +5. Can include a more detailed description after a blank line if needed +6. Dont say any other stuff only include the commit msg + +here is a sample commit msgs: +'Feat: Add Gemini LLM support for commit messages + +- Adds Gemini as an alternative LLM for generating commit +- messages, configurable via the COMMIT_LLM environment +- variable. Also updates dependencies and README.' +Here are the changes: +` From d8166784a3101e635af658557acb35ed5c33b768 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Wed, 5 Mar 2025 00:43:01 +0530 Subject: [PATCH 19/30] Update: Clarify LLM setup instructions in README --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 40b446e..cbc3635 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,17 @@ Below is a sample execution of `commit-msg`: ![Commit-msg Screenshot](image.png) +Before running the application, ensure you have set the system environment variables. + ## You can Use Gemini or Grok as the LLM to Generate Commit Messages ### Add `COMMIT_LLM` value are `gemini` or `grok` + ### Add `GROK_API_KEY` to System Variables (if use grok) + ### Add `GEMINI_API_KEY` to System Variables (if use gemini) -Before running the application, ensure you have set the system environment variables. + --- From c0428f02146da8d7a965398d3b2d2a59e84550fc Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Wed, 5 Mar 2025 00:48:23 +0530 Subject: [PATCH 20/30] Docs: Update README with project description --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cbc3635..8eb6b0d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # commit-msg +`commit-msg` is a command-line tool that generates commit messages using LLM. It is designed to help developers create clear and concise commit messages for their Git repositories. - btw i accept this from autoocomplete + ## Screenshot Below is a sample execution of `commit-msg`: From 0f966b2e73a997f7d59832ba34f584d6c91aad0c Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Wed, 5 Mar 2025 00:49:22 +0530 Subject: [PATCH 21/30] Docs: Update README with download instructions --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8eb6b0d..ffa9ff5 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ - # commit-msg -`commit-msg` is a command-line tool that generates commit messages using LLM. It is designed to help developers create clear and concise commit messages for their Git repositories. - btw i accept this from autoocomplete +`commit-msg` is a command-line tool that generates commit messages using LLM. It is designed to help developers create clear and concise commit messages for their Git repositories. - btw i accept this from auto-complete ## Screenshot @@ -19,8 +18,6 @@ Before running the application, ensure you have set the system environment varia ### Add `GEMINI_API_KEY` to System Variables (if use gemini) - - --- ## Setup @@ -47,6 +44,12 @@ This will execute `commit-msg` in the current directory: go run src/main.go . ``` +--- + +## Download + +To download the latest release of `commit-msg`, visit the [GitHub Releases](https://github.com/dfanso/commit-msg/releases) page and follow the installation instructions for your platform. + From 7d8aa34a281883810fa59c9a94f7e43a23446c3a Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Wed, 5 Mar 2025 00:52:35 +0530 Subject: [PATCH 22/30] Docs: Update README with setup and download instructions --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index ffa9ff5..ccdfc89 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,17 @@ Before running the application, ensure you have set the system environment varia ## Setup + +### Download the latest release and set the in the path variables then you can use `commit .` in any git repo + To set up `commit-msg`, run the following command: ```bash go run src/main.go --setup --path F:/Git/commit-msg --name commit-msg ``` + + --- ## Usage From 5a6d5b920f6ded37071b217262157274c651fd22 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Wed, 5 Mar 2025 21:58:02 +0530 Subject: [PATCH 23/30] Refactor: Simplify main.go and update Grok API endpoint - Removes unused imports and simplifies main function - Updates Grok API endpoint to a hardcoded URL --- src/grok/grok.go | 2 +- src/main.go | 161 +++++------------------------------------------ 2 files changed, 15 insertions(+), 148 deletions(-) diff --git a/src/grok/grok.go b/src/grok/grok.go index 665f76c..917ccab 100644 --- a/src/grok/grok.go +++ b/src/grok/grok.go @@ -35,7 +35,7 @@ func GenerateCommitMessage(config *types.Config, changes string, apiKey string) } // Create HTTP request - req, err := http.NewRequest("POST", config.GrokAPI, bytes.NewBuffer(requestBody)) + req, err := http.NewRequest("POST", "https://api.x.ai/v1/chat/completions", bytes.NewBuffer(requestBody)) if err != nil { return "", err } diff --git a/src/main.go b/src/main.go index eb8e1d2..7504c75 100644 --- a/src/main.go +++ b/src/main.go @@ -1,16 +1,13 @@ package main import ( - "flag" "fmt" "log" "os" "os/exec" "path/filepath" "strings" - "time" - "github.com/dfanso/commit-msg/src/config" "github.com/dfanso/commit-msg/src/gemini" "github.com/dfanso/commit-msg/src/grok" "github.com/dfanso/commit-msg/src/types" @@ -27,19 +24,6 @@ func normalizePath(path string) string { // Main function func main() { - // Define command line flags - configFile := flag.String("config", "commit-helper.json", "Path to config file") - setupMode := flag.Bool("setup", false, "Setup configuration") - path := flag.String("path", "", "Path to monitor (for setup)") - repoName := flag.String("name", "", "Repository name (for setup)") - apiEndpoint := flag.String("api-endpoint", "https://api.x.ai/v1/chat/completions", "X.AI API endpoint (for setup)") - flag.Parse() - - // Load environment variables from .env file - // if err := godotenv.Load(); err != nil { - // log.Printf("Warning: Error loading .env file: %v", err) - // } - // Get API key from environment variables var apiKey string if os.Getenv("COMMIT_LLM") == "google" { @@ -56,135 +40,25 @@ func main() { log.Fatalf("Invalid COMMIT_LLM value: %s", os.Getenv("COMMIT_LLM")) } - // Check for "." argument which means use current directory - if len(flag.Args()) > 0 && flag.Args()[0] == "." { - // Get current directory - currentDir, err := os.Getwd() - if err != nil { - log.Fatalf("Failed to get current directory: %v", err) - } - - // Override path with current directory - *path = currentDir - - // Check if current directory is a git repository - if !isGitRepository(currentDir) { - log.Fatalf("Current directory is not a Git repository: %s", currentDir) - } - } - - // If in setup mode, create a new configuration - if *setupMode { - if *path == "" || *repoName == "" { - log.Fatalf("Both --path and --name are required in setup mode") - } - - // Normalize the path - normalizedPath := normalizePath(*path) - - // Create new config with default endpoint - newConfig := &types.Config{ - GrokAPI: *apiEndpoint, - Repos: make(map[string]types.RepoConfig), - } - - newConfig.Repos[*repoName] = types.RepoConfig{ - Path: normalizedPath, - LastRun: time.Now().Format(time.RFC3339), - } - - if err := config.SaveConfig(*configFile, newConfig); err != nil { - log.Fatalf("Failed to save configuration: %v", err) - } - - fmt.Printf("Repository '%s' configured successfully.\n", *repoName) - return - } - - // Initialize or load configuration - cfg, err := config.LoadConfig(*configFile) + // Get current directory + currentDir, err := os.Getwd() if err != nil { - // If config doesn't exist yet, create a new one - cfg = &types.Config{ - GrokAPI: *apiEndpoint, - Repos: make(map[string]types.RepoConfig), - } - } - - // If no path provided, list available repositories - if *path == "" { - fmt.Println("Available repositories:") - if len(cfg.Repos) == 0 { - fmt.Println("No repositories configured. Use --setup to add a repository.") - return - } - for name, repo := range cfg.Repos { - fmt.Printf("- %s: %s (Last run: %s)\n", name, repo.Path, repo.LastRun) - } - return + log.Fatalf("Failed to get current directory: %v", err) } - // Validate configuration - if cfg.GrokAPI == "" { - cfg.GrokAPI = *apiEndpoint // Use default endpoint if not set - if err := config.SaveConfig(*configFile, cfg); err != nil { - log.Printf("Warning: Failed to update API endpoint in config: %v", err) - } - } - - // Normalize the input path - repoPath := normalizePath(*path) - var selectedRepo string - var repoConfig types.RepoConfig - - // Find the repository configuration by path - for name, repo := range cfg.Repos { - // Normalize the stored path for comparison - storedPath := normalizePath(repo.Path) - if storedPath == repoPath { - selectedRepo = name - repoConfig = repo - break - } + // Check if current directory is a git repository + if !isGitRepository(currentDir) { + log.Fatalf("Current directory is not a Git repository: %s", currentDir) } - // Auto-setup if repository not found in config - if selectedRepo == "" { - // Generate a repository name from the path - dirName := filepath.Base(repoPath) - if dirName == "" || dirName == "." || dirName == "/" || dirName == "\\" { - dirName = "auto-repo" - } - - // Make sure the name is unique - baseName := dirName - counter := 1 - for { - if _, exists := cfg.Repos[dirName]; !exists { - break - } - dirName = fmt.Sprintf("%s-%d", baseName, counter) - counter++ - } - - // Add the repository to the configuration - selectedRepo = dirName - repoConfig = types.RepoConfig{ - Path: repoPath, - LastRun: time.Now().Format(time.RFC3339), - } - cfg.Repos[selectedRepo] = repoConfig - - if err := config.SaveConfig(*configFile, cfg); err != nil { - log.Printf("Warning: Failed to save auto-configured repository: %v", err) - } else { - fmt.Printf("Repository '%s' auto-configured successfully.\n", selectedRepo) - } + // Create a minimal config for the API + config := &types.Config{ + GrokAPI: "https://api.x.ai/v1/chat/completions", } - // Ensure the path is a Git repository - if !isGitRepository(repoConfig.Path) { - log.Fatalf("The specified path is not a Git repository: %s", repoConfig.Path) + // Create a repo config for the current directory + repoConfig := types.RepoConfig{ + Path: currentDir, } // Get the changes @@ -201,9 +75,9 @@ func main() { // Pass API key to GenerateCommitMessage var commitMsg string if os.Getenv("COMMIT_LLM") == "google" { - commitMsg, err = gemini.GenerateCommitMessage(cfg, changes, apiKey) + commitMsg, err = gemini.GenerateCommitMessage(config, changes, apiKey) } else { - commitMsg, err = grok.GenerateCommitMessage(cfg, changes, apiKey) + commitMsg, err = grok.GenerateCommitMessage(config, changes, apiKey) } if err != nil { log.Fatalf("Failed to generate commit message: %v", err) @@ -211,13 +85,6 @@ func main() { // Display the commit message fmt.Println(commitMsg) - - // Update the last run time - repoConfig.LastRun = time.Now().Format(time.RFC3339) - cfg.Repos[selectedRepo] = repoConfig - if err := config.SaveConfig(*configFile, cfg); err != nil { - log.Printf("Warning: Failed to update last run time: %v", err) - } } // Check if directory is a git repository From dae3092f3975c70db2f2f590905165c843db5c5a Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Wed, 5 Mar 2025 22:01:06 +0530 Subject: [PATCH 24/30] Remove: Delete config.go as configuration handling is refactored - The config.go file, which previously handled loading and saving - configuration, has been removed. This change is part of a larger - refactoring effort to simplify and update the configuration system. --- src/config/config.go | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 src/config/config.go diff --git a/src/config/config.go b/src/config/config.go deleted file mode 100644 index b535aab..0000000 --- a/src/config/config.go +++ /dev/null @@ -1,33 +0,0 @@ -package config - -import ( - "encoding/json" - "os" - - "github.com/dfanso/commit-msg/src/types" -) - -// Load configuration from file -func LoadConfig(configFile string) (*types.Config, error) { - data, err := os.ReadFile(configFile) - if err != nil { - return nil, err - } - - var config types.Config - if err := json.Unmarshal(data, &config); err != nil { - return nil, err - } - - return &config, nil -} - -// Save configuration to file -func SaveConfig(configFile string, config *types.Config) error { - data, err := json.MarshalIndent(config, "", " ") - if err != nil { - return err - } - - return os.WriteFile(configFile, data, 0644) -} From e34d0fd8e7b2fedc0e23246319b36f323c6f1064 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Wed, 5 Mar 2025 22:02:36 +0530 Subject: [PATCH 25/30] Update: Clarify README usage with dynamic path - Modifies README to instruct users to use their own git repo path - Simplifies the example command to use current directory --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ccdfc89..ce4c2b0 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,10 @@ go run src/main.go --setup --path F:/Git/commit-msg --name commit-msg ## Usage -To run `commit-msg`, use: +To run `commit-msg`, use: (Use the you git repo path) ```bash -go run src/main.go --path F:/Git/commit-msg +go run src/main.go . ``` This will execute `commit-msg` in the current directory: From 4ffe2f889e1412df5a959607ecf20c4bb8a3f20e Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Wed, 5 Mar 2025 22:07:07 +0530 Subject: [PATCH 26/30] Update: Simplify README setup command for dynamic paths - Modifies the README to use a simpler command for setting up - `commit-msg`, allowing dynamic path usage for easier setup. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ce4c2b0..0af19ef 100644 --- a/README.md +++ b/README.md @@ -25,10 +25,10 @@ Before running the application, ensure you have set the system environment varia ### Download the latest release and set the in the path variables then you can use `commit .` in any git repo -To set up `commit-msg`, run the following command: +To set up `commit-msg`, run the following command: (Use the you git repo path) ```bash -go run src/main.go --setup --path F:/Git/commit-msg --name commit-msg +go run src/main.go . ``` From 50c7f6e54d504d067916843363dc709c9263db25 Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Tue, 11 Mar 2025 21:09:06 +0530 Subject: [PATCH 27/30] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0af19ef..9b4daef 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Below is a sample execution of `commit-msg`: ![Commit-msg Screenshot](image.png) -Before running the application, ensure you have set the system environment variables. +Before running the application, ensure you have set the system environment variables. and add commit.exe to path variables (same for linux macOS) ## You can Use Gemini or Grok as the LLM to Generate Commit Messages From 7010262486f8cc5d78839f6b6c805186b37a814c Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Sat, 19 Apr 2025 17:34:18 +0530 Subject: [PATCH 28/30] Update: Switch Grok model to grok-3-mini-fast-beta - Updates the AI model in GenerateCommitMessage to - use the newer, potentially faster beta version - for improved commit message generation. --- src/grok/grok.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grok/grok.go b/src/grok/grok.go index 917ccab..09e5e79 100644 --- a/src/grok/grok.go +++ b/src/grok/grok.go @@ -24,7 +24,7 @@ func GenerateCommitMessage(config *types.Config, changes string, apiKey string) Content: prompt, }, }, - Model: "grok-2-latest", + Model: "grok-3-mini-fast-beta", Stream: false, Temperature: 0, } From 1d2132e89f577464f343e6c864324cadc5da800b Mon Sep 17 00:00:00 2001 From: Leo Gavin <71537398+DFanso@users.noreply.github.com> Date: Thu, 2 Oct 2025 09:12:48 +0530 Subject: [PATCH 29/30] Update README.md to include Hacktoberfest 2025 participation and enhance installation instructions; add HACKTOBERFEST_SETUP.md to .gitignore --- .github/ISSUE_TEMPLATE/bug_report.md | 37 +++++ .github/ISSUE_TEMPLATE/feature_request.md | 29 ++++ .github/PULL_REQUEST_TEMPLATE.md | 61 +++++++ .gitignore | 1 + CODE_OF_CONDUCT.md | 54 +++++++ CONTRIBUTING.md | 184 ++++++++++++++++++++++ README.md | 181 +++++++++++++++++++-- 7 files changed, 531 insertions(+), 16 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..d9bc9cf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,37 @@ +--- +name: Bug Report +about: Create a report to help us improve +title: '[BUG] ' +labels: bug +assignees: '' +--- + +## Bug Description +A clear and concise description of what the bug is. + +## Steps to Reproduce +1. Set environment variable '...' +2. Run command '....' +3. Observe error '....' + +## Expected Behavior +A clear and concise description of what you expected to happen. + +## Actual Behavior +What actually happened. + +## Environment +- OS: [e.g., Windows 11, macOS 14, Ubuntu 22.04] +- Go Version: [e.g., 1.23.4] +- commit-msg Version: [e.g., latest from main] +- LLM Provider: [Gemini or Grok] + +## Additional Context +Add any other context about the problem here, such as: +- Error messages +- Screenshots +- Configuration files + +## Possible Solution +If you have suggestions on how to fix the bug, please share them here. + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..2a98602 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,29 @@ +--- +name: Feature Request +about: Suggest an idea for this project +title: '[FEATURE] ' +labels: enhancement +assignees: '' +--- + +## Feature Description +A clear and concise description of the feature you'd like to see. + +## Problem It Solves +Describe the problem this feature would solve. Ex. I'm always frustrated when [...] + +## Proposed Solution +A clear and concise description of what you want to happen. + +## Alternative Solutions +Describe any alternative solutions or features you've considered. + +## Use Case +Describe how this feature would be used and who would benefit from it. + +## Additional Context +Add any other context, mockups, or screenshots about the feature request here. + +## Implementation Ideas +If you have thoughts on how this could be implemented, please share them here. + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a2b0856 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,61 @@ +## Description + + +## Type of Change + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update +- [ ] Code refactoring +- [ ] Performance improvement +- [ ] Other (please describe): + +## Related Issue + +Fixes #(issue number) + +## Changes Made + + +- +- +- + +## Testing + + +- [ ] Tested with Gemini API +- [ ] Tested with Grok API +- [ ] Tested on Windows +- [ ] Tested on Linux +- [ ] Tested on macOS +- [ ] Added/updated tests (if applicable) + +## Checklist + + +- [ ] My code follows the project's code style +- [ ] I have performed a self-review of my code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings or errors +- [ ] I have tested this in a real Git repository +- [ ] I have read the [CONTRIBUTING.md](../CONTRIBUTING.md) guidelines + +## Screenshots (if applicable) + + +## Additional Notes + + +--- + +## For Hacktoberfest Participants + + +- [ ] This PR is submitted as part of Hacktoberfest 2025 + +Thank you for your contribution! ๐ŸŽ‰ + diff --git a/.gitignore b/.gitignore index 9fc3390..2020203 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ go.work .env commit-helper.json +HACKTOBERFEST_SETUP.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..dc1d744 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,54 @@ +# Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Hacktoberfest Values + +This project participates in Hacktoberfest and adheres to its core values: + +* **Everyone is welcome** - We welcome participants from all backgrounds and skill levels +* **Quantity is fun, quality is key** - We prioritize meaningful contributions over volume +* **Short-term action, long-term impact** - We respect the time and effort of all contributors + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all project spaces, and it also applies when an individual is representing the project or its community in public spaces. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team through GitHub issues. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..514ee36 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,184 @@ +# Contributing to commit-msg + +Thank you for your interest in contributing to `commit-msg`! We welcome contributions from developers of all skill levels. ๐ŸŽ‰ + +## ๐ŸŽƒ Hacktoberfest + +This project is participating in [Hacktoberfest](https://hacktoberfest.com)! We welcome quality contributions throughout October and beyond. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [How Can I Contribute?](#how-can-i-contribute) +- [Getting Started](#getting-started) +- [Development Setup](#development-setup) +- [Pull Request Process](#pull-request-process) +- [Coding Standards](#coding-standards) +- [Issue Labels](#issue-labels) + +## Code of Conduct + +This project adheres to the Hacktoberfest values: +- **Everyone is welcome** - We value diversity and inclusivity +- **Quantity is fun, quality is key** - We prioritize meaningful contributions +- **Short-term action, long-term impact** - Your contributions help build the future + +Please be respectful and constructive in all interactions. + +## How Can I Contribute? + +### ๐Ÿ› Reporting Bugs + +Before creating a bug report: +- Check the [existing issues](https://github.com/dfanso/commit-msg/issues) to avoid duplicates +- Collect information about the bug: + - OS and version + - Go version + - Steps to reproduce + - Expected vs actual behavior + +### ๐Ÿ’ก Suggesting Enhancements + +Enhancement suggestions are welcome! Please: +- Use a clear and descriptive title +- Provide a detailed description of the proposed feature +- Explain why this enhancement would be useful + +### ๐Ÿ”ง Good First Issues + +Look for issues labeled `good first issue` or `help wanted` - these are great for newcomers! + +### ๐Ÿ“ Documentation + +Improving documentation is always appreciated: +- Fix typos or unclear instructions +- Add examples +- Improve README clarity +- Translate documentation + +## Getting Started + +1. **Fork the repository** on GitHub +2. **Clone your fork** locally: + ```bash + git clone https://github.com/YOUR-USERNAME/commit-msg.git + cd commit-msg + ``` +3. **Create a branch** for your changes: + ```bash + git checkout -b feature/your-feature-name + ``` + +## Development Setup + +### Prerequisites + +- Go 1.23.4 or higher +- Git +- API key for either: + - Google Gemini (`GEMINI_API_KEY`) + - Grok (`GROK_API_KEY`) + +### Environment Setup + +1. Set up your environment variables: + ```bash + export COMMIT_LLM=gemini # or "grok" + export GEMINI_API_KEY=your-api-key-here + # OR + export GROK_API_KEY=your-api-key-here + ``` + +2. Install dependencies: + ```bash + go mod download + ``` + +3. Run the application: + ```bash + go run src/main.go . + ``` + +4. Build the executable: + ```bash + go build -o commit.exe src/main.go + ``` + +### Testing Your Changes + +Before submitting a PR: +1. Test the application in a Git repository +2. Verify both LLM providers work (if applicable) +3. Check for any errors or warnings +4. Test on your target platform + +## Pull Request Process + +1. **Update documentation** if needed (README.md, code comments) +2. **Follow the coding standards** (see below) +3. **Write clear commit messages** (ironic for this project!) +4. **Fill out the PR template** completely +5. **Link related issues** using keywords like "Fixes #123" +6. **Wait for review** - maintainers will review within 1-2 days + +### PR Title Format + +Use conventional commit format: +- `feat:` - New feature +- `fix:` - Bug fix +- `docs:` - Documentation changes +- `refactor:` - Code refactoring +- `test:` - Adding tests +- `chore:` - Maintenance tasks + +Example: `feat: add support for Claude API` + +## Coding Standards + +### Go Code Style + +- Follow [Effective Go](https://golang.org/doc/effective_go.html) guidelines +- Use `gofmt` to format your code +- Add comments for exported functions +- Keep functions small and focused +- Handle errors appropriately + +### Code Example + +```go +// GenerateMessage creates a commit message from git changes +func GenerateMessage(changes string, apiKey string) (string, error) { + if changes == "" { + return "", fmt.Errorf("no changes provided") + } + + // Implementation here + return message, nil +} +``` + +## Issue Labels + +- `good first issue` - Good for newcomers +- `help wanted` - Extra attention needed +- `bug` - Something isn't working +- `enhancement` - New feature request +- `documentation` - Documentation improvements +- `hacktoberfest` - Eligible for Hacktoberfest +- `hacktoberfest-accepted` - Approved for Hacktoberfest + +## Questions? + +Feel free to: +- Open an issue with the `question` label +- Reach out to the maintainers +- Check existing issues and PRs for similar questions + +## License + +By contributing, you agree that your contributions will be licensed under the same license as the project. + +--- + +Thank you for contributing to commit-msg! ๐Ÿš€ + diff --git a/README.md b/README.md index 9b4daef..e801ff3 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ # commit-msg -`commit-msg` is a command-line tool that generates commit messages using LLM. It is designed to help developers create clear and concise commit messages for their Git repositories. - btw i accept this from auto-complete +[![Hacktoberfest](https://img.shields.io/badge/Hacktoberfest-2025-orange.svg)](https://hacktoberfest.com/) +[![Go Version](https://img.shields.io/badge/Go-1.23.4-blue.svg)](https://golang.org/) +[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) + +`commit-msg` is a command-line tool that generates commit messages using LLM (Large Language Models). It is designed to help developers create clear and concise commit messages for their Git repositories automatically by analyzing your staged changes. ## Screenshot @@ -10,50 +14,195 @@ Below is a sample execution of `commit-msg`: Before running the application, ensure you have set the system environment variables. and add commit.exe to path variables (same for linux macOS) -## You can Use Gemini or Grok as the LLM to Generate Commit Messages +## ๐ŸŽƒ Hacktoberfest 2025 + +This project is participating in **Hacktoberfest 2025**! We welcome contributions from developers of all skill levels. + +Looking to contribute? Check out: +- [Good First Issues](https://github.com/dfanso/commit-msg/labels/good%20first%20issue) +- [Help Wanted Issues](https://github.com/dfanso/commit-msg/labels/help%20wanted) +- [Contributing Guidelines](CONTRIBUTING.md) + +## Features -### Add `COMMIT_LLM` value are `gemini` or `grok` +โœจ **AI-Powered Commit Messages** - Automatically generate meaningful commit messages +๐Ÿ”„ **Multiple LLM Support** - Choose between Google Gemini or Grok +๐Ÿ“ **Context-Aware** - Analyzes staged and unstaged changes +๐Ÿš€ **Easy to Use** - Simple CLI interface +โšก **Fast** - Quick generation of commit messages -### Add `GROK_API_KEY` to System Variables (if use grok) +## Supported LLM Providers -### Add `GEMINI_API_KEY` to System Variables (if use gemini) +You can use either **Google Gemini** or **Grok** as the LLM to generate commit messages: + +### Environment Variables + +| Variable | Values | Description | +|----------|--------|-------------| +| `COMMIT_LLM` | `gemini` or `grok` | Choose your LLM provider | +| `GEMINI_API_KEY` | Your API key | Required if using Gemini | +| `GROK_API_KEY` | Your API key | Required if using Grok | --- -## Setup +## ๐Ÿ“ฆ Installation + +### Option 1: Download Pre-built Binary (Recommended) +1. Download the latest release from the [GitHub Releases](https://github.com/dfanso/commit-msg/releases) page +2. Extract the executable to a directory +3. Add the directory to your system PATH: -### Download the latest release and set the in the path variables then you can use `commit .` in any git repo + **Windows:** + ```cmd + setx PATH "%PATH%;C:\path\to\commit-msg" + ``` -To set up `commit-msg`, run the following command: (Use the you git repo path) + **Linux/macOS:** + ```bash + export PATH=$PATH:/path/to/commit-msg + echo 'export PATH=$PATH:/path/to/commit-msg' >> ~/.bashrc # or ~/.zshrc + ``` + +4. Set up environment variables: + + **Windows:** + ```cmd + setx COMMIT_LLM "gemini" + setx GEMINI_API_KEY "your-api-key-here" + ``` + + **Linux/macOS:** + ```bash + export COMMIT_LLM=gemini + export GEMINI_API_KEY=your-api-key-here + # Add to ~/.bashrc or ~/.zshrc to persist + ``` + +### Option 2: Build from Source + +Requirements: Go 1.23.4 or higher ```bash -go run src/main.go . -``` +# Clone the repository +git clone https://github.com/dfanso/commit-msg.git +cd commit-msg + +# Install dependencies +go mod download +# Build the executable +go build -o commit src/main.go +# (Optional) Install to GOPATH +go install +``` --- -## Usage +## ๐Ÿš€ Usage + +### Basic Usage -To run `commit-msg`, use: (Use the you git repo path) +Navigate to any Git repository and run: ```bash -go run src/main.go . +commit . ``` -This will execute `commit-msg` in the current directory: +Or if running from source: ```bash go run src/main.go . ``` +### Example Workflow + +```bash +# Make changes to your code +echo "console.log('Hello World')" > app.js + +# Stage your changes +git add . + +# Generate commit message +commit . + +# Output: "feat: add hello world console log to app.js" +``` + +### Use Cases + +- ๐Ÿ“ Generate commit messages for staged changes +- ๐Ÿ” Analyze both staged and unstaged changes +- ๐Ÿ“Š Get context from recent commits +- โœ… Create conventional commit messages + +--- + +## ๐Ÿ”ง Configuration + +### Getting API Keys + +**Google Gemini:** +1. Visit [Google AI Studio](https://makersuite.google.com/app/apikey) +2. Create a new API key +3. Set the `GEMINI_API_KEY` environment variable + +**Grok (X.AI):** +1. Visit [X.AI Console](https://console.x.ai/) +2. Generate an API key +3. Set the `GROK_API_KEY` environment variable + +--- + +## ๐Ÿค Contributing + +We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for details. + +### Quick Start for Contributors + +1. Fork the repository +2. Create a feature branch: `git checkout -b feature/amazing-feature` +3. Make your changes +4. Commit your changes: `git commit -m 'feat: add amazing feature'` +5. Push to the branch: `git push origin feature/amazing-feature` +6. Open a Pull Request + +### Areas Where We Need Help + +- ๐Ÿ› Bug fixes +- โœจ New LLM provider integrations (OpenAI, Claude, etc.) +- ๐Ÿ“š Documentation improvements +- ๐Ÿงช Test coverage +- ๐ŸŒ Internationalization +- โšก Performance optimizations + --- -## Download +## ๐Ÿ“„ License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +--- + +## ๐Ÿ™ Acknowledgments + +- Thanks to all [contributors](https://github.com/dfanso/commit-msg/graphs/contributors) +- Google Gemini and X.AI Grok for providing LLM APIs +- The open-source community + +--- + +## ๐Ÿ“ž Support + +- ๐Ÿ› [Report a Bug](https://github.com/dfanso/commit-msg/issues/new?template=bug_report.md) +- ๐Ÿ’ก [Request a Feature](https://github.com/dfanso/commit-msg/issues/new?template=feature_request.md) +- ๐Ÿ’ฌ [Ask a Question](https://github.com/dfanso/commit-msg/issues) + +--- -To download the latest release of `commit-msg`, visit the [GitHub Releases](https://github.com/dfanso/commit-msg/releases) page and follow the installation instructions for your platform. +Made with โค๏ธ for Hacktoberfest 2025 From 0ba27a2d67a2e0364f4bb8632009613c03566a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20K=C3=BCndig?= Date: Thu, 2 Oct 2025 21:06:58 +0200 Subject: [PATCH 30/30] Added LLM type interface --- src/gemini/gemini.go | 19 +++++++++++++++---- src/grok/grok.go | 20 ++++++++++++++++---- src/main.go | 42 ++++++++++++++++++++++++++++++++---------- src/types/types.go | 16 ++++++++++++++++ 4 files changed, 79 insertions(+), 18 deletions(-) diff --git a/src/gemini/gemini.go b/src/gemini/gemini.go index e3cdf0e..b7742f1 100644 --- a/src/gemini/gemini.go +++ b/src/gemini/gemini.go @@ -10,13 +10,24 @@ import ( "github.com/dfanso/commit-msg/src/types" ) -func GenerateCommitMessage(config *types.Config, changes string, apiKey string) (string, error) { - // Prepare request to Gemini API +type Service struct { + apiKey string + config *types.Config +} + +func NewGeminiLLM(apiKey string, config *types.Config) (types.LLM, error) { + return Service{ + apiKey: apiKey, + config: config, + }, nil +} + +func (s Service) GenerateCommitMessage(ctx context.Context, changes string) (string, error) { + // Prepare request to Google API prompt := fmt.Sprintf("%s\n\n%s", types.CommitPrompt, changes) // Create context and client - ctx := context.Background() - client, err := genai.NewClient(ctx, option.WithAPIKey(apiKey)) + client, err := genai.NewClient(ctx, option.WithAPIKey(s.apiKey)) if err != nil { return "", err } diff --git a/src/grok/grok.go b/src/grok/grok.go index 09e5e79..1aa6b00 100644 --- a/src/grok/grok.go +++ b/src/grok/grok.go @@ -2,6 +2,7 @@ package grok import ( "bytes" + "context" "crypto/tls" "encoding/json" "fmt" @@ -12,12 +13,23 @@ import ( "github.com/dfanso/commit-msg/src/types" ) -func GenerateCommitMessage(config *types.Config, changes string, apiKey string) (string, error) { +type Service struct { + apiKey string + config *types.Config +} + +func NewGrokService(apiKey string, config *types.Config) (types.LLM, error) { + return Service{ + apiKey: apiKey, + config: config, + }, nil +} + +func (s Service) GenerateCommitMessage(ctx context.Context, changes string) (string, error) { // Prepare request to X.AI (Grok) API prompt := fmt.Sprintf("%s\n\n%s", types.CommitPrompt, changes) request := types.GrokRequest{ - Messages: []types.Message{ { Role: "user", @@ -35,14 +47,14 @@ func GenerateCommitMessage(config *types.Config, changes string, apiKey string) } // Create HTTP request - req, err := http.NewRequest("POST", "https://api.x.ai/v1/chat/completions", bytes.NewBuffer(requestBody)) + req, err := http.NewRequestWithContext(ctx, "POST", "https://api.x.ai/v1/chat/completions", bytes.NewBuffer(requestBody)) if err != nil { return "", err } // Set headers req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", apiKey)) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", s.apiKey)) // Configure HTTP client with improved TLS settings transport := &http.Transport{ diff --git a/src/main.go b/src/main.go index 7504c75..78a77e4 100644 --- a/src/main.go +++ b/src/main.go @@ -1,12 +1,14 @@ package main import ( + "context" "fmt" "log" "os" "os/exec" "path/filepath" "strings" + "time" "github.com/dfanso/commit-msg/src/gemini" "github.com/dfanso/commit-msg/src/grok" @@ -24,20 +26,29 @@ func normalizePath(path string) string { // Main function func main() { + ctx := context.Background() + + // Ensure the whole process is cancelled if it takes too long. + ctx, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() + + llmType := types.LLMType(os.Getenv("COMMIT_LLM")) + // Get API key from environment variables var apiKey string - if os.Getenv("COMMIT_LLM") == "google" { + switch { + case llmType == types.Google: apiKey = os.Getenv("GOOGLE_API_KEY") if apiKey == "" { log.Fatalf("GOOGLE_API_KEY is not set") } - } else if os.Getenv("COMMIT_LLM") == "grok" { + case llmType == types.Grok: apiKey = os.Getenv("GROK_API_KEY") if apiKey == "" { log.Fatalf("GROK_API_KEY is not set") } - } else { - log.Fatalf("Invalid COMMIT_LLM value: %s", os.Getenv("COMMIT_LLM")) + default: + log.Fatalf("Invalid COMMIT_LLM value: %s", llmType) } // Get current directory @@ -72,13 +83,12 @@ func main() { return } - // Pass API key to GenerateCommitMessage - var commitMsg string - if os.Getenv("COMMIT_LLM") == "google" { - commitMsg, err = gemini.GenerateCommitMessage(config, changes, apiKey) - } else { - commitMsg, err = grok.GenerateCommitMessage(config, changes, apiKey) + llm, err := resolveLLM(llmType, config) + if err != nil { + log.Fatalf("Failed to resolve LLM: %v", err) } + + commitMsg, err := llm.GenerateCommitMessage(ctx, changes) if err != nil { log.Fatalf("Failed to generate commit message: %v", err) } @@ -87,6 +97,18 @@ func main() { fmt.Println(commitMsg) } +// resolveLLM returns the LLM service based on the provided type. +func resolveLLM(llm types.LLMType, config *types.Config) (types.LLM, error) { + switch llm { + case types.Grok: + return grok.NewGrokService(os.Getenv("GROK_API_KEY"), config) + case types.Google: + return gemini.NewGeminiLLM(os.Getenv("GOOGLE_API_KEY"), config) + default: + return nil, fmt.Errorf("unknown LLM type %q", llm) + } +} + // Check if directory is a git repository func isGitRepository(path string) bool { cmd := exec.Command("git", "-C", path, "rev-parse", "--is-inside-work-tree") diff --git a/src/types/types.go b/src/types/types.go index 4e6f309..1bf661f 100644 --- a/src/types/types.go +++ b/src/types/types.go @@ -1,5 +1,21 @@ package types +import "context" + +// LLM abstracts the LLM api communication. +type LLM interface { + // GenerateCommitMessage generates a commit message based on the changes. + GenerateCommitMessage(ctx context.Context, changes string) (string, error) +} + +// LLMType designates a specific LLM provider. +type LLMType string + +const ( + Grok LLMType = "grok" + Google LLMType = "google" +) + // Configuration structure type Config struct { GrokAPI string `json:"grok_api"`