refactor: enhance environment variable configuration and setup wizard

- Updated the OPTIONAL_ENV_VARS dictionary to include a new "category" field for better organization of environment variables.
- Improved the setup wizard to categorize missing optional environment variables into tools and messaging platforms, enhancing user experience during configuration.
- Streamlined the prompts for configuring tools and messaging platforms, allowing for a more intuitive setup process.
This commit is contained in:
Teknium 2026-02-23 23:25:38 +00:00
parent 0858ee2f27
commit cefe038a87
2 changed files with 147 additions and 95 deletions

View file

@ -141,34 +141,41 @@ REQUIRED_ENV_VARS = {}
# Optional environment variables that enhance functionality # Optional environment variables that enhance functionality
OPTIONAL_ENV_VARS = { OPTIONAL_ENV_VARS = {
# ── Provider (handled in provider selection, not shown in checklists) ──
"OPENROUTER_API_KEY": { "OPENROUTER_API_KEY": {
"description": "OpenRouter API key (for vision, web scraping helpers, and MoA)", "description": "OpenRouter API key (for vision, web scraping helpers, and MoA)",
"prompt": "OpenRouter API key", "prompt": "OpenRouter API key",
"url": "https://openrouter.ai/keys", "url": "https://openrouter.ai/keys",
"password": True, "password": True,
"tools": ["vision_analyze", "mixture_of_agents"], "tools": ["vision_analyze", "mixture_of_agents"],
"advanced": True, # Handled in provider selection, not in tool checklist "category": "provider",
"advanced": True,
}, },
# ── Tool API keys ──
"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",
"url": "https://firecrawl.dev/", "url": "https://firecrawl.dev/",
"tools": ["web_search", "web_extract"], "tools": ["web_search", "web_extract"],
"password": True, "password": True,
"category": "tool",
}, },
"BROWSERBASE_API_KEY": { "BROWSERBASE_API_KEY": {
"description": "Browserbase API key for browser automation", "description": "Browserbase API key for browser automation",
"prompt": "Browserbase API key", "prompt": "Browserbase API key",
"url": "https://browserbase.com/", "url": "https://browserbase.com/",
"tools": ["browser_navigate", "browser_click", "etc."], "tools": ["browser_navigate", "browser_click"],
"password": True, "password": True,
"category": "tool",
}, },
"BROWSERBASE_PROJECT_ID": { "BROWSERBASE_PROJECT_ID": {
"description": "Browserbase project ID", "description": "Browserbase project ID",
"prompt": "Browserbase project ID", "prompt": "Browserbase project ID",
"url": "https://browserbase.com/", "url": "https://browserbase.com/",
"tools": ["browser_navigate", "browser_click", "etc."], "tools": ["browser_navigate", "browser_click"],
"password": False, "password": False,
"category": "tool",
}, },
"FAL_KEY": { "FAL_KEY": {
"description": "FAL API key for image generation", "description": "FAL API key for image generation",
@ -176,6 +183,7 @@ OPTIONAL_ENV_VARS = {
"url": "https://fal.ai/", "url": "https://fal.ai/",
"tools": ["image_generate"], "tools": ["image_generate"],
"password": True, "password": True,
"category": "tool",
}, },
"TINKER_API_KEY": { "TINKER_API_KEY": {
"description": "Tinker API key for RL training", "description": "Tinker API key for RL training",
@ -183,6 +191,7 @@ OPTIONAL_ENV_VARS = {
"url": "https://tinker-console.thinkingmachines.ai/keys", "url": "https://tinker-console.thinkingmachines.ai/keys",
"tools": ["rl_start_training", "rl_check_status", "rl_stop_training"], "tools": ["rl_start_training", "rl_check_status", "rl_stop_training"],
"password": True, "password": True,
"category": "tool",
}, },
"WANDB_API_KEY": { "WANDB_API_KEY": {
"description": "Weights & Biases API key for experiment tracking", "description": "Weights & Biases API key for experiment tracking",
@ -190,6 +199,7 @@ OPTIONAL_ENV_VARS = {
"url": "https://wandb.ai/authorize", "url": "https://wandb.ai/authorize",
"tools": ["rl_get_results", "rl_check_status"], "tools": ["rl_get_results", "rl_check_status"],
"password": True, "password": True,
"category": "tool",
}, },
"VOICE_TOOLS_OPENAI_KEY": { "VOICE_TOOLS_OPENAI_KEY": {
"description": "OpenAI API key for voice transcription (Whisper) and OpenAI TTS", "description": "OpenAI API key for voice transcription (Whisper) and OpenAI TTS",
@ -197,98 +207,111 @@ OPTIONAL_ENV_VARS = {
"url": "https://platform.openai.com/api-keys", "url": "https://platform.openai.com/api-keys",
"tools": ["voice_transcription", "openai_tts"], "tools": ["voice_transcription", "openai_tts"],
"password": True, "password": True,
"category": "tool",
}, },
"SLACK_BOT_TOKEN": {
"description": "Slack bot integration",
"prompt": "Slack Bot Token (xoxb-...)",
"url": "https://api.slack.com/apps",
"tools": ["slack"],
"password": True,
},
"SLACK_APP_TOKEN": {
"description": "Slack Socket Mode connection",
"prompt": "Slack App Token (xapp-...)",
"url": "https://api.slack.com/apps",
"tools": ["slack"],
"password": True,
},
# Messaging platform tokens
"TELEGRAM_BOT_TOKEN": {
"description": "Telegram bot token from @BotFather",
"prompt": "Telegram bot token",
"url": "https://t.me/BotFather",
"password": True,
},
"TELEGRAM_ALLOWED_USERS": {
"description": "Comma-separated Telegram user IDs allowed to use the bot (get ID from @userinfobot)",
"prompt": "Allowed Telegram user IDs (comma-separated)",
"url": "https://t.me/userinfobot",
"password": False,
},
"DISCORD_BOT_TOKEN": {
"description": "Discord bot token from Developer Portal",
"prompt": "Discord bot token",
"url": "https://discord.com/developers/applications",
"password": True,
},
"DISCORD_ALLOWED_USERS": {
"description": "Comma-separated Discord user IDs allowed to use the bot",
"prompt": "Allowed Discord user IDs (comma-separated)",
"url": None,
"password": False,
},
# Text-to-speech (premium providers)
"ELEVENLABS_API_KEY": { "ELEVENLABS_API_KEY": {
"description": "ElevenLabs API key for premium text-to-speech voices", "description": "ElevenLabs API key for premium text-to-speech voices",
"prompt": "ElevenLabs API key", "prompt": "ElevenLabs API key",
"url": "https://elevenlabs.io/", "url": "https://elevenlabs.io/",
"password": True, "password": True,
}, "category": "tool",
# Terminal configuration
"MESSAGING_CWD": {
"description": "Working directory for terminal commands via messaging (Telegram/Discord/etc). CLI always uses current directory.",
"prompt": "Messaging working directory (default: home)",
"url": None,
"password": False,
},
"SUDO_PASSWORD": {
"description": "Sudo password for terminal commands requiring root access",
"prompt": "Sudo password",
"url": None,
"password": True,
},
# Agent configuration
"HERMES_MAX_ITERATIONS": {
"description": "Maximum tool-calling iterations per conversation (default: 60)",
"prompt": "Max iterations",
"url": None,
"password": False,
},
"HERMES_TOOL_PROGRESS": {
"description": "Send tool progress messages in messaging channels (true/false)",
"prompt": "Enable tool progress messages",
"url": None,
"password": False,
},
"HERMES_TOOL_PROGRESS_MODE": {
"description": "Progress mode: 'all' (every tool) or 'new' (only when tool changes)",
"prompt": "Progress mode (all/new)",
"url": None,
"password": False,
}, },
"GITHUB_TOKEN": { "GITHUB_TOKEN": {
"description": "GitHub token for Skills Hub (higher API rate limits, skill publish)", "description": "GitHub token for Skills Hub (higher API rate limits, skill publish)",
"prompt": "GitHub Token", "prompt": "GitHub Token",
"url": "https://github.com/settings/tokens", "url": "https://github.com/settings/tokens",
"password": True, "password": True,
"category": "tool",
},
# ── Messaging platforms ──
"TELEGRAM_BOT_TOKEN": {
"description": "Telegram bot token from @BotFather",
"prompt": "Telegram bot token",
"url": "https://t.me/BotFather",
"password": True,
"category": "messaging",
},
"TELEGRAM_ALLOWED_USERS": {
"description": "Comma-separated Telegram user IDs allowed to use the bot (get ID from @userinfobot)",
"prompt": "Allowed Telegram user IDs (comma-separated)",
"url": "https://t.me/userinfobot",
"password": False,
"category": "messaging",
},
"DISCORD_BOT_TOKEN": {
"description": "Discord bot token from Developer Portal",
"prompt": "Discord bot token",
"url": "https://discord.com/developers/applications",
"password": True,
"category": "messaging",
},
"DISCORD_ALLOWED_USERS": {
"description": "Comma-separated Discord user IDs allowed to use the bot",
"prompt": "Allowed Discord user IDs (comma-separated)",
"url": None,
"password": False,
"category": "messaging",
},
"SLACK_BOT_TOKEN": {
"description": "Slack bot integration",
"prompt": "Slack Bot Token (xoxb-...)",
"url": "https://api.slack.com/apps",
"password": True,
"category": "messaging",
},
"SLACK_APP_TOKEN": {
"description": "Slack Socket Mode connection",
"prompt": "Slack App Token (xapp-...)",
"url": "https://api.slack.com/apps",
"password": True,
"category": "messaging",
}, },
"GATEWAY_ALLOW_ALL_USERS": { "GATEWAY_ALLOW_ALL_USERS": {
"description": "Allow all users to interact with messaging bots (true/false). Default: false (deny unless allowlisted).", "description": "Allow all users to interact with messaging bots (true/false). Default: false.",
"prompt": "Allow all users (true/false)", "prompt": "Allow all users (true/false)",
"url": None, "url": None,
"password": False, "password": False,
"category": "messaging",
"advanced": True, "advanced": True,
}, },
# ── Agent settings ──
"MESSAGING_CWD": {
"description": "Working directory for terminal commands via messaging",
"prompt": "Messaging working directory (default: home)",
"url": None,
"password": False,
"category": "setting",
},
"SUDO_PASSWORD": {
"description": "Sudo password for terminal commands requiring root access",
"prompt": "Sudo password",
"url": None,
"password": True,
"category": "setting",
},
"HERMES_MAX_ITERATIONS": {
"description": "Maximum tool-calling iterations per conversation (default: 60)",
"prompt": "Max iterations",
"url": None,
"password": False,
"category": "setting",
},
"HERMES_TOOL_PROGRESS": {
"description": "Send tool progress messages in messaging channels (true/false)",
"prompt": "Enable tool progress messages",
"url": None,
"password": False,
"category": "setting",
},
"HERMES_TOOL_PROGRESS_MODE": {
"description": "Progress mode: 'all' (every tool) or 'new' (only when tool changes)",
"prompt": "Progress mode (all/new)",
"url": None,
"password": False,
"category": "setting",
},
} }

View file

@ -472,43 +472,72 @@ def run_setup_wizard(args):
else: else:
print_warning(f" Skipped {var['name']}") print_warning(f" Skipped {var['name']}")
# Handle missing optional env vars — use a checkbox to let the # Split missing optional vars by category
# user pick which tools to configure, then prompt for keys. missing_tools = [v for v in missing_optional if v.get("category") == "tool"]
# Filter out "advanced" vars (handled elsewhere, e.g. provider step). missing_messaging = [v for v in missing_optional if v.get("category") == "messaging" and not v.get("advanced")]
missing_tool_vars = [v for v in missing_optional if not v.get("advanced")] # Settings are silently applied with defaults in quick mode
if missing_tool_vars: # ── Tool API keys (checklist) ──
if missing_tools:
print() print()
print_header("Optional Tools (Quick Setup)") print_header("Tool API Keys")
# Build checklist labels from the missing vars
checklist_labels = [] checklist_labels = []
for var in missing_tool_vars: for var in missing_tools:
tools = var.get("tools", []) tools = var.get("tools", [])
tools_str = f"{', '.join(tools[:2])}" if tools else "" tools_str = f"{', '.join(tools[:2])}" if tools else ""
checklist_labels.append(f"{var['name']}{tools_str}") checklist_labels.append(f"{var.get('description', var['name'])}{tools_str}")
selected_indices = prompt_checklist( selected_indices = prompt_checklist(
"Which missing tools would you like to configure?", "Which tools would you like to configure?",
checklist_labels, checklist_labels,
) )
# Prompt for keys only for selected tools
for idx in selected_indices: for idx in selected_indices:
var = missing_tool_vars[idx] var = missing_tools[idx]
print() print()
print(color(f" {var['name']}", Colors.CYAN)) 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']}")
if var.get("password"): if var.get("password"):
value = prompt(f" {var.get('prompt', var['name'])}", password=True) value = prompt(f" {var.get('prompt', var['name'])}", password=True)
else: else:
value = prompt(f" {var.get('prompt', var['name'])}") value = prompt(f" {var.get('prompt', var['name'])}")
if value: if value:
save_env_value(var["name"], value) save_env_value(var["name"], value)
print_success(f" Saved {var['name']}") print_success(f" Saved {var['name']}")
# ── Messaging platforms (ask per-platform, not a flat list) ──
if missing_messaging:
print()
print_header("Messaging Platforms")
print_info("Connect Hermes to messaging apps to chat from anywhere.")
print_info("You can configure these later with 'hermes setup'.")
# Group by platform
platforms = {}
for var in missing_messaging:
name = var["name"]
if "TELEGRAM" in name:
platforms.setdefault("Telegram", []).append(var)
elif "DISCORD" in name:
platforms.setdefault("Discord", []).append(var)
elif "SLACK" in name:
platforms.setdefault("Slack", []).append(var)
for platform_name, vars_list in platforms.items():
print()
if prompt_yes_no(f" Set up {platform_name}?", False):
for var in vars_list:
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']}")
# Handle missing config fields # Handle missing config fields
if missing_config: if missing_config: