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

  • [archivist]
  • [assistant]

Poirot

Investigating your Claude Code sessions.

Claude Code sessions live as JSONL files scattered under ~/.claude/projects/. Poirot mounts them in a native macOS UI — browse by repo, scrub the timeline, re-run any tool call, diff the files Claude touched. Same data, in a shape you can actually investigate.

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

The product lives at poirot.fyi. That’s where you download it, watch the demo, and read the release notes. This page is the engineering story: why a macOS app exists for something that looks like a CLI’s job, what’s inside, and what I’d do differently.

What it is, in one breath

Poirot is a native macOS app for poking through Claude Code sessions after they ran. You point it at your sessions directory and it gives you a polished UI to browse conversations, look at the diffs Claude produced, re-run commands, and stitch together a story of what actually happened. There’s nothing to log into and no key to paste. It just reads the local files that Claude Code already wrote.

Why it exists

I use Claude Code daily. After about a month, the question “what did I do last Tuesday afternoon?” stopped being answerable in any sensible way. The session logs are there. Claude Code is good about writing them. But they’re JSONL, sprawled across nested directories, and the diff context isn’t where you’d want it for review.

I wrote a few shell scripts. They turned into a Python TUI. The TUI was fine for me but unshareable. So I built the macOS app.

The thesis was always this is a desktop class problem. Reading code is a visual task. Comparing diffs side-by-side is a visual task. Filtering a thousand sessions by tag or by time is a visual task. The terminal can do it, but should it?

What’s inside

The stack is honestly simple, and that’s the point: Swift 6, SwiftUI, macOS 15+, no third-party UI dependencies. The few platform extensions I lean on are listed in the repo’s Mintfile.

The session model reads the JSONL files Claude Code writes into ~/.claude/projects/<…>/, one file per session, one line per message. Poirot indexes those into an in-memory model that the UI binds to. The indexing is lazy, so you don’t pay for sessions you don’t open.

Those JSONL files are richer than they look. Each line carries the role and a uuid, the timestamp, the model that produced it, the token usage, and a structured content payload that breaks down into the actual text, every tool call with its inputs (file paths, commands, search terms), every tool result that came back, and even Claude’s intermediate “thinking” blocks when reasoning mode was on. Poirot parses all of those into typed blocks so the UI can group them, render them, and rebuild what actually happened, step by step. The full set of fields and edge cases lives in TranscriptParser.swift and the Message model alongside it. Worth a read if you want to know exactly what survives the trip from the disk into the UI.

The diff viewer reconstructs each file-edit tool call against the file as it was at that moment, then renders the result side-by-side, syntax-highlighted, with the prompt that produced the change pinned at the top.

The re-run mechanism is small. The Resume button on the session toolbar copies claude --resume <session-id> to the clipboard, and if you’ve configured a preferred terminal in Settings, opens it ready for you to paste. That’s it. Poirot is not a wrapper around Claude Code; it’s a sibling that hands you the right command and steps out of the way.

Constraints I picked on purpose

Poirot has no server. The app reads local files. The only network connection it makes is to check for updates. That decision rules out logins, analytics, and BYOK prompts in one move. None of them would have an excuse to exist.

It also doesn’t call any LLM. Poirot reads Claude Code’s local artifacts, so asking for an API key would be theatre.

Features that can work offline (browsing, diffs, search) do. The one that can’t (re-running) just needs the Claude Code CLI to be installed.

Bundle size stays under 6 MB. Swift plus a careful resource diet.

What I’d do differently

I’d start with the search index sooner. The first three versions of Poirot didn’t have full-text search across all sessions, only filename and tag filtering. Once search landed, my whole usage pattern flipped. I stopped browsing chronologically and started searching for specific prompts I half-remembered. Building that earlier would have steered the rest of the UI choices.

I’d also have built the multi-agent view sooner. Claude Code’s sub-agent runs are a different graph than its main thread, and Poirot pretended they were the same until v26.03.5, when sub-agent visibility and the grouping + timeline view finally landed (PRs #57 and #65). The retrofit was painful.

The native bundle is also a trade-off I’d weigh again. Going Swift and SwiftUI keeps Poirot small on disk (no Electron, no embedded Chromium, no bundled runtime), and the cold start is genuinely fast. But the JavaScript and TypeScript tools that read the same JSONL files can feel snappier once they’re up, with very large session libraries especially. Web rendering with aggressive virtualization is a known-good pattern for that workload, and SwiftUI’s list and view-diffing behavior is harder to tune for the same shape. Worth it for the bundle-size and install story, but not a free win.

Try it

The product page is poirot.fyi. It has the Homebrew install command, the demo video, and the changelog. The source is at https://github.com/a7t-ai/poirot under MIT.