Pular para o conteúdo principal

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.

dica

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​

FlagPadrãoDescrição
--port9119Porta em que o servidor web roda
--host127.0.0.1Endereço de escuta
--no-open—Não abre o navegador sozinho
--insecuredesligadoObsoleta / 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)
--isolateddesligadoQuando 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 ~/.hermes está 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/pty abre um WebSocket autenticado com o token de sessão do painel
  • O servidor gera o hermes --tui atrá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 extra pty (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 /chat precisa 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 /chat mostra 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.

Pré-requisito: um hermes dashboard precisa estar rodando na máquina remota

O "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:

  1. 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.
  2. 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.

Confirme que a trava está ligada antes de tentar de novo no Desktop

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: true e "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: true mas 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:

Página de administração de configuração — filtros de seção à esquerda, campos descobertos automaticamente à direita

  • 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.yaml na 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
dica

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.

Página de administração de sessões — barra de estatísticas, poda e renomear / exportar / apagar por linha

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, errors e gateway
  • 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.

Página de administração de skills — a visão Browse hub: buscar, instalar e atualizar

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=VALUE opcionais 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.

Página de administração de MCP — seus servidores com interruptores de habilitar/desabilitar, mais o catálogo de instalação

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

Página de administração de webhooks — assinaturas com interruptores de habilitar/desabilitar

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

Página de administração de pareamento

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/.env e a marcação de habilitado para o config.yaml; o gateway conecta cada canal habilitado no próximo reinício, que você pode disparar direto da página.

Página de administração de canais — toda plataforma de mensagens com status, interruptores de habilitar e formulários de configuração por plataforma

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 psutil quando 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 o hermes update em 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 /rollback e 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.

Página de administração do sistema — estatísticas da máquina e estado do Nous Portal

Página de administração do sistema — curador de skills, gateway, memória e pool de credenciais

Página de administração do sistema — operações, checkpoints e ganchos de shell

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

Janela de novo gancho de shell

Segurança

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:

Endpoints com escopo de perfil

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 é elevated abaixo de 128 MiB (ou 15%) de memória de sistema disponível e critical abaixo 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 rebaixam pressure para unknown, para a última amostra de um gateway morto não se passar por leitura ao vivo.
  • disk — uma amostra ao vivo de shutil.disk_usage() do volume que guarda o ~/.hermes. Campos: pressure, free_mb, total_mb, used_percent, sampled_at. A pressão é elevated abaixo de 512 MB livres (ou ≥85% usado com menos de 4 GB de folga) e critical abaixo 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 caminhoFinalidade
GET /api/mcp/serversLista os servidores MCP configurados (valores de ambiente ocultados)
POST /api/mcp/serversAcrescenta um servidor. Corpo: {name, url?, command?, args?, env?, auth?}
POST /api/mcp/servers/{name}/testConecta, lista as ferramentas, desconecta
PUT /api/mcp/servers/{name}/enabledHabilita / desabilita um servidor
DELETE /api/mcp/servers/{name}Remove um servidor
GET /api/mcp/catalogNavega pelo catálogo de MCP aprovado pela Nous
POST /api/mcp/catalog/installInstala uma entrada do catálogo (com o ambiente exigido)
GET /api/messaging/platformsLista 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}/testInforma se um canal está configurado, habilitado e conectado
GET /api/pairingLista as pessoas pendentes + aprovadas nas mensagens
POST /api/pairing/approveAprova um código. Corpo: {platform, code}
POST /api/pairing/revokeRevoga uma pessoa. Corpo: {platform, user_id}
POST /api/pairing/clear-pendingDescarta todos os códigos pendentes
GET /api/webhooksLista as assinaturas + o estado de habilitação da plataforma
POST /api/webhooksCria uma assinatura (devolve o segredo de uma vez só)
DELETE /api/webhooks/{name}Remove uma assinatura
GET /api/credentials/poolLista as chaves de rotação do pool (ocultadas)
POST /api/credentials/poolAcrescenta uma chave. Corpo: {provider, api_key, label?}
DELETE /api/credentials/pool/{provider}/{index}Remove uma chave (índice começando em 1)
GET /api/memoryProvedor ativo + provedores disponíveis + tamanho dos arquivos embutidos
PUT /api/memory/providerEscolhe um provedor (vazio = só o embutido)
POST /api/memory/resetZera a memória embutida. Corpo: {target: all|memory|user}
POST /api/gateway/start · /stop · /restartCiclo de vida do gateway (em segundo plano)
POST /api/ops/doctor · /security-audit · /backup · /importDiagnóstico e manutenção (em segundo plano; acompanhe por /api/actions/{name}/status)
GET /api/ops/hooksGanchos de shell configurados + estado da lista de permissões
GET /api/ops/checkpoints · POST .../pruneInspeciona / poda o repositório do /rollback
POST /api/ops/hooks · DELETE /api/ops/hooksCria / remove um gancho de shell (com trava de consentimento)
GET /api/system/statsEstatísticas da máquina — SO, CPU, memória, disco, tempo ligado
GET /api/hermes/update/checkInforma 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 .../runEstado do curador de skills + pausar/retomar + rodar
GET /api/portalAutenticação do Nous Portal + roteamento do Tool Gateway (somente leitura)
POST /api/ops/prompt-size · /dump · /config-migrateDiagnóstico (em segundo plano)
PUT /api/webhooks/{name}/enabledHabilita / desabilita uma rota de webhook
POST /api/skills/hub/install · /uninstall · /updateAções do hub de skills (em segundo plano)
GET /api/skills/hub/searchBusca no hub de skills em todas as fontes
GET /api/sessions/statsEstatísticas do repositório de sessões
PATCH /api/sessions/{id}Renomeia / arquiva uma sessão
GET /api/sessions/{id}/exportExporta uma sessão (metadados + mensagens) em JSON
POST /api/sessions/pruneApaga 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​

