Files
beads/cmd/bd/doctor/gemini.go
beads/crew/wolf a06b40bd48 feat: add bd setup gemini for Gemini CLI integration (#845)
Add support for Gemini CLI hook-based integration, similar to Claude Code:
- bd setup gemini: Install SessionStart/PreCompress hooks
- bd setup gemini --check: Verify installation
- bd setup gemini --remove: Remove hooks
- bd setup gemini --project: Project-only installation
- bd setup gemini --stealth: Use bd prime --stealth

Also adds Gemini CLI integration check to bd doctor.

Gemini CLI's hook system is nearly identical to Claude Code's,
making this a clean, low-maintenance addition.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-02 00:02:38 -08:00

91 lines
2.1 KiB
Go

package doctor
import (
"encoding/json"
"os"
"path/filepath"
)
// CheckGemini returns Gemini CLI integration verification as a DoctorCheck
func CheckGemini() DoctorCheck {
hasHooks := hasGeminiHooks()
if hasHooks {
return DoctorCheck{
Name: "Gemini CLI Integration",
Status: StatusOK,
Message: "Hooks installed",
Detail: "SessionStart and PreCompress hooks enabled",
}
}
return DoctorCheck{
Name: "Gemini CLI Integration",
Status: StatusOK, // Not a warning - Gemini is optional
Message: "Not configured",
Detail: "Run 'bd setup gemini' to enable Gemini CLI integration",
}
}
// hasGeminiHooks checks if Gemini CLI hooks are installed
func hasGeminiHooks() bool {
home, err := os.UserHomeDir()
if err != nil {
return false
}
globalSettings := filepath.Join(home, ".gemini", "settings.json")
projectSettings := ".gemini/settings.json"
return hasGeminiBeadsHooks(globalSettings) || hasGeminiBeadsHooks(projectSettings)
}
// hasGeminiBeadsHooks checks if a settings file has bd prime hooks for Gemini CLI
func hasGeminiBeadsHooks(settingsPath string) bool {
data, err := os.ReadFile(settingsPath) // #nosec G304 -- settingsPath is constructed from known safe locations
if err != nil {
return false
}
var settings map[string]interface{}
if err := json.Unmarshal(data, &settings); err != nil {
return false
}
hooks, ok := settings["hooks"].(map[string]interface{})
if !ok {
return false
}
// Check SessionStart and PreCompress for "bd prime"
for _, event := range []string{"SessionStart", "PreCompress"} {
eventHooks, ok := hooks[event].([]interface{})
if !ok {
continue
}
for _, hook := range eventHooks {
hookMap, ok := hook.(map[string]interface{})
if !ok {
continue
}
commands, ok := hookMap["hooks"].([]interface{})
if !ok {
continue
}
for _, cmd := range commands {
cmdMap, ok := cmd.(map[string]interface{})
if !ok {
continue
}
cmdStr := cmdMap["command"]
if cmdStr == "bd prime" || cmdStr == "bd prime --stealth" {
return true
}
}
}
}
return false
}