ccviz 0.2.0 → 0.3.1
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.
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/dist/{chunk-AQM3FQFJ.js → chunk-37WFE64V.js} +60 -1
- package/dist/cli.js +1 -1
- package/dist/server/index.js +1 -1
- package/dist/ui/assets/index-Dqwhyx6J.js +70 -0
- package/dist/ui/assets/index-uh2NuGQl.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +10 -1
- package/dist/ui/assets/index-CSxlwiwa.css +0 -1
- package/dist/ui/assets/index-D5ZG06Yx.js +0 -70
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dhruv Yadav
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# ccviz
|
|
2
|
+
|
|
3
|
+
CLI tool to visualize [Claude Code](https://docs.anthropic.com/en/docs/claude-code) conversations. Opens a browser UI to explore conversation transcripts stored in `~/.claude/`, with focus on debugging MCP tool calls, identifying context bloat, and understanding token/timing patterns.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g ccviz
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Start the browser UI
|
|
17
|
+
ccviz
|
|
18
|
+
|
|
19
|
+
# Custom port
|
|
20
|
+
ccviz --port 8080
|
|
21
|
+
|
|
22
|
+
# Don't auto-open browser
|
|
23
|
+
ccviz --no-open
|
|
24
|
+
|
|
25
|
+
# Dump a conversation as JSON (no server)
|
|
26
|
+
ccviz --json ~/.claude/projects/-Users-you-code-myproject/session-id.jsonl
|
|
27
|
+
|
|
28
|
+
# Open directly to a specific conversation
|
|
29
|
+
ccviz --conversation -Users-you-code-myproject/session-id
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
### Project Browser
|
|
35
|
+
- Hierarchical folder tree of all Claude Code projects in `~/.claude/projects/`
|
|
36
|
+
- Most recent conversations shown on the landing page
|
|
37
|
+
- Search/filter conversations by title, model, or session ID
|
|
38
|
+
|
|
39
|
+
### Conversation View
|
|
40
|
+
|
|
41
|
+
Five tabs for analyzing a conversation:
|
|
42
|
+
|
|
43
|
+
**Timeline** — Turn-by-turn view with expandable user/assistant messages. Each turn shows a context contribution bar (green→red heat scale) and expandable tool call cards with full input parameters and result previews.
|
|
44
|
+
|
|
45
|
+
**Tokens** — Stacked area chart of per-turn token usage (input, output, cache creation, cache read) and cumulative context growth line chart. Interactive tooltips and per-turn breakdown table.
|
|
46
|
+
|
|
47
|
+
**Tools** — Sortable table of all tool calls with duration, result size, and MCP server info. Aggregation bar charts for call count and result size by tool. Filter by tool name or MCP/native. Click any row to see full input/output.
|
|
48
|
+
|
|
49
|
+
**Context** — Per-tool stacked bar chart showing exactly which tools cause context spikes at each turn. Click any bar segment to inspect the tool calls. Pie chart of context consumption by tool category — click a slice to drill down and see every call with input params and results. Optimization suggestions flag large results, repeated calls, and oversized reads.
|
|
50
|
+
|
|
51
|
+
**Subagents** — List of spawned subagents with token usage comparison bars. Click to expand and see the subagent's own timeline.
|
|
52
|
+
|
|
53
|
+
## Data Source
|
|
54
|
+
|
|
55
|
+
Reads conversation transcripts from `~/.claude/projects/`. Each project directory contains `.jsonl` files (one per conversation) with the full message history, tool calls, token usage, and subagent transcripts.
|
|
56
|
+
|
|
57
|
+
## Tech Stack
|
|
58
|
+
|
|
59
|
+
- **CLI**: Node.js, Commander, `open`
|
|
60
|
+
- **Server**: Express 5
|
|
61
|
+
- **Parser**: Streaming JSONL parser (handles 25MB+ conversations)
|
|
62
|
+
- **UI**: React 19, Vite, Tailwind CSS v4, [visx](https://airbnb.io/visx/) for visualizations
|
|
63
|
+
- **Build**: tsup (server), Vite (UI)
|
|
64
|
+
|
|
65
|
+
## Development
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/dhruvyad/ccviz.git
|
|
69
|
+
cd ccviz
|
|
70
|
+
npm install
|
|
71
|
+
npm run dev # Starts Express + Vite dev server with HMR
|
|
72
|
+
npm run build # Production build
|
|
73
|
+
npm start # Run production build
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
[MIT](LICENSE)
|
|
@@ -88,6 +88,63 @@ import fs2 from "fs/promises";
|
|
|
88
88
|
import path2 from "path";
|
|
89
89
|
import { createReadStream } from "fs";
|
|
90
90
|
import readline from "readline";
|
|
91
|
+
function createRecentRouter(claudeDir) {
|
|
92
|
+
const router = Router2();
|
|
93
|
+
router.get("/", async (req, res) => {
|
|
94
|
+
try {
|
|
95
|
+
const limit = Math.min(parseInt(req.query.limit) || 20, 50);
|
|
96
|
+
const projectsDir = path2.join(claudeDir, "projects");
|
|
97
|
+
const projectFolders = await fs2.readdir(projectsDir, {
|
|
98
|
+
withFileTypes: true
|
|
99
|
+
});
|
|
100
|
+
const all = [];
|
|
101
|
+
for (const folder of projectFolders) {
|
|
102
|
+
if (!folder.isDirectory()) continue;
|
|
103
|
+
const projectDir = path2.join(projectsDir, folder.name);
|
|
104
|
+
let files;
|
|
105
|
+
try {
|
|
106
|
+
files = await fs2.readdir(projectDir);
|
|
107
|
+
} catch {
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
|
|
111
|
+
for (const file of jsonlFiles) {
|
|
112
|
+
const filePath = path2.join(projectDir, file);
|
|
113
|
+
try {
|
|
114
|
+
const stat = await fs2.stat(filePath);
|
|
115
|
+
all.push({
|
|
116
|
+
sessionId: file.replace(".jsonl", ""),
|
|
117
|
+
projectEncoded: folder.name,
|
|
118
|
+
filePath,
|
|
119
|
+
sizeBytes: stat.size,
|
|
120
|
+
lastModified: stat.mtime.toISOString(),
|
|
121
|
+
mtimeMs: stat.mtimeMs
|
|
122
|
+
});
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
all.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
128
|
+
const top = all.slice(0, limit);
|
|
129
|
+
const results = await Promise.all(
|
|
130
|
+
top.map(async (entry) => {
|
|
131
|
+
const summary = await quickScan(entry.filePath);
|
|
132
|
+
return {
|
|
133
|
+
sessionId: entry.sessionId,
|
|
134
|
+
projectEncoded: entry.projectEncoded,
|
|
135
|
+
sizeBytes: entry.sizeBytes,
|
|
136
|
+
lastModified: entry.lastModified,
|
|
137
|
+
...summary
|
|
138
|
+
};
|
|
139
|
+
})
|
|
140
|
+
);
|
|
141
|
+
res.json(results);
|
|
142
|
+
} catch {
|
|
143
|
+
res.status(500).json({ error: "Failed to list recent conversations" });
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
return router;
|
|
147
|
+
}
|
|
91
148
|
function createConversationsRouter(claudeDir) {
|
|
92
149
|
const router = Router2();
|
|
93
150
|
router.get("/:encodedPath/conversations", async (req, res) => {
|
|
@@ -528,10 +585,12 @@ function createServer({ claudeDir, isDev }) {
|
|
|
528
585
|
app.use("/api/projects", createProjectsRouter(claudeDir));
|
|
529
586
|
app.use("/api/projects", createConversationsRouter(claudeDir));
|
|
530
587
|
app.use("/api/conversations", createConversationRouter(claudeDir));
|
|
588
|
+
app.use("/api/recent", createRecentRouter(claudeDir));
|
|
531
589
|
if (!isDev) {
|
|
532
590
|
const uiDir = path6.join(__dirname, "ui");
|
|
533
591
|
app.use(express.static(uiDir));
|
|
534
|
-
app.get("/{*splat}", (
|
|
592
|
+
app.get("/{*splat}", (req, res, next) => {
|
|
593
|
+
if (req.path.startsWith("/api/")) return next();
|
|
535
594
|
res.sendFile(path6.join(uiDir, "index.html"));
|
|
536
595
|
});
|
|
537
596
|
}
|
package/dist/cli.js
CHANGED