ccanalyzer 1.1.0 → 1.1.2
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/package.json +2 -2
- package/src/parser.js +37 -1
- package/src/public/app.js +21 -21
- package/src/public/index.html +2 -2
package/package.json
CHANGED
package/src/parser.js
CHANGED
|
@@ -164,6 +164,7 @@ function parseSubagents(sessionDir) {
|
|
|
164
164
|
try { meta = JSON.parse(fs.readFileSync(path.join(subagentsDir, metaFile), 'utf8')); } catch {}
|
|
165
165
|
|
|
166
166
|
const parsed = parseAgentFile(jsonlPath);
|
|
167
|
+
const { skills } = collectToolsUsed(jsonlPath);
|
|
167
168
|
agents.push({
|
|
168
169
|
agentId,
|
|
169
170
|
meta,
|
|
@@ -173,6 +174,7 @@ function parseSubagents(sessionDir) {
|
|
|
173
174
|
totalUsage: parsed.totalUsage,
|
|
174
175
|
totalCost: parsed.totalCost,
|
|
175
176
|
model: parsed.model,
|
|
177
|
+
skillsUsed: [...skills],
|
|
176
178
|
});
|
|
177
179
|
}
|
|
178
180
|
|
|
@@ -278,6 +280,25 @@ function getAllProjects() {
|
|
|
278
280
|
return projects;
|
|
279
281
|
}
|
|
280
282
|
|
|
283
|
+
function collectToolsUsed(filePath) {
|
|
284
|
+
const mcps = new Set();
|
|
285
|
+
const skills = new Set();
|
|
286
|
+
for (const entry of parseLines(filePath)) {
|
|
287
|
+
if (entry.type !== 'assistant') continue;
|
|
288
|
+
for (const block of (entry.message?.content || [])) {
|
|
289
|
+
if (!block || block.type !== 'tool_use') continue;
|
|
290
|
+
if (block.name.startsWith('mcp__')) {
|
|
291
|
+
const parts = block.name.split('__');
|
|
292
|
+
const server = (parts[1] || '').replace(/^claude_ai_/, '').replace(/_/g, ' ');
|
|
293
|
+
if (server) mcps.add(server);
|
|
294
|
+
} else if (block.name === 'Skill' && block.input?.skill) {
|
|
295
|
+
skills.add(block.input.skill);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return { mcps, skills };
|
|
300
|
+
}
|
|
301
|
+
|
|
281
302
|
function getSessionDetail(dirName, sessionFile) {
|
|
282
303
|
const filePath = path.join(PROJECTS_DIR, dirName, sessionFile);
|
|
283
304
|
if (!fs.existsSync(filePath)) throw new Error('Session not found');
|
|
@@ -297,7 +318,22 @@ function getSessionDetail(dirName, sessionFile) {
|
|
|
297
318
|
}
|
|
298
319
|
}
|
|
299
320
|
|
|
300
|
-
|
|
321
|
+
// Aggregate MCPs and skills from main session + all subagent files
|
|
322
|
+
const allMcps = new Set();
|
|
323
|
+
const allSkills = new Set();
|
|
324
|
+
const filesToScan = [filePath];
|
|
325
|
+
const subagentsDir = path.join(sessionDir, 'subagents');
|
|
326
|
+
if (fs.existsSync(subagentsDir)) {
|
|
327
|
+
fs.readdirSync(subagentsDir).filter(f => f.endsWith('.jsonl'))
|
|
328
|
+
.forEach(f => filesToScan.push(path.join(subagentsDir, f)));
|
|
329
|
+
}
|
|
330
|
+
for (const f of filesToScan) {
|
|
331
|
+
const { mcps, skills } = collectToolsUsed(f);
|
|
332
|
+
mcps.forEach(m => allMcps.add(m));
|
|
333
|
+
skills.forEach(s => allSkills.add(s));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return { ...session, agents, sessionMcps: [...allMcps], sessionSkills: [...allSkills] };
|
|
301
337
|
}
|
|
302
338
|
|
|
303
339
|
function getAgentDetail(dirName, sessionFile, agentId) {
|
package/src/public/app.js
CHANGED
|
@@ -276,6 +276,20 @@ async function loadSessionDetail(dirNameEncoded, fileEncoded) {
|
|
|
276
276
|
state.currentProject = state.projects.find(p => p.dirName === dirName);
|
|
277
277
|
}
|
|
278
278
|
|
|
279
|
+
// Build map: message uuid → skills used by agents spawned from that message
|
|
280
|
+
state.agentSkillsByUuid = {};
|
|
281
|
+
if (session.agents) {
|
|
282
|
+
for (const agent of session.agents) {
|
|
283
|
+
if (agent.spawnedByUuid && agent.skillsUsed?.length) {
|
|
284
|
+
const existing = state.agentSkillsByUuid[agent.spawnedByUuid] || [];
|
|
285
|
+
for (const s of agent.skillsUsed) {
|
|
286
|
+
if (!existing.includes(s)) existing.push(s);
|
|
287
|
+
}
|
|
288
|
+
state.agentSkillsByUuid[agent.spawnedByUuid] = existing;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
279
293
|
setBreadcrumb([state.currentProject?.path || dirName, session.title]);
|
|
280
294
|
renderSessionDetail(session, dirName, file);
|
|
281
295
|
}
|
|
@@ -288,28 +302,12 @@ function renderSessionDetail(session, dirName, file) {
|
|
|
288
302
|
? new Date(lastTimestamp) - new Date(firstTimestamp) : null;
|
|
289
303
|
const hasAgents = agents && agents.length > 0;
|
|
290
304
|
|
|
291
|
-
//
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
for (const msg of session.messages) {
|
|
295
|
-
if (msg.type !== 'assistant') continue;
|
|
296
|
-
const content = Array.isArray(msg.content) ? msg.content : [];
|
|
297
|
-
for (const block of content) {
|
|
298
|
-
if (!block || block.type !== 'tool_use') continue;
|
|
299
|
-
if (block.name.startsWith('mcp__')) {
|
|
300
|
-
const parts = block.name.split('__');
|
|
301
|
-
const server = (parts[1] || '').replace(/^claude_ai_/, '').replace(/_/g, ' ');
|
|
302
|
-
if (server) sessionMcps.add(server);
|
|
303
|
-
} else if (block.name === 'Skill' && block.input?.skill) {
|
|
304
|
-
sessionSkills.add(block.input.skill);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
const mcpBar = sessionMcps.size > 0
|
|
309
|
-
? `<div class="session-tools-bar">${[...sessionMcps].map(s => `<span class="usage-chip"><span class="tag-mcp">mcp</span>${escHtml(s)}</span>`).join('')}</div>`
|
|
305
|
+
// MCPs and skills pre-aggregated server-side (main + all subagents)
|
|
306
|
+
const mcpBar = session.sessionMcps?.length
|
|
307
|
+
? `<div class="session-tools-bar">${session.sessionMcps.map(s => `<span class="usage-chip"><span class="tag-mcp">mcp</span>${escHtml(s)}</span>`).join('')}</div>`
|
|
310
308
|
: '';
|
|
311
|
-
const skillBar = sessionSkills
|
|
312
|
-
? `<div class="session-tools-bar">${
|
|
309
|
+
const skillBar = session.sessionSkills?.length
|
|
310
|
+
? `<div class="session-tools-bar">${session.sessionSkills.map(s => `<span class="usage-chip"><span class="tag-skill">skill</span>${escHtml(s)}</span>`).join('')}</div>`
|
|
313
311
|
: '';
|
|
314
312
|
|
|
315
313
|
container.innerHTML = `
|
|
@@ -1000,6 +998,8 @@ function renderMessage(m, i, ctx, activeAgent) {
|
|
|
1000
998
|
${m.model ? `<span class="usage-chip" style="color:var(--accent)">${escHtml(modelShort(m.model))}</span>` : ''}
|
|
1001
999
|
${mcpServers.map(s => `<span class="usage-chip"><span class="tag-mcp">mcp</span>${escHtml(s)}</span>`).join('')}
|
|
1002
1000
|
${skillsUsed.map(s => `<span class="usage-chip"><span class="tag-skill">skill</span>${escHtml(s)}</span>`).join('')}
|
|
1001
|
+
${activeAgent?.kind === 'skill' && !skillsUsed.includes(activeAgent.name) ? `<span class="usage-chip"><span class="tag-skill">skill</span>${escHtml(activeAgent.name)}</span>` : ''}
|
|
1002
|
+
${(state.agentSkillsByUuid?.[m.uuid] || []).filter(s => !skillsUsed.includes(s)).map(s => `<span class="usage-chip"><span class="tag-skill">skill</span>${escHtml(s)}</span>`).join('')}
|
|
1003
1003
|
</div>`;
|
|
1004
1004
|
}
|
|
1005
1005
|
|
package/src/public/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>ccanalyzer</title>
|
|
7
|
-
<link rel="stylesheet" href="style.css?v=
|
|
7
|
+
<link rel="stylesheet" href="style.css?v=9" />
|
|
8
8
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -33,6 +33,6 @@
|
|
|
33
33
|
<div class="spinner"></div>
|
|
34
34
|
</div>
|
|
35
35
|
|
|
36
|
-
<script src="app.js?v=
|
|
36
|
+
<script src="app.js?v=9"></script>
|
|
37
37
|
</body>
|
|
38
38
|
</html>
|