14 "github.com/charmbracelet/crush/internal/oauth"
17const clientId = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"
19// AuthorizeURL returns the Claude Code Max OAuth2 authorization URL.
20func AuthorizeURL(verifier, challenge string) (string, error) {
21 u, err := url.Parse("https://claude.ai/oauth/authorize")
26 q.Set("response_type", "code")
27 q.Set("client_id", clientId)
28 q.Set("redirect_uri", "https://console.anthropic.com/oauth/code/callback")
29 q.Set("scope", "org:create_api_key user:profile user:inference")
30 q.Set("code_challenge", challenge)
31 q.Set("code_challenge_method", "S256")
32 q.Set("state", verifier)
33 u.RawQuery = q.Encode()
34 return u.String(), nil
37// ExchangeToken exchanges the authorization code for an OAuth2 token.
38func ExchangeToken(ctx context.Context, code, verifier string) (*oauth.Token, error) {
39 code = strings.TrimSpace(code)
40 parts := strings.SplitN(code, "#", 2)
47 reqBody := map[string]string{
50 "grant_type": "authorization_code",
51 "client_id": clientId,
52 "redirect_uri": "https://console.anthropic.com/oauth/code/callback",
53 "code_verifier": verifier,
56 resp, err := request(ctx, "POST", "https://console.anthropic.com/v1/oauth/token", reqBody)
60 defer resp.Body.Close()
62 body, err := io.ReadAll(resp.Body)
67 if resp.StatusCode != http.StatusOK {
68 return nil, fmt.Errorf("claude code max: failed to exchange token: status %d body %q", resp.StatusCode, string(body))
72 if err := json.Unmarshal(body, &token); err != nil {
79// RefreshToken refreshes the OAuth2 token using the provided refresh token.
80func RefreshToken(ctx context.Context, refreshToken string) (*oauth.Token, error) {
81 reqBody := map[string]string{
82 "grant_type": "refresh_token",
83 "refresh_token": refreshToken,
84 "client_id": clientId,
87 resp, err := request(ctx, "POST", "https://console.anthropic.com/v1/oauth/token", reqBody)
91 defer resp.Body.Close()
93 body, err := io.ReadAll(resp.Body)
98 if resp.StatusCode != http.StatusOK {
99 return nil, fmt.Errorf("claude code max: failed to refresh token: status %d body %q", resp.StatusCode, string(body))
102 var token oauth.Token
103 if err := json.Unmarshal(body, &token); err != nil {
110func request(ctx context.Context, method, url string, body any) (*http.Response, error) {
111 date, err := json.Marshal(body)
116 req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(date))
121 req.Header.Set("Content-Type", "application/json")
122 req.Header.Set("User-Agent", "anthropic")
124 client := &http.Client{Timeout: 30 * time.Second}
125 return client.Do(req)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list