Enhance tool availability checks and user feedback in CLI

- Updated the CLI to include a new method for displaying warnings about disabled tools due to missing API keys.
- Integrated tool availability checks into the setup wizard and doctor commands, providing users with clear information on which tools are available and what is required for full functionality.
- Improved user prompts and feedback regarding API key configuration, emphasizing the importance of setting up keys for certain tools.
- Added detailed summaries of tool availability during setup and diagnostics, enhancing the overall user experience.
This commit is contained in:
teknium1
2026-02-02 19:28:27 -08:00
parent e87bee9ccd
commit bbb5776763
4 changed files with 307 additions and 25 deletions

27
cli.py
View File

@@ -596,7 +596,7 @@ class HermesCLI:
self._show_status()
else:
# Get tools for display
tools = get_tool_definitions(enabled_toolsets=self.enabled_toolsets)
tools = get_tool_definitions(enabled_toolsets=self.enabled_toolsets, quiet_mode=True)
# Get terminal working directory (where commands will execute)
cwd = os.getenv("TERMINAL_CWD", os.getcwd())
@@ -611,8 +611,33 @@ class HermesCLI:
session_id=self.session_id,
)
# Show tool availability warnings if any tools are disabled
self._show_tool_availability_warnings()
self.console.print()
def _show_tool_availability_warnings(self):
"""Show warnings about disabled tools due to missing API keys."""
try:
from model_tools import check_tool_availability, TOOLSET_REQUIREMENTS
available, unavailable = check_tool_availability()
# Filter to only those missing API keys (not system deps)
api_key_missing = [u for u in unavailable if u["missing_vars"]]
if api_key_missing:
self.console.print()
self.console.print("[yellow]⚠️ Some tools disabled (missing API keys):[/]")
for item in api_key_missing:
tools_str = ", ".join(item["tools"][:2]) # Show first 2 tools
if len(item["tools"]) > 2:
tools_str += f", +{len(item['tools'])-2} more"
self.console.print(f" [dim]• {item['name']}[/] [dim italic]({', '.join(item['missing_vars'])})[/]")
self.console.print("[dim] Run 'hermes setup' to configure[/]")
except Exception:
pass # Don't crash on import errors
def _show_status(self):
"""Show current status bar."""
# Get tool count

View File

@@ -256,6 +256,37 @@ def run_doctor(args):
except Exception as e:
check_warn("Anthropic API", f"({e})")
# =========================================================================
# Check: Tool Availability
# =========================================================================
print()
print(color("◆ Tool Availability", Colors.CYAN, Colors.BOLD))
try:
# Add project root to path for imports
sys.path.insert(0, str(PROJECT_ROOT))
from model_tools import check_tool_availability, TOOLSET_REQUIREMENTS
available, unavailable = check_tool_availability()
for tid in available:
info = TOOLSET_REQUIREMENTS.get(tid, {})
check_ok(info.get("name", tid))
for item in unavailable:
if item["missing_vars"]:
vars_str = ", ".join(item["missing_vars"])
check_warn(item["name"], f"(missing {vars_str})")
else:
check_warn(item["name"], "(system dependency not met)")
# Count disabled tools with API key requirements
api_disabled = [u for u in unavailable if u["missing_vars"]]
if api_disabled:
issues.append("Run 'hermes setup' to configure missing API keys for full tool access")
except Exception as e:
check_warn("Could not check tool availability", f"({e})")
# =========================================================================
# Summary
# =========================================================================

View File

