Skip to content

Commit d9e1ac1

Browse files
Merge branch 'main' into ollama
2 parents 3e1d4f2 + 43b0e8c commit d9e1ac1

12 files changed

Lines changed: 949 additions & 169 deletions

File tree

CONTRIBUTING.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ Improving documentation is always appreciated:
8181
- Git
8282
- API key for either:
8383
- Google Gemini (`GEMINI_API_KEY`)
84+
- Groq (`GROQ_API_KEY`)
8485
- Grok (`GROK_API_KEY`)
8586
- Claude (`CLAUDE_API_KEY`)
8687

@@ -89,9 +90,11 @@ Improving documentation is always appreciated:
8990
1. Set up your environment variables:
9091

9192
```bash
92-
export COMMIT_LLM=gemini # or "grok"
93+
export COMMIT_LLM=gemini # or "groq" / "grok"
9394
export GEMINI_API_KEY=your-api-key-here
9495
# OR
96+
export GROQ_API_KEY=your-api-key-here
97+
# OR
9598
export GROK_API_KEY=your-api-key-here
9699
```
97100

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21-
SOFTWARE.
21+
SOFTWARE.

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ commit .
175175
2. Generate an API key
176176
3. Set the `GROK_API_KEY` environment variable
177177

178+
**Groq:**
179+
180+
1. Sign up at [Groq Cloud](https://console.groq.com/)
181+
2. Create an API key
182+
3. Set the `GROQ_API_KEY` environment variable
183+
4. _(Optional)_ Set `GROQ_MODEL` or `GROQ_API_URL` to override defaults
184+
178185
**Claude (Anthropic):**
179186

180187
1. Visit the [Anthropic Console](https://console.anthropic.com/)

cmd/cli/createMsg.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package cmd
2+
3+
import (
4+
"log"
5+
"os"
6+
7+
"github.com/atotto/clipboard"
8+
"github.com/dfanso/commit-msg/cmd/cli/store"
9+
"github.com/dfanso/commit-msg/internal/chatgpt"
10+
"github.com/dfanso/commit-msg/internal/claude"
11+
"github.com/dfanso/commit-msg/internal/display"
12+
"github.com/dfanso/commit-msg/internal/gemini"
13+
"github.com/dfanso/commit-msg/internal/git"
14+
"github.com/dfanso/commit-msg/internal/grok"
15+
"github.com/dfanso/commit-msg/internal/stats"
16+
"github.com/dfanso/commit-msg/pkg/types"
17+
"github.com/pterm/pterm"
18+
)
19+
20+
21+
func CreateCommitMsg () {
22+
23+
// Validate COMMIT_LLM and required API keys
24+
useLLM,err := store.DefaultLLMKey()
25+
if err != nil {
26+
log.Fatal(err)
27+
}
28+
29+
commitLLM := useLLM.LLM
30+
apiKey := useLLM.APIKey
31+
32+
33+
// Get current directory
34+
currentDir, err := os.Getwd()
35+
if err != nil {
36+
log.Fatalf("Failed to get current directory: %v", err)
37+
}
38+
39+
// Check if current directory is a git repository
40+
if !git.IsRepository(currentDir) {
41+
log.Fatalf("Current directory is not a Git repository: %s", currentDir)
42+
}
43+
44+
// Create a minimal config for the API
45+
config := &types.Config{
46+
GrokAPI: "https://api.x.ai/v1/chat/completions",
47+
}
48+
49+
// Create a repo config for the current directory
50+
repoConfig := types.RepoConfig{
51+
Path: currentDir,
52+
}
53+
54+
// Get file statistics before fetching changes
55+
fileStats, err := stats.GetFileStatistics(&repoConfig)
56+
if err != nil {
57+
log.Fatalf("Failed to get file statistics: %v", err)
58+
}
59+
60+
// Display header
61+
pterm.DefaultHeader.WithFullWidth().
62+
WithBackgroundStyle(pterm.NewStyle(pterm.BgDarkGray)).
63+
WithTextStyle(pterm.NewStyle(pterm.FgLightWhite)).
64+
Println("🚀 Commit Message Generator")
65+
66+
pterm.Println()
67+
68+
// Display file statistics with icons
69+
display.ShowFileStatistics(fileStats)
70+
71+
if fileStats.TotalFiles == 0 {
72+
pterm.Warning.Println("No changes detected in the Git repository.")
73+
return
74+
}
75+
76+
// Get the changes
77+
changes, err := git.GetChanges(&repoConfig)
78+
if err != nil {
79+
log.Fatalf("Failed to get Git changes: %v", err)
80+
}
81+
82+
if len(changes) == 0 {
83+
pterm.Warning.Println("No changes detected in the Git repository.")
84+
return
85+
}
86+
87+
pterm.Println()
88+
89+
// Show generating spinner
90+
spinnerGenerating, err := pterm.DefaultSpinner.
91+
WithSequence("⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏").
92+
Start("🤖 Generating commit message...")
93+
if err != nil {
94+
log.Fatalf("Failed to start spinner: %v", err)
95+
}
96+
97+
var commitMsg string
98+
99+
switch commitLLM {
100+
101+
case "Gemini":
102+
commitMsg, err = gemini.GenerateCommitMessage(config, changes, apiKey)
103+
104+
case "OpenAI":
105+
commitMsg, err = chatgpt.GenerateCommitMessage(config, changes, apiKey)
106+
107+
case "Claude":
108+
commitMsg, err = claude.GenerateCommitMessage(config, changes, apiKey)
109+
110+
default:
111+
commitMsg, err = grok.GenerateCommitMessage(config, changes, apiKey)
112+
}
113+
114+
115+
if err != nil {
116+
spinnerGenerating.Fail("Failed to generate commit message")
117+
log.Fatalf("Error: %v", err)
118+
}
119+
120+
spinnerGenerating.Success("✅ Commit message generated successfully!")
121+
122+
pterm.Println()
123+
124+
// Display the commit message in a styled panel
125+
display.ShowCommitMessage(commitMsg)
126+
127+
// Copy to clipboard
128+
err = clipboard.WriteAll(commitMsg)
129+
if err != nil {
130+
pterm.Warning.Printf("⚠️ Could not copy to clipboard: %v\n", err)
131+
} else {
132+
pterm.Success.Println("📋 Commit message copied to clipboard!")
133+
}
134+
135+
pterm.Println()
136+
137+
// Display changes preview
138+
display.ShowChangesPreview(fileStats)
139+
140+
}

cmd/cli/llmSetup.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package cmd
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"github.com/dfanso/commit-msg/cmd/cli/store"
8+
"github.com/manifoldco/promptui"
9+
)
10+
11+
12+
func SetupLLM() error {
13+
14+
providers := []string{"OpenAI", "Claude", "Gemini", "Grok"}
15+
prompt := promptui.Select{
16+
Label: "Select LLM",
17+
Items: providers,
18+
}
19+
20+
_, model, err := prompt.Run()
21+
if err != nil {
22+
return fmt.Errorf("prompt failed")
23+
}
24+
25+
apiKeyPrompt := promptui.Prompt{
26+
Label: "Enter API Key",
27+
Mask: '*',
28+
29+
}
30+
31+
apiKey, err := apiKeyPrompt.Run()
32+
if err != nil {
33+
return fmt.Errorf("failed to read API Key: %w", err)
34+
}
35+
36+
LLMConfig := store.LLMProvider{
37+
LLM: model,
38+
APIKey: apiKey,
39+
}
40+
41+
42+
43+
err = store.Save(LLMConfig)
44+
if err != nil {
45+
return err
46+
}
47+
48+
fmt.Println("LLM model added")
49+
return nil
50+
}
51+
52+
func UpdateLLM() error {
53+
54+
SavedModels, err := store.ListSavedModels()
55+
if err != nil {
56+
return err
57+
}
58+
59+
if len(SavedModels.LLMProviders) == 0 {
60+
return errors.New("no model exists, Please add atleast one model Run: 'commit llm setup'")
61+
62+
}
63+
64+
models := []string{}
65+
options := []string{"Set Default", "Change API Key", "Delete"}
66+
67+
for _, p := range SavedModels.LLMProviders {
68+
models = append(models, p.LLM)
69+
}
70+
71+
prompt := promptui.Select{
72+
Label: "Select from saved models",
73+
Items: models,
74+
}
75+
76+
_,model,err := prompt.Run()
77+
if err != nil {
78+
return err
79+
}
80+
81+
82+
prompt = promptui.Select{
83+
Label: "Select Option",
84+
Items: options,
85+
}
86+
opNo,_,err := prompt.Run()
87+
if err != nil {
88+
return err
89+
}
90+
91+
apiKeyprompt := promptui.Prompt {
92+
Label: "Enter API Key",
93+
}
94+
95+
96+
switch opNo {
97+
case 0:
98+
err := store.ChangeDefault(model)
99+
if err != nil {
100+
return err
101+
}
102+
fmt.Printf("%s set as default", model)
103+
case 1:
104+
apiKey, err := apiKeyprompt.Run()
105+
if err != nil {
106+
return err
107+
}
108+
err = store.UpdateAPIKey(model, apiKey)
109+
if err != nil {
110+
return err
111+
}
112+
fmt.Printf("%s API Key Updated", model)
113+
case 2:
114+
err := store.DeleteModel(model)
115+
if err != nil {
116+
return err
117+
}
118+
fmt.Printf("%s model deleted", model)
119+
}
120+
121+
return nil
122+
}

cmd/cli/root.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
Copyright © 2025 NAME HERE <EMAIL ADDRESS>
3+
*/
4+
package cmd
5+
6+
import (
7+
"os"
8+
9+
"github.com/spf13/cobra"
10+
)
11+
12+
// rootCmd represents the base command when called without any subcommands
13+
var rootCmd = &cobra.Command{
14+
Use: "commit",
15+
Short: "CLI tool to write commit message",
16+
Long: `Write a commit message with AI of your choice`,
17+
// Uncomment the following line if your bare application
18+
// has an action associated with it:
19+
// Run: func(cmd *cobra.Command, args []string) { },
20+
}
21+
22+
// Execute adds all child commands to the root command and sets flags appropriately.
23+
// This is called by main.main(). It only needs to happen once to the rootCmd.
24+
func Execute() {
25+
err := rootCmd.Execute()
26+
if err != nil {
27+
os.Exit(1)
28+
}
29+
}
30+
31+
var llmCmd = &cobra.Command{
32+
Use: "llm",
33+
Short: "Manage LLM configuration",
34+
}
35+
36+
var llmSetupCmd = &cobra.Command{
37+
Use: "setup",
38+
Short: "Setup your LLM provider and API key",
39+
RunE: func(cmd *cobra.Command, args []string) error {
40+
return SetupLLM()
41+
},
42+
}
43+
44+
var llmUpdateCmd = &cobra.Command{
45+
Use: "update",
46+
Short: "Update or Delete LLM Model",
47+
RunE: func(cmd *cobra.Command, args []string) error {
48+
return UpdateLLM()
49+
},
50+
}
51+
52+
var creatCommitMsg = &cobra.Command{
53+
Use: ".",
54+
Short: "Create Commit Message",
55+
RunE: func(cmd *cobra.Command, args []string) error {
56+
CreateCommitMsg()
57+
return nil
58+
},
59+
}
60+
61+
func init() {
62+
// Here you will define your flags and configuration settings.
63+
// Cobra supports persistent flags, which, if defined here,
64+
// will be global for your application.
65+
66+
// rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.commit-msg.yaml)")
67+
68+
// Cobra also supports local flags, which will only run
69+
// when this action is called directly.
70+
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
71+
rootCmd.AddCommand(creatCommitMsg)
72+
rootCmd.AddCommand(llmCmd)
73+
llmCmd.AddCommand(llmSetupCmd)
74+
llmCmd.AddCommand(llmUpdateCmd)
75+
}
76+

0 commit comments

Comments
 (0)