feat: Add Oauth support while onboarding
This commit is contained in:
parent
3637a610b9
commit
9e37c7615a
2 changed files with 129 additions and 6 deletions
|
|
@ -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":
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,16 @@ 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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Setup struct {
|
type Setup struct {
|
||||||
ConfigPath string
|
ConfigPath string
|
||||||
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue