@@ -0,0 +1,103 @@
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"
17+var 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.
22+The platform should be provided as an argument.
23+Available platforms are: claude.`,
25+# Authenticate with Claude Code Max
28+ RunE: func(cmd *cobra.Command, args []string) error {
30+ return fmt.Errorf("wrong number of arguments")
32+ if len(args) == 0 || args[0] == "" {
36+ app, err := setupAppWithProgressBar(cmd)
43+ case "anthropic", "claude":
46+ return fmt.Errorf("unknown platform: %s", args[0])
51+func loginClaude() error {
52+ ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
59+ verifier, challenge, err := claude.GetChallenge()
63+ url, err := claude.AuthorizeURL(verifier, challenge)
67+ fmt.Println("Open the following URL and follow the instructions to authenticate with Claude Code Max:")
69+ fmt.Println(lipgloss.NewStyle().Hyperlink(url, "id=claude").Render(url))
71+ fmt.Println("Press enter to continue...")
72+ if _, err := fmt.Scanln(); err != nil {
76+ fmt.Println("Now paste and code from Anthropic and press enter...")
81+ _, _ = fmt.Scanln(&code)
82+ code = strings.TrimSpace(code)
86+ fmt.Println("Exchanging authorization code...")
87+ token, err := claude.ExchangeToken(ctx, code, verifier)
94+ cfg.SetConfigField("providers.anthropic.api_key", token.AccessToken),
95+ cfg.SetConfigField("providers.anthropic.oauth", token),
101+ fmt.Println("You're now authenticated with Claude Code Max!")