From 1e6cfaa0d084d3019d3d8704359aa8e6c7f3cb85 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 3 Sep 2026 13:33:10 -0700 Subject: [PATCH] =?UTF-8?q?simplify(compat):=20setup/model=5Fswitch/nous?= =?UTF-8?q?=5Fsubscription=20=E2=80=94=20drop=2034=20re-exports,=20repoint?= =?UTF-8?q?=2014=20callers=20+=2026=20test=20files=20(~100=20sites)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- acp_adapter/model_catalog.py | 6 ++-- cli.py | 2 +- gateway/slash_commands_model.py | 2 +- hermes_cli/gateway.py | 5 +-- hermes_cli/model_setup_flows_custom.py | 2 +- hermes_cli/model_switch.py | 6 +--- hermes_cli/model_switch_providers.py | 7 +--- hermes_cli/nous_subscription.py | 9 ++--- hermes_cli/setup.py | 33 +++++-------------- hermes_cli/setup_migration.py | 5 ++- hermes_cli/setup_quick.py | 10 +++--- hermes_cli/setup_summary.py | 6 ++-- hermes_cli/setup_terminal.py | 2 ++ hermes_cli/setup_tts.py | 8 +++-- hermes_cli/tools_config_post_setup.py | 3 +- plugins/platforms/telegram/adapter.py | 3 +- tests/acp/test_named_provider_catalogs.py | 16 ++++----- .../test_25107_stale_base_url_api_mode.py | 2 +- .../test_model_command_async_offload.py | 2 +- .../test_model_command_profile_config.py | 2 +- tests/gateway/test_model_picker_persist.py | 2 +- ..._authenticated_providers_exhausted_pool.py | 4 +-- .../hermes_cli/test_copilot_in_model_list.py | 3 +- .../hermes_cli/test_list_picker_providers.py | 5 +-- tests/hermes_cli/test_macos_fda_guidance.py | 2 +- .../test_model_cache_parallel_prefetch.py | 23 ++++++------- tests/hermes_cli/test_model_catalog.py | 8 ++--- .../test_model_switch_custom_providers.py | 18 ++++------ tests/hermes_cli/test_nous_policy_surfaces.py | 3 +- tests/hermes_cli/test_nous_subscription.py | 3 +- tests/hermes_cli/test_picker_prewarm.py | 13 ++++---- tests/hermes_cli/test_setup.py | 4 +-- tests/hermes_cli/test_setup_blank_slate.py | 10 +++--- .../test_setup_openclaw_migration.py | 11 ++++--- tests/hermes_cli/test_setup_reconfigure.py | 4 +-- .../test_setup_summary_provider_warning.py | 3 +- tests/hermes_cli/test_setup_tts_xai_oauth.py | 2 +- tests/hermes_cli/test_telegram_managed_bot.py | 5 +-- tests/hermes_cli/test_tools_config.py | 2 +- .../test_94895_backend_heartbeat.py | 3 +- .../tui_gateway/test_entry_picker_prewarm.py | 6 ++-- .../tui_gateway/test_startup_orphan_sweep.py | 5 +-- tui_gateway/entry.py | 2 +- 43 files changed, 125 insertions(+), 147 deletions(-) diff --git a/acp_adapter/model_catalog.py b/acp_adapter/model_catalog.py index 8d0adf17ef..120060cb25 100644 --- a/acp_adapter/model_catalog.py +++ b/acp_adapter/model_catalog.py @@ -26,10 +26,8 @@ def _named_custom_provider_catalogs() -> list[tuple[str, str, list[tuple[str, st ``parse_model_input``/``resolve_runtime_provider`` resolve, so choice ids round-trip.""" try: from hermes_cli.config import (get_compatible_custom_providers, is_provider_enabled, load_config) - from hermes_cli.model_switch import ( - _NativePickerModelList, _declared_model_ids, _entry_models_discovered, _fetch_picker_live_models, - _models_config_is_allowlist, - ) + from hermes_cli.model_switch import _declared_model_ids, _entry_models_discovered, _models_config_is_allowlist + from hermes_cli.model_switch_providers import _NativePickerModelList, _fetch_picker_live_models from hermes_cli.model_switch_providers import _discover_flag from hermes_cli.models import should_use_ollama_native_catalog from hermes_cli.providers import custom_provider_slug diff --git a/cli.py b/cli.py index ff31406bfa..5758be1290 100644 --- a/cli.py +++ b/cli.py @@ -3697,7 +3697,7 @@ class HermesCLI(CLIAgentSetupMixin, CLICommandsMixin, CLIBillingMixin, CLITuiMix """Idle-window prewarms (picker cache, agent runtime imports) plus the redaction-off and OpenClaw-residue banners.""" # Warm the /model picker cache off-thread (else its first open blocks ~1-2s). with suppress(Exception): - from hermes_cli.model_switch import prewarm_picker_cache_async + from hermes_cli.model_switch_providers import prewarm_picker_cache_async prewarm_picker_cache_async() # Pre-import the agent runtime (~1.5s: run_agent + OpenAI SDK) off-thread; the import diff --git a/gateway/slash_commands_model.py b/gateway/slash_commands_model.py index 4337db0c82..3024f6380d 100644 --- a/gateway/slash_commands_model.py +++ b/gateway/slash_commands_model.py @@ -362,7 +362,7 @@ class GatewayModelCommandsMixin: async def _send_model_picker(self, event: MessageEvent, source, adapter, session_key: str, listing_kwargs: dict, on_model_selected) -> bool: """Send the interactive /model picker; False when nothing was sent (text fallback). *source* is session-key-normalized so the picker's thread metadata lands where the next turn reads.""" - from hermes_cli.model_switch import list_picker_providers + from hermes_cli.model_switch_providers import list_picker_providers try: # off-loop: listing can hit a synchronous HTTP fetch on a stale cache # Offload blocking provider-listing (can fall through to a synchronous urllib HTTP fetch on a # stale cache) off the event loop so the gateway doesn't freeze. See #41289. diff --git a/hermes_cli/gateway.py b/hermes_cli/gateway.py index d7d0f690fb..66a8f9aba4 100644 --- a/hermes_cli/gateway.py +++ b/hermes_cli/gateway.py @@ -19,6 +19,7 @@ import textwrap import time from dataclasses import dataclass from pathlib import Path +from hermes_cli import setup_platforms # UV's bundled Python ships a minimal PATH; ensure launchctl/systemctl are discoverable. if os.name == "posix": @@ -5351,8 +5352,8 @@ def _builtin_setup_fn(key: str): from hermes_cli import setup as _s return { # telegram/discord/slack/whatsapp/dingtalk/feishu/wecom setup_fns come from their plugins. - "bluebubbles": _s._setup_bluebubbles, - "webhooks": _s._setup_webhooks, + "bluebubbles": setup_platforms._setup_bluebubbles, + "webhooks": setup_platforms._setup_webhooks, "signal": _setup_signal, "weixin": _setup_weixin, "qqbot": _setup_qqbot, diff --git a/hermes_cli/model_setup_flows_custom.py b/hermes_cli/model_setup_flows_custom.py index 25111710da..7c0711c46e 100644 --- a/hermes_cli/model_setup_flows_custom.py +++ b/hermes_cli/model_setup_flows_custom.py @@ -251,7 +251,7 @@ def _discover_named_custom_models(provider_info: dict, api_key: str, configured_ # _save_discovered_models_to_config. A failed save is non-fatal. if live_models: with contextlib.suppress(Exception): - from hermes_cli.model_switch import _save_discovered_models_to_config + from hermes_cli.model_switch_providers import _save_discovered_models_to_config _save_discovered_models_to_config(base_url, live_models, api_mode=api_mode, headers=extra_headers or None) return models, native_catalog_empty diff --git a/hermes_cli/model_switch.py b/hermes_cli/model_switch.py index 00cdb5c3dc..45c2dd1397 100644 --- a/hermes_cli/model_switch.py +++ b/hermes_cli/model_switch.py @@ -20,11 +20,7 @@ from agent.models_dev import ( ModelCapabilities, ModelInfo, get_model_capabilities, get_model_info, list_provider_models) from utils import base_url_hostname, base_url_origin # Re-exported: callers/tests patch hermes_cli.model_switch.. -from hermes_cli.model_switch_providers import ( # noqa: F401 - _NativePickerModelList, _collect_authed_provider_slugs, _credential_pool_is_usable, - _fetch_picker_live_models, _picker_prewarm_done, _prefetch_provider_models_parallel, - _save_discovered_models_to_config, list_authenticated_providers, list_picker_providers, - prewarm_picker_cache_async) +from hermes_cli.model_switch_providers import list_authenticated_providers logger = logging.getLogger(__name__) diff --git a/hermes_cli/model_switch_providers.py b/hermes_cli/model_switch_providers.py index 72e3f3e036..8580599643 100644 --- a/hermes_cli/model_switch_providers.py +++ b/hermes_cli/model_switch_providers.py @@ -284,7 +284,6 @@ def _auth_store_has_provider(*keys: str) -> bool: def _raw_pool_usable(hermes_id: str) -> bool: """Section-1 pool check: only consult the pool when auth.json lists a raw entry.""" - from hermes_cli.model_switch import _credential_pool_is_usable try: from hermes_cli.auth import _load_auth_store store = _load_auth_store() @@ -296,7 +295,6 @@ def _raw_pool_usable(hermes_id: str) -> bool: def _pool_usable(slug: str) -> bool: - from hermes_cli.model_switch import _credential_pool_is_usable try: return _credential_pool_is_usable(slug) except Exception as exc: @@ -506,7 +504,6 @@ def _discover_endpoint_models( ``has_explicit_models`` gates the *probe* (a network-cost guard for keyless endpoints that declare a catalog), never the cache read — applying it to the read re-pins the endpoint to its declared subset. Returns ``(None, False)`` when nothing usable was found.""" - from hermes_cli.model_switch import _fetch_picker_live_models timeout = 1.5 if for_picker else 5.0 if probe_live: try: @@ -705,7 +702,6 @@ def _lap_builtin_rows(b: _PickerBuild, data: dict, user_providers: dict) -> None def _overlay_has_creds(b: _PickerBuild, pid: str, hermes_slug: str, overlay) -> bool: """Section-2 credential ladder: env/SDK, external-process executable, auth store, pool, anthropic's external credential files.""" - from hermes_cli.model_switch import _credential_pool_is_usable if overlay.keyless: return True # served anonymously (opencode-free) if overlay.auth_type == "aws_sdk": @@ -909,7 +905,7 @@ def _lap_custom_provider_rows(b: _PickerBuild, custom_providers: list) -> None: (endpoint, credential identity, api_mode, extra_headers, display prefix). Four "Ollama — X" entries on one host become one "Ollama" row; distinct prefixes sharing a proxy URL keep their own rows.""" - from hermes_cli.model_switch import _extra_headers_from_config, _save_discovered_models_to_config, _scoped_key_env + from hermes_cli.model_switch import _extra_headers_from_config, _scoped_key_env from hermes_cli.config import coerce_provider_id groups: dict[tuple, dict] = {} for entry in custom_providers: @@ -1031,7 +1027,6 @@ def list_authenticated_providers( ``refresh`` busts the model-id disk cache up front (explicit user action only); ``probe_custom_providers`` enables live ``/models`` discovery for saved custom endpoints (CLI true, GUI false); ``probe_current_custom_provider`` probes only the selected custom endpoint.""" - from hermes_cli.model_switch import _collect_authed_provider_slugs, _prefetch_provider_models_parallel from agent.models_dev import fetch_models_dev from hermes_cli.config import coerce_provider_id, stringify_provider_map diff --git a/hermes_cli/nous_subscription.py b/hermes_cli/nous_subscription.py index c50755c945..5728196f48 100644 --- a/hermes_cli/nous_subscription.py +++ b/hermes_cli/nous_subscription.py @@ -13,13 +13,8 @@ from hermes_cli.nous_account import ( from tools.managed_tool_gateway import is_managed_tool_gateway_ready from utils import is_truthy_value from tools.tool_backend_helpers import ( - fal_key_is_configured, - has_direct_modal_credentials, - managed_nous_tools_enabled, # noqa: F401 (test-patchable re-export) - normalize_browser_cloud_provider, - normalize_modal_mode, - resolve_modal_backend_state, - resolve_openai_audio_api_key, + fal_key_is_configured, has_direct_modal_credentials, normalize_browser_cloud_provider, normalize_modal_mode, + resolve_modal_backend_state, resolve_openai_audio_api_key ) diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index ec96988c83..0b444ba462 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -18,8 +18,6 @@ from pathlib import Path from typing import Callable from hermes_cli.curses_ui import MenuNavigationEvent, MenuNavigationStart -from hermes_cli.nous_subscription import get_nous_subscription_features # noqa: F401 (re-export; patched by tests) -from tools.tool_backend_helpers import managed_nous_tools_enabled # noqa: F401 (re-export; patched by tests) # Config helpers are re-exported (tests patch them on this module). display_hermes_home is # imported lazily at call sites (stale-module safety during hermes update). from hermes_cli.config import ( @@ -602,27 +600,12 @@ def _record_send_consent_change(*, enabled: bool) -> None: # Extracted sections, re-exported so callers and test patches keep resolving through # hermes_cli.setup. They import this module lazily inside bodies, so this is cycle-free. -from hermes_cli.setup_tts import ( # noqa: E402,F401 - _run_xai_oauth_login_from_setup, _setup_tts_provider, _xai_oauth_logged_in_for_setup, setup_tts, -) -from hermes_cli.setup_terminal import ( # noqa: E402,F401 - _prompt_vercel_sandbox_settings, _read_nearest_vercel_project, setup_terminal_backend, -) -from hermes_cli.setup_platforms import ( # noqa: E402,F401 - _TELEGRAM_BOT_TOKEN_RE, _profile_name_from_hermes_home, _setup_bluebubbles, _setup_telegram, - _setup_telegram_auto_result, _setup_webhooks, setup_gateway, -) +from hermes_cli.setup_tts import setup_tts # noqa: E402 +from hermes_cli.setup_terminal import setup_terminal_backend # noqa: E402 +from hermes_cli.setup_platforms import setup_gateway # noqa: E402 from hermes_cli.setup_summary import _print_setup_summary # noqa: E402,F401 -from hermes_cli.setup_migration import ( # noqa: E402,F401 - _OPENCLAW_SCRIPT, _get_section_config_summary, _load_openclaw_migration_module, - _model_section_has_credentials, _offer_openclaw_migration, _print_migration_preview, - _skip_configured_section, -) -from hermes_cli.setup_quick import ( # noqa: E402,F401 - _blank_slate_minimal_toolsets, _blank_slate_minimize_config, _blank_slate_walkthrough, - _print_macos_fda_tip, _run_blank_slate_setup, _run_first_time_quick_setup, _run_portal_one_shot, - _run_quick_setup, -) +from hermes_cli.setup_migration import _offer_openclaw_migration, _skip_configured_section # noqa: E402 +from hermes_cli.setup_quick import _run_portal_one_shot, _run_quick_setup # noqa: E402 # ── Main Wizard Orchestrator ── @@ -714,8 +697,7 @@ def _run_full_setup(config: dict, hermes_home, *, is_existing: bool, migration_r _step("tools", "Tools", lambda: setup_tools(config, first_install=not is_existing))]) -# First-time mode picker: (menu label, runner name on this module) — None falls through to Full -# Setup; runners resolve at call time so test patches on hermes_cli.setup apply. +# First-time mode picker: (menu label, setup_quick runner name) — None falls through to Full Setup. _FIRST_TIME_MODES = ( ("Quick Setup (Nous Portal) — free OAuth login, no API keys, model + tools (recommended)", "_run_first_time_quick_setup"), @@ -788,7 +770,8 @@ def _run_setup_wizard_impl(args): setup_mode = prompt_choice("How would you like to set up Hermes?", [label for label, _ in _FIRST_TIME_MODES], 0) label, runner = _FIRST_TIME_MODES[setup_mode] if runner is not None: - _run_setup_steps([(label, lambda: globals()[runner](config, hermes_home, is_existing))]) + from hermes_cli import setup_quick + _run_setup_steps([(label, lambda: getattr(setup_quick, runner)(config, hermes_home, is_existing))]) return _run_full_setup(config, hermes_home, is_existing=is_existing, migration_ran=migration_ran) diff --git a/hermes_cli/setup_migration.py b/hermes_cli/setup_migration.py index 295804d42d..bef7eabba0 100644 --- a/hermes_cli/setup_migration.py +++ b/hermes_cli/setup_migration.py @@ -141,7 +141,6 @@ _OPENCLAW_SCRIPT = ( def _load_openclaw_migration_module(): """Load the openclaw_to_hermes migration script as a module; None if it can't be loaded.""" - from hermes_cli.setup import _OPENCLAW_SCRIPT if not _OPENCLAW_SCRIPT.exists(): return None spec = importlib.util.spec_from_file_location("openclaw_to_hermes", _OPENCLAW_SCRIPT) @@ -256,8 +255,8 @@ def _offer_openclaw_migration(hermes_home: Path) -> bool: """Detect ~/.openclaw and offer to migrate during first-time setup: dry-run preview first, execute only after explicit confirmation. Returns True iff migration ran successfully.""" from hermes_cli.setup import ( - get_config_path, _info, load_config, _OPENCLAW_SCRIPT, print_header, print_info, print_success, - print_warning, prompt_yes_no, save_config, + get_config_path, _info, load_config, print_header, print_info, print_success, print_warning, prompt_yes_no, + save_config ) openclaw_dir = Path.home() / ".openclaw" if not openclaw_dir.is_dir() or not _OPENCLAW_SCRIPT.exists(): diff --git a/hermes_cli/setup_quick.py b/hermes_cli/setup_quick.py index b31dcfd22f..5df4325550 100644 --- a/hermes_cli/setup_quick.py +++ b/hermes_cli/setup_quick.py @@ -85,9 +85,8 @@ def _run_first_time_quick_setup(config: dict, hermes_home, is_existing: bool): """Streamlined first-time setup via Nous Portal: OAuth, model, terminal & messaging; everything else gets defaults.""" from hermes_cli.setup import ( - _apply_default_agent_settings, _info, print_header, print_info, _print_macos_fda_tip, - _print_setup_summary, print_success, print_warning, prompt_choice, save_config, setup_gateway, - setup_terminal_backend, + _apply_default_agent_settings, _info, print_header, print_info, _print_setup_summary, print_success, + print_warning, prompt_choice, save_config, setup_gateway, setup_terminal_backend ) # Step 1: Nous Portal — OAuth login + model selection (provider set to "nous" by the save). print_header("Nous Portal", gap=True) @@ -219,9 +218,8 @@ def _run_blank_slate_setup(config: dict, hermes_home, is_existing: bool): """Blank Slate setup — essentials only, everything else OFF; then finish now or walk through opting capabilities back in. Nothing is enabled that the user did not explicitly choose.""" from hermes_cli.setup import ( - _blank_slate_minimal_toolsets, _blank_slate_minimize_config, _blank_slate_walkthrough, _info, - print_header, print_info, print_success, prompt_choice, save_config, setup_model_provider, - setup_terminal_backend, + _info, print_header, print_info, print_success, prompt_choice, save_config, setup_model_provider, + setup_terminal_backend ) print_header("Blank Slate Setup", gap=True) _info("Everything starts OFF. First we force-enable only what's required", diff --git a/hermes_cli/setup_summary.py b/hermes_cli/setup_summary.py index 4b66eb73e8..75ea280801 100644 --- a/hermes_cli/setup_summary.py +++ b/hermes_cli/setup_summary.py @@ -2,6 +2,8 @@ resolved through the module object so test patches on ``hermes_cli.setup.`` take effect.""" import logging +from tools import tool_backend_helpers +from hermes_cli import nous_subscription logger = logging.getLogger("hermes_cli.setup") @@ -160,7 +162,7 @@ def _modal_row(config, feats): if feats.modal.direct_override: return ("Modal Execution (direct Modal)", True, None) return ("Modal Execution", False, "run 'hermes setup terminal'") - if _setup.managed_nous_tools_enabled() and feats.nous_auth_present: + if tool_backend_helpers.managed_nous_tools_enabled() and feats.nous_auth_present: return ("Modal Execution (optional via Nous subscription)", True, None) return None @@ -227,7 +229,7 @@ def _print_setup_summary(config: dict, hermes_home): _setup.print_header("Tool Availability Summary") tool_status = [] - subscription_features = _setup.get_nous_subscription_features(config) + subscription_features = nous_subscription.get_nous_subscription_features(config) for build in _TOOL_ROW_BUILDERS: row = build(config, subscription_features) tool_status.extend(row if isinstance(row, list) else [] if row is None else [row]) diff --git a/hermes_cli/setup_terminal.py b/hermes_cli/setup_terminal.py index b08f064ac8..f6e1f16da6 100644 --- a/hermes_cli/setup_terminal.py +++ b/hermes_cli/setup_terminal.py @@ -8,6 +8,8 @@ import os import shutil import sys from pathlib import Path +from tools import tool_backend_helpers +from hermes_cli import nous_subscription logger = logging.getLogger("hermes_cli.setup") diff --git a/hermes_cli/setup_tts.py b/hermes_cli/setup_tts.py index d914323f3f..8c5cca8d4b 100644 --- a/hermes_cli/setup_tts.py +++ b/hermes_cli/setup_tts.py @@ -6,6 +6,8 @@ import logging import shutil import subprocess import sys +from tools import tool_backend_helpers +from hermes_cli import nous_subscription logger = logging.getLogger("hermes_cli.setup") @@ -178,7 +180,7 @@ def _tts_local_install_step(selected: str) -> str: def _xai_oauth_path(): - if _setup._run_xai_oauth_login_from_setup(): + if _run_xai_oauth_login_from_setup(): _setup.print_success("Logged in — xAI TTS will use these OAuth credentials") return None return "xAI Grok OAuth login did not complete. Falling back to Edge TTS." @@ -198,7 +200,7 @@ def _xai_api_key_path(): def _tts_xai_step(config: dict) -> str: """xAI TTS auth. Order: existing OAuth tokens (free for SuperGrok) > existing XAI_API_KEY > offer both paths — xAI TTS works with OAuth bearer tokens too.""" - if _setup._xai_oauth_logged_in_for_setup(): + if _xai_oauth_logged_in_for_setup(): _setup.print_success("xAI TTS will use your xAI Grok OAuth (SuperGrok / Premium+) credentials") elif _setup.get_env_value("XAI_API_KEY"): _setup.print_success("xAI TTS will use your existing XAI_API_KEY") @@ -230,7 +232,7 @@ def _setup_tts_provider(config: dict): _setup.print_header("Text-to-Speech Provider (optional)") _setup._info(f"Current: {current_label}", None) options = list(_TTS_PROVIDER_CHOICES) - if _setup.managed_nous_tools_enabled() and _setup.get_nous_subscription_features(config).nous_auth_present: + if tool_backend_helpers.managed_nous_tools_enabled() and nous_subscription.get_nous_subscription_features(config).nous_auth_present: options.insert(0, ("nous-openai", "Nous Subscription (managed OpenAI TTS, billed to your subscription)")) choices = [label for _, label in options] + [f"Keep current ({current_label})"] diff --git a/hermes_cli/tools_config_post_setup.py b/hermes_cli/tools_config_post_setup.py index 9f0a9e2722..1b5d0eafb4 100644 --- a/hermes_cli/tools_config_post_setup.py +++ b/hermes_cli/tools_config_post_setup.py @@ -309,7 +309,8 @@ def _post_setup_xai_grok() -> None: _print_info(" xAI needs credentials. Choose one:") try: - from hermes_cli.setup import _run_xai_oauth_login_from_setup, prompt_choice, prompt as _setup_prompt + from hermes_cli.setup import prompt_choice, prompt as _setup_prompt + from hermes_cli.setup_tts import _run_xai_oauth_login_from_setup from hermes_cli.config import save_env_value except Exception as exc: _print_warning(f" Could not load setup helpers: {exc}") diff --git a/plugins/platforms/telegram/adapter.py b/plugins/platforms/telegram/adapter.py index 38a48cfa56..4c7efd5a9f 100644 --- a/plugins/platforms/telegram/adapter.py +++ b/plugins/platforms/telegram/adapter.py @@ -13,6 +13,7 @@ import time from contextvars import ContextVar from datetime import datetime, timezone from typing import Any, Awaitable, Callable, Dict, Iterator, List, Optional, Set +from hermes_cli import setup_platforms logger = logging.getLogger(__name__) @@ -6404,7 +6405,7 @@ async def _standalone_send(pconfig, chat_id, message, *, thread_id=None, media_f def interactive_setup() -> None: """Configure Telegram credentials and allowlist via the CLI setup wizard (lazy import).""" from hermes_cli import setup as _setup_mod - _setup_mod._setup_telegram() + setup_platforms._setup_telegram() def _apply_yaml_config(yaml_cfg: dict, telegram_cfg: dict) -> dict | None: diff --git a/tests/acp/test_named_provider_catalogs.py b/tests/acp/test_named_provider_catalogs.py index 49abc02b80..eb37a2d0ec 100644 --- a/tests/acp/test_named_provider_catalogs.py +++ b/tests/acp/test_named_provider_catalogs.py @@ -45,7 +45,7 @@ class TestNamedCustomProviderCatalogs: } ) with patch("hermes_cli.config.load_config", return_value=cfg), patch( - "hermes_cli.model_switch._fetch_picker_live_models", + "hermes_cli.model_switch_providers._fetch_picker_live_models", return_value=["model-a", "model-b"], ): catalogs = _named_custom_provider_catalogs() @@ -70,7 +70,7 @@ class TestNamedCustomProviderCatalogs: } ) with patch("hermes_cli.config.load_config", return_value=cfg), patch( - "hermes_cli.model_switch._fetch_picker_live_models", return_value=None + "hermes_cli.model_switch_providers._fetch_picker_live_models", return_value=None ): assert _named_custom_provider_catalogs() == [] @@ -86,7 +86,7 @@ class TestNamedCustomProviderCatalogs: } ) with patch("hermes_cli.config.load_config", return_value=cfg), patch( - "hermes_cli.model_switch._fetch_picker_live_models", return_value=None + "hermes_cli.model_switch_providers._fetch_picker_live_models", return_value=None ): assert _named_custom_provider_catalogs() == [] @@ -103,7 +103,7 @@ class TestNamedCustomProviderCatalogs: ] ) with patch("hermes_cli.config.load_config", return_value=cfg), patch( - "hermes_cli.model_switch._fetch_picker_live_models", return_value=None + "hermes_cli.model_switch_providers._fetch_picker_live_models", return_value=None ): catalogs = _named_custom_provider_catalogs() @@ -123,7 +123,7 @@ class TestNamedCustomProviderCatalogs: "hermes_cli.models.should_use_ollama_native_catalog", return_value=True, ), patch( - "hermes_cli.model_switch._fetch_picker_live_models", + "hermes_cli.model_switch_providers._fetch_picker_live_models", return_value=["qwen3:1.7b"], ) as fetch: catalogs = _named_custom_provider_catalogs() @@ -151,7 +151,7 @@ class TestNamedCustomProviderCatalogs: "hermes_cli.models.should_use_ollama_native_catalog", return_value=True, ), patch( - "hermes_cli.model_switch._fetch_picker_live_models", + "hermes_cli.model_switch_providers._fetch_picker_live_models", return_value=["qwen3:1.7b"], ) as fetch: catalogs = _named_custom_provider_catalogs() @@ -169,13 +169,13 @@ class TestNamedCustomProviderCatalogs: } } ) - from hermes_cli.model_switch import _NativePickerModelList + from hermes_cli.model_switch_providers import _NativePickerModelList with patch("hermes_cli.config.load_config", return_value=cfg), patch( "hermes_cli.models.should_use_ollama_native_catalog", return_value=True, ), patch( - "hermes_cli.model_switch._fetch_picker_live_models", + "hermes_cli.model_switch_providers._fetch_picker_live_models", return_value=_NativePickerModelList(), ): assert _named_custom_provider_catalogs() == [ diff --git a/tests/gateway/test_25107_stale_base_url_api_mode.py b/tests/gateway/test_25107_stale_base_url_api_mode.py index b164af302d..cbfdbe79ca 100644 --- a/tests/gateway/test_25107_stale_base_url_api_mode.py +++ b/tests/gateway/test_25107_stale_base_url_api_mode.py @@ -90,7 +90,7 @@ def _setup_isolated_home(tmp_path, monkeypatch, model_yaml_value, *, base_url="" monkeypatch.setattr(gateway_run, "_hermes_home", hermes_home) monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {}) monkeypatch.setattr( - "hermes_cli.model_switch.list_picker_providers", + "hermes_cli.model_switch_providers.list_picker_providers", lambda **kw: [{"slug": "custom", "name": "Custom", "models": ["local-llama"]}], ) monkeypatch.setattr( diff --git a/tests/gateway/test_model_command_async_offload.py b/tests/gateway/test_model_command_async_offload.py index c08e4b53f1..9936461a33 100644 --- a/tests/gateway/test_model_command_async_offload.py +++ b/tests/gateway/test_model_command_async_offload.py @@ -119,7 +119,7 @@ async def test_picker_path_offloads_list_picker_providers(_isolated_config, monk return fake_providers monkeypatch.setattr( - "hermes_cli.model_switch.list_picker_providers", + "hermes_cli.model_switch_providers.list_picker_providers", _fake_list_picker_providers, ) diff --git a/tests/gateway/test_model_command_profile_config.py b/tests/gateway/test_model_command_profile_config.py index 011f6db0fa..00fc9aa708 100644 --- a/tests/gateway/test_model_command_profile_config.py +++ b/tests/gateway/test_model_command_profile_config.py @@ -50,7 +50,7 @@ async def test_model_picker_reads_routed_profile_config(tmp_path, monkeypatch): monkeypatch.setattr(gateway_run, "_hermes_home", default_home) monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {}) monkeypatch.setattr( - "hermes_cli.model_switch.list_picker_providers", + "hermes_cli.model_switch_providers.list_picker_providers", lambda **_kwargs: [ { "slug": "secondary-provider", diff --git a/tests/gateway/test_model_picker_persist.py b/tests/gateway/test_model_picker_persist.py index 1f0782991d..7f5c4c04a3 100644 --- a/tests/gateway/test_model_picker_persist.py +++ b/tests/gateway/test_model_picker_persist.py @@ -84,7 +84,7 @@ def _fake_switch_result(): def _stub_picker_dependencies(monkeypatch): monkeypatch.setattr("agent.models_dev.fetch_models_dev", lambda: {}) monkeypatch.setattr( - "hermes_cli.model_switch.list_picker_providers", + "hermes_cli.model_switch_providers.list_picker_providers", lambda **kw: [{"slug": "openrouter", "name": "OpenRouter", "models": ["gpt-5.5"]}], ) monkeypatch.setattr( diff --git a/tests/hermes_cli/test_authenticated_providers_exhausted_pool.py b/tests/hermes_cli/test_authenticated_providers_exhausted_pool.py index 04c8680d80..0da911a22a 100644 --- a/tests/hermes_cli/test_authenticated_providers_exhausted_pool.py +++ b/tests/hermes_cli/test_authenticated_providers_exhausted_pool.py @@ -69,7 +69,7 @@ def test_exhausted_pool_provider_is_not_authenticated(monkeypatch): def test_opaque_legacy_pool_value_stays_visible(monkeypatch): """Legacy token-style auth-store values have no parsed pool entries.""" - from hermes_cli.model_switch import _credential_pool_is_usable + from hermes_cli.model_switch_providers import _credential_pool_is_usable monkeypatch.setattr( "agent.credential_pool.load_pool", @@ -90,7 +90,7 @@ def test_picker_shows_exhausted_pool_provider(monkeypatch): """The interactive picker must include providers whose credential pool entries are all exhausted, so the user can still switch to a different model under the same provider.""" - from hermes_cli.model_switch import list_picker_providers + from hermes_cli.model_switch_providers import list_picker_providers _patch_opencode_pool(monkeypatch, available=False) providers = list_picker_providers( diff --git a/tests/hermes_cli/test_copilot_in_model_list.py b/tests/hermes_cli/test_copilot_in_model_list.py index 2889627964..3fe3aabbf7 100644 --- a/tests/hermes_cli/test_copilot_in_model_list.py +++ b/tests/hermes_cli/test_copilot_in_model_list.py @@ -6,6 +6,7 @@ from unittest.mock import patch import pytest from hermes_cli.model_switch import list_authenticated_providers +from hermes_cli import model_switch_providers @patch.dict(os.environ, {"GH_TOKEN": "test-key"}, clear=False) @@ -48,7 +49,7 @@ def _no_other_copilot_creds(monkeypatch): import hermes_cli.model_switch as model_switch monkeypatch.setattr(auth, "_load_auth_store", lambda: {}) - monkeypatch.setattr(model_switch, "_credential_pool_is_usable", lambda *a, **k: False) + monkeypatch.setattr(model_switch_providers, "_credential_pool_is_usable", lambda *a, **k: False) def test_copilot_acp_listed_when_executable_resolves(tmp_path, monkeypatch, _no_other_copilot_creds): diff --git a/tests/hermes_cli/test_list_picker_providers.py b/tests/hermes_cli/test_list_picker_providers.py index 508868c098..0fdea335cd 100644 --- a/tests/hermes_cli/test_list_picker_providers.py +++ b/tests/hermes_cli/test_list_picker_providers.py @@ -17,6 +17,7 @@ network or auth state is required. import pytest from hermes_cli import model_switch +from hermes_cli import model_switch_providers @pytest.fixture(autouse=True) @@ -75,7 +76,7 @@ def test_passthrough_kwargs_to_base(monkeypatch): monkeypatch.setattr("hermes_cli.models.fetch_openrouter_models", lambda *a, **kw: []) - model_switch.list_picker_providers( + model_switch_providers.list_picker_providers( current_provider="openrouter", current_base_url="http://x", current_model="openai/gpt-5.4", @@ -101,7 +102,7 @@ def test_current_custom_endpoint_passthrough_marks_current_row(monkeypatch): monkeypatch.setattr("hermes_cli.models.fetch_openrouter_models", lambda *a, **kw: []) - result = model_switch.list_picker_providers( + result = model_switch_providers.list_picker_providers( current_provider="custom:ollama", current_base_url="http://localhost:11434/v1", current_model="glm-5.1", diff --git a/tests/hermes_cli/test_macos_fda_guidance.py b/tests/hermes_cli/test_macos_fda_guidance.py index 98d797156f..4c8b2da133 100644 --- a/tests/hermes_cli/test_macos_fda_guidance.py +++ b/tests/hermes_cli/test_macos_fda_guidance.py @@ -12,7 +12,7 @@ import contextlib import hermes_cli.doctor as doctor_mod import pathlib -from hermes_cli.setup import _print_macos_fda_tip +from hermes_cli.setup_quick import _print_macos_fda_tip from hermes_cli import doctor_platform diff --git a/tests/hermes_cli/test_model_cache_parallel_prefetch.py b/tests/hermes_cli/test_model_cache_parallel_prefetch.py index 17a2ec202e..61d8b15e3d 100644 --- a/tests/hermes_cli/test_model_cache_parallel_prefetch.py +++ b/tests/hermes_cli/test_model_cache_parallel_prefetch.py @@ -13,6 +13,7 @@ import time from unittest.mock import patch, MagicMock import pytest +from hermes_cli import model_switch_providers # --------------------------------------------------------------------------- @@ -101,7 +102,7 @@ class TestPrefetchProviderModelsParallel: def test_skips_all_fresh_entries(self, monkeypatch): """When all cache entries are fresh, no fetch is made.""" - from hermes_cli.model_switch import _prefetch_provider_models_parallel + from hermes_cli.model_switch_providers import _prefetch_provider_models_parallel fresh_cache = { "openrouter": {"fp": "fp", "at": time.time(), "models": ["m1"]}, @@ -117,7 +118,7 @@ class TestPrefetchProviderModelsParallel: def test_fetches_only_stale_entries(self, monkeypatch): """Only providers with stale/missing cache entries are fetched.""" - from hermes_cli.model_switch import _prefetch_provider_models_parallel + from hermes_cli.model_switch_providers import _prefetch_provider_models_parallel cache = { "fresh_prov": {"fp": "fp_f", "at": time.time(), "models": ["m1"]}, @@ -140,7 +141,7 @@ class TestPrefetchProviderModelsParallel: def test_fetches_in_parallel(self, monkeypatch): """Multiple providers are fetched concurrently, not serially.""" - from hermes_cli.model_switch import _prefetch_provider_models_parallel + from hermes_cli.model_switch_providers import _prefetch_provider_models_parallel # Track overlap: if serial, no two fetches should overlap in time. active = [] @@ -168,7 +169,7 @@ class TestPrefetchProviderModelsParallel: def test_swallows_exceptions(self): """A failing provider fetch doesn't raise — best-effort.""" - from hermes_cli.model_switch import _prefetch_provider_models_parallel + from hermes_cli.model_switch_providers import _prefetch_provider_models_parallel def mock_fetch(slug, force_refresh=False): raise ConnectionError("simulated network failure") @@ -182,7 +183,7 @@ class TestPrefetchProviderModelsParallel: def test_empty_list_is_noop(self): """Empty provider list does nothing.""" - from hermes_cli.model_switch import _prefetch_provider_models_parallel + from hermes_cli.model_switch_providers import _prefetch_provider_models_parallel with patch("hermes_cli.models.cached_provider_model_ids") as fetch: _prefetch_provider_models_parallel([]) @@ -206,8 +207,8 @@ class TestPrefetchIntegration: def mock_collect(data, curated, excluded): return slugs - with patch.object(model_switch, "_collect_authed_provider_slugs", side_effect=mock_collect), \ - patch.object(model_switch, "_prefetch_provider_models_parallel") as prefetch: + with patch.object(model_switch_providers, "_collect_authed_provider_slugs", side_effect=mock_collect), \ + patch.object(model_switch_providers, "_prefetch_provider_models_parallel") as prefetch: try: model_switch.list_authenticated_providers() except Exception: @@ -226,8 +227,8 @@ class TestPrefetchIntegration: def mock_collect(data, curated, excluded): return slugs - with patch.object(model_switch, "_collect_authed_provider_slugs", side_effect=mock_collect), \ - patch.object(model_switch, "_prefetch_provider_models_parallel") as prefetch: + with patch.object(model_switch_providers, "_collect_authed_provider_slugs", side_effect=mock_collect), \ + patch.object(model_switch_providers, "_prefetch_provider_models_parallel") as prefetch: try: model_switch.list_authenticated_providers() except Exception: @@ -239,8 +240,8 @@ class TestPrefetchIntegration: """When refresh=True, prefetch is skipped (serial path force-refreshes).""" from hermes_cli import model_switch - with patch.object(model_switch, "_collect_authed_provider_slugs") as collect, \ - patch.object(model_switch, "_prefetch_provider_models_parallel") as prefetch: + with patch.object(model_switch_providers, "_collect_authed_provider_slugs") as collect, \ + patch.object(model_switch_providers, "_prefetch_provider_models_parallel") as prefetch: try: model_switch.list_authenticated_providers(refresh=True) except Exception: diff --git a/tests/hermes_cli/test_model_catalog.py b/tests/hermes_cli/test_model_catalog.py index 3e9c1844ff..7478d24a57 100644 --- a/tests/hermes_cli/test_model_catalog.py +++ b/tests/hermes_cli/test_model_catalog.py @@ -354,7 +354,7 @@ class TestIntegrationWithModelsModule: from hermes_cli.models import get_curated_nous_model_ids importlib.reload(model_catalog) try: - from hermes_cli.model_switch import list_picker_providers + from hermes_cli.model_switch_providers import list_picker_providers active_home = Path(os.environ["HERMES_HOME"]) (active_home / "auth.json").write_text( @@ -403,10 +403,8 @@ class TestIntegrationWithModelsModule: from hermes_cli.models import get_curated_nous_model_ids importlib.reload(model_catalog) try: - from hermes_cli.model_switch import ( - list_authenticated_providers, - list_picker_providers, - ) + from hermes_cli.model_switch import list_authenticated_providers + from hermes_cli.model_switch_providers import list_picker_providers active_home = Path(os.environ["HERMES_HOME"]) (active_home / "auth.json").write_text( diff --git a/tests/hermes_cli/test_model_switch_custom_providers.py b/tests/hermes_cli/test_model_switch_custom_providers.py index 0a42a3795c..62c1b6c374 100644 --- a/tests/hermes_cli/test_model_switch_custom_providers.py +++ b/tests/hermes_cli/test_model_switch_custom_providers.py @@ -15,12 +15,8 @@ import time import hermes_cli.providers as providers_mod import pytest import yaml -from hermes_cli.model_switch import ( - _fetch_picker_live_models, - _save_discovered_models_to_config, - list_authenticated_providers, - switch_model, -) +from hermes_cli.model_switch import list_authenticated_providers, switch_model +from hermes_cli.model_switch_providers import _fetch_picker_live_models, _save_discovered_models_to_config from hermes_cli.providers import resolve_provider_full @@ -976,7 +972,7 @@ def test_list_authenticated_providers_current_endpoint_uses_current_slug(monkeyp def test_picker_endpoint_authorization_overrides_inferred_bearer(monkeypatch): - from hermes_cli.model_switch import _fetch_picker_live_models + from hermes_cli.model_switch_providers import _fetch_picker_live_models captured: dict[str, str] = {} @@ -1463,7 +1459,7 @@ def test_discovered_models_auto_saved_to_cache(monkeypatch): monkeypatch.setattr("hermes_cli.models.fetch_api_models", fake_fetch_api_models) monkeypatch.setattr( - "hermes_cli.model_switch._save_discovered_models_to_config", + "hermes_cli.model_switch_providers._save_discovered_models_to_config", lambda api_url, model_ids, **kwargs: save_calls.append((api_url, model_ids)), ) @@ -1506,7 +1502,7 @@ def test_save_discovered_models_preserves_dict_form(monkeypatch): """``_save_discovered_models_to_config`` must not replace a dict-form ``models`` mapping (per-model metadata like ``context_length``) with a flat list of strings (#67841).""" - from hermes_cli.model_switch import _save_discovered_models_to_config + from hermes_cli.model_switch_providers import _save_discovered_models_to_config save_calls = [] @@ -1575,7 +1571,7 @@ def test_model_flow_named_custom_persists_discovered_models(monkeypatch): save_calls = [] monkeypatch.setattr( - "hermes_cli.model_switch._save_discovered_models_to_config", + "hermes_cli.model_switch_providers._save_discovered_models_to_config", lambda api_url, model_ids, **kwargs: save_calls.append( (api_url, model_ids, kwargs) ), @@ -1942,7 +1938,7 @@ def test_cached_catalog_is_not_written_back_to_config(monkeypatch): _seed_custom_model_cache(monkeypatch, _LOCAL_CATALOG) saves = [] monkeypatch.setattr( - "hermes_cli.model_switch._save_discovered_models_to_config", + "hermes_cli.model_switch_providers._save_discovered_models_to_config", lambda api_url, model_ids, **kwargs: saves.append((api_url, model_ids)), ) diff --git a/tests/hermes_cli/test_nous_policy_surfaces.py b/tests/hermes_cli/test_nous_policy_surfaces.py index 04146a617f..5a7bd75a41 100644 --- a/tests/hermes_cli/test_nous_policy_surfaces.py +++ b/tests/hermes_cli/test_nous_policy_surfaces.py @@ -11,6 +11,7 @@ import argparse import pytest import hermes_cli.models as models_mod +from hermes_cli import model_switch_providers CURATED = ["vendor/allowed", "vendor/blocked"] ALLOWED = {"vendor/allowed"} @@ -208,7 +209,7 @@ class TestNousPrefetch: auth_mod, "_load_auth_store", lambda *a, **k: {"providers": {"nous": {"access_token": "tok"}}}, ) - slugs = ms._collect_authed_provider_slugs({}, {"nous": list(CURATED)}, []) + slugs = model_switch_providers._collect_authed_provider_slugs({}, {"nous": list(CURATED)}, []) assert "nous" not in slugs diff --git a/tests/hermes_cli/test_nous_subscription.py b/tests/hermes_cli/test_nous_subscription.py index 7f18714a46..61a0a8c736 100644 --- a/tests/hermes_cli/test_nous_subscription.py +++ b/tests/hermes_cli/test_nous_subscription.py @@ -5,6 +5,7 @@ import sys from hermes_cli.nous_account import NousPortalAccountInfo, NousToolAccessInfo from hermes_cli import nous_subscription as ns +from tools import tool_backend_helpers _POOL_COVERAGE = { @@ -396,7 +397,7 @@ def test_prompt_enable_tool_gateway_choosing_declined_tool_clears_decline(monkey def test_apply_nous_managed_defaults_writes_video_gen_config(monkeypatch): """apply_nous_managed_defaults must store the managed 'nous' selection when a Nous subscriber selects video_gen without a direct FAL_KEY.""" - monkeypatch.setattr(ns, "managed_nous_tools_enabled", lambda **kw: True) + monkeypatch.setattr(tool_backend_helpers, "managed_nous_tools_enabled", lambda **kw: True) monkeypatch.delenv("FAL_KEY", raising=False) monkeypatch.setattr(ns, "fal_key_is_configured", lambda: False) monkeypatch.setattr( diff --git a/tests/hermes_cli/test_picker_prewarm.py b/tests/hermes_cli/test_picker_prewarm.py index a83064ea87..8f1c27a07b 100644 --- a/tests/hermes_cli/test_picker_prewarm.py +++ b/tests/hermes_cli/test_picker_prewarm.py @@ -12,10 +12,11 @@ from __future__ import annotations from unittest.mock import patch import hermes_cli.model_switch as ms +from hermes_cli import model_switch_providers def _reset_guard(): - ms._picker_prewarm_done.clear() + model_switch_providers._picker_prewarm_done.clear() def test_prewarm_runs_list_authenticated_providers_once(): @@ -23,7 +24,7 @@ def test_prewarm_runs_list_authenticated_providers_once(): the warm side effect is delegated there (which disk-caches per provider).""" _reset_guard() with patch.object(ms, "list_authenticated_providers", return_value=[]) as mock_list: - t = ms.prewarm_picker_cache_async() + t = model_switch_providers.prewarm_picker_cache_async() assert t is not None, "first call must spawn a prewarm thread" t.join(timeout=10) assert not t.is_alive(), "prewarm thread should finish promptly" @@ -36,12 +37,12 @@ def test_prewarm_guard_is_once_per_process(): long-lived process never leaks one OS thread per call.""" _reset_guard() with patch.object(ms, "list_authenticated_providers", return_value=[]): - t1 = ms.prewarm_picker_cache_async() + t1 = model_switch_providers.prewarm_picker_cache_async() assert t1 is not None t1.join(timeout=10) # Subsequent calls return None (guard set) — no new thread. - assert ms.prewarm_picker_cache_async() is None - assert ms.prewarm_picker_cache_async() is None + assert model_switch_providers.prewarm_picker_cache_async() is None + assert model_switch_providers.prewarm_picker_cache_async() is None _reset_guard() @@ -116,7 +117,7 @@ def test_prewarm_warms_the_active_custom_endpoint_for_the_next_open(monkeypatch) probe_current_custom_provider=True, ) - t = ms.prewarm_picker_cache_async() + t = model_switch_providers.prewarm_picker_cache_async() assert t is not None t.join(timeout=10) diff --git a/tests/hermes_cli/test_setup.py b/tests/hermes_cli/test_setup.py index 928e7863c8..a2721a2e4d 100644 --- a/tests/hermes_cli/test_setup.py +++ b/tests/hermes_cli/test_setup.py @@ -129,7 +129,7 @@ def test_select_provider_and_model_warns_if_named_custom_provider_disappears( def test_modal_setup_persists_direct_mode_when_user_chooses_their_own_account(tmp_path, monkeypatch): - monkeypatch.setattr("hermes_cli.setup.managed_nous_tools_enabled", lambda: True) + monkeypatch.setattr("tools.tool_backend_helpers.managed_nous_tools_enabled", lambda: True) monkeypatch.setenv("HERMES_HOME", str(tmp_path)) monkeypatch.delenv("MODAL_TOKEN_ID", raising=False) monkeypatch.delenv("MODAL_TOKEN_SECRET", raising=False) @@ -147,7 +147,7 @@ def test_modal_setup_persists_direct_mode_when_user_chooses_their_own_account(tm monkeypatch.setattr("hermes_cli.setup.prompt_choice", fake_prompt_choice) monkeypatch.setattr("hermes_cli.setup.prompt", lambda *args, **kwargs: next(prompt_values)) monkeypatch.setattr( - "hermes_cli.setup.get_nous_subscription_features", + "hermes_cli.nous_subscription.get_nous_subscription_features", lambda config: type("Features", (), {"nous_auth_present": True})(), ) monkeypatch.setitem( diff --git a/tests/hermes_cli/test_setup_blank_slate.py b/tests/hermes_cli/test_setup_blank_slate.py index a3c909fc6e..c526b32686 100644 --- a/tests/hermes_cli/test_setup_blank_slate.py +++ b/tests/hermes_cli/test_setup_blank_slate.py @@ -8,10 +8,8 @@ resolver + tool-schema builder yield exactly the file/terminal tools. import pytest -from hermes_cli.setup import ( - _blank_slate_minimal_toolsets, - _blank_slate_minimize_config, -) +from hermes_cli.setup_quick import _blank_slate_minimal_toolsets, _blank_slate_minimize_config +from hermes_cli import setup_quick class TestBlankSlateMinimalToolsets: @@ -113,14 +111,14 @@ class TestBlankSlateFork: # Fork prompt returns 0 = finish now. monkeypatch.setattr(s, "prompt_choice", lambda *a, **k: 0) walked = {"called": False} - monkeypatch.setattr(s, "_blank_slate_walkthrough", + monkeypatch.setattr(setup_quick, "_blank_slate_walkthrough", lambda cfg, home: walked.__setitem__("called", True)) opted_out = {"value": None} monkeypatch.setattr("tools.skills_sync_bundled_ops.set_bundled_skills_opt_out", lambda enabled: opted_out.__setitem__("value", enabled)) cfg = {} - s._run_blank_slate_setup(cfg, tmp_path, is_existing=False) + setup_quick._run_blank_slate_setup(cfg, tmp_path, is_existing=False) # Minimal baseline was applied, walkthrough was NOT run. assert cfg["platform_toolsets"]["cli"] == ["file", "skills", "terminal", "vision"] diff --git a/tests/hermes_cli/test_setup_openclaw_migration.py b/tests/hermes_cli/test_setup_openclaw_migration.py index d864db8c1d..3e6bcbb1d1 100644 --- a/tests/hermes_cli/test_setup_openclaw_migration.py +++ b/tests/hermes_cli/test_setup_openclaw_migration.py @@ -5,6 +5,7 @@ from types import ModuleType from unittest.mock import MagicMock, patch from hermes_cli import setup as setup_mod +from hermes_cli import setup_migration # --------------------------------------------------------------------------- @@ -25,7 +26,7 @@ class TestOfferOpenclawMigration: script.write_text("# placeholder") with ( patch("hermes_cli.setup.Path.home", return_value=tmp_path), - patch.object(setup_mod, "_OPENCLAW_SCRIPT", script), + patch.object(setup_migration, "_OPENCLAW_SCRIPT", script), patch.object(setup_mod, "prompt_yes_no", return_value=False), ): assert setup_mod._offer_openclaw_migration(tmp_path / ".hermes") is False @@ -57,7 +58,7 @@ class TestOfferOpenclawMigration: with ( patch("hermes_cli.setup.Path.home", return_value=tmp_path), - patch.object(setup_mod, "_OPENCLAW_SCRIPT", script), + patch.object(setup_migration, "_OPENCLAW_SCRIPT", script), # Both prompts answered Yes: preview offer + proceed confirmation patch.object(setup_mod, "prompt_yes_no", return_value=True), patch.object(setup_mod, "get_config_path", return_value=config_path), @@ -115,7 +116,7 @@ class TestOfferOpenclawMigration: with ( patch("hermes_cli.setup.Path.home", return_value=tmp_path), - patch.object(setup_mod, "_OPENCLAW_SCRIPT", script), + patch.object(setup_migration, "_OPENCLAW_SCRIPT", script), patch.object(setup_mod, "prompt_yes_no", return_value=True), patch.object(setup_mod, "get_config_path", return_value=config_path), patch( @@ -219,7 +220,7 @@ class TestGetSectionConfigSummary: def test_model_returns_none_without_api_key(self): with patch.object(setup_mod, "get_env_value", return_value=""): - result = setup_mod._get_section_config_summary({}, "model") + result = setup_migration._get_section_config_summary({}, "model") assert result is None @@ -242,7 +243,7 @@ class TestGetSectionConfigSummary: return "sk-ant-oat01-xxx" if key == "CLAUDE_CODE_OAUTH_TOKEN" else "" with patch.object(setup_mod, "get_env_value", side_effect=env_side): - result = setup_mod._get_section_config_summary({}, "model") + result = setup_migration._get_section_config_summary({}, "model") assert result is None diff --git a/tests/hermes_cli/test_setup_reconfigure.py b/tests/hermes_cli/test_setup_reconfigure.py index da2cd9eaf7..0c07b9403a 100644 --- a/tests/hermes_cli/test_setup_reconfigure.py +++ b/tests/hermes_cli/test_setup_reconfigure.py @@ -177,7 +177,7 @@ class TestFreshInstall: m = _enter_fresh_install_patches( stack, prompt=("hermes_cli.setup.prompt_choice", {"return_value": 0}), - first="hermes_cli.setup._run_first_time_quick_setup", + first="hermes_cli.setup_quick._run_first_time_quick_setup", ) from hermes_cli.setup import run_setup_wizard from hermes_cli import setup as setup_mod @@ -199,7 +199,7 @@ class TestFreshInstall: m = _enter_fresh_install_patches( stack, prompt=("hermes_cli.setup.prompt_choice", {"return_value": 2}), - blank="hermes_cli.setup._run_blank_slate_setup", + blank="hermes_cli.setup_quick._run_blank_slate_setup", ) from hermes_cli import setup as setup_mod diff --git a/tests/hermes_cli/test_setup_summary_provider_warning.py b/tests/hermes_cli/test_setup_summary_provider_warning.py index 4d8cfb2ad9..3f6a854b83 100644 --- a/tests/hermes_cli/test_setup_summary_provider_warning.py +++ b/tests/hermes_cli/test_setup_summary_provider_warning.py @@ -9,6 +9,7 @@ working model configured (consumer-onboarding audit finding #7, Aug 2026). from unittest.mock import patch from hermes_cli.auth import AuthError +from hermes_cli import nous_subscription def _summary_output(capsys, provider_ready: bool): @@ -25,7 +26,7 @@ def _summary_output(capsys, provider_ready: bool): # Keep the summary fast/hermetic: stub the heavier feature probes. with patch("hermes_cli.auth.resolve_provider", resolver), \ - patch.object(setup_mod, "get_nous_subscription_features") as feats: + patch.object(nous_subscription, "get_nous_subscription_features") as feats: feats.side_effect = Exception("stubbed") try: setup_mod._print_setup_summary({}, "/tmp/nowhere") diff --git a/tests/hermes_cli/test_setup_tts_xai_oauth.py b/tests/hermes_cli/test_setup_tts_xai_oauth.py index 2b327beecf..57b5446792 100644 --- a/tests/hermes_cli/test_setup_tts_xai_oauth.py +++ b/tests/hermes_cli/test_setup_tts_xai_oauth.py @@ -63,7 +63,7 @@ def test_run_xai_oauth_login_from_setup_does_not_hijack_active_provider( monkeypatch.setattr("hermes_cli.auth._is_remote_session", lambda: True) from hermes_cli.auth import is_source_suppressed, suppress_credential_source - from hermes_cli.setup import _run_xai_oauth_login_from_setup + from hermes_cli.setup_tts import _run_xai_oauth_login_from_setup suppress_credential_source("xai-oauth", "device_code") assert is_source_suppressed("xai-oauth", "device_code") is True diff --git a/tests/hermes_cli/test_telegram_managed_bot.py b/tests/hermes_cli/test_telegram_managed_bot.py index 1397d99e30..5b3e805528 100644 --- a/tests/hermes_cli/test_telegram_managed_bot.py +++ b/tests/hermes_cli/test_telegram_managed_bot.py @@ -13,6 +13,7 @@ from hermes_cli.telegram_managed_bot import ( print_qr_code, render_qr_terminal, ) +from hermes_cli import setup_platforms VALID_TOKEN = "123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef" @@ -173,11 +174,11 @@ class TestSetupTelegramAuto: fake_auto_setup_telegram_bot_result, ) - assert setup._setup_telegram_auto_result() is None + assert setup_platforms._setup_telegram_auto_result() is None assert seen["profile_name"] == "oracle" def test_profile_name_from_home_path_handles_windows_separators(self): - from hermes_cli.setup import _profile_name_from_hermes_home + from hermes_cli.setup_platforms import _profile_name_from_hermes_home assert ( _profile_name_from_hermes_home( diff --git a/tests/hermes_cli/test_tools_config.py b/tests/hermes_cli/test_tools_config.py index d3cf9be51a..f69dadf624 100644 --- a/tests/hermes_cli/test_tools_config.py +++ b/tests/hermes_cli/test_tools_config.py @@ -233,7 +233,7 @@ def test_first_install_nous_auto_configures_video_gen(monkeypatch): video_gen.use_gateway so the FAL plugin can route through the gateway at runtime. Regression test for the bug where video_gen was marked as auto-configured but no config was actually written.""" - monkeypatch.setattr("hermes_cli.nous_subscription.managed_nous_tools_enabled", lambda: True) + monkeypatch.setattr("tools.tool_backend_helpers.managed_nous_tools_enabled", lambda: True) config = { "model": {"provider": "nous"}, "platform_toolsets": {"cli": []}, diff --git a/tests/tui_gateway/test_94895_backend_heartbeat.py b/tests/tui_gateway/test_94895_backend_heartbeat.py index ddaecaec27..b7d42f4df2 100644 --- a/tests/tui_gateway/test_94895_backend_heartbeat.py +++ b/tests/tui_gateway/test_94895_backend_heartbeat.py @@ -21,6 +21,7 @@ import os import pytest from hermes_state import SessionDB +from hermes_cli import model_switch_providers IDLE_S = 6 * 3600 @@ -134,7 +135,7 @@ class TestEntryAndWsWiring: monkeypatch.setattr(entry.sys, "stdin", io.StringIO("")) import hermes_cli.model_switch as ms - monkeypatch.setattr(ms, "prewarm_picker_cache_async", lambda: None) + monkeypatch.setattr(model_switch_providers, "prewarm_picker_cache_async", lambda: None) entry.main() assert started["n"] == 1 diff --git a/tests/tui_gateway/test_entry_picker_prewarm.py b/tests/tui_gateway/test_entry_picker_prewarm.py index 5db185b15f..a4bb4d17a2 100644 --- a/tests/tui_gateway/test_entry_picker_prewarm.py +++ b/tests/tui_gateway/test_entry_picker_prewarm.py @@ -9,7 +9,7 @@ fetches for every authenticated provider (#72021). These tests pin the entrypoint wiring itself (the helper's own worker/once guard is covered in ``tests/hermes_cli/test_picker_prewarm.py``): -- ``main()`` invokes ``hermes_cli.model_switch.prewarm_picker_cache_async`` +- ``main()`` invokes ``hermes_cli.model_switch_providers.prewarm_picker_cache_async`` exactly once, AFTER the ``gateway.ready`` event is written (banner shown, user about to type — the idle window the prewarm is meant to fill). - The startup path stays non-blocking: with the prewarm spied out, ``main()`` @@ -28,6 +28,8 @@ import io import hermes_cli.model_switch as ms from tui_gateway import entry +import hermes_cli.model_switch_providers +from hermes_cli import model_switch_providers def _run_main(monkeypatch, events, *, prewarm=None): @@ -58,7 +60,7 @@ def _run_main(monkeypatch, events, *, prewarm=None): events.append(("prewarm",)) return None # fire-and-forget handle; never blocks - monkeypatch.setattr(ms, "prewarm_picker_cache_async", prewarm) + monkeypatch.setattr(model_switch_providers, "prewarm_picker_cache_async", prewarm) # Empty stdin -> immediate EOF -> main() returns after entering the loop. monkeypatch.setattr(entry.sys, "stdin", io.StringIO("")) diff --git a/tests/tui_gateway/test_startup_orphan_sweep.py b/tests/tui_gateway/test_startup_orphan_sweep.py index 38c3e11e7f..90190a065e 100644 --- a/tests/tui_gateway/test_startup_orphan_sweep.py +++ b/tests/tui_gateway/test_startup_orphan_sweep.py @@ -17,6 +17,7 @@ import types from hermes_state import SessionDB from tui_gateway import entry, server +from hermes_cli import model_switch_providers IDLE_S = 6 * 3600 @@ -239,7 +240,7 @@ class TestEntryAndWsWiring: # Prewarm is imported lazily inside main(); keep it inert. import hermes_cli.model_switch as ms - monkeypatch.setattr(ms, "prewarm_picker_cache_async", lambda: None) + monkeypatch.setattr(model_switch_providers, "prewarm_picker_cache_async", lambda: None) entry.main() assert scheduled["n"] == 1 @@ -289,6 +290,6 @@ class TestEntryAndWsWiring: monkeypatch.setattr(entry.sys, "stdin", io.StringIO("")) import hermes_cli.model_switch as ms - monkeypatch.setattr(ms, "prewarm_picker_cache_async", lambda: None) + monkeypatch.setattr(model_switch_providers, "prewarm_picker_cache_async", lambda: None) entry.main() # must not raise diff --git a/tui_gateway/entry.py b/tui_gateway/entry.py index 725d17ac0c..8697e01f0d 100644 --- a/tui_gateway/entry.py +++ b/tui_gateway/entry.py @@ -266,7 +266,7 @@ def main(): # Warm the /model picker's provider-models cache in this idle window (fire-and-forget). try: - from hermes_cli.model_switch import prewarm_picker_cache_async + from hermes_cli.model_switch_providers import prewarm_picker_cache_async prewarm_picker_cache_async() except Exception: logger.debug("picker cache prewarm (tui) failed to start", exc_info=True)