mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
Update terminal configuration and enhance CLI model management
- Changed default Docker, Singularity, and Modal images in configuration files to use "nikolaik/python-nodejs:python3.11-nodejs20" for improved compatibility. - Updated the default model in the configuration to "anthropic/claude-sonnet-4.5" and adjusted related setup prompts for API provider configuration. - Introduced a new CLI option for selecting a custom OpenAI-compatible endpoint, enhancing flexibility in model provider setup. - Enhanced the prompt choice functionality to support arrow key navigation for better user experience in CLI interactions. - Updated documentation in relevant files to reflect these changes and improve user guidance.
This commit is contained in:
@@ -55,7 +55,7 @@ terminal:
|
|||||||
# cwd: "/workspace"
|
# cwd: "/workspace"
|
||||||
# timeout: 180
|
# timeout: 180
|
||||||
# lifetime_seconds: 300
|
# lifetime_seconds: 300
|
||||||
# docker_image: "python:3.11"
|
# docker_image: "nikolaik/python-nodejs:python3.11-nodejs20"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# OPTION 4: Singularity/Apptainer container
|
# OPTION 4: Singularity/Apptainer container
|
||||||
@@ -67,7 +67,7 @@ terminal:
|
|||||||
# cwd: "/workspace"
|
# cwd: "/workspace"
|
||||||
# timeout: 180
|
# timeout: 180
|
||||||
# lifetime_seconds: 300
|
# lifetime_seconds: 300
|
||||||
# singularity_image: "docker://python:3.11"
|
# singularity_image: "docker://nikolaik/python-nodejs:python3.11-nodejs20"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# OPTION 5: Modal cloud execution
|
# OPTION 5: Modal cloud execution
|
||||||
@@ -79,7 +79,7 @@ terminal:
|
|||||||
# cwd: "/workspace"
|
# cwd: "/workspace"
|
||||||
# timeout: 180
|
# timeout: 180
|
||||||
# lifetime_seconds: 300
|
# lifetime_seconds: 300
|
||||||
# modal_image: "python:3.11"
|
# modal_image: "nikolaik/python-nodejs:python3.11-nodejs20"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# SUDO SUPPORT (works with ALL backends above)
|
# SUDO SUPPORT (works with ALL backends above)
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ def ensure_hermes_home():
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|
||||||
DEFAULT_CONFIG = {
|
DEFAULT_CONFIG = {
|
||||||
"model": "anthropic/claude-sonnet-4",
|
"model": "anthropic/claude-sonnet-4.5",
|
||||||
"toolsets": ["hermes-cli"],
|
"toolsets": ["hermes-cli"],
|
||||||
"max_turns": 100,
|
"max_turns": 100,
|
||||||
|
|
||||||
@@ -79,7 +79,9 @@ DEFAULT_CONFIG = {
|
|||||||
"backend": "local",
|
"backend": "local",
|
||||||
"cwd": ".", # Use current directory
|
"cwd": ".", # Use current directory
|
||||||
"timeout": 180,
|
"timeout": 180,
|
||||||
"docker_image": "python:3.11-slim",
|
"docker_image": "nikolaik/python-nodejs:python3.11-nodejs20",
|
||||||
|
"singularity_image": "docker://nikolaik/python-nodejs:python3.11-nodejs20",
|
||||||
|
"modal_image": "nikolaik/python-nodejs:python3.11-nodejs20",
|
||||||
},
|
},
|
||||||
|
|
||||||
"browser": {
|
"browser": {
|
||||||
@@ -248,6 +250,12 @@ def show_config():
|
|||||||
|
|
||||||
if terminal.get('backend') == 'docker':
|
if terminal.get('backend') == 'docker':
|
||||||
print(f" Docker image: {terminal.get('docker_image', 'python:3.11-slim')}")
|
print(f" Docker image: {terminal.get('docker_image', 'python:3.11-slim')}")
|
||||||
|
elif terminal.get('backend') == 'singularity':
|
||||||
|
print(f" Image: {terminal.get('singularity_image', 'docker://python:3.11')}")
|
||||||
|
elif terminal.get('backend') == 'modal':
|
||||||
|
print(f" Modal image: {terminal.get('modal_image', 'python:3.11')}")
|
||||||
|
modal_token = get_env_value('MODAL_TOKEN_ID')
|
||||||
|
print(f" Modal token: {'configured' if modal_token else '(not set)'}")
|
||||||
elif terminal.get('backend') == 'ssh':
|
elif terminal.get('backend') == 'ssh':
|
||||||
ssh_host = get_env_value('TERMINAL_SSH_HOST')
|
ssh_host = get_env_value('TERMINAL_SSH_HOST')
|
||||||
ssh_user = get_env_value('TERMINAL_SSH_USER')
|
ssh_user = get_env_value('TERMINAL_SSH_USER')
|
||||||
|
|||||||
@@ -85,30 +85,56 @@ def prompt(question: str, default: str = None, password: bool = False) -> str:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
def prompt_choice(question: str, choices: list, default: int = 0) -> int:
|
def prompt_choice(question: str, choices: list, default: int = 0) -> int:
|
||||||
"""Prompt for a choice from a list."""
|
"""Prompt for a choice from a list with arrow key navigation."""
|
||||||
print(color(question, Colors.YELLOW))
|
print(color(question, Colors.YELLOW))
|
||||||
|
|
||||||
for i, choice in enumerate(choices):
|
# Try to use interactive menu if available
|
||||||
marker = "●" if i == default else "○"
|
try:
|
||||||
if i == default:
|
from simple_term_menu import TerminalMenu
|
||||||
print(color(f" {marker} {choice}", Colors.GREEN))
|
|
||||||
else:
|
# Add visual indicators
|
||||||
print(f" {marker} {choice}")
|
menu_choices = [f" {choice}" for choice in choices]
|
||||||
|
|
||||||
while True:
|
terminal_menu = TerminalMenu(
|
||||||
try:
|
menu_choices,
|
||||||
value = input(color(f" Select [1-{len(choices)}] ({default + 1}): ", Colors.DIM))
|
cursor_index=default,
|
||||||
if not value:
|
menu_cursor="→ ",
|
||||||
return default
|
menu_cursor_style=("fg_green", "bold"),
|
||||||
idx = int(value) - 1
|
menu_highlight_style=("fg_green",),
|
||||||
if 0 <= idx < len(choices):
|
cycle_cursor=True,
|
||||||
return idx
|
clear_screen=False,
|
||||||
print_error(f"Please enter a number between 1 and {len(choices)}")
|
)
|
||||||
except ValueError:
|
|
||||||
print_error("Please enter a number")
|
idx = terminal_menu.show()
|
||||||
except (KeyboardInterrupt, EOFError):
|
if idx is None: # User pressed Escape or Ctrl+C
|
||||||
print()
|
print()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
print() # Add newline after selection
|
||||||
|
return idx
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
# Fallback to number-based selection
|
||||||
|
for i, choice in enumerate(choices):
|
||||||
|
marker = "●" if i == default else "○"
|
||||||
|
if i == default:
|
||||||
|
print(color(f" {marker} {choice}", Colors.GREEN))
|
||||||
|
else:
|
||||||
|
print(f" {marker} {choice}")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
value = input(color(f" Select [1-{len(choices)}] ({default + 1}): ", Colors.DIM))
|
||||||
|
if not value:
|
||||||
|
return default
|
||||||
|
idx = int(value) - 1
|
||||||
|
if 0 <= idx < len(choices):
|
||||||
|
return idx
|
||||||
|
print_error(f"Please enter a number between 1 and {len(choices)}")
|
||||||
|
except ValueError:
|
||||||
|
print_error("Please enter a number")
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
print()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
def prompt_yes_no(question: str, default: bool = True) -> bool:
|
def prompt_yes_no(question: str, default: bool = True) -> bool:
|
||||||
"""Prompt for yes/no."""
|
"""Prompt for yes/no."""
|
||||||
@@ -159,25 +185,27 @@ def run_setup_wizard(args):
|
|||||||
|
|
||||||
# Check if already configured
|
# Check if already configured
|
||||||
existing_or = get_env_value("OPENROUTER_API_KEY")
|
existing_or = get_env_value("OPENROUTER_API_KEY")
|
||||||
existing_ant = get_env_value("ANTHROPIC_API_KEY")
|
existing_custom = get_env_value("OPENAI_BASE_URL")
|
||||||
|
|
||||||
if existing_or or existing_ant:
|
skip_provider_setup = False
|
||||||
configured = "OpenRouter" if existing_or else "Anthropic"
|
if existing_or or existing_custom:
|
||||||
print_info(f"Currently configured: {configured}")
|
if existing_or:
|
||||||
|
print_info("Currently configured: OpenRouter")
|
||||||
|
else:
|
||||||
|
print_info(f"Currently configured: Custom endpoint ({existing_custom})")
|
||||||
|
|
||||||
if not prompt_yes_no("Reconfigure API provider?", False):
|
if not prompt_yes_no("Reconfigure API provider?", False):
|
||||||
print_info("Keeping existing configuration")
|
print_info("Keeping existing configuration")
|
||||||
else:
|
skip_provider_setup = True
|
||||||
existing_or = None # Force reconfigure
|
|
||||||
|
|
||||||
if not existing_or and not existing_ant:
|
if not skip_provider_setup:
|
||||||
provider_choices = [
|
provider_choices = [
|
||||||
"OpenRouter (recommended - access to all models)",
|
"OpenRouter (recommended - access to all models)",
|
||||||
"Anthropic API (direct Claude access)",
|
"Custom OpenAI-compatible endpoint",
|
||||||
"OpenAI API",
|
|
||||||
"Skip for now"
|
"Skip for now"
|
||||||
]
|
]
|
||||||
|
|
||||||
provider_idx = prompt_choice("Select your primary model provider:", provider_choices, 0)
|
provider_idx = prompt_choice("Select your API provider:", provider_choices, 0)
|
||||||
|
|
||||||
if provider_idx == 0: # OpenRouter
|
if provider_idx == 0: # OpenRouter
|
||||||
print_info("Get your API key at: https://openrouter.ai/keys")
|
print_info("Get your API key at: https://openrouter.ai/keys")
|
||||||
@@ -186,19 +214,31 @@ def run_setup_wizard(args):
|
|||||||
save_env_value("OPENROUTER_API_KEY", api_key)
|
save_env_value("OPENROUTER_API_KEY", api_key)
|
||||||
print_success("OpenRouter API key saved")
|
print_success("OpenRouter API key saved")
|
||||||
|
|
||||||
elif provider_idx == 1: # Anthropic
|
elif provider_idx == 1: # Custom endpoint
|
||||||
print_info("Get your API key at: https://console.anthropic.com/")
|
print_info("Custom OpenAI-Compatible Endpoint Configuration:")
|
||||||
api_key = prompt("Anthropic API key", password=True)
|
print_info("Works with any API that follows OpenAI's chat completions spec")
|
||||||
if api_key:
|
|
||||||
save_env_value("ANTHROPIC_API_KEY", api_key)
|
# Show current values if set
|
||||||
print_success("Anthropic API key saved")
|
current_url = get_env_value("OPENAI_BASE_URL") or ""
|
||||||
|
current_key = get_env_value("OPENAI_API_KEY")
|
||||||
elif provider_idx == 2: # OpenAI
|
current_model = config.get('model', '')
|
||||||
print_info("Get your API key at: https://platform.openai.com/api-keys")
|
|
||||||
api_key = prompt("OpenAI API key", password=True)
|
if current_url:
|
||||||
|
print_info(f" Current URL: {current_url}")
|
||||||
|
if current_key:
|
||||||
|
print_info(f" Current key: {current_key[:8]}... (configured)")
|
||||||
|
|
||||||
|
base_url = prompt(" API base URL (e.g., https://api.example.com/v1)", current_url)
|
||||||
|
api_key = prompt(" API key", password=True)
|
||||||
|
model_name = prompt(" Model name (e.g., gpt-4, claude-3-opus)", current_model)
|
||||||
|
|
||||||
|
if base_url:
|
||||||
|
save_env_value("OPENAI_BASE_URL", base_url)
|
||||||
if api_key:
|
if api_key:
|
||||||
save_env_value("OPENAI_API_KEY", api_key)
|
save_env_value("OPENAI_API_KEY", api_key)
|
||||||
print_success("OpenAI API key saved")
|
if model_name:
|
||||||
|
config['model'] = model_name
|
||||||
|
print_success("Custom endpoint configured")
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Step 2: Model Selection
|
# Step 2: Model Selection
|
||||||
@@ -209,28 +249,40 @@ def run_setup_wizard(args):
|
|||||||
print_info(f"Current: {current_model}")
|
print_info(f"Current: {current_model}")
|
||||||
|
|
||||||
model_choices = [
|
model_choices = [
|
||||||
"anthropic/claude-sonnet-4 (recommended)",
|
"anthropic/claude-sonnet-4.5 (recommended)",
|
||||||
"anthropic/claude-opus-4",
|
"anthropic/claude-opus-4.5",
|
||||||
"openai/gpt-4o",
|
"openai/gpt-5.2",
|
||||||
"google/gemini-2.0-flash",
|
"openai/gpt-5.2-codex",
|
||||||
"Enter custom model",
|
"google/gemini-3-pro-preview",
|
||||||
"Keep current"
|
"google/gemini-3-flash-preview",
|
||||||
|
"z-ai/glm-4.7",
|
||||||
|
"moonshotai/kimi-k2.5",
|
||||||
|
"minimax/minimax-m2.1",
|
||||||
|
"Custom model",
|
||||||
|
f"Keep current ({current_model})"
|
||||||
]
|
]
|
||||||
|
|
||||||
model_idx = prompt_choice("Select default model:", model_choices, 5) # Default: keep current
|
model_idx = prompt_choice("Select default model:", model_choices, 10) # Default: keep current
|
||||||
|
|
||||||
if model_idx == 0:
|
model_map = {
|
||||||
config['model'] = "anthropic/claude-sonnet-4"
|
0: "anthropic/claude-sonnet-4.5",
|
||||||
elif model_idx == 1:
|
1: "anthropic/claude-opus-4.5",
|
||||||
config['model'] = "anthropic/claude-opus-4"
|
2: "openai/gpt-5.2",
|
||||||
elif model_idx == 2:
|
3: "openai/gpt-5.2-codex",
|
||||||
config['model'] = "openai/gpt-4o"
|
4: "google/gemini-3-pro-preview",
|
||||||
elif model_idx == 3:
|
5: "google/gemini-3-flash-preview",
|
||||||
config['model'] = "google/gemini-2.0-flash"
|
6: "z-ai/glm-4.7",
|
||||||
elif model_idx == 4:
|
7: "moonshotai/kimi-k2.5",
|
||||||
custom = prompt("Enter model name (e.g., anthropic/claude-sonnet-4)")
|
8: "minimax/minimax-m2.1",
|
||||||
|
}
|
||||||
|
|
||||||
|
if model_idx in model_map:
|
||||||
|
config['model'] = model_map[model_idx]
|
||||||
|
elif model_idx == 9: # Custom
|
||||||
|
custom = prompt("Enter model name (e.g., anthropic/claude-sonnet-4.5)")
|
||||||
if custom:
|
if custom:
|
||||||
config['model'] = custom
|
config['model'] = custom
|
||||||
|
# else: Keep current (model_idx == 10)
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Step 3: Terminal Backend
|
# Step 3: Terminal Backend
|
||||||
@@ -244,46 +296,96 @@ def run_setup_wizard(args):
|
|||||||
terminal_choices = [
|
terminal_choices = [
|
||||||
"Local (run commands on this machine - no isolation)",
|
"Local (run commands on this machine - no isolation)",
|
||||||
"Docker (isolated containers - recommended for security)",
|
"Docker (isolated containers - recommended for security)",
|
||||||
|
"Singularity/Apptainer (HPC clusters, shared compute)",
|
||||||
|
"Modal (cloud execution, GPU access, serverless)",
|
||||||
"SSH (run commands on a remote server)",
|
"SSH (run commands on a remote server)",
|
||||||
"Keep current"
|
f"Keep current ({current_backend})"
|
||||||
]
|
]
|
||||||
|
|
||||||
# Default based on current
|
# Default based on current
|
||||||
default_terminal = {'local': 0, 'docker': 1, 'ssh': 2}.get(current_backend, 0)
|
default_terminal = {'local': 0, 'docker': 1, 'singularity': 2, 'modal': 3, 'ssh': 4}.get(current_backend, 0)
|
||||||
|
|
||||||
terminal_idx = prompt_choice("Select terminal backend:", terminal_choices, 3) # Default: keep
|
terminal_idx = prompt_choice("Select terminal backend:", terminal_choices, 5) # Default: keep
|
||||||
|
|
||||||
if terminal_idx == 0: # Local
|
if terminal_idx == 0: # Local
|
||||||
config.setdefault('terminal', {})['backend'] = 'local'
|
config.setdefault('terminal', {})['backend'] = 'local'
|
||||||
print_success("Terminal set to local")
|
print_info("Local Execution Configuration:")
|
||||||
|
print_info("Commands run directly on this machine (no isolation)")
|
||||||
|
|
||||||
if prompt_yes_no("Enable sudo support? (allows agent to run sudo commands)", False):
|
if prompt_yes_no(" Enable sudo support? (allows agent to run sudo commands)", False):
|
||||||
print_warning("SECURITY WARNING: Sudo password will be stored in plaintext")
|
print_warning(" SECURITY WARNING: Sudo password will be stored in plaintext")
|
||||||
sudo_pass = prompt("Sudo password (leave empty to skip)", password=True)
|
sudo_pass = prompt(" Sudo password (leave empty to skip)", password=True)
|
||||||
if sudo_pass:
|
if sudo_pass:
|
||||||
save_env_value("SUDO_PASSWORD", sudo_pass)
|
save_env_value("SUDO_PASSWORD", sudo_pass)
|
||||||
print_success("Sudo password saved")
|
print_success(" Sudo password saved")
|
||||||
|
|
||||||
|
print_success("Terminal set to local")
|
||||||
|
|
||||||
elif terminal_idx == 1: # Docker
|
elif terminal_idx == 1: # Docker
|
||||||
config.setdefault('terminal', {})['backend'] = 'docker'
|
config.setdefault('terminal', {})['backend'] = 'docker'
|
||||||
docker_image = prompt("Docker image", config.get('terminal', {}).get('docker_image', 'python:3.11-slim'))
|
default_docker = config.get('terminal', {}).get('docker_image', 'nikolaik/python-nodejs:python3.11-nodejs20')
|
||||||
|
print_info("Docker Configuration:")
|
||||||
|
docker_image = prompt(" Docker image", default_docker)
|
||||||
config['terminal']['docker_image'] = docker_image
|
config['terminal']['docker_image'] = docker_image
|
||||||
print_success("Terminal set to Docker")
|
print_success("Terminal set to Docker")
|
||||||
|
|
||||||
elif terminal_idx == 2: # SSH
|
elif terminal_idx == 2: # Singularity
|
||||||
|
config.setdefault('terminal', {})['backend'] = 'singularity'
|
||||||
|
default_singularity = config.get('terminal', {}).get('singularity_image', 'docker://nikolaik/python-nodejs:python3.11-nodejs20')
|
||||||
|
print_info("Singularity/Apptainer Configuration:")
|
||||||
|
print_info("Requires apptainer or singularity to be installed")
|
||||||
|
singularity_image = prompt(" Image (docker:// prefix for Docker Hub)", default_singularity)
|
||||||
|
config['terminal']['singularity_image'] = singularity_image
|
||||||
|
print_success("Terminal set to Singularity/Apptainer")
|
||||||
|
|
||||||
|
elif terminal_idx == 3: # Modal
|
||||||
|
config.setdefault('terminal', {})['backend'] = 'modal'
|
||||||
|
default_modal = config.get('terminal', {}).get('modal_image', 'nikolaik/python-nodejs:python3.11-nodejs20')
|
||||||
|
print_info("Modal Cloud Configuration:")
|
||||||
|
print_info("Get credentials at: https://modal.com/settings")
|
||||||
|
|
||||||
|
# Always show current status and allow reconfiguration
|
||||||
|
current_token = get_env_value('MODAL_TOKEN_ID')
|
||||||
|
if current_token:
|
||||||
|
print_info(f" Token ID: {current_token[:8]}... (configured)")
|
||||||
|
|
||||||
|
modal_image = prompt(" Container image", default_modal)
|
||||||
|
config['terminal']['modal_image'] = modal_image
|
||||||
|
|
||||||
|
token_id = prompt(" Modal token ID", current_token or "")
|
||||||
|
token_secret = prompt(" Modal token secret", password=True)
|
||||||
|
|
||||||
|
if token_id:
|
||||||
|
save_env_value("MODAL_TOKEN_ID", token_id)
|
||||||
|
if token_secret:
|
||||||
|
save_env_value("MODAL_TOKEN_SECRET", token_secret)
|
||||||
|
|
||||||
|
print_success("Terminal set to Modal")
|
||||||
|
|
||||||
|
elif terminal_idx == 4: # SSH
|
||||||
config.setdefault('terminal', {})['backend'] = 'ssh'
|
config.setdefault('terminal', {})['backend'] = 'ssh'
|
||||||
|
print_info("SSH Remote Execution Configuration:")
|
||||||
|
print_info("Commands will run on a remote server over SSH")
|
||||||
|
|
||||||
current_host = get_env_value('TERMINAL_SSH_HOST') or ''
|
current_host = get_env_value('TERMINAL_SSH_HOST') or ''
|
||||||
current_user = get_env_value('TERMINAL_SSH_USER') or os.getenv("USER", "")
|
current_user = get_env_value('TERMINAL_SSH_USER') or os.getenv("USER", "")
|
||||||
|
current_port = get_env_value('TERMINAL_SSH_PORT') or '22'
|
||||||
|
current_key = get_env_value('TERMINAL_SSH_KEY') or '~/.ssh/id_rsa'
|
||||||
|
|
||||||
ssh_host = prompt("SSH host", current_host)
|
if current_host:
|
||||||
ssh_user = prompt("SSH user", current_user)
|
print_info(f" Current host: {current_user}@{current_host}:{current_port}")
|
||||||
ssh_key = prompt("SSH key path", "~/.ssh/id_rsa")
|
|
||||||
|
ssh_host = prompt(" SSH host", current_host)
|
||||||
|
ssh_user = prompt(" SSH user", current_user)
|
||||||
|
ssh_port = prompt(" SSH port", current_port)
|
||||||
|
ssh_key = prompt(" SSH key path (or leave empty for ssh-agent)", current_key)
|
||||||
|
|
||||||
if ssh_host:
|
if ssh_host:
|
||||||
save_env_value("TERMINAL_SSH_HOST", ssh_host)
|
save_env_value("TERMINAL_SSH_HOST", ssh_host)
|
||||||
if ssh_user:
|
if ssh_user:
|
||||||
save_env_value("TERMINAL_SSH_USER", ssh_user)
|
save_env_value("TERMINAL_SSH_USER", ssh_user)
|
||||||
|
if ssh_port and ssh_port != '22':
|
||||||
|
save_env_value("TERMINAL_SSH_PORT", ssh_port)
|
||||||
if ssh_key:
|
if ssh_key:
|
||||||
save_env_value("TERMINAL_SSH_KEY", ssh_key)
|
save_env_value("TERMINAL_SSH_KEY", ssh_key)
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ modal = ["modal", "boto3"]
|
|||||||
dev = ["pytest", "pytest-asyncio"]
|
dev = ["pytest", "pytest-asyncio"]
|
||||||
messaging = ["python-telegram-bot>=20.0", "discord.py>=2.0"]
|
messaging = ["python-telegram-bot>=20.0", "discord.py>=2.0"]
|
||||||
cron = ["croniter"]
|
cron = ["croniter"]
|
||||||
all = ["croniter", "python-telegram-bot>=20.0", "discord.py>=2.0"]
|
cli = ["simple-term-menu"]
|
||||||
|
all = ["croniter", "python-telegram-bot>=20.0", "discord.py>=2.0", "simple-term-menu"]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
hermes = "hermes_cli.main:main"
|
hermes = "hermes_cli.main:main"
|
||||||
|
|||||||
@@ -804,11 +804,13 @@ _cleanup_running = False
|
|||||||
# Configuration from environment variables
|
# Configuration from environment variables
|
||||||
def _get_env_config() -> Dict[str, Any]:
|
def _get_env_config() -> Dict[str, Any]:
|
||||||
"""Get terminal environment configuration from environment variables."""
|
"""Get terminal environment configuration from environment variables."""
|
||||||
|
# Default image with Python and Node.js for maximum compatibility
|
||||||
|
default_image = "nikolaik/python-nodejs:python3.11-nodejs20"
|
||||||
return {
|
return {
|
||||||
"env_type": os.getenv("TERMINAL_ENV", "local"), # local, docker, singularity, modal, or ssh
|
"env_type": os.getenv("TERMINAL_ENV", "local"), # local, docker, singularity, modal, or ssh
|
||||||
"docker_image": os.getenv("TERMINAL_DOCKER_IMAGE", "python:3.11"),
|
"docker_image": os.getenv("TERMINAL_DOCKER_IMAGE", default_image),
|
||||||
"singularity_image": os.getenv("TERMINAL_SINGULARITY_IMAGE", "docker://python:3.11"),
|
"singularity_image": os.getenv("TERMINAL_SINGULARITY_IMAGE", f"docker://{default_image}"),
|
||||||
"modal_image": os.getenv("TERMINAL_MODAL_IMAGE", "python:3.11"),
|
"modal_image": os.getenv("TERMINAL_MODAL_IMAGE", default_image),
|
||||||
"cwd": os.getenv("TERMINAL_CWD", "/tmp"),
|
"cwd": os.getenv("TERMINAL_CWD", "/tmp"),
|
||||||
"timeout": int(os.getenv("TERMINAL_TIMEOUT", "60")),
|
"timeout": int(os.getenv("TERMINAL_TIMEOUT", "60")),
|
||||||
"lifetime_seconds": int(os.getenv("TERMINAL_LIFETIME_SECONDS", "300")),
|
"lifetime_seconds": int(os.getenv("TERMINAL_LIFETIME_SECONDS", "300")),
|
||||||
@@ -1290,9 +1292,11 @@ if __name__ == "__main__":
|
|||||||
print(" result = terminal_tool(command='python server.py', background=True)")
|
print(" result = terminal_tool(command='python server.py', background=True)")
|
||||||
|
|
||||||
print("\nEnvironment Variables:")
|
print("\nEnvironment Variables:")
|
||||||
print(f" TERMINAL_ENV: {os.getenv('TERMINAL_ENV', 'local')} (local/docker/modal)")
|
default_img = "nikolaik/python-nodejs:python3.11-nodejs20"
|
||||||
print(f" TERMINAL_DOCKER_IMAGE: {os.getenv('TERMINAL_DOCKER_IMAGE', 'python:3.11-slim')}")
|
print(f" TERMINAL_ENV: {os.getenv('TERMINAL_ENV', 'local')} (local/docker/singularity/modal/ssh)")
|
||||||
print(f" TERMINAL_MODAL_IMAGE: {os.getenv('TERMINAL_MODAL_IMAGE', 'python:3.11-slim')}")
|
print(f" TERMINAL_DOCKER_IMAGE: {os.getenv('TERMINAL_DOCKER_IMAGE', default_img)}")
|
||||||
|
print(f" TERMINAL_SINGULARITY_IMAGE: {os.getenv('TERMINAL_SINGULARITY_IMAGE', f'docker://{default_img}')}")
|
||||||
|
print(f" TERMINAL_MODAL_IMAGE: {os.getenv('TERMINAL_MODAL_IMAGE', default_img)}")
|
||||||
print(f" TERMINAL_CWD: {os.getenv('TERMINAL_CWD', '/tmp')}")
|
print(f" TERMINAL_CWD: {os.getenv('TERMINAL_CWD', '/tmp')}")
|
||||||
print(f" TERMINAL_TIMEOUT: {os.getenv('TERMINAL_TIMEOUT', '60')}")
|
print(f" TERMINAL_TIMEOUT: {os.getenv('TERMINAL_TIMEOUT', '60')}")
|
||||||
print(f" TERMINAL_LIFETIME_SECONDS: {os.getenv('TERMINAL_LIFETIME_SECONDS', '300')}")
|
print(f" TERMINAL_LIFETIME_SECONDS: {os.getenv('TERMINAL_LIFETIME_SECONDS', '300')}")
|
||||||
|
|||||||
Reference in New Issue
Block a user