Implement configuration migration system and enhance CLI setup
- Introduced a configuration migration system to check for missing required environment variables and outdated config fields, prompting users for necessary inputs during updates. - Enhanced the CLI with new commands for checking and migrating configuration, improving user experience by providing clear guidance on required settings. - Updated the setup wizard to detect existing installations and offer quick setup options for missing configurations, streamlining the user onboarding process. - Improved messaging throughout the CLI to inform users about the status of their configuration and any required actions.
This commit is contained in:
@@ -16,7 +16,7 @@ import os
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any, Optional
|
||||
from typing import Dict, Any, Optional, List, Tuple
|
||||
|
||||
import yaml
|
||||
|
||||
@@ -98,8 +98,219 @@ DEFAULT_CONFIG = {
|
||||
"compact": False,
|
||||
"personality": "kawaii",
|
||||
},
|
||||
|
||||
# Config schema version - bump this when adding new required fields
|
||||
"_config_version": 1,
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
# Config Migration System
|
||||
# =============================================================================
|
||||
|
||||
# Required environment variables with metadata for migration prompts
|
||||
REQUIRED_ENV_VARS = {
|
||||
"OPENROUTER_API_KEY": {
|
||||
"description": "OpenRouter API key (required for vision, web scraping, and tools)",
|
||||
"prompt": "OpenRouter API key",
|
||||
"url": "https://openrouter.ai/keys",
|
||||
"required": True,
|
||||
"password": True,
|
||||
},
|
||||
}
|
||||
|
||||
# Optional environment variables that enhance functionality
|
||||
OPTIONAL_ENV_VARS = {
|
||||
"FIRECRAWL_API_KEY": {
|
||||
"description": "Firecrawl API key for web search and scraping",
|
||||
"prompt": "Firecrawl API key",
|
||||
"url": "https://firecrawl.dev/",
|
||||
"tools": ["web_search", "web_extract"],
|
||||
"password": True,
|
||||
},
|
||||
"BROWSERBASE_API_KEY": {
|
||||
"description": "Browserbase API key for browser automation",
|
||||
"prompt": "Browserbase API key",
|
||||
"url": "https://browserbase.com/",
|
||||
"tools": ["browser_navigate", "browser_click", "etc."],
|
||||
"password": True,
|
||||
},
|
||||
"BROWSERBASE_PROJECT_ID": {
|
||||
"description": "Browserbase project ID",
|
||||
"prompt": "Browserbase project ID",
|
||||
"url": "https://browserbase.com/",
|
||||
"tools": ["browser_navigate", "browser_click", "etc."],
|
||||
"password": False,
|
||||
},
|
||||
"FAL_KEY": {
|
||||
"description": "FAL API key for image generation",
|
||||
"prompt": "FAL API key",
|
||||
"url": "https://fal.ai/",
|
||||
"tools": ["image_generate"],
|
||||
"password": True,
|
||||
},
|
||||
"OPENAI_BASE_URL": {
|
||||
"description": "Custom OpenAI-compatible API endpoint URL",
|
||||
"prompt": "API base URL (e.g., https://api.example.com/v1)",
|
||||
"url": None,
|
||||
"password": False,
|
||||
},
|
||||
"OPENAI_API_KEY": {
|
||||
"description": "API key for custom OpenAI-compatible endpoint",
|
||||
"prompt": "API key for custom endpoint",
|
||||
"url": None,
|
||||
"password": True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_missing_env_vars(required_only: bool = False) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Check which environment variables are missing.
|
||||
|
||||
Returns list of dicts with var info for missing variables.
|
||||
"""
|
||||
missing = []
|
||||
|
||||
# Check required vars
|
||||
for var_name, info in REQUIRED_ENV_VARS.items():
|
||||
if not get_env_value(var_name):
|
||||
missing.append({"name": var_name, **info, "is_required": True})
|
||||
|
||||
# Check optional vars (if not required_only)
|
||||
if not required_only:
|
||||
for var_name, info in OPTIONAL_ENV_VARS.items():
|
||||
if not get_env_value(var_name):
|
||||
missing.append({"name": var_name, **info, "is_required": False})
|
||||
|
||||
return missing
|
||||
|
||||
|
||||
def get_missing_config_fields() -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Check which config fields are missing or outdated.
|
||||
|
||||
Returns list of missing/outdated fields.
|
||||
"""
|
||||
config = load_config()
|
||||
missing = []
|
||||
|
||||
# Check for new top-level keys in DEFAULT_CONFIG
|
||||
for key, default_value in DEFAULT_CONFIG.items():
|
||||
if key.startswith('_'):
|
||||
continue # Skip internal keys
|
||||
if key not in config:
|
||||
missing.append({
|
||||
"key": key,
|
||||
"default": default_value,
|
||||
"description": f"New config section: {key}",
|
||||
})
|
||||
elif isinstance(default_value, dict):
|
||||
# Check nested keys
|
||||
for subkey, subvalue in default_value.items():
|
||||
if subkey not in config.get(key, {}):
|
||||
missing.append({
|
||||
"key": f"{key}.{subkey}",
|
||||
"default": subvalue,
|
||||
"description": f"New config option: {key}.{subkey}",
|
||||
})
|
||||
|
||||
return missing
|
||||
|
||||
|
||||
def check_config_version() -> Tuple[int, int]:
|
||||
"""
|
||||
Check config version.
|
||||
|
||||
Returns (current_version, latest_version).
|
||||
"""
|
||||
config = load_config()
|
||||
current = config.get("_config_version", 0)
|
||||
latest = DEFAULT_CONFIG.get("_config_version", 1)
|
||||
return current, latest
|
||||
|
||||
|
||||
def migrate_config(interactive: bool = True, quiet: bool = False) -> Dict[str, Any]:
|
||||
"""
|
||||
Migrate config to latest version, prompting for new required fields.
|
||||
|
||||
Args:
|
||||
interactive: If True, prompt user for missing values
|
||||
quiet: If True, suppress output
|
||||
|
||||
Returns:
|
||||
Dict with migration results: {"env_added": [...], "config_added": [...], "warnings": [...]}
|
||||
"""
|
||||
results = {"env_added": [], "config_added": [], "warnings": []}
|
||||
|
||||
# Check config version
|
||||
current_ver, latest_ver = check_config_version()
|
||||
|
||||
if current_ver < latest_ver and not quiet:
|
||||
print(f"Config version: {current_ver} → {latest_ver}")
|
||||
|
||||
# Check for missing required env vars
|
||||
missing_env = get_missing_env_vars(required_only=True)
|
||||
|
||||
if missing_env and not quiet:
|
||||
print("\n⚠️ Missing required environment variables:")
|
||||
for var in missing_env:
|
||||
print(f" • {var['name']}: {var['description']}")
|
||||
|
||||
if interactive and missing_env:
|
||||
print("\nLet's configure them now:\n")
|
||||
for var in missing_env:
|
||||
if var.get("url"):
|
||||
print(f" Get your key at: {var['url']}")
|
||||
|
||||
if var.get("password"):
|
||||
import getpass
|
||||
value = getpass.getpass(f" {var['prompt']}: ")
|
||||
else:
|
||||
value = input(f" {var['prompt']}: ").strip()
|
||||
|
||||
if value:
|
||||
save_env_value(var["name"], value)
|
||||
results["env_added"].append(var["name"])
|
||||
print(f" ✓ Saved {var['name']}")
|
||||
else:
|
||||
results["warnings"].append(f"Skipped {var['name']} - some features may not work")
|
||||
print()
|
||||
|
||||
# Check for missing config fields
|
||||
missing_config = get_missing_config_fields()
|
||||
|
||||
if missing_config:
|
||||
config = load_config()
|
||||
|
||||
for field in missing_config:
|
||||
key = field["key"]
|
||||
default = field["default"]
|
||||
|
||||
# Add with default value
|
||||
if "." in key:
|
||||
# Nested key
|
||||
parent, child = key.split(".", 1)
|
||||
if parent not in config:
|
||||
config[parent] = {}
|
||||
config[parent][child] = default
|
||||
else:
|
||||
config[key] = default
|
||||
|
||||
results["config_added"].append(key)
|
||||
if not quiet:
|
||||
print(f" ✓ Added {key} = {default}")
|
||||
|
||||
# Update version and save
|
||||
config["_config_version"] = latest_ver
|
||||
save_config(config)
|
||||
elif current_ver < latest_ver:
|
||||
# Just update version
|
||||
config = load_config()
|
||||
config["_config_version"] = latest_ver
|
||||
save_config(config)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def load_config() -> Dict[str, Any]:
|
||||
"""Load configuration from ~/.hermes/config.yaml."""
|
||||
@@ -395,6 +606,106 @@ def config_command(args):
|
||||
elif subcmd == "env-path":
|
||||
print(get_env_path())
|
||||
|
||||
elif subcmd == "migrate":
|
||||
print()
|
||||
print(color("🔄 Checking configuration for updates...", Colors.CYAN, Colors.BOLD))
|
||||
print()
|
||||
|
||||
# Check what's missing
|
||||
missing_env = get_missing_env_vars(required_only=False)
|
||||
missing_config = get_missing_config_fields()
|
||||
current_ver, latest_ver = check_config_version()
|
||||
|
||||
if not missing_env and not missing_config and current_ver >= latest_ver:
|
||||
print(color("✓ Configuration is up to date!", Colors.GREEN))
|
||||
print()
|
||||
return
|
||||
|
||||
# Show what needs to be updated
|
||||
if current_ver < latest_ver:
|
||||
print(f" Config version: {current_ver} → {latest_ver}")
|
||||
|
||||
if missing_config:
|
||||
print(f"\n {len(missing_config)} new config option(s) will be added with defaults")
|
||||
|
||||
required_missing = [v for v in missing_env if v.get("is_required")]
|
||||
optional_missing = [v for v in missing_env if not v.get("is_required")]
|
||||
|
||||
if required_missing:
|
||||
print(f"\n ⚠️ {len(required_missing)} required API key(s) missing:")
|
||||
for var in required_missing:
|
||||
print(f" • {var['name']}")
|
||||
|
||||
if optional_missing:
|
||||
print(f"\n ℹ️ {len(optional_missing)} optional API key(s) not configured:")
|
||||
for var in optional_missing:
|
||||
tools = var.get("tools", [])
|
||||
tools_str = f" (enables: {', '.join(tools[:2])})" if tools else ""
|
||||
print(f" • {var['name']}{tools_str}")
|
||||
|
||||
print()
|
||||
|
||||
# Run migration
|
||||
results = migrate_config(interactive=True, quiet=False)
|
||||
|
||||
print()
|
||||
if results["env_added"] or results["config_added"]:
|
||||
print(color("✓ Configuration updated!", Colors.GREEN))
|
||||
|
||||
if results["warnings"]:
|
||||
print()
|
||||
for warning in results["warnings"]:
|
||||
print(color(f" ⚠️ {warning}", Colors.YELLOW))
|
||||
|
||||
print()
|
||||
|
||||
elif subcmd == "check":
|
||||
# Non-interactive check for what's missing
|
||||
print()
|
||||
print(color("📋 Configuration Status", Colors.CYAN, Colors.BOLD))
|
||||
print()
|
||||
|
||||
current_ver, latest_ver = check_config_version()
|
||||
if current_ver >= latest_ver:
|
||||
print(f" Config version: {current_ver} ✓")
|
||||
else:
|
||||
print(color(f" Config version: {current_ver} → {latest_ver} (update available)", Colors.YELLOW))
|
||||
|
||||
print()
|
||||
print(color(" Required:", Colors.BOLD))
|
||||
for var_name in REQUIRED_ENV_VARS:
|
||||
if get_env_value(var_name):
|
||||
print(f" ✓ {var_name}")
|
||||
else:
|
||||
print(color(f" ✗ {var_name} (missing)", Colors.RED))
|
||||
|
||||
print()
|
||||
print(color(" Optional:", Colors.BOLD))
|
||||
for var_name, info in OPTIONAL_ENV_VARS.items():
|
||||
if get_env_value(var_name):
|
||||
print(f" ✓ {var_name}")
|
||||
else:
|
||||
tools = info.get("tools", [])
|
||||
tools_str = f" → {', '.join(tools[:2])}" if tools else ""
|
||||
print(color(f" ○ {var_name}{tools_str}", Colors.DIM))
|
||||
|
||||
missing_config = get_missing_config_fields()
|
||||
if missing_config:
|
||||
print()
|
||||
print(color(f" {len(missing_config)} new config option(s) available", Colors.YELLOW))
|
||||
print(f" Run 'hermes config migrate' to add them")
|
||||
|
||||
print()
|
||||
|
||||
else:
|
||||
print(f"Unknown config command: {subcmd}")
|
||||
print()
|
||||
print("Available commands:")
|
||||
print(" hermes config Show current configuration")
|
||||
print(" hermes config edit Open config in editor")
|
||||
print(" hermes config set K V Set a config value")
|
||||
print(" hermes config check Check for missing/outdated config")
|
||||
print(" hermes config migrate Update config with new options")
|
||||
print(" hermes config path Show config file path")
|
||||
print(" hermes config env-path Show .env file path")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -170,6 +170,47 @@ def cmd_update(args):
|
||||
print("→ Updating Node.js dependencies...")
|
||||
subprocess.run(["npm", "install", "--silent"], cwd=PROJECT_ROOT, check=False)
|
||||
|
||||
print()
|
||||
print("✓ Code updated!")
|
||||
|
||||
# Check for config migrations
|
||||
print()
|
||||
print("→ Checking configuration for new options...")
|
||||
|
||||
from hermes_cli.config import (
|
||||
get_missing_env_vars, get_missing_config_fields,
|
||||
check_config_version, migrate_config
|
||||
)
|
||||
|
||||
missing_env = get_missing_env_vars(required_only=True)
|
||||
missing_config = get_missing_config_fields()
|
||||
current_ver, latest_ver = check_config_version()
|
||||
|
||||
needs_migration = missing_env or missing_config or current_ver < latest_ver
|
||||
|
||||
if needs_migration:
|
||||
print()
|
||||
if missing_env:
|
||||
print(f" ⚠️ {len(missing_env)} new required setting(s) need configuration")
|
||||
if missing_config:
|
||||
print(f" ℹ️ {len(missing_config)} new config option(s) available")
|
||||
|
||||
print()
|
||||
response = input("Would you like to configure them now? [Y/n]: ").strip().lower()
|
||||
|
||||
if response in ('', 'y', 'yes'):
|
||||
print()
|
||||
results = migrate_config(interactive=True, quiet=False)
|
||||
|
||||
if results["env_added"] or results["config_added"]:
|
||||
print()
|
||||
print("✓ Configuration updated!")
|
||||
else:
|
||||
print()
|
||||
print("Skipped. Run 'hermes config migrate' later to configure.")
|
||||
else:
|
||||
print(" ✓ Configuration is up to date")
|
||||
|
||||
print()
|
||||
print("✓ Update complete!")
|
||||
print()
|
||||
@@ -380,6 +421,12 @@ For more help on a command:
|
||||
# config env-path
|
||||
config_env = config_subparsers.add_parser("env-path", help="Print .env file path")
|
||||
|
||||
# config check
|
||||
config_check = config_subparsers.add_parser("check", help="Check for missing/outdated config")
|
||||
|
||||
# config migrate
|
||||
config_migrate = config_subparsers.add_parser("migrate", help="Update config with new options")
|
||||
|
||||
config_parser.set_defaults(func=cmd_config)
|
||||
|
||||
# =========================================================================
|
||||
|
||||
@@ -152,6 +152,106 @@ def prompt_yes_no(question: str, default: bool = True) -> bool:
|
||||
print_error("Please enter 'y' or 'n'")
|
||||
|
||||
|
||||
def _print_setup_summary(config: dict, hermes_home):
|
||||
"""Print the setup completion summary."""
|
||||
# Tool availability summary
|
||||
print()
|
||||
print_header("Tool Availability Summary")
|
||||
|
||||
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 banner
|
||||
print()
|
||||
print(color("┌─────────────────────────────────────────────────────────┐", Colors.GREEN))
|
||||
print(color("│ ✓ Setup Complete! │", Colors.GREEN))
|
||||
print(color("└─────────────────────────────────────────────────────────┘", Colors.GREEN))
|
||||
print()
|
||||
|
||||
# Show file locations prominently
|
||||
print(color("📁 All your files are in ~/.hermes/:", Colors.CYAN, Colors.BOLD))
|
||||
print()
|
||||
print(f" {color('Settings:', Colors.YELLOW)} {get_config_path()}")
|
||||
print(f" {color('API Keys:', Colors.YELLOW)} {get_env_path()}")
|
||||
print(f" {color('Data:', Colors.YELLOW)} {hermes_home}/cron/, sessions/, logs/")
|
||||
print()
|
||||
|
||||
print(color("─" * 60, Colors.DIM))
|
||||
print()
|
||||
print(color("📝 To edit your configuration:", Colors.CYAN, Colors.BOLD))
|
||||
print()
|
||||
print(f" {color('hermes config', Colors.GREEN)} View current settings")
|
||||
print(f" {color('hermes config edit', Colors.GREEN)} Open config in your editor")
|
||||
print(f" {color('hermes config set KEY VALUE', Colors.GREEN)}")
|
||||
print(f" Set a specific value")
|
||||
print()
|
||||
print(f" Or edit the files directly:")
|
||||
print(f" {color(f'nano {get_config_path()}', Colors.DIM)}")
|
||||
print(f" {color(f'nano {get_env_path()}', Colors.DIM)}")
|
||||
print()
|
||||
|
||||
print(color("─" * 60, Colors.DIM))
|
||||
print()
|
||||
print(color("🚀 Ready to go!", Colors.CYAN, Colors.BOLD))
|
||||
print()
|
||||
print(f" {color('hermes', Colors.GREEN)} Start chatting")
|
||||
print(f" {color('hermes gateway', Colors.GREEN)} Start messaging gateway")
|
||||
print(f" {color('hermes doctor', Colors.GREEN)} Check for issues")
|
||||
print()
|
||||
|
||||
|
||||
def run_setup_wizard(args):
|
||||
"""Run the interactive setup wizard."""
|
||||
ensure_hermes_home()
|
||||
@@ -159,6 +259,24 @@ def run_setup_wizard(args):
|
||||
config = load_config()
|
||||
hermes_home = get_hermes_home()
|
||||
|
||||
# Check if this is an existing installation with config
|
||||
is_existing = get_env_value("OPENROUTER_API_KEY") is not None or get_config_path().exists()
|
||||
|
||||
# Import migration helpers
|
||||
from hermes_cli.config import (
|
||||
get_missing_env_vars, get_missing_config_fields,
|
||||
check_config_version, migrate_config,
|
||||
REQUIRED_ENV_VARS, OPTIONAL_ENV_VARS
|
||||
)
|
||||
|
||||
# Check what's missing
|
||||
missing_required = [v for v in get_missing_env_vars(required_only=False) if v.get("is_required")]
|
||||
missing_optional = [v for v in get_missing_env_vars(required_only=False) if not v.get("is_required")]
|
||||
missing_config = get_missing_config_fields()
|
||||
current_ver, latest_ver = check_config_version()
|
||||
|
||||
has_missing = missing_required or missing_optional or missing_config or current_ver < latest_ver
|
||||
|
||||
print()
|
||||
print(color("┌─────────────────────────────────────────────────────────┐", Colors.MAGENTA))
|
||||
print(color("│ 🦋 Hermes Agent Setup Wizard │", Colors.MAGENTA))
|
||||
@@ -167,8 +285,126 @@ def run_setup_wizard(args):
|
||||
print(color("│ Press Ctrl+C at any time to exit. │", Colors.MAGENTA))
|
||||
print(color("└─────────────────────────────────────────────────────────┘", Colors.MAGENTA))
|
||||
|
||||
# If existing installation, show what's missing and offer quick mode
|
||||
quick_mode = False
|
||||
if is_existing and has_missing:
|
||||
print()
|
||||
print_header("Existing Installation Detected")
|
||||
print_success("You already have Hermes configured!")
|
||||
print()
|
||||
|
||||
if missing_required:
|
||||
print_warning(f" {len(missing_required)} required setting(s) missing:")
|
||||
for var in missing_required:
|
||||
print(f" • {var['name']}")
|
||||
|
||||
if missing_optional:
|
||||
print_info(f" {len(missing_optional)} optional tool(s) not configured:")
|
||||
for var in missing_optional[:3]: # Show first 3
|
||||
tools = var.get("tools", [])
|
||||
tools_str = f" → {', '.join(tools[:2])}" if tools else ""
|
||||
print(f" • {var['name']}{tools_str}")
|
||||
if len(missing_optional) > 3:
|
||||
print(f" • ...and {len(missing_optional) - 3} more")
|
||||
|
||||
if missing_config:
|
||||
print_info(f" {len(missing_config)} new config option(s) available")
|
||||
|
||||
print()
|
||||
|
||||
setup_choices = [
|
||||
"Quick setup - just configure missing items",
|
||||
"Full setup - reconfigure everything",
|
||||
"Skip - exit setup"
|
||||
]
|
||||
|
||||
choice = prompt_choice("What would you like to do?", setup_choices, 0)
|
||||
|
||||
if choice == 0:
|
||||
quick_mode = True
|
||||
elif choice == 2:
|
||||
print()
|
||||
print_info("Exiting. Run 'hermes setup' again when ready.")
|
||||
return
|
||||
# choice == 1 continues with full setup
|
||||
|
||||
elif is_existing and not has_missing:
|
||||
print()
|
||||
print_header("Configuration Status")
|
||||
print_success("Your configuration is complete!")
|
||||
print()
|
||||
|
||||
if not prompt_yes_no("Would you like to reconfigure anyway?", False):
|
||||
print()
|
||||
print_info("Exiting. Your configuration is already set up.")
|
||||
print_info(f"Config: {get_config_path()}")
|
||||
print_info(f"Secrets: {get_env_path()}")
|
||||
return
|
||||
|
||||
# Quick mode: only configure missing items
|
||||
if quick_mode:
|
||||
print()
|
||||
print_header("Quick Setup - Missing Items Only")
|
||||
|
||||
# Handle missing required env vars
|
||||
if missing_required:
|
||||
for var in missing_required:
|
||||
print()
|
||||
print(color(f" {var['name']}", Colors.CYAN))
|
||||
print_info(f" {var.get('description', '')}")
|
||||
if var.get("url"):
|
||||
print_info(f" Get key at: {var['url']}")
|
||||
|
||||
if var.get("password"):
|
||||
value = prompt(f" {var.get('prompt', var['name'])}", password=True)
|
||||
else:
|
||||
value = prompt(f" {var.get('prompt', var['name'])}")
|
||||
|
||||
if value:
|
||||
save_env_value(var["name"], value)
|
||||
print_success(f" Saved {var['name']}")
|
||||
else:
|
||||
print_warning(f" Skipped {var['name']}")
|
||||
|
||||
# Handle missing optional env vars
|
||||
if missing_optional:
|
||||
print()
|
||||
print_header("Optional Tools (Quick Setup)")
|
||||
|
||||
for var in missing_optional:
|
||||
tools = var.get("tools", [])
|
||||
tools_str = f" (enables: {', '.join(tools[:2])})" if tools else ""
|
||||
|
||||
if prompt_yes_no(f"Configure {var['name']}{tools_str}?", False):
|
||||
if var.get("url"):
|
||||
print_info(f" Get key at: {var['url']}")
|
||||
|
||||
if var.get("password"):
|
||||
value = prompt(f" {var.get('prompt', var['name'])}", password=True)
|
||||
else:
|
||||
value = prompt(f" {var.get('prompt', var['name'])}")
|
||||
|
||||
if value:
|
||||
save_env_value(var["name"], value)
|
||||
print_success(f" Saved")
|
||||
|
||||
# Handle missing config fields
|
||||
if missing_config:
|
||||
print()
|
||||
print_info(f"Adding {len(missing_config)} new config option(s) with defaults...")
|
||||
for field in missing_config:
|
||||
print_success(f" Added {field['key']} = {field['default']}")
|
||||
|
||||
# Update config version
|
||||
config["_config_version"] = latest_ver
|
||||
save_config(config)
|
||||
|
||||
# Jump to summary
|
||||
_print_setup_summary(config, hermes_home)
|
||||
return
|
||||
|
||||
# =========================================================================
|
||||
# Step 0: Show paths
|
||||
# Step 0: Show paths (full setup)
|
||||
# =========================================================================
|
||||
print_header("Configuration Location")
|
||||
print_info(f"Config file: {get_config_path()}")
|
||||
@@ -586,108 +822,7 @@ def run_setup_wizard(args):
|
||||
print_success(" Configured ✓")
|
||||
|
||||
# =========================================================================
|
||||
# Save config
|
||||
# Save config and show summary
|
||||
# =========================================================================
|
||||
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!
|
||||
# =========================================================================
|
||||
print()
|
||||
print(color("┌─────────────────────────────────────────────────────────┐", Colors.GREEN))
|
||||
print(color("│ ✓ Setup Complete! │", Colors.GREEN))
|
||||
print(color("└─────────────────────────────────────────────────────────┘", Colors.GREEN))
|
||||
print()
|
||||
|
||||
# Show file locations prominently
|
||||
print(color("📁 All your files are in ~/.hermes/:", Colors.CYAN, Colors.BOLD))
|
||||
print()
|
||||
print(f" {color('Settings:', Colors.YELLOW)} {get_config_path()}")
|
||||
print(f" {color('API Keys:', Colors.YELLOW)} {get_env_path()}")
|
||||
print(f" {color('Data:', Colors.YELLOW)} {hermes_home}/cron/, sessions/, logs/")
|
||||
print()
|
||||
|
||||
print(color("─" * 60, Colors.DIM))
|
||||
print()
|
||||
print(color("📝 To edit your configuration:", Colors.CYAN, Colors.BOLD))
|
||||
print()
|
||||
print(f" {color('hermes config', Colors.GREEN)} View current settings")
|
||||
print(f" {color('hermes config edit', Colors.GREEN)} Open config in your editor")
|
||||
print(f" {color('hermes config set KEY VALUE', Colors.GREEN)}")
|
||||
print(f" Set a specific value")
|
||||
print()
|
||||
print(f" Or edit the files directly:")
|
||||
print(f" {color(f'nano {get_config_path()}', Colors.DIM)}")
|
||||
print(f" {color(f'nano {get_env_path()}', Colors.DIM)}")
|
||||
print()
|
||||
|
||||
print(color("─" * 60, Colors.DIM))
|
||||
print()
|
||||
print(color("🚀 Ready to go!", Colors.CYAN, Colors.BOLD))
|
||||
print()
|
||||
print(f" {color('hermes', Colors.GREEN)} Start chatting")
|
||||
print(f" {color('hermes gateway', Colors.GREEN)} Start messaging gateway")
|
||||
print(f" {color('hermes doctor', Colors.GREEN)} Check for issues")
|
||||
print()
|
||||
_print_setup_summary(config, hermes_home)
|
||||
|
||||
Reference in New Issue
Block a user