npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

agent-session-format

v0.9.0

Published

Environment-agnostic parsers that normalize AI coding-agent session transcripts into one zod-validated intermediate representation (NIR).

Readme

agent-session-format

Environment-agnostic parsers that normalize AI coding-agent session transcripts into one zod-validated intermediate representation (NIR). Extracted from agent-viewer and session-forge so both — and any other tool — can share a single parsing implementation.

Consumed by agent-viewer & session-forge.

The design rule: pure functions only

Nothing in src/ touches the environment: no fs, path, os, no bun:*, no network, no directory scanning. Every parser is a pure function:

  • input — a string, lines, or already-open database rows (via an injected interface, see below);
  • output — NIR objects validated by the zod schema in src/schema.ts.

All file I/O, path globs, SSH transports, and database opening stay in the consuming app. The package runs unchanged in Node, Bun, and the browser.

Format families

| Family | Sources | Layout | Entry point | |---|---|---|---| | claude-code | Claude Code | ~/.claude/projects/<slug>/*.jsonl | parseClaudeCodeTranscript(text, opts) | | codex-family | Codex CLI, DeepSeek (rollout JSONL) | rollout event stream | parseCodexRollout(text, opts) | | codex-family | Kimi Code | agents/<name>/wire.jsonl event stream | parseKimiWire(text, opts) | | codex-family | codewhale, DeepSeek (single-file JSON) | {metadata, messages} document | parseSessionJsonDocument / parseCodewhaleSession(text, opts) | | opencode-sqlite | opencode | opencode.db (SQLite) | opencodeSessionsFromDb(db, opts) / opencodeSessionFromDb(db, id, opts) | | antigravity-transcript | Gemini / Antigravity | brain/<id>/.../transcript.jsonl | parseAntigravityTranscript(text, opts) | | hermes | Hermes | request_dump_*.json or state.db | parseHermesDump(text, opts) / hermesSessionsFromDb(db, opts) / hermesSessionFromDb(db, id, opts) | | generic detection | unknown agents | any of the above | detectKind(text), parseDetectedTranscript(kind, text, opts) |

detectKind classifies arbitrary transcript text (a whole file or a truncated head) as claude-style / codex-style / chat-style / session-style — content classification only, no directory scanning.

Install

npm i github:Atituiset/agent-session-format

The package ships both ESM and CJS builds: import resolves to dist/index.js, require("agent-session-format") (e.g. from a CommonJS Electron main process) resolves to dist/index.cjs, with type declarations for both. dist/ is committed, so git-dependency installs work without running build scripts.

Usage

import { readFileSync } from "node:fs";
import { parseClaudeCodeTranscript, detectKind, parseDetectedTranscript } from "agent-session-format";

// Known format: parse directly.
const session = parseClaudeCodeTranscript(readFileSync("session.jsonl", "utf8"), {
  source: "claude-code",        // recorded as session.source
  filePath: "session.jsonl",    // used to derive the id (and the Claude project slug)
});

// Unknown format: classify first, then dispatch.
const kind = detectKind(headSample);           // "claude-style" | "codex-style" | ... | null
if (kind) parseDetectedTranscript(kind, text, { source: "my-agent", id: "run-1" });

Parsers return null when nothing parseable is found, and never throw on corrupt transcript content (bad lines/rows are skipped). They throw only on missing options (no derivable session id).

The NIR model

NirSession (zod: nirSessionSchema):

  • id, source, sourceVersion, projectPath, startedAt, endedAt
  • title, model, cost, tokens — optional session-level extensions
  • messages: NirMessage[] — one message per event: role (user | assistant | tool | system), content, timestamp, thinking, model, tokens, and tool-call detail: toolName, toolInput, toolCallId (correlates an assistant tool call with its role: "tool" result message), plus agent / agentLabel swimlane tags for subagent lanes. Parsers never truncate message, thinking, or tool-result content.
  • rawMeta — source-specific extras (patch file lists, sidechain/meta/ compaction counts, estimated tokens, …).

Claude Code specifics

  • Subagent lanes: isSidechain rows parse into their own swimlane instead of being dropped. The lane agent is the spawning Task tool_use id (resolvable through the parentUuid chain) and agentLabel its description; unresolvable roots fall back to sidechain-<rootUuid>. Separate subagent transcript files can be parsed with parseClaudeCodeTranscript(text, { agent, agentLabel, ... }) to tag every message with that lane.
  • Tokens: usage is deduplicated by message.id — Claude repeats rows with the full usage object when a streamed message is retried, and naive summing double-counts. Usage is accumulated per swimlane: rawMeta.tokensByAgent holds the per-lane breakdown (main plus one entry per subagent lane), session.tokens the grand total, and each lane's last assistant message carries that lane's tokens.
  • Noise rows: isMeta rows (local-command caveats) are skipped and counted in rawMeta.metaMessages; compaction summaries parse as user messages and are counted in rawMeta.compactions; type: "summary" rows provide the title fallback after ai-title.

Injected SQLite (opencode, hermes state.db)

The package never opens a database. It defines a minimal interface —

interface SqliteStatement { all(...params: unknown[]): unknown[] | Promise<unknown[]> }
interface SqliteDb { prepare(sql: string): SqliteStatement }

— and does SQL → NIR mapping only. all results are awaited, so synchronous drivers (Bun, better-sqlite3) and asynchronous ones (remote query bridges) both work unchanged.

Bun consumer:

import { Database } from "bun:sqlite";
import { opencodeSessionsFromDb } from "agent-session-format";

const db = new Database("/home/me/.local/share/opencode/opencode.db", { readonly: true });
const sessions = await opencodeSessionsFromDb(db, { source: "opencode" });

Node consumer (better-sqlite3):

import Database from "better-sqlite3";
import { opencodeSessionsFromDb } from "agent-session-format";

const db = new Database("/home/me/.local/share/opencode/opencode.db", { readonly: true });
const sessions = await opencodeSessionsFromDb(db, { source: "opencode" });

Async bridge consumer (e.g. a remote/WSL query path):

const db = {
  prepare: (sql: string) => ({
    all: (...params: unknown[]) => queryRemoteSqlite(dbPath, sql, params), // Promise<rows>
  }),
};
const sessions = await opencodeSessionsFromDb(db, { source: "opencode" });

Single-session reads: when you only need one session, prefer the per-session variants — every query is filtered by session id, which over a remote bridge avoids one round-trip per message of every other session. They return the same NIR shape as the whole-DB variants, or null when the session id is not found:

import { opencodeSessionFromDb, hermesSessionFromDb } from "agent-session-format";

const one = await opencodeSessionFromDb(db, "ses_abc123", { source: "opencode" });
const hermes = await hermesSessionFromDb(db, "20260602_233910_562888", { source: "hermes" });

Development

npm install     # also builds dist/ via the prepare script
npm run build   # tsup → ESM + .d.ts in dist/
npm test        # vitest
npm run typecheck

dist/ is committed to git on purpose: installs via a git dependency (npm i github:Atituiset/agent-session-format, or a Bun git install) work without running build scripts, and the prepare script covers the cases where they do run.

License

MIT