1package cmd
2
3import (
4 "cmp"
5 "context"
6 "fmt"
7 "os"
8 "os/signal"
9 "strings"
10
11 "charm.land/lipgloss/v2"
12 "github.com/charmbracelet/crush/internal/config"
13 "github.com/charmbracelet/crush/internal/oauth/claude"
14 "github.com/spf13/cobra"
15)
16
17var loginCmd = &cobra.Command{
18 Aliases: []string{"auth"},
19 Use: "login [platform]",
20 Short: "Login Crush to a platform",
21 Long: `Login Crush to a specified platform.
22The platform should be provided as an argument.
23Available platforms are: claude.`,
24 Example: `
25# Authenticate with Claude Code Max
26crush login claude
27 `,
28 ValidArgs: []cobra.Completion{
29 "claude",
30 "anthropic",
31 },
32 Args: cobra.ExactArgs(1),
33 RunE: func(cmd *cobra.Command, args []string) error {
34 if len(args) > 1 {
35 return fmt.Errorf("wrong number of arguments")
36 }
37 if len(args) == 0 || args[0] == "" {
38 return cmd.Help()
39 }
40
41 app, err := setupAppWithProgressBar(cmd)
42 if err != nil {
43 return err
44 }
45 defer app.Shutdown()
46
47 switch args[0] {
48 case "anthropic", "claude":
49 return loginClaude()
50 default:
51 return fmt.Errorf("unknown platform: %s", args[0])
52 }
53 },
54}
55
56func loginClaude() error {
57 ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
58 go func() {
59 <-ctx.Done()
60 cancel()
61 os.Exit(1)
62 }()
63
64 verifier, challenge, err := claude.GetChallenge()
65 if err != nil {
66 return err
67 }
68 url, err := claude.AuthorizeURL(verifier, challenge)
69 if err != nil {
70 return err
71 }
72 fmt.Println("Open the following URL and follow the instructions to authenticate with Claude Code Max:")
73 fmt.Println()
74 fmt.Println(lipgloss.NewStyle().Hyperlink(url, "id=claude").Render(url))
75 fmt.Println()
76 fmt.Println("Press enter to continue...")
77 if _, err := fmt.Scanln(); err != nil {
78 return err
79 }
80
81 fmt.Println("Now paste and code from Anthropic and press enter...")
82 fmt.Println()
83 fmt.Print("> ")
84 var code string
85 for code == "" {
86 _, _ = fmt.Scanln(&code)
87 code = strings.TrimSpace(code)
88 }
89
90 fmt.Println()
91 fmt.Println("Exchanging authorization code...")
92 token, err := claude.ExchangeToken(ctx, code, verifier)
93 if err != nil {
94 return err
95 }
96
97 cfg := config.Get()
98 if err := cmp.Or(
99 cfg.SetConfigField("providers.anthropic.api_key", token.AccessToken),
100 cfg.SetConfigField("providers.anthropic.oauth", token),
101 ); err != nil {
102 return err
103 }
104
105 fmt.Println()
106 fmt.Println("You're now authenticated with Claude Code Max!")
107 return nil
108}