diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 19a0ac49f..a209ea11c 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -173,9 +173,25 @@ def _relative_time(ts) -> str: def _has_any_provider_configured() -> bool: """Check if at least one inference provider is usable.""" - from hermes_cli.config import get_env_path, get_hermes_home + from hermes_cli.config import get_env_path, get_hermes_home, load_config from hermes_cli.auth import get_auth_status + # Determine whether Hermes itself has been explicitly configured (model + # in config that isn't the hardcoded default). Used below to gate external + # tool credentials (Claude Code, Codex CLI) that shouldn't silently skip + # the setup wizard on a fresh install. + from hermes_cli.config import DEFAULT_CONFIG + _DEFAULT_MODEL = DEFAULT_CONFIG.get("model", "") + cfg = load_config() + model_cfg = cfg.get("model") + if isinstance(model_cfg, dict): + _model_name = (model_cfg.get("default") or "").strip() + elif isinstance(model_cfg, str): + _model_name = model_cfg.strip() + else: + _model_name = "" + _has_hermes_config = _model_name and _model_name != _DEFAULT_MODEL + # Check env vars (may be set by .env or shell). # OPENAI_BASE_URL alone counts — local models (vLLM, llama.cpp, etc.) # often don't require an API key. @@ -231,15 +247,16 @@ def _has_any_provider_configured() -> bool: # Check for Claude Code OAuth credentials (~/.claude/.credentials.json) - # These are used by resolve_anthropic_token() at runtime but were missing - # from this startup gate check. - try: - from agent.anthropic_adapter import read_claude_code_credentials, is_claude_code_token_valid - creds = read_claude_code_credentials() - if creds and (is_claude_code_token_valid(creds) or creds.get("refreshToken")): - return True - except Exception: - pass + # Only count these if Hermes has been explicitly configured — Claude Code + # being installed doesn't mean the user wants Hermes to use their tokens. + if _has_hermes_config: + try: + from agent.anthropic_adapter import read_claude_code_credentials, is_claude_code_token_valid + creds = read_claude_code_credentials() + if creds and (is_claude_code_token_valid(creds) or creds.get("refreshToken")): + return True + except Exception: + pass return False diff --git a/tests/test_api_key_providers.py b/tests/test_api_key_providers.py index 0c6337d3e..e250bbb25 100644 --- a/tests/test_api_key_providers.py +++ b/tests/test_api_key_providers.py @@ -622,6 +622,57 @@ class TestHasAnyProviderConfigured: from hermes_cli.main import _has_any_provider_configured assert _has_any_provider_configured() is True + def test_claude_code_creds_ignored_on_fresh_install(self, monkeypatch, tmp_path): + """Claude Code credentials should NOT skip the wizard when Hermes is unconfigured.""" + from hermes_cli import config as config_module + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + monkeypatch.setattr(config_module, "get_env_path", lambda: hermes_home / ".env") + monkeypatch.setattr(config_module, "get_hermes_home", lambda: hermes_home) + # Clear all provider env vars so earlier checks don't short-circuit + for var in ("OPENROUTER_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", + "ANTHROPIC_TOKEN", "OPENAI_BASE_URL"): + monkeypatch.delenv(var, raising=False) + # Simulate valid Claude Code credentials + monkeypatch.setattr( + "agent.anthropic_adapter.read_claude_code_credentials", + lambda: {"accessToken": "sk-ant-test", "refreshToken": "ref-tok"}, + ) + monkeypatch.setattr( + "agent.anthropic_adapter.is_claude_code_token_valid", + lambda creds: True, + ) + from hermes_cli.main import _has_any_provider_configured + assert _has_any_provider_configured() is False + + def test_claude_code_creds_counted_when_hermes_configured(self, monkeypatch, tmp_path): + """Claude Code credentials should count when Hermes has been explicitly configured.""" + import yaml + from hermes_cli import config as config_module + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + # Write a config with a non-default model to simulate explicit configuration + config_file = hermes_home / "config.yaml" + config_file.write_text(yaml.dump({"model": {"default": "my-local-model"}})) + monkeypatch.setattr(config_module, "get_env_path", lambda: hermes_home / ".env") + monkeypatch.setattr(config_module, "get_hermes_home", lambda: hermes_home) + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + # Clear all provider env vars + for var in ("OPENROUTER_API_KEY", "OPENAI_API_KEY", "ANTHROPIC_API_KEY", + "ANTHROPIC_TOKEN", "OPENAI_BASE_URL"): + monkeypatch.delenv(var, raising=False) + # Simulate valid Claude Code credentials + monkeypatch.setattr( + "agent.anthropic_adapter.read_claude_code_credentials", + lambda: {"accessToken": "sk-ant-test", "refreshToken": "ref-tok"}, + ) + monkeypatch.setattr( + "agent.anthropic_adapter.is_claude_code_token_valid", + lambda creds: True, + ) + from hermes_cli.main import _has_any_provider_configured + assert _has_any_provider_configured() is True + # ============================================================================= # Kimi Code auto-detection tests