Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Generate Git commit messages from staged diffs using your preferred LLM CLI.

git-ai-commit does not talk to LLM APIs directly.
Instead, it delegates generation to existing LLM CLIs such as Claude Code, Gemini, or Codex, so no API keys, SDKs, or vendor-specific integrations are required.
Instead, it delegates generation to existing LLM CLIs such as Claude Code, Antigravity, or Codex, so no API keys, SDKs, or vendor-specific integrations are required.

## Install

Expand Down Expand Up @@ -132,7 +132,8 @@ git-ai-commit treats LLMs as external commands, not as APIs. This design avoids
Supported engines:

- `claude`
- `gemini`
- `agy` (Antigravity CLI)
- `gemini` (deprecated — see note below)
- `codex`

Built-in defaults are applied when `engines.<name>.args` is not set.
Expand All @@ -141,7 +142,9 @@ For `claude`, defaults include:
- `-p --model haiku`
- `--settings "{\"attribution\":{\"commit\":\"\",\"pr\":\"\"}}"` (prevents automatic `Co-authored-by` metadata)

If no engine is configured, auto-detection tries commands in this order: `claude` → `gemini` → `codex`. The first available command is used.
If no engine is configured, auto-detection tries commands in this order: `claude` → `agy` → `gemini` → `codex`. The first available command is used.

> **Gemini CLI deprecation:** Google is transitioning Gemini CLI to Antigravity CLI (`agy`). After **June 18, 2026**, Gemini CLI stops serving requests for Google AI Pro/Ultra subscribers and free users. Gemini CLI continues to work for users on Gemini Code Assist Standard/Enterprise or paid API keys. If you previously used `engine = "gemini"`, switch to `engine = "agy"`.

Any other engine name is treated as a direct command and executed with the prompt on stdin.

Expand Down
14 changes: 14 additions & 0 deletions internal/app/app_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,20 @@ func TestSelectEngineGeminiDefault(t *testing.T) {
}
}

func TestSelectEngineAgyDefault(t *testing.T) {
cfg := config.Default()
cfg.DefaultEngine = "agy"
cfg.Engines = map[string]config.EngineConfig{}

_, command, err := selectEngine(cfg)
if err != nil {
t.Fatalf("selectEngine error: %v", err)
}
if command != "agy -p {{prompt}}" {
t.Fatalf("command = %q", command)
}
}

func TestSelectEngineCustomArgs(t *testing.T) {
cfg := config.Default()
cfg.DefaultEngine = "claude"
Expand Down
3 changes: 2 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ var DefaultEngineArgs = map[string][]string{
"claude": {"-p", "--model", "haiku", "--settings", "{\"attribution\":{\"commit\":\"\",\"pr\":\"\"}}", "--no-session-persistence"},
"cursor-agent": {"-p"},
"gemini": {"-m", "gemini-2.5-flash", "-p", "{{prompt}}"},
"agy": {"-p", "{{prompt}}"},
}

func Default() Config {
Expand Down Expand Up @@ -353,7 +354,7 @@ func LoadPromptPreset(name string) (string, error) {
}

func autodetectEngine() string {
candidates := []string{"claude", "gemini", "codex"}
candidates := []string{"claude", "agy", "gemini", "codex"}
for _, name := range candidates {
if _, err := exec.LookPath(name); err == nil {
return name
Expand Down
24 changes: 24 additions & 0 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,30 @@ func TestAutodetectEngineOrder(t *testing.T) {
})
}

func TestAutodetectEnginePrefersAgyOverGemini(t *testing.T) {
configHome := t.TempDir()
binDir := filepath.Join(configHome, "bin")
if err := os.MkdirAll(binDir, 0o755); err != nil {
t.Fatalf("mkdir bin: %v", err)
}
makeExecutable(t, binDir, "codex")
makeExecutable(t, binDir, "gemini")
makeExecutable(t, binDir, "agy")

t.Setenv("PATH", binDir)
t.Setenv("XDG_CONFIG_HOME", t.TempDir())

withDir(t, t.TempDir(), func() {
cfg, err := Load()
if err != nil {
t.Fatalf("Load error: %v", err)
}
if cfg.DefaultEngine != "agy" {
t.Fatalf("DefaultEngine = %q", cfg.DefaultEngine)
}
})
}

func makeExecutable(t *testing.T, dir, name string) {
t.Helper()
path := filepath.Join(dir, name)
Expand Down