diff --git a/hermes_cli/auth.py b/hermes_cli/auth.py index 7bde989ff..929d2de4c 100644 --- a/hermes_cli/auth.py +++ b/hermes_cli/auth.py @@ -2100,6 +2100,24 @@ def get_qwen_auth_status() -> Dict[str, Any]: # Actual HTTP traffic goes to https://cloudcode-pa.googleapis.com/v1internal:*. # ============================================================================= +def _mark_google_gemini_cli_active(creds: Dict[str, Any]) -> None: + """Set active_provider to google-gemini-cli in auth.json. + + The actual OAuth tokens live in the Google credential file managed by + agent.google_oauth. This function only writes a minimal provider-state + entry (email for display) and sets active_provider so that + get_active_provider() and _model_section_has_credentials() detect the + provider for the setup wizard and status commands. + """ + with _auth_store_lock(): + auth_store = _load_auth_store() + state: Dict[str, Any] = {} + if creds.get("email"): + state["email"] = str(creds["email"]) + _save_provider_state(auth_store, "google-gemini-cli", state) + _save_auth_store(auth_store) + + def resolve_gemini_oauth_runtime_credentials( *, force_refresh: bool = False, diff --git a/hermes_cli/auth_commands.py b/hermes_cli/auth_commands.py index 0ca562762..ed6d15aa5 100644 --- a/hermes_cli/auth_commands.py +++ b/hermes_cli/auth_commands.py @@ -353,6 +353,7 @@ def auth_add_command(args) -> None: from agent.google_oauth import run_gemini_oauth_login_pure creds = run_gemini_oauth_login_pure() + auth_mod._mark_google_gemini_cli_active(creds) label = (getattr(args, "label", None) or "").strip() or ( creds.get("email") or _oauth_default_label(provider, len(pool.entries()) + 1) ) diff --git a/tests/hermes_cli/test_auth_commands.py b/tests/hermes_cli/test_auth_commands.py index f6fc408a0..f96a8e100 100644 --- a/tests/hermes_cli/test_auth_commands.py +++ b/tests/hermes_cli/test_auth_commands.py @@ -97,6 +97,51 @@ def test_auth_add_anthropic_oauth_persists_pool_entry(tmp_path, monkeypatch): assert entry["expires_at_ms"] == 1711234567000 +def test_auth_add_google_gemini_cli_sets_active_provider(tmp_path, monkeypatch): + """hermes auth add google-gemini-cli must set active_provider in auth.json. + + Tokens are managed by agent.google_oauth (written to the Google credential + file by start_oauth_flow). The auth.json entry must record active_provider + so get_active_provider() and _model_section_has_credentials() detect the + provider — without storing tokens that would become stale. + """ + monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes")) + _write_auth_store(tmp_path, {"version": 1, "providers": {}}) + monkeypatch.setattr( + "agent.google_oauth.run_gemini_oauth_login_pure", + lambda: { + "access_token": "ya29.test-token", + "refresh_token": "google-refresh", + "email": "user@example.com", + "expires_at_ms": 9999999999000, + "project_id": "my-project", + }, + ) + + from hermes_cli.auth_commands import auth_add_command + + class _Args: + provider = "google-gemini-cli" + auth_type = "oauth" + api_key = None + label = None + + auth_add_command(_Args()) + + payload = json.loads((tmp_path / "hermes" / "auth.json").read_text()) + assert payload["active_provider"] == "google-gemini-cli" + state = payload["providers"]["google-gemini-cli"] + # Only email stored — no access_token/refresh_token (those live in + # the Google OAuth credential file managed by agent.google_oauth). + assert state.get("email") == "user@example.com" + assert "access_token" not in state + assert "refresh_token" not in state + # pool entry from pool.add_entry() still present for hermes auth list + entries = payload["credential_pool"]["google-gemini-cli"] + entry = next(item for item in entries if item["source"] == "manual:google_pkce") + assert entry["access_token"] == "ya29.test-token" + + def test_auth_add_nous_oauth_persists_pool_entry(tmp_path, monkeypatch): monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes")) _write_auth_store(tmp_path, {"version": 1, "providers": {}})