refactor: centralize slash command registry (#1603)
* refactor: centralize slash command registry Replace 7+ scattered command definition sites with a single CommandDef registry in hermes_cli/commands.py. All downstream consumers now derive from this registry: - CLI process_command() resolves aliases via resolve_command() - Gateway _known_commands uses GATEWAY_KNOWN_COMMANDS frozenset - Gateway help text generated by gateway_help_lines() - Telegram BotCommands generated by telegram_bot_commands() - Slack subcommand map generated by slack_subcommand_map() Adding a command or alias is now a one-line change to COMMAND_REGISTRY instead of touching 6+ files. Bugfixes included: - Telegram now registers /rollback, /background (were missing) - Slack now has /voice, /update, /reload-mcp (were missing) - Gateway duplicate 'reasoning' dispatch (dead code) removed - Gateway help text can no longer drift from CLI help Backwards-compatible: COMMANDS and COMMANDS_BY_CATEGORY dicts are rebuilt from the registry, so existing imports work unchanged. * docs: update developer docs for centralized command registry Update AGENTS.md with full 'Slash Command Registry' and 'Adding a Slash Command' sections covering CommandDef fields, registry helpers, and the one-line alias workflow. Also update: - CONTRIBUTING.md: commands.py description - website/docs/reference/slash-commands.md: reference central registry - docs/plans/centralize-command-registry.md: mark COMPLETED - plans/checkpoint-rollback.md: reference new pattern - hermes-agent-dev skill: architecture table * chore: remove stale plan docs
This commit is contained in:
46
AGENTS.md
46
AGENTS.md
@@ -129,14 +129,50 @@ Messages follow OpenAI format: `{"role": "system/user/assistant/tool", ...}`. Re
|
||||
- **KawaiiSpinner** (`agent/display.py`) — animated faces during API calls, `┊` activity feed for tool results
|
||||
- `load_cli_config()` in cli.py merges hardcoded defaults + user config YAML
|
||||
- **Skin engine** (`hermes_cli/skin_engine.py`) — data-driven CLI theming; initialized from `display.skin` config key at startup; skins customize banner colors, spinner faces/verbs/wings, tool prefix, response box, branding text
|
||||
- `process_command()` is a method on `HermesCLI` (not in commands.py)
|
||||
- `process_command()` is a method on `HermesCLI` — dispatches on canonical command name resolved via `resolve_command()` from the central registry
|
||||
- Skill slash commands: `agent/skill_commands.py` scans `~/.hermes/skills/`, injects as **user message** (not system prompt) to preserve prompt caching
|
||||
|
||||
### Adding CLI Commands
|
||||
### Slash Command Registry (`hermes_cli/commands.py`)
|
||||
|
||||
1. Add to `COMMANDS` dict in `hermes_cli/commands.py`
|
||||
2. Add handler in `HermesCLI.process_command()` in `cli.py`
|
||||
3. For persistent settings, use `save_config_value()` in `cli.py`
|
||||
All slash commands are defined in a central `COMMAND_REGISTRY` list of `CommandDef` objects. Every downstream consumer derives from this registry automatically:
|
||||
|
||||
- **CLI** — `process_command()` resolves aliases via `resolve_command()`, dispatches on canonical name
|
||||
- **Gateway** — `GATEWAY_KNOWN_COMMANDS` frozenset for hook emission, `resolve_command()` for dispatch
|
||||
- **Gateway help** — `gateway_help_lines()` generates `/help` output
|
||||
- **Telegram** — `telegram_bot_commands()` generates the BotCommand menu
|
||||
- **Slack** — `slack_subcommand_map()` generates `/hermes` subcommand routing
|
||||
- **Autocomplete** — `COMMANDS` flat dict feeds `SlashCommandCompleter`
|
||||
- **CLI help** — `COMMANDS_BY_CATEGORY` dict feeds `show_help()`
|
||||
|
||||
### Adding a Slash Command
|
||||
|
||||
1. Add a `CommandDef` entry to `COMMAND_REGISTRY` in `hermes_cli/commands.py`:
|
||||
```python
|
||||
CommandDef("mycommand", "Description of what it does", "Session",
|
||||
aliases=("mc",), args_hint="[arg]"),
|
||||
```
|
||||
2. Add handler in `HermesCLI.process_command()` in `cli.py`:
|
||||
```python
|
||||
elif canonical == "mycommand":
|
||||
self._handle_mycommand(cmd_original)
|
||||
```
|
||||
3. If the command is available in the gateway, add a handler in `gateway/run.py`:
|
||||
```python
|
||||
if canonical == "mycommand":
|
||||
return await self._handle_mycommand(event)
|
||||
```
|
||||
4. For persistent settings, use `save_config_value()` in `cli.py`
|
||||
|
||||
**CommandDef fields:**
|
||||
- `name` — canonical name without slash (e.g. `"background"`)
|
||||
- `description` — human-readable description
|
||||
- `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]"`)
|
||||
- `cli_only` — only available in the interactive CLI
|
||||
- `gateway_only` — only available in messaging platforms
|
||||
|
||||
**Adding an alias** requires only adding it to the `aliases` tuple on the existing `CommandDef`. No other file changes needed — dispatch, help text, Telegram menu, Slack mapping, and autocomplete all update automatically.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user