Documentação oficial desta página · v0.20.6
Painel Web do Hermes
Painel Web do Hermes
O painel web é uma interface de navegador para gerenciar sua instalação do Hermes Agent. Em vez de editar arquivos YAML ou rodar comandos de CLI, você configura ajustes, gerencia chaves de API e acompanha sessões por uma interface web limpa.
A autenticação no modo hospedado usa o OAuth do Nous Portal; se você também quer que o painel converse com um backend de verdade, o hermes setup --portal também liga o modelo e o tool gateway. Veja Nous Portal.
Início rápido
hermes dashboard
Isso sobe um servidor web local e abre o http://127.0.0.1:9119 no seu navegador. O painel roda inteiramente na sua máquina — nenhum dado sai do localhost.
Opções
| Flag | Padrão | Descrição |
|---|---|---|
--port | 9119 | Porta em que o servidor web roda |
--host | 127.0.0.1 | Endereço de escuta |
--no-open | — | Não abre o navegador sozinho |
--insecure | desligado | Obsoleta / sem efeito. Antes ela pulava a autenticação num bind fora do loopback; hoje não desliga mais a autenticação — um bind público sempre exige um provedor de autenticação (senha ou OAuth) |
--isolated | desligado | Quando iniciado a partir de um perfil nomeado (worker dashboard), roda um servidor dedicado por perfil em vez de rotear para o painel da máquina |
# Porta personalizada
hermes dashboard --port 8080
# Escutar em todas as interfaces (cuidado em redes compartilhadas)
hermes dashboard --host 0.0.0.0
# Iniciar sem abrir o navegador
hermes dashboard --no-open
Gerenciar vários perfis
O painel é uma superfície de gerenciamento no nível da máquina: um servidor gerencia todo perfil da máquina. Um seletor de perfil na barra lateral (visível sempre que existe mais de um perfil) decide qual perfil as páginas de gerenciamento leem e escrevem — Config, Chaves de API, Skills, MCP, Modelos e a aba Chat seguem todos ele. Enquanto um perfil diferente do próprio painel está selecionado, um aviso âmbar nomeia o perfil gerenciado, para o alvo de escrita nunca ser ambíguo.
A seleção fica na URL (?profile=<name>), então links diretos como
http://127.0.0.1:9119/skills?profile=worker chegam com o seletor
pré-selecionado e sobrevivem a um recarregamento.
Iniciar o painel a partir de um apelido de perfil roteia para o painel da máquina em vez de subir um segundo servidor:
worker dashboard
# → já rodando: abre o navegador em ?profile=worker
# → não rodando: sobe o painel da máquina com "worker" pré-selecionado
Passe --isolated para sair disso e rodar um servidor dedicado ao escopo daquele
perfil (o comportamento anterior à unificação — útil se você expõe de propósito
os painéis de perfis diferentes com autenticações diferentes).
A aba Chat também segue o seletor: um chat com escopo gera o processo filho
de PTY com o HERMES_HOME do perfil selecionado, então a conversa roda com o
modelo, as skills, a memória e o histórico de sessão daquele perfil. Trocar de
perfil inicia uma sessão de terminal nova.
O que continua por perfil e não é absorvido pelo seletor: processos de gateway
(gerencie-os por hermes -p <name> gateway …), o banco de sessões de cada perfil
e os agendadores de cron (a página Cron já agrega todos os perfis, com filtro próprio).
Pré-requisitos
A instalação padrão do hermes-agent não traz a pilha HTTP nem o auxiliar de PTY — eles são extras opcionais. O painel web precisa do FastAPI e do Uvicorn (extra web). A aba Chat também precisa do ptyprocess para gerar a TUI embutida atrás de um pseudoterminal (extra pty em sistemas POSIX). Instale os dois com:
cd ~/.hermes/hermes-agent && uv pip install -e ".[web,pty]"
O extra web puxa FastAPI/Uvicorn; o pty puxa ptyprocess (POSIX) ou pywinpty (Windows nativo — note que a TUI embutida em si ainda exige WSL). O cd ~/.hermes/hermes-agent && uv pip install -e ".[all]" inclui os dois extras e é o caminho mais fácil se você também quer mensagens/voz/etc.
Quando você roda o hermes dashboard sem as dependências, ele avisa o que instalar. Se o frontend ainda não foi compilado e o npm está disponível, a compilação acontece sozinha no primeiro início.
A aba Chat faz parte de todo hermes dashboard — o painel de chat embutido no navegador (rodando a TUI sobre PTY/WebSocket) está sempre disponível, sem precisar de flag extra.
Páginas
Status
A página inicial mostra um panorama ao vivo da sua instalação:
- Versão do agente e data de lançamento
- Estado do gateway — rodando/parado, PID, plataformas conectadas e o estado delas
- Sessões ativas — quantidade de sessões ativas nos últimos 5 minutos
- Sessões recentes — lista das 20 sessões mais recentes com modelo, contagem de mensagens, uso de tokens e uma prévia da conversa
A página de status se atualiza sozinha a cada 5 segundos.
Aviso de pressão de recursos
Quando a máquina está com pouca memória ou pouco disco, um aviso aparece no topo do painel (alimentado pela mesma consulta de status — sem requisições extras):
- "Your agent is almost out of memory and may restart" — a memória disponível do sistema caiu para o nível elevado (< 128 MiB ou < 15%) ou crítico (< 64 MiB ou < 5%), conforme amostrado pelo batimento de 30 segundos do gateway.
- "Your agent restarted unexpectedly, most likely because it ran out of memory" — o registro de ciclo de vida guardou uma saída suja sob pressão de memória no boot anterior (uma suspeita de OOM kill).
- Avisos de disco — o volume que guarda o
~/.hermesestá quase cheio (elevado abaixo de 512 MB livres, crítico abaixo de 256 MB livres).
Só o aviso ativo mais grave aparece de cada vez (disco crítico > memória crítica > reinício por OOM > disco elevado > memória elevada). As dispensas valem só para o boot atual do gateway: dispensar um aviso traz o próximo aviso ativo, um reinício do gateway ou um agravamento (elevado → crítico) o reabre, e um batimento velho não renderiza nada, em vez de dar um alerta falso.
Chat
A aba Chat embute a TUI completa do Hermes (a mesma interface que você tem com hermes --tui) direto no navegador. Tudo que você faz na TUI do terminal — comandos de barra, seletor de modelo, cartões de chamada de ferramenta, streaming de markdown, avisos de clarify/sudo/aprovação, temas de skin — funciona igual aqui, porque o painel está rodando o binário real da TUI e renderizando a saída ANSI dele pelo xterm.js com o renderizador WebGL, para um layout de células perfeito no pixel.
Como funciona:
- O
/api/ptyabre um WebSocket autenticado com o token de sessão do painel - O servidor gera o
hermes --tuiatrás de um pseudoterminal POSIX - As teclas viajam até o PTY; a saída ANSI volta em streaming para o navegador
- O renderizador WebGL do xterm.js pinta cada célula numa grade de pixels inteiros; rastreamento de mouse (SGR 1006), caracteres largos (Unicode 11) e glifos de desenho de caixa renderizam nativamente
- Redimensionar a janela do navegador redimensiona a TUI pelo complemento
@xterm/addon-fit
Retomar uma sessão existente: na aba Sessões, clique no ícone de play (▶) ao lado de qualquer sessão. Isso salta para /chat?resume=<id> e inicia a TUI com --resume, carregando o histórico completo.
Seletor de sessões (trilho à direita): a aba Chat tem a própria lista de conversas em estilo ChatGPT, num trilho fino à direita do terminal, para você trocar de conversa sem sair da página. O trilho empilha o seletor de modelo no topo e a lista de sessões logo abaixo; o terminal ocupa quase toda a tela. A lista mostra suas sessões mais recentes do perfil ativo — título (com prévia da mensagem como reserva), tempo relativo desde a última atividade, contagem de mensagens e o canal de origem das sessões que não vêm da CLI. Clique em qualquer linha para retomá-la ali mesmo (o terminal renasce com o histórico daquela conversa); a sessão ativa fica destacada. Nova conversa inicia uma sessão em branco, e um controle de atualização recarrega a lista. O trilho é só de leitura, para troca — apagar, renomear, exportar e limpeza em lote continuam na aba Sessões. Em telas estreitas ele vira um painel deslizante.
Pré-requisitos:
- Node.js (o mesmo requisito do
hermes --tui; o pacote da TUI é compilado no primeiro início) ptyprocess— instalado pelo extrapty(cd ~/.hermes/hermes-agent && uv pip install -e ".[web,pty]", ou o[all], que cobre os dois)- Kernel POSIX (Linux, macOS ou WSL2). O painel de terminal do
/chatprecisa especificamente de um PTY POSIX — o Python nativo do Windows não tem equivalente, então numa instalação nativa de Windows o resto do painel (sessões, tarefas, métricas, editor de configuração) funciona, mas a aba/chatmostra um aviso pedindo que você use o WSL2 para esse recurso.
Feche a aba do navegador e o PTY é encerrado com limpeza no servidor. Reabrir gera uma sessão nova.
Para apontar o Hermes Desktop para um painel rodando em outra máquina, em vez do backend embutido dele, veja a seção de backend remoto abaixo.
Conectar o Hermes Desktop a um backend remoto
Normalmente o Hermes Desktop inicia o próprio backend local, mas ele também pode se ligar a um painel rodando numa máquina remota (uma VM, uma máquina do seu laboratório caseiro, etc.) por Settings → Gateways → Remote gateway. Essa é a origem mais comum dos relatos de "o Desktop diz que o backend está pronto mas o chat nunca funciona", porque a verificação de prontidão do Desktop confere menos coisas do que a conexão de chat ao vivo precisa.
hermes dashboard precisa estar rodando na máquina remotaO "backend remoto" ao qual o Desktop se conecta é um processo hermes dashboard rodando na máquina remota — o mesmo servidor que esta página documenta. Ele tem que estar no ar e alcançável antes de qualquer passo abaixo importar; o Desktop se liga a ele, não o inicia para você. Mantenha-o rodando sob systemd/tmux/etc., para sobreviver a logout e reinício. O gateway (Telegram/Discord/Slack/etc.) é um processo de longa duração separado — inicie-o por conta própria se você depende dos canais de mensagem; não é a ele que o aplicativo de desktop se conecta.
A sonda de "o backend remoto está pronto" do Desktop só bate no GET /api/status, que é um endpoint público — ele responde assim que qualquer painel está rodando na máquina. A conexão de chat ao vivo é um WebSocket separado para /api/ws (e /api/pty), e esse socket é barrado por mais duas verificações que a sonda de status nunca toca:
- Você precisa estar autenticado. Quando o painel escuta num endereço fora do loopback, ele liga a trava de autenticação. Proteja-o com um usuário e uma senha (o provedor de usuário/senha incluído); o Desktop entra uma vez e reaproveita a sessão resultante no WebSocket por um tíquete de uso único. Sem um provedor configurado, um painel fora do loopback falha fechado no início.
- O host de escuta precisa permitir o cliente e bater com o cabeçalho Host. Um bind de loopback (
127.0.0.1) só aceita clientes de loopback, então uma máquina remota é rejeitada na camada de socket, credenciais à parte. Escute num endereço fora do loopback (--host 0.0.0.0) para a trava de IP do par deixar o cliente remoto passar. A URL remota que você digita no Desktop precisa alcançar o painel pelo mesmo host em que ele escuta — a proteção contra DNS rebinding exige que o cabeçalho Host bata.
Configurar o painel remoto
Defina um usuário e uma senha, depois rode o painel escutando num endereço alcançável. Para um serviço do systemd:
[Service]
EnvironmentFile=%h/.hermes/.env
ExecStart=/caminho/do/venv/bin/python -m hermes_cli.main dashboard \
--host 0.0.0.0 --port 9119 --no-open
com o ~/.hermes/.env contendo:
HERMES_DASHBOARD_BASIC_AUTH_USERNAME=admin
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=escolha-uma-senha-forte
HERMES_DASHBOARD_BASIC_AUTH_SECRET=<32+ bytes aleatórios; openssl rand -base64 32>
Depois, no Desktop, digite a Remote URL (por exemplo, http://VM_IP:9119) e clique em Sign in com esse usuário e essa senha. Veja a seção do provedor de usuário/senha para a superfície completa de configuração.
De qualquer máquina, verifique se o painel anuncia o provedor de usuário/senha:
curl -s http://VM_IP:9119/api/status | jq '.auth_required, .auth_providers'
# true
# ["basic"]
auth_required: truee"basic"na lista de provedores → o fluxo de Sign in do Desktop vai funcionar.auth_required: false→ o bind é de loopback, ou a trava não engatou. Escute num endereço fora do loopback.auth_required: truemas sem o provedor"basic"→ as variáveis de ambiente de usuário/senha não foram carregadas. Corrija isso primeiro.
Se o /api/status mostra a trava ligada com o provedor "basic" e o Desktop ainda assim não conecta depois do login, o problema está além da configuração básica — pegue um desktop.log novo (Settings → Gateways → Open logs) mais os logs do painel da mesma janela de tentativa e procure o código de fechamento do /api/ws (4403 = WS de chat rejeitado pela trava de requisição, por exemplo divergência de Host/par; 4401 = o tíquete do WS não autenticou).
Config
Um editor em formulário para o config.yaml. Todos os mais de 150 campos de configuração são descobertos sozinhos a partir do DEFAULT_CONFIG e organizados em categorias com abas:

- model — modelo padrão, provedor, URL base, ajustes de raciocínio
- terminal — backend (local/docker/ssh/modal), tempo limite, preferências de shell
- display — skin, progresso das ferramentas, exibição ao retomar, ajustes do indicador de atividade
- agent — máximo de iterações, tempo limite do gateway, camada de serviço
- delegation — limites de subagentes, esforço de raciocínio
- memory — escolha de provedor, ajustes de injeção de contexto
- approvals — modo de aprovação de comandos perigosos (smart/manual/off)
- E mais — toda seção do config.yaml tem campos de formulário correspondentes
Campos com valores válidos conhecidos (backend de terminal, skin, modo de aprovação, etc.) viram listas suspensas. Booleanos viram interruptores. Todo o resto é um campo de texto.
Ações:
- Save — grava as mudanças no
config.yamlna hora - Reset to defaults — devolve todos os campos aos valores padrão (não salva até você clicar em Save)
- Export — baixa a configuração atual em JSON
- Import — envia um arquivo JSON de configuração para substituir os valores atuais
As mudanças de configuração valem na próxima sessão do agente ou no próximo reinício do gateway. O painel web edita o mesmo arquivo config.yaml que o hermes config set e o gateway leem.
Chaves de API
Gerencia o arquivo .env onde ficam as chaves de API e as credenciais. As chaves são agrupadas por categoria:
- Provedores de LLM — OpenRouter, Anthropic, OpenAI, DeepSeek, etc.
- Chaves de API de ferramentas — Browserbase, Firecrawl, Tavily, ElevenLabs, etc.
- Plataformas de mensagens — tokens de bot de Telegram, Discord, Slack, etc.
- Ajustes do agente — variáveis de ambiente não secretas, como
API_SERVER_ENABLED
Cada chave mostra:
- Se ela está definida agora (com uma prévia ocultada do valor)
- Uma descrição do que ela serve
- Um link para a página de cadastro/chave do provedor
- Um campo para definir ou atualizar o valor
- Um botão de exclusão para removê-la
Chaves avançadas ou pouco usadas ficam escondidas por padrão atrás de um interruptor.
Sessões
Navegue e inspecione todas as sessões do agente. Cada linha mostra o título da sessão, o ícone da plataforma de origem (CLI, Telegram, Discord, Slack, cron), o nome do modelo, a contagem de mensagens, a contagem de chamadas de ferramenta e há quanto tempo ela esteve ativa. Sessões ao vivo ganham um selo pulsante.
- Filtro — as abas Chats / Automação / Todas delimitam a lista: Chats (o padrão) mostra as conversas humanas e esconde o ruído de automação (sessões de cron, ferramenta, API e ACP); Automação mostra só essas; Todas mostra tudo. Uma lista suspensa de origem exata estreita ainda mais, até um único canal (só Telegram, por exemplo). A busca respeita o filtro ativo.
- Busca — busca em texto completo por todo o conteúdo das mensagens, usando FTS5. Os resultados mostram trechos destacados e rolam sozinhos até a primeira mensagem correspondente quando você expande.
- Estatísticas — uma barra de resumo mostra o total de sessões, quantas estão ativas no repositório, quantas foram arquivadas, o total de mensagens e uma divisão por origem.
- Expandir — clique numa sessão para carregar o histórico completo de mensagens. As mensagens são coloridas por papel (user, assistant, system, tool) e renderizadas como Markdown com destaque de sintaxe.
- Chamadas de ferramenta — mensagens do assistente com chamadas de ferramenta mostram blocos recolhíveis com o nome da função e os argumentos em JSON.
- Renomear — defina ou limpe o título de uma sessão ali mesmo (ícone de lápis).
- Exportar — baixe uma sessão (metadados + histórico completo de mensagens) em JSON (ícone de download).
- Podar — o botão "Prune old sessions" do cabeçalho apaga sessões encerradas com mais de N dias.
- Apagar — remova uma sessão e o histórico de mensagens dela com o ícone da lixeira.

Logs
Veja os arquivos de log do agente, do gateway e de erros, com filtro e acompanhamento ao vivo.
- File — alterne entre os arquivos de log
agent,errorsegateway - Level — filtre por nível de log: ALL, DEBUG, INFO, WARNING ou ERROR
- Component — filtre pelo componente de origem: all, gateway, agent, tools, cli ou cron
- Lines — escolha quantas linhas exibir (50, 100, 200 ou 500)
- Auto-refresh — ligue o acompanhamento ao vivo, que busca novas linhas de log a cada 5 segundos
- Colorido — as linhas de log são coloridas por gravidade (vermelho para erros, amarelo para avisos, apagado para depuração)
Análises
Análises de uso e custo calculadas a partir do histórico de sessões. Escolha um período (7, 30 ou 90 dias) para ver:
- Cartões de resumo — total de tokens (entrada/saída), percentual de acerto de cache, custo total estimado ou real, e total de sessões com a média diária
- Gráfico diário de tokens — gráfico de barras empilhadas mostrando o uso de tokens de entrada e saída por dia, com dicas ao passar o mouse mostrando a divisão e o custo
- Tabela de divisão diária — data, contagem de sessões, tokens de entrada, tokens de saída, taxa de acerto de cache e custo de cada dia
- Divisão por modelo — tabela mostrando cada modelo usado, a contagem de sessões dele, o uso de tokens e o custo estimado
Cron
Crie e gerencie tarefas de cron agendadas que rodam prompts do agente numa cadência recorrente.
- Criar — preencha um nome (opcional), o prompt, a expressão de cron (por exemplo,
0 9 * * *) e o destino de entrega (local, Telegram, Discord, Slack ou e-mail) - Lista de tarefas — cada tarefa mostra o nome, uma prévia do prompt, a expressão de agendamento, o selo de estado (habilitada/pausada/erro), o destino de entrega, a hora da última execução e a da próxima
- Pausar / Retomar — alterna uma tarefa entre ativa e pausada
- Editar — abre uma janela pré-preenchida para mudar o prompt, o agendamento, o nome ou o destino de entrega de uma tarefa
- Disparar agora — executa uma tarefa na hora, fora do agendamento normal
- Apagar — remove uma tarefa de cron em definitivo
Perfis
Crie e gerencie perfis — instâncias isoladas do Hermes com configuração, skills e sessões próprias.
- Cartões de perfil — cada um mostra o modelo/provedor, a contagem de skills, o estado do gateway, a descrição e os selos (ativo, padrão, apelido)
- Criar — nome + opções de clonar-do-padrão / clonar-tudo / sem-skills-embutidas, descrição e modelo; a página dedicada de Construtor de Perfil (
/profiles/new) oferece o fluxo completo (modelo, MCPs, skills) - Gerenciar skills e ferramentas — salta para a página de Skills com escopo naquele perfil (ajusta o seletor de perfil da barra lateral)
- Definir como ativo — muda o padrão fixo que as futuras execuções de CLI/gateway vão pegar (igual ao
hermes profile use). Isso não muda o que o painel gerencia — essa é a função do seletor de perfil - Editar modelo / descrição / SOUL — editores embutidos que escrevem naquele perfil
- Renomear / Apagar — só para perfis nomeados
Skills
Navegue, busque e ligue/desligue skills e conjuntos de ferramentas instalados, e instale novos a partir do hub. As skills são carregadas de ~/.hermes/skills/ e agrupadas por categoria.
- Busca — filtre as skills e os conjuntos de ferramentas instalados por nome, descrição ou categoria
- Filtro de categoria — clique nas pílulas de categoria para estreitar a lista (por exemplo, MLOps, MCP, Red Teaming, IA)
- Ligar/desligar — habilite ou desabilite skills individuais com um interruptor. As mudanças valem na próxima sessão.
- Conjuntos de ferramentas — uma visão separada mostra os conjuntos embutidos (operações de arquivo, navegação na web, etc.) com o estado ativo/inativo, os requisitos de configuração e a lista de ferramentas incluídas
- Navegar no hub — uma terceira visão busca no hub de skills em todas as fontes (o mesmo que o
hermes skills search), instala qualquer resultado pelo identificador com um log de instalação ao vivo, e oferece um botão "Update all" para atualizar as skills instaladas.

MCP
Gerencie servidores MCP sem a CLI. O mesmo bloco mcp_servers
do config.yaml que o hermes mcp lê.
Seus servidores MCP:
- Adicionar — registre um servidor HTTP/SSE (URL) ou um servidor stdio (comando + argumentos), com variáveis de ambiente
KEY=VALUEopcionais para servidores stdio - Habilitar / desabilitar — ligue ou desligue um servidor sem apagá-lo. Um servidor desabilitado continua na configuração, para você reabilitá-lo depois. Vale no próximo reinício do gateway.
- Testar — conecta ao servidor, lista as ferramentas dele e desconecta — confere a conexão antes de o agente depender dela
- Remover — apaga um servidor da configuração
- Valores de ambiente com cara de segredo aparecem ocultados na lista
Catálogo: navegue pelos servidores MCP aprovados pela Nous (o catálogo optional-mcps/
embutido) e instale qualquer um deles com um clique. Entradas que precisam de chaves de API
pedem por elas ali mesmo; os valores vão para o .env. É o mesmo catálogo que o
hermes mcp catalog / hermes mcp install usam.

Webhooks
Gerencie assinaturas de webhook dinâmicas. A plataforma de webhook precisa estar habilitada antes nas configurações de mensagens; a página mostra um aviso quando não está.
- Criar — nome, descrição, filtro de evento, destino de entrega, modo opcional de entrega direta e um prompt de agente. Na criação, a página mostra a URL da rota e o segredo HMAC de uma vez só, para você copiar.
- Habilitar / desabilitar — ligue ou desligue uma assinatura. Rotas desabilitadas continuam no arquivo de assinaturas, mas o gateway rejeita os eventos de entrada delas (403). O gateway recarrega o arquivo a quente, então a mudança vale no próximo evento — sem reinício.
- Listar — cada assinatura mostra a URL, os eventos e o destino de entrega
- Apagar — remove uma assinatura

Pareamento
Aprove e revogue pessoas nas mensagens sem a CLI — é assim que quem administra de
longe integra pessoas de Telegram/Discord/etc. a um gateway pareado. Paridade total
com o hermes pairing.
- Pedidos pendentes — cada um mostra plataforma, código, pessoa e idade, com um botão Approve
- Pessoas aprovadas — cada uma mostra plataforma e usuário, com um botão Revoke
- Limpar pendentes — descarta todos os códigos de pareamento em aberto

Canais
Conecte o Hermes a qualquer plataforma de mensagens pelo navegador — paridade total
com o hermes setup gateway. A página lista todo canal aceito (Telegram,
Discord, Slack, Matrix, Mattermost, WhatsApp, Signal, BlueBubbles/iMessage,
E-mail, SMS/Twilio, DingTalk, Feishu/Lark, WeCom, WeChat, QQ Bot, Yuanbao, mais
o servidor de API e os endpoints de webhook) com o estado ao vivo da conexão.
- Configurar — abre um formulário por plataforma com exatamente os campos de que aquele canal precisa (token de bot, token de app, URL do servidor, lista de permissões, etc.). Segredos aparecem como campos de senha e são guardados ocultados; deixar um campo em branco mantém o valor existente. Campos obrigatórios são marcados e validados. Um link de "Setup guide" aponta para a documentação de credenciais da plataforma.
- Habilitar / desabilitar — ligue ou desligue um canal. A credencial continua no disco; só o estado ativo muda.
- Testar — confere se o canal está configurado, habilitado e relatando uma conexão ao vivo pelo gateway.
- Reiniciar o gateway — as credenciais vão para o
~/.hermes/.enve a marcação de habilitado para oconfig.yaml; o gateway conecta cada canal habilitado no próximo reinício, que você pode disparar direto da página.

Sistema
Um painel de administração consolidado para operações que valem para a instalação inteira:
- Host — estatísticas ao vivo do sistema: SO / kernel, arquitetura, nome da máquina, versões do Python e do Hermes, contagem de núcleos de CPU + utilização, memória, uso de disco da pasta do Hermes, tempo ligado e carga média. (CPU/memória/disco vêm do
psutilquando ele está instalado; os campos de identidade sempre aparecem.) A versão do Hermes traz um selo de estado de atualização (em dia / N commits atrás) e um botão Check for updates. Quando há atualização disponível numa instalação por git, um botão Update now abre uma janela de confirmação — mostrando quantos commits você vai puxar — antes de rodar ohermes updateem segundo plano. Em instalações por Docker/Nix o painel não consegue aplicar a atualização no lugar, então ele mostra o comando externo correto. - Nous Portal — estado do login, o provedor de inferência ativo e a tabela de roteamento do Tool Gateway (quais ferramentas rodam pelo Portal e quais rodam localmente), com um link para gerenciar sua assinatura. Espelho somente leitura do
hermes portal. - Curador de skills — o estado da manutenção de skills em segundo plano (ativo / pausado, intervalo, última execução) com pausar/retomar e um botão de rodar agora. Espelha o
hermes curator. - Gateway — inicie, pare e reinicie o gateway de mensagens, com estado ao vivo (rodando/parado, PID, situação)
- Memória — escolha o provedor externo de memória (ou só o embutido) e zere os repositórios embutidos
MEMORY.md/USER.md - Pool de credenciais — acrescente e remova as chaves de API rotativas que o agente usa em rodízio (por provedor). As chaves aparecem ocultadas na lista; o valor cru só chega ao agente.
- Operações — rode o
doctor, uma auditoria de segurança, crie um backup, restaure a partir de um arquivo de backup, atualize skills, mostre a divisão de tamanho do prompt de sistema, gere um dump de suporte ou migre a configuração de ajustes aposentados. Cada uma dispara uma ação em segundo plano cujo log ao vivo é transmitido para a página. - Checkpoints — veja o tamanho do repositório sombra do
/rollbacke pode-o - Ganchos de shell — liste os ganchos configurados com o estado de consentimento + executável, crie um gancho (evento, comando, filtro, tempo limite, com uma concessão de consentimento opcional) e remova um. Ganchos rodam comandos arbitrários, então o formulário de criação traz um aviso de segurança e o gancho só dispara depois do consentimento concedido.



Criar um gancho de shell (note a caixa de consentimento e o aviso de que ele roda comandos arbitrários):

O painel web lê e escreve o seu arquivo .env, que contém chaves de API e segredos. Ele escuta em 127.0.0.1 por padrão — acessível só da sua máquina local, sem login. Escutar em qualquer endereço fora do loopback (inclusive 0.0.0.0) liga a trava de autenticação: o servidor se recusa a iniciar até que um provedor de autenticação (usuário/senha ou OAuth) esteja configurado.
Comando de barra /reload
O PR do painel também acrescenta um comando de barra /reload à CLI interativa. Depois de mudar chaves de API pelo painel web (ou editando o .env direto), use o /reload numa sessão ativa de CLI para pegar as mudanças sem reiniciar:
You → /reload
Reloaded .env (3 var(s) updated)
Isso relê o ~/.hermes/.env para o ambiente do processo em execução. Útil quando você acabou de acrescentar a chave de um provedor novo pelo painel e quer usá-la na hora.
API REST
O painel web expõe uma API REST que o frontend consome. Você também pode chamar esses endpoints direto, para automação:
As famílias de endpoints de gerenciamento — /api/config, /api/env, /api/skills,
/api/tools/toolsets, /api/mcp e /api/model/{info,options,auxiliary,set} —
aceitam um parâmetro de consulta opcional ?profile=<name> (ou "profile" no
corpo JSON, nas escritas) que limita a leitura/escrita ao HERMES_HOME daquele
perfil. Omitido = o próprio perfil do painel. Nomes de perfil desconhecidos
devolvem 404. O WebSocket /api/pty aceita o mesmo parâmetro, para gerar um
chat sob o perfil selecionado.
GET /api/status
Devolve a versão do agente, o estado do gateway, os estados das plataformas e a contagem de sessões ativas.
A resposta também traz dois blocos informativos de recursos (eles nunca afetam o
veredito de saúde de components/overall):
memory— destilado do batimento de 30 segundos do gateway e do registro de ciclo de vida. Campos:pressure(ok/elevated/critical/unknown),gateway_rss_mb,system_total_mb,system_available_mb,swap_used_mb,sampled_at,boot_id,last_boot_unclean,last_boot_suspected_oom. A pressão éelevatedabaixo de 128 MiB (ou 15%) de memória de sistema disponível ecriticalabaixo de 64 MiB (ou 5%) — os mesmos níveis em que uma saída suja seguinte seria marcada como suspeita de OOM kill. Batimentos com mais de 150 segundos (ou datados no futuro) mantêm os números mas rebaixampressureparaunknown, para a última amostra de um gateway morto não se passar por leitura ao vivo.disk— uma amostra ao vivo deshutil.disk_usage()do volume que guarda o~/.hermes. Campos:pressure,free_mb,total_mb,used_percent,sampled_at. A pressão éelevatedabaixo de 512 MB livres (ou ≥85% usado com menos de 4 GB de folga) ecriticalabaixo de 256 MB livres (ou ≥95% usado com menos de 1 GB de folga).
Os dois coletores são à prova de falha: qualquer erro de amostragem rebaixa o
bloco para {"pressure": "unknown"} em vez de derrubar o endpoint de status. Os
números são grosseiros (MB inteiros, percentuais inteiros), já que o /api/status
é público.
GET /api/sessions
Devolve as 20 sessões mais recentes com metadados (modelo, contagem de tokens, marcas de tempo, prévia).
GET /api/config
Devolve o conteúdo atual do config.yaml em JSON.
GET /api/config/defaults
Devolve os valores padrão de configuração.
GET /api/config/schema
Devolve um esquema descrevendo cada campo de configuração — tipo, descrição, categoria e opções de seleção quando cabem. O frontend usa isso para renderizar o campo certo para cada item.
PUT /api/config
Salva uma configuração nova. Corpo: {"config": {...}}.
GET /api/env
Devolve todas as variáveis de ambiente conhecidas com o estado definido/não definido, valores ocultados, descrições e categorias.
PUT /api/env
Define uma variável de ambiente. Corpo: {"key": "VAR_NAME", "value": "secret"}.
DELETE /api/env
Remove uma variável de ambiente. Corpo: {"key": "VAR_NAME"}.
GET /api/sessions/{session_id}
Devolve os metadados de uma única sessão.
GET /api/sessions/{session_id}/messages
Devolve uma página limitada do histórico de mensagens, incluindo chamadas de ferramenta e marcas de tempo. Por padrão, devolve as 500 mensagens mais recentes em ordem cronológica. Use limit (máximo 500), offset e order=oldest|latest para paginar de forma explícita.
GET /api/sessions/search
Busca em texto completo pelo conteúdo das mensagens. Parâmetro de consulta: q. Devolve os IDs das sessões correspondentes com trechos destacados.
DELETE /api/sessions/{session_id}
Apaga uma sessão e o histórico de mensagens dela.
GET /api/logs
Devolve linhas de log. Parâmetros de consulta: file (agent/errors/gateway), lines (quantidade), level, component.
GET /api/analytics/usage
Devolve análises de uso de tokens, custo e sessões. Parâmetro de consulta: days (padrão 30). A resposta inclui divisões diárias e agregados por modelo.
GET /api/cron/jobs
Devolve todas as tarefas de cron configuradas com o estado, o agendamento e o histórico de execuções.
POST /api/cron/jobs
Cria uma tarefa de cron nova. Corpo: {"prompt": "...", "schedule": "0 9 * * *", "name": "...", "deliver": "local"}.
POST /api/cron/jobs/{job_id}/pause
Pausa uma tarefa de cron.
POST /api/cron/jobs/{job_id}/resume
Retoma uma tarefa de cron pausada.
POST /api/cron/jobs/{job_id}/trigger
Dispara na hora uma tarefa de cron, fora do agendamento dela.
DELETE /api/cron/jobs/{job_id}
Apaga uma tarefa de cron.
GET /api/skills
Devolve todas as skills com nome, descrição, categoria e estado de habilitação.
PUT /api/skills/toggle
Habilita ou desabilita uma skill. Corpo: {"name": "skill-name", "enabled": true}.
GET /api/tools/toolsets
Devolve todos os conjuntos de ferramentas com rótulo, descrição, lista de ferramentas e estado ativo/configurado.
Endpoints de administração
Eles alimentam as páginas de MCP, Canais, Webhooks, Pareamento e Sistema. Todos ficam atrás da
mesma trava de autenticação do resto do /api/.
| Método e caminho | Finalidade |
|---|---|
GET /api/mcp/servers | Lista os servidores MCP configurados (valores de ambiente ocultados) |
POST /api/mcp/servers | Acrescenta um servidor. Corpo: {name, url?, command?, args?, env?, auth?} |
POST /api/mcp/servers/{name}/test | Conecta, lista as ferramentas, desconecta |
PUT /api/mcp/servers/{name}/enabled | Habilita / desabilita um servidor |
DELETE /api/mcp/servers/{name} | Remove um servidor |
GET /api/mcp/catalog | Navega pelo catálogo de MCP aprovado pela Nous |
POST /api/mcp/catalog/install | Instala uma entrada do catálogo (com o ambiente exigido) |
GET /api/messaging/platforms | Lista todo canal de mensagens com estado + campos de configuração por plataforma |
PUT /api/messaging/platforms/{id} | Configura um canal. Corpo: {enabled?, env?, clear_env?} (env vai para o .env, enabled para o config.yaml) |
POST /api/messaging/platforms/{id}/test | Informa se um canal está configurado, habilitado e conectado |
GET /api/pairing | Lista as pessoas pendentes + aprovadas nas mensagens |
POST /api/pairing/approve | Aprova um código. Corpo: {platform, code} |
POST /api/pairing/revoke | Revoga uma pessoa. Corpo: {platform, user_id} |
POST /api/pairing/clear-pending | Descarta todos os códigos pendentes |
GET /api/webhooks | Lista as assinaturas + o estado de habilitação da plataforma |
POST /api/webhooks | Cria uma assinatura (devolve o segredo de uma vez só) |
DELETE /api/webhooks/{name} | Remove uma assinatura |
GET /api/credentials/pool | Lista as chaves de rotação do pool (ocultadas) |
POST /api/credentials/pool | Acrescenta uma chave. Corpo: {provider, api_key, label?} |
DELETE /api/credentials/pool/{provider}/{index} | Remove uma chave (índice começando em 1) |
GET /api/memory | Provedor ativo + provedores disponíveis + tamanho dos arquivos embutidos |
PUT /api/memory/provider | Escolhe um provedor (vazio = só o embutido) |
POST /api/memory/reset | Zera a memória embutida. Corpo: {target: all|memory|user} |
POST /api/gateway/start · /stop · /restart | Ciclo de vida do gateway (em segundo plano) |
POST /api/ops/doctor · /security-audit · /backup · /import | Diagnóstico e manutenção (em segundo plano; acompanhe por /api/actions/{name}/status) |
GET /api/ops/hooks | Ganchos de shell configurados + estado da lista de permissões |
GET /api/ops/checkpoints · POST .../prune | Inspeciona / poda o repositório do /rollback |
POST /api/ops/hooks · DELETE /api/ops/hooks | Cria / remove um gancho de shell (com trava de consentimento) |
GET /api/system/stats | Estatísticas da máquina — SO, CPU, memória, disco, tempo ligado |
GET /api/hermes/update/check | Informa se há atualização (commits atrás, método de instalação) sem aplicar. Em instalações por git que estão atrasadas, devolve também uma lista commits (sha, summary, author, at) do que mudou. ?force=1 derruba o cache de 6h |
GET /api/curator · PUT .../paused · POST .../run | Estado do curador de skills + pausar/retomar + rodar |
GET /api/portal | Autenticação do Nous Portal + roteamento do Tool Gateway (somente leitura) |
POST /api/ops/prompt-size · /dump · /config-migrate | Diagnóstico (em segundo plano) |
PUT /api/webhooks/{name}/enabled | Habilita / desabilita uma rota de webhook |
POST /api/skills/hub/install · /uninstall · /update | Ações do hub de skills (em segundo plano) |
GET /api/skills/hub/search | Busca no hub de skills em todas as fontes |
GET /api/sessions/stats | Estatísticas do repositório de sessões |
PATCH /api/sessions/{id} | Renomeia / arquiva uma sessão |
GET /api/sessions/{id}/export | Exporta uma sessão (metadados + mensagens) em JSON |
POST /api/sessions/prune | Apaga sessões encerradas com mais de N dias |
PUT /api/cron/jobs/{id} | Edita o prompt / agendamento / nome / destino de uma tarefa de cron |
Autenticação (modo com trava)
Quando o painel escuta num endereço público ou fora do loopback — qualquer coisa que não seja 127.0.0.1 / localhost —, o Hermes Agent liga uma trava de autenticação. Toda requisição precisa levar um cookie de sessão verificado, senão é devolvida à página de login. Três provedores vêm de fábrica:
- Usuário/senha — o jeito mais simples de colocar autenticação num painel auto-hospedado / on-premises / de laboratório caseiro. Sem provedor externo de identidade. Use só numa rede confiável ou atrás de uma VPN — não para exposição na internet pública.
- OAuth (Nous Portal) — para implantações hospedadas e qualquer painel alcançável pela internet pública, e o caminho recomendado para uma conexão remota do Hermes Desktop. Todo login é verificado contra a sua conta Nous, então este é o provedor adequado para uso voltado à internet.
- OIDC auto-hospedado — para trazer o seu próprio provedor de identidade via OpenID Connect padrão (Keycloak, Auth0, Okta, Google, GitHub por uma ponte OIDC, etc.). Sem envolver o Nous Portal; adequado para exposição na internet pública quando está atrás de um servidor OIDC conforme.
Painéis de operador que escutam no loopback não são afetados — sem autenticação, sem página de login.
Quando a trava engata
| Flags | Trava de autenticação | Caso de uso |
|---|---|---|
hermes dashboard (padrão — escuta em 127.0.0.1) | DESLIGADA | Desenvolvimento local |
hermes dashboard --host 0.0.0.0 | LIGADA | Remoto / produção — proteja com o provedor de usuário/senha ou com OAuth |
A trava está ligada se, e somente se, o host de escuta não é 127.0.0.1, ::1 nem localhost. Escutar em 0.0.0.0 (ou em qualquer endereço RFC1918 / de LAN) engata a trava. A flag antiga --insecure não desliga mais nada — ela é aceita por compatibilidade, mas ignorada, com um aviso.
--insecure não faz nada — ela não desliga a autenticaçãoDesde o endurecimento de junho de 2026, o --insecure não pula mais a autenticação do painel: um bind fora do loopback sempre exige um provedor de autenticação (o provedor de usuário/senha ou OAuth). Se você quer um painel sem autenticação, escute em 127.0.0.1 e chegue nele por um túnel SSH ou pelo Tailscale.
Semântica de falhar fechado
Se a trava fosse engatar mas nenhum DashboardAuthProvider está registrado (sem plugin da Nous, sem plugin próprio), o hermes dashboard se recusa a escutar, com uma mensagem de erro explícita. Não existe uma reserva do tipo "negar por padrão mas aceitar tudo" — um painel com trava mal configurado nunca inicia.
Quando você roda hermes dashboard --host 0.0.0.0 de forma interativa (num terminal de verdade) e ainda não há provedor configurado, o Hermes não só falha — ele se oferece para configurar um na hora: escolha usuário e senha (grava dashboard.basic_auth no config.yaml e você está no ar em segundos) ou OAuth (que aponta para o hermes dashboard register). Chamadas não interativas — Docker/s6, CI, execuções em pipe — pulam a pergunta e batem no erro de falhar fechado acima, então uma implantação desassistida continua nunca iniciando sem autenticação.
Provedor padrão: Nous Research
O plugin embutido plugins/dashboard_auth/nous está sempre instalado e é carregado sozinho. Ele registra sozinho um DashboardAuthProvider chamado nous quando há um client ID configurado.
Como todo login é verificado contra o Nous Portal e protegido pela sua conta Nous, o provedor Nous é o adequado para expor um painel à internet pública.
Registrar um painel
Para usar o provedor Nous você precisa de um client ID de OAuth (no formato agent:{id}). Há duas formas de conseguir um:
-
CLI —
hermes dashboard register. Rode na máquina onde o painel vive. Ele resolve o seu login Nous existente (rode ohermes setupantes, se você não estiver logado), registra um cliente OAuth auto-hospedado no Portal e grava oHERMES_DASHBOARD_OAUTH_CLIENT_IDno~/.hermes/.envpara você. Flags opcionais:--name(um rótulo legível, senão gerado automaticamente) e--redirect-uri(uma URL pública de retorno em HTTPS, para uma máquina voltada à internet).hermes dashboard register# ✓ Registered dashboard "swift_falcon"# …grava HERMES_DASHBOARD_OAUTH_CLIENT_ID em ~/.hermes/.env -
Interface gráfica — a página Local Dashboards. Abra
/local-dashboardsno Nous Portal para registrar, nomear, gerenciar e revogar painéis auto-hospedados pelo navegador. Copie o client IDagent:{id}resultante para oHERMES_DASHBOARD_OAUTH_CLIENT_ID(ambiente) ou para odashboard.oauth.client_id(config.yaml). É aqui também que você revoga um painel registrado pela CLI.
Configuração
O plugin lê de duas superfícies, e a variável de ambiente vence quando está definida e não vazia:
config.yaml — a superfície canônica:
dashboard:
oauth:
client_id: agent:01HXYZ… # obrigatório para engatar a trava
Variáveis de ambiente — sobreposições de quem opera:
| Variável de ambiente | Sobrepõe | Formato | Provisionada por |
|---|---|---|---|
HERMES_DASHBOARD_OAUTH_CLIENT_ID | dashboard.oauth.client_id | agent:{instance_id} | hermes dashboard register |
Pela convenção do Hermes Agent (o ~/.hermes/.env é só para chaves de API / segredos), o config.yaml é o lugar recomendado para definir esses valores em desenvolvimento local, on-premises e em qualquer implantação que você controle diretamente. O caminho por variável de ambiente existe para a injeção de segredos de uma plataforma de hospedagem conseguir empurrar client_ids por implantação sem ninguém precisar editar o config.yaml dentro da imagem — esse é o propósito principal dele.
Valores de ambiente vazios são tratados como não definidos, então um segredo de plataforma provisionado mas não preenchido não consegue ofuscar sem querer uma entrada válida do config.yaml.
Se nenhuma das duas fontes fornece um client_id, o plugin informa o motivo específico e o erro de falhar fechado do painel diz exatamente o que corrigir:
Refusing to bind dashboard to 0.0.0.0 — the auth gate engages on
non-loopback binds, but no auth providers are registered.
Bundled providers reported these issues:
• nous: HERMES_DASHBOARD_OAUTH_CLIENT_ID is not set (and
dashboard.oauth.client_id in config.yaml is empty). …
Configure an auth provider before exposing the dashboard:
• Password: set dashboard.basic_auth.username + password_hash in config.yaml
• OAuth: run `hermes dashboard register` (Nous Portal) or install a
DashboardAuthProvider plugin.
There is no unauthenticated public-bind option — to keep it local, bind
127.0.0.1 and tunnel in (SSH / Tailscale).
Exemplo prático: Nous Research
De uma instalação do Hermes já logada a um painel com trava da Nous em três passos.
1. Faça login e registre o painel. O hermes dashboard register usa o seu login Nous existente para provisionar um cliente OAuth e grava o HERMES_DASHBOARD_OAUTH_CLIENT_ID no ~/.hermes/.env para você:
hermes setup # se você ainda não estiver logado no Nous Portal
hermes dashboard register
# ✓ Registered dashboard "swift_falcon"
# …grava HERMES_DASHBOARD_OAUTH_CLIENT_ID em ~/.hermes/.env
2. Rode o painel num endereço alcançável. Um bind fora do loopback engata a trava de OAuth, e o client_id recém-gravado ativa o provedor nous:
hermes dashboard --host 0.0.0.0 --port 9119 --no-open
3. Faça login. Abra http://<host>:9119/ e você será mandado para o /login. Clique em Sign in with Nous Research → autentique no Portal → volte para o painel autenticado. Confirme a trava de qualquer máquina:
curl -s http://<host>:9119/api/status | jq '.auth_required, .auth_providers'
# true
# ["nous"]
O GET /api/auth/me então devolve a sessão verificada (provider: nous). Numa máquina voltada à internet, registre com --redirect-uri https://hermes.example.com/auth/callback e defina o HERMES_DASHBOARD_PUBLIC_URL para o retorno de OAuth resolver na sua URL pública (veja Sobreposição de URL pública).
Provedor de usuário/senha (sem IDP OAuth)
Se você não quer montar um provedor de identidade OAuth — uma implantação auto-hospedada do tipo "só quero uma senha no meu painel" —, o plugin embutido plugins/dashboard_auth/basic registra um DashboardAuthProvider chamado basic que autentica com usuário e senha em vez de um redirecionamento OAuth.
Ele se encaixa na mesma trava do provedor OAuth: a trava engata num bind fora do loopback, a página de login renderiza um formulário de credenciais para esse provedor (em vez de um botão "Log in with X"), e tudo depois do login — cookies de sessão, renovação transparente, tíquetes de WS, logout, o log de auditoria — é idêntico ao caminho OAuth. As sessões são tokens sem estado assinados por HMAC que o próprio provedor emite, então não há banco de dados nem IDP externo. O hash de senha usa o scrypt da biblioteca padrão (sem dependência de terceiros).
O provedor de usuário/senha é pensado para painéis auto-hospedados / on-premises / de laboratório caseiro numa rede confiável, ou alcançáveis só por VPN. Ele protege uma única credencial compartilhada, sem provedor externo de identidade, MFA nem contas por pessoa por trás, então não é adequado para expor um painel direto à internet pública. Para um painel voltado à internet, use o provedor Nous Research (ou o seu próprio provedor de OIDC auto-hospedado / OAuth personalizado).
Configuração
Como o provedor Nous, ele lê do config.yaml (canônico), com as variáveis de ambiente vencendo quando definidas e não vazias. Ele só ativa quando username mais password_hash (preferido) ou password estão configurados — senão não faz nada, então quem usa OAuth e quem opera no loopback não é afetado.
config.yaml:
dashboard:
basic_auth:
username: admin
# Preferido — nada em texto puro em repouso. Calcule com:
# python -c "from plugins.dashboard_auth.basic import hash_password; print(hash_password('PW'))"
password_hash: "scrypt$16384$8$1$…$…"
# ...ou uma senha em texto puro (com hash em memória no carregamento; menos seguro em repouso):
# password: "s3cret"
secret: "<32+ bytes aleatórios, base64 ou hex>" # chave de assinatura dos tokens
session_ttl_seconds: 43200 # opcional; validade do token de acesso (padrão 12h)
Sobreposições por ambiente:
| Variável de ambiente | Sobrepõe | Notas |
|---|---|---|
HERMES_DASHBOARD_BASIC_AUTH_USERNAME | dashboard.basic_auth.username | obrigatória para ativar |
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH | dashboard.basic_auth.password_hash | preferida (sem texto puro em repouso) |
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD | dashboard.basic_auth.password | texto puro; vence um password_hash da configuração, para você poder rotacionar pelo ambiente |
HERMES_DASHBOARD_BASIC_AUTH_SECRET | dashboard.basic_auth.secret | chave de assinatura dos tokens |
HERMES_DASHBOARD_BASIC_AUTH_TTL_SECONDS | dashboard.basic_auth.session_ttl_seconds | validade do token de acesso |
secret explícito para sessões estáveisQuando o secret está vazio, uma chave de assinatura aleatória é gerada por processo. Isso serve para um processo só, mas significa que toda sessão é invalidada no reinício e que as sessões não atravessam vários workers. Defina um secret explícito em implantações que sobrevivem a reinício ou usam vários workers.
O endpoint /auth/password-login tem limite de taxa por IP de cliente (padrão de 10 tentativas por minuto → HTTP 429) e devolve um único 401 Invalid credentials genérico tanto para usuário desconhecido quanto para senha errada, então ele não serve de oráculo para enumerar nomes de usuário.
Exemplo prático: usuário/senha
Do zero a um painel com trava de senha numa rede confiável, em três passos.
1. Defina as credenciais em ~/.hermes/.env. Gere o hash da senha para não deixar texto puro em repouso, e defina um segredo de assinatura estável para as sessões sobreviverem a reinícios:
# Calcule um hash scrypt da senha que você escolheu:
HASH=$(python -c "from plugins.dashboard_auth.basic import hash_password; print(hash_password('escolha-uma-senha-forte'))")
cat >> ~/.hermes/.env <<EOF
HERMES_DASHBOARD_BASIC_AUTH_USERNAME=admin
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH=$HASH
HERMES_DASHBOARD_BASIC_AUTH_SECRET=$(openssl rand -base64 32)
EOF
chmod 600 ~/.hermes/.env
2. Rode o painel num endereço alcançável. Um bind fora do loopback engata a trava, e o usuário + hash ativam o provedor basic:
hermes dashboard --host 0.0.0.0 --port 9119 --no-open
3. Faça login. Abra http://<host>:9119/ e você será mandado para o /login — um formulário de credenciais (não um botão "Sign in with X"). Digite admin / a sua senha → chegue ao painel autenticado. Confirme a trava de qualquer máquina:
curl -s http://<host>:9119/api/status | jq '.auth_required, .auth_providers'
# true
# ["basic"]
O GET /api/auth/me então devolve a sessão verificada (provider: basic). Mantenha isto atrás de uma VPN — veja o aviso acima; para uma máquina pública, use o provedor Nous Research ou o de OIDC auto-hospedado.
Escrever o seu próprio provedor de senha
O basic é só uma implementação de um ponto de extensão. Qualquer plugin pode registrar um provedor de senha: defina supports_password = True na sua subclasse de DashboardAuthProvider e implemente complete_password_login(*, username, password) -> Session (levante InvalidCredentialsError ao rejeitar, ProviderError se o seu repositório de apoio estiver fora do ar). Os métodos OAuth start_login / complete_login podem ficar como esboços com NotImplementedError num provedor só de senha. Esse é o caminho para bind LDAP, um banco de credenciais ou qualquer outro esquema de autenticação sem redirecionamento — o framework cuida do formulário, da rota, dos cookies e da renovação para você.
Provedor de OIDC auto-hospedado
Se você roda o seu próprio provedor de identidade, o plugin embutido plugins/dashboard_auth/self_hosted autentica o painel contra ele usando OpenID Connect padrão — sem código por IDP, sem envolver o Nous Portal. Ele é verificado contra e funciona com qualquer servidor OIDC conforme:
Authentik · Keycloak · Zitadel · Authelia · Auth0 · Okta · Google · …
Como o provedor Nous, ele carrega sozinho e só se registra depois de configurado, então não faz nada em painéis de loopback.
Configuração
Configure um issuer e um client_id (um cliente público com PKCE — sem client secret). O plugin busca o authorization_endpoint, o token_endpoint e o jwks_uri do IDP em {issuer}/.well-known/openid-configuration, então você nunca fixa URLs de endpoint no código.
config.yaml — a superfície canônica:
dashboard:
oauth:
provider: self-hosted
self_hosted:
issuer: https://auth.example.com/application/o/hermes/ # obrigatório
client_id: hermes-dashboard # obrigatório
scopes: "openid profile email" # opcional (este é o padrão)
Variáveis de ambiente — sobreposições de quem opera (o ambiente vence o config.yaml quando definido e não vazio; um valor vazio conta como não definido):
| Variável de ambiente | Sobrepõe | Notas |
|---|---|---|
HERMES_DASHBOARD_OIDC_ISSUER | dashboard.oauth.self_hosted.issuer | URL do issuer OIDC — obrigatória |
HERMES_DASHBOARD_OIDC_CLIENT_ID | dashboard.oauth.self_hosted.client_id | ID de cliente público — obrigatória |
HERMES_DASHBOARD_OIDC_SCOPES | dashboard.oauth.self_hosted.scopes | Por padrão, openid profile email |
No seu IDP, registre uma aplicação/cliente público com a concessão authorization-code + PKCE (S256) e acrescente o retorno do painel como URI de redirecionamento permitido. O retorno é <URL pública do painel>/auth/callback (veja Sobreposição de URL pública para como o painel deriva a URL pública dele atrás de um proxy).
O que ele verifica
O provedor verifica o ID token do OpenID Connect (RS256/ES256) contra o jwks_uri descoberto, com as reivindicações iss e aud presas ao issuer e ao client_id que você configurou. As reivindicações padrão do OIDC mapeiam para a sessão do painel:
| Campo da sessão | Reivindicação(ões) |
|---|---|
user_id | sub (obrigatória) |
email | email |
display_name | name → preferred_username → nickname → email |
org_id | org_id / organization, senão os groups unidos |
É o ID token que estabelece a identidade — o access token é tratado como opaco (a especificação do OIDC não exige que ele seja um JWT). As URLs de endpoint precisam ser HTTPS (http:// de loopback é permitido para IDPs de desenvolvimento local), e o issuer anunciado no documento de descoberta precisa bater com o que você configurou (uma diferença de barra no fim é tolerada). Refresh tokens, quando o IDP os emite, são usados para reautenticação silenciosa pela concessão padrão refresh_token; o logout chama o revocation_endpoint (RFC 7009) do IDP quando ele é anunciado.
Clientes confidenciais (os que têm um
client_secret) ainda não são aceitos — configure um cliente público + PKCE, que é a escolha típica para um painel voltado ao navegador.
Exemplo prático: Keycloak
O Keycloak é um dos servidores OIDC auto-hospedados mais fáceis de subir para um teste local — ele roda como um único contêiner em modo de desenvolvimento (banco em memória) e expõe uma descoberta OIDC de manual. Este passo a passo leva você do zero a um login funcionando no painel em poucos minutos.
1. Rode o Keycloak com um realm pré-configurado. Salve esta exportação de realm como realm-hermes.json — ela define um realm hermes, um cliente público com PKCE (hermes-dashboard) e uma pessoa de teste, tudo importado no boot, para não haver nada a clicar na interface de administração:
{
"realm": "hermes",
"enabled": true,
"clients": [
{
"clientId": "hermes-dashboard",
"name": "Hermes Agent Dashboard",
"enabled": true,
"publicClient": true,
"standardFlowEnabled": true,
"protocol": "openid-connect",
"redirectUris": ["http://localhost:9119/auth/callback"],
"webOrigins": ["http://localhost:9119"],
"attributes": { "pkce.code.challenge.method": "S256" }
}
],
"users": [
{
"username": "testuser",
"enabled": true,
"emailVerified": true,
"email": "testuser@example.com",
"firstName": "Test",
"lastName": "User",
"credentials": [
{ "type": "password", "value": "testpassword", "temporary": false }
]
}
]
}
Suba (Keycloak 26+), montando esse arquivo no diretório de importação:
docker run --rm -p 8080:8080 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
-v "$PWD/realm-hermes.json:/opt/keycloak/data/import/realm-hermes.json:ro" \
quay.io/keycloak/keycloak:26.0 \
start-dev --import-realm
Assim que estiver no ar, o realm anuncia a descoberta OIDC padrão em
http://localhost:8080/realms/hermes/.well-known/openid-configuration (issuer
http://localhost:8080/realms/hermes). O console de administração fica em
http://localhost:8080/ (admin / admin).
2. Aponte o painel para ele. O plugin auto-hospedado permite um issuer http:// de loopback (HTTPS é obrigatório para qualquer issuer fora do loopback), então o Keycloak local funciona como está:
export HERMES_DASHBOARD_OIDC_ISSUER="http://localhost:8080/realms/hermes"
export HERMES_DASHBOARD_OIDC_CLIENT_ID="hermes-dashboard"
export HERMES_DASHBOARD_PUBLIC_URL="http://localhost:9119"
hermes dashboard --host 0.0.0.0 --port 9119 --no-open
O HERMES_DASHBOARD_PUBLIC_URL diz ao painel que o retorno de OAuth dele é
http://localhost:9119/auth/callback — a URI de redirecionamento que o realm
registrou acima. Escutar em 0.0.0.0 (um bind fora do loopback) é o que
engata a trava de OAuth.
3. Faça login. Abra http://localhost:9119/ e você será mandado para o /login. Clique em Sign in with Self-Hosted OIDC → autentique no Keycloak como testuser / testpassword → volte para o painel autenticado. A barra lateral mostra Logged in as Test User via self-hosted, e o GET /api/auth/me devolve a sessão verificada (provider: self-hosted, email: testuser@example.com).
Se você escutar ou navegar em outro host/porta, acrescente o
…/auth/callbackdaquela origem às Valid redirect URIs do cliente, no console de administração do Keycloak (Clients → hermes-dashboard → Settings). O mesmo padrão vale para Authentik, Zitadel, Authelia e outros servidores OIDC — só mudam a URL do issuer e a interface de registro de cliente.
Sobreposição de URL pública
Por padrão, o painel reconstrói a URL de retorno do OAuth a partir da requisição — X-Forwarded-Host + X-Forwarded-Proto + X-Forwarded-Prefix (quando o uvicorn está configurado com proxy_headers=True, o que o start_server habilita sob a trava). Isso funciona de imediato atrás de um proxy reverso que define os três cabeçalhos corretamente.
Para implantações atrás de proxies reversos que não encaminham esses cabeçalhos de forma confiável (configurações manuais de nginx, ingressos on-premises, implantações com domínio personalizado e cadeias de proxy parciais), defina dashboard.public_url (ou HERMES_DASHBOARD_PUBLIC_URL) com a URL pública completa pela qual o painel é alcançado:
dashboard:
public_url: "https://dashboard.example.com/hermes"
trusted_proxies:
- "172.20.0.5"
Quando definida, a URL de retorno do OAuth vira <public_url>/auth/callback ao pé da letra — o X-Forwarded-Prefix é ignorado nesse caminho, porque quem opera declarou a URL pública de forma explícita. Isso é de propósito: empilhar o prefixo por cima duplicaria o prefixo no caso comum, em que ele já está embutido no public_url.
O nome de host do public_url também é aceito como valor exato de Host do HTTP
e de Origin do WebSocket. Isso serve a um proxy reverso que preserva o nome de
host visto pelo navegador enquanto encaminha para um painel escutando em
127.0.0.1. Curingas e correspondência por sufixo não são permitidos, então uma
máquina atacante como dashboard.example.com.evil.test continua rejeitada pela
proteção contra DNS rebinding.
Declarar um public_url fora do loopback sempre engata a trava de autenticação
do painel, mesmo quando o backend escuta no loopback. Configure um provedor de
senha ou OAuth primeiro; sem um, o Hermes falha fechado no início. Isso impede
que o token de sessão local da SPA vire um mecanismo de autenticação remota pelo
proxy. O Uvicorn também habilita o processamento de cabeçalhos de proxy nesse
modo. Proxies de loopback são considerados confiáveis automaticamente. Se o
terminador de TLS conecta de outro contêiner ou de outra máquina, acrescente o
endereço IP exato dele a dashboard.trusted_proxies, ou acrescente um CIDR
limitado para uma rede de proxy dedicada quando o endereço for dinâmico:
dashboard:
public_url: "https://dashboard.example.com/hermes"
trusted_proxies:
- "172.20.0.0/24"
Só os pares listados podem fornecer X-Forwarded-Proto e X-Forwarded-For.
O Hermes sempre preserva a confiança no loopback e rejeita *, 0.0.0.0/0 e
::/0. Confiar numa rede significa que todo contêiner ou máquina dela pode
fornecer metadados de encaminhamento, então prefira um IP exato de proxy ou uma
rede dedicada só de proxy.
# O backend continua alcançável só nesta máquina.
hermes dashboard --host 127.0.0.1 --port 9119 --no-open
Aponte o proxy reverso de TLS para http://127.0.0.1:9119 e use
a mesma origem externa em dashboard.public_url.
O Tailscale Serve é um exemplo desse formato de implantação: ele pode terminar
HTTPS restrito à tailnet num nome de host https://<machine>.<tailnet>.ts.net
enquanto encaminha para o painel de loopback. Use exatamente essa origem HTTPS
como dashboard.public_url. Ela ainda é tratada como uma origem voltada ao
navegador fora do loopback e, portanto, exige um provedor de autenticação do
painel; isso não exige tornar o serviço alcançável pela internet pública.
Mesma precedência dos outros ajustes do painel — o ambiente vence o config.yaml:
| Superfície | Caminho de sobreposição | Quando usar |
|---|---|---|
dashboard.public_url no config.yaml | HERMES_DASHBOARD_PUBLIC_URL | Desenvolvimento local / on-premises (canônico) |
Variável de ambiente HERMES_DASHBOARD_PUBLIC_URL | — | Segredos de plataforma de hospedagem / CI |
| (não definida) | — | Padrão — reconstruir a partir dos cabeçalhos X-Forwarded-* |
A validação rejeita valores sem esquema http:// / https://, sem host, ou que contenham aspas, sinais de menor/maior, espaços em branco ou caracteres de controle. Um valor malformado cai em silêncio na reconstrução por cabeçalho, para o fluxo de login continuar funcionando em vez de despachar a pessoa para uma URL hostil.
Nota: o
public_urlsobrepõe só a URL de retorno do OAuth. A marcaçãoSecuredo cookie continua controlada pelorequest.url.scheme, usando oX-Forwarded-Protoapenas quando o par que conecta é de loopback ou está listado emtrusted_proxies. Combine umpublic_urlem HTTPS com terminação de TLS e uma entrada limitada de proxy confiável quando o proxy não estiver no loopback.
Fluxo OAuth
O provedor implementa o contrato de OAuth v1 do Nous Portal — concessão authorization-code com PKCE (S256):
- A pessoa acessa
/sem cookie de sessão → a trava redireciona para/login. - A página de login mostra um botão "Continue with Nous Research" →
/auth/login?provider=nous. - O servidor guarda o estado do PKCE num cookie de vida curta e redireciona para
https://portal.nousresearch.com/oauth/authorize?…. - A pessoa autentica no Portal e chega em
/auth/callback?code=…&state=…. - O servidor troca o código por um token de acesso em
POST /api/oauth/token, verifica a assinatura do JWT contra o JWKS do Portal (/.well-known/jwks.json) e define o cookiehermes_session_at. - A pessoa é redirecionada para
/(ou para o caminho original do link direto, pelo parâmetro de consultanext=).
Tokens de acesso têm validade de 15 minutos. Não existe refresh token no contrato v1 — quando o token expira, o invólucro de fetch da SPA detecta o envelope 401 e navega a página inteira de volta para /login, refazendo o fluxo.
Cookies definidos
| Nome | Duração | Notas |
|---|---|---|
hermes_session_at | Validade do token (15 min) | HttpOnly, SameSite=Lax, Secure quando em HTTPS |
hermes_session_pkce | 10 min | HttpOnly; guarda o verificador do PKCE + a dica de provedor durante a ida e volta. SameSite=None + Secure em HTTPS (precisa sobreviver à cadeia de redirecionamento entre sites do IDP — o Chromium descarta cookies SameSite=Lax definidos num 302 numa cadeia entre sites); SameSite=Lax em HTTP de loopback |
hermes_session_rt | sem uso na v1 | Reservado para compatibilidade futura; não é escrito quando o refresh_token está vazio |
Todos os três são Path=/. Os cookies de sessão são SameSite=Lax; o cookie do PKCE é SameSite=None quando definido em HTTPS (veja a tabela). A marcação Secure é definida quando o painel é alcançado por HTTPS (detectado pelo esquema da URL da requisição — honra o X-Forwarded-Proto de um terminador de TLS acima, sob proxy_headers=True).
Sair
O widget da barra lateral mostra Logged in as <user_id…> via nous com um ícone de saída. Clicar nele faz um POST em /auth/logout, que limpa todos os cookies de autenticação do painel e redireciona de volta para /login.
Log de auditoria
Todo início de login, sucesso, falha e falha de verificação de sessão é escrito como uma linha JSON em $HERMES_HOME/logs/dashboard-auth.log. Campos sensíveis (access_token, refresh_token, code, code_verifier, state, cabeçalho Authorization) são ocultados antes do registro.
Provedores personalizados
Para encaixar um provedor OAuth que não seja o da Nous (Google, GitHub, um OIDC próprio), crie um plugin que registre um DashboardAuthProvider:
# ~/.hermes/plugins/dashboard-auth-myidp/__init__.py
from hermes_cli.dashboard_auth import DashboardAuthProvider, Session, LoginStart
class MyIdPProvider(DashboardAuthProvider):
name = "myidp"
display_name = "My Identity Provider"
def start_login(self, *, redirect_uri): ...
def complete_login(self, *, code, state, code_verifier, redirect_uri): ...
def verify_session(self, *, access_token): ...
def refresh_session(self, *, refresh_token): ...
def revoke_session(self, *, refresh_token): ...
def register(ctx):
ctx.register_dashboard_auth_provider(MyIdPProvider())
A página de login lista todos os provedores registrados; dá para empilhar vários provedores, e a pessoa escolhe um em /login.
Autenticação não interativa (por bearer token)
Além do login humano interativo (cookies de sessão + renovação), a classe abstrata DashboardAuthProvider aceita uma capacidade não interativa, de serviço para serviço, por supports_token = True + verify_token(token=...). Quando um provedor adere, um Authorization: Bearer <token> de entrada é verificado e, dando certo, um TokenPrincipal é anexado à requisição (request.state.token_principal) nos endpoints que aquele provedor marca como autenticáveis por token — sem cookie, sem redirecionamento, sem renovação.
O primeiro consumidor incluído é o provedor drain (plugins/dashboard_auth/drain): o nous-account-service provisiona um segredo por agente via HERMES_DASHBOARD_DRAIN_SECRET, e o provedor verifica os bearer tokens de entrada contra ele com uma comparação de tempo constante, registrando o /api/gateway/drain como autenticável por token. Ele falha fechado — um segredo fraco/curto (< 256 bits) é rejeitado no registro e o endpoint fica desabilitado; e não faz nada quando a variável de ambiente não está definida. Os ajustes de comportamento (scope, min_secret_chars) ficam em dashboard.drain_auth, no config.yaml.
Provedores personalizados podem implementar supports_token/verify_token do mesmo jeito para expor os próprios endpoints autenticáveis por máquina.
Confirmar que a trava está ligada
# Caminho rápido por variável de ambiente.
HERMES_DASHBOARD_OAUTH_CLIENT_ID=agent:test \
hermes dashboard --host 0.0.0.0
# Ou o equivalente pelo config.yaml (recomendado para dev local / on-premises):
#
# dashboard:
# oauth:
# client_id: agent:test
#
# e então só:
hermes dashboard --host 0.0.0.0
# Bata no /api/status para ver o estado da trava:
curl -s http://127.0.0.1:9119/api/status | jq '.auth_required, .auth_providers'
# true
# ["nous"]
A StatusPage em React do painel mostra os mesmos campos em "Web server". Um AuthWidget na barra lateral mostra a identidade atual depois que você entra.
Conectar o Hermes Desktop a um backend remoto
O Hermes Desktop consegue conduzir um backend do Hermes rodando em outra máquina (um VPS, um servidor caseiro, um Mini atrás do Tailscale). No aplicativo isso fica em Settings → Gateways → Remote gateway, que pede uma Remote URL e uma forma de fazer Sign in. (Para o aplicativo de desktop em si — instalação, ajustes, chat — veja a página do Hermes Desktop.)
Você protege o painel remoto com um dos provedores de autenticação incluídos, e o aplicativo de desktop entra usando o que o backend anunciar. Para um backend alcançável além da sua própria máquina — um VPS, uma máquina pública, qualquer coisa voltada à internet —, o provedor recomendado é o OAuth (Nous Portal) (registre-o com o hermes dashboard register e entre com Sign in with Nous Research). O provedor de usuário/senha incluído é a opção mais rápida quando o backend está numa LAN confiável ou alcançável só por VPN, mas não é adequado para exposição direta à internet pública. Fazer o painel escutar num endereço fora do loopback engata a trava de autenticação dele; depois do login, o Desktop reaproveita a sessão no WebSocket do chat sozinho — não há token para copiar e colar.
A receita abaixo usa o caminho de usuário/senha por ser o mais rápido de montar numa rede confiável; para o caminho de OAuth, veja Provedor padrão: Nous Research.
No backend (a máquina remota)
# 1. Defina as credenciais de login do painel em ~/.hermes/.env (arquivo de segredos, 0600).
cat >> ~/.hermes/.env <<'EOF'
HERMES_DASHBOARD_BASIC_AUTH_USERNAME=admin
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=escolha-uma-senha-forte
# Recomendado: um segredo de assinatura estável, para as sessões sobreviverem a reinícios.
HERMES_DASHBOARD_BASIC_AUTH_SECRET=$(openssl rand -base64 32)
EOF
chmod 600 ~/.hermes/.env
# 2. Rode o painel escutando num endereço alcançável. O bind fora do loopback
# engata a trava de autenticação; o provedor de usuário/senha cuida do login.
hermes dashboard --no-open --host 0.0.0.0 --port 9119
Prefere nada em texto puro em repouso? Use o HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH com um hash scrypt — veja Provedor de usuário/senha para a superfície completa.
Se você roda o painel como serviço do systemd, o ~/.hermes/.env é lido sozinho quando a unidade tem EnvironmentFile=%h/.hermes/.env, então as credenciais já estão no ambiente no boot.
O painel lê e escreve o seu .env (chaves de API, segredos) e pode rodar comandos do agente. A configuração de usuário/senha mostrada aqui é para uma rede confiável — nunca exponha um painel protegido por senha direto à internet aberta. Coloque atrás de uma VPN. O Tailscale é a opção limpa: escute no IP tailscale da máquina (--host <tailscale-ip>) e use http://<tailscale-ip>:9119 como Remote URL. Só os dispositivos da sua tailnet conseguem alcançar. Para alcançar um backend pela internet pública, use o provedor OAuth (Nous Portal).
No Hermes Desktop
Settings → Gateways → Remote gateway:
- Remote URL —
http://<backend-host>:9119(prefixos de caminho como/hermesfuncionam se você colocar um proxy reverso na frente) - Sign in — o aplicativo detecta o gateway de usuário/senha e mostra um botão Sign in; clique nele e digite as credenciais do passo 1
- Save and reconnect — passa a casca do desktop para o backend remoto
A sessão se renova sozinha e sobrevive a reinícios quando o HERMES_DASHBOARD_BASIC_AUTH_SECRET está definido no backend.
Sobreposição por variável de ambiente
Em vez do ajuste dentro do aplicativo, você pode apontar o desktop para um backend com uma variável de ambiente antes de iniciá-lo. Quando o HERMES_DESKTOP_REMOTE_URL está definido, ele sobrepõe a URL salva no aplicativo (o painel de ajustes de Gateway mostra um selo de "env override" e desabilita a edição); você ainda faz Sign in com o seu usuário e a sua senha pelo painel.
| Variável de ambiente | Valor |
|---|---|
HERMES_DESKTOP_REMOTE_URL | http://<backend-host>:9119 |
Solução de problemas
- "Remote gateway incomplete" — você não digitou uma URL remota.
- O login falha com 401 / "Invalid credentials" — o usuário ou a senha não bate com o
HERMES_DASHBOARD_BASIC_AUTH_USERNAME/HERMES_DASHBOARD_BASIC_AUTH_PASSWORDdo backend. O backend devolve o mesmo erro genérico para usuário desconhecido e senha errada, então confira os dois. Confirme a trava comcurl -s http://<host>:9119/api/status | jq '.auth_required, .auth_providers'— deve informartruee incluir"basic". - Não aparece o botão "Sign in" — ele pede um token de sessão — o provedor de usuário/senha não está ativo (o
/api/statusnão vai listar"basic"). Confirme que o usuário e uma senha (ou hash de senha) estão definidos e que o processo do painel os carregou. - Deslogado a cada reinício — defina o
HERMES_DASHBOARD_BASIC_AUTH_SECRETcom um valor estável; senão a chave de assinatura é regerada a cada boot. - Conexão recusada / tempo esgotado — o backend escutou em
127.0.0.1(o padrão) em vez de um endereço alcançável, ou um firewall/VPN está bloqueando a porta. Escute em0.0.0.0ou no IP tailscale e abra a porta para a sua rede confiável.
CORS
O servidor web restringe o CORS só a origens de localhost:
http://localhost:9119/http://127.0.0.1:9119(produção)http://localhost:3000/http://127.0.0.1:3000http://localhost:5173/http://127.0.0.1:5173(servidor de desenvolvimento do Vite)
Se você roda o servidor numa porta personalizada, aquela origem é acrescentada sozinha.
Desenvolvimento
Se você contribui com o frontend do painel web:
# Terminal 1: sobe a API do backend
hermes dashboard --no-open
# Terminal 2: sobe o servidor de desenvolvimento do Vite com HMR
cd web/
npm install
npm run dev
O servidor de desenvolvimento do Vite em http://localhost:5173 encaminha as requisições /api para o backend FastAPI em http://127.0.0.1:9119.
O frontend é feito com React 19, TypeScript, Tailwind CSS v4 e componentes no estilo shadcn/ui. As compilações de produção saem em hermes_cli/web_dist/, que o servidor FastAPI serve como uma SPA estática.
Compilação automática na atualização
Quando você roda o hermes update, o frontend web é recompilado sozinho se o npm estiver disponível. Isso mantém o painel em sincronia com as atualizações de código. Se o npm não estiver instalado, a atualização pula a compilação do frontend e o hermes dashboard a faz no primeiro início.
Temas e plugins
O painel vem com oito temas embutidos e pode ser estendido com temas definidos por você, abas de plugin e rotas de API no backend — tudo por encaixe, sem precisar clonar o repositório.
Troque de tema ao vivo pela barra do cabeçalho — clique no ícone de paleta ao lado do seletor de idioma. A escolha é gravada no config.yaml, em dashboard.theme, e restaurada ao carregar a página.
Mude a fonte de forma independente pelo mesmo seletor — a seção Font, abaixo da lista de temas, sobrepõe a fonte de interface do tema ativo, seja qual for. A escolha persiste entre trocas de tema (config.yaml → dashboard.font); escolha Theme default para limpar e voltar à fonte do próprio tema ativo.
Temas embutidos:
| Tema | Caráter |
|---|---|
Hermes Teal (default) | Verde-azulado escuro + creme, fontes do sistema, espaçamento confortável |
Hermes Teal (Large) (default-large) | Igual ao padrão, com texto de 18px e espaçamento mais folgado |
Nous Blue (nous-blue) | Azuis da marca Nous com espaçamento arejado |
Midnight (midnight) | Azul-violeta profundo, Inter + JetBrains Mono |
Ember (ember) | Carmesim quente + bronze, serifada Spectral + IBM Plex Mono |
Mono (mono) | Tons de cinza, IBM Plex, compacto |
Cyberpunk (cyberpunk) | Verde neon sobre preto, Share Tech Mono |
Rosé (rose) | Rosa + marfim, serifada Fraunces, espaçoso |
Para criar o seu próprio tema, acrescentar uma aba de plugin, injetar em espaços da casca ou expor endpoints REST específicos de plugin, veja Estender o Painel — o guia completo cobre:
- Esquema YAML de tema — paleta, tipografia, layout, ativos, componentStyles, colorOverrides, customCSS
- Variantes de layout —
standard,cockpit,tiled - Manifesto de plugin, SDK, espaços da casca, espaços com escopo de página (injetar widgets em páginas embutidas sem sobrescrevê-las), rotas FastAPI de backend
- Um passo a passo completo combinando tema e plugin (a demonstração de cockpit Strike Freedom)
- Descoberta, recarregamento e solução de problemas