Arquitetura da CLI¶
Documentação técnica da camada Presentation (CLI) do CreateAgents AI.
📐 Visão Geral¶
A CLI segue o Command Pattern para processar entrada do usuário e executar ações. É totalmente desacoplada da camada de aplicação através de interfaces.
ChatCLIApplication (orquestrador)
├── CommandRegistry (registro de comandos)
├── TerminalRenderer (UI/renderização)
├── InputReader (leitura de entrada)
└── CommandHandlers (processadores específicos)
├── ChatCommandHandler
├── HelpCommandHandler
├── MetricsCommandHandler
├── ConfigsCommandHandler
├── ToolsCommandHandler
└── ClearCommandHandler
🎯 Componentes Principais¶
1. ChatCLIApplication¶
Responsabilidade: Orquestrador principal do ciclo de vida da CLI.
Localização: src/createagents/presentation/cli/application/chat_cli_app.py
class ChatCLIApplication:
"""Main CLI application orchestrator.
Responsibility: Orchestrate the CLI application lifecycle.
This follows:
- SRP: Only handles application orchestration
- DIP: Depends on abstractions (CommandHandler interface)
- OCP: New commands can be added by registering new handlers
"""
def __init__(self, agent: 'CreateAgent'):
self._agent = agent
self._renderer = TerminalRenderer()
self._input_reader = InputReader()
self._registry = CommandRegistry()
self._setup_commands()
def run(self) -> None:
"""Start the CLI application main loop."""
# Loop principal
Métodos:
__init__(agent)- Inicializa componentes_setup_commands()- Registra handlers de comandosrun()- Loop principal da aplicação_is_exit_command(input)- Verifica comandos de saída
2. CommandHandler (Interface)¶
Responsabilidade: Interface abstrata para handlers de comandos.
Localização: src/createagents/presentation/cli/commands/base_command.py
class CommandHandler(ABC):
"""Abstract base class for command handlers.
This implements the Command Pattern, allowing dynamic
command registration and execution.
"""
def __init__(self, renderer: TerminalRenderer):
self._renderer = renderer
@abstractmethod
def can_handle(self, user_input: str) -> bool:
"""Check if this handler can process the input."""
pass
@abstractmethod
def execute(self, agent: 'CreateAgent', user_input: str) -> None:
"""Execute the command."""
pass
@abstractmethod
def get_aliases(self) -> List[str]:
"""Get command aliases."""
pass
3. CommandRegistry¶
Responsabilidade: Registro e resolução de comandos.
Localização: src/createagents/presentation/cli/application/command_registry.py
class CommandRegistry:
"""Registry for command handlers.
Responsibility: Maintain and resolve command handlers.
This follows OCP: new handlers can be added without modification.
"""
def __init__(self):
self._handlers: List[CommandHandler] = []
def register(self, handler: CommandHandler) -> None:
"""Register a command handler."""
self._handlers.append(handler)
def find_handler(self, user_input: str) -> Optional[CommandHandler]:
"""Find the first handler that can process the input."""
for handler in self._handlers:
if handler.can_handle(user_input):
return handler
return None
Padrão de Registro:
Os handlers são registrados em ordem, do mais específico ao mais genérico. O ChatCommandHandler deve ser sempre o último (handler padrão).
4. Command Handlers¶
ChatCommandHandler¶
Responsabilidade: Processar mensagens de chat (handler padrão).
class ChatCommandHandler(CommandHandler):
"""Handles regular chat messages (default handler)."""
def can_handle(self, user_input: str) -> bool:
# Aceita qualquer entrada (fallback)
return True
def execute(self, agent: 'CreateAgent', user_input: str) -> None:
# Processa streaming assíncrono
asyncio.run(self._async_execute(agent, user_input))
HelpCommandHandler¶
Responsabilidade: Exibir ajuda e lista de comandos.
class HelpCommandHandler(CommandHandler):
def can_handle(self, user_input: str) -> bool:
normalized = self._normalize_input(user_input)
return normalized in self.get_aliases()
def get_aliases(self) -> List[str]:
return ['/help', 'help']
MetricsCommandHandler¶
Responsabilidade: Exibir métricas de performance.
class MetricsCommandHandler(CommandHandler):
def execute(self, agent: 'CreateAgent', user_input: str) -> None:
metrics = agent.get_metrics()
# Formata e renderiza métricas
(Outros handlers seguem estrutura similar)
5. TerminalRenderer¶
Responsabilidade: Renderização de UI no terminal.
Localização: src/createagents/presentation/cli/ui/terminal_renderer.py
class TerminalRenderer:
"""Handles terminal rendering with colors and formatting.
Responsibility: Encapsulate all terminal output logic.
This follows SRP by handling only rendering concerns.
"""
def __init__(self):
self._formatter = TerminalFormatter()
self._colors = ColorScheme()
def render_welcome_screen(self) -> None:
"""Display welcome message."""
def render_prompt(self) -> None:
"""Display user input prompt."""
def render_assistant_token(self, token: str) -> None:
"""Render a single token from assistant."""
def render_system_message(self, message: str) -> None:
"""Render a system message."""
def render_error(self, message: str) -> None:
"""Render an error message."""
Métodos de Renderização:
render_welcome_screen()- Tela de boas-vindasrender_prompt()- Prompt de entradarender_input_indicator()- Indicador ✎render_assistant_token(token)- Token individual do agenterender_thinking_indicator()- Indicador “pensando…”render_system_message(msg)- Mensagem do sistemarender_error(msg)- Mensagem de errorender_metrics(metrics)- Exibir métricasrender_configs(configs)- Exibir configuraçõesrender_tools(tools)- Exibir ferramentas
6. TerminalFormatter¶
Responsabilidade: Formatação de markdown para terminal.
Localização: src/createagents/presentation/cli/ui/terminal_formatter.py
class TerminalFormatter:
"""Formats markdown text for terminal display.
Converts markdown elements to terminal-compatible formatting.
"""
@staticmethod
def format_markdown(text: str) -> str:
"""Convert markdown to terminal formatting."""
# Converte **bold**, *italic*, `code`, etc.
7. ColorScheme¶
Responsabilidade: Define esquema de cores do terminal.
Localização: src/createagents/presentation/cli/ui/color_scheme.py
class ColorScheme:
"""Defines color scheme for terminal output."""
PRIMARY = "\033[36m" # Cyan
SUCCESS = "\033[32m" # Green
WARNING = "\033[33m" # Yellow
ERROR = "\033[31m" # Red
INFO = "\033[34m" # Blue
COMMAND = "\033[35m" # Magenta
RESET = "\033[0m"
🔄 Fluxo de Execução¶
1. Inicialização¶
main()
→ ChatCLIApplication(agent)
→ __init__
→ TerminalRenderer()
→ InputReader()
→ CommandRegistry()
→ _setup_commands()
→ registry.register(HelpCommandHandler)
→ registry.register(MetricsCommandHandler)
→ ... (outros comandos)
→ registry.register(ChatCommandHandler) ← ÚLTIMO
2. Loop Principal¶
app.run()
→ renderer.render_welcome_screen()
→ while True:
→ renderer.render_prompt()
→ user_input = input_reader.read_user_input()
→ if _is_exit_command(user_input): break
→ handler = registry.find_handler(user_input)
→ handler.execute(agent, user_input)
3. Processamento de Comando¶
# Exemplo: /metrics
registry.find_handler("/metrics")
→ itera handlers registrados
→ MetricsCommandHandler.can_handle("/metrics") → True
→ retorna MetricsCommandHandler
MetricsCommandHandler.execute(agent, "/metrics")
→ metrics = agent.get_metrics()
→ renderer.render_metrics(metrics)
4. Processamento de Chat (Streaming)¶
ChatCommandHandler.execute(agent, "Olá")
→ asyncio.run(_async_execute(agent, "Olá"))
→ renderer.render_thinking_indicator()
→ response = await agent.chat("Olá")
→ async for token in response:
→ renderer.render_assistant_token(token)
→ renderer.clear_thinking_indicator()
🎨 Princípios Arquiteturais¶
Single Responsibility (SRP)¶
Cada classe tem uma responsabilidade única:
ChatCLIApplication: OrquestraçãoCommandRegistry: Registro e resoluçãoTerminalRenderer: RenderizaçãoCommandHandler: Processamento de comando específico
Open/Closed (OCP)¶
Aberto para extensão via novos handlers:
# Adicionar novo comando sem modificar código existente
class CustomCommandHandler(CommandHandler):
def can_handle(self, user_input: str) -> bool:
return user_input.startswith('/custom')
def execute(self, agent, user_input):
# Implementação customizada
pass
def get_aliases(self):
return ['/custom']
# Registrar
registry.register(CustomCommandHandler(renderer))
Dependency Inversion (DIP)¶
Handlers dependem de abstrações (CommandHandler), não implementações concretas.
Command Pattern¶
Cada handler encapsula uma ação como objeto, permitindo:
- Parametrização de clientes com diferentes solicitações
- Enfileiramento de solicitações
- Suporte a operações reversíveis
🛠️ Adicionando Novos Comandos¶
Passo 1: Criar Handler¶
# src/createagents/presentation/cli/commands/my_command.py
from .base_command import CommandHandler
class MyCommandHandler(CommandHandler):
def can_handle(self, user_input: str) -> bool:
return self._normalize_input(user_input) in self.get_aliases()
def execute(self, agent: 'CreateAgent', user_input: str) -> None:
# Sua lógica aqui
result = self._my_logic(agent)
self._renderer.render_system_message(result)
def get_aliases(self) -> List[str]:
return ['/mycommand', 'mycommand']
def _my_logic(self, agent):
# Implementação
return "Resultado do comando"
Passo 2: Registrar Handler¶
# src/createagents/presentation/cli/application/chat_cli_app.py
def _setup_commands(self) -> None:
# ...outros comandos...
self._registry.register(MyCommandHandler(self._renderer))
# ChatCommandHandler deve ser sempre último
self._registry.register(ChatCommandHandler(self._renderer))
Passo 3: Exportar (Opcional)¶
# src/createagents/presentation/cli/commands/__init__.py
from .my_command import MyCommandHandler
__all__ = [
# ...outros...
'MyCommandHandler',
]
📊 Testabilidade¶
A arquitetura permite fácil testabilidade:
import pytest
from unittest.mock import Mock
def test_help_command_handler():
# Mock renderer
mock_renderer = Mock()
handler = HelpCommandHandler(mock_renderer)
# Mock agent
mock_agent = Mock()
# Test can_handle
assert handler.can_handle("/help") == True
assert handler.can_handle("other") == False
# Test execute
handler.execute(mock_agent, "/help")
mock_renderer.render_system_message.assert_called_once()
💡 Best Practices¶
- Handler Registration Order: Específico → Genérico
- ChatCommandHandler Last: Sempre registre como último (fallback)
- Use Renderer: Nunca faça
print()diretamente, useself._renderer - Normalize Input: Use
_normalize_input()para comparações - Async Awareness: Chat é assíncrono, use
asyncio.run()se necessário
📚 Próximos Passos¶
Versão: 0.1.3 | Atualização: 01/12/2025