Documentação oficial desta página · v0.20.6
Estender a CLI
O Hermes expõe ganchos de extensão protegidos no HermesCLI, para que CLIs derivadas acrescentem
widgets, atalhos de teclado e ajustes de layout sem sobrescrever o método run(), que tem mais de
mil linhas. Isso mantém a sua extensão desacoplada das mudanças internas.
Pontos de extensão
São cinco costuras disponíveis:
| Gancho | Para que serve | Sobrescreva quando… |
|---|---|---|
_get_extra_tui_widgets() | Injetar widgets no layout | Você precisa de um elemento fixo (painel, linha de status, mini-player) |
_register_extra_tui_keybindings(kb, *, input_area) | Acrescentar atalhos | Você precisa de teclas de atalho (mostrar painéis, controles, atalhos modais) |
_build_tui_layout_children(**widgets) | Controle total da ordem dos widgets | Você precisa reordenar ou embrulhar widgets existentes (raro) |
process_command() | Acrescentar comandos de barra | Você precisa tratar /meucomando (gancho que já existia) |
_build_tui_style_dict() | Estilos próprios do prompt_toolkit | Você precisa de cores ou estilo próprios (gancho que já existia) |
Os três primeiros são ganchos protegidos novos. Os dois últimos já existiam.
Começando: uma CLI derivada
#!/usr/bin/env python3
"""my_cli.py — exemplo de CLI derivada que estende o 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):
"""Acrescenta um painel de informação, que aparece e some, acima da barra de status."""
cli_ref = self
return [
Window(
FormattedTextControl(lambda: "📊 Conteúdo do meu painel"),
height=1,
filter=Condition(lambda: cli_ref._panel_visible),
),
]
def _register_extra_tui_keybindings(self, kb, *, input_area):
"""F2 mostra e esconde o painel."""
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:
"""Acrescenta um comando /panel."""
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()
Rodando:
cd ~/.hermes/hermes-agent
source .venv/bin/activate
python my_cli.py
Referência dos ganchos
_get_extra_tui_widgets()
Devolve uma lista de widgets do prompt_toolkit para inserir no layout. Os widgets aparecem entre o espaçador e a barra de status — acima da área de entrada e abaixo da saída principal.
def _get_extra_tui_widgets(self) -> list:
return [] # padrão: nenhum widget extra
Cada widget deve ser um container do prompt_toolkit (Window, ConditionalContainer, HSplit).
Use ConditionalContainer ou filter=Condition(...) para poder mostrar e esconder.
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),
),
]
_register_extra_tui_keybindings(kb, *, input_area)
Chamado depois de o Hermes registrar os próprios atalhos e antes de montar o layout. Acrescente os
seus ao kb.
def _register_extra_tui_keybindings(self, kb, *, input_area):
pass # padrão: nenhum atalho extra
Parâmetros:
kb— a instância deKeyBindingsda aplicação prompt_toolkitinput_area— o widgetTextAreaprincipal, se você precisar ler ou manipular a entrada
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 "
Evite conflito com os atalhos embutidos: Enter (enviar), Escape Enter (nova linha), Ctrl-C
(interromper), Ctrl-D (sair), Tab (aceitar sugestão). Teclas de função a partir de F2 e
combinações com Ctrl costumam ser seguras.
_build_tui_layout_children(**widgets)
Sobrescreva só quando precisar de controle total da ordem. A maioria das extensões deve usar o
_get_extra_tui_widgets().
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:
A implementação padrão devolve (widgets None são filtrados):
[
Window(height=0), # âncora
sudo_widget, # pedido de senha do sudo (condicional)
secret_widget, # entrada de segredo (condicional)
approval_widget, # aprovação de comando perigoso (condicional)
clarify_widget, # interface de pergunta de esclarecimento (condicional)
model_picker_widget, # sobreposição do seletor de modelo (condicional)
spinner_widget, # indicador de pensamento (condicional)
spacer, # preenche o espaço vertical restante
*self._get_extra_tui_widgets(), # SEUS WIDGETS ENTRAM AQUI
status_bar, # linha de status com modelo, tokens e contexto
input_rule_top, # ─── borda acima da entrada
image_bar, # indicador de imagens anexadas
input_area, # entrada de texto
input_rule_bot, # ─── borda abaixo da entrada
voice_status_bar, # status do modo de voz (condicional)
completions_menu, # lista de autocompletar
]
Diagrama do layout
De cima para baixo:
- Área de saída — histórico da conversa, com rolagem
- Espaçador
- Widgets extras — de
_get_extra_tui_widgets() - Barra de status — modelo, porcentagem de contexto, tempo decorrido
- Barra de imagens — quantidade de imagens anexadas
- Área de entrada — o texto do usuário
- Status de voz — indicador de gravação
- Menu de autocompletar — sugestões
Dicas
- Invalide a tela depois de mudar estado: chame
self._invalidate()para forçar um redesenho. - Acesse o estado do agente:
self.agent,self.modeleself.conversation_historyestão disponíveis. - Estilos próprios: sobrescreva o
_build_tui_style_dict()e acrescente entradas para as suas classes de estilo. - Comandos de barra: sobrescreva o
process_command(), trate os seus comandos e chamesuper().process_command(cmd)para todo o resto. - Não sobrescreva o
run()a menos que seja indispensável — os ganchos existem justamente para evitar esse acoplamento.