@@ -510,48 +510,148 @@ def run_setup_wizard(args):
# Step 7: Additional Tools (Optional)
# =========================================================================
print_header("Additional Tools (Optional)")
print_info("These tools extend the agent's capabilities.")
print_info("Without their API keys, the corresponding features will be disabled.")
print()
# Firecrawl
if not get_env_value('FIRECRAWL_API_KEY'):
if prompt_yes_no("Set up web scraping (Firecrawl)?", False):
print_info("Get your API key at: https://firecrawl.dev/")
api_key = prompt("Firecrawl API key", password=True)
# Firecrawl - Web scraping
print_info("" * 50)
print(color(" Web Search & Scraping (Firecrawl)", Colors.CYAN))
print_info(" Enables: web_search, web_extract tools")
print_info(" Use case: Search the web, read webpage content")
if get_env_value('FIRECRAWL_API_KEY'):
print_success(" Status: Configured ✓")
if prompt_yes_no(" Update Firecrawl API key?", False):
api_key = prompt(" API key", password=True)
if api_key:
save_env_value("FIRECRAWL_API_KEY", api_key)
print_success("Firecrawl API key saved")
print_success(" Updated")
else:
print_info("Firecrawl: already configured")
print_warning(" Status: Not configured (tools will be disabled)")
if prompt_yes_no(" Set up Firecrawl?", False):
print_info(" Get your API key at: https://firecrawl.dev/")
api_key = prompt(" API key", password=True)
if api_key:
save_env_value("FIRECRAWL_API_KEY", api_key)
print_success(" Configured ✓")
print()
# Browserbase
if not get_env_value('BROWSERBASE_API_KEY'):
if prompt_yes_no("Set up browser automation (Browserbase)?", False):
print_info("Get your API key at: https://browserbase.com/")
api_key = prompt("Browserbase API key", password=True)
project_id = prompt("Browserbase project ID")
# Browserbase - Browser automation
print_info("" * 50)
print(color(" Browser Automation (Browserbase)", Colors.CYAN))
print_info(" Enables: browser_navigate, browser_click, etc.")
print_info(" Use case: Interact with web pages, fill forms, screenshots")
if get_env_value('BROWSERBASE_API_KEY'):
print_success(" Status: Configured ✓")
if prompt_yes_no(" Update Browserbase credentials?", False):
api_key = prompt(" API key", password=True)
project_id = prompt(" Project ID")
if api_key:
save_env_value("BROWSERBASE_API_KEY", api_key)
if project_id:
save_env_value("BROWSERBASE_PROJECT_ID", project_id)
print_success("Browserbase configured")
print_success(" Updated")
else:
print_info("Browserbase: already configured")
print_warning(" Status: Not configured (tools will be disabled)")
if prompt_yes_no(" Set up Browserbase?", False):
print_info(" Get credentials at: https://browserbase.com/")
api_key = prompt(" API key", password=True)
project_id = prompt(" Project ID")
if api_key:
save_env_value("BROWSERBASE_API_KEY", api_key)
if project_id:
save_env_value("BROWSERBASE_PROJECT_ID", project_id)
print_success(" Configured ✓")
print()
# FAL
if not get_env_value('FAL_KEY'):
if prompt_yes_no("Set up image generation (FAL)?", False):
print_info("Get your API key at: https://fal.ai/")
api_key = prompt("FAL API key", password=True)
# FAL - Image generation
print_info("" * 50)
print(color(" Image Generation (FAL)", Colors.CYAN))
print_info(" Enables: image_generate tool")
print_info(" Use case: Generate images from text prompts (FLUX)")
if get_env_value('FAL_KEY'):
print_success(" Status: Configured ✓")
if prompt_yes_no(" Update FAL API key?", False):
api_key = prompt(" API key", password=True)
if api_key:
save_env_value("FAL_KEY", api_key)
print_success("FAL API key saved")
print_success(" Updated")
else:
print_info("FAL: already configured")
print_warning(" Status: Not configured (tool will be disabled)")
if prompt_yes_no(" Set up FAL?", False):
print_info(" Get your API key at: https://fal.ai/")
api_key = prompt(" API key", password=True)
if api_key:
save_env_value("FAL_KEY", api_key)
print_success(" Configured ✓")
# =========================================================================
# Save config
# =========================================================================
save_config(config)
# =========================================================================
# Tool Availability Summary
# =========================================================================
print()
print_header("Tool Availability Summary")
# Check which tools are available
tool_status = []
# OpenRouter (required for vision, moa)
if get_env_value('OPENROUTER_API_KEY'):
tool_status.append(("Vision (image analysis)", True, None))
tool_status.append(("Mixture of Agents", True, None))
else:
tool_status.append(("Vision (image analysis)", False, "OPENROUTER_API_KEY"))
tool_status.append(("Mixture of Agents", False, "OPENROUTER_API_KEY"))
# Firecrawl (web tools)
if get_env_value('FIRECRAWL_API_KEY'):
tool_status.append(("Web Search & Extract", True, None))
else:
tool_status.append(("Web Search & Extract", False, "FIRECRAWL_API_KEY"))
# Browserbase (browser tools)
if get_env_value('BROWSERBASE_API_KEY'):
tool_status.append(("Browser Automation", True, None))
else:
tool_status.append(("Browser Automation", False, "BROWSERBASE_API_KEY"))
# FAL (image generation)
if get_env_value('FAL_KEY'):
tool_status.append(("Image Generation", True, None))
else:
tool_status.append(("Image Generation", False, "FAL_KEY"))
# Terminal (always available if system deps met)
tool_status.append(("Terminal/Commands", True, None))
# Skills (always available if skills dir exists)
tool_status.append(("Skills Knowledge Base", True, None))
# Print status
available_count = sum(1 for _, avail, _ in tool_status if avail)
total_count = len(tool_status)
print_info(f"{available_count}/{total_count} tool categories available:")
print()
for name, available, missing_var in tool_status:
if available:
print(f" {color('', Colors.GREEN)} {name}")
else:
print(f" {color('', Colors.RED)} {name} {color(f'(missing {missing_var})', Colors.DIM)}")
print()
disabled_tools = [(name, var) for name, avail, var in tool_status if not avail]
if disabled_tools:
print_warning("Some tools are disabled. Run 'hermes setup' again to configure them,")
print_warning("or edit ~/.hermes/.env directly to add the missing API keys.")
print()
# =========================================================================
# Done!
# =========================================================================
@@ -568,7 +668,7 @@ def run_setup_wizard(args):
print(f" Model, terminal backend, compression, etc.")
print()
print(f" {color('API Keys:', Colors.YELLOW)} {get_env_path()}")
print(f" OpenRouter, Anthropic, Firecrawl, etc.")
print(f" OpenRouter, Custom Endpoint, tool API keys")
print()
print(f" {color('Data:', Colors.YELLOW)} {hermes_home}/")
print(f" Cron jobs, sessions, logs")

