Navigate, search, copy, and export your AI coding-agent sessions (opencode, Claude Code, ...) from one local, read-only tool.
Project description
scrollback
Browse, search, copy, and export your AI coding-agent sessions from one local, read-only tool. scrollback reads the conversation history that agents like opencode and Claude Code already keep on disk and gives you a single, consistent view across them — from a scriptable command line or a local web app.
Everything is local-first and strictly read-only: scrollback never modifies, locks for writing, or uploads your data.
You can use it two ways. From the command line, list, search, and export your sessions in a single scriptable tool:
Or open the local web app to read a transcript in full — with rendered Markdown, syntax-highlighted code, and typeset LaTeX math:
Both views read the same on-disk session stores, so you can jump between them freely. (The screenshots above use synthetic demo data.)
For AI agents: read
CONTRIBUTING.mdfor the project conventions. This README is for human readers.
Contents
- Why scrollback
- Install
- Quick start
- The command line
- The web app
- Running it as an app
- Fast search (optional index)
- Supported sources
- Configuration
- Safety
- Development
- License
Why scrollback
AI coding agents persist rich session data locally, but each in its own format and with no convenient way to browse that history or take it with you. scrollback fills that gap with four things:
- See any past conversation as a readable transcript.
- Search across every session by keyword (title or full text).
- Export a session to Markdown, JSON, HTML, or plain text.
- Copy a message or a whole session straight to your clipboard.
Its niche among similar tools: pure Python, works equally from the CLI and a web UI, reads multiple agents directly from their live on-disk stores (no sync step, no plugins, no upload), and treats export and copy as first-class.
Install
pip install "scrollback[all]" # CLI + web app + native window + colour
Requires Python 3.10+. The bare CLI has no runtime dependencies (standard library only); optional features come from extras:
"scrollback[web]"— the local web app (FastAPI, uvicorn) and the native app window (pywebview)."scrollback[rich]"— coloured terminal output."scrollback[all]"— everything a user might want at runtime (web+rich).
If you'd rather keep it isolated from your system Python (recommended, but
optional), pipx installs it in its own environment
and still puts the scrollback command on your PATH:
pipx install "scrollback[all]"
Either way, plain pip works the same; pipx is just a convenience.
From a local clone (for development), use an editable install with the dev extra:
pip install -e ".[web,dev]"
Quick start
scrollback doctor # what was detected on this machine?
scrollback list # recent sessions, newest first
scrollback show latest # print the most recent transcript
scrollback web # open the browser UI
scrollback doctor is the best first command: it reports which agents
were found, how many sessions each has, and which optional features are
available.
The command line
The CLI is organised around a few verbs. Commands that operate on a single
session accept a selector: a full id, a unique prefix, a
source-qualified id (opencode:ses_0eae9810), or the keyword latest.
Listing and viewing
scrollback list --source opencode -n 10 # one source, 10 rows
scrollback list --dir myproject # filter by directory substring
scrollback list -q "refactor" # filter by title substring
scrollback list --since 2026-06-01 --until 2026-06-30 # date range
scrollback list --usage # add cost + token (in/out) columns
scrollback list -n 20 --page 2 # pagination (page size = --limit)
scrollback show latest --reasoning # include the model's thinking
scrollback show <selector> --no-tools # hide tool calls and output
By default, subagent sessions (for example opencode @explore subagents)
are folded under their parent; pass --no-fold to list them flat.
Output is coloured when the rich extra is installed and the output is a
terminal; piping, or --plain, falls back to plain text.
Searching
scrollback search "merge conflict" # full-text across all sessions
scrollback search "ssh" --source opencode --json
Search scans message text across sessions. On a large history you can make it near-instant with an optional index.
Exporting and copying
scrollback export latest -f markdown -o session.md
scrollback export <selector> -f html -o session.html
scrollback export <selector> -f html --math rendered -o session.html
scrollback export <selector> -f json # to stdout
scrollback copy latest -f markdown # render and copy to the clipboard
The formats are markdown (md), json, html, and text (txt).
Markdown, HTML, and text honour --reasoning (include the model's
thinking) and --no-tools (omit tool calls and their output); JSON is a
faithful structured dump with bulky raw blobs stripped for readability.
Exported HTML and Markdown render the assistant's Markdown with syntax-
highlighted code, and the HTML is a self-contained file that prints well.
Mathematical notation in delimited LaTeX ($...$, $$...$$, \(...\),
\[...\]) is preserved verbatim in every format, never mangled by the
Markdown pass. --math controls how the HTML export treats it: raw
(verbatim source, the default), latex (verbatim, marked never-to-typeset
— best for pasting into a paper), or rendered (typeset with KaTeX, which
is embedded into the file with its fonts so the equations render offline).
In the web app the same choice is a math: toggle in the transcript header.
Stats and resume
scrollback stats # totals, by-source + top projects
scrollback resume latest # print the native resume command
scrollback resume <selector> --copy # ...and copy it to the clipboard
stats aggregates session counts, message/token/cost totals, and your
busiest projects. resume prints the command to continue a session in its
own agent (for example opencode --session <id> or claude --resume <id>),
with a cd into the session's project directory.
A note on token figures. Where the source records it, scrollback reports tokens in four buckets — input, output, cache read, and cache write — because they mean different things and are priced very differently. In agentic sessions the conversation context is re-sent every turn but served from the prompt cache, so cache reads usually dominate total volume while costing a fraction of fresh input. "Total tokens" is therefore not one number; the cost figure (when available) is the most faithful summary of consumption. Sources that don't record a given figure show it as blank rather than a misleading zero.
The web app
scrollback web starts a local, read-only browser UI — FastAPI plus a
small vanilla-JavaScript frontend with no build step — bound to
127.0.0.1. Open it with scrollback web (a browser tab),
scrollback web --window (a standalone browser window), or
scrollback web --app (a native desktop window; see
Running it as an app).
What it offers:
- A browse / stats view switch in the header; the brand mark resets everything to the initial state.
- A session list with source-filter chips and date filters, loading incrementally as you scroll.
- An explicit search scope toggle — search session titles, message contents, or both at once (combined results are grouped).
- Subagents collapsed under their parent, expandable on demand (including Claude Code's nested sidechain transcripts).
- A transcript reader with a collapsible frozen header (auto-
collapses as you scroll; toggle with
h) over a scrolling message body, Markdown rendering with syntax highlighting, LaTeX math (source / paste-ready / typeset), in-transcript find, show-reasoning / show-tools toggles, and per-message and per-session copy. - A stats page with usage broken down per tool (sessions, messages,
input/output/cache tokens, and cost where the tool records it) plus an
overall total; it respects the same
since/untildate filters. - Export (Markdown / HTML / JSON), print, a light/dark theme,
and keyboard navigation (
/search,j/kmove,Enteropen,ffind,hcollapse header,Escblur).
On a narrow window (for example split-screen) the session list collapses
into a slide-in drawer you open with the sessions button, so browsing
still works when there isn't room for a permanent sidebar.
Large transcripts open instantly because the app loads a session's header
first and then pages messages in as you scroll, rather than transferring an
entire multi-megabyte transcript at once. Deep links work too: the open
session is reflected in the URL hash (#opencode/<id>), and ?q=<text>
pre-fills a content search.
Running it as an app
You don't have to type a command every time. After pip install ".[web]":
-
Short commands are on your
PATH:scrollback-web(a browser tab) andscrollback-app(a native window). -
A double-clickable launcher is one command away:
scrollback install-launcher # both: Desktop launcher + .app (macOS) scrollback install-launcher --desktop # only the Desktop launcher scrollback install-launcher --app-bundle # only the ~/Applications/.app (macOS)
With no flags it installs everything for your OS; the two flags let you pick just one. The Desktop launcher is
scrollback.commandon macOS,scrollback.baton Windows, and an application-menu entry plusscrollback.shon Linux.--app-bundlebuilds an~/Applications/scrollback.appon macOS and falls back to the Desktop launcher on other platforms (where there is no.app). Use--dest <dir>to place artifacts elsewhere.
The launchers open a native window via pywebview when it is available:
no browser tab, no terminal, and closing the window stops the server and
frees the port. On a system where pywebview cannot run (for example a
headless Linux box without a GTK/Qt WebKit backend), scrollback falls back
to a standalone browser window that auto-stops the server shortly after the
window closes. All of this behaviour is decided in Python, so the launcher
scripts stay free of OS-specific assumptions and ship inside the package
for pip install users.
To clean up, scrollback uninstall removes the artifacts scrollback
created — the launchers, the macOS .app, the optional search index, and the
launcher log — after a confirmation (--yes to skip it, --dry-run to
preview). It never touches your agent data, and it does not remove the Python
package itself: it prints the right pip/pipx uninstall command to finish
the job (a program can't reliably uninstall the package it is running from).
Fast search (optional index)
By default, search is a lexical scan over your live data: zero setup, always correct, but its cost grows with the size of your history. For a large corpus, build an optional full-text index:
scrollback index # one-time build; re-run to update (incremental)
scrollback index --stats # show what's indexed
scrollback index --clear # delete the index
The index is a separate SQLite FTS5 database at
~/.cache/scrollback/index.db (override with SCROLLBACK_INDEX). It is
derived and disposable: your source data is never modified, and deleting
the index simply reverts to the lexical scan. Re-running index only
re-processes new or changed sessions and prunes deleted ones; the web app
also refreshes it in the background on startup when it is stale.
Once built, both the CLI and the web app use it automatically, turning a
multi-second query into a few milliseconds. If your Python's SQLite was
built without FTS5, index says so and search keeps working without it.
Supported sources
| Source | Reads | Default location |
|---|---|---|
opencode |
SQLite (session / message / part), read-only |
~/.local/share/opencode/opencode.db |
claudecode |
per-project JSONL transcripts + nested subagent sidechains | ~/.claude/projects/ |
codex |
per-session rollout-*.jsonl rollouts |
~/.codex/sessions/ |
aider |
per-project .aider.chat.history.md Markdown logs |
set SCROLLBACK_AIDER_DIRS to opt in |
More agents (Gemini CLI, Zed, VS Code Copilot Chat, GitHub Copilot CLI) are
researched and queued — see ROADMAP.md.
Adding another agent is a small, self-contained change: implement the
Source interface in src/scrollback/sources/base.py and register it in
src/scrollback/sources/registry.py. The CLI, search, export, web app,
and index all work against the common model automatically — see the
opencode (SQLite) and Claude Code (JSONL) adapters as references, and
CONTRIBUTING.md for the conventions.
Configuration
scrollback reads each agent's data from its default location, but you can point it elsewhere, and you can control how the web server binds:
| Variable | Purpose |
|---|---|
SCROLLBACK_OPENCODE_DB |
path to opencode.db |
SCROLLBACK_CLAUDE_DIR |
path to ~/.claude or ~/.claude/projects |
SCROLLBACK_CODEX_DIR |
path to ~/.codex or ~/.codex/sessions |
SCROLLBACK_AIDER_DIRS |
colon-separated dirs to scan for Aider history (opt-in) |
SCROLLBACK_PORT |
web server port (default 8765; or use --port) |
SCROLLBACK_HOST |
web server bind host (default 127.0.0.1; or use --host) |
SCROLLBACK_INDEX |
path to the search index database |
The web server defaults to 127.0.0.1. If the chosen port is busy,
scrollback automatically picks the next free one (--strict-port fails
instead). Binding to a non-loopback host prints a warning, since the
read-only API is unauthenticated.
Safety
scrollback is read-only by design, and the design is enforced:
- The opencode SQLite database is opened with
mode=ro— it is never created or written, and reads are safe against a live write-ahead log. - Claude Code JSONL files are read as read-only.
- A test asserts the opencode database's modification time is unchanged
across reads (
tests/test_sources_live.py). - The web app binds to localhost, rejects unexpected
Hostheaders (a DNS-rebinding guard), and sanitizes rendered transcript content.
Development
pip install -e ".[web,dev]"
pytest -q # tests
ruff check src tests # lint
See CONTRIBUTING.md for project conventions (the
read-only invariant, stdlib-first dependencies, platform-agnostic code,
and how to add a new agent source) and CHANGELOG.md for
what has landed so far.
License
MIT — see LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file scrollback-0.3.0.tar.gz.
File metadata
- Download URL: scrollback-0.3.0.tar.gz
- Upload date:
- Size: 1.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
94ee09faf7c5235720cf8c154a463d6b84425aebd16fbb49fe4e40d65c9660b7
|
|
| MD5 |
0d5709c449037fe47a469eb851b183ab
|
|
| BLAKE2b-256 |
fe7a34afcbaf5b91195d1fa43a00f347c8a20587ff750b0062bc420ae3e79382
|
Provenance
The following attestation bundles were made for scrollback-0.3.0.tar.gz:
Publisher:
publish.yml on a-attia/scrollback
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scrollback-0.3.0.tar.gz -
Subject digest:
94ee09faf7c5235720cf8c154a463d6b84425aebd16fbb49fe4e40d65c9660b7 - Sigstore transparency entry: 2037107141
- Sigstore integration time:
-
Permalink:
a-attia/scrollback@e52fda030d3c179a3d590c62dffb72683f77600d -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/a-attia
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e52fda030d3c179a3d590c62dffb72683f77600d -
Trigger Event:
push
-
Statement type:
File details
Details for the file scrollback-0.3.0-py3-none-any.whl.
File metadata
- Download URL: scrollback-0.3.0-py3-none-any.whl
- Upload date:
- Size: 605.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9fa7bd392b607c7967577f68f0e61ad9ebf050af6a704bb9a261df7f5d98f45
|
|
| MD5 |
1701f455342c632ece385a0c583a5988
|
|
| BLAKE2b-256 |
c7fcfc1bd64184e07049ebe6af3936b00b889cfed2e91da79c2035db3923d430
|
Provenance
The following attestation bundles were made for scrollback-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on a-attia/scrollback
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
scrollback-0.3.0-py3-none-any.whl -
Subject digest:
a9fa7bd392b607c7967577f68f0e61ad9ebf050af6a704bb9a261df7f5d98f45 - Sigstore transparency entry: 2037107871
- Sigstore integration time:
-
Permalink:
a-attia/scrollback@e52fda030d3c179a3d590c62dffb72683f77600d -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/a-attia
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e52fda030d3c179a3d590c62dffb72683f77600d -
Trigger Event:
push
-
Statement type: