Skip to content

Commit 2b25029

Browse files
authored
Merge pull request #30 from lcarrasco/main
feat: Add Anthropic provider and multi-language support
2 parents 15565b9 + c184947 commit 2b25029

File tree

7 files changed

+361
-44
lines changed

7 files changed

+361
-44
lines changed

README.md

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ AI-powered Git commit message generator that analyzes your staged changes and ou
44

55
## Features
66

7-
- Generates 10 commit message suggestions from your staged diff
8-
- Providers: GitHub Copilot (default), OpenAI
9-
- Interactive config to pick provider/model and set keys
7+
- Generates configurable number of commit message suggestions from your staged diff
8+
- Providers: GitHub Copilot (default), OpenAI, Anthropic (Claude Code CLI)
9+
- Multi-language support: English and Spanish
10+
- Interactive config to pick provider/model/language and set keys
1011
- Simple output suitable for piping into TUI menus (one message per line)
1112

1213
## Installation
@@ -27,9 +28,9 @@ go build -o lazycommit main.go
2728

2829
- Root command: `lazycommit`
2930
- Subcommands:
30-
- `lazycommit commit` — prints 10 suggested commit messages to stdout, one per line, based on `git diff --cached`.
31-
- `lazycommit config get` — prints the active provider and model.
32-
- `lazycommit config set` — interactive setup for provider, API key, and model.
31+
- `lazycommit commit` — prints suggested commit messages to stdout, one per line, based on `git diff --cached`.
32+
- `lazycommit config get` — prints the active provider, model, and language.
33+
- `lazycommit config set` — interactive setup for provider, API key, model, and language.
3334

3435
Exit behaviors:
3536
- If no staged changes: prints "No staged changes to commit." and exits 0.
@@ -67,6 +68,7 @@ Contains API keys, tokens, and provider-specific settings. **Do not share this f
6768

6869
```yaml
6970
active_provider: copilot # default if a GitHub token is found
71+
language: en # commit message language: "en" (English) or "es" (Spanish)
7072
providers:
7173
copilot:
7274
api_key: "$GITHUB_TOKEN" # Uses GitHub token; token is exchanged internally
@@ -76,13 +78,28 @@ providers:
7678
api_key: "$OPENAI_API_KEY"
7779
model: "gpt-4o"
7880
# endpoint_url: "https://api.openai.com/v1" # Optional - uses default if not specified
81+
anthropic:
82+
model: "claude-haiku-4-5" # Uses Claude Code CLI - no API key needed
83+
num_suggestions: 10 # Number of commit suggestions to generate
7984
# Custom provider example (e.g., local Ollama):
8085
# local:
8186
# api_key: "not-needed"
8287
# model: "llama3.1:8b"
8388
# endpoint_url: "http://localhost:11434/v1"
8489
```
8590