View File

@@ -28,7 +28,8 @@ Usage:
import json
import asyncio
from typing import Dict, Any, List, Optional
import os
from typing import Dict, Any, List, Optional, Tuple
from tools.web_tools import web_search_tool, web_extract_tool, web_crawl_tool, check_firecrawl_api_key
from tools.terminal_tool import terminal_tool, check_terminal_requirements, TERMINAL_TOOL_DESCRIPTION, cleanup_vm
@@ -71,6 +72,131 @@ from toolsets import (
get_toolset_info, print_toolset_tree
)
# =============================================================================
# Tool Availability Checking
# =============================================================================
# Maps toolsets to their required API keys/environment variables
TOOLSET_REQUIREMENTS = {
"web": {
"name": "Web Search & Extract",
"env_vars": ["FIRECRAWL_API_KEY"],
"check_fn": check_firecrawl_api_key,
"setup_url": "https://firecrawl.dev/",
"tools": ["web_search", "web_extract"],
},
"vision": {
"name": "Vision (Image Analysis)",
"env_vars": ["OPENROUTER_API_KEY"],
"check_fn": check_vision_requirements,
"setup_url": "https://openrouter.ai/keys",
"tools": ["vision_analyze"],
},
"moa": {
"name": "Mixture of Agents",
"env_vars": ["OPENROUTER_API_KEY"],
"check_fn": check_moa_requirements,
"setup_url": "https://openrouter.ai/keys",
"tools": ["mixture_of_agents"],
},
"image_gen": {
"name": "Image Generation",
"env_vars": ["FAL_KEY"],
"check_fn": check_image_generation_requirements,
"setup_url": "https://fal.ai/",
"tools": ["image_generate"],
},
"browser": {
"name": "Browser Automation",
"env_vars": ["BROWSERBASE_API_KEY", "BROWSERBASE_PROJECT_ID"],
"check_fn": check_browser_requirements,
"setup_url": "https://browserbase.com/",
"tools": ["browser_navigate", "browser_snapshot", "browser_click", "browser_type"],
},
"terminal": {
"name": "Terminal/Command Execution",
"env_vars": [], # No API key required, just system dependencies
"check_fn": check_terminal_requirements,
"setup_url": None,
"tools": ["terminal"],
},
"skills": {
"name": "Skills Knowledge Base",
"env_vars": [], # Just needs skills directory
"check_fn": check_skills_requirements,
"setup_url": None,
"tools": ["skills_categories", "skills_list", "skill_view"],
},
}
def check_tool_availability(quiet: bool = False) -> Tuple[List[str], List[Dict[str, Any]]]:
"""
Check which tool categories are available based on API keys and requirements.
Returns:
Tuple containing:
- List of available toolset names
- List of dicts with info about unavailable toolsets and what's missing
"""
available = []
unavailable = []
for toolset_id, info in TOOLSET_REQUIREMENTS.items():
if info["check_fn"]():
available.append(toolset_id)
else:
# Figure out what's missing
missing_vars = [var for var in info["env_vars"] if not os.getenv(var)]
unavailable.append({
"id": toolset_id,
"name": info["name"],
"missing_vars": missing_vars,
"setup_url": info["setup_url"],
"tools": info["tools"],
})
return available, unavailable
def print_tool_availability_warnings(unavailable: List[Dict[str, Any]], prefix: str = ""):
"""Print warnings about unavailable tools."""
if not unavailable:
return
# Filter to only those missing API keys (not system dependencies)
api_key_missing = [u for u in unavailable if u["missing_vars"]]
if api_key_missing:
print(f"{prefix}⚠️ Some tools are disabled due to missing API keys:")
for item in api_key_missing:
vars_str = ", ".join(item["missing_vars"])
print(f"{prefix}{item['name']}: missing {vars_str}")
if item["setup_url"]:
print(f"{prefix} Get key at: {item['setup_url']}")
print(f"{prefix} Run 'hermes setup' to configure API keys")
print()
def get_tool_availability_summary() -> Dict[str, Any]:
"""
Get a summary of tool availability for display in status/doctor commands.
Returns:
Dict with 'available' and 'unavailable' lists of tool info
"""
available, unavailable = check_tool_availability()
return {
"available": [
{"id": tid, "name": TOOLSET_REQUIREMENTS[tid]["name"], "tools": TOOLSET_REQUIREMENTS[tid]["tools"]}
for tid in available
],
"unavailable": unavailable,
}
def get_web_tool_definitions() -> List[Dict[str, Any]]:
"""
Get tool definitions for web tools in OpenAI's expected format.