Session logs, made readable
I work with several AI coding tools, and each one keeps its own logs, in its own format, in its own place. ai-transcripts reads four of them: Claude Code, Gemini, Copilot and Antigravity. One file, sources.conf, says where each one keeps its logs. Every session becomes a Markdown transcript a person can read, with a metadata file beside it that records each turn: who spoke, when, the model, the tokens, the tool calls. A field a log does not report stays empty rather than zero, so a tool that reports no usage is counted but never priced.
It is how I measure my agent sessions, which is what ties it to Agent ops in the network.
Who is "user"?
The most interesting bug was in a word. Claude Code writes many records with the role "user" that no person typed: the instructions of a skill it loads, notices from background tasks, its own notes, the summary it writes when it compacts a conversation. In one real export, skill instructions alone were about 31% of the text. A small model reading that export took them for me, pasting in documentation. Decision 0019 sorts them out. The tool marks the records it writes itself, so those records now get headings of their own and the role "harness", and the count of user turns is mine alone.
The next export showed a second impostor. A subagent's log starts with a "user" record that is really the parent agent's instruction, and it carries none of those marks. In one export, those instructions were 82% of the text under the user heading. Decision 0020 gives that first record its own heading and the same "harness" role.
Numbers first
The first layer is plain arithmetic, and it always runs: usage by model and by day, sessions, tools, and what each session would have cost at the API's list prices, from a dated price table. That cost is a measure of how heavy a session was, not a bill. A model with no price is left empty and listed in the report, never counted as free.
Ten friction flags mark the places worth a second look: I corrected the agent, it repeated the same call three times, errors came in a burst, it took twenty tool turns without me, half an hour passed between two turns, I refused a tool call, an answer was cut off. Each threshold is a setting.
A model only if asked
The second layer runs only when I ask for it. Nine prompts, seven per session and two over the whole set, each written to disk as the exact text sent. It has three ways to run them: a list of OpenAI-compatible endpoints, the Claude Code command line, or a Claude Code session itself. In that last mode, the analyzer writes the prompts to files and stops with exit code 3; the session writes the answers, and a second command collects them into the report. Every transcript heading an answer cites is checked against the transcript, and the ones that do not exist are counted in the report, not hidden.
One prompt in, one answer out
The second package, ai, is the transport underneath. ai-call.sh sends one prompt to an OpenAI-compatible endpoint and prints only the answer. It walks a list of endpoints from the top, and a network error, an error status or an empty answer moves it to the next one. It warns when a prompt leaves the machine, and also when a local address serves a cloud model, because the text leaves just the same. claude-call.sh keeps the same contract through Claude Code, with no tools and no saved session, so the analyzer's own calls never turn up as sessions to analyze.
Both packages are tested offline. ai-transcripts has 237 tests over sample logs, and the ai suite replaces the network and Claude Code with stand-ins.