cc-depth 1.0.0
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/README.md +78 -0
- package/cli.mjs +196 -0
- package/index.html +293 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# cc-depth
|
|
2
|
+
|
|
3
|
+
How many turns per Claude Code session?
|
|
4
|
+
|
|
5
|
+
Shows the distribution of conversation depth — how many back-and-forth exchanges happen in each session.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx cc-depth
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
cc-depth — Turns per session
|
|
15
|
+
|
|
16
|
+
1 ███░░░░░░░░░░░░░░░░░░░░░░░░░░░ 18 ( 3%)
|
|
17
|
+
2–5 ██████████████████░░░░░░░░░░░░ 104 ( 20%)
|
|
18
|
+
6–15 █████████████░░░░░░░░░░░░░░░░░ 75 ( 14%)
|
|
19
|
+
16–30 ████████░░░░░░░░░░░░░░░░░░░░░░ 48 ( 9%)
|
|
20
|
+
31–60 █████████░░░░░░░░░░░░░░░░░░░░░ 54 ( 10%)
|
|
21
|
+
61–100 █████████░░░░░░░░░░░░░░░░░░░░░ 53 ( 10%)
|
|
22
|
+
101+ ██████████████████████████████ 174 ( 33%)
|
|
23
|
+
|
|
24
|
+
─────────────────────────────────────────────────────────
|
|
25
|
+
Median: 38 turns/session
|
|
26
|
+
Mean: 263 turns/session
|
|
27
|
+
Peak: 14,169 turns
|
|
28
|
+
Style: 🔄 Loop Runner (extended sessions or autonomous loop)
|
|
29
|
+
|
|
30
|
+
Analyzed 526 sessions
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Style Classifications
|
|
34
|
+
|
|
35
|
+
| Style | Median | What it means |
|
|
36
|
+
|-------|--------|---------------|
|
|
37
|
+
| 💬 Quick Prompter | ≤ 3 turns | One-shot queries, fast iterations |
|
|
38
|
+
| ✅ Task Completer | 4–10 turns | Focused task sessions |
|
|
39
|
+
| 🤝 Collaborative Coder | 11–30 turns | Back-and-forth workflow |
|
|
40
|
+
| 🔄 Loop Runner | > 30 turns | Extended sessions or autonomous loop |
|
|
41
|
+
|
|
42
|
+
## Options
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npx cc-depth # All sessions
|
|
46
|
+
npx cc-depth --json # JSON output
|
|
47
|
+
npx cc-depth --projects=cc-loop # Filter by project name
|
|
48
|
+
npx cc-depth --help # Show help
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Browser Version
|
|
52
|
+
|
|
53
|
+
→ **[yurukusa.github.io/cc-depth](https://yurukusa.github.io/cc-depth/)**
|
|
54
|
+
|
|
55
|
+
Drag in your `~/.claude` folder. Runs entirely locally.
|
|
56
|
+
|
|
57
|
+
## What counts as a "turn"?
|
|
58
|
+
|
|
59
|
+
Each user message in a session counts as one turn. This includes:
|
|
60
|
+
- Your direct prompts and questions
|
|
61
|
+
- Continuation messages (in autonomous setups like cc-loop)
|
|
62
|
+
- System-level messages
|
|
63
|
+
|
|
64
|
+
For interactive users, a typical session is 2–30 turns. Autonomous loop setups show much higher counts.
|
|
65
|
+
|
|
66
|
+
## Part of cc-toolkit
|
|
67
|
+
|
|
68
|
+
cc-depth is tool #49 in [cc-toolkit](https://yurukusa.github.io/cc-toolkit/) — 50 free tools for Claude Code users.
|
|
69
|
+
|
|
70
|
+
Related:
|
|
71
|
+
- [cc-session-length](https://github.com/yurukusa/cc-session-length) — Duration distribution
|
|
72
|
+
- [cc-momentum](https://github.com/yurukusa/cc-momentum) — Week-by-week session trend
|
|
73
|
+
- [cc-gap](https://github.com/yurukusa/cc-gap) — Time between sessions
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
**GitHub**: [yurukusa/cc-depth](https://github.com/yurukusa/cc-depth)
|
|
78
|
+
**Try it**: `npx cc-depth`
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cc-depth — How many turns per Claude Code session?
|
|
4
|
+
* Shows the distribution of conversation depth across your sessions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
8
|
+
import { join, resolve } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
const jsonMode = args.includes('--json');
|
|
13
|
+
const showHelp = args.includes('--help') || args.includes('-h');
|
|
14
|
+
const projectsFlag = args.find(a => a.startsWith('--projects='));
|
|
15
|
+
const projectsFilter = projectsFlag ? projectsFlag.split('=')[1] : null;
|
|
16
|
+
|
|
17
|
+
if (showHelp) {
|
|
18
|
+
console.log(`cc-depth — Conversation depth per Claude Code session
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
npx cc-depth # All sessions
|
|
22
|
+
npx cc-depth --json # JSON output
|
|
23
|
+
npx cc-depth --projects=cc-loop # Filter by project name
|
|
24
|
+
|
|
25
|
+
Shows how many turns (user messages) occur per session.
|
|
26
|
+
`);
|
|
27
|
+
process.exit(0);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Find ~/.claude/projects directory
|
|
31
|
+
const claudeDir = join(homedir(), '.claude', 'projects');
|
|
32
|
+
|
|
33
|
+
function scanProjects(dir) {
|
|
34
|
+
const sessions = [];
|
|
35
|
+
let projectDirs;
|
|
36
|
+
try {
|
|
37
|
+
projectDirs = readdirSync(dir);
|
|
38
|
+
} catch {
|
|
39
|
+
return sessions;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
for (const projDir of projectDirs) {
|
|
43
|
+
if (projectsFilter && !projDir.includes(projectsFilter)) continue;
|
|
44
|
+
const projPath = join(dir, projDir);
|
|
45
|
+
let entries;
|
|
46
|
+
try {
|
|
47
|
+
entries = readdirSync(projPath);
|
|
48
|
+
} catch {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
if (!entry.endsWith('.jsonl')) continue;
|
|
54
|
+
// Skip subagent files
|
|
55
|
+
if (projPath.includes('/subagents/') || entry.includes('subagent')) continue;
|
|
56
|
+
|
|
57
|
+
const filePath = join(projPath, entry);
|
|
58
|
+
// Check subdirs (subagents)
|
|
59
|
+
try {
|
|
60
|
+
const stat = statSync(filePath);
|
|
61
|
+
if (!stat.isFile()) continue;
|
|
62
|
+
} catch {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Count "type":"user" occurrences in file
|
|
67
|
+
let content;
|
|
68
|
+
try {
|
|
69
|
+
content = readFileSync(filePath, 'utf8');
|
|
70
|
+
} catch {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Fast string counting — "type":"user" at top level
|
|
75
|
+
// Each line is one JSON object; we count lines containing this pattern
|
|
76
|
+
let turns = 0;
|
|
77
|
+
let firstTimestamp = null;
|
|
78
|
+
const lines = content.split('\n');
|
|
79
|
+
for (const line of lines) {
|
|
80
|
+
if (!line.trim()) continue;
|
|
81
|
+
// Count user turns
|
|
82
|
+
if (line.includes('"type":"user"')) {
|
|
83
|
+
turns++;
|
|
84
|
+
}
|
|
85
|
+
// Grab first timestamp for date info
|
|
86
|
+
if (!firstTimestamp && line.includes('"timestamp"')) {
|
|
87
|
+
const m = line.match(/"timestamp":"([^"]+)"/);
|
|
88
|
+
if (m) firstTimestamp = m[1];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (turns > 0) {
|
|
93
|
+
sessions.push({
|
|
94
|
+
id: entry.replace('.jsonl', ''),
|
|
95
|
+
project: projDir,
|
|
96
|
+
turns,
|
|
97
|
+
date: firstTimestamp ? new Date(firstTimestamp) : null,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return sessions;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const sessions = scanProjects(claudeDir);
|
|
107
|
+
|
|
108
|
+
if (sessions.length === 0) {
|
|
109
|
+
console.error('No session files found. Make sure ~/.claude/projects/ exists.');
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Sort by turns ascending for stats
|
|
114
|
+
const turnCounts = sessions.map(s => s.turns).sort((a, b) => a - b);
|
|
115
|
+
const n = turnCounts.length;
|
|
116
|
+
const total = turnCounts.reduce((a, b) => a + b, 0);
|
|
117
|
+
const median = turnCounts[Math.floor(n / 2)];
|
|
118
|
+
const mean = total / n;
|
|
119
|
+
const max = turnCounts[n - 1];
|
|
120
|
+
const min = turnCounts[0];
|
|
121
|
+
|
|
122
|
+
// Peak session
|
|
123
|
+
const peakSession = sessions.reduce((a, b) => a.turns > b.turns ? a : b);
|
|
124
|
+
|
|
125
|
+
// Distribution buckets
|
|
126
|
+
const buckets = [
|
|
127
|
+
{ label: '1', min: 1, max: 1, count: 0 },
|
|
128
|
+
{ label: '2–5', min: 2, max: 5, count: 0 },
|
|
129
|
+
{ label: '6–15', min: 6, max: 15, count: 0 },
|
|
130
|
+
{ label: '16–30', min: 16, max: 30, count: 0 },
|
|
131
|
+
{ label: '31–60', min: 31, max: 60, count: 0 },
|
|
132
|
+
{ label: '61–100',min: 61, max: 100, count: 0 },
|
|
133
|
+
{ label: '101+', min: 101, max: Infinity, count: 0 },
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
for (const t of turnCounts) {
|
|
137
|
+
for (const b of buckets) {
|
|
138
|
+
if (t >= b.min && t <= b.max) {
|
|
139
|
+
b.count++;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Classification
|
|
146
|
+
function classify(median) {
|
|
147
|
+
if (median <= 3) return { label: '💬 Quick Prompter', desc: 'one-shot queries, fast iterations' };
|
|
148
|
+
if (median <= 10) return { label: '✅ Task Completer', desc: 'focused task sessions' };
|
|
149
|
+
if (median <= 30) return { label: '🤝 Collaborative Coder', desc: 'back-and-forth workflow' };
|
|
150
|
+
return { label: '🔄 Loop Runner', desc: 'extended sessions or autonomous loop' };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const style = classify(median);
|
|
154
|
+
|
|
155
|
+
if (jsonMode) {
|
|
156
|
+
console.log(JSON.stringify({
|
|
157
|
+
sessions: n,
|
|
158
|
+
median_turns: median,
|
|
159
|
+
mean_turns: Math.round(mean),
|
|
160
|
+
min_turns: min,
|
|
161
|
+
max_turns: max,
|
|
162
|
+
style: style.label,
|
|
163
|
+
buckets: buckets.map(b => ({ range: b.label, count: b.count, pct: Math.round(b.count / n * 100) })),
|
|
164
|
+
peak: { id: peakSession.id, turns: peakSession.turns, date: peakSession.date },
|
|
165
|
+
}, null, 2));
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Bar chart rendering
|
|
170
|
+
const BAR_WIDTH = 30;
|
|
171
|
+
const maxCount = Math.max(...buckets.map(b => b.count));
|
|
172
|
+
|
|
173
|
+
function bar(count) {
|
|
174
|
+
const filled = maxCount > 0 ? Math.round((count / maxCount) * BAR_WIDTH) : 0;
|
|
175
|
+
return '█'.repeat(filled) + '░'.repeat(BAR_WIDTH - filled);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function rpad(str, len) {
|
|
179
|
+
return str + ' '.repeat(Math.max(0, len - str.length));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log('cc-depth — Turns per session\n');
|
|
183
|
+
|
|
184
|
+
for (const b of buckets) {
|
|
185
|
+
const label = rpad(b.label, 7);
|
|
186
|
+
const countStr = String(b.count).padStart(4);
|
|
187
|
+
const pct = (b.count / n * 100).toFixed(0).padStart(3);
|
|
188
|
+
console.log(` ${label} ${bar(b.count)} ${countStr} (${pct}%)`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log('\n' + '─'.repeat(57));
|
|
192
|
+
console.log(` Median: ${median} turns/session`);
|
|
193
|
+
console.log(` Mean: ${Math.round(mean)} turns/session`);
|
|
194
|
+
console.log(` Peak: ${max.toLocaleString()} turns`);
|
|
195
|
+
console.log(` Style: ${style.label} (${style.desc})`);
|
|
196
|
+
console.log(`\n Analyzed ${n} sessions`);
|
package/index.html
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>cc-depth — Conversation depth per session</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
+
body {
|
|
10
|
+
background: #0d1117;
|
|
11
|
+
color: #c9d1d9;
|
|
12
|
+
font-family: 'SF Mono', 'Consolas', 'Cascadia Code', monospace;
|
|
13
|
+
min-height: 100vh;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
align-items: center;
|
|
17
|
+
padding: 40px 20px;
|
|
18
|
+
}
|
|
19
|
+
h1 {
|
|
20
|
+
font-size: 1.5rem;
|
|
21
|
+
color: #f0883e;
|
|
22
|
+
margin-bottom: 6px;
|
|
23
|
+
letter-spacing: -0.5px;
|
|
24
|
+
}
|
|
25
|
+
.subtitle {
|
|
26
|
+
color: #8b949e;
|
|
27
|
+
font-size: 0.875rem;
|
|
28
|
+
margin-bottom: 32px;
|
|
29
|
+
}
|
|
30
|
+
.drop-zone {
|
|
31
|
+
border: 2px dashed #30363d;
|
|
32
|
+
border-radius: 12px;
|
|
33
|
+
padding: 48px 64px;
|
|
34
|
+
text-align: center;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
transition: all 0.2s;
|
|
37
|
+
max-width: 480px;
|
|
38
|
+
width: 100%;
|
|
39
|
+
margin-bottom: 32px;
|
|
40
|
+
}
|
|
41
|
+
.drop-zone:hover, .drop-zone.drag-over {
|
|
42
|
+
border-color: #f0883e;
|
|
43
|
+
background: rgba(240,136,62,0.05);
|
|
44
|
+
}
|
|
45
|
+
.drop-icon { font-size: 2.5rem; margin-bottom: 12px; }
|
|
46
|
+
.drop-text { color: #8b949e; font-size: 0.875rem; line-height: 1.6; }
|
|
47
|
+
.drop-text strong { color: #c9d1d9; }
|
|
48
|
+
#file-input { display: none; }
|
|
49
|
+
|
|
50
|
+
.result { display: none; max-width: 600px; width: 100%; }
|
|
51
|
+
.result.visible { display: block; }
|
|
52
|
+
|
|
53
|
+
.card {
|
|
54
|
+
background: #161b22;
|
|
55
|
+
border: 1px solid #30363d;
|
|
56
|
+
border-radius: 8px;
|
|
57
|
+
padding: 20px;
|
|
58
|
+
margin-bottom: 16px;
|
|
59
|
+
}
|
|
60
|
+
.card-title {
|
|
61
|
+
color: #8b949e;
|
|
62
|
+
font-size: 0.75rem;
|
|
63
|
+
text-transform: uppercase;
|
|
64
|
+
letter-spacing: 1px;
|
|
65
|
+
margin-bottom: 16px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.stats-grid {
|
|
69
|
+
display: grid;
|
|
70
|
+
grid-template-columns: repeat(2, 1fr);
|
|
71
|
+
gap: 16px;
|
|
72
|
+
margin-bottom: 16px;
|
|
73
|
+
}
|
|
74
|
+
.stat-item { text-align: center; }
|
|
75
|
+
.stat-val { font-size: 1.6rem; color: #f0883e; font-weight: 700; }
|
|
76
|
+
.stat-lbl { font-size: 0.75rem; color: #8b949e; margin-top: 2px; }
|
|
77
|
+
|
|
78
|
+
.style-badge {
|
|
79
|
+
text-align: center;
|
|
80
|
+
padding: 10px;
|
|
81
|
+
background: rgba(240,136,62,0.1);
|
|
82
|
+
border: 1px solid rgba(240,136,62,0.3);
|
|
83
|
+
border-radius: 6px;
|
|
84
|
+
font-size: 1rem;
|
|
85
|
+
color: #f0883e;
|
|
86
|
+
}
|
|
87
|
+
.style-desc { font-size: 0.8rem; color: #8b949e; margin-top: 4px; }
|
|
88
|
+
|
|
89
|
+
.bar-chart { font-size: 0.8rem; }
|
|
90
|
+
.bar-row {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
gap: 10px;
|
|
94
|
+
margin-bottom: 6px;
|
|
95
|
+
}
|
|
96
|
+
.bar-label { width: 56px; text-align: right; color: #8b949e; flex-shrink: 0; }
|
|
97
|
+
.bar-track {
|
|
98
|
+
flex: 1;
|
|
99
|
+
height: 14px;
|
|
100
|
+
background: #21262d;
|
|
101
|
+
border-radius: 3px;
|
|
102
|
+
overflow: hidden;
|
|
103
|
+
}
|
|
104
|
+
.bar-fill {
|
|
105
|
+
height: 100%;
|
|
106
|
+
background: #f0883e;
|
|
107
|
+
border-radius: 3px;
|
|
108
|
+
transition: width 0.6s ease;
|
|
109
|
+
}
|
|
110
|
+
.bar-count { width: 64px; color: #c9d1d9; text-align: right; flex-shrink: 0; }
|
|
111
|
+
|
|
112
|
+
.footer { color: #8b949e; font-size: 0.75rem; text-align: center; margin-top: 12px; }
|
|
113
|
+
.footer a { color: #f0883e; text-decoration: none; }
|
|
114
|
+
.footer a:hover { text-decoration: underline; }
|
|
115
|
+
|
|
116
|
+
.reset-btn {
|
|
117
|
+
margin-top: 16px;
|
|
118
|
+
background: none;
|
|
119
|
+
border: 1px solid #30363d;
|
|
120
|
+
color: #8b949e;
|
|
121
|
+
padding: 8px 16px;
|
|
122
|
+
border-radius: 6px;
|
|
123
|
+
cursor: pointer;
|
|
124
|
+
font-family: inherit;
|
|
125
|
+
font-size: 0.8rem;
|
|
126
|
+
display: block;
|
|
127
|
+
width: 100%;
|
|
128
|
+
transition: all 0.2s;
|
|
129
|
+
}
|
|
130
|
+
.reset-btn:hover { border-color: #f0883e; color: #f0883e; }
|
|
131
|
+
</style>
|
|
132
|
+
</head>
|
|
133
|
+
<body>
|
|
134
|
+
|
|
135
|
+
<h1>📊 cc-depth</h1>
|
|
136
|
+
<p class="subtitle">Conversation depth per Claude Code session</p>
|
|
137
|
+
|
|
138
|
+
<div class="drop-zone" id="drop-zone">
|
|
139
|
+
<div class="drop-icon">📁</div>
|
|
140
|
+
<div class="drop-text">
|
|
141
|
+
<strong>Drop your ~/.claude folder here</strong><br>
|
|
142
|
+
or click to select<br>
|
|
143
|
+
<br>
|
|
144
|
+
Reads session files locally.<br>
|
|
145
|
+
Nothing is uploaded.
|
|
146
|
+
</div>
|
|
147
|
+
<input type="file" id="file-input" webkitdirectory multiple accept=".jsonl">
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
<div class="result" id="result">
|
|
151
|
+
<div class="card">
|
|
152
|
+
<div class="card-title">Session Depth Summary</div>
|
|
153
|
+
<div class="stats-grid">
|
|
154
|
+
<div class="stat-item">
|
|
155
|
+
<div class="stat-val" id="stat-median">—</div>
|
|
156
|
+
<div class="stat-lbl">median turns</div>
|
|
157
|
+
</div>
|
|
158
|
+
<div class="stat-item">
|
|
159
|
+
<div class="stat-val" id="stat-mean">—</div>
|
|
160
|
+
<div class="stat-lbl">mean turns</div>
|
|
161
|
+
</div>
|
|
162
|
+
<div class="stat-item">
|
|
163
|
+
<div class="stat-val" id="stat-max">—</div>
|
|
164
|
+
<div class="stat-lbl">peak turns</div>
|
|
165
|
+
</div>
|
|
166
|
+
<div class="stat-item">
|
|
167
|
+
<div class="stat-val" id="stat-sessions">—</div>
|
|
168
|
+
<div class="stat-lbl">sessions</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
<div class="style-badge" id="stat-style">—</div>
|
|
172
|
+
<div class="style-desc" id="stat-desc"></div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<div class="card">
|
|
176
|
+
<div class="card-title">Distribution</div>
|
|
177
|
+
<div class="bar-chart" id="bar-chart"></div>
|
|
178
|
+
</div>
|
|
179
|
+
|
|
180
|
+
<button class="reset-btn" id="reset-btn">← Analyze another folder</button>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<div class="footer">
|
|
184
|
+
<a href="https://github.com/yurukusa/cc-depth" target="_blank">cc-depth</a> ·
|
|
185
|
+
Part of <a href="https://yurukusa.github.io/cc-toolkit/" target="_blank">cc-toolkit</a> · 106 free tools for Claude Code
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<script>
|
|
189
|
+
const dropZone = document.getElementById('drop-zone');
|
|
190
|
+
const fileInput = document.getElementById('file-input');
|
|
191
|
+
const resultEl = document.getElementById('result');
|
|
192
|
+
const resetBtn = document.getElementById('reset-btn');
|
|
193
|
+
|
|
194
|
+
dropZone.addEventListener('click', () => fileInput.click());
|
|
195
|
+
dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('drag-over'); });
|
|
196
|
+
dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over'));
|
|
197
|
+
dropZone.addEventListener('drop', e => {
|
|
198
|
+
e.preventDefault();
|
|
199
|
+
dropZone.classList.remove('drag-over');
|
|
200
|
+
processFiles(e.dataTransfer.files);
|
|
201
|
+
});
|
|
202
|
+
fileInput.addEventListener('change', () => processFiles(fileInput.files));
|
|
203
|
+
resetBtn.addEventListener('click', () => {
|
|
204
|
+
resultEl.classList.remove('visible');
|
|
205
|
+
dropZone.style.display = '';
|
|
206
|
+
fileInput.value = '';
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
async function processFiles(files) {
|
|
210
|
+
const jsonlFiles = Array.from(files).filter(f => {
|
|
211
|
+
const p = f.webkitRelativePath || f.name;
|
|
212
|
+
return p.endsWith('.jsonl') && !p.includes('/subagents/');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (jsonlFiles.length === 0) {
|
|
216
|
+
alert('No session files found. Drop your ~/.claude folder.');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
dropZone.style.display = 'none';
|
|
221
|
+
|
|
222
|
+
const sessions = [];
|
|
223
|
+
const BATCH = 50;
|
|
224
|
+
for (let i = 0; i < jsonlFiles.length; i += BATCH) {
|
|
225
|
+
const batch = jsonlFiles.slice(i, i + BATCH);
|
|
226
|
+
await Promise.all(batch.map(async f => {
|
|
227
|
+
try {
|
|
228
|
+
const text = await f.text();
|
|
229
|
+
let turns = 0;
|
|
230
|
+
for (const line of text.split('\n')) {
|
|
231
|
+
if (line.includes('"type":"user"')) turns++;
|
|
232
|
+
}
|
|
233
|
+
if (turns > 0) sessions.push(turns);
|
|
234
|
+
} catch {}
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (sessions.length === 0) {
|
|
239
|
+
alert('Could not parse session files.');
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
sessions.sort((a, b) => a - b);
|
|
244
|
+
const n = sessions.length;
|
|
245
|
+
const median = sessions[Math.floor(n / 2)];
|
|
246
|
+
const mean = Math.round(sessions.reduce((a, b) => a + b, 0) / n);
|
|
247
|
+
const max = sessions[n - 1];
|
|
248
|
+
|
|
249
|
+
const classify = m => {
|
|
250
|
+
if (m <= 3) return { label: '💬 Quick Prompter', desc: 'one-shot queries, fast iterations' };
|
|
251
|
+
if (m <= 10) return { label: '✅ Task Completer', desc: 'focused task sessions' };
|
|
252
|
+
if (m <= 30) return { label: '🤝 Collaborative Coder', desc: 'back-and-forth workflow' };
|
|
253
|
+
return { label: '🔄 Loop Runner', desc: 'extended sessions or autonomous loop' };
|
|
254
|
+
};
|
|
255
|
+
const style = classify(median);
|
|
256
|
+
|
|
257
|
+
document.getElementById('stat-median').textContent = median;
|
|
258
|
+
document.getElementById('stat-mean').textContent = mean.toLocaleString();
|
|
259
|
+
document.getElementById('stat-max').textContent = max.toLocaleString();
|
|
260
|
+
document.getElementById('stat-sessions').textContent = n;
|
|
261
|
+
document.getElementById('stat-style').textContent = style.label;
|
|
262
|
+
document.getElementById('stat-desc').textContent = style.desc;
|
|
263
|
+
|
|
264
|
+
const buckets = [
|
|
265
|
+
{ label: '1', min: 1, max: 1 },
|
|
266
|
+
{ label: '2–5', min: 2, max: 5 },
|
|
267
|
+
{ label: '6–15', min: 6, max: 15 },
|
|
268
|
+
{ label: '16–30', min: 16, max: 30 },
|
|
269
|
+
{ label: '31–60', min: 31, max: 60 },
|
|
270
|
+
{ label: '61–100',min: 61, max: 100 },
|
|
271
|
+
{ label: '101+', min: 101, max: Infinity },
|
|
272
|
+
].map(b => ({
|
|
273
|
+
...b,
|
|
274
|
+
count: sessions.filter(t => t >= b.min && t <= b.max).length,
|
|
275
|
+
}));
|
|
276
|
+
|
|
277
|
+
const maxCount = Math.max(...buckets.map(b => b.count));
|
|
278
|
+
const chart = document.getElementById('bar-chart');
|
|
279
|
+
chart.innerHTML = buckets.map(b => `
|
|
280
|
+
<div class="bar-row">
|
|
281
|
+
<div class="bar-label">${b.label}</div>
|
|
282
|
+
<div class="bar-track">
|
|
283
|
+
<div class="bar-fill" style="width:${maxCount > 0 ? (b.count / maxCount * 100) : 0}%"></div>
|
|
284
|
+
</div>
|
|
285
|
+
<div class="bar-count">${b.count} (${Math.round(b.count / n * 100)}%)</div>
|
|
286
|
+
</div>
|
|
287
|
+
`).join('');
|
|
288
|
+
|
|
289
|
+
resultEl.classList.add('visible');
|
|
290
|
+
}
|
|
291
|
+
</script>
|
|
292
|
+
</body>
|
|
293
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cc-depth",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "How many turns per Claude Code session? Distribution of conversation depth.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cc-depth": "./cli.mjs"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node cli.mjs"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"claude",
|
|
14
|
+
"claude-code",
|
|
15
|
+
"ai",
|
|
16
|
+
"developer-tools",
|
|
17
|
+
"analytics",
|
|
18
|
+
"productivity"
|
|
19
|
+
],
|
|
20
|
+
"author": "yurukusa",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
}
|
|
25
|
+
}
|