diff --git a/hermes_cli/tools_config.py b/hermes_cli/tools_config.py index 326d3bb48..e401a2342 100644 --- a/hermes_cli/tools_config.py +++ b/hermes_cli/tools_config.py @@ -202,6 +202,13 @@ TOOL_CATEGORIES = { "name": "Text-to-Speech", "icon": "๐Ÿ”Š", "providers": [ + { + "name": "Microsoft Edge TTS", + "badge": "โ˜… recommended ยท free", + "tag": "Good quality, no API key needed", + "env_vars": [], + "tts_provider": "edge", + }, { "name": "Nous Subscription", "badge": "subscription", @@ -212,13 +219,6 @@ TOOL_CATEGORIES = { "managed_nous_feature": "tts", "override_env_vars": ["VOICE_TOOLS_OPENAI_KEY", "OPENAI_API_KEY"], }, - { - "name": "Microsoft Edge TTS", - "badge": "โ˜… recommended ยท free", - "tag": "Good quality, no API key needed", - "env_vars": [], - "tts_provider": "edge", - }, { "name": "OpenAI TTS", "badge": "paid", @@ -406,15 +406,26 @@ TOOL_CATEGORIES = { # Per-provider rows for Browserbase, Browser Use, and Firecrawl are # injected at runtime from plugins.browser..provider via # _plugin_browser_providers() in _visible_providers(). Only - # non-provider UX setup-flow rows remain here: + # non-provider UX setup-flow rows remain here. "Local Browser" is + # listed FIRST so it is the default-highlighted (index 0) choice on a + # fresh install โ€” pressing Enter must land on the free, no-key local + # backend, never on the paid Nous Subscription gateway row: + # - "Local Browser" โ€” non-cloud option, no CloudBrowserProvider. # - "Nous Subscription (Browser Use cloud)" โ€” managed Browser Use # billed via Nous subscription (requires_nous_auth + # override_env_vars). Uses the browser-use plugin as the # underlying backend but has a distinct setup UX. - # - "Local Browser" โ€” non-cloud option, no CloudBrowserProvider. # - "Camofox" โ€” anti-detection local Firefox; short-circuits the # cloud-provider dispatch path via _is_camofox_mode(). "providers": [ + { + "name": "Local Browser", + "badge": "โ˜… recommended ยท free", + "tag": "Headless Chromium, no API key needed", + "env_vars": [], + "browser_provider": "local", + "post_setup": "agent_browser", + }, { "name": "Nous Subscription (Browser Use cloud)", "badge": "subscription", @@ -426,14 +437,6 @@ TOOL_CATEGORIES = { "override_env_vars": ["BROWSER_USE_API_KEY"], "post_setup": "agent_browser", }, - { - "name": "Local Browser", - "badge": "โ˜… recommended ยท free", - "tag": "Headless Chromium, no API key needed", - "env_vars": [], - "browser_provider": "local", - "post_setup": "agent_browser", - }, { "name": "Camofox", "badge": "free ยท local", diff --git a/tests/hermes_cli/test_tools_config.py b/tests/hermes_cli/test_tools_config.py index 07373e81e..953516e73 100644 --- a/tests/hermes_cli/test_tools_config.py +++ b/tests/hermes_cli/test_tools_config.py @@ -609,7 +609,12 @@ def test_visible_providers_include_nous_subscription_when_logged_in(monkeypatch) providers = _visible_providers(TOOL_CATEGORIES["browser"], config) - assert providers[0]["name"].startswith("Nous Subscription") + # The managed Nous row is listed (not necessarily first โ€” "Local Browser" + # sorts first so a fresh-install Enter lands on the free local backend). + assert any(p["name"].startswith("Nous Subscription") for p in providers) + # "Local Browser" must be the index-0 default so pressing Enter never + # walks a user into a paid Nous Portal login. + assert providers[0]["name"] == "Local Browser" def test_visible_providers_show_nous_subscription_when_logged_out(monkeypatch): @@ -685,7 +690,9 @@ def test_visible_providers_force_fresh_shows_nous_subscription_after_upgrade(mon force_fresh=True, ) - assert providers[0]["name"].startswith("Nous Subscription") + # The managed Nous row reappears after the entitlement upgrade. It is no + # longer asserted to be first โ€” "Local Browser" sorts first by design. + assert any(p["name"].startswith("Nous Subscription") for p in providers) assert ("features", True) in calls @@ -702,6 +709,33 @@ def test_local_browser_provider_is_saved_explicitly(monkeypatch): assert config["browser"]["cloud_provider"] == "local" +def test_fresh_install_browser_default_is_free_local_not_paid_nous(): + """On a fresh install the browser picker must default to the free local + backend, never the paid Nous Subscription gateway. + + Regression: the Nous row used to sort first, so the menu cursor defaulted + to index 0 (Nous) and pressing Enter walked users straight into a Nous + Portal login for a paid offering (Javier's bug, June 2026). + """ + from hermes_cli.tools_config import _detect_active_provider_index + + providers = TOOL_CATEGORIES["browser"]["providers"] + assert providers[0]["name"] == "Local Browser" + assert providers[0]["browser_provider"] == "local" + # Nothing active/configured โ†’ cursor defaults to index 0 (the free local row). + assert _detect_active_provider_index(providers, {}) == 0 + + +def test_fresh_install_tts_default_is_free_edge_not_paid_nous(): + """TTS picker defaults to the free Edge backend on a fresh install.""" + from hermes_cli.tools_config import _detect_active_provider_index + + providers = TOOL_CATEGORIES["tts"]["providers"] + assert providers[0]["name"] == "Microsoft Edge TTS" + assert providers[0]["tts_provider"] == "edge" + assert _detect_active_provider_index(providers, {}) == 0 + + def test_reconfigure_lists_enabled_web_without_existing_provider_config(monkeypatch): config = {"platform_toolsets": {"cli": ["web"]}} seen = {}