diff --git a/hermes_cli/auth_commands.py b/hermes_cli/auth_commands.py index c6e23b42f6..20d0282001 100644 --- a/hermes_cli/auth_commands.py +++ b/hermes_cli/auth_commands.py @@ -4,6 +4,7 @@ from __future__ import annotations from getpass import getpass import math +import sys import time from types import SimpleNamespace import uuid @@ -160,7 +161,10 @@ def auth_add_command(args) -> None: default_label = _api_key_default_label(len(pool.entries()) + 1) label = (getattr(args, "label", None) or "").strip() if not label: - label = input(f"Label (optional, default: {default_label}): ").strip() or default_label + if sys.stdin.isatty(): + label = input(f"Label (optional, default: {default_label}): ").strip() or default_label + else: + label = default_label entry = PooledCredential( provider=provider, id=uuid.uuid4().hex[:6], diff --git a/hermes_cli/main.py b/hermes_cli/main.py index 638f2a31c3..5c6db4e904 100644 --- a/hermes_cli/main.py +++ b/hermes_cli/main.py @@ -6325,8 +6325,13 @@ Examples: sys.stderr = _io.StringIO() args = parser.parse_args(_processed_argv) sys.stderr = _saved_stderr - except SystemExit: + except SystemExit as exc: sys.stderr = _saved_stderr + # Help/version flags (exit code 0) already printed output — + # re-raise immediately to avoid a second parse_args printing + # the same help text again (#10230). + if exc.code == 0: + raise # Subcommand name was consumed as a flag value (e.g. -c model). # Fall back to optional subparsers so argparse handles it normally. subparsers.required = False diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index 699bde23e9..dee0cb23d2 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -786,7 +786,8 @@ def list_authenticated_providers( from hermes_cli.models import OPENROUTER_MODELS, _PROVIDER_MODELS results: List[dict] = [] - seen_slugs: set = set() + seen_slugs: set = set() # lowercase-normalized to catch case variants (#9545) + seen_mdev_ids: set = set() # prevent duplicate entries for aliases (e.g. kimi-coding + kimi-coding-cn) data = fetch_models_dev() @@ -799,6 +800,11 @@ def list_authenticated_providers( # --- 1. Check Hermes-mapped providers --- for hermes_id, mdev_id in PROVIDER_TO_MODELS_DEV.items(): + # Skip aliases that map to the same models.dev provider (e.g. + # kimi-coding and kimi-coding-cn both → kimi-for-coding). + # The first one with valid credentials wins (#10526). + if mdev_id in seen_mdev_ids: + continue pdata = data.get(mdev_id) if not isinstance(pdata, dict): continue @@ -837,7 +843,8 @@ def list_authenticated_providers( "total_models": total, "source": "built-in", }) - seen_slugs.add(slug) + seen_slugs.add(slug.lower()) + seen_mdev_ids.add(mdev_id) # --- 2. Check Hermes-only providers (nous, openai-codex, copilot, opencode-go) --- from hermes_cli.providers import HERMES_OVERLAYS @@ -849,12 +856,12 @@ def list_authenticated_providers( _mdev_to_hermes = {v: k for k, v in PROVIDER_TO_MODELS_DEV.items()} for pid, overlay in HERMES_OVERLAYS.items(): - if pid in seen_slugs: + if pid.lower() in seen_slugs: continue # Resolve Hermes slug — e.g. "github-copilot" → "copilot" hermes_slug = _mdev_to_hermes.get(pid, pid) - if hermes_slug in seen_slugs: + if hermes_slug.lower() in seen_slugs: continue # Check if credentials exist @@ -935,8 +942,8 @@ def list_authenticated_providers( "total_models": total, "source": "hermes", }) - seen_slugs.add(pid) - seen_slugs.add(hermes_slug) + seen_slugs.add(pid.lower()) + seen_slugs.add(hermes_slug.lower()) # --- 2b. Cross-check canonical provider list --- # Catches providers that are in CANONICAL_PROVIDERS but weren't found @@ -948,7 +955,7 @@ def list_authenticated_providers( _canon_provs = [] for _cp in _canon_provs: - if _cp.slug in seen_slugs: + if _cp.slug.lower() in seen_slugs: continue # Check credentials via PROVIDER_REGISTRY (auth.py) @@ -995,7 +1002,7 @@ def list_authenticated_providers( "total_models": _cp_total, "source": "canonical", }) - seen_slugs.add(_cp.slug) + seen_slugs.add(_cp.slug.lower()) # --- 3. User-defined endpoints from config --- if user_providers and isinstance(user_providers, dict): @@ -1068,7 +1075,7 @@ def list_authenticated_providers( groups[slug]["models"].append(default_model) for slug, grp in groups.items(): - if slug in seen_slugs: + if slug.lower() in seen_slugs: continue results.append({ "slug": slug, @@ -1080,7 +1087,7 @@ def list_authenticated_providers( "source": "user-config", "api_url": grp["api_url"], }) - seen_slugs.add(slug) + seen_slugs.add(slug.lower()) # Sort: current provider first, then by model count descending results.sort(key=lambda r: (not r["is_current"], -r["total_models"]))