/new, /model, /help, and everything else — are defined once in a central registry and automatically propagate to every consumer: CLI help output, gateway dispatch, Telegram bot command menu, Slack subcommand routing, and tab autocomplete.
The CommandDef dataclass
Every slash command is represented by aCommandDef instance in hermes_cli/commands.py:
Fields
| Field | Description |
|---|---|
name | Canonical command name without the leading slash. E.g. "background". |
description | Human-readable description shown in /help and Telegram menus. |
category | One of: "Session", "Configuration", "Tools & Skills", "Info", "Exit". |
aliases | Tuple of alternative names. E.g. ("bg",). |
args_hint | Argument placeholder shown in help. E.g. "<prompt>", "[name]". |
subcommands | Tab-completable subcommand list. E.g. ("list", "add", "remove"). |
cli_only | True if the command is only available in the interactive CLI, not the gateway. |
gateway_only | True if the command is only available in messaging platforms, not the CLI. |
Adding a slash command
Add CommandDef to COMMAND_REGISTRY
Open Every downstream consumer — CLI help, gateway help, Telegram menu, Slack routing, autocomplete — derives from this registry at import time. No other registry-related changes are needed.
hermes_cli/commands.py and add a CommandDef entry to the COMMAND_REGISTRY list. Place it in the appropriate category section.Add handler in HermesCLI.process_command()
Open For persistent settings, use
cli.py and add a branch in HermesCLI.process_command(). Commands are dispatched on the canonical name returned by resolve_command():save_config_value() in cli.py rather than writing to the config file directly.Add gateway handler in gateway/run.py (if applicable)
If the command should be available in messaging platforms (i.e. Skip this step if you set
gateway_only=True or neither flag is set), add a handler in gateway/run.py:cli_only=True on the CommandDef.How aliases work
Aliases are fully automatic. Adding an alias to thealiases tuple on a CommandDef is the only change required. All consumers update automatically:
- CLI dispatch —
resolve_command()maps both canonical name and aliases to the sameCommandDef - CLI help — aliases shown alongside the canonical command
- Gateway dispatch —
GATEWAY_KNOWN_COMMANDSfrozenset includes all aliases - Telegram bot menu — canonical name only (aliases excluded from the visible menu)
- Slack routing —
slack_subcommand_map()maps every alias to the command handler - Tab autocomplete —
COMMANDSflat dict includes alias → description entries
/background command has the bg alias:
/background my task and /bg my task resolve to the same handler with zero extra code.
Command categories
Commands are grouped by category in/help output. Use the appropriate category:
| Category | Purpose |
|---|---|
"Session" | Managing conversation state: new, reset, history, retry, undo |
"Configuration" | Model, provider, prompt, settings |
"Tools & Skills" | Tool management, skill browsing, cron, browser, plugins |
"Info" | Help, usage stats, platform status, diagnostics |
"Exit" | Quit commands |
Derived consumers
All downstream consumers are built fromCOMMAND_REGISTRY at import time:
| Consumer | Derived from | Purpose |
|---|---|---|
COMMANDS | All non-gateway-only commands | Flat dict for autocomplete |
COMMANDS_BY_CATEGORY | All non-gateway-only commands | Categorized dict for /help |
SUBCOMMANDS | Commands with subcommands= | Tab-completable subcommand lists |
GATEWAY_KNOWN_COMMANDS | All non-cli-only commands | Frozenset for gateway hook emission |
gateway_help_lines() | All non-cli-only commands | /help output for messaging platforms |
telegram_bot_commands() | All non-cli-only commands | Telegram setMyCommands menu |
slack_subcommand_map() | All non-cli-only commands | /hermes subcommand routing |