Projetos / Poirot github.com/a7t-ai/poirot
Poirot
Investigando suas sessões do Claude Code.
As sessões do Claude Code vivem como arquivos JSONL espalhados em ~/.claude/projects/. Poirot monta tudo numa UI nativa de macOS — navega por repositório, percorre a timeline, re-executa qualquer tool call, vê o diff dos arquivos que o Claude tocou. Os mesmos dados, em um formato que dá pra investigar.
O produto mora em poirot.fyi. Essa página é a história de engenharia por trás dele: por que um app de macOS existe para algo que parece tarefa de CLI.
O que é, em uma frase
Poirot é um app nativo de macOS para navegar pelas sessões do Claude Code depois que rodaram. Você aponta para sua pasta de sessões e uma UI polida deixa você ler cada conversa, conferir os diffs que o Claude produziu, re-executar comandos e juntar a história do que aconteceu. Não tem login para fazer nem chave para colar. Ele só lê o JSONL local que o Claude Code já escreveu.
Por que ele existe
Eu uso o Claude Code todos os dias, e depois de um mês mais ou menos a pergunta “o que eu fiz terça à tarde?” parou de ter resposta razoável. O Claude Code escreve os logs de sessão direitinho, mas em JSONL espalhado por pastas aninhadas, com o contexto dos diffs enterrado onde você nunca olharia para revisar.
Uns scripts em shell viraram um TUI em Python que servia para mim mas não dava para compartilhar, então construí o app de macOS.
A tese sempre foi que isso é um problema de classe desktop. Ler logs espalhados, comparar diffs e filtrar entre mil sessões são tarefas que o terminal consegue fazer, mas será que devia?
O que tem dentro
A stack é honestamente simples, e esse é o ponto: Swift 6, SwiftUI, macOS 15+, sem dependências de UI de terceiros. As poucas extensões de plataforma que eu uso ficam listadas no Mintfile do repo.
O modelo de sessão lê os arquivos JSONL que o Claude Code escreve em ~/.claude/projects/<…>/, um arquivo por sessão, uma linha por mensagem. O Poirot indexa esses arquivos num modelo em memória ao qual a UI se liga. A indexação é lazy, então você não paga pelas sessões que não abre.
Esses arquivos JSONL são mais ricos do que parecem. Cada linha carrega o role e um uuid, o timestamp, o modelo que produziu aquela mensagem, o uso de tokens, e um payload de conteúdo estruturado que se desdobra em texto puro, em cada tool call com seus inputs (caminhos de arquivo, comandos, termos de busca), em cada tool result que voltou, e até nos blocos de “thinking” do Claude quando o modo de reasoning estava ligado. O Poirot parseia tudo isso em blocos tipados para a UI agrupar, renderizar e reconstruir o que de fato aconteceu, passo a passo. O conjunto completo de campos e casos de borda mora em TranscriptParser.swift e no modelo Message ao lado. Vale a leitura se você quer saber exatamente o que sobrevive da viagem do disco até a UI.
O viewer de diff reconstrói cada tool call de edição de arquivo contra o arquivo como ele estava naquele momento, e então renderiza o resultado lado a lado, com syntax highlight, com o prompt que produziu a mudança fixado no topo.
O mecanismo de re-run é pequeno. O botão de Resume na toolbar da sessão copia claude --resume <session-id> para o clipboard, e se você configurou um terminal preferido no Settings, abre ele já pronto para você colar. Só isso. O Poirot não é um wrapper do Claude Code, é um irmão que te entrega o comando certo e sai do caminho.
Restrições que escolhi de propósito
O Poirot não tem servidor. O app lê arquivos locais, com a única conexão de saída indo verificar updates. Essa decisão sozinha tirou do mapa a bagagem usual de login, analytics e prompts de BYOK antes que qualquer um deles tivesse desculpa para existir.
Também não chama nenhum LLM. O app lê os artefatos locais do Claude Code, então pedir chave de API seria teatro.
Os recursos que conseguem funcionar offline (navegação, diffs, busca) funcionam. O único que não consegue (re-executar) só precisa que o CLI do Claude Code esteja instalado.
O bundle fica abaixo de 6 MB, em Swift mais uma dieta cuidadosa de recursos.
O que eu faria diferente
Eu teria começado o índice de busca mais cedo. As três primeiras versões do Poirot só tinham filtro por nome de arquivo e por tag, e depois que a busca full-text entrou em todas as sessões, meu padrão de uso virou de navegação cronológica para buscar por prompts específicos que eu lembrava pela metade. Ter construído isso antes teria guiado o resto das escolhas de UI.
Também teria construído a view de multi-agente antes. As runs de sub-agentes do Claude Code são um grafo diferente da thread principal, e o Poirot fingiu que eram iguais até a v26.03.5, quando a visibilidade de sub-agente e a view de agrupamento + timeline finalmente entraram (PRs #57 e #65). A reforma depois doeu.
O bundle nativo também é um trade-off que eu pesaria de novo. Ir de Swift e SwiftUI mantém o Poirot pequeno no disco (sem Electron, sem Chromium embarcado, sem runtime junto), e o cold start é rápido de verdade. Mas as ferramentas em JavaScript e TypeScript que leem os mesmos arquivos JSONL conseguem parecer mais ágeis depois que sobem, especialmente com bibliotecas de sessão bem grandes. Renderização web com virtualização agressiva é um padrão conhecido para essa carga, e o comportamento de list e view-diffing do SwiftUI é mais difícil de calibrar para o mesmo formato. Vale a pena pelo tamanho do bundle e pela história de instalação, mas não é vitória de graça.
Como rodar
A página do produto é poirot.fyi, com o comando de instalação via Homebrew, o vídeo da demo e o changelog. O código está em https://github.com/a7t-ai/poirot sob MIT.