ccanalyzer 1.0.1 → 1.1.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/bin/index.js +1 -1
- package/package.json +1 -1
- package/src/parser.js +37 -1
- package/src/public/app.js +134 -4
- package/src/public/index.html +4 -4
- package/src/public/style.css +62 -2
package/bin/index.js
CHANGED
|
@@ -5,7 +5,7 @@ const { startServer } = require('../src/server');
|
|
|
5
5
|
const port = parseInt(process.env.PORT || '3737', 10);
|
|
6
6
|
|
|
7
7
|
startServer(port).then(({ url }) => {
|
|
8
|
-
console.log(`\n
|
|
8
|
+
console.log(`\n ccanalyzer running at ${url}\n`);
|
|
9
9
|
try {
|
|
10
10
|
// Try to open browser
|
|
11
11
|
import('open').then(m => m.default(url)).catch(() => {});
|
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
|
@@ -62,6 +62,27 @@ function showView(name) {
|
|
|
62
62
|
});
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
function pushUrl(params) {
|
|
66
|
+
const qs = new URLSearchParams(params).toString();
|
|
67
|
+
history.pushState(params, '', qs ? '?' + qs : location.pathname);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function restoreFromUrl() {
|
|
71
|
+
const p = new URLSearchParams(location.search);
|
|
72
|
+
const project = p.get('project');
|
|
73
|
+
const session = p.get('session');
|
|
74
|
+
if (project && session) return loadSessionDetail(encodeURIComponent(project), encodeURIComponent(session));
|
|
75
|
+
if (project) return loadSessions(encodeURIComponent(project));
|
|
76
|
+
loadDashboard();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
window.addEventListener('popstate', e => {
|
|
80
|
+
const d = e.state || {};
|
|
81
|
+
if (d.session && d.project) loadSessionDetail(encodeURIComponent(d.project), encodeURIComponent(d.session));
|
|
82
|
+
else if (d.project) loadSessions(encodeURIComponent(d.project));
|
|
83
|
+
else loadDashboard();
|
|
84
|
+
});
|
|
85
|
+
|
|
65
86
|
function setBreadcrumb(parts) {
|
|
66
87
|
$('breadcrumb').innerHTML = parts.map(p => `<div style="color:var(--text3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(p)}</div>`).join('');
|
|
67
88
|
}
|
|
@@ -75,6 +96,7 @@ async function api(path) {
|
|
|
75
96
|
|
|
76
97
|
/* ── Dashboard ── */
|
|
77
98
|
async function loadDashboard() {
|
|
99
|
+
pushUrl({});
|
|
78
100
|
showView('dashboard');
|
|
79
101
|
setBreadcrumb(['Dashboard']);
|
|
80
102
|
const container = $('view-dashboard');
|
|
@@ -178,6 +200,7 @@ function initActivityChart() {
|
|
|
178
200
|
/* ── Sessions list ── */
|
|
179
201
|
async function loadSessions(dirNameEncoded) {
|
|
180
202
|
const dirName = decodeURIComponent(dirNameEncoded);
|
|
203
|
+
pushUrl({ project: dirName });
|
|
181
204
|
showView('sessions');
|
|
182
205
|
|
|
183
206
|
if (!state.projects) {
|
|
@@ -230,6 +253,7 @@ async function loadSessions(dirNameEncoded) {
|
|
|
230
253
|
async function loadSessionDetail(dirNameEncoded, fileEncoded) {
|
|
231
254
|
const dirName = decodeURIComponent(dirNameEncoded);
|
|
232
255
|
const file = decodeURIComponent(fileEncoded);
|
|
256
|
+
pushUrl({ project: dirName, session: file });
|
|
233
257
|
showView('session-detail');
|
|
234
258
|
const container = $('view-session-detail');
|
|
235
259
|
container.innerHTML = '<div style="padding:40px;text-align:center;color:var(--text3)">Chargement...</div>';
|
|
@@ -252,6 +276,20 @@ async function loadSessionDetail(dirNameEncoded, fileEncoded) {
|
|
|
252
276
|
state.currentProject = state.projects.find(p => p.dirName === dirName);
|
|
253
277
|
}
|
|
254
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
|
+
|
|
255
293
|
setBreadcrumb([state.currentProject?.path || dirName, session.title]);
|
|
256
294
|
renderSessionDetail(session, dirName, file);
|
|
257
295
|
}
|
|
@@ -264,6 +302,14 @@ function renderSessionDetail(session, dirName, file) {
|
|
|
264
302
|
? new Date(lastTimestamp) - new Date(firstTimestamp) : null;
|
|
265
303
|
const hasAgents = agents && agents.length > 0;
|
|
266
304
|
|
|
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>`
|
|
308
|
+
: '';
|
|
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>`
|
|
311
|
+
: '';
|
|
312
|
+
|
|
267
313
|
container.innerHTML = `
|
|
268
314
|
<button class="back-btn" onclick="loadSessions('${encodeURIComponent(dirName)}')">← Sessions</button>
|
|
269
315
|
<div class="page-header">
|
|
@@ -278,6 +324,7 @@ function renderSessionDetail(session, dirName, file) {
|
|
|
278
324
|
${cwd ? `<div class="meta-item"><div class="meta-label">Répertoire</div><div class="meta-value mono">${escHtml(cwd.replace('/home/olivier-j/', '~/'))}</div></div>` : ''}
|
|
279
325
|
${gitBranch && gitBranch !== 'HEAD' ? `<div class="meta-item"><div class="meta-label">Branche</div><div class="meta-value mono" style="color:var(--green)">${escHtml(gitBranch)}</div></div>` : ''}
|
|
280
326
|
</div>
|
|
327
|
+
${mcpBar}${skillBar}
|
|
281
328
|
|
|
282
329
|
<div class="token-bar">
|
|
283
330
|
<div class="token-item"><div class="token-label">Input</div><div class="token-value input">${fmt(totalUsage.input)}</div></div>
|
|
@@ -821,15 +868,84 @@ function renderMessages(messages, ctx) {
|
|
|
821
868
|
return true;
|
|
822
869
|
});
|
|
823
870
|
if (!filtered.length) return '<div class="empty"><p>Aucun message</p></div>';
|
|
824
|
-
|
|
871
|
+
|
|
872
|
+
// Track active skill/agent context across messages
|
|
873
|
+
let activeAgent = null;
|
|
874
|
+
return filtered.map((m, i) => {
|
|
875
|
+
if (m.type === 'assistant') {
|
|
876
|
+
const content = Array.isArray(m.content) ? m.content : [];
|
|
877
|
+
for (const block of content) {
|
|
878
|
+
if (!block || block.type !== 'tool_use') continue;
|
|
879
|
+
if (block.name === 'Skill') {
|
|
880
|
+
activeAgent = { kind: 'skill', name: block.input?.skill || '?' };
|
|
881
|
+
} else if (block.name === 'Agent') {
|
|
882
|
+
const name = block.input?.description || block.input?.subagent_type || 'agent';
|
|
883
|
+
activeAgent = { kind: 'agent', name: name.slice(0, 40) };
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
return renderMessage(m, i, ctx, activeAgent);
|
|
888
|
+
}).join('');
|
|
825
889
|
}
|
|
826
890
|
|
|
827
|
-
function renderMessage(m, i, ctx) {
|
|
891
|
+
function renderMessage(m, i, ctx, activeAgent) {
|
|
828
892
|
const isUser = m.type === 'user';
|
|
829
893
|
const isAgent = m.isSidechain;
|
|
830
894
|
const collapseByDefault = !isUser && i > 0;
|
|
831
895
|
const pfx = ctx === 'modal' ? 'modal-' : '';
|
|
832
896
|
|
|
897
|
+
// Extract tool info for assistant messages (used in header hint + usage chips)
|
|
898
|
+
const msgToolNames = [];
|
|
899
|
+
const mcpServers = [];
|
|
900
|
+
const skillsUsed = [];
|
|
901
|
+
if (!isUser) {
|
|
902
|
+
const contentArr = Array.isArray(m.content) ? m.content : [];
|
|
903
|
+
for (const block of contentArr) {
|
|
904
|
+
if (!block || block.type !== 'tool_use') continue;
|
|
905
|
+
msgToolNames.push(block.name);
|
|
906
|
+
if (block.name.startsWith('mcp__')) {
|
|
907
|
+
const parts = block.name.split('__');
|
|
908
|
+
const server = (parts[1] || '').replace(/^claude_ai_/, '').replace(/_/g, ' ');
|
|
909
|
+
if (server && !mcpServers.includes(server)) mcpServers.push(server);
|
|
910
|
+
} else if (block.name === 'Skill' && block.input?.skill) {
|
|
911
|
+
const sn = block.input.skill;
|
|
912
|
+
if (!skillsUsed.includes(sn)) skillsUsed.push(sn);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
// Build a human-readable hint for the collapsed header
|
|
917
|
+
let toolHint = '';
|
|
918
|
+
if (!isUser && msgToolNames.length > 0) {
|
|
919
|
+
const hintParts = [];
|
|
920
|
+
// Agent spawns: show description
|
|
921
|
+
const contentArr2 = Array.isArray(m.content) ? m.content : [];
|
|
922
|
+
for (const block of contentArr2) {
|
|
923
|
+
if (!block || block.type !== 'tool_use') continue;
|
|
924
|
+
if (block.name === 'Agent') {
|
|
925
|
+
const desc = block.input?.description || block.input?.subagent_type || 'agent';
|
|
926
|
+
hintParts.push(`⬡ ${desc.slice(0, 40)}`);
|
|
927
|
+
} else if (block.name === 'Skill') {
|
|
928
|
+
hintParts.push(`skill:${block.input?.skill || '?'}`);
|
|
929
|
+
} else if (block.name.startsWith('mcp__')) {
|
|
930
|
+
const parts = block.name.split('__');
|
|
931
|
+
const server = (parts[1] || '').replace(/^claude_ai_/, '');
|
|
932
|
+
const tool = (parts[2] || '').replace(/^[a-z]+-/, '');
|
|
933
|
+
if (!hintParts.some(p => p.startsWith(`mcp:${server}`)))
|
|
934
|
+
hintParts.push(`mcp:${server}/${tool}`);
|
|
935
|
+
} else if (!['Read','Write','Edit','Bash','WebSearch','WebFetch','ToolSearch'].includes(block.name)) {
|
|
936
|
+
hintParts.push(block.name);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
// Fallback to common tools if nothing notable
|
|
940
|
+
if (!hintParts.length) {
|
|
941
|
+
const common = msgToolNames.slice(0, 3);
|
|
942
|
+
hintParts.push(...common);
|
|
943
|
+
if (msgToolNames.length > 3) hintParts.push(`+${msgToolNames.length - 3}`);
|
|
944
|
+
}
|
|
945
|
+
toolHint = hintParts.slice(0, 3).join(' ');
|
|
946
|
+
if (hintParts.length > 3) toolHint += ` +${hintParts.length - 3}`;
|
|
947
|
+
}
|
|
948
|
+
|
|
833
949
|
let bodyHtml = '';
|
|
834
950
|
if (isUser) {
|
|
835
951
|
if (typeof m.content === 'string') {
|
|
@@ -880,24 +996,38 @@ function renderMessage(m, i, ctx) {
|
|
|
880
996
|
${u.cache_creation_input_tokens ? `<span class="usage-chip">cache↑: <span class="cw">${fmt(u.cache_creation_input_tokens)}</span></span>` : ''}
|
|
881
997
|
${u.cache_read_input_tokens ? `<span class="usage-chip">cache↓: <span class="cr">${fmt(u.cache_read_input_tokens)}</span></span>` : ''}
|
|
882
998
|
${m.model ? `<span class="usage-chip" style="color:var(--accent)">${escHtml(modelShort(m.model))}</span>` : ''}
|
|
999
|
+
${mcpServers.map(s => `<span class="usage-chip"><span class="tag-mcp">mcp</span>${escHtml(s)}</span>`).join('')}
|
|
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('')}
|
|
883
1003
|
</div>`;
|
|
884
1004
|
}
|
|
885
1005
|
|
|
886
1006
|
const ts = m.timestamp ? fmtTime(m.timestamp) : '';
|
|
887
1007
|
const msgId = `${pfx}msg-${i}`;
|
|
888
1008
|
|
|
1009
|
+
let agentBadge = '';
|
|
1010
|
+
if (!isUser && activeAgent) {
|
|
1011
|
+
const icon = activeAgent.kind === 'skill' ? '⚡' : '⬡';
|
|
1012
|
+
const col = activeAgent.kind === 'skill' ? 'var(--purple)' : 'var(--teal)';
|
|
1013
|
+
agentBadge = `<span class="msg-agent-badge" style="color:${col}">${icon} ${escHtml(activeAgent.name)}</span>`;
|
|
1014
|
+
}
|
|
1015
|
+
|
|
889
1016
|
return `
|
|
890
1017
|
<div class="message ${isAgent ? 'sidechain' : ''} ${collapseByDefault ? 'collapsed' : ''}" id="${msgId}" data-uuid="${escHtml(m.uuid || '')}">
|
|
891
1018
|
<div class="message-header" onclick="this.parentElement.classList.toggle('collapsed')">
|
|
892
1019
|
<span class="msg-role ${isUser ? 'user' : 'assistant'}">${isUser ? '👤 User' : '🤖 AI'}</span>
|
|
1020
|
+
${agentBadge}
|
|
893
1021
|
${isAgent ? '<span class="badge badge-sidechain">agent</span>' : ''}
|
|
1022
|
+
${toolHint ? `<span class="msg-tool-hint">${escHtml(toolHint)}</span>` : ''}
|
|
894
1023
|
<div class="msg-meta">
|
|
895
1024
|
${ts ? `<span>${ts}</span>` : ''}
|
|
896
1025
|
${m.stopReason ? `<span style="color:var(--text3)">${escHtml(m.stopReason)}</span>` : ''}
|
|
897
1026
|
</div>
|
|
898
1027
|
<span class="msg-chevron">▼</span>
|
|
899
1028
|
</div>
|
|
900
|
-
<div class="message-body">${bodyHtml}
|
|
1029
|
+
<div class="message-body">${bodyHtml}</div>
|
|
1030
|
+
${usageHtml ? `<div class="message-stats">${usageHtml}</div>` : ''}
|
|
901
1031
|
</div>`;
|
|
902
1032
|
}
|
|
903
1033
|
|
|
@@ -920,4 +1050,4 @@ window.setMsgFilter = setMsgFilter;
|
|
|
920
1050
|
window.toggleTimeline = toggleTimeline;
|
|
921
1051
|
window.closeAgentModal = closeAgentModal;
|
|
922
1052
|
|
|
923
|
-
|
|
1053
|
+
restoreFromUrl();
|
package/src/public/index.html
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>
|
|
7
|
-
<link rel="stylesheet" href="style.css" />
|
|
6
|
+
<title>ccanalyzer</title>
|
|
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>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<nav id="sidebar">
|
|
13
13
|
<div class="logo">
|
|
14
14
|
<span class="logo-icon">◈</span>
|
|
15
|
-
<span class="logo-text">
|
|
15
|
+
<span class="logo-text">ccanalyzer</span>
|
|
16
16
|
</div>
|
|
17
17
|
<div id="nav-menu">
|
|
18
18
|
<a href="#" class="nav-item active" data-view="dashboard">
|
|
@@ -33,6 +33,6 @@
|
|
|
33
33
|
<div class="spinner"></div>
|
|
34
34
|
</div>
|
|
35
35
|
|
|
36
|
-
<script src="app.js"></script>
|
|
36
|
+
<script src="app.js?v=9"></script>
|
|
37
37
|
</body>
|
|
38
38
|
</html>
|
package/src/public/style.css
CHANGED
|
@@ -241,7 +241,7 @@ tr:hover td { background: var(--bg3); cursor: pointer; }
|
|
|
241
241
|
display: flex;
|
|
242
242
|
flex-wrap: wrap;
|
|
243
243
|
gap: 12px;
|
|
244
|
-
margin-bottom:
|
|
244
|
+
margin-bottom: 14px;
|
|
245
245
|
padding: 16px 20px;
|
|
246
246
|
background: var(--bg2);
|
|
247
247
|
border: 1px solid var(--border);
|
|
@@ -262,7 +262,7 @@ tr:hover td { background: var(--bg3); cursor: pointer; }
|
|
|
262
262
|
background: var(--bg2);
|
|
263
263
|
border: 1px solid var(--border);
|
|
264
264
|
border-radius: var(--radius2);
|
|
265
|
-
margin-bottom:
|
|
265
|
+
margin-bottom: 14px;
|
|
266
266
|
flex-wrap: wrap;
|
|
267
267
|
}
|
|
268
268
|
.token-item { display: flex; flex-direction: column; gap: 3px; }
|
|
@@ -335,6 +335,31 @@ tr:hover td { background: var(--bg3); cursor: pointer; }
|
|
|
335
335
|
}
|
|
336
336
|
.message.collapsed .message-body { display: none; }
|
|
337
337
|
|
|
338
|
+
.message-stats {
|
|
339
|
+
padding: 8px 16px 10px;
|
|
340
|
+
background: var(--bg2);
|
|
341
|
+
border-top: 1px solid var(--border);
|
|
342
|
+
}
|
|
343
|
+
.message-stats .usage-inline {
|
|
344
|
+
margin-top: 0;
|
|
345
|
+
padding-top: 0;
|
|
346
|
+
border-top: none;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.msg-agent-badge {
|
|
350
|
+
font-family: var(--font);
|
|
351
|
+
font-size: 10px;
|
|
352
|
+
font-weight: 600;
|
|
353
|
+
padding: 1px 6px;
|
|
354
|
+
border-radius: 4px;
|
|
355
|
+
background: #1a1e28;
|
|
356
|
+
flex-shrink: 0;
|
|
357
|
+
white-space: nowrap;
|
|
358
|
+
overflow: hidden;
|
|
359
|
+
text-overflow: ellipsis;
|
|
360
|
+
max-width: 200px;
|
|
361
|
+
}
|
|
362
|
+
|
|
338
363
|
.msg-text {
|
|
339
364
|
color: var(--text);
|
|
340
365
|
line-height: 1.65;
|
|
@@ -405,6 +430,41 @@ tr:hover td { background: var(--bg3); cursor: pointer; }
|
|
|
405
430
|
.usage-chip .cw { color: var(--yellow); }
|
|
406
431
|
.usage-chip .cr { color: var(--teal); }
|
|
407
432
|
|
|
433
|
+
.tag-mcp, .tag-skill {
|
|
434
|
+
display: inline-block;
|
|
435
|
+
font-size: 9px;
|
|
436
|
+
font-weight: 700;
|
|
437
|
+
text-transform: uppercase;
|
|
438
|
+
letter-spacing: 0.5px;
|
|
439
|
+
padding: 1px 4px;
|
|
440
|
+
border-radius: 3px;
|
|
441
|
+
margin-right: 4px;
|
|
442
|
+
}
|
|
443
|
+
.tag-mcp { background: #1a2e2a; color: var(--teal); }
|
|
444
|
+
.tag-skill { background: #221a30; color: var(--purple); }
|
|
445
|
+
|
|
446
|
+
.msg-tool-hint {
|
|
447
|
+
font-family: var(--font);
|
|
448
|
+
font-size: 11px;
|
|
449
|
+
color: var(--text3);
|
|
450
|
+
overflow: hidden;
|
|
451
|
+
text-overflow: ellipsis;
|
|
452
|
+
white-space: nowrap;
|
|
453
|
+
max-width: 320px;
|
|
454
|
+
flex-shrink: 1;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.session-tools-bar {
|
|
458
|
+
display: flex;
|
|
459
|
+
flex-wrap: wrap;
|
|
460
|
+
gap: 8px;
|
|
461
|
+
padding: 10px 16px;
|
|
462
|
+
background: var(--bg2);
|
|
463
|
+
border: 1px solid var(--border);
|
|
464
|
+
border-radius: var(--radius);
|
|
465
|
+
margin-bottom: 14px;
|
|
466
|
+
}
|
|
467
|
+
|
|
408
468
|
/* ── Back button ── */
|
|
409
469
|
.back-btn {
|
|
410
470
|
display: inline-flex;
|