Overview

agentscript

A terminal-first reader for Claude Code and Codex JSONL transcripts, written in Go. It normalizes two different agent-log formats into one stream of stably-numbered blocks you can open, search, and slice. More importantly, it exports context-efficient markdown so one agent can hand a session to another without drowning in raw JSONL.

GoCLI
Coding AgentsClaude CodeCodex
agentscript

I spend most of my day inside a coding agent. Usually it’s a goldengoose session driving Claude Code or Codex against a real repo, and two things happen often enough that they stopped being annoyances and became a tool. The first: a session gets corrupted, or I want context from an older one that has nothing to do with what I’m building right now, perhaps something I worked out three projects ago and want back. The second is subtler and matters more. When I hand a task from one agent to another, the thing I actually want to pass is what happened in the first session: the decisions and the commands that worked. The only record of that is a JSONL file that neither I nor the receiving agent can cheaply read. agentscript fixed both. It turns Claude Code and Codex transcripts into stably-numbered blocks you can open, search, and slice, and, just as importantly, into context-efficient markdown an agent can read without burning its whole context window on the raw log.

The output that matters is a handoff, not a pretty log

Most of my projects are systems work; this is where the same rigor goes into a problem that’s really about context engineering. The raw JSONL of an hour-long agent session is enormous and mostly noise: thinking blocks, tool-call plumbing, results the model already consumed and moved past. Paste that into a second agent to bring it up to speed and you’ve spent its entire context window on plumbing before it does anything useful. So agentscript’s filters exist to subtract: --messages-only keeps just the human-and-assistant conversation, --tools Bash keeps only the shell commands that actually ran, --hide-thinking drops the model’s scratch work. Compose those with --format md --out handoff.md and you get a small, readable markdown file that preserves the decisions and the commands and throws away the tokens nobody downstream needs.

That artifact is the one I reach for constantly: a distilled transcript I can drop into Agentbox or straight into another agent, so the second agent starts already knowing what the first one did instead of re-deriving it. This is the same instinct I lean on everywhere I do applied-AI work: in Zodega it was never invalidating the prompt cache; here it’s never spending context on a log when a slice of it would do. The useful unit isn’t the transcript; it’s the part of it worth carrying forward, and the whole tool is built to isolate that cheaply.

The block index is an address, not a line number

Everything above depends on being able to point at an exact moment in a session and trust that the pointer holds. So when agentscript parses a transcript, each renderable block (a user turn, an assistant turn, a thinking block, a tool call, a shell command, a result) gets an index assigned once, #000 through #N, and nothing after that ever renumbers it. Every filter flag hides blocks but leaves the numbering alone, so a filtered view deliberately shows gaps: #003, #007, #012. That looks like a bug until you see it as an address.

That’s what makes search and slicing compose. search returns block indexes and hands you a ready-to-run agentscript open <path> --around 342; you jump there, pull the neighborhood with --around 342 --before 25 --after 50, and carry exactly that out. If hiding thinking blocks shifted every number, the slice you copied a second ago would point somewhere else entirely. The gaps are the feature: the number is a stable handle on a specific moment, not a count of what happens to be visible.

One block model over two schemas

The reason search, slice, and every renderer are each written once is that they never touch raw JSONL. They see the block model, and the parsers do the reconciling. Claude Code entries are user/assistant objects with content arrays (text, thinking, tool_use, tool_result); agentscript dedupes tool calls by id and strips the XML-ish system tags (<user_query>, <system-reminder>, task-notification blocks) that would otherwise bury the actual conversation. Codex is harder: it ships in two different on-disk formats, a newer item.completed shape and a legacy event_msg/response_item shape with turn tracking, so agentscript detects which one it’s looking at and forks to the right parser at runtime.

The part I like is the normalization past mere parsing. Codex records a shell run as an exec_command event and a file edit as an apply_patch blob; agentscript re-shapes both into the same Bash command block and Edit/Write tool blocks Claude Code emits natively. So --tools Bash means the same thing whichever agent wrote the session, and a handoff reads consistently regardless of its origin. Do that reconciliation in one place and the rest of the tool, including every extraction workflow above, gets to be provider-agnostic for free.

Search is deliberately dumb

There’s no index, no fuzzy matching, no embeddings. search walks the default roots (~/.claude/projects, ~/.codex/sessions), re-parses every transcript it finds, and does a case-insensitive substring scan over each block’s text, tool name, and serialized tool input. That’s O(transcripts × file size) on every call, and I’m fine with it. This is a local tool pointed at a few hundred sessions on my own disk, not a log pipeline. An index would buy speed and cost me cache invalidation, staleness, and a daemon to keep warm; substring-scanning a directory is instant at this scale with zero moving parts, and when it stops being fast enough I’ll add one, not a day sooner. One unglamorous reality underneath it: a full session is sometimes a single absurdly long JSON line, so files are read whole and scanned with a buffer sized for 32MB lines. A naive scanner chokes on exactly the transcript you most want to read.

A Go binary, like everything else in my toolbelt

Every small tool I build for myself ends up a Go CLI for the same reasons: it compiles to a single static binary with zero runtime dependencies, it’s fast, and it runs on any platform without a Node or Python install to babysit. agentscript is Go 1.26, standard library only, with no third-party modules at all, which is why the whole thing is one file you can drop on a fresh machine and run. It still installs the way the rest of my toolbelt does, npm i -g @amxv/agentscript, because the npm package is a thin shim that unpacks the right prebuilt binary per platform and falls back to go build. It’s the same shape as webctx and cf-cli, spun out of the same Go CLI template, with a static binary, npm release automation, and a zuedocs documentation site, so standing up a new one is mostly writing the part that’s actually new. The code splits cleanly along the same line as the idea: an app layer owns the CLI surface and the terminal picker, and a transcript package owns the domain, including discovery, the two parsers, the block model, and the text/markdown/JSON renderers. It’s a small tool with a narrow job, and it earns its keep every time I need to know what one of my agents actually did.

Use and to navigate