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:
27
cli.py
27
cli.py
@@ -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
|
||||
|
||||
@@ -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
|
||||
# =========================================================================
|
||||
|
||||
@@ -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")
|
||||
|
||||
128
model_tools.py
128
model_tools.py
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user