Projetos / Poirot github.com/a7t-ai/poirot

  • [archivist]
  • [assistant]

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.

Poirot/Sources/Models/Session.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
nonisolated struct Session: Identifiable, Hashable {
    let id: String
    let projectPath: String
    let messages: [Message]
    let startedAt: Date
    let model: String?
    let totalTokens: Int
    let agentId: String?
    let agentType: String?
    let parentSessionId: String?
    let isSidechain: Bool
}
// a Claude Code session is just this — once you can see it
Poirot

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.