Skip to content

Commit a6c5874

Browse files
authored
Merge branch 'main' into add-pull-request-suggestions
2 parents 4640cdf + 2b25029 commit a6c5874

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,10 +4,11 @@ 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-
- Generates 10 pull request titles based on the diff between the current branch and a target branch
9-
- Providers: GitHub Copilot (default), OpenAI
10-
- Interactive config to pick provider/model and set keys
7+
- Generates configurable number of commit message suggestions from your staged diff
8+
- Generates 10 pull request titles based on the diff between the current branch and a target branch
9+
- Providers: GitHub Copilot (default), OpenAI, Anthropic (Claude Code CLI)
10+
- Multi-language support: English and Spanish
11+
- Interactive config to pick provider/model/language and set keys
1112
- Simple output suitable for piping into TUI menus (one message per line)
1213

1314
## Installation
@@ -30,8 +31,8 @@ go build -o lazycommit main.go
3031
- Subcommands:
3132
- `lazycommit commit` — prints 10 suggested commit messages to stdout, one per line, based on `git diff --cached`.
3233
- `lazycommit pr <target-branch>` — prints 10 suggested pull request titles to stdout, one per line, based on diff between current branch and `<target-branch>`.
33-
- `lazycommit config get` — prints the active provider and model.
34-
- `lazycommit config set` — interactive setup for provider, API key, and model.
34+
- `lazycommit config get` — prints the active provider, model and language.
35+
- `lazycommit config set` — interactive setup for provider, API key, model, and language.
3536

3637
Exit behaviors:
3738
- If no staged changes: prints "No staged changes to commit." and exits 0.
@@ -75,6 +76,7 @@ Contains API keys, tokens, and provider-specific settings. **Do not share this f
7576

7677
```yaml
7778
active_provider: copilot # default if a GitHub token is found
79+
language: en # commit message language: "en" (English) or "es" (Spanish)
7880
providers:
7981
copilot:
8082
api_key: "$GITHUB_TOKEN" # Uses GitHub token; token is exchanged internally
@@ -84,13 +86,28 @@ providers:
8486
api_key: "$OPENAI_API_KEY"
8587
model: "gpt-4o"
8688
# endpoint_url: "https://api.openai.com/v1" # Optional - uses default if not specified
89+
anthropic:
90+
model: "claude-haiku-4-5" # Uses Claude Code CLI - no API key needed
91+
num_suggestions: 10 # Number of commit suggestions to generate
8792
# Custom provider example (e.g., local Ollama):
8893
# local:
8994
# api_key: "not-needed"
9095
# model: "llama3.1:8b"
9196
# endpoint_url: "http://localhost:11434/v1"
9297
```
9398

99+
#### Anthropic Provider (Claude Code CLI)
100+
101+
The Anthropic provider integrates with your local [Claude Code CLI](https://github.com/anthropics/claude-code) installation:
102+
103+
- **No API key required**: Uses your existing Claude Code authentication
104+
- **Fast and cost-effective**: Leverages Claude Haiku model
105+
- **Configurable**: Set custom number of suggestions per provider
106+
107+
Requirements:
108+
- Claude Code CLI installed and authenticated
109+
- Command `claude` available in PATH
110+
94111
### 2. Prompt Configuration (`~/.config/.lazycommit.prompts.yaml`)
95112
Contains prompt templates and message configurations. **Safe to share in dotfiles and Git.**
96113

@@ -134,6 +151,28 @@ providers:
134151
<!-- endpoint_url: "https://api.z.ai/api/paas/v4/" -->
135152
<!-- ``` -->
136153

154+
### Language Configuration
155+
156+
lazycommit supports generating commit messages in different languages. Set the `language` field in your config:
157+
158+
```yaml
159+
language: es # Spanish
160+
# or
161+
language: en # English (default)
162+
```
163+
164+
You can also configure it interactively:
165+
166+
```bash
167+
lazycommit config set # Select language in the interactive menu
168+
```
169+
170+
The language setting automatically instructs the AI to generate commit messages in the specified language, regardless of the provider used.
171+
172+
**Supported languages:**
173+
- `en` - English (default)
174+
- `es` - Spanish (Español)
175+
137176
## Integration with TUI Git clients
138177

139178
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)