Enhance messaging gateway configuration and security features
- Added new environment variables for Telegram and Discord bot configurations, including `TELEGRAM_ALLOWED_USERS` and `DISCORD_ALLOWED_USERS`, to restrict bot access to specific users. - Updated documentation in AGENTS.md and README.md to include detailed setup instructions for the messaging gateway, emphasizing the importance of user allowlists for security. - Improved the CLI setup wizard to prompt for allowed user IDs during configuration, enhancing user guidance and security awareness. - Refined the gateway run script to support user authorization checks, ensuring only allowed users can interact with the bot.
This commit is contained in:
parent
3e634aa7e4
commit
17a5efb416
9 changed files with 397 additions and 38 deletions
|
|
@ -40,6 +40,7 @@ FAL_KEY=
|
||||||
# - modal: Runs in Modal cloud sandboxes (scalable, requires Modal account)
|
# - modal: Runs in Modal cloud sandboxes (scalable, requires Modal account)
|
||||||
TERMINAL_ENV=local
|
TERMINAL_ENV=local
|
||||||
|
|
||||||
|
|
||||||
# Container images (for singularity/docker/modal backends)
|
# Container images (for singularity/docker/modal backends)
|
||||||
TERMINAL_DOCKER_IMAGE=python:3.11
|
TERMINAL_DOCKER_IMAGE=python:3.11
|
||||||
TERMINAL_SINGULARITY_IMAGE=docker://python:3.11
|
TERMINAL_SINGULARITY_IMAGE=docker://python:3.11
|
||||||
|
|
|
||||||
37
AGENTS.md
37
AGENTS.md
|
|
@ -180,6 +180,43 @@ The unified `hermes` command provides all functionality:
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Messaging Gateway
|
||||||
|
|
||||||
|
The gateway connects Hermes to Telegram, Discord, and WhatsApp.
|
||||||
|
|
||||||
|
### Configuration (in `~/.hermes/.env`):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Telegram
|
||||||
|
TELEGRAM_BOT_TOKEN=123456:ABC-DEF... # From @BotFather
|
||||||
|
TELEGRAM_ALLOWED_USERS=123456789,987654 # Comma-separated user IDs (from @userinfobot)
|
||||||
|
|
||||||
|
# Discord
|
||||||
|
DISCORD_BOT_TOKEN=MTIz... # From Developer Portal
|
||||||
|
DISCORD_ALLOWED_USERS=123456789012345678 # Comma-separated user IDs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security (User Allowlists):
|
||||||
|
|
||||||
|
**IMPORTANT**: Without an allowlist, anyone who finds your bot can use it!
|
||||||
|
|
||||||
|
The gateway checks `{PLATFORM}_ALLOWED_USERS` environment variables:
|
||||||
|
- If set: Only listed user IDs can interact with the bot
|
||||||
|
- If unset: All users are allowed (dangerous with terminal access!)
|
||||||
|
|
||||||
|
Users can find their IDs:
|
||||||
|
- **Telegram**: Message [@userinfobot](https://t.me/userinfobot)
|
||||||
|
- **Discord**: Enable Developer Mode, right-click name → Copy ID
|
||||||
|
|
||||||
|
### Platform Toolsets:
|
||||||
|
|
||||||
|
Each platform has a dedicated toolset in `toolsets.py`:
|
||||||
|
- `hermes-telegram`: Full tools including terminal (with safety checks)
|
||||||
|
- `hermes-discord`: Full tools including terminal
|
||||||
|
- `hermes-whatsapp`: Full tools including terminal
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Configuration System
|
## Configuration System
|
||||||
|
|
||||||
Configuration files are stored in `~/.hermes/` for easy user access:
|
Configuration files are stored in `~/.hermes/` for easy user access:
|
||||||
|
|
|
||||||
62
README.md
62
README.md
|
|
@ -187,21 +187,61 @@ hermes config set terminal.backend modal
|
||||||
|
|
||||||
### 📱 Messaging Gateway
|
### 📱 Messaging Gateway
|
||||||
|
|
||||||
Chat with Hermes from Telegram, Discord, or WhatsApp:
|
Chat with Hermes from Telegram, Discord, or WhatsApp.
|
||||||
|
|
||||||
|
#### Telegram Setup
|
||||||
|
|
||||||
|
1. **Create a bot:** Message [@BotFather](https://t.me/BotFather) on Telegram, use `/newbot`
|
||||||
|
2. **Get your user ID:** Message [@userinfobot](https://t.me/userinfobot) - it replies with your numeric ID
|
||||||
|
3. **Configure:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Configure your bot token
|
# Add to ~/.hermes/.env:
|
||||||
hermes config set TELEGRAM_BOT_TOKEN "your_token"
|
TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
|
||||||
|
TELEGRAM_ALLOWED_USERS=YOUR_USER_ID # Comma-separated for multiple users
|
||||||
# Start the gateway
|
|
||||||
hermes gateway
|
|
||||||
|
|
||||||
# Or install as a service
|
|
||||||
hermes gateway install
|
|
||||||
hermes gateway start
|
|
||||||
```
|
```
|
||||||
|
|
||||||
See [docs/messaging.md](docs/messaging.md) for full setup.
|
4. **Start the gateway:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hermes gateway # Run in foreground
|
||||||
|
hermes gateway install # Install as systemd service (Linux)
|
||||||
|
hermes gateway start # Start the service
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Discord Setup
|
||||||
|
|
||||||
|
1. **Create a bot:** Go to [Discord Developer Portal](https://discord.com/developers/applications)
|
||||||
|
2. **Get your user ID:** Enable Developer Mode in Discord settings, right-click your name → Copy ID
|
||||||
|
3. **Configure:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Add to ~/.hermes/.env:
|
||||||
|
DISCORD_BOT_TOKEN=MTIz...
|
||||||
|
DISCORD_ALLOWED_USERS=YOUR_USER_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Security (Important!)
|
||||||
|
|
||||||
|
**Without an allowlist, anyone who finds your bot can use it!**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Restrict to specific users (recommended):
|
||||||
|
TELEGRAM_ALLOWED_USERS=123456789,987654321
|
||||||
|
DISCORD_ALLOWED_USERS=123456789012345678
|
||||||
|
|
||||||
|
# Or allow all users in a specific platform:
|
||||||
|
# (Leave the variable unset - NOT recommended for bots with terminal access)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Gateway Commands
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `/new` or `/reset` | Start fresh conversation |
|
||||||
|
| `/status` | Show session info |
|
||||||
|
|
||||||
|
See [docs/messaging.md](docs/messaging.md) for WhatsApp and advanced setup.
|
||||||
|
|
||||||
### ⏰ Scheduled Tasks (Cron)
|
### ⏰ Scheduled Tasks (Cron)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,12 @@ model:
|
||||||
# OPTION 1: Local execution (default)
|
# OPTION 1: Local execution (default)
|
||||||
# Commands run directly on your machine in the current directory
|
# Commands run directly on your machine in the current directory
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
# Working directory behavior:
|
||||||
|
# - CLI (`hermes` command): Uses "." (current directory where you run hermes)
|
||||||
|
# - Messaging (Telegram/Discord): Uses MESSAGING_CWD from .env (default: home)
|
||||||
terminal:
|
terminal:
|
||||||
env_type: "local"
|
env_type: "local"
|
||||||
cwd: "." # Use "." for current directory, or specify absolute path
|
cwd: "." # CLI working directory - "." means current directory
|
||||||
timeout: 180
|
timeout: 180
|
||||||
lifetime_seconds: 300
|
lifetime_seconds: 300
|
||||||
# sudo_password: "" # Enable sudo commands (pipes via sudo -S) - SECURITY WARNING: plaintext!
|
# sudo_password: "" # Enable sudo commands (pipes via sudo -S) - SECURITY WARNING: plaintext!
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ from typing import Dict, Optional, Any, List
|
||||||
# Add parent directory to path
|
# Add parent directory to path
|
||||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||||
|
|
||||||
# Load environment variables from ~/.hermes/.env
|
# Load environment variables from ~/.hermes/.env first
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
_env_path = Path.home() / '.hermes' / '.env'
|
_env_path = Path.home() / '.hermes' / '.env'
|
||||||
if _env_path.exists():
|
if _env_path.exists():
|
||||||
|
|
@ -32,6 +32,15 @@ if _env_path.exists():
|
||||||
# Also try project .env as fallback
|
# Also try project .env as fallback
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
|
# Gateway runs in quiet mode - suppress debug output and use cwd directly (no temp dirs)
|
||||||
|
os.environ["HERMES_QUIET"] = "1"
|
||||||
|
|
||||||
|
# Set terminal working directory for messaging platforms
|
||||||
|
# Uses MESSAGING_CWD if set, otherwise defaults to home directory
|
||||||
|
# This is separate from CLI which uses the directory where `hermes` is run
|
||||||
|
messaging_cwd = os.getenv("MESSAGING_CWD") or str(Path.home())
|
||||||
|
os.environ["TERMINAL_CWD"] = messaging_cwd
|
||||||
|
|
||||||
from gateway.config import (
|
from gateway.config import (
|
||||||
Platform,
|
Platform,
|
||||||
GatewayConfig,
|
GatewayConfig,
|
||||||
|
|
@ -163,19 +172,63 @@ class GatewayRunner:
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def _is_user_authorized(self, source: SessionSource) -> bool:
|
||||||
|
"""
|
||||||
|
Check if a user is authorized to use the bot.
|
||||||
|
|
||||||
|
Authorization is checked via environment variables:
|
||||||
|
- GATEWAY_ALLOWED_USERS: Comma-separated list of user IDs (all platforms)
|
||||||
|
- TELEGRAM_ALLOWED_USERS: Telegram-specific user IDs
|
||||||
|
- DISCORD_ALLOWED_USERS: Discord-specific user IDs
|
||||||
|
|
||||||
|
If no allowlist is configured, all users are allowed (open access).
|
||||||
|
"""
|
||||||
|
user_id = source.user_id
|
||||||
|
if not user_id:
|
||||||
|
return False # Can't verify unknown users
|
||||||
|
|
||||||
|
# Check platform-specific allowlist first
|
||||||
|
platform_env_map = {
|
||||||
|
Platform.TELEGRAM: "TELEGRAM_ALLOWED_USERS",
|
||||||
|
Platform.DISCORD: "DISCORD_ALLOWED_USERS",
|
||||||
|
Platform.WHATSAPP: "WHATSAPP_ALLOWED_USERS",
|
||||||
|
}
|
||||||
|
|
||||||
|
platform_allowlist = os.getenv(platform_env_map.get(source.platform, ""))
|
||||||
|
global_allowlist = os.getenv("GATEWAY_ALLOWED_USERS", "")
|
||||||
|
|
||||||
|
# If no allowlists configured, allow all (backward compatible)
|
||||||
|
if not platform_allowlist and not global_allowlist:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Check if user is in any allowlist
|
||||||
|
allowed_ids = set()
|
||||||
|
if platform_allowlist:
|
||||||
|
allowed_ids.update(uid.strip() for uid in platform_allowlist.split(","))
|
||||||
|
if global_allowlist:
|
||||||
|
allowed_ids.update(uid.strip() for uid in global_allowlist.split(","))
|
||||||
|
|
||||||
|
return user_id in allowed_ids
|
||||||
|
|
||||||
async def _handle_message(self, event: MessageEvent) -> Optional[str]:
|
async def _handle_message(self, event: MessageEvent) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Handle an incoming message from any platform.
|
Handle an incoming message from any platform.
|
||||||
|
|
||||||
This is the core message processing pipeline:
|
This is the core message processing pipeline:
|
||||||
1. Check for commands (/new, /reset, etc.)
|
1. Check user authorization
|
||||||
2. Get or create session
|
2. Check for commands (/new, /reset, etc.)
|
||||||
3. Build context for agent
|
3. Get or create session
|
||||||
4. Run agent conversation
|
4. Build context for agent
|
||||||
5. Return response
|
5. Run agent conversation
|
||||||
|
6. Return response
|
||||||
"""
|
"""
|
||||||
source = event.source
|
source = event.source
|
||||||
|
|
||||||
|
# Check if user is authorized
|
||||||
|
if not self._is_user_authorized(source):
|
||||||
|
print(f"[gateway] Unauthorized user: {source.user_id} ({source.user_name}) on {source.platform.value}")
|
||||||
|
return None # Silently ignore unauthorized users
|
||||||
|
|
||||||
# Check for reset commands
|
# Check for reset commands
|
||||||
command = event.get_command()
|
command = event.get_command()
|
||||||
if command in ["new", "reset"]:
|
if command in ["new", "reset"]:
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,44 @@ OPTIONAL_ENV_VARS = {
|
||||||
"url": None,
|
"url": None,
|
||||||
"password": True,
|
"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,
|
||||||
|
},
|
||||||
|
# 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,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ Handles: hermes gateway [run|start|stop|restart|status|install|uninstall]
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
|
import signal
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -13,6 +14,70 @@ from pathlib import Path
|
||||||
PROJECT_ROOT = Path(__file__).parent.parent.resolve()
|
PROJECT_ROOT = Path(__file__).parent.parent.resolve()
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Process Management (for manual gateway runs)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
def find_gateway_pids() -> list:
|
||||||
|
"""Find PIDs of running gateway processes."""
|
||||||
|
pids = []
|
||||||
|
try:
|
||||||
|
# Look for gateway processes with multiple patterns
|
||||||
|
patterns = [
|
||||||
|
"hermes_cli.main gateway",
|
||||||
|
"hermes gateway",
|
||||||
|
"gateway/run.py",
|
||||||
|
]
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
["ps", "aux"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True
|
||||||
|
)
|
||||||
|
|
||||||
|
for line in result.stdout.split('\n'):
|
||||||
|
# Skip grep and current process
|
||||||
|
if 'grep' in line or str(os.getpid()) in line:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for pattern in patterns:
|
||||||
|
if pattern in line:
|
||||||
|
parts = line.split()
|
||||||
|
if len(parts) > 1:
|
||||||
|
try:
|
||||||
|
pid = int(parts[1])
|
||||||
|
if pid not in pids:
|
||||||
|
pids.append(pid)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return pids
|
||||||
|
|
||||||
|
|
||||||
|
def kill_gateway_processes(force: bool = False) -> int:
|
||||||
|
"""Kill any running gateway processes. Returns count killed."""
|
||||||
|
pids = find_gateway_pids()
|
||||||
|
killed = 0
|
||||||
|
|
||||||
|
for pid in pids:
|
||||||
|
try:
|
||||||
|
if force:
|
||||||
|
os.kill(pid, signal.SIGKILL)
|
||||||
|
else:
|
||||||
|
os.kill(pid, signal.SIGTERM)
|
||||||
|
killed += 1
|
||||||
|
except ProcessLookupError:
|
||||||
|
# Process already gone
|
||||||
|
pass
|
||||||
|
except PermissionError:
|
||||||
|
print(f"⚠ Permission denied to kill PID {pid}")
|
||||||
|
|
||||||
|
return killed
|
||||||
|
|
||||||
|
|
||||||
def is_linux() -> bool:
|
def is_linux() -> bool:
|
||||||
return sys.platform.startswith('linux')
|
return sys.platform.startswith('linux')
|
||||||
|
|
||||||
|
|
@ -343,29 +408,80 @@ def gateway_command(args):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
elif subcmd == "stop":
|
elif subcmd == "stop":
|
||||||
if is_linux():
|
# Try service first, fall back to killing processes directly
|
||||||
systemd_stop()
|
service_available = False
|
||||||
elif is_macos():
|
|
||||||
launchd_stop()
|
if is_linux() and get_systemd_unit_path().exists():
|
||||||
else:
|
try:
|
||||||
print("Not supported on this platform.")
|
systemd_stop()
|
||||||
sys.exit(1)
|
service_available = True
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
pass # Fall through to process kill
|
||||||
|
elif is_macos() and get_launchd_plist_path().exists():
|
||||||
|
try:
|
||||||
|
launchd_stop()
|
||||||
|
service_available = True
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not service_available:
|
||||||
|
# Kill gateway processes directly
|
||||||
|
killed = kill_gateway_processes()
|
||||||
|
if killed:
|
||||||
|
print(f"✓ Stopped {killed} gateway process(es)")
|
||||||
|
else:
|
||||||
|
print("✗ No gateway processes found")
|
||||||
|
|
||||||
elif subcmd == "restart":
|
elif subcmd == "restart":
|
||||||
if is_linux():
|
# Try service first, fall back to killing and restarting
|
||||||
systemd_restart()
|
service_available = False
|
||||||
elif is_macos():
|
|
||||||
launchd_restart()
|
if is_linux() and get_systemd_unit_path().exists():
|
||||||
else:
|
try:
|
||||||
print("Not supported on this platform.")
|
systemd_restart()
|
||||||
sys.exit(1)
|
service_available = True
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
pass
|
||||||
|
elif is_macos() and get_launchd_plist_path().exists():
|
||||||
|
try:
|
||||||
|
launchd_restart()
|
||||||
|
service_available = True
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if not service_available:
|
||||||
|
# Manual restart: kill existing processes
|
||||||
|
killed = kill_gateway_processes()
|
||||||
|
if killed:
|
||||||
|
print(f"✓ Stopped {killed} gateway process(es)")
|
||||||
|
|
||||||
|
import time
|
||||||
|
time.sleep(2)
|
||||||
|
|
||||||
|
# Start fresh
|
||||||
|
print("Starting gateway...")
|
||||||
|
run_gateway(verbose=False)
|
||||||
|
|
||||||
elif subcmd == "status":
|
elif subcmd == "status":
|
||||||
deep = getattr(args, 'deep', False)
|
deep = getattr(args, 'deep', False)
|
||||||
if is_linux():
|
|
||||||
|
# Check for service first
|
||||||
|
if is_linux() and get_systemd_unit_path().exists():
|
||||||
systemd_status(deep)
|
systemd_status(deep)
|
||||||
elif is_macos():
|
elif is_macos() and get_launchd_plist_path().exists():
|
||||||
launchd_status(deep)
|
launchd_status(deep)
|
||||||
else:
|
else:
|
||||||
print("Not supported on this platform.")
|
# Check for manually running processes
|
||||||
sys.exit(1)
|
pids = find_gateway_pids()
|
||||||
|
if pids:
|
||||||
|
print(f"✓ Gateway is running (PID: {', '.join(map(str, pids))})")
|
||||||
|
print(" (Running manually, not as a system service)")
|
||||||
|
print()
|
||||||
|
print("To install as a service:")
|
||||||
|
print(" hermes gateway install")
|
||||||
|
else:
|
||||||
|
print("✗ Gateway is not running")
|
||||||
|
print()
|
||||||
|
print("To start:")
|
||||||
|
print(" hermes gateway # Run in foreground")
|
||||||
|
print(" hermes gateway install # Install as service")
|
||||||
|
|
|
||||||
|
|
@ -591,6 +591,23 @@ def run_setup_wizard(args):
|
||||||
if is_windows:
|
if is_windows:
|
||||||
print_info("Note: On Windows, commands run via cmd.exe or PowerShell")
|
print_info("Note: On Windows, commands run via cmd.exe or PowerShell")
|
||||||
|
|
||||||
|
# Messaging working directory configuration
|
||||||
|
print_info("")
|
||||||
|
print_info("Working Directory for Messaging (Telegram/Discord/etc):")
|
||||||
|
print_info(" The CLI always uses the directory you run 'hermes' from")
|
||||||
|
print_info(" But messaging bots need a static starting directory")
|
||||||
|
|
||||||
|
current_cwd = get_env_value('MESSAGING_CWD') or str(Path.home())
|
||||||
|
print_info(f" Current: {current_cwd}")
|
||||||
|
|
||||||
|
cwd_input = prompt(" Messaging working directory", current_cwd)
|
||||||
|
# Expand ~ to full path
|
||||||
|
if cwd_input.startswith('~'):
|
||||||
|
cwd_expanded = str(Path.home()) + cwd_input[1:]
|
||||||
|
else:
|
||||||
|
cwd_expanded = cwd_input
|
||||||
|
save_env_value("MESSAGING_CWD", cwd_expanded)
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -720,10 +737,36 @@ def run_setup_wizard(args):
|
||||||
save_env_value("TELEGRAM_BOT_TOKEN", token)
|
save_env_value("TELEGRAM_BOT_TOKEN", token)
|
||||||
print_success("Telegram token saved")
|
print_success("Telegram token saved")
|
||||||
|
|
||||||
|
# Allowed users (security)
|
||||||
|
print()
|
||||||
|
print_info("🔒 Security: Restrict who can use your bot")
|
||||||
|
print_info(" To find your Telegram user ID:")
|
||||||
|
print_info(" 1. Message @userinfobot on Telegram")
|
||||||
|
print_info(" 2. It will reply with your numeric ID (e.g., 123456789)")
|
||||||
|
print()
|
||||||
|
allowed_users = prompt("Allowed user IDs (comma-separated, leave empty for open access)")
|
||||||
|
if allowed_users:
|
||||||
|
save_env_value("TELEGRAM_ALLOWED_USERS", allowed_users.replace(" ", ""))
|
||||||
|
print_success("Telegram allowlist configured - only listed users can use the bot")
|
||||||
|
else:
|
||||||
|
print_info("⚠️ No allowlist set - anyone who finds your bot can use it!")
|
||||||
|
|
||||||
home_channel = prompt("Home channel ID (optional, for cron delivery)")
|
home_channel = prompt("Home channel ID (optional, for cron delivery)")
|
||||||
if home_channel:
|
if home_channel:
|
||||||
save_env_value("TELEGRAM_HOME_CHANNEL", home_channel)
|
save_env_value("TELEGRAM_HOME_CHANNEL", home_channel)
|
||||||
|
|
||||||
|
# Check/update existing Telegram allowlist
|
||||||
|
elif existing_telegram:
|
||||||
|
existing_allowlist = get_env_value('TELEGRAM_ALLOWED_USERS')
|
||||||
|
if not existing_allowlist:
|
||||||
|
print_info("⚠️ Telegram has no user allowlist - anyone can use your bot!")
|
||||||
|
if prompt_yes_no("Add allowed users now?", True):
|
||||||
|
print_info(" To find your Telegram user ID: message @userinfobot")
|
||||||
|
allowed_users = prompt("Allowed user IDs (comma-separated)")
|
||||||
|
if allowed_users:
|
||||||
|
save_env_value("TELEGRAM_ALLOWED_USERS", allowed_users.replace(" ", ""))
|
||||||
|
print_success("Telegram allowlist configured")
|
||||||
|
|
||||||
# Discord
|
# Discord
|
||||||
existing_discord = get_env_value('DISCORD_BOT_TOKEN')
|
existing_discord = get_env_value('DISCORD_BOT_TOKEN')
|
||||||
if existing_discord:
|
if existing_discord:
|
||||||
|
|
@ -738,10 +781,36 @@ def run_setup_wizard(args):
|
||||||
save_env_value("DISCORD_BOT_TOKEN", token)
|
save_env_value("DISCORD_BOT_TOKEN", token)
|
||||||
print_success("Discord token saved")
|
print_success("Discord token saved")
|
||||||
|
|
||||||
|
# Allowed users (security)
|
||||||
|
print()
|
||||||
|
print_info("🔒 Security: Restrict who can use your bot")
|
||||||
|
print_info(" To find your Discord user ID:")
|
||||||
|
print_info(" 1. Enable Developer Mode in Discord settings")
|
||||||
|
print_info(" 2. Right-click your name → Copy ID")
|
||||||
|
print()
|
||||||
|
allowed_users = prompt("Allowed user IDs (comma-separated, leave empty for open access)")
|
||||||
|
if allowed_users:
|
||||||
|
save_env_value("DISCORD_ALLOWED_USERS", allowed_users.replace(" ", ""))
|
||||||
|
print_success("Discord allowlist configured")
|
||||||
|
else:
|
||||||
|
print_info("⚠️ No allowlist set - anyone in servers with your bot can use it!")
|
||||||
|
|
||||||
home_channel = prompt("Home channel ID (optional, for cron delivery)")
|
home_channel = prompt("Home channel ID (optional, for cron delivery)")
|
||||||
if home_channel:
|
if home_channel:
|
||||||
save_env_value("DISCORD_HOME_CHANNEL", home_channel)
|
save_env_value("DISCORD_HOME_CHANNEL", home_channel)
|
||||||
|
|
||||||
|
# Check/update existing Discord allowlist
|
||||||
|
elif existing_discord:
|
||||||
|
existing_allowlist = get_env_value('DISCORD_ALLOWED_USERS')
|
||||||
|
if not existing_allowlist:
|
||||||
|
print_info("⚠️ Discord has no user allowlist - anyone can use your bot!")
|
||||||
|
if prompt_yes_no("Add allowed users now?", True):
|
||||||
|
print_info(" To find Discord ID: Enable Developer Mode, right-click name → Copy ID")
|
||||||
|
allowed_users = prompt("Allowed user IDs (comma-separated)")
|
||||||
|
if allowed_users:
|
||||||
|
save_env_value("DISCORD_ALLOWED_USERS", allowed_users.replace(" ", ""))
|
||||||
|
print_success("Discord allowlist configured")
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Step 7: Additional Tools (Optional)
|
# Step 7: Additional Tools (Optional)
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
|
||||||
|
|
@ -139,9 +139,11 @@ TOOLSETS = {
|
||||||
# ==========================================================================
|
# ==========================================================================
|
||||||
|
|
||||||
"hermes-telegram": {
|
"hermes-telegram": {
|
||||||
"description": "Telegram bot toolset - web research, skills, cronjobs (no terminal/browser for security)",
|
"description": "Telegram bot toolset - full access for personal use (terminal has safety checks)",
|
||||||
"tools": [
|
"tools": [
|
||||||
# Web tools - safe for messaging
|
# Terminal - enabled with dangerous command approval system
|
||||||
|
"terminal",
|
||||||
|
# Web tools
|
||||||
"web_search", "web_extract",
|
"web_search", "web_extract",
|
||||||
# Vision - analyze images sent by users
|
# Vision - analyze images sent by users
|
||||||
"vision_analyze",
|
"vision_analyze",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue