Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 10 additions & 16 deletions hermes_cli/auth_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,24 +329,18 @@ def auth_add_command(args) -> None:
open_browser=not getattr(args, "no_browser", False),
manual_paste=bool(getattr(args, "manual_paste", False)),
)
label = (getattr(args, "label", None) or "").strip() or label_from_token(
creds["tokens"]["access_token"],
_oauth_default_label(provider, len(pool.entries()) + 1),
)
entry = PooledCredential(
provider=provider,
id=uuid.uuid4().hex[:6],
label=label,
auth_type=AUTH_TYPE_OAUTH,
priority=0,
source=f"{SOURCE_MANUAL}:xai_pkce",
access_token=creds["tokens"]["access_token"],
refresh_token=creds["tokens"].get("refresh_token"),
base_url=creds.get("base_url"),
auth_mod._save_xai_oauth_tokens(
creds["tokens"],
discovery=creds.get("discovery"),
redirect_uri=creds.get("redirect_uri", ""),
last_refresh=creds.get("last_refresh"),
)
pool.add_entry(entry)
print(f'Added {provider} OAuth credential #{len(pool.entries())}: "{entry.label}"')
pool = load_pool(provider)
entry = next((e for e in pool.entries() if getattr(e, "source", "") == "loopback_pkce"), None)
shown_label = entry.label if entry is not None else label_from_token(
creds["tokens"]["access_token"], _oauth_default_label(provider, 1)
)
print(f'Saved {provider} OAuth credentials: "{shown_label}"')
return

if provider == "google-gemini-cli":
Expand Down
44 changes: 44 additions & 0 deletions tests/hermes_cli/test_auth_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,50 @@ class _Args:
assert entry["base_url"] == "https://chatgpt.com/backend-api/codex"


def test_auth_add_xai_oauth_persists_provider_state(tmp_path, monkeypatch):
"""hermes auth add xai-oauth must set active_provider so setup detects the provider."""
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes"))
_write_auth_store(tmp_path, {"version": 1, "providers": {}})
access_token = "xai-test-access-token"
monkeypatch.setattr(
"hermes_cli.auth._xai_oauth_loopback_login",
lambda **kwargs: {
"tokens": {
"access_token": access_token,
"refresh_token": "xai-refresh-token",
"id_token": "",
"token_type": "Bearer",
},
"discovery": {"token_endpoint": "https://auth.x.ai/token"},
"redirect_uri": "http://127.0.0.1:7777/callback",
"base_url": "https://api.x.ai/v1",
"last_refresh": "2026-06-02T10:00:00Z",
"source": "oauth-loopback",
},
)

from hermes_cli.auth_commands import auth_add_command

class _Args:
provider = "xai-oauth"
auth_type = "oauth"
api_key = None
label = None
timeout = None
no_browser = False
manual_paste = False

auth_add_command(_Args())

payload = json.loads((tmp_path / "hermes" / "auth.json").read_text())
assert payload["active_provider"] == "xai-oauth"
assert payload["providers"]["xai-oauth"]["tokens"]["access_token"] == access_token
entries = payload["credential_pool"]["xai-oauth"]
entry = next(item for item in entries if item["source"] == "loopback_pkce")
assert entry["source"] == "loopback_pkce"
assert entry["refresh_token"] == "xai-refresh-token"


def test_auth_remove_reindexes_priorities(tmp_path, monkeypatch):
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes"))
# Prevent pool auto-seeding from host env vars and file-backed sources
Expand Down
Loading