refactor: update environment variable configuration and add multi-select checklist for tool setup
- Cleared the REQUIRED_ENV_VARS dictionary as no single environment variable is universally required. - Enhanced the OPTIONAL_ENV_VARS with improved descriptions and added advanced options for better user guidance. - Introduced a new prompt_checklist function to allow users to select tools during setup, improving the configuration experience. - Updated the setup wizard to handle missing optional environment variables using the new checklist, streamlining the tool configuration process.
This commit is contained in:
parent
674a6f96d3
commit
b3bf21db56
2 changed files with 351 additions and 191 deletions
|
|
@ -133,19 +133,22 @@ DEFAULT_CONFIG = {
|
||||||
# Config Migration System
|
# Config Migration System
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
# Required environment variables with metadata for migration prompts
|
# Required environment variables with metadata for migration prompts.
|
||||||
REQUIRED_ENV_VARS = {
|
# LLM provider is required but handled in the setup wizard's provider
|
||||||
"OPENROUTER_API_KEY": {
|
# selection step (Nous Portal / OpenRouter / Custom endpoint), so this
|
||||||
"description": "OpenRouter API key (required for vision, web scraping, and tools)",
|
# dict is intentionally empty — no single env var is universally required.
|
||||||
"prompt": "OpenRouter API key",
|
REQUIRED_ENV_VARS = {}
|
||||||
"url": "https://openrouter.ai/keys",
|
|
||||||
"required": True,
|
|
||||||
"password": True,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
# Optional environment variables that enhance functionality
|
# Optional environment variables that enhance functionality
|
||||||
OPTIONAL_ENV_VARS = {
|
OPTIONAL_ENV_VARS = {
|
||||||
|
"OPENROUTER_API_KEY": {
|
||||||
|
"description": "OpenRouter API key (for vision, web scraping helpers, and MoA)",
|
||||||
|
"prompt": "OpenRouter API key",
|
||||||
|
"url": "https://openrouter.ai/keys",
|
||||||
|
"password": True,
|
||||||
|
"tools": ["vision_analyze", "mixture_of_agents"],
|
||||||
|
"advanced": True, # Handled in provider selection, not in tool checklist
|
||||||
|
},
|
||||||
"FIRECRAWL_API_KEY": {
|
"FIRECRAWL_API_KEY": {
|
||||||
"description": "Firecrawl API key for web search and scraping",
|
"description": "Firecrawl API key for web search and scraping",
|
||||||
"prompt": "Firecrawl API key",
|
"prompt": "Firecrawl API key",
|
||||||
|
|
@ -188,13 +191,6 @@ OPTIONAL_ENV_VARS = {
|
||||||
"tools": ["rl_get_results", "rl_check_status"],
|
"tools": ["rl_get_results", "rl_check_status"],
|
||||||
"password": True,
|
"password": True,
|
||||||
},
|
},
|
||||||
"OPENAI_BASE_URL": {
|
|
||||||
"description": "Custom OpenAI-compatible API endpoint (for VLLM/SGLang/etc.)",
|
|
||||||
"prompt": "OpenAI-compatible base URL (only if running your own endpoint)",
|
|
||||||
"url": None,
|
|
||||||
"password": False,
|
|
||||||
"advanced": True, # Hide from standard migrate flow
|
|
||||||
},
|
|
||||||
"HERMES_OPENAI_API_KEY": {
|
"HERMES_OPENAI_API_KEY": {
|
||||||
"description": "OpenAI API key for voice transcription (Whisper) and OpenAI TTS",
|
"description": "OpenAI API key for voice transcription (Whisper) and OpenAI TTS",
|
||||||
"prompt": "OpenAI API Key (for Whisper STT + TTS)",
|
"prompt": "OpenAI API Key (for Whisper STT + TTS)",
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,100 @@ def prompt_yes_no(question: str, default: bool = True) -> bool:
|
||||||
print_error("Please enter 'y' or 'n'")
|
print_error("Please enter 'y' or 'n'")
|
||||||
|
|
||||||
|
|
||||||
|
def prompt_checklist(title: str, items: list, pre_selected: list = None) -> list:
|
||||||
|
"""
|
||||||
|
Display a multi-select checklist and return the indices of selected items.
|
||||||
|
|
||||||
|
Each item in `items` is a display string. `pre_selected` is a list of
|
||||||
|
indices that should be checked by default. A "Continue →" option is
|
||||||
|
appended at the end — the user toggles items with Space and confirms
|
||||||
|
with Enter on "Continue →".
|
||||||
|
|
||||||
|
Falls back to a numbered toggle interface when simple_term_menu is
|
||||||
|
unavailable.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of selected indices (not including the Continue option).
|
||||||
|
"""
|
||||||
|
if pre_selected is None:
|
||||||
|
pre_selected = []
|
||||||
|
|
||||||
|
print(color(title, Colors.YELLOW))
|
||||||
|
print_info("Select the tools you want, then choose Continue.")
|
||||||
|
print()
|
||||||
|
|
||||||
|
try:
|
||||||
|
from simple_term_menu import TerminalMenu
|
||||||
|
|
||||||
|
menu_items = [f" {item}" for item in items] + [" Continue →"]
|
||||||
|
|
||||||
|
# Build preselected indices string (e.g. "0,2,4")
|
||||||
|
preselected = [str(i) for i in pre_selected]
|
||||||
|
|
||||||
|
terminal_menu = TerminalMenu(
|
||||||
|
menu_items,
|
||||||
|
multi_select=True,
|
||||||
|
show_multi_select_hint=True,
|
||||||
|
multi_select_cursor="[✓] ",
|
||||||
|
multi_select_cursor_brackets_style=("", ""),
|
||||||
|
multi_select_select_on_accept=False,
|
||||||
|
multi_select_empty_ok=True,
|
||||||
|
preselected_entries=preselected if preselected else None,
|
||||||
|
menu_cursor="→ ",
|
||||||
|
menu_cursor_style=("fg_green", "bold"),
|
||||||
|
menu_highlight_style=("fg_green",),
|
||||||
|
cycle_cursor=True,
|
||||||
|
clear_screen=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
terminal_menu.show()
|
||||||
|
|
||||||
|
if terminal_menu.chosen_menu_entries is None:
|
||||||
|
# User pressed Escape
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Filter out the "Continue →" entry and return original indices
|
||||||
|
continue_idx = len(items)
|
||||||
|
selected = [i for i in terminal_menu.chosen_menu_indices if i != continue_idx]
|
||||||
|
return selected
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
# Fallback: numbered toggle interface
|
||||||
|
selected = set(pre_selected)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
for i, item in enumerate(items):
|
||||||
|
marker = color("[✓]", Colors.GREEN) if i in selected else "[ ]"
|
||||||
|
print(f" {marker} {i + 1}. {item}")
|
||||||
|
print(f" {len(items) + 1}. {color('Continue →', Colors.GREEN)}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = input(color(" Toggle item # (or Enter to continue): ", Colors.DIM)).strip()
|
||||||
|
if not value:
|
||||||
|
break
|
||||||
|
idx = int(value) - 1
|
||||||
|
if idx == len(items):
|
||||||
|
break
|
||||||
|
if 0 <= idx < len(items):
|
||||||
|
if idx in selected:
|
||||||
|
selected.discard(idx)
|
||||||
|
else:
|
||||||
|
selected.add(idx)
|
||||||
|
else:
|
||||||
|
print_error(f"Enter a number between 1 and {len(items) + 1}")
|
||||||
|
except ValueError:
|
||||||
|
print_error("Enter a number")
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
print()
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Clear and redraw (simple approach)
|
||||||
|
print()
|
||||||
|
|
||||||
|
return sorted(selected)
|
||||||
|
|
||||||
|
|
||||||
def _print_setup_summary(config: dict, hermes_home):
|
def _print_setup_summary(config: dict, hermes_home):
|
||||||
"""Print the setup completion summary."""
|
"""Print the setup completion summary."""
|
||||||
# Tool availability summary
|
# Tool availability summary
|
||||||
|
|
@ -268,8 +362,12 @@ def run_setup_wizard(args):
|
||||||
config = load_config()
|
config = load_config()
|
||||||
hermes_home = get_hermes_home()
|
hermes_home = get_hermes_home()
|
||||||
|
|
||||||
# Check if this is an existing installation with config
|
# Check if this is an existing installation with config (any provider or config file)
|
||||||
is_existing = get_env_value("OPENROUTER_API_KEY") is not None or get_config_path().exists()
|
is_existing = (
|
||||||
|
get_env_value("OPENROUTER_API_KEY") is not None
|
||||||
|
or get_env_value("OPENAI_BASE_URL") is not None
|
||||||
|
or get_config_path().exists()
|
||||||
|
)
|
||||||
|
|
||||||
# Import migration helpers
|
# Import migration helpers
|
||||||
from hermes_cli.config import (
|
from hermes_cli.config import (
|
||||||
|
|
@ -375,16 +473,32 @@ def run_setup_wizard(args):
|
||||||
else:
|
else:
|
||||||
print_warning(f" Skipped {var['name']}")
|
print_warning(f" Skipped {var['name']}")
|
||||||
|
|
||||||
# Handle missing optional env vars
|
# Handle missing optional env vars — use a checkbox to let the
|
||||||
if missing_optional:
|
# user pick which tools to configure, then prompt for keys.
|
||||||
|
# Filter out "advanced" vars (handled elsewhere, e.g. provider step).
|
||||||
|
missing_tool_vars = [v for v in missing_optional if not v.get("advanced")]
|
||||||
|
|
||||||
|
if missing_tool_vars:
|
||||||
print()
|
print()
|
||||||
print_header("Optional Tools (Quick Setup)")
|
print_header("Optional Tools (Quick Setup)")
|
||||||
|
|
||||||
for var in missing_optional:
|
# Build checklist labels from the missing vars
|
||||||
|
checklist_labels = []
|
||||||
|
for var in missing_tool_vars:
|
||||||
tools = var.get("tools", [])
|
tools = var.get("tools", [])
|
||||||
tools_str = f" (enables: {', '.join(tools[:2])})" if tools else ""
|
tools_str = f" → {', '.join(tools[:2])}" if tools else ""
|
||||||
|
checklist_labels.append(f"{var['name']}{tools_str}")
|
||||||
|
|
||||||
if prompt_yes_no(f"Configure {var['name']}{tools_str}?", False):
|
selected_indices = prompt_checklist(
|
||||||
|
"Which missing tools would you like to configure?",
|
||||||
|
checklist_labels,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Prompt for keys only for selected tools
|
||||||
|
for idx in selected_indices:
|
||||||
|
var = missing_tool_vars[idx]
|
||||||
|
print()
|
||||||
|
print(color(f" {var['name']}", Colors.CYAN))
|
||||||
if var.get("url"):
|
if var.get("url"):
|
||||||
print_info(f" Get key at: {var['url']}")
|
print_info(f" Get key at: {var['url']}")
|
||||||
|
|
||||||
|
|
@ -395,7 +509,7 @@ def run_setup_wizard(args):
|
||||||
|
|
||||||
if value:
|
if value:
|
||||||
save_env_value(var["name"], value)
|
save_env_value(var["name"], value)
|
||||||
print_success(f" Saved")
|
print_success(f" Saved {var['name']}")
|
||||||
|
|
||||||
# Handle missing config fields
|
# Handle missing config fields
|
||||||
if missing_config:
|
if missing_config:
|
||||||
|
|
@ -440,6 +554,9 @@ def run_setup_wizard(args):
|
||||||
existing_or = get_env_value("OPENROUTER_API_KEY")
|
existing_or = get_env_value("OPENROUTER_API_KEY")
|
||||||
active_oauth = get_active_provider()
|
active_oauth = get_active_provider()
|
||||||
|
|
||||||
|
# Detect if any provider is already configured
|
||||||
|
has_any_provider = bool(active_oauth or existing_custom or existing_or)
|
||||||
|
|
||||||
# Build "keep current" label
|
# Build "keep current" label
|
||||||
if active_oauth and active_oauth in PROVIDER_REGISTRY:
|
if active_oauth and active_oauth in PROVIDER_REGISTRY:
|
||||||
keep_label = f"Keep current ({PROVIDER_REGISTRY[active_oauth].name})"
|
keep_label = f"Keep current ({PROVIDER_REGISTRY[active_oauth].name})"
|
||||||
|
|
@ -448,16 +565,24 @@ def run_setup_wizard(args):
|
||||||
elif existing_or:
|
elif existing_or:
|
||||||
keep_label = "Keep current (OpenRouter)"
|
keep_label = "Keep current (OpenRouter)"
|
||||||
else:
|
else:
|
||||||
keep_label = "Keep current"
|
keep_label = None # No provider configured — don't show "Keep current"
|
||||||
|
|
||||||
provider_choices = [
|
provider_choices = [
|
||||||
"Login with Nous Portal (Nous Research subscription)",
|
"Login with Nous Portal (Nous Research subscription)",
|
||||||
"OpenRouter API key (100+ models, pay-per-use)",
|
"OpenRouter API key (100+ models, pay-per-use)",
|
||||||
"Custom OpenAI-compatible endpoint (self-hosted / VLLM / etc.)",
|
"Custom OpenAI-compatible endpoint (self-hosted / VLLM / etc.)",
|
||||||
keep_label,
|
|
||||||
]
|
]
|
||||||
|
if keep_label:
|
||||||
|
provider_choices.append(keep_label)
|
||||||
|
|
||||||
provider_idx = prompt_choice("Select your inference provider:", provider_choices, 3)
|
# Default to "Keep current" if a provider exists, otherwise OpenRouter (most common)
|
||||||
|
default_provider = len(provider_choices) - 1 if has_any_provider else 1
|
||||||
|
|
||||||
|
if not has_any_provider:
|
||||||
|
print_warning("An inference provider is required for Hermes to work.")
|
||||||
|
print()
|
||||||
|
|
||||||
|
provider_idx = prompt_choice("Select your inference provider:", provider_choices, default_provider)
|
||||||
|
|
||||||
# Track which provider was selected for model step
|
# Track which provider was selected for model step
|
||||||
selected_provider = None # "nous", "openrouter", "custom", or None (keep)
|
selected_provider = None # "nous", "openrouter", "custom", or None (keep)
|
||||||
|
|
@ -557,7 +682,7 @@ def run_setup_wizard(args):
|
||||||
config['model'] = model_name
|
config['model'] = model_name
|
||||||
save_env_value("LLM_MODEL", model_name)
|
save_env_value("LLM_MODEL", model_name)
|
||||||
print_success("Custom endpoint configured")
|
print_success("Custom endpoint configured")
|
||||||
# else: provider_idx == 3, keep current
|
# else: provider_idx == 3 (Keep current) — only shown when a provider already exists
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Step 1b: OpenRouter API Key for tools (if not already set)
|
# Step 1b: OpenRouter API Key for tools (if not already set)
|
||||||
|
|
@ -1087,43 +1212,97 @@ def run_setup_wizard(args):
|
||||||
print_info("━" * 50)
|
print_info("━" * 50)
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Step 8: Additional Tools (Optional)
|
# Step 8: Additional Tools (Checkbox Selection)
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
print_header("Additional Tools (Optional)")
|
print_header("Additional Tools")
|
||||||
print_info("These tools extend the agent's capabilities.")
|
print_info("Select which tools you'd like to configure.")
|
||||||
print_info("Without their API keys, the corresponding features will be disabled.")
|
print_info("You can always add more later with 'hermes setup'.")
|
||||||
print()
|
print()
|
||||||
|
|
||||||
# Firecrawl - Web scraping
|
# Define tool categories for the checklist.
|
||||||
print_info("─" * 50)
|
# Each entry: (display_label, setup_function_key, check_keys)
|
||||||
print(color(" Web Search & Scraping (Firecrawl)", Colors.CYAN))
|
# check_keys = env vars that indicate this tool is already configured
|
||||||
print_info(" Enables: web_search, web_extract tools")
|
TOOL_CATEGORIES = [
|
||||||
print_info(" Use case: Search the web, read webpage content")
|
{
|
||||||
if get_env_value('FIRECRAWL_API_KEY'):
|
"label": "🔍 Web Search & Scraping (Firecrawl)",
|
||||||
print_success(" Status: Configured ✓")
|
"key": "firecrawl",
|
||||||
if prompt_yes_no(" Update Firecrawl API key?", False):
|
"check": ["FIRECRAWL_API_KEY"],
|
||||||
api_key = prompt(" API key", password=True)
|
},
|
||||||
|
{
|
||||||
|
"label": "🌐 Browser Automation (Browserbase)",
|
||||||
|
"key": "browserbase",
|
||||||
|
"check": ["BROWSERBASE_API_KEY"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "🎨 Image Generation (FAL / FLUX)",
|
||||||
|
"key": "fal",
|
||||||
|
"check": ["FAL_KEY"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "🎤 Voice Transcription & TTS (OpenAI Whisper + TTS)",
|
||||||
|
"key": "openai_voice",
|
||||||
|
"check": ["HERMES_OPENAI_API_KEY"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "🗣️ Premium Text-to-Speech (ElevenLabs)",
|
||||||
|
"key": "elevenlabs",
|
||||||
|
"check": ["ELEVENLABS_API_KEY"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "🧪 RL Training (Tinker + WandB)",
|
||||||
|
"key": "rl_training",
|
||||||
|
"check": ["TINKER_API_KEY", "WANDB_API_KEY"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "🔧 Skills Hub (GitHub token for higher rate limits)",
|
||||||
|
"key": "github",
|
||||||
|
"check": ["GITHUB_TOKEN"],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
# Pre-select tools that are already configured
|
||||||
|
pre_selected = []
|
||||||
|
for i, cat in enumerate(TOOL_CATEGORIES):
|
||||||
|
if all(get_env_value(k) for k in cat["check"]):
|
||||||
|
pre_selected.append(i)
|
||||||
|
|
||||||
|
checklist_labels = [cat["label"] for cat in TOOL_CATEGORIES]
|
||||||
|
selected_indices = prompt_checklist(
|
||||||
|
"Which tools would you like to enable?",
|
||||||
|
checklist_labels,
|
||||||
|
pre_selected=pre_selected,
|
||||||
|
)
|
||||||
|
|
||||||
|
selected_keys = {TOOL_CATEGORIES[i]["key"] for i in selected_indices}
|
||||||
|
|
||||||
|
# Now prompt for API keys only for the tools the user selected
|
||||||
|
|
||||||
|
if "firecrawl" in selected_keys:
|
||||||
|
print()
|
||||||
|
print(color(" ─── Web Search & Scraping (Firecrawl) ───", Colors.CYAN))
|
||||||
|
print_info(" Get your API key at: https://firecrawl.dev/")
|
||||||
|
existing = get_env_value('FIRECRAWL_API_KEY')
|
||||||
|
if existing:
|
||||||
|
print_success(" Already configured ✓")
|
||||||
|
if prompt_yes_no(" Update API key?", False):
|
||||||
|
api_key = prompt(" Firecrawl API key", password=True)
|
||||||
if api_key:
|
if api_key:
|
||||||
save_env_value("FIRECRAWL_API_KEY", api_key)
|
save_env_value("FIRECRAWL_API_KEY", api_key)
|
||||||
print_success(" Updated")
|
print_success(" Updated")
|
||||||
else:
|
else:
|
||||||
print_warning(" Status: Not configured (tools will be disabled)")
|
api_key = prompt(" Firecrawl API key", password=True)
|
||||||
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:
|
if api_key:
|
||||||
save_env_value("FIRECRAWL_API_KEY", api_key)
|
save_env_value("FIRECRAWL_API_KEY", api_key)
|
||||||
print_success(" Configured ✓")
|
print_success(" Configured ✓")
|
||||||
print()
|
|
||||||
|
|
||||||
# Browserbase - Browser automation
|
if "browserbase" in selected_keys:
|
||||||
print_info("─" * 50)
|
print()
|
||||||
print(color(" Browser Automation (Browserbase)", Colors.CYAN))
|
print(color(" ─── Browser Automation (Browserbase) ───", Colors.CYAN))
|
||||||
print_info(" Enables: browser_navigate, browser_click, etc.")
|
print_info(" Get credentials at: https://browserbase.com/")
|
||||||
print_info(" Use case: Interact with web pages, fill forms, screenshots")
|
existing = get_env_value('BROWSERBASE_API_KEY')
|
||||||
if get_env_value('BROWSERBASE_API_KEY'):
|
if existing:
|
||||||
print_success(" Status: Configured ✓")
|
print_success(" Already configured ✓")
|
||||||
if prompt_yes_no(" Update Browserbase credentials?", False):
|
if prompt_yes_no(" Update credentials?", False):
|
||||||
api_key = prompt(" API key", password=True)
|
api_key = prompt(" API key", password=True)
|
||||||
project_id = prompt(" Project ID")
|
project_id = prompt(" Project ID")
|
||||||
if api_key:
|
if api_key:
|
||||||
|
|
@ -1132,17 +1311,14 @@ def run_setup_wizard(args):
|
||||||
save_env_value("BROWSERBASE_PROJECT_ID", project_id)
|
save_env_value("BROWSERBASE_PROJECT_ID", project_id)
|
||||||
print_success(" Updated")
|
print_success(" Updated")
|
||||||
else:
|
else:
|
||||||
print_warning(" Status: Not configured (tools will be disabled)")
|
api_key = prompt(" Browserbase API key", password=True)
|
||||||
if prompt_yes_no(" Set up Browserbase?", False):
|
project_id = prompt(" Browserbase Project ID")
|
||||||
print_info(" Get credentials at: https://browserbase.com/")
|
|
||||||
api_key = prompt(" API key", password=True)
|
|
||||||
project_id = prompt(" Project ID")
|
|
||||||
if api_key:
|
if api_key:
|
||||||
save_env_value("BROWSERBASE_API_KEY", api_key)
|
save_env_value("BROWSERBASE_API_KEY", api_key)
|
||||||
if project_id:
|
if project_id:
|
||||||
save_env_value("BROWSERBASE_PROJECT_ID", project_id)
|
save_env_value("BROWSERBASE_PROJECT_ID", project_id)
|
||||||
|
|
||||||
# Check if Node.js dependencies are installed (required for browser tools)
|
# Auto-install Node.js deps if possible
|
||||||
import shutil
|
import shutil
|
||||||
node_modules = PROJECT_ROOT / "node_modules" / "agent-browser"
|
node_modules = PROJECT_ROOT / "node_modules" / "agent-browser"
|
||||||
if not node_modules.exists() and shutil.which("npm"):
|
if not node_modules.exists() and shutil.which("npm"):
|
||||||
|
|
@ -1159,92 +1335,91 @@ def run_setup_wizard(args):
|
||||||
elif not node_modules.exists():
|
elif not node_modules.exists():
|
||||||
print_warning(" Node.js not found — browser tools require: npm install (in the hermes-agent directory)")
|
print_warning(" Node.js not found — browser tools require: npm install (in the hermes-agent directory)")
|
||||||
|
|
||||||
print_success(" Configured ✓")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# 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:
|
if api_key:
|
||||||
save_env_value("FAL_KEY", api_key)
|
print_success(" Configured ✓")
|
||||||
print_success(" Updated")
|
|
||||||
else:
|
if "fal" in selected_keys:
|
||||||
print_warning(" Status: Not configured (tool will be disabled)")
|
print()
|
||||||
if prompt_yes_no(" Set up FAL?", False):
|
print(color(" ─── Image Generation (FAL) ───", Colors.CYAN))
|
||||||
print_info(" Get your API key at: https://fal.ai/")
|
print_info(" Get your API key at: https://fal.ai/")
|
||||||
api_key = prompt(" API key", password=True)
|
existing = get_env_value('FAL_KEY')
|
||||||
|
if existing:
|
||||||
|
print_success(" Already configured ✓")
|
||||||
|
if prompt_yes_no(" Update API key?", False):
|
||||||
|
api_key = prompt(" FAL API key", password=True)
|
||||||
|
if api_key:
|
||||||
|
save_env_value("FAL_KEY", api_key)
|
||||||
|
print_success(" Updated")
|
||||||
|
else:
|
||||||
|
api_key = prompt(" FAL API key", password=True)
|
||||||
if api_key:
|
if api_key:
|
||||||
save_env_value("FAL_KEY", api_key)
|
save_env_value("FAL_KEY", api_key)
|
||||||
print_success(" Configured ✓")
|
print_success(" Configured ✓")
|
||||||
print()
|
|
||||||
|
|
||||||
# ElevenLabs - Premium TTS
|
if "openai_voice" in selected_keys:
|
||||||
print_info("─" * 50)
|
print()
|
||||||
print(color(" Text-to-Speech - ElevenLabs (Premium)", Colors.CYAN))
|
print(color(" ─── Voice Transcription & TTS (OpenAI) ───", Colors.CYAN))
|
||||||
print_info(" Enables: Premium TTS voices (Edge TTS is free and works without a key)")
|
print_info(" Used for Whisper speech-to-text and OpenAI TTS voices.")
|
||||||
print_info(" Use case: High-quality, customizable voice synthesis")
|
print_info(" Get your API key at: https://platform.openai.com/api-keys")
|
||||||
if get_env_value('ELEVENLABS_API_KEY'):
|
existing = get_env_value('HERMES_OPENAI_API_KEY')
|
||||||
print_success(" Status: Configured ✓")
|
if existing:
|
||||||
if prompt_yes_no(" Update ElevenLabs API key?", False):
|
print_success(" Already configured ✓")
|
||||||
api_key = prompt(" API key", password=True)
|
if prompt_yes_no(" Update API key?", False):
|
||||||
|
api_key = prompt(" OpenAI API key", password=True)
|
||||||
|
if api_key:
|
||||||
|
save_env_value("HERMES_OPENAI_API_KEY", api_key)
|
||||||
|
print_success(" Updated")
|
||||||
|
else:
|
||||||
|
api_key = prompt(" OpenAI API key", password=True)
|
||||||
|
if api_key:
|
||||||
|
save_env_value("HERMES_OPENAI_API_KEY", api_key)
|
||||||
|
print_success(" Configured ✓")
|
||||||
|
|
||||||
|
if "elevenlabs" in selected_keys:
|
||||||
|
print()
|
||||||
|
print(color(" ─── Premium TTS (ElevenLabs) ───", Colors.CYAN))
|
||||||
|
print_info(" High-quality voice synthesis. Free Edge TTS works without a key.")
|
||||||
|
print_info(" Get your API key at: https://elevenlabs.io/")
|
||||||
|
existing = get_env_value('ELEVENLABS_API_KEY')
|
||||||
|
if existing:
|
||||||
|
print_success(" Already configured ✓")
|
||||||
|
if prompt_yes_no(" Update API key?", False):
|
||||||
|
api_key = prompt(" ElevenLabs API key", password=True)
|
||||||
if api_key:
|
if api_key:
|
||||||
save_env_value("ELEVENLABS_API_KEY", api_key)
|
save_env_value("ELEVENLABS_API_KEY", api_key)
|
||||||
print_success(" Updated")
|
print_success(" Updated")
|
||||||
else:
|
else:
|
||||||
print_warning(" Status: Not configured (free Edge TTS will be used by default)")
|
api_key = prompt(" ElevenLabs API key", password=True)
|
||||||
if prompt_yes_no(" Set up ElevenLabs?", False):
|
|
||||||
print_info(" Get your API key at: https://elevenlabs.io/")
|
|
||||||
api_key = prompt(" API key", password=True)
|
|
||||||
if api_key:
|
if api_key:
|
||||||
save_env_value("ELEVENLABS_API_KEY", api_key)
|
save_env_value("ELEVENLABS_API_KEY", api_key)
|
||||||
print_success(" Configured ✓")
|
print_success(" Configured ✓")
|
||||||
|
|
||||||
|
if "rl_training" in selected_keys:
|
||||||
print()
|
print()
|
||||||
|
print(color(" ─── RL Training (Tinker + WandB) ───", Colors.CYAN))
|
||||||
|
|
||||||
# Tinker + WandB - RL Training
|
|
||||||
print_info("─" * 50)
|
|
||||||
print(color(" RL Training (Tinker + WandB)", Colors.CYAN))
|
|
||||||
print_info(" Enables: rl_start_training, rl_check_status, rl_get_results tools")
|
|
||||||
print_info(" Use case: Run reinforcement learning training via Tinker API")
|
|
||||||
tinker_configured = get_env_value('TINKER_API_KEY')
|
|
||||||
wandb_configured = get_env_value('WANDB_API_KEY')
|
|
||||||
|
|
||||||
# Check Python version requirement upfront
|
|
||||||
rl_python_ok = sys.version_info >= (3, 11)
|
rl_python_ok = sys.version_info >= (3, 11)
|
||||||
if not rl_python_ok:
|
if not rl_python_ok:
|
||||||
print_warning(f" Requires Python 3.11+ (current: {sys.version_info.major}.{sys.version_info.minor})")
|
print_error(f" Requires Python 3.11+ (current: {sys.version_info.major}.{sys.version_info.minor})")
|
||||||
|
|
||||||
if tinker_configured and wandb_configured:
|
|
||||||
print_success(" Status: Configured ✓")
|
|
||||||
if prompt_yes_no(" Update RL training credentials?", False):
|
|
||||||
api_key = prompt(" Tinker API key", password=True)
|
|
||||||
if api_key:
|
|
||||||
save_env_value("TINKER_API_KEY", api_key)
|
|
||||||
wandb_key = prompt(" WandB API key", password=True)
|
|
||||||
if wandb_key:
|
|
||||||
save_env_value("WANDB_API_KEY", wandb_key)
|
|
||||||
print_success(" Updated")
|
|
||||||
else:
|
|
||||||
if tinker_configured:
|
|
||||||
print_warning(" Status: Tinker configured, WandB missing")
|
|
||||||
elif wandb_configured:
|
|
||||||
print_warning(" Status: WandB configured, Tinker missing")
|
|
||||||
else:
|
|
||||||
print_warning(" Status: Not configured (tools will be disabled)")
|
|
||||||
|
|
||||||
if prompt_yes_no(" Set up RL Training?", False):
|
|
||||||
# Check Python version before proceeding
|
|
||||||
if not rl_python_ok:
|
|
||||||
print_error(f" Python 3.11+ required (current: {sys.version_info.major}.{sys.version_info.minor})")
|
|
||||||
print_info(" Upgrade Python and reinstall to enable RL training tools")
|
print_info(" Upgrade Python and reinstall to enable RL training tools")
|
||||||
else:
|
else:
|
||||||
print_info(" Get Tinker key at: https://tinker-console.thinkingmachines.ai/keys")
|
print_info(" Get Tinker key at: https://tinker-console.thinkingmachines.ai/keys")
|
||||||
print_info(" Get WandB key at: https://wandb.ai/authorize")
|
print_info(" Get WandB key at: https://wandb.ai/authorize")
|
||||||
|
|
||||||
|
tinker_existing = get_env_value('TINKER_API_KEY')
|
||||||
|
wandb_existing = get_env_value('WANDB_API_KEY')
|
||||||
|
|
||||||
|
if tinker_existing and wandb_existing:
|
||||||
|
print_success(" Already configured ✓")
|
||||||
|
if prompt_yes_no(" Update credentials?", False):
|
||||||
|
api_key = prompt(" Tinker API key", password=True)
|
||||||
|
if api_key:
|
||||||
|
save_env_value("TINKER_API_KEY", api_key)
|
||||||
|
wandb_key = prompt(" WandB API key", password=True)
|
||||||
|
if wandb_key:
|
||||||
|
save_env_value("WANDB_API_KEY", wandb_key)
|
||||||
|
print_success(" Updated")
|
||||||
|
else:
|
||||||
api_key = prompt(" Tinker API key", password=True)
|
api_key = prompt(" Tinker API key", password=True)
|
||||||
if api_key:
|
if api_key:
|
||||||
save_env_value("TINKER_API_KEY", api_key)
|
save_env_value("TINKER_API_KEY", api_key)
|
||||||
|
|
@ -1252,7 +1427,7 @@ def run_setup_wizard(args):
|
||||||
if wandb_key:
|
if wandb_key:
|
||||||
save_env_value("WANDB_API_KEY", wandb_key)
|
save_env_value("WANDB_API_KEY", wandb_key)
|
||||||
|
|
||||||
# Check if tinker-atropos submodule is installed
|
# Auto-install tinker-atropos submodule if missing
|
||||||
try:
|
try:
|
||||||
__import__("tinker_atropos")
|
__import__("tinker_atropos")
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
@ -1261,7 +1436,6 @@ def run_setup_wizard(args):
|
||||||
print_info(" Installing tinker-atropos submodule...")
|
print_info(" Installing tinker-atropos submodule...")
|
||||||
import subprocess
|
import subprocess
|
||||||
import shutil
|
import shutil
|
||||||
# Prefer uv for speed, fall back to pip
|
|
||||||
uv_bin = shutil.which("uv")
|
uv_bin = shutil.which("uv")
|
||||||
if uv_bin:
|
if uv_bin:
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
|
|
@ -1288,35 +1462,25 @@ def run_setup_wizard(args):
|
||||||
else:
|
else:
|
||||||
print_warning(" Partially configured (both keys required)")
|
print_warning(" Partially configured (both keys required)")
|
||||||
|
|
||||||
# =========================================================================
|
if "github" in selected_keys:
|
||||||
# Step 9: Skills Hub (Optional)
|
|
||||||
# =========================================================================
|
|
||||||
print_header("Skills Hub (Optional)")
|
|
||||||
print_info("A GitHub token enables higher API rate limits for skill search/install,")
|
|
||||||
print_info("and is required for publishing skills via GitHub PRs.")
|
|
||||||
print()
|
print()
|
||||||
|
print(color(" ─── Skills Hub (GitHub) ───", Colors.CYAN))
|
||||||
github_configured = get_env_value('GITHUB_TOKEN')
|
print_info(" Enables higher API rate limits for skill search/install")
|
||||||
if github_configured:
|
print_info(" and publishing skills via GitHub PRs.")
|
||||||
print_success(" GitHub token: configured ✓")
|
print_info(" Get a token at: https://github.com/settings/tokens")
|
||||||
choice = prompt(" Reconfigure? (y/N)", default="n")
|
existing = get_env_value('GITHUB_TOKEN')
|
||||||
if choice.lower() == 'y':
|
if existing:
|
||||||
|
print_success(" Already configured ✓")
|
||||||
|
if prompt_yes_no(" Update token?", False):
|
||||||
token = prompt(" GitHub Token (ghp_...)", password=True)
|
token = prompt(" GitHub Token (ghp_...)", password=True)
|
||||||
if token:
|
if token:
|
||||||
save_env_value("GITHUB_TOKEN", token)
|
save_env_value("GITHUB_TOKEN", token)
|
||||||
print_success(" Updated")
|
print_success(" Updated")
|
||||||
else:
|
else:
|
||||||
print_warning(" GitHub token: not configured (60 req/hr rate limit)")
|
|
||||||
choice = prompt(" Configure now? (y/N)", default="n")
|
|
||||||
if choice.lower() == 'y':
|
|
||||||
print_info(" Get a token at: https://github.com/settings/tokens")
|
|
||||||
print_info(" Recommended: Fine-grained token with Contents + Pull Requests permissions")
|
|
||||||
token = prompt(" GitHub Token", password=True)
|
token = prompt(" GitHub Token", password=True)
|
||||||
if token:
|
if token:
|
||||||
save_env_value("GITHUB_TOKEN", token)
|
save_env_value("GITHUB_TOKEN", token)
|
||||||
print_success(" Configured ✓")
|
print_success(" Configured ✓")
|
||||||
else:
|
|
||||||
print_info(" Skipped — you can add it later in ~/.hermes/.env")
|
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Save config and show summary
|
# Save config and show summary
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue