claude-scrollback 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,202 @@
1
+ Metadata-Version: 2.4
2
+ Name: claude-scrollback
3
+ Version: 0.1.0
4
+ Summary: Lightweight viewer for Claude Code session transcripts
5
+ License-Expression: MIT
6
+ Keywords: claude,anthropic,claude-code,ai,transcript,viewer
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Environment :: Console
9
+ Classifier: Environment :: Web Environment
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Utilities
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ Provides-Extra: build
16
+ Requires-Dist: build; extra == "build"
17
+ Requires-Dist: twine; extra == "build"
18
+
19
+ # claude-scrollback
20
+
21
+ A lightweight viewer for [Claude Code](https://claude.ai/claude-code) session transcripts. Converts `.jsonl` session files into readable, interactive HTML — with a built-in server for browsing and a static site generator for archiving.
22
+
23
+ Pure python, no dependencies to install.
24
+
25
+ **[Live demo →](https://alexdej.github.io/claude-scrollback/)**
26
+
27
+ ## Install
28
+
29
+ pip (recommended)
30
+ ```bash
31
+ pip install claude-scrollback
32
+ ```
33
+
34
+ from source:
35
+
36
+ ```bash
37
+ git clone https://github.com/alexdej/claude-scrollback
38
+ pip install -e claude-scrollback/
39
+ ```
40
+
41
+ Or use `generator.py` standalone:
42
+
43
+ ```bash
44
+ git clone https://github.com/alexdej/claude-scrollback
45
+ python claude-scrollback/claude_scrollback/generator.py <dir>
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ ### Quick start
51
+
52
+ Installation installs a `claude-scrollback` command.
53
+
54
+ ```bash
55
+ # Browse all your Claude Code sessions (uses ~/.claude/projects/ automatically)
56
+ claude-scrollback view
57
+
58
+ # Browse sessions for the current Claude Code project directory
59
+ claude-scrollback view .
60
+
61
+ # Browse sessions for a specific project
62
+ claude-scrollback view ~/projects/myapp
63
+
64
+ # View a single session file
65
+ claude-scrollback view path/to/session.jsonl
66
+ ```
67
+
68
+ All directory modes start a local server and open your browser automatically.
69
+
70
+ `claude-scrollback` is the command installed by `pip install claude-scrollback`. If you're running from a clone without installing, use `python -m claude_scrollback` in place of `claude-scrollback` throughout. Add a shell alias if you want something shorter:
71
+
72
+ ```bash
73
+ alias sb='claude-scrollback'
74
+ ```
75
+
76
+ ### Subcommands
77
+
78
+ ```
79
+ claude-scrollback view [path] [-p PORT] [-n]
80
+ claude-scrollback show [uuid]
81
+ claude-scrollback generate [path] [-o OUTDIR]
82
+
83
+ view start a local server and open the session browser
84
+ show find and open a session by UUID
85
+ generate generate static HTML from session files
86
+ ```
87
+
88
+ ### view
89
+
90
+ ```bash
91
+ claude-scrollback view # all projects, port 8080, opens browser
92
+ claude-scrollback view . -p 9000 # current project, custom port
93
+ claude-scrollback view . -n # start server without opening browser
94
+ claude-scrollback view session.jsonl # convert single file, open in browser
95
+ ```
96
+
97
+ Options: `-p/--port PORT` (default: 8080), `-n/--no-open`
98
+
99
+ ### show
100
+
101
+ Find a session by UUID and open it in the browser. Searches `~/.claude/projects/` automatically.
102
+
103
+ ```bash
104
+ claude-scrollback show abc123de-f456-7890-abcd-ef1234567890
105
+
106
+ # Pipe in any text containing UUIDs — all matching sessions are opened
107
+ git log --format="%B" | claude-scrollback show
108
+ git show HEAD | claude-scrollback show
109
+ ```
110
+
111
+ ### generate
112
+
113
+ Generate a static HTML site from session files.
114
+
115
+ ```bash
116
+ claude-scrollback generate # all projects -> ./_site/
117
+ claude-scrollback generate . -o ~/my-archive/ # current project, custom output dir
118
+ claude-scrollback generate session.jsonl # single file -> session.html
119
+ ```
120
+
121
+ Options: `-o/--out-dir DIR` (default: `_site/`)
122
+
123
+ ### Path resolution
124
+
125
+ When you pass a project directory (like `.`), scrollback maps it to the corresponding Claude Code sessions folder automatically. Claude Code stores sessions under `~/.claude/projects/` with the project path encoded as the directory name (colons, slashes, and backslashes replaced with `-`):
126
+
127
+ ```
128
+ ~/projects/myapp -> ~/.claude/projects/-home-you-projects-myapp/
129
+ ~/work/another-project -> ~/.claude/projects/-home-you-work-another-project/
130
+ ```
131
+
132
+ If the path you give already contains `.jsonl` files (directly or in subdirectories), it's used as-is.
133
+
134
+ ## What it renders
135
+
136
+ Each session page shows:
137
+
138
+ - **Human messages** and **Claude responses** in distinct styled bubbles
139
+ - **Tool calls** (Read, Edit, Bash, Glob, Write, etc.) collapsible with full inputs
140
+ - **Tool results** collapsible, truncated for large outputs
141
+ - **Thinking blocks** collapsible when present
142
+ - **Context compaction markers** when Claude Code summarised the context mid-session
143
+ - **API errors** (rate limits, auth failures) surfaced inline
144
+ - **Token usage** per response
145
+ - **Session metadata**: working directory, git branch, start/end time, message and tool call counts
146
+ - **Resume command** — one click copies `cd <project>` and `claude --resume <session-id>` to clipboard
147
+
148
+ The index page lists all sessions sorted newest-first with a live filter box and per-session metadata pills.
149
+
150
+ > **Note:** Session transcripts include everything shown to Claude during the conversation — file contents, command output, environment details, and more. Review before sharing or publishing.
151
+
152
+ ## Linking sessions to commits
153
+
154
+ Include the session ID as a git trailer when committing AI-assisted work:
155
+
156
+ ```
157
+ Fix authentication token refresh race condition
158
+
159
+ Claude-Session: abc123de-f456-7890-abcd-ef1234567890
160
+ ```
161
+
162
+ The session ID is shown in the metadata card on each session page with a copy button. The resume command (also one-click copyable) lets you pick up the conversation right where it left off.
163
+
164
+ To find and open sessions referenced in your git log:
165
+
166
+ ```bash
167
+ # Open all Claude-sessions from recent history
168
+ git log --format="%B" | claude-scrollback show
169
+
170
+ # Open the session from a specific commit
171
+ git show <commit> | claude-scrollback show
172
+
173
+ # Or open a session directly by UUID
174
+ claude-scrollback show <uuid>
175
+ ```
176
+
177
+ ## Session directory layout
178
+
179
+ Claude Code organises sessions by project under `~/.claude/projects/`:
180
+
181
+ ```
182
+ ~/.claude/projects/
183
+ -home-you-projects-myapp/
184
+ abc123.jsonl
185
+ def456.jsonl
186
+ -home-you-work-another-project/
187
+ ...
188
+ ```
189
+
190
+ `scrollback` handles both flat directories (one project) and nested trees (all projects).
191
+
192
+ ## Example sessions
193
+
194
+ The `example/projects/` directory contains synthetic sessions demonstrating the viewer across different scenarios. To browse them:
195
+
196
+ ```bash
197
+ claude-scrollback view example/projects/
198
+ ```
199
+
200
+ ## Requirements
201
+
202
+ Python 3.8+, standard library only.
@@ -0,0 +1,184 @@
1
+ # claude-scrollback
2
+
3
+ A lightweight viewer for [Claude Code](https://claude.ai/claude-code) session transcripts. Converts `.jsonl` session files into readable, interactive HTML — with a built-in server for browsing and a static site generator for archiving.
4
+
5
+ Pure python, no dependencies to install.
6
+
7
+ **[Live demo →](https://alexdej.github.io/claude-scrollback/)**
8
+
9
+ ## Install
10
+
11
+ pip (recommended)
12
+ ```bash
13
+ pip install claude-scrollback
14
+ ```
15
+
16
+ from source:
17
+
18
+ ```bash
19
+ git clone https://github.com/alexdej/claude-scrollback
20
+ pip install -e claude-scrollback/
21
+ ```
22
+
23
+ Or use `generator.py` standalone:
24
+
25
+ ```bash
26
+ git clone https://github.com/alexdej/claude-scrollback
27
+ python claude-scrollback/claude_scrollback/generator.py <dir>
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ### Quick start
33
+
34
+ Installation installs a `claude-scrollback` command.
35
+
36
+ ```bash
37
+ # Browse all your Claude Code sessions (uses ~/.claude/projects/ automatically)
38
+ claude-scrollback view
39
+
40
+ # Browse sessions for the current Claude Code project directory
41
+ claude-scrollback view .
42
+
43
+ # Browse sessions for a specific project
44
+ claude-scrollback view ~/projects/myapp
45
+
46
+ # View a single session file
47
+ claude-scrollback view path/to/session.jsonl
48
+ ```
49
+
50
+ All directory modes start a local server and open your browser automatically.
51
+
52
+ `claude-scrollback` is the command installed by `pip install claude-scrollback`. If you're running from a clone without installing, use `python -m claude_scrollback` in place of `claude-scrollback` throughout. Add a shell alias if you want something shorter:
53
+
54
+ ```bash
55
+ alias sb='claude-scrollback'
56
+ ```
57
+
58
+ ### Subcommands
59
+
60
+ ```
61
+ claude-scrollback view [path] [-p PORT] [-n]
62
+ claude-scrollback show [uuid]
63
+ claude-scrollback generate [path] [-o OUTDIR]
64
+
65
+ view start a local server and open the session browser
66
+ show find and open a session by UUID
67
+ generate generate static HTML from session files
68
+ ```
69
+
70
+ ### view
71
+
72
+ ```bash
73
+ claude-scrollback view # all projects, port 8080, opens browser
74
+ claude-scrollback view . -p 9000 # current project, custom port
75
+ claude-scrollback view . -n # start server without opening browser
76
+ claude-scrollback view session.jsonl # convert single file, open in browser
77
+ ```
78
+
79
+ Options: `-p/--port PORT` (default: 8080), `-n/--no-open`
80
+
81
+ ### show
82
+
83
+ Find a session by UUID and open it in the browser. Searches `~/.claude/projects/` automatically.
84
+
85
+ ```bash
86
+ claude-scrollback show abc123de-f456-7890-abcd-ef1234567890
87
+
88
+ # Pipe in any text containing UUIDs — all matching sessions are opened
89
+ git log --format="%B" | claude-scrollback show
90
+ git show HEAD | claude-scrollback show
91
+ ```
92
+
93
+ ### generate
94
+
95
+ Generate a static HTML site from session files.
96
+
97
+ ```bash
98
+ claude-scrollback generate # all projects -> ./_site/
99
+ claude-scrollback generate . -o ~/my-archive/ # current project, custom output dir
100
+ claude-scrollback generate session.jsonl # single file -> session.html
101
+ ```
102
+
103
+ Options: `-o/--out-dir DIR` (default: `_site/`)
104
+
105
+ ### Path resolution
106
+
107
+ When you pass a project directory (like `.`), scrollback maps it to the corresponding Claude Code sessions folder automatically. Claude Code stores sessions under `~/.claude/projects/` with the project path encoded as the directory name (colons, slashes, and backslashes replaced with `-`):
108
+
109
+ ```
110
+ ~/projects/myapp -> ~/.claude/projects/-home-you-projects-myapp/
111
+ ~/work/another-project -> ~/.claude/projects/-home-you-work-another-project/
112
+ ```
113
+
114
+ If the path you give already contains `.jsonl` files (directly or in subdirectories), it's used as-is.
115
+
116
+ ## What it renders
117
+
118
+ Each session page shows:
119
+
120
+ - **Human messages** and **Claude responses** in distinct styled bubbles
121
+ - **Tool calls** (Read, Edit, Bash, Glob, Write, etc.) collapsible with full inputs
122
+ - **Tool results** collapsible, truncated for large outputs
123
+ - **Thinking blocks** collapsible when present
124
+ - **Context compaction markers** when Claude Code summarised the context mid-session
125
+ - **API errors** (rate limits, auth failures) surfaced inline
126
+ - **Token usage** per response
127
+ - **Session metadata**: working directory, git branch, start/end time, message and tool call counts
128
+ - **Resume command** — one click copies `cd <project>` and `claude --resume <session-id>` to clipboard
129
+
130
+ The index page lists all sessions sorted newest-first with a live filter box and per-session metadata pills.
131
+
132
+ > **Note:** Session transcripts include everything shown to Claude during the conversation — file contents, command output, environment details, and more. Review before sharing or publishing.
133
+
134
+ ## Linking sessions to commits
135
+
136
+ Include the session ID as a git trailer when committing AI-assisted work:
137
+
138
+ ```
139
+ Fix authentication token refresh race condition
140
+
141
+ Claude-Session: abc123de-f456-7890-abcd-ef1234567890
142
+ ```
143
+
144
+ The session ID is shown in the metadata card on each session page with a copy button. The resume command (also one-click copyable) lets you pick up the conversation right where it left off.
145
+
146
+ To find and open sessions referenced in your git log:
147
+
148
+ ```bash
149
+ # Open all Claude-sessions from recent history
150
+ git log --format="%B" | claude-scrollback show
151
+
152
+ # Open the session from a specific commit
153
+ git show <commit> | claude-scrollback show
154
+
155
+ # Or open a session directly by UUID
156
+ claude-scrollback show <uuid>
157
+ ```
158
+
159
+ ## Session directory layout
160
+
161
+ Claude Code organises sessions by project under `~/.claude/projects/`:
162
+
163
+ ```
164
+ ~/.claude/projects/
165
+ -home-you-projects-myapp/
166
+ abc123.jsonl
167
+ def456.jsonl
168
+ -home-you-work-another-project/
169
+ ...
170
+ ```
171
+
172
+ `scrollback` handles both flat directories (one project) and nested trees (all projects).
173
+
174
+ ## Example sessions
175
+
176
+ The `example/projects/` directory contains synthetic sessions demonstrating the viewer across different scenarios. To browse them:
177
+
178
+ ```bash
179
+ claude-scrollback view example/projects/
180
+ ```
181
+
182
+ ## Requirements
183
+
184
+ Python 3.8+, standard library only.
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ claude-scrollback CLI
4
+ Usage: claude-scrollback <command> [path] [options]
5
+ python -m claude_scrollback <command> [path] [options]
6
+ """
7
+
8
+ import re
9
+ import sys
10
+ import argparse
11
+ import tempfile
12
+ import webbrowser
13
+ import threading
14
+ import time
15
+ from pathlib import Path
16
+
17
+ from .generator import generate_html, process_directory
18
+ from .server import run as run_server
19
+
20
+
21
+ # ── Path resolution ────────────────────────────────────────────────────────
22
+
23
+ def map_project_to_sessions(project_path: Path) -> Path:
24
+ """
25
+ Map a project directory to its ~/.claude/projects/ entry.
26
+ Claude Code encodes the path by replacing : / \\ with -.
27
+ e.g. C:\\Users\\alex\\Projects\\myapp -> C--Users-alex-Projects-myapp
28
+ """
29
+ path_str = str(project_path.resolve())
30
+ mapped = path_str.replace("\\", "-").replace("/", "-").replace(":", "-")
31
+ return Path.home() / ".claude" / "projects" / mapped
32
+
33
+
34
+ def find_sessions_dir(path_str: str) -> Path:
35
+ """
36
+ Given a path string, return the sessions directory to use.
37
+
38
+ Resolution order:
39
+ 1. Path has .jsonl files directly or recursively -> use as-is
40
+ 2. Otherwise -> map to ~/.claude/projects/<encoded>/
41
+ """
42
+ path = Path(path_str).resolve()
43
+ if not path.exists():
44
+ raise FileNotFoundError(f"Path not found: {path}")
45
+
46
+ if any(path.glob("*.jsonl")) or any(path.rglob("*.jsonl")):
47
+ return path
48
+
49
+ mapped = map_project_to_sessions(path)
50
+ if mapped.exists() and any(mapped.rglob("*.jsonl")):
51
+ print(f"Using sessions from {mapped}")
52
+ return mapped
53
+
54
+ raise FileNotFoundError(
55
+ f"No Claude Code sessions found.\n"
56
+ f" Checked: {path}\n"
57
+ f" Checked: {mapped}\n"
58
+ f"Run from a Claude Code project directory, or pass the sessions path directly."
59
+ )
60
+
61
+
62
+ def default_sessions_dir() -> Path:
63
+ """Return ~/.claude/projects/ if it exists and has sessions."""
64
+ default = Path.home() / ".claude" / "projects"
65
+ if default.exists() and any(default.rglob("*.jsonl")):
66
+ return default
67
+ raise FileNotFoundError(
68
+ "~/.claude/projects/ not found or empty.\n"
69
+ "Pass a path: claude-scrollback view <project-or-sessions-dir>"
70
+ )
71
+
72
+
73
+ def resolve(path_arg):
74
+ """Resolve optional path arg to a sessions directory."""
75
+ if path_arg:
76
+ return find_sessions_dir(path_arg)
77
+ return default_sessions_dir()
78
+
79
+
80
+ def open_browser(url: str, delay: float = 0.4):
81
+ """Open browser after a short delay (gives server time to start)."""
82
+ def _open():
83
+ time.sleep(delay)
84
+ try:
85
+ webbrowser.open(url)
86
+ except Exception:
87
+ pass
88
+ threading.Thread(target=_open, daemon=True).start()
89
+
90
+
91
+ # ── Subcommands ────────────────────────────────────────────────────────────
92
+
93
+ def cmd_view(args):
94
+ # Single file
95
+ if args.path and Path(args.path).is_file():
96
+ src = Path(args.path)
97
+ if src.suffix != ".jsonl":
98
+ print(f"Error: {src} is not a .jsonl file")
99
+ sys.exit(1)
100
+ out = src.with_suffix(".html")
101
+ print(f"Generating {out} ...")
102
+ generate_html(src, out)
103
+ url = out.resolve().as_uri()
104
+ if not args.no_open:
105
+ webbrowser.open(url)
106
+ else:
107
+ print(f"Open: {url}")
108
+ return
109
+
110
+ try:
111
+ sessions_dir = resolve(args.path)
112
+ except FileNotFoundError as e:
113
+ print(f"Error: {e}")
114
+ sys.exit(1)
115
+
116
+ url = f"http://localhost:{args.port}"
117
+ if not args.no_open:
118
+ open_browser(url)
119
+ run_server(sessions_dir, args.port)
120
+
121
+
122
+
123
+ UUID_RE = re.compile(
124
+ r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
125
+ re.IGNORECASE,
126
+ )
127
+
128
+ def cmd_show(args):
129
+ # Collect UUIDs from positional arg and/or piped stdin
130
+ uuids = []
131
+ if args.uuid:
132
+ if UUID_RE.fullmatch(args.uuid):
133
+ uuids.append(args.uuid.lower())
134
+ else:
135
+ print(f"Error: '{args.uuid}' is not a valid UUID")
136
+ sys.exit(1)
137
+
138
+ if not sys.stdin.isatty():
139
+ text = sys.stdin.read()
140
+ for m in UUID_RE.finditer(text):
141
+ u = m.group(0).lower()
142
+ if u not in uuids:
143
+ uuids.append(u)
144
+
145
+ if not uuids:
146
+ print("Error: provide a UUID argument or pipe text containing UUIDs")
147
+ sys.exit(1)
148
+
149
+ projects = Path.home() / ".claude" / "projects"
150
+ opened = 0
151
+ for uuid in uuids:
152
+ matches = list(projects.rglob(f"{uuid}.jsonl"))
153
+ if not matches:
154
+ print(f"Warning: no session found for {uuid}")
155
+ continue
156
+ for src in matches:
157
+ out = Path(tempfile.mkdtemp()) / (src.stem + ".html")
158
+ generate_html(src, out)
159
+ webbrowser.open(out.resolve().as_uri())
160
+ print(f"Opened: {src.name}")
161
+ opened += 1
162
+
163
+ if opened == 0:
164
+ sys.exit(1)
165
+
166
+
167
+ def cmd_generate(args):
168
+ if args.path and Path(args.path).is_file():
169
+ src = Path(args.path)
170
+ if src.suffix != ".jsonl":
171
+ print(f"Error: {src} is not a .jsonl file")
172
+ sys.exit(1)
173
+ out = src.with_suffix(".html")
174
+ generate_html(src, out)
175
+ print(f"Written to {out}")
176
+ return
177
+
178
+ try:
179
+ sessions_dir = resolve(args.path)
180
+ except FileNotFoundError as e:
181
+ print(f"Error: {e}")
182
+ sys.exit(1)
183
+
184
+ out_dir = Path(args.out_dir)
185
+ print(f"Generating static site from {sessions_dir} -> {out_dir} ...")
186
+ process_directory(sessions_dir, out_dir)
187
+
188
+
189
+ # ── Main ───────────────────────────────────────────────────────────────────
190
+
191
+ def main():
192
+ parser = argparse.ArgumentParser(
193
+ prog="claude-scrollback",
194
+ description="Lightweight viewer for Claude Code session transcripts.",
195
+ )
196
+ sub = parser.add_subparsers(dest="command", metavar="command")
197
+ sub.required = True
198
+
199
+ # view
200
+ p_view = sub.add_parser(
201
+ "view",
202
+ help="start a local server and open the session browser",
203
+ )
204
+ p_view.add_argument(
205
+ "path", nargs="?",
206
+ help="session file (.jsonl), sessions dir, or project dir "
207
+ "(default: ~/.claude/projects/)",
208
+ )
209
+ p_view.add_argument("-p", "--port", type=int, default=8080, help="port (default: 8080)")
210
+ p_view.add_argument("-n", "--no-open", action="store_true", help="don't open browser")
211
+
212
+ # show
213
+ p_show = sub.add_parser(
214
+ "show",
215
+ help="find and open a session by UUID (also accepts piped text)",
216
+ )
217
+ p_show.add_argument(
218
+ "uuid", nargs="?",
219
+ help="session UUID (or omit and pipe text containing UUIDs)",
220
+ )
221
+
222
+ # generate
223
+ p_gen = sub.add_parser(
224
+ "generate",
225
+ help="generate static HTML from session files",
226
+ )
227
+ p_gen.add_argument(
228
+ "path", nargs="?",
229
+ help="session file (.jsonl), sessions dir, or project dir "
230
+ "(default: ~/.claude/projects/)",
231
+ )
232
+ p_gen.add_argument(
233
+ "-o", "--out-dir", default="_site", metavar="DIR",
234
+ help="output directory (default: _site/)",
235
+ )
236
+
237
+ args = parser.parse_args()
238
+
239
+ if args.command == "view":
240
+ cmd_view(args)
241
+ elif args.command == "show":
242
+ cmd_show(args)
243
+ elif args.command == "generate":
244
+ cmd_generate(args)
245
+
246
+
247
+ if __name__ == "__main__":
248
+ main()