Extending the CLI
توسيع سطر الأوامر
What this page is, and what it holds.
This page covers Extending the CLI. You will use hermes source here; about 4 minutes to read. A plugin runs with the agent's full permissions. Do not install one you cannot read.
Build wrapper CLIs that extend the Hermes TUI with custom widgets, keybindings, and layout changes
Outcomes taken from this page, not a template.
- Understand what الإضافات is and when you need it.
- Run
hermes sourceand understand what happens next. - Read the table and take only the row that applies to you.
Exactly as they appear in Hermes.
hermes source
Jump to the part you need.
Nothing summarised away.
The documentation body below is reproduced from the official source so commands and identifiers stay exact. Each section carries a short note describing what it contains.
Hermes exposes protected extension hooks on HermesCLI so wrapper CLIs can add widgets, keybindings, and layout customizations without overriding the 1000+ line run() method. This keeps your extension decoupled from internal changes.
Extension points
A lookup table. Do not read it all; find the row that applies to you.
There are five extension seams available:
| Hook | Purpose | Override when... |
|---|---|---|
_get_extra_tui_widgets() | Inject widgets into the layout | You need a persistent UI element (panel, status line, mini-player) |
_register_extra_tui_keybindings(kb, *, input_area) | Add keyboard shortcuts | You need hotkeys (toggle panels, transport controls, modal shortcuts) |
_build_tui_layout_children(**widgets) | Full control over widget ordering | You need to reorder or wrap existing widgets (rare) |
process_command() | Add custom slash commands | You need /mycommand handling (pre-existing hook) |
_build_tui_style_dict() | Custom prompt_toolkit styles | You need custom colors or styling (pre-existing hook) |
The first three are new protected hooks. The last two already existed.
Quick start: a wrapper CLI
Ordered, practical steps. Run one and confirm it worked before moving on. Commands here: hermes source.
#!/usr/bin/env python3
"""my_cli.py — Example wrapper CLI that extends Hermes."""
from cli import HermesCLI
from prompt_toolkit.layout import FormattedTextControl, Window
from prompt_toolkit.filters import Condition
class MyCLI(HermesCLI):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self._panel_visible = False
def _get_extra_tui_widgets(self):
"""Add a toggleable info panel above the status bar."""
cli_ref = self
return [
Window(
FormattedTextControl(lambda: "📊 My custom panel content"),
height=1,
filter=Condition(lambda: cli_ref._panel_visible),
),
]
def _register_extra_tui_keybindings(self, kb, *, input_area):
"""F2 toggles the custom panel."""
cli_ref = self
@kb.add("f2")
def _toggle_panel(event):
cli_ref._panel_visible = not cli_ref._panel_visible
def process_command(self, cmd: str) -> bool:
"""Add a /panel slash command."""
if cmd.strip().lower() == "/panel":
self._panel_visible = not self._panel_visible
state = "visible" if self._panel_visible else "hidden"
print(f"Panel is now {state}")
return True
return super().process_command(cmd)
if __name__ == "__main__":
cli = MyCLI()
cli.run()Run it:
cd ~/.hermes/hermes-agent
source .venv/bin/activate
python my_cli.pyHook reference
Explains the idea itself. Read it slowly; the later sections build on it.
getextratuiwidgets()
Returns a list of prompt_toolkit widgets to insert into the TUI layout. Widgets appear between the spacer and the status bar — above the input area but below the main output.
def _get_extra_tui_widgets(self) -> list:
return [] # default: no extra widgetsEach widget should be a prompt_toolkit container (e.g., Window, ConditionalContainer, HSplit). Use ConditionalContainer or filter=Condition(...) to make widgets toggleable.
from prompt_toolkit.layout import ConditionalContainer, Window, FormattedTextControl
from prompt_toolkit.filters import Condition
def _get_extra_tui_widgets(self):
return [
ConditionalContainer(
Window(FormattedTextControl("Status: connected"), height=1),
filter=Condition(lambda: self._show_status),
),
]registerextratuikeybindings(kb, , inputarea)
Called after Hermes registers its own keybindings and before the layout is built. Add your keybindings to kb.
def _register_extra_tui_keybindings(self, kb, *, input_area):
pass # default: no extra keybindingsParameters:
kb— TheKeyBindingsinstance for the prompt_toolkit applicationinput_area— The mainTextAreawidget, if you need to read or manipulate user input
def _register_extra_tui_keybindings(self, kb, *, input_area):
cli_ref = self
@kb.add("f3")
def _clear_input(event):
input_area.text = ""
@kb.add("f4")
def _insert_template(event):
input_area.text = "/search "Avoid conflicts with built-in keybindings: Enter (submit), Escape Enter (newline), Ctrl-C (interrupt), Ctrl-D (exit), Tab (auto-suggest accept). Function keys F2+ and Ctrl-combinations are generally safe.
buildtuilayoutchildren(widgets)
Override this only when you need full control over widget ordering. Most extensions should use _get_extra_tui_widgets() instead.
def _build_tui_layout_children(self, *, sudo_widget, secret_widget,
approval_widget, clarify_widget, model_picker_widget=None,
spinner_widget=None, spacer, status_bar, input_rule_top,
image_bar, input_area, input_rule_bot, voice_status_bar,
completions_menu) -> list:The default implementation returns (any None widgets are filtered out):
[
Window(height=0), # anchor
sudo_widget, # sudo password prompt (conditional)
secret_widget, # secret input prompt (conditional)
approval_widget, # dangerous command approval (conditional)
clarify_widget, # clarify question UI (conditional)
model_picker_widget, # model picker overlay (conditional)
spinner_widget, # thinking spinner (conditional)
spacer, # fills remaining vertical space
*self._get_extra_tui_widgets(), # YOUR WIDGETS GO HERE
status_bar, # model/token/context status line
input_rule_top, # ─── border above input
image_bar, # attached images indicator
input_area, # user text input
input_rule_bot, # ─── border below input
voice_status_bar, # voice mode status (conditional)
completions_menu, # autocomplete dropdown
]Layout diagram
Explains the idea itself. Read it slowly; the later sections build on it.
The default layout from top to bottom:
- Output area — scrolling conversation history
- Spacer
- Extra widgets — from
_get_extra_tui_widgets() - Status bar — model, context %, elapsed time
- Image bar — attached image count
- Input area — user prompt
- Voice status — recording indicator
- Completions menu — autocomplete suggestions
Tips
Explains the idea itself. Read it slowly; the later sections build on it.
- Invalidate the display after state changes: call
self._invalidate()to trigger a prompt_toolkit redraw. - Access agent state:
self.agent,self.model,self.conversation_historyare all available. - Custom styles: Override
_build_tui_style_dict()and add entries for your custom style classes. - Slash commands: Override
process_command(), handle your commands, and callsuper().process_command(cmd)for everything else. - Don't override
run()unless absolutely necessary — the extension hooks exist specifically to avoid that coupling.
2 questions answered by this page alone.
Every option is a real identifier from the Hermes documentation. The wrong ones are real too, just from other pages.