feat: Add Oauth support while onboarding

This commit is contained in:
Yoftahe 2026-02-23 16:24:47 +03:00
parent 3637a610b9
commit 9e37c7615a
2 changed files with 129 additions and 6 deletions

View file

@ -91,6 +91,14 @@ func GetChannelInfo(channel string) (ChannelInfo, bool) {
return info, ok return info, ok
} }
func isOAuthProvider(provider string) bool {
switch strings.ToLower(provider) {
case "openai", "anthropic", "google-antigravity", "antigravity":
return true
}
return false
}
// QuestionType defines the type of response expected for a question. // QuestionType defines the type of response expected for a question.
type QuestionType string type QuestionType string
@ -415,6 +423,17 @@ func BuildSessionRegistry(cfg *config.Config) SessionRegistry {
switch q.ID { switch q.ID {
case "provider": case "provider":
registry.Sessions[i].Questions[j].Options = provOptions registry.Sessions[i].Questions[j].Options = provOptions
case "provider_auth_method":
// Determine auth options based on selected provider
selectedProvider := cfg.Agents.Defaults.Provider
if selectedProvider == "" {
selectedProvider = registry.Answers["provider"]
}
if isOAuthProvider(selectedProvider) {
registry.Sessions[i].Questions[j].Options = []string{"oauth_login", "api_key"}
} else {
registry.Sessions[i].Questions[j].Options = []string{"api_key"}
}
case "channel_select": case "channel_select":
registry.Sessions[i].Questions[j].Options = channelOptions registry.Sessions[i].Questions[j].Options = channelOptions
case "model_select": case "model_select":

View file

@ -11,6 +11,7 @@ import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/sipeed/picoclaw/pkg/auth"
"github.com/sipeed/picoclaw/pkg/config" "github.com/sipeed/picoclaw/pkg/config"
) )
@ -19,6 +20,7 @@ type Setup struct {
Cfg *config.Config Cfg *config.Config
Steps []string Steps []string
Confirmed bool Confirmed bool
OAuthProvider string
} }
func NewSetup(configPath string) (*Setup, error) { func NewSetup(configPath string) (*Setup, error) {
@ -53,6 +55,44 @@ func (s *Setup) Run() error {
return fmt.Errorf("failed to save config: %w", err) return fmt.Errorf("failed to save config: %w", err)
} }
if s.OAuthProvider != "" {
fmt.Println("\nRunning OAuth login for", s.OAuthProvider, "...")
if err := runOAuthLogin(s.OAuthProvider); err != nil {
return fmt.Errorf("oauth login failed: %w", err)
}
}
return nil
}
func runOAuthLogin(provider string) error {
switch strings.ToLower(provider) {
case "openai":
cfg := auth.OpenAIOAuthConfig()
cred, err := auth.LoginBrowser(cfg)
if err != nil {
return fmt.Errorf("openai login failed: %w", err)
}
if err := auth.SetCredential("openai", cred); err != nil {
return fmt.Errorf("failed to save credentials: %w", err)
}
fmt.Println("OpenAI login successful!")
case "anthropic":
fmt.Println("Anthropic OAuth not available. Please run: picoclaw auth login --provider anthropic")
case "google-antigravity", "antigravity":
cfg := auth.GoogleAntigravityOAuthConfig()
cred, err := auth.LoginBrowser(cfg)
if err != nil {
return fmt.Errorf("google-antigravity login failed: %w", err)
}
cred.Provider = "google-antigravity"
if err := auth.SetCredential("google-antigravity", cred); err != nil {
return fmt.Errorf("failed to save credentials: %w", err)
}
fmt.Println("Google Antigravity login successful!")
default:
return fmt.Errorf("unsupported OAuth provider: %s", provider)
}
return nil return nil
} }
@ -492,7 +532,11 @@ func (t *tuiModel) validateSession(questions []Question) []string {
var missing []string var missing []string
for _, q := range questions { for _, q := range questions {
// Skip optional fields // Skip optional fields
if q.ID == "provider_api_key" || q.ID == "provider_api_base" { if q.ID == "provider_api_base" {
continue
}
// Skip provider_api_key if using oauth_login
if q.ID == "provider_api_key" && t.answers["provider_auth_method"] == "oauth_login" {
continue continue
} }
// Skip channel token fields - they're optional based on channel selection // Skip channel token fields - they're optional based on channel selection
@ -559,6 +603,11 @@ func (t *tuiModel) saveAnswer(q Question) {
if q.ID == "channel_select" { if q.ID == "channel_select" {
t.updateChannelQuestionPrompt() t.updateChannelQuestionPrompt()
} }
// Update provider auth method options when provider is selected
if q.ID == "provider" {
t.updateProviderAuthOptions()
}
} }
func (t *tuiModel) updateChannelQuestionPrompt() { func (t *tuiModel) updateChannelQuestionPrompt() {
@ -619,6 +668,33 @@ func getChannelFieldIDs(channel string) []string {
return nil return nil
} }
func (t *tuiModel) updateProviderAuthOptions() {
provider := t.answers["provider"]
if provider == "" {
return
}
for i := range t.registry.Sessions {
if t.registry.Sessions[i].ID == "provider" {
for j := range t.registry.Sessions[i].Questions {
q := &t.registry.Sessions[i].Questions[j]
if q.ID == "provider_auth_method" {
if isOAuthProvider(provider) {
q.Options = []string{"oauth_login", "api_key"}
} else {
q.Options = []string{"api_key"}
}
// Reset selection if out of bounds
if idx, ok := t.selIdx[q.ID]; ok && idx >= len(q.Options) {
t.selIdx[q.ID] = 0
}
}
}
break
}
}
}
func (t *tuiModel) applyAnswersToConfig() { func (t *tuiModel) applyAnswersToConfig() {
cfg := t.setup.Cfg cfg := t.setup.Cfg
@ -634,6 +710,29 @@ func (t *tuiModel) applyAnswersToConfig() {
cfg.Agents.Defaults.RestrictToWorkspace = (val == "yes") cfg.Agents.Defaults.RestrictToWorkspace = (val == "yes")
case "provider": case "provider":
cfg.Agents.Defaults.Provider = val cfg.Agents.Defaults.Provider = val
// Set auth method if oauth_login was selected
if t.answers["provider_auth_method"] == "oauth_login" {
authMethod := "oauth"
prov := strings.ToLower(val)
switch prov {
case "openai":
cfg.Providers.OpenAI.AuthMethod = authMethod
case "anthropic":
cfg.Providers.Anthropic.AuthMethod = authMethod
case "google-antigravity", "antigravity":
cfg.Providers.Antigravity.AuthMethod = authMethod
}
// Also update model list
for i := range cfg.ModelList {
pfx := config.ParseProtocol(cfg.ModelList[i].Model)
if pfx == "" {
pfx = "openai"
}
if pfx == prov {
cfg.ModelList[i].AuthMethod = authMethod
}
}
}
case "model_select": case "model_select":
if val != "custom" { if val != "custom" {
cfg.Agents.Defaults.Model = val cfg.Agents.Defaults.Model = val
@ -741,7 +840,7 @@ func (t *tuiModel) applyAnswersToConfig() {
} }
case "provider_api_key": case "provider_api_key":
prov := t.answers["provider"] prov := t.answers["provider"]
if prov != "" { if prov != "" && t.answers["provider_auth_method"] == "api_key" {
SetProviderCredential(cfg, prov, "api_key", val) SetProviderCredential(cfg, prov, "api_key", val)
} }
case "provider_api_base": case "provider_api_base":
@ -749,6 +848,11 @@ func (t *tuiModel) applyAnswersToConfig() {
if prov != "" { if prov != "" {
SetProviderCredential(cfg, prov, "api_base", val) SetProviderCredential(cfg, prov, "api_base", val)
} }
case "provider_auth_method":
prov := t.answers["provider"]
if prov != "" && val == "oauth_login" {
t.setup.OAuthProvider = prov
}
} }
} }
} }