fix: replace stale 'hermes login' refs with 'hermes auth' + fix credential removal re-seeding (#5670)
Two fixes: 1. Replace all stale 'hermes login' references with 'hermes auth' across auth.py, auxiliary_client.py, delegate_tool.py, config.py, run_agent.py, and documentation. The 'hermes login' command was deprecated; 'hermes auth' now handles OAuth credential management. 2. Fix credential removal not persisting for singleton-sourced credentials (device_code for openai-codex/nous, hermes_pkce for anthropic). auth_remove_command already cleared env vars for env-sourced credentials, but singleton credentials stored in the auth store were re-seeded by _seed_from_singletons() on the next load_pool() call. Now clears the underlying auth store entry when removing singleton-sourced credentials.
This commit is contained in:
@@ -936,7 +936,7 @@ def _read_codex_tokens(*, _lock: bool = True) -> Dict[str, Any]:
|
||||
state = _load_provider_state(auth_store, "openai-codex")
|
||||
if not state:
|
||||
raise AuthError(
|
||||
"No Codex credentials stored. Run `hermes login` to authenticate.",
|
||||
"No Codex credentials stored. Run `hermes auth` to authenticate.",
|
||||
provider="openai-codex",
|
||||
code="codex_auth_missing",
|
||||
relogin_required=True,
|
||||
@@ -944,7 +944,7 @@ def _read_codex_tokens(*, _lock: bool = True) -> Dict[str, Any]:
|
||||
tokens = state.get("tokens")
|
||||
if not isinstance(tokens, dict):
|
||||
raise AuthError(
|
||||
"Codex auth state is missing tokens. Run `hermes login` to re-authenticate.",
|
||||
"Codex auth state is missing tokens. Run `hermes auth` to re-authenticate.",
|
||||
provider="openai-codex",
|
||||
code="codex_auth_invalid_shape",
|
||||
relogin_required=True,
|
||||
@@ -953,14 +953,14 @@ def _read_codex_tokens(*, _lock: bool = True) -> Dict[str, Any]:
|
||||
refresh_token = tokens.get("refresh_token")
|
||||
if not isinstance(access_token, str) or not access_token.strip():
|
||||
raise AuthError(
|
||||
"Codex auth is missing access_token. Run `hermes login` to re-authenticate.",
|
||||
"Codex auth is missing access_token. Run `hermes auth` to re-authenticate.",
|
||||
provider="openai-codex",
|
||||
code="codex_auth_missing_access_token",
|
||||
relogin_required=True,
|
||||
)
|
||||
if not isinstance(refresh_token, str) or not refresh_token.strip():
|
||||
raise AuthError(
|
||||
"Codex auth is missing refresh_token. Run `hermes login` to re-authenticate.",
|
||||
"Codex auth is missing refresh_token. Run `hermes auth` to re-authenticate.",
|
||||
provider="openai-codex",
|
||||
code="codex_auth_missing_refresh_token",
|
||||
relogin_required=True,
|
||||
@@ -995,7 +995,7 @@ def refresh_codex_oauth_pure(
|
||||
del access_token # Access token is only used by callers to decide whether to refresh.
|
||||
if not isinstance(refresh_token, str) or not refresh_token.strip():
|
||||
raise AuthError(
|
||||
"Codex auth is missing refresh_token. Run `hermes login` to re-authenticate.",
|
||||
"Codex auth is missing refresh_token. Run `hermes auth` to re-authenticate.",
|
||||
provider="openai-codex",
|
||||
code="codex_auth_missing_refresh_token",
|
||||
relogin_required=True,
|
||||
@@ -1035,7 +1035,7 @@ def refresh_codex_oauth_pure(
|
||||
"Codex refresh token was already consumed by another client "
|
||||
"(e.g. Codex CLI or VS Code extension). "
|
||||
"Run `codex` in your terminal to generate fresh tokens, "
|
||||
"then run `hermes login --provider openai-codex` to re-authenticate."
|
||||
"then run `hermes auth` to re-authenticate."
|
||||
)
|
||||
relogin_required = True
|
||||
raise AuthError(
|
||||
@@ -1140,7 +1140,7 @@ def resolve_codex_runtime_credentials(
|
||||
logger.info("Migrating Codex credentials from ~/.codex/ to Hermes auth store")
|
||||
print("⚠️ Migrating Codex credentials to Hermes's own auth store.")
|
||||
print(" This avoids conflicts with Codex CLI and VS Code.")
|
||||
print(" Run `hermes login` to create a fully independent session.\n")
|
||||
print(" Run `hermes auth` to create a fully independent session.\n")
|
||||
_save_codex_tokens(cli_tokens)
|
||||
data = _read_codex_tokens()
|
||||
else:
|
||||
@@ -2096,7 +2096,7 @@ def detect_external_credentials() -> List[Dict[str, Any]]:
|
||||
found.append({
|
||||
"provider": "openai-codex",
|
||||
"path": str(codex_path),
|
||||
"label": f"Codex CLI credentials found ({codex_path}) — run `hermes login` to create a separate session",
|
||||
"label": f"Codex CLI credentials found ({codex_path}) — run `hermes auth` to create a separate session",
|
||||
})
|
||||
|
||||
return found
|
||||
@@ -2345,8 +2345,8 @@ def _save_model_choice(model_id: str) -> None:
|
||||
def login_command(args) -> None:
|
||||
"""Deprecated: use 'hermes model' or 'hermes setup' instead."""
|
||||
print("The 'hermes login' command has been removed.")
|
||||
print("Use 'hermes model' to select a provider and model,")
|
||||
print("or 'hermes setup' for full interactive setup.")
|
||||
print("Use 'hermes auth' to manage credentials,")
|
||||
print("'hermes model' to select a provider, or 'hermes setup' for full setup.")
|
||||
raise SystemExit(0)
|
||||
|
||||
|
||||
|
||||
@@ -305,6 +305,32 @@ def auth_remove_command(args) -> None:
|
||||
if cleared:
|
||||
print(f"Cleared {env_var} from .env")
|
||||
|
||||
# If this was a singleton-seeded credential (OAuth device_code, hermes_pkce),
|
||||
# clear the underlying auth store / credential file so it doesn't get
|
||||
# re-seeded on the next load_pool() call.
|
||||
elif removed.source == "device_code" and provider in ("openai-codex", "nous"):
|
||||
from hermes_cli.auth import (
|
||||
_load_auth_store, _save_auth_store, _auth_store_lock,
|
||||
)
|
||||
with _auth_store_lock():
|
||||
auth_store = _load_auth_store()
|
||||
providers_dict = auth_store.get("providers")
|
||||
if isinstance(providers_dict, dict) and provider in providers_dict:
|
||||
del providers_dict[provider]
|
||||
_save_auth_store(auth_store)
|
||||
print(f"Cleared {provider} OAuth tokens from auth store")
|
||||
|
||||
elif removed.source == "hermes_pkce" and provider == "anthropic":
|
||||
from hermes_constants import get_hermes_home
|
||||
oauth_file = get_hermes_home() / ".anthropic_oauth.json"
|
||||
if oauth_file.exists():
|
||||
oauth_file.unlink()
|
||||
print("Cleared Hermes Anthropic OAuth credentials")
|
||||
|
||||
elif removed.source == "claude_code" and provider == "anthropic":
|
||||
print("Note: Claude Code credentials live in ~/.claude/.credentials.json")
|
||||
print(" Remove them manually if you want to deauthorize Claude Code.")
|
||||
|
||||
|
||||
def auth_reset_command(args) -> None:
|
||||
provider = _normalize_provider(getattr(args, "provider", ""))
|
||||
|
||||
@@ -1932,8 +1932,8 @@ _FALLBACK_COMMENT = """
|
||||
#
|
||||
# Supported providers:
|
||||
# openrouter (OPENROUTER_API_KEY) — routes to any model
|
||||
# openai-codex (OAuth — hermes login) — OpenAI Codex
|
||||
# nous (OAuth — hermes login) — Nous Portal
|
||||
# openai-codex (OAuth — hermes auth) — OpenAI Codex
|
||||
# nous (OAuth — hermes auth) — Nous Portal
|
||||
# zai (ZAI_API_KEY) — Z.AI / GLM
|
||||
# kimi-coding (KIMI_API_KEY) — Kimi / Moonshot
|
||||
# minimax (MINIMAX_API_KEY) — MiniMax
|
||||
@@ -1975,8 +1975,8 @@ _COMMENTED_SECTIONS = """
|
||||
#
|
||||
# Supported providers:
|
||||
# openrouter (OPENROUTER_API_KEY) — routes to any model
|
||||
# openai-codex (OAuth — hermes login) — OpenAI Codex
|
||||
# nous (OAuth — hermes login) — Nous Portal
|
||||
# openai-codex (OAuth — hermes auth) — OpenAI Codex
|
||||
# nous (OAuth — hermes auth) — Nous Portal
|
||||
# zai (ZAI_API_KEY) — Z.AI / GLM
|
||||
# kimi-coding (KIMI_API_KEY) — Kimi / Moonshot
|
||||
# minimax (MINIMAX_API_KEY) — MiniMax
|
||||
|
||||
Reference in New Issue
Block a user