diff --git a/hermes_cli/doctor.py b/hermes_cli/doctor.py index 064b1d68d..a14ec47f9 100644 --- a/hermes_cli/doctor.py +++ b/hermes_cli/doctor.py @@ -454,11 +454,7 @@ def run_doctor(args): print(color("◆ Auth Providers", Colors.CYAN, Colors.BOLD)) try: - from hermes_cli.auth import ( - get_nous_auth_status, - get_codex_auth_status, - get_gemini_oauth_auth_status, - ) + from hermes_cli.auth import get_nous_auth_status, get_codex_auth_status nous_status = get_nous_auth_status() if nous_status.get("logged_in"): @@ -473,20 +469,6 @@ def run_doctor(args): check_warn("OpenAI Codex auth", "(not logged in)") if codex_status.get("error"): check_info(codex_status["error"]) - - gemini_status = get_gemini_oauth_auth_status() - if gemini_status.get("logged_in"): - email = gemini_status.get("email") or "" - project = gemini_status.get("project_id") or "" - pieces = [] - if email: - pieces.append(email) - if project: - pieces.append(f"project={project}") - suffix = f" ({', '.join(pieces)})" if pieces else "" - check_ok("Google Gemini OAuth", f"(logged in{suffix})") - else: - check_warn("Google Gemini OAuth", "(not logged in)") except Exception as e: check_warn("Auth provider status", f"(could not check: {e})") @@ -1048,10 +1030,24 @@ def run_doctor(args): else: check_warn(item["name"], "(system dependency not met)") - # Count disabled tools with API key requirements - api_disabled = [u for u in unavailable if (u.get("missing_vars") or u.get("env_vars"))] + # Only summarize missing API keys for toolsets actually enabled in the CLI. + # Otherwise default-off / disabled toolsets (for example rl) create noisy + # false positives in the final "Found N issue(s)" summary. + try: + from hermes_cli.config import load_config + from hermes_cli.tools_config import _get_platform_tools + + enabled_cli_toolsets = set(_get_platform_tools(load_config(), "cli", include_default_mcp_servers=False)) + except Exception: + enabled_cli_toolsets = set() + + api_disabled = [ + u for u in unavailable + if (u.get("missing_vars") or u.get("env_vars")) + and (not enabled_cli_toolsets or u.get("name") in enabled_cli_toolsets) + ] if api_disabled: - issues.append("Run 'hermes setup' to configure missing API keys for full tool access") + issues.append("Run 'hermes setup' to configure missing API keys for enabled toolsets") except Exception as e: check_warn("Could not check tool availability", f"({e})") diff --git a/tests/hermes_cli/test_doctor.py b/tests/hermes_cli/test_doctor.py index 948cafaf7..ee27aa350 100644 --- a/tests/hermes_cli/test_doctor.py +++ b/tests/hermes_cli/test_doctor.py @@ -302,7 +302,7 @@ def test_run_doctor_kimi_cn_env_is_detected_and_probe_is_null_safe(monkeypatch, home = tmp_path / ".hermes" home.mkdir(parents=True, exist_ok=True) (home / "config.yaml").write_text("memory: {}\n", encoding="utf-8") - (home / ".env").write_text("KIMI_CN_API_KEY=sk-test\n", encoding="utf-8") + (home / ".env").write_text("KIMI_CN_API_KEY=***", encoding="utf-8") project = tmp_path / "project" project.mkdir(exist_ok=True) @@ -345,6 +345,91 @@ def test_run_doctor_kimi_cn_env_is_detected_and_probe_is_null_safe(monkeypatch, assert any(url == "https://api.moonshot.cn/v1/models" for url, _, _ in calls) +def test_run_doctor_ignores_missing_api_keys_for_disabled_toolsets(monkeypatch, tmp_path): + home = tmp_path / ".hermes" + home.mkdir(parents=True, exist_ok=True) + (home / "config.yaml").write_text("memory: {}\n", encoding="utf-8") + project = tmp_path / "project" + project.mkdir(exist_ok=True) + + monkeypatch.setattr(doctor_mod, "HERMES_HOME", home) + monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project) + monkeypatch.setattr(doctor_mod, "_DHH", str(home)) + + fake_model_tools = types.SimpleNamespace( + check_tool_availability=lambda *a, **kw: ( + ["web"], + [{"name": "rl", "missing_vars": ["TINKER_API_KEY", "WANDB_API_KEY"]}], + ), + TOOLSET_REQUIREMENTS={"web": {"name": "web"}}, + ) + monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools) + + try: + from hermes_cli import auth as _auth_mod + monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) + monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) + import httpx + monkeypatch.setattr(httpx, "get", lambda *a, **kw: types.SimpleNamespace(status_code=200)) + from hermes_cli import config as _config_mod + from hermes_cli import tools_config as _tools_mod + monkeypatch.setattr(_config_mod, "load_config", lambda: {"platform_toolsets": {"cli": ["web"]}}) + monkeypatch.setattr(_tools_mod, "_get_platform_tools", lambda *a, **kw: {"web"}) + except Exception: + pass + + import io, contextlib + buf = io.StringIO() + with contextlib.redirect_stdout(buf): + doctor_mod.run_doctor(Namespace(fix=False)) + out = buf.getvalue() + + assert "⚠ rl" in out or "rl" in out + assert "Run 'hermes setup' to configure missing API keys for enabled toolsets" not in out + + +def test_run_doctor_reports_missing_api_keys_for_enabled_toolsets(monkeypatch, tmp_path): + home = tmp_path / ".hermes" + home.mkdir(parents=True, exist_ok=True) + (home / "config.yaml").write_text("memory: {}\n", encoding="utf-8") + project = tmp_path / "project" + project.mkdir(exist_ok=True) + + monkeypatch.setattr(doctor_mod, "HERMES_HOME", home) + monkeypatch.setattr(doctor_mod, "PROJECT_ROOT", project) + monkeypatch.setattr(doctor_mod, "_DHH", str(home)) + + fake_model_tools = types.SimpleNamespace( + check_tool_availability=lambda *a, **kw: ( + [], + [{"name": "web", "missing_vars": ["OPENROUTER_API_KEY"]}], + ), + TOOLSET_REQUIREMENTS={}, + ) + monkeypatch.setitem(sys.modules, "model_tools", fake_model_tools) + + try: + from hermes_cli import auth as _auth_mod + monkeypatch.setattr(_auth_mod, "get_nous_auth_status", lambda: {}) + monkeypatch.setattr(_auth_mod, "get_codex_auth_status", lambda: {}) + import httpx + monkeypatch.setattr(httpx, "get", lambda *a, **kw: types.SimpleNamespace(status_code=200)) + from hermes_cli import config as _config_mod + from hermes_cli import tools_config as _tools_mod + monkeypatch.setattr(_config_mod, "load_config", lambda: {"platform_toolsets": {"cli": ["web"]}}) + monkeypatch.setattr(_tools_mod, "_get_platform_tools", lambda *a, **kw: {"web"}) + except Exception: + pass + + import io, contextlib + buf = io.StringIO() + with contextlib.redirect_stdout(buf): + doctor_mod.run_doctor(Namespace(fix=False)) + out = buf.getvalue() + + assert "Run 'hermes setup' to configure missing API keys for enabled toolsets" in out + + @pytest.mark.parametrize("base_url", [None, "https://opencode.ai/zen/go/v1"]) def test_run_doctor_opencode_go_skips_invalid_models_probe(monkeypatch, tmp_path, base_url): home = tmp_path / ".hermes"