diff --git a/generate_website.py b/generate_website.py
index 99476e7..b7d7855 100755
--- a/generate_website.py
+++ b/generate_website.py
@@ -455,7 +455,6 @@ def generate_html(bot_name: str, title: str, introduction: str, commands: List[T
for cmd_name, cmd_instance in category_commands:
primary_name = cmd_instance.name if hasattr(cmd_instance, 'name') else cmd_name
- description = getattr(cmd_instance, 'description', 'No description available')
keywords = getattr(cmd_instance, 'keywords', [])
# Filter out the primary name from keywords to avoid duplication
@@ -464,6 +463,23 @@ def generate_html(bot_name: str, title: str, introduction: str, commands: List[T
# Get channel restriction info
channel_info = get_channel_info(cmd_instance, monitor_channels)
+ # Get usage information including usage syntax, examples, parameters, and sub-commands
+ try:
+ usage_info = cmd_instance.get_usage_info()
+ usage_syntax = usage_info.get('usage', '')
+ examples = usage_info.get('examples', [])
+ parameters = usage_info.get('parameters', [])
+ subcommands = usage_info.get('subcommands', [])
+ # Use short_description for website if available, otherwise fallback to description
+ short_desc = usage_info.get('short_description', '')
+ description = short_desc if short_desc else usage_info.get('description', 'No description available')
+ except Exception:
+ usage_syntax = ''
+ examples = []
+ parameters = []
+ subcommands = []
+ description = getattr(cmd_instance, 'description', 'No description available')
+
commands_html += f'
\n'
commands_html += f'
\n'
commands_html += f'
{escape_html(primary_name)}
\n'
@@ -485,11 +501,28 @@ def generate_html(bot_name: str, title: str, introduction: str, commands: List[T
commands_html += f' \n'
commands_html += f'
{escape_html(description)}
\n'
- # Get usage information including sub-commands
+ # Render usage, examples, parameters, subcommands
try:
- usage_info = cmd_instance.get_usage_info()
- subcommands = usage_info.get('subcommands', [])
+ # Render usage syntax
+ if usage_syntax:
+ commands_html += f'
{escape_html(usage_syntax)}
\n'
+
+ # Render parameters
+ if parameters:
+ commands_html += f'
\n'
+ commands_html += f' \n'
+ for param in parameters:
+ param_name = param.get('name', '')
+ param_desc = param.get('description', '')
+ if param_name and param_desc:
+ commands_html += f'
\n'
+ commands_html += f' {escape_html(param_name)}\n'
+ commands_html += f' {escape_html(param_desc)}\n'
+ commands_html += f'
\n'
+ commands_html += f'
\n'
+
+ # Render subcommands
if subcommands:
commands_html += f'
\n'
commands_html += f'
Sub-commands:
\n'
@@ -569,7 +602,7 @@ def generate_html(bot_name: str, title: str, introduction: str, commands: List[T
--accent-yellow: #ffd700;
--text-primary: #e8edf4;
--text-secondary: #8892a4;
- --text-muted: #4a5568;
+ --text-muted: #6b7280;
--border-subtle: rgba(255,255,255,0.06);
--glow-blue: rgba(0, 212, 255, 0.15);
--glow-cyan: rgba(0, 255, 200, 0.1);
@@ -1067,6 +1100,57 @@ def generate_html(bot_name: str, title: str, introduction: str, commands: List[T
font-size: 0.95rem;
}}
+ .command-usage {{
+ margin: 0.75rem 0;
+ padding: 0.6rem 0.9rem;
+ background: rgba(0, 0, 0, 0.3);
+ border-radius: 8px;
+ border-left: 3px solid var(--accent-blue);
+ }}
+
+ .command-usage code {{
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.85rem;
+ color: var(--accent-cyan);
+ }}
+
+ .command-params {{
+ margin-top: 0.75rem;
+ padding-top: 0.75rem;
+ border-top: 1px solid var(--border-subtle);
+ }}
+
+ .params-header {{
+ font-size: 0.8rem;
+ font-weight: 600;
+ color: var(--text-muted);
+ margin-bottom: 0.5rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ }}
+
+ .param-item {{
+ display: flex;
+ gap: 0.5rem;
+ margin-bottom: 0.4rem;
+ font-size: 0.85rem;
+ align-items: flex-start;
+ }}
+
+ .param-name {{
+ font-family: 'JetBrains Mono', monospace;
+ color: var(--accent-orange);
+ font-weight: 500;
+ min-width: 80px;
+ flex-shrink: 0;
+ }}
+
+ .param-desc {{
+ color: var(--text-secondary);
+ flex: 1;
+ line-height: 1.4;
+ }}
+
.command-subcommands {{
margin-top: 1rem;
padding-top: 1rem;
diff --git a/modules/commands/airplanes_command.py b/modules/commands/airplanes_command.py
index 5d25c16..8da5f20 100644
--- a/modules/commands/airplanes_command.py
+++ b/modules/commands/airplanes_command.py
@@ -29,6 +29,16 @@ class AirplanesCommand(BaseCommand):
cooldown_seconds = 2 # Respect API rate limit of 1 req/sec with buffer
requires_internet = True
+ # Documentation
+ short_description = "Get aircraft overhead using ADS-B data"
+ usage = "airplanes [lat,lon|here] [radius=N] [options]"
+ examples = ["airplanes", "overhead 47.6,-122.3"]
+ parameters = [
+ {"name": "location", "description": "Coordinates or here for your companion's location if advertised"},
+ {"name": "radius", "description": "Search radius in nautical miles (default: 25)"},
+ {"name": "filters", "description": "alt=, type=, military, closest, etc."}
+ ]
+
def __init__(self, bot):
super().__init__(bot)
self.airplanes_enabled = self.get_config_value('Airplanes_Command', 'enabled', fallback=True, value_type='bool')
diff --git a/modules/commands/alert_command.py b/modules/commands/alert_command.py
index f06c6cd..9a29ed1 100644
--- a/modules/commands/alert_command.py
+++ b/modules/commands/alert_command.py
@@ -178,6 +178,15 @@ class AlertCommand(BaseCommand):
description = "Get active emergency incidents (usage: alert seattle, alert 98258, alert 178th seattle, alert seattle all)"
category = "emergency"
cooldown_seconds = 10 # 10 second cooldown to prevent API abuse
+
+ # Documentation
+ short_description = "Get active emergency incidents from PulsePoint"
+ usage = "alert
[all]"
+ examples = ["alert seattle", "alert 98101 all"]
+ parameters = [
+ {"name": "location", "description": "City, zip code, or street address"},
+ {"name": "all", "description": "Show all incidents (not just nearby)"}
+ ]
requires_internet = True # Requires internet access for PulsePoint API
def __init__(self, bot):
diff --git a/modules/commands/alternatives/wx_international.py b/modules/commands/alternatives/wx_international.py
index 721e828..026c211 100644
--- a/modules/commands/alternatives/wx_international.py
+++ b/modules/commands/alternatives/wx_international.py
@@ -25,6 +25,15 @@ class GlobalWxCommand(BaseCommand):
cooldown_seconds = 5 # 5 second cooldown per user to prevent API abuse
requires_internet = True # Requires internet access for Open-Meteo API and geocoding
+ # Documentation
+ short_description = "Get weather for any global location using Open-Meteo API"
+ usage = "gwx [tomorrow|7d|hourly]"
+ examples = ["gwx Tokyo", "gwx Paris, France"]
+ parameters = [
+ {"name": "location", "description": "City name, country, or coordinates"},
+ {"name": "option", "description": "tomorrow, 7d, or hourly (optional)"}
+ ]
+
# Error constants - will use translations instead
ERROR_FETCHING_DATA = "ERROR_FETCHING_DATA" # Placeholder, will use translate()
NO_ALERTS = "No weather alerts available"
diff --git a/modules/commands/aqi_command.py b/modules/commands/aqi_command.py
index 922bbdd..c9489cc 100644
--- a/modules/commands/aqi_command.py
+++ b/modules/commands/aqi_command.py
@@ -31,6 +31,14 @@ class AqiCommand(BaseCommand):
cooldown_seconds = 5 # 5 second cooldown per user to prevent API abuse
requires_internet = True # Requires internet access for OpenMeteo API and geocoding
+ # Documentation
+ short_description = "Get Air Quality Index for a location"
+ usage = "aqi "
+ examples = ["aqi seattle", "aqi 47.6,-122.3"]
+ parameters = [
+ {"name": "location", "description": "City, neighborhood, lat/lon, or 'help'"}
+ ]
+
# Error constants
ERROR_FETCHING_DATA = "Error fetching AQI data"
NO_DATA_AVAILABLE = "No AQI data available"
diff --git a/modules/commands/base_command.py b/modules/commands/base_command.py
index 2ba2d88..8015c69 100644
--- a/modules/commands/base_command.py
+++ b/modules/commands/base_command.py
@@ -31,6 +31,7 @@ class BaseCommand(ABC):
category: str = "general"
# Documentation fields - to be overridden by subclasses for website generation
+ short_description: str = "" # Brief description for website (without usage syntax)
usage: str = "" # Usage syntax, e.g., "wx [tomorrow|7d|hourly|alerts]"
examples: List[str] = [] # Example commands, e.g., ["wx 98101", "wx seattle tomorrow"]
parameters: List[Dict[str, str]] = [] # Parameter definitions, e.g., [{"name": "location", "description": "US zip code or city name"}]
@@ -176,21 +177,28 @@ class BaseCommand(ABC):
def get_usage_info(self) -> Dict[str, Any]:
"""Get structured usage information including sub-commands and options.
+ Uses class attributes as defaults, with translation overrides for i18n support.
+
Returns:
Dict with keys:
- - 'description': Main command description
+ - 'description': Main command description (for help text)
+ - 'short_description': Brief description for website (without usage syntax)
+ - 'usage': Usage syntax string (e.g., "wx [option]")
- 'subcommands': List of dicts with 'name' and 'description'
- - 'usage_patterns': List of usage pattern strings
- 'examples': List of example strings
+ - 'parameters': List of dicts with 'name' and 'description'
"""
+ # Start with class attribute defaults
usage_info = {
'description': self.description or "No description available",
+ 'short_description': self.short_description or "",
+ 'usage': self.usage or "",
'subcommands': [],
- 'usage_patterns': [],
- 'examples': []
+ 'examples': list(self.examples) if self.examples else [],
+ 'parameters': list(self.parameters) if self.parameters else []
}
- # Try to get structured data from translations
+ # Try to get structured data from translations (i18n overrides)
if hasattr(self.bot, 'translator'):
try:
# Get subcommands from translations
@@ -199,17 +207,23 @@ class BaseCommand(ABC):
if subcommands_data and isinstance(subcommands_data, list):
usage_info['subcommands'] = subcommands_data
- # Get usage patterns from translations
- usage_patterns_key = f"commands.{self.name}.usage_patterns"
- usage_patterns_data = self.translate_get_value(usage_patterns_key)
- if usage_patterns_data and isinstance(usage_patterns_data, list):
- usage_info['usage_patterns'] = usage_patterns_data
-
- # Get examples from translations
+ # Get examples from translations (override class attribute)
examples_key = f"commands.{self.name}.examples"
examples_data = self.translate_get_value(examples_key)
if examples_data and isinstance(examples_data, list):
usage_info['examples'] = examples_data
+
+ # Get usage from translations (override class attribute)
+ usage_key = f"commands.{self.name}.usage_syntax"
+ usage_data = self.translate_get_value(usage_key)
+ if usage_data and isinstance(usage_data, str):
+ usage_info['usage'] = usage_data
+
+ # Get parameters from translations (override class attribute)
+ params_key = f"commands.{self.name}.parameters"
+ params_data = self.translate_get_value(params_key)
+ if params_data and isinstance(params_data, list):
+ usage_info['parameters'] = params_data
except Exception as e:
self.logger.debug(f"Could not load usage info from translations for {self.name}: {e}")
diff --git a/modules/commands/channels_command.py b/modules/commands/channels_command.py
index 1b69386..b40d32e 100644
--- a/modules/commands/channels_command.py
+++ b/modules/commands/channels_command.py
@@ -24,6 +24,16 @@ class ChannelsCommand(BaseCommand):
description = "Lists hashtag channels with sub-categories. Use 'channels' for general, 'channels list' for all categories, 'channels ' for specific categories, 'channels #channel' for specific channel info."
category = "basic"
+ # Documentation
+ short_description = "Lists hashtag channels with sub-categories"
+ usage = "channels [list|category|#channel]"
+ examples = ["channels", "channels list"]
+ parameters = [
+ {"name": "list", "description": "Show all channel categories"},
+ {"name": "category", "description": "Filter by category name"},
+ {"name": "#channel", "description": "Get info on a specific channel"}
+ ]
+
def __init__(self, bot):
"""Initialize the channels command.
diff --git a/modules/commands/cmd_command.py b/modules/commands/cmd_command.py
index 15dfecd..aeb2efa 100644
--- a/modules/commands/cmd_command.py
+++ b/modules/commands/cmd_command.py
@@ -18,6 +18,11 @@ class CmdCommand(BaseCommand):
description = "Lists available commands in compact format"
category = "basic"
+ # Documentation
+ short_description = "Lists available commands"
+ usage = "cmd"
+ examples = ["cmd"]
+
def __init__(self, bot):
"""Initialize the cmd command.
diff --git a/modules/commands/dadjoke_command.py b/modules/commands/dadjoke_command.py
index b84d416..6540266 100644
--- a/modules/commands/dadjoke_command.py
+++ b/modules/commands/dadjoke_command.py
@@ -24,6 +24,11 @@ class DadJokeCommand(BaseCommand):
cooldown_seconds = 3
requires_internet = True # Requires internet access for API calls
+ # Documentation
+ short_description = "Get a random dad joke"
+ usage = "dadjoke"
+ examples = ["dadjoke"]
+
# API configuration
DAD_JOKE_API_URL = "https://icanhazdadjoke.com/"
TIMEOUT = 10 # seconds
diff --git a/modules/commands/dice_command.py b/modules/commands/dice_command.py
index 666a661..abcfef2 100644
--- a/modules/commands/dice_command.py
+++ b/modules/commands/dice_command.py
@@ -18,6 +18,20 @@ class DiceCommand(BaseCommand):
description = "Roll dice for D&D and tabletop games. Use 'dice' for d6, 'dice d20' for d20, 'dice 2d6' for 2d6, 'dice d10 d6' for mixed dice, 'dice decade' for decade die (00-90), etc."
category = "games"
+ # Documentation
+ short_description = "Roll dice for tabletop games"
+ usage = "dice [NdX|dX|decade]"
+ examples = [
+ "dice",
+ "dice d20",
+ "dice 2d6",
+ "dice d10 d6",
+ "dice decade"
+ ]
+ parameters = [
+ {"name": "dice", "description": "Dice notation: d6, 2d8, d10 d6, decade"}
+ ]
+
# Standard D&D dice types
DICE_TYPES = {
'd4': 4,
diff --git a/modules/commands/hacker_command.py b/modules/commands/hacker_command.py
index 6321ac4..0675533 100644
--- a/modules/commands/hacker_command.py
+++ b/modules/commands/hacker_command.py
@@ -22,6 +22,11 @@ class HackerCommand(BaseCommand):
description = "Simulates hacking a supervillain's mainframe with hilarious error messages"
category = "fun"
+ # Documentation
+ short_description = "Try Linux commands and get supervillain mainframe errors"
+ usage = ""
+ examples = ["sudo make me a sandwich", "rm -rf /"]
+
def __init__(self, bot: Any):
"""Initialize the hacker command.
diff --git a/modules/commands/hello_command.py b/modules/commands/hello_command.py
index e3ce22c..02c09dd 100644
--- a/modules/commands/hello_command.py
+++ b/modules/commands/hello_command.py
@@ -19,6 +19,11 @@ class HelloCommand(BaseCommand):
description = "Responds to greetings with robot-themed responses"
category = "basic"
+ # Documentation
+ short_description = "Responds to greetings with robot-themed responses"
+ usage = "hello"
+ examples = ["hello", "hi", "hey"]
+
def __init__(self, bot: Any):
"""Initialize the hello command.
diff --git a/modules/commands/help_command.py b/modules/commands/help_command.py
index db2a30f..52d4deb 100644
--- a/modules/commands/help_command.py
+++ b/modules/commands/help_command.py
@@ -25,6 +25,14 @@ class HelpCommand(BaseCommand):
description = "Shows commands. Use 'help ' for details."
category = "basic"
+ # Documentation
+ short_description = "Get help on available commands"
+ usage = "help [command]"
+ examples = ["help", "help wx"]
+ parameters = [
+ {"name": "command", "description": "Command name for detailed help (optional)"}
+ ]
+
def __init__(self, bot):
"""Initialize the help command.
diff --git a/modules/commands/hfcond_command.py b/modules/commands/hfcond_command.py
index 94d1afc..6b96cc5 100644
--- a/modules/commands/hfcond_command.py
+++ b/modules/commands/hfcond_command.py
@@ -22,6 +22,11 @@ class HfcondCommand(BaseCommand):
category = "solar"
requires_internet = True # Requires internet access for hamqsl.com API
+ # Documentation
+ short_description = "Get HF band conditions for ham radio"
+ usage = "hfcond"
+ examples = ["hfcond"]
+
def __init__(self, bot):
"""Initialize the hfcond command.
diff --git a/modules/commands/joke_command.py b/modules/commands/joke_command.py
index 9755dfd..552d7fd 100644
--- a/modules/commands/joke_command.py
+++ b/modules/commands/joke_command.py
@@ -24,6 +24,14 @@ class JokeCommand(BaseCommand):
requires_dm = False # Works in both channels and DMs
requires_internet = True # Requires internet access for API calls
+ # Documentation
+ short_description = "Get a random joke"
+ usage = "joke [category]"
+ examples = ["joke", "joke programming"]
+ parameters = [
+ {"name": "category", "description": "programming, pun, misc, dark (optional)"}
+ ]
+
# Supported categories
SUPPORTED_CATEGORIES = {
'programming': 'Programming',
diff --git a/modules/commands/magic8_command.py b/modules/commands/magic8_command.py
index 97a884f..5b1b16a 100644
--- a/modules/commands/magic8_command.py
+++ b/modules/commands/magic8_command.py
@@ -27,6 +27,11 @@ class Magic8Command(BaseCommand):
description = "Emulates the classic Magic 8-ball toy'"
category = "games"
+ # Documentation
+ short_description = "Ask the Magic 8-Ball a yes/no question"
+ usage = "magic8 "
+ examples = ["magic8 Will it rain tomorrow?"]
+
def __init__(self, bot):
"""Initialize the magic8 command.
diff --git a/modules/commands/moon_command.py b/modules/commands/moon_command.py
index 6498c47..feb61e3 100644
--- a/modules/commands/moon_command.py
+++ b/modules/commands/moon_command.py
@@ -17,6 +17,11 @@ class MoonCommand(BaseCommand):
description = "Get moon phase, rise/set times and position"
category = "solar"
+ # Documentation
+ short_description = "Get moon phase and rise/set times"
+ usage = "moon"
+ examples = ["moon"]
+
def __init__(self, bot):
"""Initialize the moon command.
diff --git a/modules/commands/multitest_command.py b/modules/commands/multitest_command.py
index 543e829..09731bd 100644
--- a/modules/commands/multitest_command.py
+++ b/modules/commands/multitest_command.py
@@ -21,6 +21,11 @@ class MultitestCommand(BaseCommand):
description = "Listens for 6 seconds and collects all unique paths from incoming messages"
category = "meshcore_info"
+ # Documentation
+ short_description = "Listens for 6 seconds and collects all unique paths your incoming messages took to reach the bot"
+ usage = "multitest"
+ examples = ["multitest", "mt"]
+
def __init__(self, bot):
super().__init__(bot)
self.multitest_enabled = self.get_config_value('Multitest_Command', 'enabled', fallback=True, value_type='bool')
diff --git a/modules/commands/path_command.py b/modules/commands/path_command.py
index 5765a4f..e3cd6c2 100644
--- a/modules/commands/path_command.py
+++ b/modules/commands/path_command.py
@@ -24,6 +24,11 @@ class PathCommand(BaseCommand):
cooldown_seconds = 1
category = "meshcore_info"
+ # Documentation
+ short_description = "Decode path data to show repeaters involved in message routing"
+ usage = "path [hex_data]"
+ examples = ["path", "decode"]
+
def __init__(self, bot):
super().__init__(bot)
self.path_enabled = self.get_config_value('Path_Command', 'enabled', fallback=True, value_type='bool')
diff --git a/modules/commands/ping_command.py b/modules/commands/ping_command.py
index 7b0e11f..8024955 100644
--- a/modules/commands/ping_command.py
+++ b/modules/commands/ping_command.py
@@ -22,6 +22,11 @@ class PingCommand(BaseCommand):
description = "Responds to 'ping' with 'Pong!'"
category = "basic"
+ # Documentation
+ short_description = "Get a quick 'pong'response from the bot"
+ usage = "ping"
+ examples = ["ping"]
+
def __init__(self, bot):
"""Initialize the ping command.
diff --git a/modules/commands/prefix_command.py b/modules/commands/prefix_command.py
index 907dc12..b948e97 100644
--- a/modules/commands/prefix_command.py
+++ b/modules/commands/prefix_command.py
@@ -27,6 +27,15 @@ class PrefixCommand(BaseCommand):
cooldown_seconds = 2
requires_internet = False # Will be set to True in __init__ if API is configured
+ # Documentation
+ short_description = "Look up repeaters by two-character prefix and show their locations (if known)"
+ usage = "prefix "
+ examples = ["prefix 1A", "prefix free"]
+ parameters = [
+ {"name": "prefix", "description": "Two-character prefix (e.g., 1A, 2B)"},
+ {"name": "free", "description": "Show available/unused prefixes"}
+ ]
+
def __init__(self, bot: Any):
"""Initialize the prefix command.
diff --git a/modules/commands/roll_command.py b/modules/commands/roll_command.py
index 7c98f74..ed692b7 100644
--- a/modules/commands/roll_command.py
+++ b/modules/commands/roll_command.py
@@ -24,6 +24,14 @@ class RollCommand(BaseCommand):
description = "Roll a random number between 1 and X (default 100). Use 'roll' for 1-100, 'roll 50' for 1-50, etc."
category = "games"
+ # Documentation
+ short_description = "Roll a random number between 1 and X"
+ usage = "roll [max]"
+ examples = ["roll", "roll 50"]
+ parameters = [
+ {"name": "max", "description": "Maximum value (default: 100, max: 10000)"}
+ ]
+
def __init__(self, bot):
"""Initialize the roll command.
diff --git a/modules/commands/satpass_command.py b/modules/commands/satpass_command.py
index 1a4c202..247cf6b 100644
--- a/modules/commands/satpass_command.py
+++ b/modules/commands/satpass_command.py
@@ -18,6 +18,15 @@ class SatpassCommand(BaseCommand):
category = "solar"
requires_internet = True # Requires internet access for N2YO API
+ # Documentation
+ short_description = "Get satellite pass predictions"
+ usage = "satpass [visual]"
+ examples = ["satpass iss", "satpass 25544 visual"]
+ parameters = [
+ {"name": "satellite", "description": "NORAD ID or shortcut (iss, hst, starlink)"},
+ {"name": "visual", "description": "Add 'visual' for visible passes only"}
+ ]
+
# Common satellite shortcuts
SATELLITE_SHORTCUTS = {
'iss': '25544',
diff --git a/modules/commands/solar_command.py b/modules/commands/solar_command.py
index 598db75..6c3cbbe 100644
--- a/modules/commands/solar_command.py
+++ b/modules/commands/solar_command.py
@@ -22,6 +22,11 @@ class SolarCommand(BaseCommand):
category = "solar"
requires_internet = True # Requires internet access for hamqsl.com API
+ # Documentation
+ short_description = "Get current solar conditions and HF band info"
+ usage = "solar"
+ examples = ["solar"]
+
def __init__(self, bot):
"""Initialize the solar command.
diff --git a/modules/commands/solarforecast_command.py b/modules/commands/solarforecast_command.py
index dae12ef..5691ae4 100644
--- a/modules/commands/solarforecast_command.py
+++ b/modules/commands/solarforecast_command.py
@@ -29,6 +29,17 @@ class SolarforecastCommand(BaseCommand):
cooldown_seconds = 10 # 10 second cooldown per user
requires_internet = True # Requires internet access for Forecast.Solar API and geocoding
+ # Documentation
+ short_description = "Get solar panel production forecast for a location or repeater"
+ usage = "sf [watts] [azimuth] [angle]"
+ examples = ["sf seattle", "sf 47.6,-122.3 200"]
+ parameters = [
+ {"name": "location", "description": "City, coordinates, or repeater name"},
+ {"name": "watts", "description": "Panel size in watts (default: 100)"},
+ {"name": "azimuth", "description": "Panel direction, 0=south (default: 0)"},
+ {"name": "angle", "description": "Panel tilt in degrees (default: 30)"}
+ ]
+
# Error constants - will use translations instead
ERROR_FETCHING_DATA = "Error fetching forecast" # Deprecated - use translate
NO_DATA_AVAILABLE = "No forecast data" # Deprecated - use translate
diff --git a/modules/commands/sports_command.py b/modules/commands/sports_command.py
index d93fe7b..65d530c 100644
--- a/modules/commands/sports_command.py
+++ b/modules/commands/sports_command.py
@@ -44,6 +44,15 @@ class SportsCommand(BaseCommand):
cooldown_seconds = 3 # 3 second cooldown per user to prevent API abuse
requires_internet = True # Requires internet access for ESPN API
+ # Documentation
+ short_description = "Get sports scores and schedules"
+ usage = "sports [team|league]"
+ examples = ["sports", "sports seahawks", "sports nfl"]
+ parameters = [
+ {"name": "team", "description": "Team name (e.g., seahawks, mariners)"},
+ {"name": "league", "description": "League code (nfl, mlb, nba, nhl, mls)"}
+ ]
+
# ESPN client
espn_client: Optional[ESPNClient] = None
diff --git a/modules/commands/stats_command.py b/modules/commands/stats_command.py
index 12be104..a6d97a7 100644
--- a/modules/commands/stats_command.py
+++ b/modules/commands/stats_command.py
@@ -25,6 +25,14 @@ class StatsCommand(BaseCommand):
description = "Show statistics for past 24 hours. Use 'stats messages', 'stats channels', or 'stats paths' for specific stats."
category = "analytics"
+ # Documentation
+ short_description = "Show bot usage statistics for past 24 hours"
+ usage = "stats [messages|channels|paths]"
+ examples = ["stats", "stats channels"]
+ parameters = [
+ {"name": "type", "description": "messages, channels, or paths (optional)"}
+ ]
+
def __init__(self, bot: Any):
"""Initialize the stats command.
diff --git a/modules/commands/sun_command.py b/modules/commands/sun_command.py
index 07ffbd4..f48cb73 100644
--- a/modules/commands/sun_command.py
+++ b/modules/commands/sun_command.py
@@ -21,6 +21,11 @@ class SunCommand(BaseCommand):
description = "Get sunrise/sunset times"
category = "solar"
+ # Documentation
+ short_description = "Get sunrise and sunset times"
+ usage = "sun"
+ examples = ["sun"]
+
def __init__(self, bot):
"""Initialize the sun command.
diff --git a/modules/commands/test_command.py b/modules/commands/test_command.py
index 5ca7f1c..c763f9a 100644
--- a/modules/commands/test_command.py
+++ b/modules/commands/test_command.py
@@ -26,6 +26,11 @@ class TestCommand(BaseCommand):
description = "Responds to 'test' or 't' with connection info"
category = "basic"
+ # Documentation
+ short_description = "Get test response with connection info"
+ usage = "test [phrase]"
+ examples = ["test", "t hello world"]
+
def __init__(self, bot):
super().__init__(bot)
self.test_enabled = self.get_config_value('Test_Command', 'enabled', fallback=True, value_type='bool')
diff --git a/modules/commands/wx_command.py b/modules/commands/wx_command.py
index 1b12520..eb59668 100644
--- a/modules/commands/wx_command.py
+++ b/modules/commands/wx_command.py
@@ -38,6 +38,15 @@ class WxCommand(BaseCommand):
cooldown_seconds = 5 # 5 second cooldown per user to prevent API abuse
requires_internet = True # Requires internet access for NOAA API and geocoding
+ # Documentation
+ short_description = "Get weather for a US location using NOAA weather data"
+ usage = "wx [tomorrow|7d|hourly|alerts]"
+ examples = ["wx 98101", "wx seattle", "wx 90210 7d"]
+ parameters = [
+ {"name": "location", "description": "US zip code or city name"},
+ {"name": "option", "description": "tomorrow, 7d, hourly, or alerts (optional)"}
+ ]
+
# Error constants
NO_DATA_NOGPS = "No GPS data available"
ERROR_FETCHING_DATA = "Error fetching weather data"
diff --git a/website/index.html b/website/index.html
index 7356ecf..7fdcea5 100644
--- a/website/index.html
+++ b/website/index.html
@@ -21,7 +21,7 @@
--accent-yellow: #ffd700;
--text-primary: #e8edf4;
--text-secondary: #8892a4;
- --text-muted: #4a5568;
+ --text-muted: #6b7280;
--border-subtle: rgba(255,255,255,0.06);
--glow-blue: rgba(0, 212, 255, 0.15);
--glow-cyan: rgba(0, 255, 200, 0.1);
@@ -519,6 +519,57 @@
font-size: 0.95rem;
}
+ .command-usage {
+ margin: 0.75rem 0;
+ padding: 0.6rem 0.9rem;
+ background: rgba(0, 0, 0, 0.3);
+ border-radius: 8px;
+ border-left: 3px solid var(--accent-blue);
+ }
+
+ .command-usage code {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 0.85rem;
+ color: var(--accent-cyan);
+ }
+
+ .command-params {
+ margin-top: 0.75rem;
+ padding-top: 0.75rem;
+ border-top: 1px solid var(--border-subtle);
+ }
+
+ .params-header {
+ font-size: 0.8rem;
+ font-weight: 600;
+ color: var(--text-muted);
+ margin-bottom: 0.5rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+ }
+
+ .param-item {
+ display: flex;
+ gap: 0.5rem;
+ margin-bottom: 0.4rem;
+ font-size: 0.85rem;
+ align-items: flex-start;
+ }
+
+ .param-name {
+ font-family: 'JetBrains Mono', monospace;
+ color: var(--accent-orange);
+ font-weight: 500;
+ min-width: 80px;
+ flex-shrink: 0;
+ }
+
+ .param-desc {
+ color: var(--text-secondary);
+ flex: 1;
+ line-height: 1.4;
+ }
+
.command-subcommands {
margin-top: 1rem;
padding-top: 1rem;
@@ -884,7 +935,8 @@
t
- Responds to 'test' or 't' with connection info
+ Get test response with connection info
+ test [phrase]
Responds to greetings with robot-themed responses
+ hello
ping
-
Responds to 'ping' with 'Pong!'
+
Get a quick 'pong'response from the bot
+
ping
help
-
Shows commands. Use 'help <command>' for details.
+
Get help on available commands
+
help [command]
+
+
+
+ command
+ Command name for detailed help (optional)
+
+
@@ -919,7 +981,23 @@
channel
- Lists hashtag channels with sub-categories. Use 'channels' for general, 'channels list' for all categories, 'channels <category>' for specific categories, 'channels #channel' for specific channel info.
+ Lists hashtag channels with sub-categories
+ channels [list|category|#channel]
+
+
+
+ list
+ Show all channel categories
+
+
+ category
+ Filter by category name
+
+
+ #channel
+ Get info on a specific channel
+
+
@@ -928,7 +1006,8 @@
commands
- Lists available commands in compact format
+ Lists available commands
+ cmd
@@ -939,7 +1018,15 @@
stats
- Show statistics for past 24 hours. Use 'stats messages', 'stats channels', or 'stats paths' for specific stats.
+ Show bot usage statistics for past 24 hours
+ stats [messages|channels|paths]
+
+
+
+ type
+ messages, channels, or paths (optional)
+
+
@@ -955,7 +1042,19 @@
incidents
- Get active emergency incidents (usage: alert seattle, alert 98258, alert 178th seattle, alert seattle all)
+ Get active emergency incidents from PulsePoint
+ alert <location> [all]
+
+
+
+ location
+ City, zip code, or street address
+
+
+ all
+ Show all incidents (not just nearby)
+
+
@@ -969,7 +1068,15 @@
jokes
- Get a random joke or joke from specific category (usage: joke [category])
+ Get a random joke
+ joke [category]
+
+
+
+ category
+ programming, pun, misc, dark (optional)
+
+
Channel: #jokes
@@ -986,7 +1093,8 @@
dad jokes
- Get a random dad joke from icanhazdadjoke.com
+ Get a random dad joke
+ dadjoke
Channel: #jokes
@@ -1001,7 +1109,8 @@
+24 more
- Simulates hacking a supervillain's mainframe with hilarious error messages
+ Try Linux commands and get supervillain mainframe errors
+ <linux_command>
@@ -1012,19 +1121,36 @@
dice
- Roll dice for D&D and tabletop games. Use 'dice' for d6, 'dice d20' for d20, 'dice 2d6' for 2d6, 'dice d10 d6' for mixed dice, 'dice decade' for decade die (00-90), etc.
+ Roll dice for tabletop games
+ dice [NdX|dX|decade]
+
+
+
+ dice
+ Dice notation: d6, 2d8, d10 d6, decade
+
+
magic8
-
Emulates the classic Magic 8-ball toy'
+
Ask the Magic 8-Ball a yes/no question
+
magic8 <question>
roll
-
Roll a random number between 1 and X (default 100). Use 'roll' for 1-100, 'roll 50' for 1-50, etc.
+
Roll a random number between 1 and X
+
roll [max]
+
+
+
+ max
+ Maximum value (default: 100, max: 10000)
+
+
@@ -1041,7 +1167,23 @@
overhead
- Get aircraft overhead (usage: airplanes [location] [options] or overhead [lat,lon])
+ Get aircraft overhead using ADS-B data
+ airplanes [lat,lon|here] [radius=N] [options]
+
+
+
+ location
+ Coordinates or here for your companion's location if advertised
+
+
+ radius
+ Search radius in nautical miles (default: 25)
+
+
+ filters
+ alt=, type=, military, closest, etc.
+
+
@@ -1057,7 +1199,8 @@
p
- Decode hex path data to show which repeaters were involved in message routing
+ Decode path data to show repeaters involved in message routing
+ path [hex_data]
@@ -1067,7 +1210,19 @@
lookup
- Look up repeaters by two-character prefix (e.g., 'prefix 1A')
+ Look up repeaters by two-character prefix and show their locations (if known)
+ prefix <XX|free|refresh>
+
+
+
+ prefix
+ Two-character prefix (e.g., 1A, 2B)
+
+
+ free
+ Show available/unused prefixes
+
+
- Listens for 6 seconds and collects all unique paths from incoming messages
+ Listens for 6 seconds and collects all unique paths your incoming messages took to reach the bot
+ multitest
@@ -1088,30 +1244,46 @@
hfcond
Get HF band conditions for ham radio
+ hfcond
moon
-
Get moon phase, rise/set times and position
+
Get moon phase and rise/set times
+
moon
solar
Get current solar conditions and HF band info
+
solar
satpass
-
Get satellite pass info: satpass <NORAD_number_or_shortcut> [visual]
+
Get satellite pass predictions
+
satpass <NORAD_number|shortcut> [visual]
+
+
+
+ satellite
+ NORAD ID or shortcut (iss, hst, starlink)
+
+
+ visual
+ Add 'visual' for visible passes only
+
+
sun
-
Get sunrise/sunset times
+
Get sunrise and sunset times
+
sun
@@ -1120,7 +1292,27 @@
sf
- Get solar panel production forecast (usage: sf <location|repeater_name|coordinates|zipcode> [panel_size] [azimuth, 0=south] [angle])
+ Get solar panel production forecast for a location or repeater
+ sf <location> [watts] [azimuth] [angle]
+
+
+
+ location
+ City, coordinates, or repeater name
+
+
+ watts
+ Panel size in watts (default: 100)
+
+
+ azimuth
+ Panel direction, 0=south (default: 0)
+
+
+ angle
+ Panel tilt in degrees (default: 30)
+
+
@@ -1135,7 +1327,19 @@
scores
- Get sports scores and schedules (usage: sports [team/league])
+ Get sports scores and schedules
+ sports [team|league]
+
+
+
+ team
+ Team name (e.g., seahawks, mariners)
+
+
+ league
+ League code (nfl, mlb, nba, nhl, mls)
+
+
Channels: #BotTest, #bot, #sounders, #seahawks
@@ -1152,7 +1356,19 @@
wxalert
- Get weather information for a zip code (usage: wx 12345)
+ Get weather for a US location using NOAA weather data
+ wx <zipcode|city> [tomorrow|7d|hourly|alerts]
+
+
+
+ location
+ US zip code or city name
+
+
+ option
+ tomorrow, 7d, hourly, or alerts (optional)
+
+
@@ -1162,7 +1378,19 @@
gwxa
- Get weather information for any global location (usage: gwx Tokyo)
+ Get weather for any global location using Open-Meteo API
+ gwx <location> [tomorrow|7d|hourly]
+
+
+
+ location
+ City name, country, or coordinates
+
+
+ option
+ tomorrow, 7d, or hourly (optional)
+
+
@@ -1173,7 +1401,15 @@
air_quality
- Get Air Quality Index for a location (usage: aqi seattle, aqi greenwood, aqi vancouver canada, aqi 47.6,-122.3, or aqi help)
+ Get Air Quality Index for a location
+ aqi <city|neighborhood|coordinates|help>
+
+
+
+ location
+ City, neighborhood, lat/lon, or 'help'
+
+