# @molecule/api-agent-transcript

> Reads a coding agent's session export (Claude Code, Codex, Molecule IDE) into one normalized session: turns, models and the files the agent wrote

Latest version **1.0.1** (published 2026-09-24) · Apache-2.0 license · 0 weekly downloads

## Install

```sh
npm install @molecule/api-agent-transcript
pnpm add @molecule/api-agent-transcript
yarn add @molecule/api-agent-transcript
bun add @molecule/api-agent-transcript
```

## Health

**Score 75/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; has provenance; recently updated; high maintenance score; high quality score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 1.0.1 |
| Published | 2026-09-24 |
| First published | 2026-09-24 |
| Weekly downloads | 0 |
| License | Apache-2.0 |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 42.1 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 44 |
| Author | Molecule Dev, Inc. |
| Maintainers | vialoh |
| Keywords | molecule, agent, transcript, session, claude-code, codex, provenance |

## Links

- npm: https://www.npmjs.com/package/@molecule/api-agent-transcript
- Repository: https://github.com/molecule-dev/molecule
- Homepage: https://www.molecule.dev/packages/api-agent-transcript
- Issues: https://github.com/molecule-dev/molecule/issues
- npm.io page: https://npm.io/package/@molecule/api-agent-transcript

## Alternatives

- [@clerk/clerk-expo](https://npm.io/package/@clerk/clerk-expo.md) — 133.6K weekly downloads
- [@pothos/plugin-authz](https://npm.io/package/@pothos/plugin-authz.md) — 12.4K weekly downloads
- [@bounded-sh/client](https://npm.io/package/@bounded-sh/client.md) — 3.2K weekly downloads
- [@luigi-project/plugin-auth-oauth2](https://npm.io/package/@luigi-project/plugin-auth-oauth2.md) — 2.3K weekly downloads
- [@nocobase/plugin-verification](https://npm.io/package/@nocobase/plugin-verification.md) — 2.0K weekly downloads

## Recent versions

- 1.0.1 (latest) — 2026-09-24
- 1.0.0 — 2026-09-24

## README

<!--
AUTO-GENERATED — DO NOT EDIT THIS FILE.
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
To change this document, edit the module-level JSDoc in src/index.ts.
Generated: 2026-09-24T11:13:30.218Z
-->

# @molecule/api-agent-transcript

> **Auto-generated, AI-first package reference** for the [molecule.dev](https://molecule.dev) ecosystem.
> It is written to be read by coding agents as much as by people, and is generated from this
> package's source — edit `src/index.ts` JSDoc, not this file.

One normalized shape for a coding agent's session, whatever harness recorded it.

Defines `AgentSession` — the turns of a conversation (the user's messages as
typed, the assistant's prose replies, and the files each assistant turn
wrote) — and the `AgentTranscriptReader` contract that format bonds
implement. Readers, one per real export format: `@molecule/api-agent-transcript-claude-code`
(the `/export` text and the session `.jsonl`), `-codex` (the Markdown export
and the rollout `.jsonl`), `-molecule-ide` (a Molecule IDE conversation's
JSON). `@molecule/api-agent-transcript-autodetect` bundles all three and
picks the one that recognizes each file — bond that.

The example is what most apps want transcripts for: read a post's folder of
transcript exports at build time and attribute the post's paragraphs with
`@molecule/api-text-provenance`, writing `provenance.json`. To read one
file on its own: `readTranscript({ text, fileName })` returns the session.

## Quick Start

```typescript
// provenance.ts — runs in Node at build time (a build script or a Vite plugin), never in the page.
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'
import { dirname, join } from 'node:path'

import {
  canReadTranscript,
  readTranscript,
  setProvider as setTranscriptReader,
} from '@molecule/api-agent-transcript'
import { provider as anyTranscript } from '@molecule/api-agent-transcript-autodetect'
import { attributeText, setProvider as setAttribution } from '@molecule/api-text-provenance'
import { provider as wordOverlap } from '@molecule/api-text-provenance-overlap'

setTranscriptReader(anyTranscript) // reads Claude Code, Codex and Molecule IDE exports
setAttribution(wordOverlap)

// One block of the post, in page order. `prompt` and `model` are on every ai span.
export interface ProvenanceSpan {
  text: string // the block's markdown: a paragraph, heading, list or code block
  origin: 'human' | 'ai'
  prompt?: string // the person's message the AI was answering, as typed
  model?: string // the model that wrote it, as the transcript names it
}

// What /<slug>/provenance.json holds.
export interface Provenance {
  aiShare: number // 0..1, the share of the post's words the AI wrote
  words: number
  aiWords: number
  prompts: string[] // the distinct prompts behind the ai spans, in page order
  spans: ProvenanceSpan[]
}

// The post's top-level blocks: front matter dropped, split on blank lines, never inside a code fence.
export function markdownBlocks(markdown: string): string[] {
  const body = markdown.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '')
  const blocks: string[] = []
  let lines: string[] = []
  let inFence = false
  for (const line of body.split(/\r?\n/)) {
    if (/^\s*(`{3}|~{3})/.test(line)) inFence = !inFence
    if (!inFence && line.trim() === '') {
      if (lines.length > 0) blocks.push(lines.join('\n'))
      lines = []
    } else {
      lines.push(line)
    }
  }
  if (lines.length > 0) blocks.push(lines.join('\n'))
  return blocks
}

// Attribute one post from its markdown file and the folder holding its transcript exports.
// A missing or empty folder is a 100% human post; files that are not transcripts are skipped.
export function postProvenance(markdownFile: string, transcriptDir: string): Provenance {
  const blocks = markdownBlocks(readFileSync(markdownFile, 'utf8'))
  const files = existsSync(transcriptDir)
    ? readdirSync(transcriptDir, { withFileTypes: true })
        .filter((entry) => entry.isFile())
        .map((entry) => entry.name)
        .sort()
    : []
  const sessions = files
    .map((name) => ({ text: readFileSync(join(transcriptDir, name), 'utf8'), fileName: name }))
    .filter((input) => canReadTranscript(input))
    .map((input) => readTranscript(input))
  const result = attributeText({ paragraphs: blocks, sessions })
  return {
    aiShare: result.aiShare,
    words: result.words,
    aiWords: result.aiWords,
    prompts: result.prompts,
    spans: result.paragraphs.map((p): ProvenanceSpan => {
      const text = blocks[p.index]
      return p.origin === 'ai'
        ? { text, origin: 'ai', prompt: p.prompt, model: p.model }
        : { text, origin: 'human' }
    }),
  }
}

// Write provenance.json, creating its folder.
export function writeProvenance(outFile: string, provenance: Provenance): void {
  mkdirSync(dirname(outFile), { recursive: true })
  writeFileSync(outFile, `${JSON.stringify(provenance, null, 2)}\n`)
}

// In the build, for each PUBLISHED post (skip drafts), after the site's own build has written dist/:
// writeProvenance('dist/my-post/provenance.json', postProvenance('posts/my-post.md', 'transcripts/my-post'))
```

## Type

`core`

## Installation

```bash
npm install @molecule/api-agent-transcript @molecule/api-bond @molecule/api-i18n
```

## API

### Interfaces

#### `AgentFileWrite`

A file the assistant wrote or edited during a turn.

```typescript
interface AgentFileWrite {
  /** The path as the session recorded it (absolute or relative to the session's working directory). */
  path: string
  /** `create` = the whole file was written; `edit` = part of an existing file was replaced. */
  kind: 'create' | 'edit'
  /**
   * The text the assistant put into the file: the whole file for `create`, the
   * inserted/replacement text for `edit`. Empty when the export omits it.
   */
  text: string
  /** False when the export shows only part of the text (collapsed, truncated or elided). */
  complete: boolean
}
```

#### `AgentSession`

A whole session, normalized.

```typescript
interface AgentSession {
  /** The reader that produced it, e.g. `claude-code`, `codex`, `molecule-ide`. */
  format: string
  /** The harness's own name for itself, e.g. `Claude Code`. */
  harness: string
  /** The harness version, when the export records it. */
  harnessVersion?: string
  /** The session's model when a single one is named for the whole session. Per-turn models are on each turn. */
  model?: string
  /** ISO 8601 start time, when recorded. */
  startedAt?: string
  /** The turns, in order. */
  turns: AgentTurn[]
}
```

#### `AgentTranscriptReader`

The contract every transcript reader bond implements.

A reader recognizes its own format with `detect()` and never guesses: it
returns false for anything it does not positively recognize, so a composing
reader (`@molecule/api-agent-transcript-autodetect`) can try each in turn.

```typescript
interface AgentTranscriptReader {
  /** A stable id for the format family, e.g. `claude-code`. */
  readonly format: string
  /** A human label, e.g. `Claude Code`. */
  readonly label: string
  /**
   * Whether this reader recognizes the input.
   *
   * @param input - The transcript.
   * @returns True only when the input is positively this reader's format.
   */
  detect(input: TranscriptInput): boolean
  /**
   * Read the transcript into a normalized session.
   *
   * @param input - The transcript.
   * @returns The session.
   * @throws {Error} When the input is not this reader's format.
   */
  read(input: TranscriptInput): AgentSession
}
```

#### `AgentTurn`

One turn of the conversation. Consecutive assistant messages before the next user message form one turn.

```typescript
interface AgentTurn {
  /** Who spoke. */
  role: AgentTurnRole
  /**
   * What was said. For `user`: the message as typed (the harness's injected
   * context is never included). For `assistant`: its prose replies, joined by a
   * blank line — tool calls are not prose; the files they wrote are in `files`.
   */
  text: string
  /** ISO 8601 time of the turn's first message, when the export records it. */
  timestamp?: string
  /** The model that produced an assistant turn, when the export records it. */
  model?: string
  /** Files an assistant turn wrote. Always empty for user turns. */
  files: AgentFileWrite[]
}
```

#### `TranscriptInput`

A transcript to read: its text, and its file name when known (some readers use the extension as a hint).

```typescript
interface TranscriptInput {
  /** The file's full text. */
  text: string
  /** The file name or path, e.g. `session.jsonl`, `codex-session.md`. */
  fileName?: string
}
```

### Types

#### `AgentTurnRole`

Who said a turn. Tool calls and their results are folded into the assistant turn that made them.

```typescript
type AgentTurnRole = 'user' | 'assistant'
```

### Functions

#### `canReadTranscript(input)`

Whether the bonded reader recognizes a transcript.

```typescript
function canReadTranscript(input: TranscriptInput): boolean
```

- `input` — The transcript text and, when known, its file name.

**Returns:** True when the bonded reader can read it.

#### `getProvider()`

Retrieves the bonded transcript reader, throwing if none is configured.

```typescript
function getProvider(): AgentTranscriptReader
```

**Returns:** The bonded reader.

#### `hasProvider()`

Checks whether a transcript reader is bonded.

```typescript
function hasProvider(): boolean
```

**Returns:** `true` if a reader is bonded.

#### `readTranscript(input)`

Read a transcript into a normalized session with the bonded reader.

```typescript
function readTranscript(input: TranscriptInput): AgentSession
```

- `input` — The transcript text and, when known, its file name.

**Returns:** The normalized session.

#### `setProvider(provider)`

Registers a transcript reader as the active one. Called during application startup.

```typescript
function setProvider(provider: AgentTranscriptReader): void
```

- `provider` — The reader to bond.

## Available Providers

| Provider                       | Package                                       |
| ------------------------------ | --------------------------------------------- |
| Agent transcript autodetect    | `@molecule/api-agent-transcript-autodetect`   |
| Claude Code transcript reader  | `@molecule/api-agent-transcript-claude-code`  |
| Codex CLI transcript reader    | `@molecule/api-agent-transcript-codex`        |
| Molecule IDE transcript reader | `@molecule/api-agent-transcript-molecule-ide` |

## Injection Notes

### Requirements

Peer dependencies:

- `@molecule/api-bond` ^1.0.1
- `@molecule/api-i18n` ^1.0.1

### Runtime Dependencies

- `@molecule/api-bond`
- `@molecule/api-i18n`

- **Do NOT parse an export yourself** (splitting on `---`, `❯`, `## User`,
  role labels). The readers already drop harness noise: a user turn is only
  what the person typed — no environment blocks, slash-command echoes,
  auto-continue messages or tool results — so it is safe to show as "the
  prompt".
- **Do NOT import this from page or client code.** It is server-only and
  throws in a browser bundle; read transcripts at build time or in your API.
- **Do NOT call `readTranscript` on every file in a folder.** A file no
  reader recognizes throws. Filter with `canReadTranscript()` first, as the
  example does, so a README beside the exports is skipped.
- Keep each transcript's original file name and pass it as `fileName` — it
  is a detection hint.
- Assistant text is prose only. Tool calls are not text; the files they
  wrote are in `turn.files` (`create` = whole file, `edit` = the replacement
  text), and content an agent put in a file counts as the agent's writing.
- Exports lose detail, and `complete: false` says so. Claude Code's
  `/export` renders markdown (headings lose their `#`, bold loses its `**`)
  and collapses some edits; prefer its session `.jsonl` when you have it.
- Parsing is pure and synchronous; nothing is fetched or written.

---
_Source: https://npm.io/package/@molecule/api-agent-transcript · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
