@@ -16,6 +16,7 @@ import (
1616 "github.com/charmbracelet/crush/internal/csync"
1717 "github.com/charmbracelet/crush/internal/env"
1818 "github.com/charmbracelet/crush/internal/oauth"
19+ "github.com/charmbracelet/crush/internal/oauth/claude"
1920 "github.com/invopop/jsonschema"
2021 "github.com/tidwall/sjson"
2122)
@@ -468,6 +469,43 @@ func (c *Config) SetConfigField(key string, value any) error {
468469 return nil
469470}
470471
472+func (c *Config) RefreshOAuthToken(ctx context.Context, providerID string) error {
473+ providerConfig, exists := c.Providers.Get(providerID)
474+ if !exists {
475+ return fmt.Errorf("provider %s not found", providerID)
476+ }
477+
478+ if providerConfig.OAuthToken == nil {
479+ return fmt.Errorf("provider %s does not have an OAuth token", providerID)
480+ }
481+
482+ // Only Anthropic provider uses OAuth for now
483+ if providerID != string(catwalk.InferenceProviderAnthropic) {
484+ return fmt.Errorf("OAuth refresh not supported for provider %s", providerID)
485+ }
486+
487+ newToken, err := claude.RefreshToken(ctx, providerConfig.OAuthToken.RefreshToken)
488+ if err != nil {
489+ return fmt.Errorf("failed to refresh OAuth token for provider %s: %w", providerID, err)
490+ }
491+
492+ slog.Info("Successfully refreshed OAuth token in background", "provider", providerID)
493+ providerConfig.OAuthToken = newToken
494+ providerConfig.APIKey = fmt.Sprintf("Bearer %s", newToken.AccessToken)
495+ providerConfig.SetupClaudeCode()
496+
497+ c.Providers.Set(providerID, providerConfig)
498+
499+ if err := cmp.Or(
500+ c.SetConfigField(fmt.Sprintf("providers.%s.api_key", providerID), newToken.AccessToken),
501+ c.SetConfigField(fmt.Sprintf("providers.%s.oauth", providerID), newToken),
502+ ); err != nil {
503+ return fmt.Errorf("failed to persist refreshed token: %w", err)
504+ }
505+
506+ return nil
507+}
508+
471509func (c *Config) SetProviderAPIKey(providerID string, apiKey any) error {
472510 var providerConfig ProviderConfig
473511 var exists bool