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:
parent
e7f15afdd4
commit
c17c4744e4
3 changed files with 38 additions and 8 deletions
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 == "" {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue