Skip to content
yteraokaPublic

About

Interactive CLI to browse and resume Claude Code sessions from any directory

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ccr

日本語

ccr is a small CLI tool written in Go that lets you resume a Claude Code session from any directory. It shows an interactive picker of your past sessions, then cds into the selected session's original working directory and replaces itself via exec with claude --resume <session_id>.

Session files are looked up under the CLAUDE_CONFIG_DIR environment variable, or $HOME/.claude if it isn't set.

By default only sessions for the current directory are listed; pass -g to also list sessions from other directories.

ai-title and last-prompt information from the session file is shown as well.

Typing v in the list renders the session as HTML and opens it in your web browser.

Installation

Homebrew

brew tap yteraoka/cask
brew trust yteraoka/cask
brew install --cask ccr

mise

mise use -g github:yteraoka/ccr@0.0.1

Go

go install github.com/yteraoka/ccr/cmd/ccr@latest

Or build from a checkout:

go build -o ccr ./cmd/ccr

Usage

ccr

OR

ccr -g

By default, only sessions belonging to the current directory are targeted (matched the same way Claude Code encodes ${CLAUDE_CONFIG_DIR}/projects/<dir> — every character outside a-zA-Z0-9 in the cwd becomes -). Adding -g targets sessions from every project instead.

On a machine with no browser — a server you are working on over SSH, typically — add -n (-no-browser) so that v only prints the transcript URL instead of trying to open it. CCR_NO_BROWSER=1 does the same without the flag, and ccr behaves that way on its own whenever there is no way to open a browser at all. See Viewing a full transcript (v).

The terminal splits into two panes:

  • Top pane — sessions sorted by recency (most recently active first), with columns TIMESTAMP (local time), SESSION ID, PID (only shown for sessions with a currently running claude process — see below), TOKENS (cumulative token usage across the session's assistant messages), and CWD (basename). On a terminal too narrow to fit both (under 95 columns), the session id is shortened to its first 8 characters so CWD stays readable. The bottom line of this pane shows the available keys.
  • Bottom pane — a live preview of the highlighted session: its full session id (even when the list above shortens it), directory, title (if Claude Code has generated one), file size, token usage broken down by kind (in / out / cache write / cache read) behind the total, start/end time, and the last few prompts you sent in that session.

Keybindings

Key Action
↑/k/p, ↓/j/n Move the cursor
Space/PageDown, b/Backspace/PageUp Page through the list
g, G Jump to the first or last session
/ Filter the list as you type (id and directory)
Enter Resume the selected session (cd + exec claude --resume <id>)
i Inspect the selected session's raw jsonl without leaving the terminal
v View the full transcript of the selected session in your browser (see below)
q, Esc, Ctrl-C Quit without doing anything

Environment variables

  • CLAUDE_CONFIG_DIR — where Claude Code stores its data. Defaults to ${HOME}/.claude.
  • BROWSER — the command used to open the transcript viewer (see below). Follows the common convention: if any word contains %s, the URL is substituted there; otherwise the URL is appended as the last argument. If BROWSER is unset, macOS falls back to opening the URL with open; on other platforms there is nothing to fall back to, so v shows the URL instead of opening it.
  • CCR_NO_BROWSER — set it to 1 (anything but 0, false, no, or off) to make v show the transcript URL instead of opening a browser, the same as passing -n.
  • If a .envrc file exists in the destination directory, it is loaded via direnv exec.

Inspecting the raw jsonl (i)

Pressing i opens the selected session's file in a full-screen viewer, without leaving the terminal. It lists one row per line — the line number in the file, its type, its timestamp, and the start of the raw text — n and p step a line, Space pages the list down, b and Backspace page it up, / searches it incrementally, and i (or Enter) opens the line under the cursor as pretty-printed JSON, in a modal floating over the list.

Filtering works the same on both lists: type and only the matching rows stay, with the prompt showing how many of how many. Enter ends the typing but keeps the list narrowed, so the ordinary keys then move, resume and open within it; Esc clears the filter, and only then does it leave the screen. On the session list a query matches the id and the directory; in the file it matches the whole raw line, so tool_use or a path finds the lines carrying it. Enter only means resume on the picker's own list, so it is free here.

Inside the modal, n and p step to the next and previous line and show it straight away, so you can walk the file without closing and reopening it at every line; the list behind follows along. Every movement key means the same on both screens: n/p step a line, Space pages down, and b and Backspace page up. The list stays visible behind it, dimmed, so opening a line never loses your place in the file. The modal wraps to its width and scrolls. Lines that are not valid JSON are listed too and shown as they are: seeing them is the point of a raw preview. q/Esc steps back out, first closing the modal and then the viewer.

Viewing a full transcript (v)

Pressing v on a session renders its entire jsonl transcript as a self-contained, light-themed HTML page — Human and AI turns are visually distinguished, prose is rendered as Markdown, and commands/diffs/file content are syntax-highlighted. Notifications the harness injects on your behalf (a sub agent reporting its result, a monitor event) arrive as user lines but are labelled 🔔 Notification rather than attributed to you.

Runs of back-to-back tool calls are folded into a single collapsed N tool calls block, with each call collapsed again inside it and individually expandable — its summary names the tool and what it acted on, so the transcript reads as a conversation until you open one. A failed call is coloured on that summary line, so you can spot it without opening anything. A turn that also says something keeps its own card and ends the run.

A filter pane on the right toggles what the page shows: message kinds (Human, Claude, Notification, System, and thinking blocks) and each tool that was actually called (Read, Edit, Bash, …), with a count next to each and All/None buttons. Only kinds present in that session get a row, and a message left with nothing visible is hidden along with its contents. Assistant turns that report token usage show it in the card header next to the timestamp, broken down the same way as in the picker.

If the file records a cost-state line, the header shows what the session cost, with a collapsed breakdown of the time it spent and what each model was asked to do. Sessions written without one show no cost at all.

Every event carries a { } button that shows the original jsonl line behind it. The JSON is not embedded in the page — that would roughly double a transcript already measured in megabytes — so the page holds only the line's offset and length and asks the server for those bytes when you press the button.

If the session spawned sub agents, each one's own transcript is a click away: next to the tool call that started it, on the report it sent back, and listed under Sub agents in the pane. Those pages render exactly like a session and link back to the one they belong to. Command output, diffs, and file content are collapsed by default (click to expand) so the page stays scannable.

The page isn't written to a file: ccr starts a small local HTTP server (the first free port starting at 8000) that renders each session on demand at http://localhost:<port>/<session_id>, then opens that URL via $BROWSER. The server is started once per ccr run and reused for every session you view afterwards; once it's running, the preview pane shows Serving at: <url> for the highlighted session even if you haven't pressed v on it yet. The picker keeps running — it doesn't exit after opening a transcript.

Without a browser

Run ccr -n (or set CCR_NO_BROWSER=1) and v starts the server without opening anything, printing serving at http://localhost:<port>/<session_id> on the picker's bottom line for you to copy. ccr does this by itself when there is no browser to open — $BROWSER unset with no platform fallback — so a headless machine needs no flag at all.

The server only listens on 127.0.0.1, so to read the page from another machine, forward the port over SSH:

ssh -L 8000:localhost:8000 user@server

Then open the URL that ccr printed, on your own machine.

Detecting already-running sessions

Claude Code records a snapshot of every running (or not cleanly terminated) session at ${CLAUDE_CONFIG_DIR}/sessions/<pid>.json, including its pid and sessionId. Before building the session list, ccr reads these files and checks that the recorded pid is both alive and actually a claude process; stale or mismatched entries are ignored (the files themselves are never deleted). For sessions with a live process, the PID column in the list shows it.

License

MIT — see LICENSE.

About

Interactive CLI to browse and resume Claude Code sessions from any directory

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages