fix(anthropic): improve auth UX with clear setup-token vs API key choice

Both 'hermes model' and 'hermes setup model' now present a clear
two-option auth flow when no credentials are found:

  1. Claude Pro/Max subscription (setup-token)
     - Step-by-step instructions to run 'claude setup-token'
     - User pastes the resulting sk-ant-oat01-... token

  2. Anthropic API key (pay-per-token)
     - Link to console.anthropic.com/settings/keys
     - User pastes sk-ant-api03-... key

Also handles:
  - Auto-detection of existing Claude Code creds (~/.claude/.credentials.json)
  - Existing credentials shown with option to update
  - Consistent UX between 'hermes model' and 'hermes setup model'
This commit is contained in:
teknium1
2026-03-12 16:28:00 -07:00
parent 7086fde37e
commit 38aa47ad6c
2 changed files with 110 additions and 38 deletions

View File

@@ -1560,7 +1560,7 @@ def _model_flow_api_key_provider(config, provider_id, current_model=""):
def _model_flow_anthropic(config, current_model=""):
"""Flow for Anthropic provider — API key, setup-token, or Claude Code creds."""
"""Flow for Anthropic provider — setup-token, API key, or Claude Code creds."""
import os
from hermes_cli.auth import (
PROVIDER_REGISTRY, _prompt_model_selection, _save_model_choice,
@@ -1571,15 +1571,13 @@ def _model_flow_anthropic(config, current_model=""):
pconfig = PROVIDER_REGISTRY["anthropic"]
# Check for existing credentials (env vars or Claude Code)
# Check for existing credentials
existing_key = (
get_env_value("ANTHROPIC_API_KEY")
or os.getenv("ANTHROPIC_API_KEY", "")
or get_env_value("ANTHROPIC_TOKEN")
or os.getenv("ANTHROPIC_TOKEN", "")
)
# Check for Claude Code auto-discovery
cc_available = False
try:
from agent.anthropic_adapter import read_claude_code_credentials, is_claude_code_token_valid
@@ -1590,21 +1588,75 @@ def _model_flow_anthropic(config, current_model=""):
pass
if existing_key:
print(f" Anthropic key: {existing_key[:12]}... ✓")
elif cc_available:
print(" Claude Code credentials: ✓ (auto-detected from ~/.claude/.credentials.json)")
else:
print("No Anthropic credentials found.")
print(f" Anthropic credentials: {existing_key[:12]}... ✓")
print()
try:
new_key = input("ANTHROPIC_API_KEY (or Enter to cancel): ").strip()
update = input("Update credentials? [y/N]: ").strip().lower()
except (KeyboardInterrupt, EOFError):
update = ""
if update != "y":
pass # skip to model selection
else:
existing_key = "" # fall through to auth choice below
elif cc_available:
print(" Claude Code credentials: ✓ (auto-detected)")
print()
if not existing_key and not cc_available:
# No credentials — show auth method choice
print()
print(" Choose authentication method:")
print()
print(" 1. Claude Pro/Max subscription (setup-token)")
print(" 2. Anthropic API key (pay-per-token)")
print(" 3. Cancel")
print()
try:
choice = input(" Choice [1/2/3]: ").strip()
except (KeyboardInterrupt, EOFError):
print()
return
if not new_key:
print("Cancelled.")
if choice == "1":
print()
print(" To get a setup-token from your Claude subscription:")
print()
print(" 1. Install Claude Code: npm install -g @anthropic-ai/claude-code")
print(" 2. Run: claude setup-token")
print(" 3. Open the URL it prints in your browser")
print(" 4. Log in and click \"Authorize\"")
print(" 5. Paste the auth code back into Claude Code")
print(" 6. Copy the resulting sk-ant-oat01-... token")
print()
try:
token = input(" Paste setup-token here: ").strip()
except (KeyboardInterrupt, EOFError):
print()
return
if not token:
print(" Cancelled.")
return
save_env_value("ANTHROPIC_API_KEY", token)
print(" ✓ Setup-token saved.")
elif choice == "2":
print()
print(" Get an API key at: https://console.anthropic.com/settings/keys")
print()
try:
api_key = input(" API key (sk-ant-api03-...): ").strip()
except (KeyboardInterrupt, EOFError):
print()
return
if not api_key:
print(" Cancelled.")
return
save_env_value("ANTHROPIC_API_KEY", api_key)
print(" ✓ API key saved.")
else:
print(" No change.")
return
save_env_value("ANTHROPIC_API_KEY", new_key)
print("API key saved.")
print()
# Model selection

View File

@@ -1008,41 +1008,61 @@ def setup_model_provider(config: dict):
elif provider_idx == 8: # Anthropic
selected_provider = "anthropic"
print()
print_header("Anthropic API Key or Claude Code Credentials")
print_header("Anthropic Authentication")
from hermes_cli.auth import PROVIDER_REGISTRY
pconfig = PROVIDER_REGISTRY["anthropic"]
print_info(f"Provider: {pconfig.name}")
print_info("Accepts API keys (sk-ant-api-*) or setup-tokens (sk-ant-oat-*)")
print_info("Get an API key at: https://console.anthropic.com/")
print_info("Or run 'claude setup-token' to get a setup-token from Claude Code")
print()
# Check for Claude Code credential auto-discovery
from agent.anthropic_adapter import read_claude_code_credentials, is_claude_code_token_valid
cc_creds = read_claude_code_credentials()
if cc_creds and is_claude_code_token_valid(cc_creds):
print_success("Found valid Claude Code credentials (~/.claude/.credentials.json)")
if not prompt_yes_no("Use Claude Code credentials? (You can also enter an API key)", True):
if prompt_yes_no("Use these credentials?", True):
print_success("Using Claude Code subscription credentials")
else:
cc_creds = None
existing_key = get_env_value("ANTHROPIC_API_KEY") or get_env_value("ANTHROPIC_TOKEN")
if cc_creds and is_claude_code_token_valid(cc_creds):
# Use Claude Code creds — no need to prompt for a key
print_success("Using Claude Code subscription credentials")
elif existing_key:
print_info(f"Current: {existing_key[:12]}... (configured)")
if prompt_yes_no("Update key?", False):
api_key = prompt("Enter Anthropic API key or setup-token", password=True)
if api_key:
save_env_value("ANTHROPIC_API_KEY", api_key)
print_success("Anthropic key saved")
else:
api_key = prompt("Enter Anthropic API key or setup-token", password=True)
if api_key:
save_env_value("ANTHROPIC_API_KEY", api_key)
print_success("Anthropic key saved")
else:
print_warning("Skipped - agent won't work without an API key")
if not (cc_creds and is_claude_code_token_valid(cc_creds)):
if existing_key:
print_info(f"Current credentials: {existing_key[:12]}...")
if not prompt_yes_no("Update credentials?", False):
existing_key = None # skip — keep existing
if not existing_key and not (cc_creds and is_claude_code_token_valid(cc_creds)):
auth_choices = [
"Claude Pro/Max subscription (setup-token)",
"Anthropic API key (pay-per-token)",
]
auth_idx = prompt_choice("Choose authentication method:", auth_choices, 0)
if auth_idx == 0:
print()
print_info("To get a setup-token from your Claude subscription:")
print_info(" 1. Install Claude Code: npm install -g @anthropic-ai/claude-code")
print_info(" 2. Run: claude setup-token")
print_info(" 3. Open the URL it prints in your browser")
print_info(" 4. Log in and click \"Authorize\"")
print_info(" 5. Paste the auth code back into Claude Code")
print_info(" 6. Copy the resulting sk-ant-oat01-... token")
print()
token = prompt("Paste setup-token here", password=True)
if token:
save_env_value("ANTHROPIC_API_KEY", token)
print_success("Setup-token saved")
else:
print_warning("Skipped — agent won't work without credentials")
else:
print()
print_info("Get an API key at: https://console.anthropic.com/settings/keys")
print()
api_key = prompt("API key (sk-ant-api03-...)", password=True)
if api_key:
save_env_value("ANTHROPIC_API_KEY", api_key)
print_success("API key saved")
else:
print_warning("Skipped — agent won't work without credentials")
# Clear custom endpoint vars if switching
if existing_custom: