cc-subagent 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.
Files changed (4) hide show
  1. package/README.md +59 -0
  2. package/cli.mjs +203 -0
  3. package/index.html +249 -0
  4. package/package.json +25 -0
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # cc-subagent
2
+
3
+ How many subagents does your Claude Code spawn? Shows subagent adoption rate, total count, peak sessions, and per-project breakdown.
4
+
5
+ ```
6
+ cc-subagent — Claude Code subagent usage
7
+
8
+ Main sessions: 755
9
+ Sessions w/ subagents: 324 (42.9% of sessions)
10
+ Total subagent sessions: 3,677
11
+ Avg per spawning session: 11.3
12
+ Avg per all sessions: 4.9
13
+ Total subagent data: 1003.0 MB
14
+ Peak in one session: 284 subagents
15
+
16
+ ────────────────────────────────────────────────────────
17
+ Subagents per session (spawning sessions only)
18
+
19
+ 1 ███████████░░░░░░░░░░░░░ 64 (19.8%)
20
+ 2-5 ████████████████████████ 142 (43.8%)
21
+ 6-10 ████████░░░░░░░░░░░░░░░░ 48 (14.8%)
22
+ 11-30 ███████░░░░░░░░░░░░░░░░░ 43 (13.3%)
23
+ 31-100 ████░░░░░░░░░░░░░░░░░░░░ 21 (6.5%)
24
+ 100+ █░░░░░░░░░░░░░░░░░░░░░░░ 6 (1.9%)
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```bash
30
+ npx cc-subagent # Subagent count and adoption stats
31
+ npx cc-subagent --json # JSON output
32
+ ```
33
+
34
+ ## What it shows
35
+
36
+ - **Adoption rate** — what fraction of your sessions spawned at least one subagent
37
+ - **Total count** — cumulative subagent sessions across your history
38
+ - **Distribution** — single subagent vs teams of 100+
39
+ - **Peak session** — the session that spawned the most subagents
40
+ - **Subagent data size** — total disk usage of subagent session files
41
+ - **By project** — which projects rely on subagents most
42
+
43
+ ## About subagents
44
+
45
+ When Claude Code uses the `Agent` tool, it spawns a subagent — a child Claude session that handles a specific subtask. Subagent session files are stored at `~/.claude/projects/PROJ/SESSION_ID/subagents/agent-XXXXX.jsonl`.
46
+
47
+ Most tools filter out subagent files by default. cc-subagent specifically analyzes what's usually hidden.
48
+
49
+ ## Privacy
50
+
51
+ Reads file metadata only (names, sizes). No file content is accessed or transmitted. Everything runs locally.
52
+
53
+ ## Browser version
54
+
55
+ Drop your `~/.claude` folder into [cc-subagent on the web](https://yurukusa.github.io/cc-subagent/) — no install required.
56
+
57
+ ---
58
+
59
+ Part of [cc-toolkit](https://yurukusa.github.io/cc-toolkit/) — 60 free tools for Claude Code
package/cli.mjs ADDED
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * cc-subagent — How many subagents does your Claude Code spawn?
4
+ * Analyzes subagent usage: sessions, count, size, and per-project breakdown.
5
+ */
6
+
7
+ import { readdirSync, statSync } from 'fs';
8
+ import { join } 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
+
15
+ if (showHelp) {
16
+ console.log(`cc-subagent — Subagent usage in your Claude Code sessions
17
+
18
+ Usage:
19
+ npx cc-subagent # Subagent count, size, and project breakdown
20
+ npx cc-subagent --json # JSON output
21
+ `);
22
+ process.exit(0);
23
+ }
24
+
25
+ const claudeDir = join(homedir(), '.claude', 'projects');
26
+
27
+ function humanSize(bytes) {
28
+ if (bytes >= 1024 ** 3) return (bytes / 1024 ** 3).toFixed(1) + ' GB';
29
+ if (bytes >= 1024 ** 2) return (bytes / 1024 ** 2).toFixed(1) + ' MB';
30
+ if (bytes >= 1024) return (bytes / 1024).toFixed(1) + ' KB';
31
+ return bytes + ' B';
32
+ }
33
+
34
+ function projectName(dirName) {
35
+ const stripped = dirName.replace(/^-home-[^-]+/, '').replace(/^-/, '');
36
+ return stripped || '~/ (home)';
37
+ }
38
+
39
+ let projectDirs;
40
+ try {
41
+ projectDirs = readdirSync(claudeDir);
42
+ } catch {
43
+ console.error(`Cannot read ${claudeDir}`);
44
+ process.exit(1);
45
+ }
46
+
47
+ // Totals
48
+ let totalMainSessions = 0;
49
+ let sessionsWithSubagents = 0;
50
+ let totalSubagentSessions = 0;
51
+ let totalSubagentBytes = 0;
52
+ let maxSubagentsInSession = 0;
53
+ let maxSubagentsSession = '';
54
+
55
+ const byProject = {}; // { projDir: { subagentSessions, parentSessions, bytes, name } }
56
+ const distribution = { 1: 0, '2-5': 0, '6-10': 0, '11-30': 0, '31-100': 0, '100+': 0 };
57
+
58
+ for (const projDir of projectDirs) {
59
+ const projPath = join(claudeDir, projDir);
60
+ let pstat;
61
+ try {
62
+ pstat = statSync(projPath);
63
+ if (!pstat.isDirectory()) continue;
64
+ } catch { continue; }
65
+
66
+ const projLabel = projectName(projDir);
67
+ if (!byProject[projDir]) byProject[projDir] = { subagentSessions: 0, parentSessions: 0, bytes: 0, name: projLabel };
68
+
69
+ // Each entry in projPath could be a session file OR a session dir (when it has subagents)
70
+ let entries;
71
+ try { entries = readdirSync(projPath); } catch { continue; }
72
+
73
+ // Count main sessions (*.jsonl files directly in projPath)
74
+ const mainSessions = entries.filter(e => e.endsWith('.jsonl'));
75
+ totalMainSessions += mainSessions.length;
76
+ byProject[projDir].parentSessions += mainSessions.length;
77
+
78
+ // Check subdirectories for session dirs with subagents
79
+ for (const entry of entries) {
80
+ if (entry.endsWith('.jsonl')) continue; // skip session files
81
+ const sessionDir = join(projPath, entry);
82
+ let sdstat;
83
+ try {
84
+ sdstat = statSync(sessionDir);
85
+ if (!sdstat.isDirectory()) continue;
86
+ } catch { continue; }
87
+
88
+ const subagentsDir = join(sessionDir, 'subagents');
89
+ let subEntries;
90
+ try {
91
+ subEntries = readdirSync(subagentsDir);
92
+ } catch { continue; }
93
+
94
+ const subFiles = subEntries.filter(e => e.endsWith('.jsonl'));
95
+ if (!subFiles.length) continue;
96
+
97
+ // This session has subagents
98
+ sessionsWithSubagents++;
99
+ const sessionSubCount = subFiles.length;
100
+ totalSubagentSessions += sessionSubCount;
101
+
102
+ // Distribution bucket
103
+ if (sessionSubCount === 1) distribution[1]++;
104
+ else if (sessionSubCount <= 5) distribution['2-5']++;
105
+ else if (sessionSubCount <= 10) distribution['6-10']++;
106
+ else if (sessionSubCount <= 30) distribution['11-30']++;
107
+ else if (sessionSubCount <= 100) distribution['31-100']++;
108
+ else distribution['100+']++;
109
+
110
+ // Max
111
+ if (sessionSubCount > maxSubagentsInSession) {
112
+ maxSubagentsInSession = sessionSubCount;
113
+ maxSubagentsSession = `${projLabel}/${entry.slice(0, 8)}...`;
114
+ }
115
+
116
+ // Size of subagent files
117
+ let sessionSubBytes = 0;
118
+ for (const sf of subFiles) {
119
+ try {
120
+ sessionSubBytes += statSync(join(subagentsDir, sf)).size;
121
+ } catch {}
122
+ }
123
+ totalSubagentBytes += sessionSubBytes;
124
+ byProject[projDir].subagentSessions += sessionSubCount;
125
+ byProject[projDir].bytes += sessionSubBytes;
126
+ }
127
+ }
128
+
129
+ if (totalMainSessions === 0) {
130
+ console.error('No session files found.');
131
+ process.exit(1);
132
+ }
133
+
134
+ const adoptionRate = (sessionsWithSubagents / totalMainSessions * 100).toFixed(1);
135
+ const avgPerSpawner = sessionsWithSubagents > 0 ? (totalSubagentSessions / sessionsWithSubagents).toFixed(1) : '0';
136
+ const avgPerAll = (totalSubagentSessions / totalMainSessions).toFixed(1);
137
+
138
+ const sortedProjects = Object.entries(byProject)
139
+ .filter(([, d]) => d.subagentSessions > 0)
140
+ .sort((a, b) => b[1].subagentSessions - a[1].subagentSessions);
141
+
142
+ if (jsonMode) {
143
+ console.log(JSON.stringify({
144
+ main_sessions: totalMainSessions,
145
+ sessions_with_subagents: sessionsWithSubagents,
146
+ adoption_rate_pct: parseFloat(adoptionRate),
147
+ total_subagent_sessions: totalSubagentSessions,
148
+ avg_per_spawning_session: parseFloat(avgPerSpawner),
149
+ avg_per_all_sessions: parseFloat(avgPerAll),
150
+ total_subagent_size: humanSize(totalSubagentBytes),
151
+ max_in_single_session: maxSubagentsInSession,
152
+ distribution,
153
+ by_project: sortedProjects.slice(0, 10).map(([, d]) => ({
154
+ project: d.name,
155
+ subagent_sessions: d.subagentSessions,
156
+ size: humanSize(d.bytes),
157
+ })),
158
+ }, null, 2));
159
+ process.exit(0);
160
+ }
161
+
162
+ // Terminal display
163
+ const BAR_WIDTH = 24;
164
+
165
+ function countBar(n, max) {
166
+ const filled = max > 0 ? Math.round((n / max) * BAR_WIDTH) : 0;
167
+ return '█'.repeat(filled) + '░'.repeat(BAR_WIDTH - filled);
168
+ }
169
+
170
+ function rpad(str, len) {
171
+ return str + ' '.repeat(Math.max(0, len - str.length));
172
+ }
173
+
174
+ console.log('cc-subagent — Claude Code subagent usage\n');
175
+
176
+ console.log(` Main sessions: ${totalMainSessions.toLocaleString()}`);
177
+ console.log(` Sessions w/ subagents:${sessionsWithSubagents.toLocaleString().padStart(5)} (${adoptionRate}% of sessions)`);
178
+ console.log(` Total subagent sessions: ${totalSubagentSessions.toLocaleString()}`);
179
+ console.log(` Avg per spawning session: ${avgPerSpawner}`);
180
+ console.log(` Avg per all sessions: ${avgPerAll}`);
181
+ console.log(` Total subagent data: ${humanSize(totalSubagentBytes)}`);
182
+ console.log(` Peak in one session: ${maxSubagentsInSession} subagents`);
183
+
184
+ // Distribution
185
+ console.log('\n' + '─'.repeat(56));
186
+ console.log(' Subagents per session (spawning sessions only)\n');
187
+ const maxDist = Math.max(...Object.values(distribution));
188
+ for (const [label, count] of Object.entries(distribution)) {
189
+ const pct = sessionsWithSubagents > 0 ? (count / sessionsWithSubagents * 100).toFixed(1) : '0.0';
190
+ console.log(` ${label.padEnd(8)} ${countBar(count, maxDist)} ${String(count).padStart(4)} (${pct}%)`);
191
+ }
192
+
193
+ // Project breakdown
194
+ if (sortedProjects.length) {
195
+ console.log('\n' + '─'.repeat(56));
196
+ console.log(' By project (top 8)\n');
197
+ const maxProj = sortedProjects[0][1].subagentSessions;
198
+ const maxLabel = Math.max(...sortedProjects.slice(0, 8).map(([, d]) => d.name.length));
199
+ for (const [, data] of sortedProjects.slice(0, 8)) {
200
+ const label = rpad(data.name, maxLabel);
201
+ console.log(` ${label} ${countBar(data.subagentSessions, maxProj)} ${String(data.subagentSessions).padStart(4)} ${humanSize(data.bytes)}`);
202
+ }
203
+ }
package/index.html ADDED
@@ -0,0 +1,249 @@
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-subagent — Claude Code subagent usage</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 { font-size: 1.5rem; color: #ff7b72; margin-bottom: 6px; }
20
+ .subtitle { color: #8b949e; font-size: 0.875rem; margin-bottom: 32px; }
21
+
22
+ .drop-zone {
23
+ border: 2px dashed #30363d;
24
+ border-radius: 12px;
25
+ padding: 48px 64px;
26
+ text-align: center;
27
+ cursor: pointer;
28
+ transition: all 0.2s;
29
+ max-width: 480px;
30
+ width: 100%;
31
+ margin-bottom: 32px;
32
+ }
33
+ .drop-zone:hover, .drop-zone.drag-over { border-color: #ff7b72; background: rgba(255,123,114,0.05); }
34
+ .drop-text { color: #8b949e; font-size: 0.875rem; line-height: 1.6; }
35
+ .drop-text strong { color: #c9d1d9; }
36
+ #file-input { display: none; }
37
+
38
+ .result { display: none; max-width: 620px; width: 100%; }
39
+ .result.visible { display: block; }
40
+ .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 20px; margin-bottom: 16px; }
41
+ .card-title { color: #8b949e; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 16px; }
42
+
43
+ .stats-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }
44
+ @media (min-width: 500px) { .stats-grid { grid-template-columns: repeat(3, 1fr); } }
45
+ .stat-item { text-align: center; }
46
+ .stat-val { font-size: 1.2rem; color: #ff7b72; font-weight: 700; }
47
+ .stat-lbl { font-size: 0.68rem; color: #8b949e; margin-top: 2px; }
48
+
49
+ .adoption-badge {
50
+ text-align: center;
51
+ padding: 12px;
52
+ background: rgba(255,123,114,0.1);
53
+ border: 1px solid rgba(255,123,114,0.3);
54
+ border-radius: 6px;
55
+ margin-top: 16px;
56
+ font-size: 0.9rem;
57
+ color: #ff7b72;
58
+ }
59
+
60
+ .bar-chart { font-size: 0.78rem; }
61
+ .bar-row { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
62
+ .bar-label { width: 64px; text-align: right; color: #8b949e; flex-shrink: 0; }
63
+ .bar-label-wide { width: 160px; text-align: right; color: #c9d1d9; flex-shrink: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
64
+ .bar-track { flex: 1; height: 14px; background: #21262d; border-radius: 3px; overflow: hidden; }
65
+ .bar-fill { height: 100%; background: #ff7b72; border-radius: 3px; transition: width 0.5s ease; }
66
+ .bar-count { width: 64px; color: #8b949e; text-align: right; flex-shrink: 0; font-size: 0.72rem; }
67
+
68
+ .section-title { color: #8b949e; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 10px; margin-top: 16px; }
69
+ .section-title:first-child { margin-top: 0; }
70
+
71
+ .reset-btn { margin-top: 16px; background: none; border: 1px solid #30363d; color: #8b949e; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-family: inherit; font-size: 0.8rem; display: block; width: 100%; transition: all 0.2s; }
72
+ .reset-btn:hover { border-color: #ff7b72; color: #ff7b72; }
73
+ .footer { color: #8b949e; font-size: 0.75rem; text-align: center; margin-top: 12px; }
74
+ .footer a { color: #ff7b72; text-decoration: none; }
75
+ .footer a:hover { text-decoration: underline; }
76
+ </style>
77
+ </head>
78
+ <body>
79
+ <h1>🤖 cc-subagent</h1>
80
+ <p class="subtitle">How many subagents does your Claude Code spawn?</p>
81
+
82
+ <div class="drop-zone" id="drop-zone">
83
+ <div style="font-size:2.5rem;margin-bottom:12px;">📁</div>
84
+ <div class="drop-text">
85
+ <strong>Drop your ~/.claude folder here</strong><br>
86
+ or click to select<br><br>
87
+ Reads file metadata only (no content).<br>
88
+ Nothing is uploaded.
89
+ </div>
90
+ <input type="file" id="file-input" webkitdirectory multiple accept=".jsonl">
91
+ </div>
92
+
93
+ <div class="result" id="result">
94
+ <div class="card">
95
+ <div class="card-title">Subagent Summary</div>
96
+ <div class="stats-grid">
97
+ <div class="stat-item"><div class="stat-val" id="stat-main">—</div><div class="stat-lbl">main sessions</div></div>
98
+ <div class="stat-item"><div class="stat-val" id="stat-spawners">—</div><div class="stat-lbl">spawned subagents</div></div>
99
+ <div class="stat-item"><div class="stat-val" id="stat-total">—</div><div class="stat-lbl">total subagents</div></div>
100
+ <div class="stat-item"><div class="stat-val" id="stat-avg">—</div><div class="stat-lbl">avg per spawner</div></div>
101
+ <div class="stat-item"><div class="stat-val" id="stat-size">—</div><div class="stat-lbl">subagent data</div></div>
102
+ <div class="stat-item"><div class="stat-val" id="stat-peak">—</div><div class="stat-lbl">peak (1 session)</div></div>
103
+ </div>
104
+ <div class="adoption-badge" id="adoption-badge">—</div>
105
+ </div>
106
+
107
+ <div class="card">
108
+ <div class="section-title">Subagents per session (spawning sessions)</div>
109
+ <div class="bar-chart" id="dist-chart"></div>
110
+ <div class="section-title">By project (top 8)</div>
111
+ <div class="bar-chart" id="proj-chart"></div>
112
+ </div>
113
+
114
+ <button class="reset-btn" id="reset-btn">← Analyze another folder</button>
115
+ </div>
116
+
117
+ <div class="footer">
118
+ <a href="https://github.com/yurukusa/cc-subagent" target="_blank">cc-subagent</a> ·
119
+ Part of <a href="https://yurukusa.github.io/cc-toolkit/" target="_blank">cc-toolkit</a> · 106 free tools for Claude Code
120
+ </div>
121
+
122
+ <script>
123
+ const dropZone = document.getElementById('drop-zone');
124
+ const fileInput = document.getElementById('file-input');
125
+ const resultEl = document.getElementById('result');
126
+ const resetBtn = document.getElementById('reset-btn');
127
+
128
+ dropZone.addEventListener('click', () => fileInput.click());
129
+ dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('drag-over'); });
130
+ dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over'));
131
+ dropZone.addEventListener('drop', e => { e.preventDefault(); dropZone.classList.remove('drag-over'); processFiles(e.dataTransfer.files); });
132
+ fileInput.addEventListener('change', () => processFiles(fileInput.files));
133
+ resetBtn.addEventListener('click', () => { resultEl.classList.remove('visible'); dropZone.style.display = ''; fileInput.value = ''; });
134
+
135
+ function humanSize(bytes) {
136
+ if (bytes >= 1024**3) return (bytes/1024**3).toFixed(1) + ' GB';
137
+ if (bytes >= 1024**2) return (bytes/1024**2).toFixed(1) + ' MB';
138
+ if (bytes >= 1024) return (bytes/1024).toFixed(1) + ' KB';
139
+ return bytes + ' B';
140
+ }
141
+
142
+ function projectName(webkitPath) {
143
+ const parts = webkitPath.split('/');
144
+ const projIdx = parts.indexOf('projects');
145
+ if (projIdx >= 0 && parts[projIdx + 1]) {
146
+ const dir = parts[projIdx + 1];
147
+ const stripped = dir.replace(/^-home-[^-]+/, '').replace(/^-/, '');
148
+ return stripped || '~/ (home)';
149
+ }
150
+ return 'unknown';
151
+ }
152
+
153
+ function processFiles(files) {
154
+ const allFiles = Array.from(files);
155
+
156
+ // Separate main sessions and subagent files
157
+ const mainFiles = allFiles.filter(f => {
158
+ const p = f.webkitRelativePath || f.name;
159
+ return p.endsWith('.jsonl') && !p.includes('/subagents/');
160
+ });
161
+ const subFiles = allFiles.filter(f => {
162
+ const p = f.webkitRelativePath || f.name;
163
+ return p.endsWith('.jsonl') && p.includes('/subagents/');
164
+ });
165
+
166
+ if (!mainFiles.length && !subFiles.length) { alert('No session files found.'); return; }
167
+ dropZone.style.display = 'none';
168
+
169
+ // Group subagent files by parent session path
170
+ const byParent = {}; // parentPath → count + bytes
171
+ let totalSubBytes = 0;
172
+ for (const f of subFiles) {
173
+ const p = f.webkitRelativePath || f.name;
174
+ const parentPath = p.split('/subagents/')[0];
175
+ if (!byParent[parentPath]) byParent[parentPath] = { count: 0, bytes: 0 };
176
+ byParent[parentPath].count++;
177
+ byParent[parentPath].bytes += f.size;
178
+ totalSubBytes += f.size;
179
+ }
180
+
181
+ const totalMain = mainFiles.length;
182
+ const spawnerCount = Object.keys(byParent).length;
183
+ const totalSubCount = subFiles.length;
184
+ const adoptionRate = totalMain > 0 ? (spawnerCount / totalMain * 100) : 0;
185
+ const avgPerSpawner = spawnerCount > 0 ? (totalSubCount / spawnerCount).toFixed(1) : '0';
186
+
187
+ let peakCount = 0;
188
+ for (const d of Object.values(byParent)) {
189
+ if (d.count > peakCount) peakCount = d.count;
190
+ }
191
+
192
+ // Distribution
193
+ const dist = { 1: 0, '2-5': 0, '6-10': 0, '11-30': 0, '31-100': 0, '100+': 0 };
194
+ for (const d of Object.values(byParent)) {
195
+ const n = d.count;
196
+ if (n === 1) dist[1]++;
197
+ else if (n <= 5) dist['2-5']++;
198
+ else if (n <= 10) dist['6-10']++;
199
+ else if (n <= 30) dist['11-30']++;
200
+ else if (n <= 100) dist['31-100']++;
201
+ else dist['100+']++;
202
+ }
203
+
204
+ // By project (using subagent file paths)
205
+ const byProj = {};
206
+ for (const f of subFiles) {
207
+ const proj = projectName(f.webkitRelativePath || f.name);
208
+ if (!byProj[proj]) byProj[proj] = { count: 0, bytes: 0 };
209
+ byProj[proj].count++;
210
+ byProj[proj].bytes += f.size;
211
+ }
212
+
213
+ // Render
214
+ document.getElementById('stat-main').textContent = totalMain.toLocaleString();
215
+ document.getElementById('stat-spawners').textContent = spawnerCount.toLocaleString();
216
+ document.getElementById('stat-total').textContent = totalSubCount.toLocaleString();
217
+ document.getElementById('stat-avg').textContent = avgPerSpawner;
218
+ document.getElementById('stat-size').textContent = humanSize(totalSubBytes);
219
+ document.getElementById('stat-peak').textContent = peakCount;
220
+ document.getElementById('adoption-badge').textContent =
221
+ `${adoptionRate.toFixed(1)}% of sessions spawned subagents · ${(totalSubCount / (totalMain || 1)).toFixed(1)} subagents per session on average`;
222
+
223
+ // Distribution chart
224
+ const maxDist = Math.max(...Object.values(dist), 1);
225
+ document.getElementById('dist-chart').innerHTML = Object.entries(dist).map(([label, count]) => {
226
+ const pct = spawnerCount > 0 ? (count / spawnerCount * 100).toFixed(1) : '0.0';
227
+ return `<div class="bar-row">
228
+ <div class="bar-label">${label}</div>
229
+ <div class="bar-track"><div class="bar-fill" style="width:${(count/maxDist*100).toFixed(1)}%"></div></div>
230
+ <div class="bar-count">${count} (${pct}%)</div>
231
+ </div>`;
232
+ }).join('');
233
+
234
+ // Project chart
235
+ const projs = Object.entries(byProj).sort((a,b) => b[1].count - a[1].count).slice(0, 8);
236
+ const maxProj = projs[0]?.[1].count || 1;
237
+ document.getElementById('proj-chart').innerHTML = projs.map(([name, d]) =>
238
+ `<div class="bar-row">
239
+ <div class="bar-label-wide">${name}</div>
240
+ <div class="bar-track"><div class="bar-fill" style="width:${(d.count/maxProj*100).toFixed(1)}%"></div></div>
241
+ <div class="bar-count">${d.count} / ${humanSize(d.bytes)}</div>
242
+ </div>`
243
+ ).join('');
244
+
245
+ resultEl.classList.add('visible');
246
+ }
247
+ </script>
248
+ </body>
249
+ </html>
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "cc-subagent",
3
+ "version": "1.0.0",
4
+ "description": "How many subagents does your Claude Code spawn? Session count, adoption rate, and per-project breakdown.",
5
+ "type": "module",
6
+ "bin": {
7
+ "cc-subagent": "./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
+ "agents"
19
+ ],
20
+ "author": "yurukusa",
21
+ "license": "MIT",
22
+ "engines": {
23
+ "node": ">=18"
24
+ }
25
+ }