feat: add subscription OAuth option to Anthropic onboarding
This commit is contained in:
parent
d6e609055a
commit
c31af80182
1 changed files with 104 additions and 13 deletions
|
|
@ -15,6 +15,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/sipeed/picoclaw/pkg/auth"
|
||||
"github.com/sipeed/picoclaw/pkg/config"
|
||||
)
|
||||
|
||||
|
|
@ -22,7 +23,9 @@ import (
|
|||
//go:embed workspace
|
||||
var embeddedFiles embed.FS
|
||||
|
||||
// providerChoice holds the details for a user-selected provider
|
||||
// providerChoice holds the details for a user-selected provider.
|
||||
// If onboardFunc is set, it handles the entire provider setup (auth + config)
|
||||
// and needsAPIKey/keyPrompt/validateFunc are ignored.
|
||||
type providerChoice struct {
|
||||
name string
|
||||
modelName string
|
||||
|
|
@ -30,6 +33,7 @@ type providerChoice struct {
|
|||
keyPrompt string
|
||||
validateURL string
|
||||
validateFunc func(apiKey string) *http.Request
|
||||
onboardFunc func(reader *bufio.Reader, cfg *config.Config) (verified bool)
|
||||
}
|
||||
|
||||
var providerChoices = []providerChoice{
|
||||
|
|
@ -52,15 +56,7 @@ var providerChoices = []providerChoice{
|
|||
{
|
||||
name: "Anthropic",
|
||||
modelName: "claude-sonnet-4.6",
|
||||
needsAPIKey: true,
|
||||
keyPrompt: "Enter your Anthropic API key: ",
|
||||
validateURL: "https://api.anthropic.com/v1/models",
|
||||
validateFunc: func(apiKey string) *http.Request {
|
||||
req, _ := http.NewRequest("GET", "https://api.anthropic.com/v1/models", nil)
|
||||
req.Header.Set("x-api-key", apiKey)
|
||||
req.Header.Set("anthropic-version", "2023-06-01")
|
||||
return req
|
||||
},
|
||||
onboardFunc: onboardAnthropic,
|
||||
},
|
||||
{
|
||||
name: "OpenAI",
|
||||
|
|
@ -88,6 +84,97 @@ var providerChoices = []providerChoice{
|
|||
},
|
||||
}
|
||||
|
||||
// onboardAnthropic handles Anthropic provider setup during onboarding,
|
||||
// offering Claude Max/Pro subscription (OAuth) or manual API key entry.
|
||||
func onboardAnthropic(reader *bufio.Reader, cfg *config.Config) (verified bool) {
|
||||
fmt.Println("\nHow would you like to authenticate with Anthropic?")
|
||||
fmt.Println(" 1. Claude Max/Pro subscription (OAuth — free inference) [default]")
|
||||
fmt.Println(" 2. API key (pay-per-use)")
|
||||
fmt.Print("\nEnter choice [1]: ")
|
||||
|
||||
input, _ := reader.ReadString('\n')
|
||||
input = strings.TrimSpace(input)
|
||||
if input == "" {
|
||||
input = "1"
|
||||
}
|
||||
|
||||
switch input {
|
||||
case "1":
|
||||
return onboardAnthropicOAuth(cfg)
|
||||
case "2":
|
||||
return onboardAnthropicAPIKey(reader, cfg)
|
||||
default:
|
||||
fmt.Printf("Unknown choice %q, using subscription login.\n", input)
|
||||
return onboardAnthropicOAuth(cfg)
|
||||
}
|
||||
}
|
||||
|
||||
// onboardAnthropicOAuth performs the Claude Max/Pro OAuth flow during onboarding.
|
||||
func onboardAnthropicOAuth(cfg *config.Config) bool {
|
||||
cred, err := auth.LoginAnthropicOAuth(auth.AnthropicOAuthMax)
|
||||
if err != nil {
|
||||
fmt.Printf(" [!] OAuth login failed: %v\n", err)
|
||||
fmt.Println(" You can try again later with: picoclaw auth login --provider anthropic")
|
||||
return false
|
||||
}
|
||||
|
||||
if err := auth.SetCredential("anthropic", cred); err != nil {
|
||||
fmt.Printf(" [!] Failed to save credentials: %v\n", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Update config to use OAuth
|
||||
for i := range cfg.ModelList {
|
||||
if isAnthropicModel(cfg.ModelList[i].Model) {
|
||||
cfg.ModelList[i].AuthMethod = "oauth"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf(" [\u2713] Anthropic login successful")
|
||||
if cred.Email != "" {
|
||||
fmt.Printf(" (%s)", cred.Email)
|
||||
}
|
||||
if cred.SubscriptionType != "" {
|
||||
fmt.Printf(" [%s]", cred.SubscriptionType)
|
||||
}
|
||||
fmt.Println()
|
||||
return true
|
||||
}
|
||||
|
||||
// onboardAnthropicAPIKey collects and validates an Anthropic API key during onboarding.
|
||||
func onboardAnthropicAPIKey(reader *bufio.Reader, cfg *config.Config) bool {
|
||||
fmt.Print("Enter your Anthropic API key: ")
|
||||
apiKey, _ := reader.ReadString('\n')
|
||||
apiKey = strings.TrimSpace(apiKey)
|
||||
|
||||
if apiKey == "" {
|
||||
fmt.Println(" [!] No API key provided")
|
||||
return false
|
||||
}
|
||||
|
||||
// Set the API key on the matching model entry
|
||||
for i := range cfg.ModelList {
|
||||
if cfg.ModelList[i].ModelName == "claude-sonnet-4.6" {
|
||||
cfg.ModelList[i].APIKey = apiKey
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the key
|
||||
choice := providerChoice{
|
||||
name: "Anthropic",
|
||||
validateFunc: func(key string) *http.Request {
|
||||
req, _ := http.NewRequest("GET", "https://api.anthropic.com/v1/models", nil)
|
||||
req.Header.Set("x-api-key", key)
|
||||
req.Header.Set("anthropic-version", "2023-06-01")
|
||||
return req
|
||||
},
|
||||
}
|
||||
verifyAPIKey(choice, apiKey)
|
||||
return true
|
||||
}
|
||||
|
||||
func onboard() {
|
||||
for _, arg := range os.Args[2:] {
|
||||
switch arg {
|
||||
|
|
@ -122,7 +209,7 @@ func onboard() {
|
|||
fmt.Println("Choose your AI provider:")
|
||||
fmt.Println(" 1. Ollama (local, free — no API key needed) [default]")
|
||||
fmt.Println(" 2. OpenRouter (100+ models, one API key)")
|
||||
fmt.Println(" 3. Anthropic (Claude)")
|
||||
fmt.Println(" 3. Anthropic (Claude — subscription or API key)")
|
||||
fmt.Println(" 4. OpenAI (GPT)")
|
||||
fmt.Println(" 5. DeepSeek")
|
||||
fmt.Println(" 6. Skip — I'll configure manually")
|
||||
|
|
@ -159,11 +246,15 @@ func onboard() {
|
|||
choiceIdx = 0
|
||||
}
|
||||
|
||||
var customOnboard bool
|
||||
if choiceIdx >= 0 {
|
||||
choice := providerChoices[choiceIdx]
|
||||
cfg.Agents.Defaults.Model = choice.modelName
|
||||
|
||||
if choice.needsAPIKey {
|
||||
if choice.onboardFunc != nil {
|
||||
customOnboard = true
|
||||
choice.onboardFunc(reader, cfg)
|
||||
} else if choice.needsAPIKey {
|
||||
fmt.Print(choice.keyPrompt)
|
||||
apiKey, _ = reader.ReadString('\n')
|
||||
apiKey = strings.TrimSpace(apiKey)
|
||||
|
|
@ -193,7 +284,7 @@ func onboard() {
|
|||
fmt.Printf(" [\u2713] Config written to %s\n", configPath)
|
||||
fmt.Printf(" [\u2713] Workspace initialized\n")
|
||||
|
||||
if choiceIdx >= 0 {
|
||||
if choiceIdx >= 0 && !customOnboard {
|
||||
choice := providerChoices[choiceIdx]
|
||||
if choiceIdx == 0 {
|
||||
// Ollama: check if running
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue