diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 62d8a19a773..dad5284ca8f 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -357,7 +357,7 @@ DEFAULT_CONFIG = { }, # Config schema version - bump this when adding new required fields - "_config_version": 9, + "_config_version": 10, } # ============================================================================= @@ -371,6 +371,7 @@ ENV_VARS_BY_VERSION: Dict[int, List[str]] = { 4: ["VOICE_TOOLS_OPENAI_KEY", "ELEVENLABS_API_KEY"], 5: ["WHATSAPP_ENABLED", "WHATSAPP_MODE", "WHATSAPP_ALLOWED_USERS", "SLACK_BOT_TOKEN", "SLACK_APP_TOKEN", "SLACK_ALLOWED_USERS"], + 10: ["TAVILY_API_KEY"], } # Required environment variables with metadata for migration prompts. @@ -550,6 +551,14 @@ OPTIONAL_ENV_VARS = { "password": True, "category": "tool", }, + "TAVILY_API_KEY": { + "description": "Tavily API key for Tavily-based research skills and integrations", + "prompt": "Tavily API key", + "url": "https://app.tavily.com/home", + "tools": ["tavily skills", "research integrations"], + "password": True, + "category": "tool", + }, "FIRECRAWL_API_URL": { "description": "Firecrawl API URL for self-hosted instances (optional)", "prompt": "Firecrawl API URL (leave empty for cloud)", @@ -1450,6 +1459,7 @@ def show_config(): ("OPENROUTER_API_KEY", "OpenRouter"), ("VOICE_TOOLS_OPENAI_KEY", "OpenAI (STT/TTS)"), ("FIRECRAWL_API_KEY", "Firecrawl"), + ("TAVILY_API_KEY", "Tavily"), ("BROWSERBASE_API_KEY", "Browserbase"), ("BROWSER_USE_API_KEY", "Browser Use"), ("FAL_KEY", "FAL"), @@ -1598,7 +1608,7 @@ def set_config_value(key: str, value: str): # Check if it's an API key (goes to .env) api_keys = [ 'OPENROUTER_API_KEY', 'OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'VOICE_TOOLS_OPENAI_KEY', - 'FIRECRAWL_API_KEY', 'FIRECRAWL_API_URL', 'BROWSERBASE_API_KEY', 'BROWSERBASE_PROJECT_ID', 'BROWSER_USE_API_KEY', + 'FIRECRAWL_API_KEY', 'TAVILY_API_KEY', 'FIRECRAWL_API_URL', 'BROWSERBASE_API_KEY', 'BROWSERBASE_PROJECT_ID', 'BROWSER_USE_API_KEY', 'FAL_KEY', 'TELEGRAM_BOT_TOKEN', 'DISCORD_BOT_TOKEN', 'TERMINAL_SSH_HOST', 'TERMINAL_SSH_USER', 'TERMINAL_SSH_KEY', 'SUDO_PASSWORD', 'SLACK_BOT_TOKEN', 'SLACK_APP_TOKEN', diff --git a/hermes_cli/setup.py b/hermes_cli/setup.py index a79844def56..d3f34df1596 100644 --- a/hermes_cli/setup.py +++ b/hermes_cli/setup.py @@ -450,6 +450,12 @@ def _print_setup_summary(config: dict, hermes_home): else: tool_status.append(("Web Search & Extract", False, "FIRECRAWL_API_KEY")) + # Tavily (skills / integrations) + if get_env_value("TAVILY_API_KEY"): + tool_status.append(("Tavily Research Integrations", True, None)) + else: + tool_status.append(("Tavily Research Integrations", False, "TAVILY_API_KEY")) + # Browser tools (local Chromium or Browserbase cloud) import shutil diff --git a/hermes_cli/status.py b/hermes_cli/status.py index be490e9306c..6e15f4e169e 100644 --- a/hermes_cli/status.py +++ b/hermes_cli/status.py @@ -120,6 +120,7 @@ def show_status(args): "MiniMax": "MINIMAX_API_KEY", "MiniMax-CN": "MINIMAX_CN_API_KEY", "Firecrawl": "FIRECRAWL_API_KEY", + "Tavily": "TAVILY_API_KEY", "Browserbase": "BROWSERBASE_API_KEY", # Optional — local browser works without this "FAL": "FAL_KEY", "Tinker": "TINKER_API_KEY", diff --git a/hermes_cli/tools_config.py b/hermes_cli/tools_config.py index d106d0c472d..8b25735f65e 100644 --- a/hermes_cli/tools_config.py +++ b/hermes_cli/tools_config.py @@ -150,9 +150,16 @@ TOOL_CATEGORIES = { "web": { "name": "Web Search & Extract", "setup_title": "Select Search Provider", - "setup_note": "A free DuckDuckGo search skill is also included — skip this if you don't need Firecrawl.", + "setup_note": "A free DuckDuckGo search skill is also included, and Tavily-based skills/integrations can be configured here too.", "icon": "🔍", "providers": [ + { + "name": "Tavily", + "tag": "Search-first research for Tavily-based skills and integrations", + "env_vars": [ + {"key": "TAVILY_API_KEY", "prompt": "Tavily API key", "url": "https://app.tavily.com/home"}, + ], + }, { "name": "Firecrawl Cloud", "tag": "Recommended - hosted service", diff --git a/tests/hermes_cli/test_config.py b/tests/hermes_cli/test_config.py index ba4f5c84456..f9dd18b615f 100644 --- a/tests/hermes_cli/test_config.py +++ b/tests/hermes_cli/test_config.py @@ -8,6 +8,8 @@ import yaml from hermes_cli.config import ( DEFAULT_CONFIG, + ENV_VARS_BY_VERSION, + OPTIONAL_ENV_VARS, get_hermes_home, ensure_hermes_home, load_config, @@ -345,3 +347,12 @@ class TestAnthropicTokenMigration: }): migrate_config(interactive=False, quiet=True) assert load_env().get("ANTHROPIC_TOKEN") == "current-token" + + +class TestOptionalToolKeys: + def test_tavily_api_key_is_registered_for_setup_and_migration(self): + tavily = OPTIONAL_ENV_VARS["TAVILY_API_KEY"] + assert tavily["prompt"] == "Tavily API key" + assert tavily["category"] == "tool" + assert "research" in tavily["description"].lower() + assert "TAVILY_API_KEY" in ENV_VARS_BY_VERSION[10] diff --git a/tests/hermes_cli/test_status.py b/tests/hermes_cli/test_status.py new file mode 100644 index 00000000000..374e57b29ee --- /dev/null +++ b/tests/hermes_cli/test_status.py @@ -0,0 +1,14 @@ +from types import SimpleNamespace + +from hermes_cli.status import show_status + + +def test_show_status_includes_tavily_key(monkeypatch, capsys, tmp_path): + monkeypatch.setenv("HERMES_HOME", str(tmp_path)) + monkeypatch.setenv("TAVILY_API_KEY", "tvly-1234567890abcdef") + + show_status(SimpleNamespace(all=False, deep=False)) + + output = capsys.readouterr().out + assert "Tavily" in output + assert "tvly...cdef" in output