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.
- claude_scrollback-0.1.0/PKG-INFO +202 -0
- claude_scrollback-0.1.0/README.md +184 -0
- claude_scrollback-0.1.0/claude_scrollback/__init__.py +1 -0
- claude_scrollback-0.1.0/claude_scrollback/__main__.py +248 -0
- claude_scrollback-0.1.0/claude_scrollback/generator.py +1388 -0
- claude_scrollback-0.1.0/claude_scrollback/server.py +69 -0
- claude_scrollback-0.1.0/claude_scrollback.egg-info/PKG-INFO +202 -0
- claude_scrollback-0.1.0/claude_scrollback.egg-info/SOURCES.txt +12 -0
- claude_scrollback-0.1.0/claude_scrollback.egg-info/dependency_links.txt +1 -0
- claude_scrollback-0.1.0/claude_scrollback.egg-info/entry_points.txt +2 -0
- claude_scrollback-0.1.0/claude_scrollback.egg-info/requires.txt +4 -0
- claude_scrollback-0.1.0/claude_scrollback.egg-info/top_level.txt +1 -0
- claude_scrollback-0.1.0/pyproject.toml +29 -0
- claude_scrollback-0.1.0/setup.cfg +4 -0
|
@@ -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()
|