ai-conversation-extractor
Convert AI conversation transcripts (Claude Code, Codex CLI, ChatGPT/Gemini JSONL; Cursor IDE SQLite storage) to clean, readable Markdown files. Strips binary blobs (base64 images, PDFs — typically 95%+ of file size) while preserving all human-readable content: user messages, assistant text, thinking, tool calls, and tool results.
See scripts/README.md for full JSONL format documentation.
Supported Formats
Auto-detected from file content — no flags needed:
| Format | Source | Typical location |
|---|---|---|
| Claude Code | Claude Code CLI sessions | ~/.claude/projects/*/*.jsonl |
| Codex CLI | OpenAI Codex CLI sessions (event stream with timestamp/type/payload) | ~/.codex/sessions/*.jsonl |
| Codex history | Codex CLI prompt history (session_id/ts/text) — user prompts only | ~/.codex/history.jsonl |
| Simple | ChatGPT / Gemini exports (role/message) | varies |
| Cursor | Cursor IDE chat/composer conversations (SQLite, via cursor_extract.py) | ~/Library/Application Support/Cursor/User/globalStorage/state.vscdb |
Usage
Run the script at scripts/extract.py (within this skill's directory):
bash# Single file — outputs .md alongside the .jsonl python3 <skill-dir>/scripts/extract.py <file.jsonl> # Single file with custom output path python3 <skill-dir>/scripts/extract.py <file.jsonl> -o output.md # All .jsonl in a directory python3 <skill-dir>/scripts/extract.py <directory> # Recursive (includes subdirectories) python3 <skill-dir>/scripts/extract.py <directory> --recursive # Messages-only view (user + final assistant response per turn, no tools/thinking) python3 <skill-dir>/scripts/extract.py <file.jsonl> --ua-final-only # Batch convert to a specific output directory python3 <skill-dir>/scripts/extract.py <directory> --out-dir ./converted/ # Keep Codex environment context and boilerplate in messages-only mode python3 <skill-dir>/scripts/extract.py <file.jsonl> --ua-final-only --keep-env-context --keep-agent-boilerplate
No external dependencies — uses only Python 3.12+ stdlib (json, re, pathlib, argparse, sqlite3).
Cursor IDE conversations
Cursor stores conversations in a global SQLite DB (cursorDiskKV table: composerData:<id> metadata rows + bubbleId:<composerId>:<bubbleId> message rows; old versions inline the conversation in composerData). Use scripts/cursor_extract.py (opens the DB read-only, safe with Cursor running):
bash# List all conversations (newest first): composerId, last-updated, message count, title python3 <skill-dir>/scripts/cursor_extract.py --list [--limit 50] # Find conversations whose content matches a regex (full DB scan — can take minutes on multi-GB DBs) python3 <skill-dir>/scripts/cursor_extract.py --grep "AccessManager|rate.?limit" # Extract specific conversations to Markdown (named <title-slug>-<id8>.md) python3 <skill-dir>/scripts/cursor_extract.py <composerId> [<composerId>...] --out-dir ./converted/ # Extract everything / use a custom DB path (e.g. Linux/Windows) python3 <skill-dir>/scripts/cursor_extract.py --all --out-dir ./converted/ --db <path/to/state.vscdb>
Output uses the same Markdown conventions as extract.py. Notes: per-message timestamps are not stored by Cursor (only conversation-level created/updated times, used for frontmatter and file mtime); tool results in very recent Cursor versions may live in encrypted blobs and show as tool calls without results.
CLI Flags
| Flag | Description |
|---|---|
-o, --output | Custom output path (single-file mode only) |
-r, --recursive | Recurse into subdirectories |
--ua-final-only | Messages-only view: keeps user messages and final assistant response per turn, drops tool calls/results/thinking |
--out-dir | Output directory for batch mode (writes <stem>.md into this folder) |
--keep-env-context | With --ua-final-only: retain Codex <environment_context> user messages (dropped by default) |
--keep-agent-boilerplate | With --ua-final-only: retain Codex AGENTS.md/skills boilerplate messages (dropped by default) |
What Gets Stripped
- Base64-encoded images and documents (biggest savings)
<system-reminder>tags injected by the toolfile-history-snapshotrecords (editor state backups)progressrecords (hook events)systemrecords (turn durations, metadata)- Meta messages (
isMeta: true, like/initinjections) - Line-number prefixes from file reads (
123→)
What Gets Preserved
- User text messages (full)
- Assistant text responses (full)
- Assistant thinking blocks (truncated at 3000 chars, in
<details>tags) - Tool calls (name + JSON input, truncated at 4000 chars)
- Tool results (text content, full; no truncation)
- Image/document placeholders (e.g.,
*[attached document: application/pdf]*) - Error results from rejected tool uses
- Per-message timestamps (Claude Code format only)
- Source file metadata (YAML frontmatter) and filesystem mtime (output .md matches source .jsonl mtime for natural sort-by-date)
Output Format
- YAML frontmatter:
source,modified(ISO 8601) for parseable metadata - Filesystem: Output .md file's modification time is set to match the source .jsonl, so
ls -tor Finder sort-by-date preserves conversation order - Markdown with
## User/## Assistantheadings separated by---. Per-message timestamps shown as*2026-02-09T23:51:07Z*when available (Claude Code format). Tool calls formatted as**Tool: \Name`**with JSON code blocks. Tool results asResult (`id`):` with code blocks.


