Hermes Auth CLI Parity Manifest
Source-of-truth manifest of hermes auth, hermes model, hermes setup, and hermes mcp login as they exist in canonical Python Hermes today, for Gormes parity planning.
Hermes Auth CLI Parity Manifest
Read-only audit of the operator-visible CLI surface for provider auth and
provider lifecycle in canonical Python Hermes
(workspace-mineru/hermes-agent/). Captured for downstream Gormes planner
splits - no Hermes or Gormes runtime code changed by this pass.
Scope: only commands that authenticate, configure, or remove an inference provider (or the Spotify control-plane provider). Out of scope: chat, gateway, cron, sessions, tools, mcp non-login subcommands, slack manifest, whatsapp, backup, status, doctor.
All paths below are absolute Python Hermes paths.
1. Command-tree manifest (non-deprecated surface)
hermes auth is the canonical pooled-credential CLI; hermes model,
hermes setup, and hermes mcp login are the supported adjacent surfaces a
user reaches when picking a provider, running the full wizard, or repairing a
broken MCP OAuth session.
| Command form | Behavior summary | Source: file:lines |
|---|---|---|
hermes auth | Bare invocation. Prints the credential pool (per provider, with current pick markers and exhaustion status), prints AWS Bedrock identity if boto3 resolves credentials, then enters an interactive menu: add / remove / reset cooldowns / set rotation strategy / exit. | hermes_cli/auth_commands.py:449-507, hermes_cli/auth_commands.py:632-656; parser at hermes_cli/main.py:8245-8307 |
hermes auth add <provider> [--type oauth|api-key] [--label LBL] [--api-key VAL] [--portal-url URL] [--inference-url URL] [--client-id ID] [--scope S] [--no-browser] [--timeout SEC] [--insecure] [--ca-bundle PATH] | Add a pooled credential for <provider>. Default type is oauth for `anthropic | nous |
hermes auth list [provider] | List pooled credentials for one provider (filter) or every provider in PROVIDER_REGISTRY + {openrouter} + list_custom_pool_providers(). Marks the currently-selected entry with <-, shows label, auth type, source, and exhaustion status with retry-window hints. | parser hermes_cli/main.py:8283-8284; impl hermes_cli/auth_commands.py:339-363 |
hermes auth remove <provider> <target> | Remove a credential by index, entry id, or exact label. After pool removal, dispatches the unified find_removal_step from agent.credential_sources which performs source-specific cleanup (env vars, external OAuth files like ~/.codex/auth.json, gh-cli copilot tokens, claude-code, qwen-cli, etc.) and may suppress that source so it does not get re-imported. | parser hermes_cli/main.py:8285-8291; impl hermes_cli/auth_commands.py:366-401 |
hermes auth reset <provider> | Clear exhaustion / rate-limit / auth-failure status on every credential for <provider> so they are eligible to be picked again on the next inference call. | parser hermes_cli/main.py:8292-8295; impl hermes_cli/auth_commands.py:404-408 |
hermes auth status <provider> | Print logged-in / logged-out for <provider> via auth.get_auth_status(provider), including auth_type, client_id, redirect_uri, scope, expires_at, api_base_url where available. | parser hermes_cli/main.py:8296-8297; impl hermes_cli/auth_commands.py:411-428, hermes_cli/auth.py:3419-3446 |
hermes auth logout <provider> | Delegates to auth.logout_command with provider=<provider>. Validates against is_known_auth_provider, calls clear_provider_auth(target), resets model.provider if config matched, and prints a follow-up hint depending on whether OPENROUTER_API_KEY is set. | parser hermes_cli/main.py:8298-8299; impl hermes_cli/auth_commands.py:431-432, hermes_cli/auth.py:4334-4360 |
hermes auth spotify [login|status|logout] [--client-id ID] [--redirect-uri URI] [--scope S] [--no-browser] [--timeout SEC] | Spotify control-plane auth (separate from inference providers). Default action is login (PKCE browser flow). status and logout route through auth_status_command / auth_logout_command with provider="spotify". Login flow prompts an interactive setup wizard if no client_id is found in env, args, or stored state. | parser hermes_cli/main.py:8300-8307; impl hermes_cli/auth_commands.py:435-446, login flow hermes_cli/auth.py:2018-2108 |
hermes model [--portal-url URL] [--inference-url URL] [--client-id ID] [--scope S] [--no-browser] [--timeout SEC] [--ca-bundle PATH] [--insecure] | Interactive provider + model picker. Calls select_provider_and_model(args) which: shows current provider/model, presents the provider menu, runs the per-provider login flow if needed (e.g. Codex via _login_openai_codex, Nous via device code, anthropic OAuth via anthropic_adapter.run_hermes_oauth_login_pure, etc.), fetches the model catalog (provider-specific or via model_catalog), prompts model selection, and persists model.default + model.provider to config. Requires a TTY (_require_tty("model")). | parser hermes_cli/main.py:7862-7902; handler hermes_cli/main.py:1445-1448 then select_provider_and_model at hermes_cli/main.py:1451+ |
hermes setup [section] [--non-interactive] [--reset] [--reconfigure] [--quick] | Interactive setup wizard. With no section, runs the full wizard (which on existing installs is implicitly --reconfigure mode). section constrains it to one block: model (calls setup_model_provider, the same provider+model picker as hermes model), tts, terminal, gateway, tools, agent. --quick only prompts items that are missing/unset; --reset clears config to defaults; --non-interactive uses defaults/env vars. | parser hermes_cli/main.py:8089-8123; handler hermes_cli/main.py:1438-1442; wizard hermes_cli/setup.py:667+ (model section), hermes_cli/setup.py:1165+ (tts), 1175+ (terminal), 1541+ (agent) |
hermes mcp login <name> | Force re-authentication for an OAuth-based MCP server. Looks up the server in MCP config, refuses if auth != "oauth" or if no URL, then MCPOAuthManager.remove(name) to wipe disk + in-memory cache, and finally calls _probe_single_server(name, server_config) which re-runs the OAuth flow (browser redirect + callback capture) and reports the resulting tool count. | parser hermes_cli/main.py:9278-9282; handler hermes_cli/mcp_config.py:582-636; dispatch hermes_cli/mcp_config.py:740-758 |
Notes:
hermes authandhermes modeltogether are the operator-visible auth surface.hermes setup modelreaches the sameselect_provider_and_modelpath ashermes model, but routes through the wizard’s defaults/quick logic.hermes logout(top-level, nothermes auth logout) still exists and is not in the deprecation note below - it is supported but narrowly scoped (--providerchoices:nous,openai-codex,spotify). It dispatches to the samelogout_commandashermes auth logout. Parser athermes_cli/main.py:8232-8243.- This is an open question for parity: see section 5.
2. Deprecation note - hermes login
hermes login is the historical entry point that the Gormes README must
not recommend. The parser is still registered (so hermes login --help
still prints), but the handler is a stub that prints a deprecation message
and exits cleanly.
Source: hermes_cli/auth.py:3848-3853
def login_command(args) -> None:
"""Deprecated: use 'hermes model' or 'hermes setup' instead."""
print("The 'hermes login' command has been removed.")
print("Use 'hermes auth' to manage credentials,")
print("'hermes model' to select a provider, or 'hermes setup' for full setup.")
raise SystemExit(0)Parser definition (still wired so flags do not break old scripts, but every
flag is ignored): hermes_cli/main.py:8186-8227. Flags accepted but no-op:
--provider {nous,openai-codex}, --portal-url, --inference-url,
--client-id, --scope, --no-browser, --timeout, --ca-bundle,
--insecure.
Redirect targets:
hermes auth- pooled credentials.hermes model- provider + model picker (and the OAuth flow for that provider).hermes setup- full or sectioned wizard.
Gormes parity rule. Gormes follows the Q1B decision: do not register a
top-level login subcommand. gormes login should travel through Cobra’s
unknown-command path and emit deterministic
unknown_command_login_suggested_auth_add guidance:
did you mean "gormes auth add <provider> --type oauth"?. It must not parse
legacy login flags, run OAuth, open a browser, or mutate auth state. README and
docs should never tell a user to run hermes login --provider openai-codex;
the supported recipe is hermes auth add openai-codex (or hermes model then
pick OpenAI Codex).
3. Per-provider auth add behavior matrix
<provider> is the positional arg to hermes auth add. _OAUTH_CAPABLE_PROVIDERS = {"anthropic", "nous", "openai-codex", "qwen-oauth", "google-gemini-cli"} (hermes_cli/auth_commands.py:36). For all other providers in PROVIDER_REGISTRY, --type oauth is rejected (raises SystemExit("hermes auth add <provider> is not implemented for auth type oauth yet.") via the catch-all at auth_commands.py:336); only --type api-key works.
| Provider id | Default flow | Source function | Stored to ~/.hermes/auth.json (or pool) | Account-pool semantics | Notes |
|---|---|---|---|---|---|
anthropic | OAuth (PKCE) | agent.anthropic_adapter.run_hermes_oauth_login_pure invoked from auth_commands.py:221-245 | PooledCredential(auth_type=oauth, source="manual:hermes_pkce", access_token, refresh_token, expires_at_ms, base_url="https://api.anthropic.com") | Multiple OAuth credentials supported; pooled rotation per credential_pool_strategies.anthropic. | Also accepts --type api-key; env-var fallbacks ANTHROPIC_API_KEY, ANTHROPIC_TOKEN, CLAUDE_CODE_OAUTH_TOKEN (auth.py:240). |
nous | OAuth device code | auth.py:_nous_device_code_login (auth.py:4075+), persisted via persist_nous_credentials (auth.py:2891+) | provider state under providers.nous plus mint-agent-key state; pool entry honors --label. | Pool entry is the persisted Nous state. Label propagated into providers.nous so label_from_token does not overwrite it on the next load_pool("nous"). | Honors --portal-url, --inference-url, --client-id, --scope, --no-browser, --timeout, --insecure, --ca-bundle, --min-key-ttl-seconds (default 5min). Defaults from DEFAULT_NOUS_PORTAL_URL / DEFAULT_NOUS_INFERENCE_URL / DEFAULT_NOUS_CLIENT_ID / DEFAULT_NOUS_SCOPE (auth.py:135-143). |
openai-codex | OAuth device code | auth.py:_codex_device_code_login (auth.py:3930+); pool persistence at auth_commands.py:270-293 | PooledCredential(auth_type=oauth, source="manual:device_code", access_token, refresh_token, base_url="https://chatgpt.com/backend-api/codex" / DEFAULT_CODEX_BASE_URL, last_refresh) | Pre-add: unsuppress_credential_source("openai-codex", "device_code") so a re-link after auth remove is not skipped. | Vendor CLI conflict. Hermes maintains its own Codex OAuth session separate from the Codex CLI / VS Code extension to avoid refresh-token rotation collisions where one app’s refresh invalidates the other (auth.py:2120-2126). The non-pooled _login_openai_codex (auth.py:3856+) prompts to import ~/.codex/auth.json if found but warns “a separate login is recommended” and “Hermes will keep working independently with its own session” (auth.py:3897-3909). The pooled auth add path (auth_commands.py:270-293) does not reuse ~/.codex/; it always runs a fresh device-code flow. Removal step (agent.credential_sources for openai-codex/device_code) wipes the Hermes-owned tokens and suppresses the source. |
qwen-oauth | Vendor CLI import (no fresh OAuth) | auth.py:resolve_qwen_runtime_credentials(refresh_if_expiring=False) invoked from auth_commands.py:316-334; tokens read from Qwen-CLI’s ~/.qwen/oauth_creds.json via _read_qwen_cli_tokens (auth.py:1278+). | PooledCredential(auth_type=oauth, source="manual:qwen_cli", access_token=<api_key>, base_url=<resolved>) | Token refresh handled by Hermes via _refresh_qwen_cli_tokens (auth.py:1321+). | If Qwen CLI is not installed or ~/.qwen/oauth_creds.json is missing, the call raises and auth add qwen-oauth fails. There is no Hermes-native Qwen OAuth flow. |
google-gemini-cli | OAuth (browser PKCE) | agent.google_oauth.run_gemini_oauth_login_pure invoked from auth_commands.py:295-314 | PooledCredential(auth_type=oauth, source="manual:google_pkce", access_token, refresh_token); default label is the user’s email if returned. | Status / refresh via resolve_gemini_oauth_runtime_credentials (auth.py:1453+) and get_gemini_oauth_auth_status (auth.py:1495+). | Endpoint: DEFAULT_GEMINI_CLOUDCODE_BASE_URL (Google CloudCode endpoint), distinct from gemini (Google AI Studio). |
spotify | Browser PKCE (control-plane, not inference) | auth.login_spotify_command (auth.py:2018-2108) - entered via hermes auth spotify [login], not hermes auth add spotify. | provider state under providers.spotify (NOT set_active=True). Stored: client_id, redirect_uri, scope list, accounts/api base URLs, access/refresh tokens, expiry. | Single Spotify session per Hermes home. spotify is not in PROVIDER_REGISTRY; trying hermes auth add spotify is rejected. | If client_id is not configured anywhere, prompts an interactive developer-app setup wizard (_spotify_interactive_setup, auth.py:1957+). Honors HERMES_SPOTIFY_CLIENT_ID env. Redirect URI must be allow-listed in the user’s Spotify app. |
copilot | API key | env-var resolution via ProviderConfig.api_key_env_vars=("COPILOT_GITHUB_TOKEN","GH_TOKEN","GITHUB_TOKEN"); hermes auth add copilot --type api-key writes a manual entry. | PooledCredential(auth_type=api_key, source="manual", access_token=<paste>, base_url=DEFAULT_GITHUB_MODELS_BASE_URL) | Standard pool. | gh_cli source is a separate auto-detected source with its own RemovalStep. |
copilot-acp | external_process (vendor CLI) | auth_type="external_process" (auth.py:170-176). auth add of api-key is allowed but routes through API-key handler. | base URL DEFAULT_COPILOT_ACP_BASE_URL (override COPILOT_ACP_BASE_URL). | n/a - process-managed auth. | Treated as an API-key provider for pool purposes. |
gemini | API key | env vars GOOGLE_API_KEY, GEMINI_API_KEY. | manual entry, base https://generativelanguage.googleapis.com/v1beta, override GEMINI_BASE_URL. | Standard pool. | Distinct from google-gemini-cli. |
zai | API key | env vars GLM_API_KEY, ZAI_API_KEY, Z_AI_API_KEY. | manual entry, base https://api.z.ai/api/paas/v4, override GLM_BASE_URL. | Standard pool. | detect_zai_endpoint (auth.py:514) auto-rewrites base for some keys. |
kimi-coding | API key | env vars KIMI_API_KEY, KIMI_CODING_API_KEY; base https://api.moonshot.ai/v1, override KIMI_BASE_URL. | manual entry. | Standard pool. | _resolve_kimi_base_url (auth.py:409) auto-redirects sk-kimi- keys to api.kimi.com/coding. |
kimi-coding-cn | API key | env var KIMI_CN_API_KEY. | manual entry, base https://api.moonshot.cn/v1. | Standard pool. | Mainland China endpoint. |
stepfun | API key | STEPFUN_API_KEY, base STEPFUN_STEP_PLAN_INTL_BASE_URL, override STEPFUN_BASE_URL. | manual entry. | Standard pool. | |
arcee | API key | ARCEEAI_API_KEY, base https://api.arcee.ai/api/v1, override ARCEE_BASE_URL. | manual entry. | Standard pool. | |
minimax | API key | MINIMAX_API_KEY, base https://api.minimax.io/anthropic, override MINIMAX_BASE_URL. | manual entry. | Standard pool. | Anthropic-shaped surface. |
minimax-cn | API key | MINIMAX_CN_API_KEY, base https://api.minimaxi.com/anthropic, override MINIMAX_CN_BASE_URL. | manual entry. | Standard pool. | |
alibaba | API key | DASHSCOPE_API_KEY, base https://dashscope-intl.aliyuncs.com/compatible-mode/v1, override DASHSCOPE_BASE_URL. | manual entry. | Standard pool. | |
alibaba-coding-plan | API key | ALIBABA_CODING_PLAN_API_KEY, DASHSCOPE_API_KEY, base https://coding-intl.dashscope.aliyuncs.com/v1. | manual entry. | Standard pool. | |
deepseek | API key | DEEPSEEK_API_KEY, base https://api.deepseek.com/v1. | manual entry. | Standard pool. | |
xai | API key | XAI_API_KEY, base https://api.x.ai/v1. | manual entry. | Standard pool. | |
nvidia | API key | NVIDIA_API_KEY, base https://integrate.api.nvidia.com/v1. | manual entry. | Standard pool. | |
ai-gateway | API key | AI_GATEWAY_API_KEY, base https://ai-gateway.vercel.sh/v1. | manual entry. | Standard pool. | Vercel AI Gateway. |
opencode-zen | API key | OPENCODE_ZEN_API_KEY, base https://opencode.ai/zen/v1. | manual entry. | Standard pool. | |
opencode-go | API key | OPENCODE_GO_API_KEY, base https://opencode.ai/zen/go/v1. | manual entry. | Standard pool. | Mixed API surface (OpenAI-compat for GLM/Kimi, Anthropic for MiniMax) selected per-model. |
kilocode | API key | KILOCODE_API_KEY, base https://api.kilo.ai/api/gateway. | manual entry. | Standard pool. | |
huggingface | API key | HF_TOKEN, base https://router.huggingface.co/v1. | manual entry. | Standard pool. | |
xiaomi | API key | XIAOMI_API_KEY, base https://api.xiaomimimo.com/v1. | manual entry. | Standard pool. | |
ollama-cloud | API key | OLLAMA_API_KEY, base DEFAULT_OLLAMA_CLOUD_BASE_URL, override OLLAMA_BASE_URL. | manual entry. | Standard pool. | |
bedrock | AWS SDK (boto3 chain) | auth_type="aws_sdk" (auth.py:351-358). hermes auth add bedrock is not handled - bedrock surfaces in the bare hermes auth summary via boto3 STS (auth_commands.py:457-476). | n/a - credentials come from boto3 chain (env vars, profiles, SSO, role). | n/a | No --type api-key path because pool dispatch falls through auth_commands.py:336 for unrecognized OAuth and never gets here for AWS SDK. Open question in section 5. |
azure-foundry | API key | AZURE_FOUNDRY_API_KEY, base set per-deployment via AZURE_FOUNDRY_BASE_URL. | manual entry. | Standard pool. | Inference base intentionally empty in registry - user must provide it. |
openrouter (alias or, open-router) | API key | auth_commands._normalize_provider rewrites to openrouter. Not in PROVIDER_REGISTRY; base URL hardcoded to OPENROUTER_BASE_URL in _provider_base_url. | PooledCredential(auth_type=api_key, source="manual", access_token, base_url=OPENROUTER_BASE_URL). | Standard pool. | Special-cased in _normalize_provider. |
custom:<name> | API key | matches custom_providers config entries; resolved via _resolve_custom_provider_input and _get_custom_provider_config. | PooledCredential(auth_type=api_key, source="manual", access_token, base_url=<from custom_providers>). | Standard pool. | Display name vs provider_key both accepted. |
4. auth list / status / logout / reset / spotify behavior
auth list [provider] - Resolves the filter via _normalize_provider (so
aliases like or become openrouter). With no filter, iterates over
sorted(PROVIDER_REGISTRY.keys() + {"openrouter"} + list_custom_pool_providers()).
For each provider with at least one entry, prints a header
<provider> (N credentials): and one line per entry showing index, label,
auth_type, source (with the manual: prefix stripped via _display_source),
exhaustion status (_format_exhausted_status distinguishes rate-limited /
auth-failed / exhausted with retry-window math), and a <- marker on the
currently-selected entry. Source: hermes_cli/auth_commands.py:339-363.
auth status <provider> - Required positional. Calls
auth.get_auth_status(provider) (auth.py:3419-3446) which dispatches to per-
provider status helpers (get_nous_auth_status auth.py:3256+,
get_codex_auth_status auth.py:3309+, get_qwen_auth_status auth.py:1425+,
get_gemini_oauth_auth_status auth.py:1495+, get_spotify_auth_status
auth.py:1938+, get_api_key_provider_status auth.py:3358+,
get_external_process_provider_status auth.py:3389+). Prints
<provider>: logged in plus indented metadata
(auth_type, client_id, redirect_uri, scope, expires_at, api_base_url)
or <provider>: logged out (<reason>) on failure. Source:
hermes_cli/auth_commands.py:411-428.
auth logout <provider> - Wraps auth.logout_command with a
SimpleNamespace. The underlying command (auth.py:4334-4360) validates
is_known_auth_provider, picks the target as provider or active or config_provider, calls
clear_provider_auth(target) to wipe providers.<target> from auth.json and
clears active_provider if it pointed there, resets model.provider in config
when it matched, and prints
Logged out of <Display Name>. followed by either
Hermes will use OpenRouter for inference. (if OPENROUTER_API_KEY) or
Run 'hermes model' or configure an API key to use Hermes.. Source:
hermes_cli/auth_commands.py:431-432.
auth reset <provider> - Calls pool.reset_statuses() on the named
provider’s pool, which clears every entry’s exhaustion / cooldown so all
credentials are eligible again on the next inference call. Prints
Reset status on <N> <provider> credentials. Source:
hermes_cli/auth_commands.py:404-408.
auth spotify [login|status|logout] - Default action is login
(no subcommand). login runs PKCE: builds an authorize URL, opens a browser
unless --no-browser or remote-session detected, waits for the localhost
callback (_spotify_wait_for_callback, default 180s), exchanges code+verifier
for tokens, and stores providers.spotify with set_active=False (Spotify is
not an inference provider so it must not steal active_provider). If no
client_id is supplied or stored, runs _spotify_interactive_setup to walk the
user through creating a Spotify developer app. status and logout route to
the generic auth_status_command / auth_logout_command with
provider="spotify". Source: hermes_cli/auth_commands.py:435-446,
hermes_cli/auth.py:2018-2108.
5. Open questions for the planner
hermes logout(top-level) status. Parser atmain.py:8232-8243is live, dispatches to the samelogout_commandashermes auth logout, but only exposes provider choicesnous|openai-codex|spotify. Is this command on a deprecation path (sibling ofhermes login) or supported in parallel? The user’s framing of the parity rule listshermes auth ... logoutas the non-deprecated shape; the planner should decide whether Gormes shipsgormes logout, onlygormes auth logout, or both.auth_commandvs interactive barehermes auth. The bare invocation prints both pool state and an AWS Bedrock STS identity (auth_commands.py:457-476) that depends on optionalboto3. Gormes parity needs a Go path that prints the same pool table; the AWS STS print can be a separate row gated on AWS SDK availability - confirm whether the planner wants AWS bedrock identity in the Goncho equivalent or carved off.auth add bedrock. Source has no explicit handler. The catch-all atauth_commands.py:336rejects OAuth, and there is no bedrock-specific API-key path becauseauth_type="aws_sdk". Confirm whether Gormes should match the gap (refuse and tell the user to set AWS env / profile) or close it (offergormes auth add bedrock --profile <name>).auth add openrouteraccepts only--type api-key. Same catch-all atauth_commands.py:336as bedrock, but openrouter is silently allowed because_normalize_providerrewrites it and the API-key branch atauth_commands.py:194-219does not requireprovider in PROVIDER_REGISTRY(the gate atauth_commands.py:163-164explicitly allowsopenrouterandcustom:*). Confirm Gormes treats openrouter as a first-class provider ID even though it has noProviderConfig.Vendor-CLI race envelope (Codex CLI / VS Code). The pooled
auth add openai-codexpath always runs a fresh device-code flow and stores tokens in~/.hermes/auth.json, rejecting reuse of~/.codex/. Only the legacy_login_openai_codexflow (still reachable viahermes model-> OpenAI Codex provider when no Hermes credentials exist) prompts to import~/.codex/auth.json. Gormes parity must replicate the isolation contract - but the planner needs to decide whethergormes auth add openai-codexkeeps offering import (parity withcmd_model) or stays strict (parity withauth add). This is the surface the user flagged as the README error.OAuth-capable provider set.
_OAUTH_CAPABLE_PROVIDERS = {"anthropic", "nous", "openai-codex", "qwen-oauth", "google-gemini-cli"}is hard-coded in two places (auth_commands.py:36and again inline atauth_commands.py:173). If a future provider grows OAuth, both spots need updating. The Gormes equivalent should derive this fromProviderConfig.auth_typeinstead.Spotify out of
PROVIDER_REGISTRY. Spotify is reachable only viaauth spotify, never viaauth add. Parity question: is Spotify a first-class auth provider in Gormes (separate command tree), or should it ride the sameauthsubparser?hermes mcp loginrequiresauth == "oauth". Header-auth and stdio MCP servers cannot be re-authed via this command; the handler tells the user tohermes mcp remove+hermes mcp addinstead (mcp_config.py:613-614). Confirm whether the Gormes equivalent narrows the surface the same way.Top-level
hermes loginparser still registered. The deprecated subparser still consumes flags sohermes login --provider openai-codexexits cleanly with the deprecation message instead ofargparsecomplaining about unknown args. Gormes parity needs to decide: keep the parser shim (cleanest deprecation), or drop the command entirely and letgormes loginproduce a “command not found” hint that points togormes auth.