Uma mini cidade em pixel art no navegador que mostra, em tempo real, o que seus agentes do Claude Code estão fazendo. Alimentada por hooks, roda 100% local e não gasta nenhum token: é só código Node.js + canvas.
Projeto independente, sem ligação com a Anthropic. Ele usa os hooks do Claude Code, cujo formato pode mudar com o tempo.
- Node.js puro, sem dependências (nada de
npm install). - Servidor escuta apenas em
127.0.0.1, porta 4777 por padrão. - Tempo real via Server-Sent Events (SSE).
- Windows 10 ou 11. O botão de abrir pastas usa o Explorer, as notificações são as do
Windows e o início automático usa um arquivo
.vbs. - Node.js (versão atual LTS).
- Claude Code, para gerar os eventos reais. Sem ele você ainda vê a cidade com o simulador.
git clone https://github.com/dalmeidasdev/cidade-agentes.git
cd cidade-agentes
node server.jsAbra http://localhost:4777 no navegador.
Com o servidor rodando, em outro terminal:
node test-events.js # roteiro de ~50 s: três sessões, subagentes e um pedido de permissão
node test-events.js --loop # repete até Ctrl+CO script entrega os eventos ao hook.js pelo stdin, igual ao Claude Code. Assim ele testa o
caminho todo.
Se o servidor não estiver rodando, os hooks não atrapalham o Claude Code, mas nada é registrado (sem histórico, relatório nem conquistas). Para a cidade ligar sozinha quando você entra no Windows:
node install-autostart.js # instala e já liga a cidade agora
node install-autostart.js --uninstall # deixa de ligar sozinhaIsso cria só um arquivo: cidade-agentes.vbs na pasta Inicializar do seu usuário
(%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup). Ele roda o launcher.js sem abrir
janela. Não usa admin, registro nem serviços.
- Abrir a cidade: dê dois cliques em
iniciar-cidade.cmd. Ele liga o servidor se precisar e abre o navegador. - Log do servidor:
data\server.log(zera sozinho quando passa de 1 MB). Cada execução do launcher deixa uma linha, por exemplolauncher (login): já estava rodando. A origem pode serlogin,atalhooumanual, então dá para conferir se o autostart rodou. - Parar o servidor:
Você também pode encerrar o
Get-CimInstance Win32_Process -Filter "Name='node.exe'" | Where-Object { $_.CommandLine -match 'cidade-agentes.server\.js' } | ForEach-Object { Stop-Process -Id $_.ProcessId }
node.exepelo Gerenciador de Tarefas.
O install-hooks.js lê o settings.json, mescla as entradas novas preservando tudo o que já
existe (inclusive outros hooks) e salva um backup em settings.json.bak antes de gravar.
Pode rodar de novo sem duplicar nada.
# Global: vale para todas as sessões (%USERPROFILE%\.claude\settings.json)
node install-hooks.js --global
# Só em um projeto (<caminho>\.claude\settings.json)
node install-hooks.js --project C:\caminho\do\projetoPara desinstalar (remove só as entradas que o script adicionou):
node install-hooks.js --global --uninstall
node install-hooks.js --project C:\caminho\do\projeto --uninstallDepois de instalar, abra uma sessão nova do Claude Code (ou confira em /hooks).
Eventos instalados: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse,
PostToolUseFailure, SubagentStart, SubagentStop, Notification, Stop, SessionEnd.
Cada hook é registrado assim:
{ "type": "command", "command": "node \"C:/.../cidade-agentes/hook.js\"", "async": true, "timeout": 5 }async: truefaz o hook rodar em segundo plano, sem bloquear o Claude Code.- O
hook.jstem timeout de 500 ms para o POST. Se o servidor estiver desligado, ele sai com código 0 sem imprimir nada. - Só é enviado um resumo: nome do evento, sessão, agente, ferramenta, pasta e um resumo curto da ação (caminho do arquivo ou os primeiros 80 caracteres do comando). O conteúdo de arquivos nunca é enviado.
$env:CIDADE_PORT=5000; node server.jsO hook.js também lê CIDADE_PORT. Se você mudar a porta, defina a variável de forma
permanente para o Claude Code também enxergar:
[Environment]::SetEnvironmentVariable('CIDADE_PORT', '5000', 'User')(ou use a chave "env": { "CIDADE_PORT": "5000" } no settings.json do Claude Code).
| Prédio | Ferramentas / eventos |
|---|---|
| 🏛️ Prefeitura | SessionStart, UserPromptSubmit, Agent/Task (delegando para subagentes) |
| 📚 Biblioteca | Read, Grep, Glob |
| 🔨 Oficina | Edit, Write, MultiEdit, NotebookEdit |
| 🧪 Laboratório | Bash |
| 📮 Correios/Porto | WebFetch, WebSearch, qualquer mcp__* |
| 🏚️ Outros | qualquer outra ferramenta |
| ☕ Praça/Café | agentes ociosos (depois do Stop) |
- Cada sessão vira um habitante com cor própria e o nome da pasta (
cwd). - Subagentes são habitantes menores (de boné), ligados ao pai por uma linha tracejada. No
SubagentStopeles vão para casa (saem pela esquerda). Notificationmostra um "❗" piscando (normalmente é pedido de permissão).Stop: o habitante vai para a Praça/Café.SessionEnd: dá tchau e vai embora.- Acima da cabeça aparece só um ícone de estado (⚙️ trabalhando, ❗ esperando, 💤 dormindo). O balão com a ação completa aparece nos primeiros segundos, ao passar o mouse ou para o agente selecionado.
- A luz da cidade segue o relógio: amanhecer, dia, entardecer e noite (com postes acesos).
- Clique num habitante (ou no cartão dele na aba 👥 Agentes) para abrir o perfil:
título pela atividade (📚 Rato de biblioteca, 🔨 Mão na massa, 🧪 Cientista, 📮 Pombo-correio,
🏛️ Delegador), estado atual, pasta, sessão, contadores, ferramentas mais usadas, arquivos
recentes, último pedido, subagentes e linha do tempo.
- Clique no nome para dar um apelido. Ele fica salvo no navegador, por pasta.
- 📍 Ir até destaca o habitante; 🗑 Esquecer remove o perfil da cidade.
- Clique num prédio para entrar. Cada prédio tem uma sala com estações de trabalho, e quem está lá aparece trabalhando: o terminal do Laboratório mostra os últimos comandos, o mural da Prefeitura mostra o último pedido, a Oficina solta faíscas... O painel mostra quem está lá dentro e o histórico do prédio. Para sair: Esc, a porta ou "← Voltar à cidade".
- Abas do painel: 👥 Agentes, 📜 Eventos (dá para filtrar pelo agente selecionado) e 📊 Hoje (contadores e visitas por prédio).
Cada alerta explica o quê, onde e como responder:
- O quê: a ação que está esperando sua aprovação, por exemplo
Quer usar Bash: npm run deploy. Se foi um subagente que pediu, ele aparece entre parênteses. - Onde: o programa em que a sessão roda (terminal do VS Code, Windows Terminal, app Claude Desktop…), a pasta (com botão de copiar) e a hora em que a sessão abriu. Isso ajuda quando há várias janelas abertas na mesma pasta.
- Como: o passo a passo para cada tipo de alerta:
| Tipo | O que significa |
|---|---|
permission_prompt |
pede permissão: aprove ou recuse na janela da sessão |
idle_prompt |
terminou e espera sua próxima mensagem |
elicitation_dialog |
um servidor MCP abriu um formulário para preencher |
elicitation_url_dialog |
um servidor MCP pede para abrir um link (por exemplo, login) |
agent_needs_input |
um agente parou esperando sua resposta |
Avisos informativos (auth_success, agent_completed, avisos de cota…) não viram ❗: aparecem
só num toast curto, sem som.
Onde o alerta aparece:
- toast no topo com o resumo; clique para abrir o cartão completo no perfil do agente;
- chip "❗ esperando você" e título da aba
(1❗) Cidade dos Agentes; - bip curto. Liga e desliga no botão 🔊. O navegador só libera som depois do primeiro clique na página;
- notificação do Windows se você ligar no botão 🔔. Ela só aparece quando a aba está em segundo plano e fica na tela até você clicar.
O alerta some sozinho assim que a sessão volta a andar (você respondeu).
- Ação rodando há mais de 30 s ganha um selo ⏱ 1:23 sobre o agente. Ele fica âmbar depois de 2 min.
- O chip do topo mostra há quanto tempo alguém espera você (
⏳ 2:10). - No perfil aparecem: "rodando há…", o tempo gasto por ferramenta, as ações mais lentas e quanto tempo o agente esperou você.
A aba Relatório mostra o dia selecionado (hoje e os 6 anteriores):
- ações, tempo ativo, falhas e tempo esperando você;
- gráfico de atividade por hora, empilhado por tipo (leitura, edição, comando, web, outros), com opção de ver como tabela;
- tempo ativo por projeto (soma dos intervalos de até 5 min entre eventos da sessão);
- os últimos 7 dias;
- arquivos mais editados e mais lidos;
- ações mais lentas, falhas (com o erro) e subagentes por tipo;
- a grade de 🏆 Conquistas.
O botão ⏪ Replay revê qualquer um dos últimos 7 dias:
- ▶/⏸, velocidade 1×/10×/60× e um slider para pular para qualquer horário;
- pausas longas sem eventos são puladas;
- ● Ao vivo volta ao presente;
- os alertas reais continuam chegando durante o replay.
Embaixo da cidade fica a Rua dos Projetos: uma casinha por pasta de projeto, com a cor do projeto.
- A sessão sai de casa ao começar e volta para casa ao encerrar.
- A janela acende quando há sessão ativa.
- A bandeirinha da caixa de correio sobe quando alguém do projeto espera você.
- Clique na casa para ver o projeto: sessões, números de hoje e da semana, e um link para o relatório só daquele projeto.
-
Carros circulam pelas avenidas (mais carros quando há mais agentes trabalhando), pássaros cruzam o céu e pombos fogem de quem passa pela praça.
-
Uma falha põe fogo e fumaça no prédio da ferramenta por 20 s. 3 falhas em 10 min fazem chover (também nas janelas dos interiores).
-
Subagentes usam acessórios conforme o tipo:
Tipo Acessório Explore lupa Plan prancheta revisor óculos teste tubo de ensaio general-purpose mochila outros boné -
As sessões principais usam um chapéu na cor do projeto.
Na aba 📜 Eventos, o campo de busca procura nos últimos 30 dias por nome de arquivo,
comando, projeto, erro ou tipo de subagente (ex.: checkout.ts, npm test, timeout). Clique
num resultado para abrir o replay pausado naquele momento.
Na aba 📊 Relatório, o botão 📝 Resumo em texto gera um resumo em Markdown do dia (ou do projeto filtrado). Ele traz o tempo ativo, as ações, as falhas e, para cada projeto, os pedidos, os arquivos editados e as falhas. Dá para copiar ou baixar .md, bom para colar no daily.
O perfil do agente, o cartão de alerta e a vista do projeto têm os botões 📂 Abrir pasta (Explorer) e 🧑💻 VS Code (este só aparece se o VS Code estiver instalado).
Por segurança, o servidor:
- só abre pastas que a cidade já conhece (projetos e sessões vistos);
- nunca usa um shell;
- só aceita o pedido vindo da própria página da cidade, então outros sites abertos no navegador não conseguem usar a rota.
Cada prédio sobe de nível (★ a ★★★★) conforme o uso acumulado: a Biblioteca pelas leituras, a Oficina pelas edições, o Laboratório pelos comandos, e assim por diante. Os limites são 50, 200, 500 e 1500; a praça cresce pelos dias com atividade.
| Nível | O que muda |
|---|---|
| 1 | floreiras nas janelas |
| 2 | torre/anexo |
| 3 | varal de luzinhas |
| 4 | telhado dourado e nome novo (Fábrica, Grande Biblioteca, Centro de Pesquisa…) |
A praça ganha flores, coreto, bancos e uma fonte maior. A vista do prédio mostra quanto falta
para o próximo nível. Quando um prédio sobe de nível, aparece um aviso e uma festinha. As
regras ficam em GROWTH em public/buildings.js.
Ao desbloquear uma conquista, a cidade solta fogos de artifício e confete por alguns segundos.
Medalhas como Mestre de obras (100 edições), Coruja, Madrugador, Dia limpo, Sequência de 5
dias, Resposta rápida (permissão respondida em menos de 10 s), Paciência (ação de 5 min ou
mais) e Bairro cheio. Cada uma aparece com um toast dourado e um som curto. A lista e as regras
ficam em achievements.js.
O servidor guarda tudo localmente em data/:
data/state.json: perfis, histórico dos prédios, projetos, contadores, conquistas e os últimos 50 eventos;data/events/AAAA-MM-DD.jsonl: todos os eventos de cada dia, que alimentam o relatório e o replay. Ficam guardados por 30 dias e trazem só os resumos que o hook manda (caminho do arquivo, os primeiros 80 caracteres do comando, os primeiros 60 do pedido), nunca o conteúdo de arquivos. Por isso a cidade se reconstrói mesmo depois de reiniciar o servidor;- perfis de sessões encerradas há mais de 24 h, ou sem sinal há 7 dias, são apagados sozinhos.
Para zerar tudo, pare o servidor e apague a pasta data. Se o arquivo corromper, o servidor o
renomeia para state.json.corrupt e começa do zero.
- Tudo fica na sua máquina. O servidor só escuta em
127.0.0.1e nada é enviado para a internet. - Mesmo sendo só um resumo, os primeiros 80 caracteres de um comando podem conter algo
sensível, como um token colado numa linha de comando. Por isso a pasta
data/está no.gitignore: nunca a envie para o GitHub nem a compartilhe. - Os hooks podem ser removidos a qualquer momento com
node install-hooks.js --global --uninstall.
O simulador usa sessões simulado-*. Elas aparecem ao vivo, mas não entram no
histórico, nos relatórios nem nas conquistas. Para tirá-las da tela: node test-events.js --limpar.
APIs locais (úteis para depurar): /api/state, /api/report?day=AAAA-MM-DD&project=<pasta>, /api/search?q=<texto>,
/api/week e /api/events?day=AAAA-MM-DD.
GET http://localhost:4777/api/state mostra todo o estado em JSON (útil para depurar).
O desenho da cidade fica em public/art.js:
| O que | Onde |
|---|---|
| Paleta geral (grama, asfalto, calçada, contornos, vidros…) | objeto PAL |
| Cores de cada prédio | wall/roof em public/buildings.js |
| Formato de cada prédio | uma função por prédio em DRAWERS (prefeitura com frontão e colunas, biblioteca com cúpula, oficina com telhado de fábrica, laboratório com cúpula de vidro, correios com torre do relógio, armazém) |
Um prédio novo sem função própria usa uma caixa padrão.
- Chão, ruas, calçadas e cercas vivas: desenhados uma vez numa camada em cache (
rebuild()) e só copiados a cada quadro. - Luz da noite: um mapa de luz (escuridão com "buracos" nos postes, janelas, faróis e
fogo) em
applyLighting(). - Vinheta e gradação de cor: em
postFx(). - Sombras: seguem a hora do dia.
- Habitantes: o sprite (cerca de 16 px, com contorno, caminhada em 4 quadros, piscada e
ferramenta na mão ao trabalhar) fica em
drawPersonAt()nopublic/city.js.
- Novo prédio: em
public/buildings.js(usado pelo navegador e pelo servidor), acrescente uma entrada emBUILDINGS:Useestadio: { name: 'Estádio', icon: '🏟️', wall: '#ccc', roof: '#a33', tools: ['MinhaFerramenta'], about: 'Descrição que aparece no painel.' },
match: (nome) => nome.startsWith('algo')para casar ferramentas por padrão. Depois dê uma posição a ele emLAYOUT(colde 0 a 3,row0 ou 1,spanopcional). Se precisar de mais espaço, aumenteCOLS. - Sala do prédio: em
public/interiors.js, acrescente uma entrada emTHEMEScom as cores (wall,wallDark,floorA,floorB) e, se quiser, as funçõesdecorate(parede e fundo),desk(móvel de cada estação),tick(efeitos) eoverlay(textos). Sem tema, o prédio usa uma sala padrão. - Sprite no lugar do desenho: coloque a imagem em
public/(por exemplopublic/sprites/oficina.png) e adicionesprite: 'sprites/oficina.png'ao prédio. A imagem é esticada no retângulo do prédio sem suavização, então pixel art fica nítida. - Habitantes: o desenho fica em
drawPersonAt()(public/city.js), usado na cidade, nos interiores e nos avatares do painel. Troque ospx(...)por umdrawImage(...)para usar uma spritesheet.
server.js servidor HTTP + SSE (porta 4777, só 127.0.0.1)
profiles.js perfis, projetos, tempos e gravação em data/ (state.json + events/)
reports.js relatórios do dia/semana a partir de data/events/*.jsonl
achievements.js conquistas e contadores da vida toda
hook.js chamado pelo Claude Code; envia o resumo do evento
install-hooks.js instala/desinstala os hooks no settings.json
launcher.js liga o servidor em segundo plano (se ainda não estiver rodando)
install-autostart.js faz a cidade ligar sozinha ao entrar no Windows
iniciar-cidade.cmd atalho: liga e abre a cidade
test-events.js simulador de eventos (--loop, --limpar)
public/index.html página, HUD e estilos
public/buildings.js prédios e regras ferramenta → prédio (compartilhado com o servidor)
public/city.js núcleo: estado, conexão, cena da cidade, habitantes, cliques
public/art.js visual: paleta, chão em cache, prédios, casas, sombras, luz
public/interiors.js salas dos prédios
public/hud.js painel, perfis, projetos, toasts, som e notificações
public/report.js aba Relatório (gráficos e conquistas)
public/replay.js replay do dia
data/ estado salvo e histórico (criado automaticamente)
Este projeto foi feito com apoio do Claude Code. Eu defini a ideia e os requisitos, testei tudo no meu próprio computador e ajustei o que não funcionou.
MIT © Diego Almeida