FlagsTrava de autenticaçãoCaso de uso
hermes dashboard (padrão — escuta em 127.0.0.1)DESLIGADADesenvolvimento local
hermes dashboard --host 0.0.0.0LIGADARemoto / 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ção

Desde 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 o hermes setup antes, se você não estiver logado), registra um cliente OAuth auto-hospedado no Portal e grava o HERMES_DASHBOARD_OAUTH_CLIENT_ID no ~/.hermes/.env para 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-dashboards no Nous Portal para registrar, nomear, gerenciar e revogar painéis auto-hospedados pelo navegador. Copie o client ID agent:{id} resultante para o HERMES_DASHBOARD_OAUTH_CLIENT_ID (ambiente) ou para o dashboard.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 ambienteSobrepõeFormatoProvisionada por
HERMES_DASHBOARD_OAUTH_CLIENT_IDdashboard.oauth.client_idagent:{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).

Use isto só em redes confiáveis — não na internet pública

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 ambienteSobrepõeNotas
HERMES_DASHBOARD_BASIC_AUTH_USERNAMEdashboard.basic_auth.usernameobrigatória para ativar
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASHdashboard.basic_auth.password_hashpreferida (sem texto puro em repouso)
HERMES_DASHBOARD_BASIC_AUTH_PASSWORDdashboard.basic_auth.passwordtexto puro; vence um password_hash da configuração, para você poder rotacionar pelo ambiente
HERMES_DASHBOARD_BASIC_AUTH_SECRETdashboard.basic_auth.secretchave de assinatura dos tokens
HERMES_DASHBOARD_BASIC_AUTH_TTL_SECONDSdashboard.basic_auth.session_ttl_secondsvalidade do token de acesso
Defina um secret explícito para sessões estáveis

Quando 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 ambienteSobrepõeNotas
HERMES_DASHBOARD_OIDC_ISSUERdashboard.oauth.self_hosted.issuerURL do issuer OIDC — obrigatória
HERMES_DASHBOARD_OIDC_CLIENT_IDdashboard.oauth.self_hosted.client_idID de cliente público — obrigatória
HERMES_DASHBOARD_OIDC_SCOPESdashboard.oauth.self_hosted.scopesPor 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ãoReivindicação(ões)
user_idsub (obrigatória)
emailemail
display_namename → preferred_username → nickname → email
org_idorg_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/callback daquela 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ícieCaminho de sobreposiçãoQuando usar
dashboard.public_url no config.yamlHERMES_DASHBOARD_PUBLIC_URLDesenvolvimento 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_url sobrepõe só a URL de retorno do OAuth. A marcação Secure do cookie continua controlada pelo request.url.scheme, usando o X-Forwarded-Proto apenas quando o par que conecta é de loopback ou está listado em trusted_proxies. Combine um public_url em 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):

  1. A pessoa acessa / sem cookie de sessão → a trava redireciona para /login.
  2. A página de login mostra um botão "Continue with Nous Research" → /auth/login?provider=nous.
  3. O servidor guarda o estado do PKCE num cookie de vida curta e redireciona para https://portal.nousresearch.com/oauth/authorize?….
  4. A pessoa autentica no Portal e chega em /auth/callback?code=…&state=….
  5. 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 cookie hermes_session_at.
  6. A pessoa é redirecionada para / (ou para o caminho original do link direto, pelo parâmetro de consulta next=).

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​

NomeDuraçãoNotas
hermes_session_atValidade do token (15 min)HttpOnly, SameSite=Lax, Secure quando em HTTPS
hermes_session_pkce10 minHttpOnly; 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_rtsem uso na v1Reservado 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.

aviso

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 /hermes funcionam 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 ambienteValor
HERMES_DESKTOP_REMOTE_URLhttp://<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_PASSWORD do backend. O backend devolve o mesmo erro genérico para usuário desconhecido e senha errada, então confira os dois. Confirme a trava com curl -s http://<host>:9119/api/status | jq '.auth_required, .auth_providers' — deve informar true e 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/status nã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_SECRET com 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 em 0.0.0.0 ou 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:3000
  • http://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:

TemaCará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