From c17c4744e47aebfcfc0b1a706c4f328f5c96dec2 Mon Sep 17 00:00:00 2001 From: Atharva Date: Mon, 16 Feb 2026 17:18:37 +0530 Subject: [PATCH] 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 --- cmd/picoclaw/main.go | 14 ++++++++++---- pkg/auth/token.go | 2 ++ pkg/providers/http_provider.go | 30 ++++++++++++++++++++++++++---- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/cmd/picoclaw/main.go b/cmd/picoclaw/main.go index 10b53948b..55a3fcae2 100644 --- a/cmd/picoclaw/main.go +++ b/cmd/picoclaw/main.go @@ -789,13 +789,14 @@ func authHelp() { fmt.Println(" status Show current auth status") fmt.Println() fmt.Println("Login options:") - fmt.Println(" --provider Provider to login with (openai, anthropic)") + fmt.Println(" --provider Provider to login with (openai, anthropic, gemini)") fmt.Println(" --device-code Use device code flow (for headless environments)") fmt.Println() fmt.Println("Examples:") fmt.Println(" picoclaw auth login --provider openai") fmt.Println(" picoclaw auth login --provider openai --device-code") 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 status") } @@ -819,18 +820,18 @@ func authLoginCmd() { if provider == "" { fmt.Println("Error: --provider is required") - fmt.Println("Supported providers: openai, anthropic") + fmt.Println("Supported providers: openai, anthropic, gemini") return } switch provider { case "openai": authLoginOpenAI(useDeviceCode) - case "anthropic": + case "anthropic", "gemini": authLoginPasteToken(provider) default: 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" case "openai": appCfg.Providers.OpenAI.AuthMethod = "token" + case "gemini": + appCfg.Providers.Gemini.AuthMethod = "token" } if err := config.SaveConfig(getConfigPath(), appCfg); err != nil { fmt.Printf("Warning: could not update config: %v\n", err) @@ -925,6 +928,8 @@ func authLogoutCmd() { appCfg.Providers.OpenAI.AuthMethod = "" case "anthropic": appCfg.Providers.Anthropic.AuthMethod = "" + case "gemini": + appCfg.Providers.Gemini.AuthMethod = "" } config.SaveConfig(getConfigPath(), appCfg) } @@ -940,6 +945,7 @@ func authLogoutCmd() { if err == nil { appCfg.Providers.OpenAI.AuthMethod = "" appCfg.Providers.Anthropic.AuthMethod = "" + appCfg.Providers.Gemini.AuthMethod = "" config.SaveConfig(getConfigPath(), appCfg) } diff --git a/pkg/auth/token.go b/pkg/auth/token.go index a5a13ff03..c43c2f844 100644 --- a/pkg/auth/token.go +++ b/pkg/auth/token.go @@ -37,6 +37,8 @@ func providerDisplayName(provider string) string { return "console.anthropic.com" case "openai": return "platform.openai.com" + case "gemini": + return "aistudio.google.com/apikey" default: return provider } diff --git a/pkg/providers/http_provider.go b/pkg/providers/http_provider.go index 4cf2c6db2..ab297ed27 100644 --- a/pkg/providers/http_provider.go +++ b/pkg/providers/http_provider.go @@ -281,8 +281,19 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { } } case "gemini", "google": - if cfg.Providers.Gemini.APIKey != "" { - apiKey = cfg.Providers.Gemini.APIKey + if cfg.Providers.Gemini.APIKey != "" || cfg.Providers.Gemini.AuthMethod != "" { + 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 if apiBase == "" { apiBase = "https://generativelanguage.googleapis.com/v1beta" @@ -378,8 +389,19 @@ func CreateProvider(cfg *config.Config) (LLMProvider, error) { apiBase = "https://api.openai.com/v1" } - case (strings.Contains(lowerModel, "gemini") || strings.HasPrefix(model, "google/")) && cfg.Providers.Gemini.APIKey != "": - apiKey = cfg.Providers.Gemini.APIKey + case (strings.Contains(lowerModel, "gemini") || strings.HasPrefix(model, "google/")) && (cfg.Providers.Gemini.APIKey != "" || cfg.Providers.Gemini.AuthMethod != ""): + 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 proxy = cfg.Providers.Gemini.Proxy if apiBase == "" {