feat: add Gemini CLI auth login support

Add Gemini (Google AI) as a supported provider for CLI authentication.
Users can now store their Gemini API key via:

  picoclaw auth login --provider gemini

The key is stored securely in ~/.picoclaw/auth.json and loaded
automatically when the provider is set to gemini with auth_method=token.

Changes:
- pkg/auth/token.go: add Gemini display name (aistudio.google.com/apikey)
- cmd/picoclaw/main.go: wire Gemini into login/logout/help commands
- pkg/providers/http_provider.go: load Gemini API key from auth store
  when auth_method is 'token' (both explicit provider and model-name
  fallback detection)

Closes #267
This commit is contained in:
Atharva 2026-02-16 17:18:37 +05:30
parent e7f15afdd4
commit c17c4744e4
3 changed files with 38 additions and 8 deletions

View file

@ -789,13 +789,14 @@ func authHelp() {
fmt.Println(" status Show current auth status") fmt.Println(" status Show current auth status")
fmt.Println() fmt.Println()
fmt.Println("Login options:") fmt.Println("Login options:")
fmt.Println(" --provider <name> Provider to login with (openai, anthropic)") fmt.Println(" --provider <name> Provider to login with (openai, anthropic, gemini)")
fmt.Println(" --device-code Use device code flow (for headless environments)") fmt.Println(" --device-code Use device code flow (for headless environments)")
fmt.Println() fmt.Println()
fmt.Println("Examples:") fmt.Println("Examples:")
fmt.Println(" picoclaw auth login --provider openai") fmt.Println(" picoclaw auth login --provider openai")
fmt.Println(" picoclaw auth login --provider openai --device-code") fmt.Println(" picoclaw auth login --provider openai --device-code")
fmt.Println(" picoclaw auth login --provider anthropic") fmt.Println(" picoclaw auth login --provider anthropic")
fmt.Println(" picoclaw auth login --provider gemini")
fmt.Println(" picoclaw auth logout --provider openai") fmt.Println(" picoclaw auth logout --provider openai")
fmt.Println(" picoclaw auth status") fmt.Println(" picoclaw auth status")
} }
@ -819,18 +820,18 @@ func authLoginCmd() {
if provider == "" { if provider == "" {
fmt.Println("Error: --provider is required") fmt.Println("Error: --provider is required")
fmt.Println("Supported providers: openai, anthropic") fmt.Println("Supported providers: openai, anthropic, gemini")
return return
} }
switch provider { switch provider {
case "openai": case "openai":
authLoginOpenAI(useDeviceCode) authLoginOpenAI(useDeviceCode)
case "anthropic": case "anthropic", "gemini":
authLoginPasteToken(provider) authLoginPasteToken(provider)
default: default:
fmt.Printf("Unsupported provider: %s\n", provider) fmt.Printf("Unsupported provider: %s\n", provider)
fmt.Println("Supported providers: openai, anthropic") fmt.Println("Supported providers: openai, anthropic, gemini")
} }
} }
@ -889,6 +890,8 @@ func authLoginPasteToken(provider string) {
appCfg.Providers.Anthropic.AuthMethod = "token" appCfg.Providers.Anthropic.AuthMethod = "token"
case "openai": case "openai":
appCfg.Providers.OpenAI.AuthMethod = "token" appCfg.Providers.OpenAI.AuthMethod = "token"
case "gemini":
appCfg.Providers.Gemini.AuthMethod = "token"
} }
if err := config.SaveConfig(getConfigPath(), appCfg); err != nil { if err := config.SaveConfig(getConfigPath(), appCfg); err != nil {
fmt.Printf("Warning: could not update config: %v\n", err) fmt.Printf("Warning: could not update config: %v\n", err)
@ -925,6 +928,8 @@ func authLogoutCmd() {
appCfg.Providers.OpenAI.AuthMethod = "" appCfg.Providers.OpenAI.AuthMethod = ""
case "anthropic": case "anthropic":
appCfg.Providers.Anthropic.AuthMethod = "" appCfg.Providers.Anthropic.AuthMethod = ""
case "gemini":
appCfg.Providers.Gemini.AuthMethod = ""
} }
config.SaveConfig(getConfigPath(), appCfg) config.SaveConfig(getConfigPath(), appCfg)
} }
@ -940,6 +945,7 @@ func authLogoutCmd() {
if err == nil { if err == nil {
appCfg.Providers.OpenAI.AuthMethod = "" appCfg.Providers.OpenAI.AuthMethod = ""
appCfg.Providers.Anthropic.AuthMethod = "" appCfg.Providers.Anthropic.AuthMethod = ""
appCfg.Providers.Gemini.AuthMethod = ""
config.SaveConfig(getConfigPath(), appCfg) config.SaveConfig(getConfigPath(), appCfg)
} }

View file

@ -37,6 +37,8 @@ func providerDisplayName(provider string) string {
return "console.anthropic.com" return "console.anthropic.com"
case "openai": case "openai":
return "platform.openai.com" return "platform.openai.com"
case "gemini":
return "aistudio.google.com/apikey"
default: default:
return provider return provider
} }

View file

@ -281,8 +281,19 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) {
} }
} }
case "gemini", "google": case "gemini", "google":
if cfg.Providers.Gemini.APIKey != "" { if cfg.Providers.Gemini.APIKey != "" || cfg.Providers.Gemini.AuthMethod != "" {
apiKey = cfg.Providers.Gemini.APIKey if cfg.Providers.Gemini.AuthMethod == "token" && cfg.Providers.Gemini.APIKey == "" {
cred, err := auth.GetCredential("gemini")
if err != nil {
return nil, fmt.Errorf("loading auth credentials: %w", err)
}
if cred == nil {
return nil, fmt.Errorf("no credentials for gemini. Run: picoclaw auth login --provider gemini")
}
apiKey = cred.AccessToken
} else {
apiKey = cfg.Providers.Gemini.APIKey
}
apiBase = cfg.Providers.Gemini.APIBase apiBase = cfg.Providers.Gemini.APIBase
if apiBase == "" { if apiBase == "" {
apiBase = "https://generativelanguage.googleapis.com/v1beta" apiBase = "https://generativelanguage.googleapis.com/v1beta"
@ -378,8 +389,19 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) {
apiBase = "https://api.openai.com/v1" apiBase = "https://api.openai.com/v1"
} }
case (strings.Contains(lowerModel, "gemini") || strings.HasPrefix(model, "google/")) && cfg.Providers.Gemini.APIKey != "": case (strings.Contains(lowerModel, "gemini") || strings.HasPrefix(model, "google/")) && (cfg.Providers.Gemini.APIKey != "" || cfg.Providers.Gemini.AuthMethod != ""):
apiKey = cfg.Providers.Gemini.APIKey if cfg.Providers.Gemini.AuthMethod == "token" && cfg.Providers.Gemini.APIKey == "" {
cred, err := auth.GetCredential("gemini")
if err != nil {
return nil, fmt.Errorf("loading auth credentials: %w", err)
}
if cred == nil {
return nil, fmt.Errorf("no credentials for gemini. Run: picoclaw auth login --provider gemini")
}
apiKey = cred.AccessToken
} else {
apiKey = cfg.Providers.Gemini.APIKey
}
apiBase = cfg.Providers.Gemini.APIBase apiBase = cfg.Providers.Gemini.APIBase
proxy = cfg.Providers.Gemini.Proxy proxy = cfg.Providers.Gemini.Proxy
if apiBase == "" { if apiBase == "" {