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=""): 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 import os
from hermes_cli.auth import ( from hermes_cli.auth import (
PROVIDER_REGISTRY, _prompt_model_selection, _save_model_choice, PROVIDER_REGISTRY, _prompt_model_selection, _save_model_choice,
@ -1571,15 +1571,13 @@ def _model_flow_anthropic(config, current_model=""):
pconfig = PROVIDER_REGISTRY["anthropic"] pconfig = PROVIDER_REGISTRY["anthropic"]
# Check for existing credentials (env vars or Claude Code) # Check for existing credentials
existing_key = ( existing_key = (
get_env_value("ANTHROPIC_API_KEY") get_env_value("ANTHROPIC_API_KEY")
or os.getenv("ANTHROPIC_API_KEY", "") or os.getenv("ANTHROPIC_API_KEY", "")
or get_env_value("ANTHROPIC_TOKEN") or get_env_value("ANTHROPIC_TOKEN")
or os.getenv("ANTHROPIC_TOKEN", "") or os.getenv("ANTHROPIC_TOKEN", "")
) )
# Check for Claude Code auto-discovery
cc_available = False cc_available = False
try: try:
from agent.anthropic_adapter import read_claude_code_credentials, is_claude_code_token_valid 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 pass
if existing_key: if existing_key:
print(f" Anthropic key: {existing_key[:12]}... ✓") print(f" Anthropic credentials: {existing_key[:12]}... ✓")
elif cc_available: print()
print(" Claude Code credentials: ✓ (auto-detected from ~/.claude/.credentials.json)")
else:
print("No Anthropic credentials found.")
try: 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): except (KeyboardInterrupt, EOFError):
print() print()
return return
if not new_key:
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.") print(" Cancelled.")
return return
save_env_value("ANTHROPIC_API_KEY", new_key) save_env_value("ANTHROPIC_API_KEY", token)
print("API key saved.") 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
print() print()
# Model selection # Model selection

View file

@ -1008,41 +1008,61 @@ def setup_model_provider(config: dict):
elif provider_idx == 8: # Anthropic elif provider_idx == 8: # Anthropic
selected_provider = "anthropic" selected_provider = "anthropic"
print() print()
print_header("Anthropic API Key or Claude Code Credentials") print_header("Anthropic Authentication")
from hermes_cli.auth import PROVIDER_REGISTRY from hermes_cli.auth import PROVIDER_REGISTRY
pconfig = PROVIDER_REGISTRY["anthropic"] 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 # Check for Claude Code credential auto-discovery
from agent.anthropic_adapter import read_claude_code_credentials, is_claude_code_token_valid from agent.anthropic_adapter import read_claude_code_credentials, is_claude_code_token_valid
cc_creds = read_claude_code_credentials() cc_creds = read_claude_code_credentials()
if cc_creds and is_claude_code_token_valid(cc_creds): if cc_creds and is_claude_code_token_valid(cc_creds):
print_success("Found valid Claude Code credentials (~/.claude/.credentials.json)") 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 cc_creds = None
existing_key = get_env_value("ANTHROPIC_API_KEY") or get_env_value("ANTHROPIC_TOKEN") 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 if not (cc_creds and is_claude_code_token_valid(cc_creds)):
print_success("Using Claude Code subscription credentials") if existing_key:
elif existing_key: print_info(f"Current credentials: {existing_key[:12]}...")
print_info(f"Current: {existing_key[:12]}... (configured)") if not prompt_yes_no("Update credentials?", False):
if prompt_yes_no("Update key?", False): existing_key = None # skip — keep existing
api_key = prompt("Enter Anthropic API key or setup-token", password=True)
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: if api_key:
save_env_value("ANTHROPIC_API_KEY", api_key) save_env_value("ANTHROPIC_API_KEY", api_key)
print_success("Anthropic key saved") print_success("API key saved")
else: else:
api_key = prompt("Enter Anthropic API key or setup-token", password=True) print_warning("Skipped — agent won't work without credentials")
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")
# Clear custom endpoint vars if switching # Clear custom endpoint vars if switching
if existing_custom: if existing_custom: