Start a session in Claude Code, hit a usage limit or a wall, and pick it up in Codex with the full conversation, reasoning, and tool history intact:
txcript maps each harness's native transcript format through a typed common model. Native load/save is byte-lossless; cross-harness conversion preserves messages, reasoning, tool calls, tool results, images, metadata, and usage where available. It ships as a CLI, a Rust crate, and an npm package.
Highlights
- 12 harnesses, one model: every format converts through
Transcript<Common>, so adding a harness connects it to all the others. - A format for everyone else: agents txcript has never heard of emit the documented Simple interchange JSON — a file or a stream, handed to txcript directly — and their transcripts continue in any supported harness.
- Byte-lossless round-trips: loading and saving a session in its own format reproduces it exactly.
- Continue anywhere:
txcript continue <id> --with <harness>rewrites a session into another harness's native format and launches it. The original is never modified. - Search everything: fuzzy/substring search across every session on the machine (fzf-style syntax, powered by nucleo), as a library API, a one-shot CLI query, or an interactive picker.
- MCP server:
txcript mcpexposes read-onlylist_sessions,search_sessions, andread_sessiontools, so agents can mine past sessions as context. - Documented formats: every harness's on-disk format is written up in
docs/formats/, with provenance for each claim (official docs, source permalinks, or reverse-engineering notes).
Supported harnesses
flowchart LR
claude["Claude Code"] <--> common(("Transcript<Common>"))
codex["Codex"] <--> common
opencode["OpenCode"] <--> common
pi["pi"] <--> common
campfire["Campfire"] <--> common
common <--> cursor["Cursor CLI"]
common <--> cursordesktop["Cursor desktop"]
common <--> grok["Grok CLI"]
common <--> antigravity["Antigravity"]
simple["Simple (any agent)"] --> common
hermes["Hermes Agent"] --> common
amp["Amp"] --> common
Discovery, listing, search, view, and native round-trips work for every harness with a local store. The id strings are what the CLI and WASM APIs take.
| Harness | id | Sessions on disk | Native format | Convert | Continue into | Doc |
|---|---|---|---|---|---|---|
| Claude Code | claude_code |
~/.claude/projects/ |
JSONL | ⇄ | ✓ | spec |
| Codex | codex |
~/.codex/sessions/ |
rollout JSONL | ⇄ | ✓ | spec |
| OpenCode | opencode |
~/.local/share/opencode/opencode.db |
SQLite | ⇄ | ✓ | spec |
| pi | pi |
~/.pi/agent/sessions/ |
JSONL | ⇄ | ✓ | spec |
| Campfire | campfire |
~/.campfire/agent/sessions/ |
JSONL | ⇄ | ✓ | spec |
| Cursor CLI | cursor |
~/.cursor/chats/ |
SQLite | ⇄ | ✓ | spec |
| Cursor desktop | cursor_desktop |
<Cursor User dir>/globalStorage/ |
SQLite | ⇄ | ✓ | — |
| Grok CLI | grok |
~/.grok/sessions/ |
JSON session dir | ⇄ | ✓ | spec |
| Hermes Agent | hermes |
~/.hermes/state.db |
SQLite | → | — 3 | spec |
| Amp | amp |
~/.local/share/amp/threads/ |
thread JSON | → | — 1 | spec |
| Antigravity | antigravity |
~/.gemini/antigravity-cli/ |
SQLite | ⇄ | ✓ | spec |
| Simple | simple |
— 2 | interchange JSON | → | — 2 | spec |
1 Amp threads are server-side and the CLI has no import: sessions convert from Amp, but can't be continued into it.
2 Simple is txcript's own interchange format — the on-ramp for any agent not listed above. There is no app and no managed directory: a Simple session is a document (a file, or stdin) handed to txcript continue directly, and the continued conversation lives in the target harness from then on.
3 Hermes's state.db is read-only in txcript and Hermes has no session-import command: sessions convert from Hermes, but can't be continued into it.
Install
CLI (installs the txcript binary):
# or from a checkout: cargo install --path cli
Rust crate:
npm package (prebuilt WASM, no Rust toolchain needed):
CLI
Discover local sessions and continue one in any harness:
|
continue writes the session where the target harness keeps its sessions, then launches that harness on it, handing over the terminal:
- Same-harness: resumes the original in place.
- Cross-harness (
--with): re-synthesizes the session into the target's native format. What is written is always a copy; the source session is never modified or removed. - A Simple document instead of an id —
txcript continue ./run.json --with claude_code, ormy-agent | txcript continue - --with claude_code— brings any agent's transcript in the same way;--withis required since a document has no harness of its own. - The launch command is per-harness and overridable: set
TRANSCRIPT_<HARNESS>_RESUME_CMDto a{id}template, e.g.TRANSCRIPT_CODEX_RESUME_CMD="codex resume {id}".
view prints the session as compact text, each message numbered by a ── #N ── rule. #range selects messages by those printed ordinals, 1-based and inclusive:
abc#7: message 7 onlyabc#5-12: messages 5 through 12abc#5-: message 5 to the endabc#-10: start through message 10
continue accepts the same suffix and continues just those messages as a new session. A range that would cut a tool call away from its result is refused, and the error suggests the nearest valid range.
Search
The picker is dependency-free (raw-mode ANSI): type to filter with fzf-style fuzzy syntax, arrows / ctrl-p/n to move, Enter to continue the selection in its own harness (or --with), Esc to cancel. Every row shows which kind of content matched: user text, assistant text, thinking, tool use, tool output, or session metadata.
MCP server
Exposes three read-only tools; their optional filters match the CLI:
list_sessions(from?, cwd?)search_sessions(pattern, from?, cwd?)read_session(id, from?)
* Omitting from includes every harness; omitting cwd applies no directory filter. Sessions without a recorded working directory match only when cwd is omitted.
Shell completions
Rust crate
[]
= "0.6"
# Drops the OpenCode SQLite store (rusqlite); the OpenCode codec stays available.
# txcript = { version = "0.6", default-features = false }
Three layers, smallest to largest:
Codec:to_common/from_commonper harness;convert::<A, B>chains them through the canonical model.TextCodec:from_text/to_textto parse and render a harness's native session text, no I/O.Store: discover/load/save against a real backend (session directories, or SQLite DBs for OpenCode and both Cursors).
Convert in memory (no filesystem):
use ;
use ;
let claude = from_text?; // Transcript<ClaudeCode>
let codex = ?; // Transcript<Codex>
let codex_text = to_text?; // native rollout JSONL
Or go through disk with a Store:
use ;
use ;
let store = default_root.expect;
let found = store.discover?; // cheap metadata scan
let claude = store.load?; // Transcript<ClaudeCode>
let codex = ?;
default_root.expect.save?; // resumable on disk
The canonical model is Transcript<Common>: Meta + Vec<Message>, where a Message holds typed Blocks (Text, Thinking, ToolUse, ToolResult, Image) and a typed Tool enum.
Slash commands the user ran at the harness (/release patch) are canonical too: a Tool::Command call on the user turn, paired with what the command printed back as its ToolResult.
Search (feature search, on by default)
txcript::search supports fuzzy and substring search over transcripts via nucleo. One-shot search:
use ;
let hits = search; // fzf syntax: 'exact ^prefix !not
for hit in hits
For picker-style search, build an Index once and query it per keystroke:
use ;
let mut index = new;
index.insert; // re-insert replaces; caller owns refresh
let matches = index.query; // ranked docs, best lines as hits
An empty pattern returns documents newest-first. Tool outputs are excluded by default; use Origin::ALL to include them. Query.harnesses, Query.limit, and Query.hits_per_doc narrow results.
Text projection
txcript::text::to_text(&common) is the projection behind txcript view: a one-way, token-conscious rendering of Transcript<Common> for use as LLM context. It keeps messages, reasoning text, and compact tool calls/results; replay-only payloads (encrypted reasoning, usage accounting, inline image bytes) are omitted. to_text_fragment(&common, &span) renders a Span of the body, keeping each message's ordinal in the full session.
npm package
The npm package ships the codec as prebuilt WASM for Bun, Node, and browsers. The JS host owns all I/O and calls in for the transformation; the Store layer (filesystem, SQLite, subprocess) stays native and is excluded from the WASM build.
import { convert, toCommon, fromCommon, harnesses } from "txcript";
import { readFileSync, writeFileSync } from "node:fs";
const input = readFileSync("rollout.jsonl", "utf8");
// native -> native (e.g. a Codex rollout into Claude Code's JSONL)
writeFileSync("session.jsonl", convert(input, "codex", "claude_code"));
// canonical view, and back
const common = JSON.parse(toCommon(input, "codex")); // { meta, messages }
const pi = fromCommon(JSON.stringify(common), "pi");
harnesses(); // ["claude_code","codex","opencode","pi","campfire","cursor","cursor_desktop","grok","hermes","amp","antigravity","simple"]
Text-in / text-out: input is the source harness's native session text and the result is the target's. Invalid harness names or unparseable input throw a JS Error.
| Harness | Session text |
|---|---|
claude_code, codex, pi, campfire |
session JSONL |
opencode |
opencode export JSON |
cursor |
JSON export of the session's store.db |
cursor_desktop |
JSON dump of the session's state.vscdb rows |
grok |
JSON bundle of the session directory's files |
hermes |
hermes sessions export JSON object |
amp |
amp threads export JSON |
antigravity |
JSON dump of the conversation database, protobuf blobs hex-encoded |
simple |
the Simple interchange JSON document |
To build the wasm from source instead:
Format documentation
Not all of these transcript formats are documented by their vendors. docs/formats/ has one document per harness covering where sessions live on disk, how discovery finds them, a dissection of every part of the format, and its quirks, each tagged with the provenance of what it claims: official documentation, the harness's own open-source serialization code (cited with commit-pinned permalinks), or reverse engineering.
Development
&&
The binary lives in its own workspace crate (cli/, package txcript-cli) so its dependencies (clap) never touch library consumers.