91+
#### Anthropic Provider (Claude Code CLI)
92+
93+
The Anthropic provider integrates with your local [Claude Code CLI](https://github.com/anthropics/claude-code) installation:
94+
95+
- **No API key required**: Uses your existing Claude Code authentication
96+
- **Fast and cost-effective**: Leverages Claude Haiku model
97+
- **Configurable**: Set custom number of suggestions per provider
98+
99+
Requirements:
100+
- Claude Code CLI installed and authenticated
101+
- Command `claude` available in PATH
102+
86103
### 2. Prompt Configuration (`~/.config/.lazycommit.prompts.yaml`)
87104
Contains prompt templates and message configurations. **Safe to share in dotfiles and Git.**
88105

@@ -125,6 +142,28 @@ providers:
125142
<!-- endpoint_url: "https://api.z.ai/api/paas/v4/" -->
126143
<!-- ``` -->
127144

145+
### Language Configuration
146+
147+
lazycommit supports generating commit messages in different languages. Set the `language` field in your config:
148+
149+
```yaml
150+
language: es # Spanish
151+
# or
152+
language: en # English (default)
153+
```
154+
155+
You can also configure it interactively:
156+
157+
```bash
158+
lazycommit config set # Select language in the interactive menu
159+
```
160+
161+
The language setting automatically instructs the AI to generate commit messages in the specified language, regardless of the provider used.
162+
163+
**Supported languages:**
164+
- `en` - English (default)
165+
- `es` - Spanish (Español)
166+
128167
## Integration with TUI Git clients
129168

130169
Because `lazycommit commit` prints plain lines, it plugs nicely into menu UIs.

cmd/commit.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,20 @@ var commitCmd = &cobra.Command{
4040
var aiProvider CommitProvider
4141

4242
providerName := config.GetProvider()
43-
apiKey, err := config.GetAPIKey()
44-
if err != nil {
45-
fmt.Fprintf(os.Stderr, "Error getting API key: %v\n", err)
46-
os.Exit(1)
43+
44+
// API key is not needed for anthropic provider (uses CLI)
45+
var apiKey string
46+
if providerName != "anthropic" {
47+
var err error
48+
apiKey, err = config.GetAPIKey()
49+
if err != nil {
50+
fmt.Fprintf(os.Stderr, "Error getting API key: %v\n", err)
51+
os.Exit(1)
52+
}
4753
}
4854

4955
var model string
50-
if providerName == "copilot" || providerName == "openai" {
56+
if providerName == "copilot" || providerName == "openai" || providerName == "anthropic" {
5157
var err error
5258
model, err = config.GetModel()
5359
if err != nil {
@@ -67,6 +73,13 @@ var commitCmd = &cobra.Command{
6773
aiProvider = provider.NewCopilotProviderWithModel(apiKey, model, endpoint)
6874
case "openai":
6975
aiProvider = provider.NewOpenAIProvider(apiKey, model, endpoint)
76+
case "anthropic":
77+
// Get num_suggestions from config (default to 10)
78+
numSuggestions := config.GetNumSuggestions()
79+
if numSuggestions <= 0 {
80+
numSuggestions = 10
81+
}
82+
aiProvider = provider.NewAnthropicProvider(model, numSuggestions)
7083
default:
7184
// Default to copilot if provider is not set or unknown
7285
aiProvider = provider.NewCopilotProvider(apiKey, endpoint)

cmd/config.go

Lines changed: 97 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ var getCmd = &cobra.Command{
3232
fmt.Println("Error getting endpoint:", err)
3333
os.Exit(1)
3434
}
35+
language := config.GetLanguage()
3536
fmt.Printf("Active Provider: %s\n", provider)
3637
fmt.Printf("Model: %s\n", model)
3738
fmt.Printf("Endpoint: %s\n", endpoint)
39+
fmt.Printf("Language: %s\n", language)
3840
},
3941
}
4042

@@ -79,7 +81,7 @@ func runInteractiveConfig() {
7981

8082
providerPrompt := &survey.Select{
8183
Message: "Choose a provider:",
82-
Options: []string{"openai", "copilot"},
84+
Options: []string{"openai", "copilot", "anthropic"},
8385
Default: currentProvider,
8486
}
8587
var selectedProvider string
@@ -99,7 +101,42 @@ func runInteractiveConfig() {
99101
currentModel = ""
100102
}
101103

102-
if selectedProvider != "copilot" {
104+
// Language configuration
105+
currentLanguage := config.GetLanguage()
106+
languagePrompt := &survey.Select{
107+
Message: "Choose a language for commit messages:",
108+
Options: []string{"English (en)", "Español (es)"},
109+
Default: func() string {
110+
if currentLanguage == "es" {
111+
return "Español (es)"
112+
}
113+
return "English (en)"
114+
}(),
115+
}
116+
var selectedLanguage string
117+
err = survey.AskOne(languagePrompt, &selectedLanguage)
118+
if err != nil {
119+
fmt.Println(err.Error())
120+
return
121+
}
122+
123+
// Extract language code from selection
124+
langCode := "en"
125+
if selectedLanguage == "Español (es)" {
126+
langCode = "es"
127+
}
128+
129+
if langCode != currentLanguage {
130+
err := config.SetLanguage(langCode)
131+
if err != nil {
132+
fmt.Printf("Error setting language: %v\n", err)
133+
return
134+
}
135+
fmt.Printf("Language set to: %s\n", langCode)
136+
}
137+
138+
// API key configuration - skip for copilot and anthropic
139+
if selectedProvider != "copilot" && selectedProvider != "anthropic" {
103140
apiKeyPrompt := &survey.Input{
104141
Message: fmt.Sprintf("Enter API Key for %s:", selectedProvider),
105142
}
@@ -117,21 +154,31 @@ func runInteractiveConfig() {
117154
}
118155
fmt.Printf("API key for %s set.\n", selectedProvider)
119156
}
157+
} else if selectedProvider == "anthropic" {
158+
fmt.Println("Anthropic provider uses Claude Code CLI - no API key needed.")
120159
}
121160

122-
// Dynamically generate available models for OpenAI
161+
// Dynamically generate available models
123162
availableModels := map[string][]string{
124-
"openai": {},
125-
"copilot": {"openai/gpt-5-mini"}, // TODO: update if copilot models are dynamic
163+
"openai": {},
164+
"copilot": {"openai/gpt-5-mini"},
165+
"anthropic": {},
126166
}
127167

128168
modelDisplayToID := map[string]string{}
169+
129170
if selectedProvider == "openai" {
130171
for id, m := range models.OpenAIModels {
131172
display := fmt.Sprintf("%s (%s)", m.Name, string(id))
132173
availableModels["openai"] = append(availableModels["openai"], display)
133174
modelDisplayToID[display] = string(id)
134175
}
176+
} else if selectedProvider == "anthropic" {
177+
for _, m := range models.AnthropicModels {
178+
display := fmt.Sprintf("%s (%s)", m.Name, m.APIModel)
179+
availableModels["anthropic"] = append(availableModels["anthropic"], display)
180+
modelDisplayToID[display] = m.APIModel
181+
}
135182
}
136183

137184
modelPrompt := &survey.Select{
@@ -142,7 +189,7 @@ func runInteractiveConfig() {
142189
// Try to set the default to the current model if possible
143190
isValidDefault := false
144191
currentDisplay := ""
145-
if selectedProvider == "openai" {
192+
if selectedProvider == "openai" || selectedProvider == "anthropic" {
146193
for display, id := range modelDisplayToID {
147194
if id == currentModel || display == currentModel {
148195
isValidDefault = true
@@ -171,7 +218,7 @@ func runInteractiveConfig() {
171218
}
172219

173220
selectedModel := selectedDisplay
174-
if selectedProvider == "openai" {
221+
if selectedProvider == "openai" || selectedProvider == "anthropic" {
175222
selectedModel = modelDisplayToID[selectedDisplay]
176223
}
177224

@@ -184,31 +231,55 @@ func runInteractiveConfig() {
184231
fmt.Printf("Model set to: %s\n", selectedModel)
185232
}
186233

234+
// Number of suggestions configuration for anthropic
235+
if selectedProvider == "anthropic" {
236+
numSuggestionsPrompt := &survey.Input{
237+
Message: "Number of commit message suggestions (default: 10):",
238+
Default: "10",
239+
}
240+
var numSuggestions string
241+
err := survey.AskOne(numSuggestionsPrompt, &numSuggestions)
242+
if err != nil {
243+
fmt.Println(err.Error())
244+
return
245+
}
246+
if numSuggestions != "" {
247+
err := config.SetNumSuggestions(selectedProvider, numSuggestions)
248+
if err != nil {
249+
fmt.Printf("Error setting num_suggestions: %v\n", err)
250+
return
251+
}
252+
fmt.Printf("Number of suggestions set to: %s\n", numSuggestions)
253+
}
254+
}
255+
187256
// Get current endpoint
188257
currentEndpoint, _ := config.GetEndpoint()
189258

190-
// Endpoint configuration prompt
191-
endpointPrompt := &survey.Input{
192-
Message: "Enter custom endpoint URL (leave empty for default):",
193-
Default: currentEndpoint,
194-
}
195-
var endpoint string
196-
err = survey.AskOne(endpointPrompt, &endpoint, survey.WithValidator(validateEndpointURL))
197-
if err != nil {
198-
fmt.Println(err.Error())
199-
return
200-
}
201-
202-
// Only set endpoint if it's different from current
203-
if endpoint != currentEndpoint && endpoint != "" {
204-
err := config.SetEndpoint(selectedProvider, endpoint)
259+
// Endpoint configuration prompt - skip for anthropic since it uses CLI
260+
if selectedProvider != "anthropic" {
261+
endpointPrompt := &survey.Input{
262+
Message: "Enter custom endpoint URL (leave empty for default):",
263+
Default: currentEndpoint,
264+
}
265+
var endpoint string
266+
err = survey.AskOne(endpointPrompt, &endpoint, survey.WithValidator(validateEndpointURL))
205267
if err != nil {
206-
fmt.Printf("Error setting endpoint: %v\n", err)
268+
fmt.Println(err.Error())
207269
return
208270
}
209-
fmt.Printf("Endpoint set to: %s\n", endpoint)
210-
} else if endpoint == "" {
211-
fmt.Println("Using default endpoint for provider")
271+
272+
// Only set endpoint if it's different from current
273+
if endpoint != currentEndpoint && endpoint != "" {
274+
err := config.SetEndpoint(selectedProvider, endpoint)
275+
if err != nil {
276+
fmt.Printf("Error setting endpoint: %v\n", err)
277+
return
278+
}
279+
fmt.Printf("Endpoint set to: %s\n", endpoint)
280+
} else if endpoint == "" {
281+
fmt.Println("Using default endpoint for provider")
282+
}
212283
}
213284
}
214285

0 commit comments

Comments
 (0)