fix(setup): default browser/TTS picker to free local backend, not paid Nous (#37800)

The Browser Automation and Text-to-Speech provider pickers listed the paid
"Nous Subscription" gateway row first, so on a fresh install the menu cursor
defaulted to index 0 (Nous). Pressing Enter selected it and ran the inline
Nous Portal device-code login — walking users into a paid offering they
never chose.

Reorder both provider lists so the free, no-key local backend is index 0
(Local Browser / Microsoft Edge TTS). Users who already configured Nous are
unaffected: _detect_active_provider_index still resolves their active row
first, so the cursor lands on Nous (now index 1) for them.

Reported by Javier via Kujila.
This commit is contained in:
Teknium
2026-06-02 19:49:10 -07:00
committed by GitHub
parent 918aef267b
commit b28dd3417d
2 changed files with 56 additions and 19 deletions

View File

@ -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.<vendor>.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",

View File

@ -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 = {}