txcript 0.2.0

A typed intermediate representation for converting AI coding-agent session transcripts between harness formats (Claude Code, Codex, OpenCode, pi).
Documentation

txcript

txcript lets you swap the agent you're using in the middle of a session.

It converts session transcripts between Claude Code, Codex, OpenCode, pi, Campfire, and Cursor. Your messages, the agent's reasoning, tool calls, and images all come across, so the new agent picks up the conversation where the old one left off.

Each harness has its own native transcript shape. This crate maps those shapes through a typed common model, then re-emits them for another harness. Native load/save stays byte-lossless; cross-harness transformation preserves the semantic conversation: messages, reasoning, tool calls, tool results, images, metadata, and usage where available.

Supported harnesses (string ids in parentheses, used by the CLI and WASM):

  • Claude Code (claude_code)
  • Codex (codex)
  • OpenCode (opencode)
  • pi (pi)
  • Campfire (campfire)
  • Cursor (cursor)

It ships three ways: a Rust library, a CLI, and a WASM module for Bun / Node / the browser.

Use as a library

[dependencies]
txcript = "0.1"
# Drops the OpenCode SQLite store (rusqlite); the OpenCode codec stays available.
# txcript = { version = "0.1", default-features = false }

Three layers, smallest to largest:

  • Codecto_common / from_common per harness; convert::<A, B> chains them through the canonical model.
  • TextCodecfrom_text / to_text: parse/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 Cursor).

Convert in memory (no filesystem):

use txcript::harness::{claude_code, codex};
use txcript::{Codec, TextCodec, convert};

let claude = claude_code::ClaudeCode::from_text(jsonl_text)?;          // Transcript<ClaudeCode>
let codex = convert::<claude_code::ClaudeCode, codex::Codex>(&claude)?; // Transcript<Codex>
let codex_text = codex::Codex::to_text(&codex)?;                       // native rollout JSONL

Or go through disk with a Store:

use txcript::harness::{claude_code, codex};
use txcript::{Store, convert};

let store = claude_code::ClaudeStore::default_root().expect("home dir");
let found = store.discover()?;                       // cheap metadata scan
let claude = store.load(&found[0].reference)?;       // Transcript<ClaudeCode>

let codex = convert::<_, codex::Codex>(&claude)?;
codex::CodexStore::default_root().expect("home dir").save(&codex)?;  // 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.

Use as a CLI

cargo install txcript        # installs the `txcript` binary

It discovers local sessions and continues one in any harness — the offline half of replay's continue --local:

txcript list                             # local sessions across every harness
txcript continue <id>                    # continue <id>, then launch its harness
    [--with <harness>]                    #   ...continuing in <harness> instead
    [--from <harness>]                    #   scope the id lookup to one harness
    [--out <dir>]                         #   write under <dir>; implies --no-resume
    [--no-resume]                         #   write the session but don't launch

continue hands the terminal to the harness when done (on Unix it execs). Same-harness continues resume the original in place; --with re-synthesizes into another harness's native format first. Override the launch command per harness with TRANSCRIPT_<HARNESS>_RESUME_CMD (a {id} template), e.g. TRANSCRIPT_CODEX_RESUME_CMD="codex resume {id}".

Use as a WASM module (Bun / Node)

The pure codec compiles to WebAssembly; 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.

Install from git

bun add git+ssh://git@github.com/skillsynchq/txcript.git

prepare builds the wasm on install, so the machine needs the Rust toolchain. Run the one-time toolchain setup, then it builds automatically:

# once per machine: wasm32 target + matching wasm-bindgen-cli
bun --cwd node_modules/txcript run setup

(Bun may ask you to trust the dependency before it runs prepare; add "txcript" to trustedDependencies in your package.json.)

Or build from a local checkout

git clone https://github.com/skillsynchq/txcript.git
cd txcript
bun run setup        # once: wasm target + wasm-bindgen-cli
bun run build        # produces ./pkg

Then import by path (e.g. as a sibling of your project), and wire it as a prebuild step:

// your project's package.json
{
  "scripts": {
    "build:txcript": "cd ../txcript && bun run build",
    "prebuild": "bun run build:txcript"
  }
}

API

import { convert, toCommon, fromCommon, harnesses } from "txcript";
// (or "../txcript/pkg/txcript.js" for a local checkout)
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"]

Text-in / text-out: input is a harness's native session text (JSONL for claude_code/codex/pi/campfire, the opencode export JSON for opencode, and a JSON export of Cursor's store.db for cursor); the result is the target's native text. Invalid harness names or unparseable input throw a JS Error.

Development

cargo test                                          # native suite
cargo test --no-default-features                    # without the SQLite store
bun run build && bun examples/convert.ts <file> <from> <to>