Files
mystuff/devel/crush/patches/patch-internal_cmd_login_go
2026-07-08 11:07:34 +02:00

121 lines
3.2 KiB
Plaintext

--- internal/cmd/login.go
+++ internal/cmd/login.go
@@ -6,12 +6,14 @@
"fmt"
"os"
"os/signal"
+ "strings"
"charm.land/lipgloss/v2"
"github.com/charmbracelet/crush/internal/client"
"github.com/charmbracelet/crush/internal/clipboard"
"github.com/charmbracelet/crush/internal/config"
"github.com/charmbracelet/crush/internal/oauth"
+ "github.com/charmbracelet/crush/internal/oauth/claude"
"github.com/charmbracelet/crush/internal/oauth/copilot"
"github.com/charmbracelet/crush/internal/oauth/hyper"
"github.com/charmbracelet/x/ansi"
@@ -25,11 +27,14 @@
Short: "Login Crush to a platform",
Long: `Login Crush to a specified platform.
The platform should be provided as an argument.
-Available platforms are: hyper, copilot.`,
+Available platforms are: hyper, claude, copilot.`,
Example: `
# Authenticate with Charm Hyper
crush login
+# Authenticate with Claude Code Max
+crush login claude
+
# Authenticate with GitHub Copilot
crush login copilot
@@ -38,6 +43,8 @@
`,
ValidArgs: []cobra.Completion{
"hyper",
+ "claude",
+ "anthropic",
"copilot",
"github",
"github-copilot",
@@ -64,6 +71,8 @@
switch provider {
case "hyper":
return loginHyper(c, ws.ID, force)
+ case "anthropic", "claude":
+ return loginClaude(c, ws.ID, force)
case "copilot", "github", "github-copilot":
return loginCopilot(c, ws.ID, force)
default:
@@ -140,6 +149,68 @@
fmt.Println()
fmt.Println("You're now authenticated with Hyper!")
+ return nil
+}
+
+func loginClaude(c *client.Client, wsID string, force bool) error {
+ ctx := getLoginContext()
+
+ if !force {
+ cfg, err := c.GetConfig(ctx, wsID)
+ if err == nil && cfg != nil {
+ if pc, ok := cfg.Providers.Get("anthropic"); ok && pc.OAuthToken != nil {
+ fmt.Println("You are already logged in to Claude Code Max.")
+ fmt.Println("Use --force to re-authenticate.")
+ return nil
+ }
+ }
+ }
+
+ verifier, challenge, err := claude.GetChallenge()
+ if err != nil {
+ return err
+ }
+ authURL, err := claude.AuthorizeURL(verifier, challenge)
+ if err != nil {
+ return err
+ }
+
+ fmt.Println("Open the following URL to authorize your Claude Code Max subscription:")
+ fmt.Println()
+ fmt.Println(lipgloss.NewStyle().Hyperlink(authURL, "id=claude").Render(authURL))
+ fmt.Println()
+ fmt.Println("Press enter to open in your browser...")
+ waitEnter()
+ if err := browser.OpenURL(authURL); err != nil {
+ fmt.Println("Could not open the URL. Open it manually in your browser.")
+ }
+
+ fmt.Println()
+ fmt.Println("After authorizing, paste the code shown on the page and press enter:")
+ fmt.Println()
+ fmt.Print("> ")
+ var code string
+ for code == "" {
+ _, _ = fmt.Scan(&code)
+ code = strings.TrimSpace(code)
+ }
+
+ fmt.Println()
+ fmt.Println("Exchanging authorization code...")
+ token, err := claude.ExchangeToken(ctx, code, verifier)
+ if err != nil {
+ return err
+ }
+
+ if err := cmp.Or(
+ c.SetConfigField(ctx, wsID, config.ScopeGlobal, "providers.anthropic.api_key", token.AccessToken),
+ c.SetConfigField(ctx, wsID, config.ScopeGlobal, "providers.anthropic.oauth", token),
+ ); err != nil {
+ return err
+ }
+
+ fmt.Println()
+ fmt.Println("You're now authenticated with Claude Code Max!")
return nil
}