A terminal TUI that gathers the chat sessions every AI coding harness leaves on your machine — Claude Code, Codex, Gemini CLI, and more — into one browsable, groupable, deletable view. Think of it as a terminal-native alternative to Claude Code UI, covering every harness at once.
Status: functional. Browsing, search, grouping, permanent deletion with Markdown export, and orphan cleanup all work across Claude, Codex, Gemini, and opencode.
Each harness scatters sessions in its own format and location: Claude under
~/.claude/projects/<encoded-cwd>/, Codex in a date tree under
~/.codex/sessions/, Gemini as mutation logs under ~/.gemini/tmp/<hash>/, and
opencode as rows in a shared SQLite database. Over time these pile up alongside
orphaned folders pointing at projects you deleted long ago. sx reads them all,
lets you scroll any transcript, and permanently removes the ones you no longer
want.
- Unified browser — sessions grouped by harness, then by project.
- Scrollable transcripts — normalized rendering across every harness.
- Orphan detection — finds session folders whose project is gone, plus stray temp files, and reports why each is flagged.
- Permanent delete with guardrails — dry-run preview, typed confirmation
for bulk operations, a path allowlist, an active-session guard, and an
append-only deletion log. No accidental
rm -rf. - Pre-delete Markdown export — optionally archive a transcript to Markdown before removing it (also available as a standalone export action).
- Forward-looking — harnesses you have not installed yet appear greyed out and light up the moment their session store shows up on disk.
| Harness | Status | Store |
|---|---|---|
| Claude Code | ✅ verified | ~/.claude/projects/<encoded-cwd>/<id>.jsonl |
| Codex | ✅ verified | ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl |
| Gemini CLI | ✅ verified | ~/.gemini/tmp/<hash>/chats/session-*.jsonl |
| opencode | ✅ verified | ~/.local/share/opencode/opencode.db (SQLite) |
| Qwen Code | 🔎 dormant | Gemini-fork layout |
| Continue | 🔎 dormant | ~/.continue/sessions/*.json |
| Goose | 🔎 dormant | ~/.local/share/goose/sessions/ |
| Cline | 🧩 dormant | ~/.cline/data/workspaces/<id>/ |
| Cursor | 🧩 dormant | ~/.cursor/ (SQLite) |
| Crush | 🧩 dormant | SQLite |
✅ verified on a real machine · 🔎 format known · 🧩 needs probing once installed
You need uv. Pick one of the two ways below.
uvx fetches, builds, and runs sx in a throwaway environment — nothing is
added to your PATH:
uvx --from git+https://github.com/junxit/agentic-session-explorer.git sxTyping that every time is a mouthful, so add an sx alias to your shell. The
alias also sets SX_NO_UPDATE_CHECK=1, because uvx already pulls the latest
build — the in-app "update available" prompt would be redundant here.
zsh — append to ~/.zshrc:
alias sx='SX_NO_UPDATE_CHECK=1 uvx --from git+https://github.com/junxit/agentic-session-explorer.git sx'bash — append to ~/.bashrc (Linux) or ~/.bash_profile (macOS):
alias sx='SX_NO_UPDATE_CHECK=1 uvx --from git+https://github.com/junxit/agentic-session-explorer.git sx'fish — append to ~/.config/fish/config.fish:
alias sx 'env SX_NO_UPDATE_CHECK=1 uvx --from git+https://github.com/junxit/agentic-session-explorer.git sx'Then reload (source ~/.zshrc, etc.) and run sx, sx list, sx --version —
arguments pass straight through the alias.
uvx caches builds, so a plain run may reuse a recent one. To force the newest
commit, add --refresh to the alias's command, or pin a released tag with
...explorer.git@v0.2.0.
This puts a persistent sx on your PATH:
uv tool install git+https://github.com/junxit/agentic-session-explorer.git(From a local clone, uv tool install . works too.)
Upgrade:
uv tool upgrade sx
# or force the very latest commit on main:
uv tool install --force git+https://github.com/junxit/agentic-session-explorer.gitUninstall:
uv tool uninstall sxWhen you run an installed sx interactively, it checks GitHub at most once a
day for a newer release and prints a one-line notice (the TUI shows a toast).
The check is cached, times out fast, fails silently, and never blocks startup.
Turn it off with --no-update-check or by exporting SX_NO_UPDATE_CHECK=1.
sx # launch the interactive TUI
sx list # list every discovered session as plain text
sx harnesses # show all known harnesses and their status
sx version # show the installed version and check for a newer one
sx update # show (or, with --run, execute) the upgrade command| Key | Action |
|---|---|
↑/↓ or j/k |
Move the selection |
enter |
Open the highlighted session's transcript |
g / G |
Jump to top / bottom of a transcript |
m |
Cycle grouping: project → date → recency |
/ |
Filter by title or project (live) |
e |
Export the highlighted session to Markdown |
d |
Permanently delete the highlighted session (with preview) |
o |
Open the orphan-cleanup screen |
r |
Re-scan all harness stores |
q |
Quit |
A session written within the last 90 seconds is flagged ● LIVE; deleting one
requires typing DELETE to confirm. Bulk orphan deletion requires typing
DELETE <n>. Exports default to ./session-exports/ and never overwrite an
existing file. Every deletion is appended to ./sx-deletions.log (set
SX_LOG_FILE to keep one log across directories).
flowchart TD
TUI["Textual TUI"] --> REG["Adapter registry"]
CLI["sx list (CLI)"] --> REG
REG --> BASE["HarnessAdapter (abstract)"]
BASE --> JF["JsonlFolderAdapter"]
JF --> CL["ClaudeAdapter"]
JF --> CX["CodexAdapter"]
JF --> GM["GeminiAdapter"]
BASE --> OC["OpencodeAdapter\n(SQLite, row-level delete)"]
BASE --> DORM["dormant adapters\n(Cline, Cursor, Crush, ...)"]
REG --> DEL["DeleteService\n(guards + op-log)"]
REG --> EXP["MarkdownExporter"]
Every adapter normalizes its harness into the same Session and Message
types, so the transcript viewer, the exporter, and the delete flow are each
written once and work for all harnesses — present and future. Most harnesses
store one file per session; opencode keeps all sessions as rows in a shared
SQLite database, so its adapter subclasses HarnessAdapter directly and deletes
a session's rows (and its session_diff sidecar) without ever touching the
database file or other sessions. Its shared log/ files are left alone.
uv sync --extra dev
uv run sx list
uv run pytestThe update check compares the installed version against the latest GitHub
release (falling back to the highest vX.Y.Z tag). To publish an update:
-
Bump
versioninpyproject.tomland__version__insrc/sx/__init__.py. -
Commit, then tag and release:
git tag v0.2.0 && git push --tags gh release create v0.2.0 --generate-notes
Installed copies will then prompt their users to upgrade.
sx deletes permanently — there is no trash or undo. Everything below exists so
that the confirmation you see is the whole truth about what is about to go:
- The preview is complete. It lists the files to be removed, any non-file
work (a database-backed harness reports the row count), what a folder actually
contains — including nested transcripts and
memory/files — and anything the guard will refuse. - Failures are never reported as success. A refused deletion says so, keeps its row, and leaves the file on disk.
- A store-root allowlist blocks any target outside a harness's own directory, and refuses a store root itself.
- Live sessions need a typed
DELETE. Liveness comes from the harness (file mtime, or the database column that tracks activity); when it can't be determined the session is treated as live rather than assumed safe. - Bulk actions need a typed
DELETE <n>, with the count derived from the same list that will be deleted. - Export never overwrites. Colliding archive names get a suffix, so "export before deleting" cannot destroy the archive it just made.
- Untrusted transcript text can't drive your terminal. Control sequences are
replaced with visible glyphs (
␛,␇) in both the TUI and exported Markdown. - Every deletion is logged to
./sx-deletions.log(owner-only), and a logging failure is surfaced rather than swallowed. The log is per-directory; setSX_LOG_FILEto collect every deletion in one place.
Sessions whose project lives on an unmounted volume are treated as unavailable, not deleted, so unplugging a drive never turns real transcripts into cleanup candidates.
MIT