claude-code-watch 0.1.3 → 0.1.4
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 +1 -1
- package/public/index.html +151 -3
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -208,6 +208,33 @@ body {
|
|
|
208
208
|
.tree-row:hover .tree-actions { display: flex; }
|
|
209
209
|
.tree-row.selected>.tree-actions { display: flex; }
|
|
210
210
|
|
|
211
|
+
/* ── Tokens page ── */
|
|
212
|
+
#tokens-page { flex: 1; display: flex; flex-direction: column; overflow-y: auto; padding: 16px 24px; gap: 16px; background: var(--bg); }
|
|
213
|
+
.token-card { background: var(--bg2); border: 1px solid var(--border); border-radius: 8px; padding: 12px 16px; }
|
|
214
|
+
.token-card-title { font-size: 13px; font-weight: 600; color: var(--white); margin-bottom: 8px; display: flex; align-items: center; gap: 6px; }
|
|
215
|
+
.token-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 8px; }
|
|
216
|
+
.token-item { display: flex; flex-direction: column; gap: 2px; }
|
|
217
|
+
.token-label { font-size: 10px; color: var(--dim); text-transform: uppercase; letter-spacing: 0.5px; }
|
|
218
|
+
.token-value { font-size: 16px; font-weight: 600; color: var(--white); font-family: monospace; }
|
|
219
|
+
.token-bar { height: 4px; border-radius: 2px; background: var(--bg3); margin-top: 4px; }
|
|
220
|
+
.token-bar-fill { height: 100%; border-radius: 2px; background: var(--purple); transition: width 0.3s; }
|
|
221
|
+
.token-bar-fill.warn { background: var(--yellow); }
|
|
222
|
+
.token-bar-fill.danger { background: var(--red); }
|
|
223
|
+
.token-section-title { font-size: 12px; font-weight: 600; color: var(--dim); margin: 8px 0 4px; padding-bottom: 4px; border-bottom: 1px solid var(--border); }
|
|
224
|
+
.token-table { width: 100%; border-collapse: collapse; font-size: 12px; margin-top: 4px; }
|
|
225
|
+
.token-table th { background: var(--bg3); color: var(--white); padding: 6px 8px; text-align: left; font-weight: 600; font-size: 11px; border-bottom: 1px solid var(--border); }
|
|
226
|
+
.token-table td { padding: 5px 8px; color: var(--text); border-bottom: 1px solid var(--border); font-family: monospace; }
|
|
227
|
+
.token-table tr:hover td { background: var(--bg3); }
|
|
228
|
+
.token-detail-row { display: flex; gap: 4px; margin-top: 6px; font-size: 12px; flex-wrap: wrap; }
|
|
229
|
+
.token-detail-row .token-kv { color: var(--dim); }
|
|
230
|
+
.token-detail-row .token-kv b { color: var(--text); font-family: monospace; }
|
|
231
|
+
.token-usage-line { display: flex; align-items: baseline; gap: 8px; margin-top: 2px; }
|
|
232
|
+
.token-usage-line .token-pct { font-size: 12px; font-weight: 600; }
|
|
233
|
+
.token-usage-line .token-pct.warn { color: var(--yellow); }
|
|
234
|
+
.token-usage-line .token-pct.danger { color: var(--red); }
|
|
235
|
+
.token-usage-line .token-ctx-info { font-size: 11px; color: var(--dim); }
|
|
236
|
+
.token-active-dot { font-size: 10px; }
|
|
237
|
+
|
|
211
238
|
/* ── Stream panel ── */
|
|
212
239
|
#stream-panel-wrap {
|
|
213
240
|
flex: 1; display: flex; flex-direction: column; overflow: hidden;
|
|
@@ -380,6 +407,9 @@ body {
|
|
|
380
407
|
<body>
|
|
381
408
|
|
|
382
409
|
<div id="header">
|
|
410
|
+
<button class="btn on" id="tab-stream" onclick="switchTab('stream')">📡 Stream</button>
|
|
411
|
+
<button class="btn" id="tab-tokens" onclick="switchTab('tokens')">📊 Tokens</button>
|
|
412
|
+
<span class="sep">│</span>
|
|
383
413
|
<button class="btn on" id="btn-thinking" onclick="toggleThinking()" data-tooltip="Toggle thinking">🧠 Thinking</button>
|
|
384
414
|
<button class="btn on" id="btn-tool-input" onclick="toggleToolInput()" data-tooltip="Toggle tool input">🔧 Tools</button>
|
|
385
415
|
<button class="btn on" id="btn-tool-output" onclick="toggleToolOutput()" data-tooltip="Toggle tool output">📤 Output</button>
|
|
@@ -424,6 +454,22 @@ body {
|
|
|
424
454
|
</div>
|
|
425
455
|
</div>
|
|
426
456
|
|
|
457
|
+
<div id="tokens-page" style="display:none">
|
|
458
|
+
<div class="token-card" id="token-overview">
|
|
459
|
+
<div class="token-card-title">📊 Token Overview</div>
|
|
460
|
+
<div class="token-grid" id="token-overview-grid"></div>
|
|
461
|
+
</div>
|
|
462
|
+
<div class="token-section-title">按 Agent 分项</div>
|
|
463
|
+
<div id="token-agent-cards"></div>
|
|
464
|
+
<div class="token-section-title" style="cursor:pointer" onclick="toggleTokenTable()" id="token-table-toggle">明细表格 ▸</div>
|
|
465
|
+
<div id="token-table-wrap" style="display:none">
|
|
466
|
+
<table class="token-table" id="token-detail-table">
|
|
467
|
+
<thead><tr><th>Agent</th><th>Model</th><th>Input</th><th>Output</th><th>Cache+</th><th>Cache Read</th><th>Context</th><th>%</th><th>I/O</th></tr></thead>
|
|
468
|
+
<tbody id="token-table-body"></tbody>
|
|
469
|
+
</table>
|
|
470
|
+
</div>
|
|
471
|
+
</div>
|
|
472
|
+
|
|
427
473
|
<div id="footer">
|
|
428
474
|
<span id="scroll-pos">0%</span>
|
|
429
475
|
<span class="sep">│</span>
|
|
@@ -516,6 +562,7 @@ let showActivity = true;
|
|
|
516
562
|
let showTokenCount = true;
|
|
517
563
|
let autoDiscovery = true;
|
|
518
564
|
let appVersion = '';
|
|
565
|
+
let currentTab = 'stream';
|
|
519
566
|
|
|
520
567
|
const HIDDEN_KEY = 'claude-watch-hidden';
|
|
521
568
|
function loadHiddenSessions() {
|
|
@@ -546,8 +593,8 @@ function computeTokensFromContext() {
|
|
|
546
593
|
for (const ctx of Object.values(contextData)) {
|
|
547
594
|
totalInput += ctx.inputTokens || 0;
|
|
548
595
|
totalOutput += ctx.outputTokens || 0;
|
|
549
|
-
totalCacheCreate += ctx.
|
|
550
|
-
totalCacheRead += ctx.
|
|
596
|
+
totalCacheCreate += ctx.cacheCreation || 0;
|
|
597
|
+
totalCacheRead += ctx.cacheRead || 0;
|
|
551
598
|
}
|
|
552
599
|
}
|
|
553
600
|
|
|
@@ -676,7 +723,7 @@ function handleMessage(msg) {
|
|
|
676
723
|
case 'newBackgroundTask': handleNewBgTask(msg.payload); break;
|
|
677
724
|
case 'sessionRemoved': handleSessionRemoved(msg.payload); break;
|
|
678
725
|
case 'autoDiscoveryChanged': autoDiscovery = msg.payload.enabled; scheduleRender(); break;
|
|
679
|
-
case 'context': contextData = msg.payload; updateTreeDots(); refreshButtons(); break;
|
|
726
|
+
case 'context': contextData = msg.payload; updateTreeDots(); refreshButtons(); if (currentTab === 'tokens') renderTokenPage(); break;
|
|
680
727
|
case 'config':
|
|
681
728
|
if (msg.payload.version) appVersion = msg.payload.version;
|
|
682
729
|
if (msg.payload.collapseAfter > 0 && !collapseTimer) {
|
|
@@ -1630,6 +1677,107 @@ function toggleTokenDisplay() {
|
|
|
1630
1677
|
scheduleRender();
|
|
1631
1678
|
refreshButtons();
|
|
1632
1679
|
}
|
|
1680
|
+
|
|
1681
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
1682
|
+
// Tab switching & Token page
|
|
1683
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
1684
|
+
|
|
1685
|
+
function switchTab(tab) {
|
|
1686
|
+
currentTab = tab;
|
|
1687
|
+
document.getElementById('main').style.display = tab === 'stream' ? 'flex' : 'none';
|
|
1688
|
+
document.getElementById('tokens-page').style.display = tab === 'tokens' ? 'flex' : 'none';
|
|
1689
|
+
document.getElementById('tab-stream').classList.toggle('on', tab === 'stream');
|
|
1690
|
+
document.getElementById('tab-tokens').classList.toggle('on', tab === 'tokens');
|
|
1691
|
+
// footer 只在 stream 模式下有意义
|
|
1692
|
+
document.getElementById('footer').style.display = tab === 'stream' ? 'flex' : 'none';
|
|
1693
|
+
if (tab === 'tokens') renderTokenPage();
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
let tokenTableVisible = false;
|
|
1697
|
+
function toggleTokenTable() {
|
|
1698
|
+
tokenTableVisible = !tokenTableVisible;
|
|
1699
|
+
document.getElementById('token-table-wrap').style.display = tokenTableVisible ? 'block' : 'none';
|
|
1700
|
+
document.getElementById('token-table-toggle').textContent = '明细表格 ' + (tokenTableVisible ? '▾' : '▸');
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function renderTokenPage() {
|
|
1704
|
+
computeTokensFromContext();
|
|
1705
|
+
const entries = Object.entries(contextData);
|
|
1706
|
+
if (entries.length === 0) {
|
|
1707
|
+
document.getElementById('token-overview-grid').innerHTML = '<div style="color:var(--dim);padding:8px">暂无 Token 数据</div>';
|
|
1708
|
+
document.getElementById('token-agent-cards').innerHTML = '';
|
|
1709
|
+
document.getElementById('token-table-body').innerHTML = '';
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
// ── Overview card ──
|
|
1714
|
+
const overviewGrid = document.getElementById('token-overview-grid');
|
|
1715
|
+
const overviewItems = [
|
|
1716
|
+
{ label: 'Input Tokens', value: fmtTok(totalInput), pct: null },
|
|
1717
|
+
{ label: 'Output Tokens', value: fmtTok(totalOutput), pct: null },
|
|
1718
|
+
{ label: 'Cache Creation', value: fmtTok(totalCacheCreate), pct: null },
|
|
1719
|
+
{ label: 'Cache Read', value: fmtTok(totalCacheRead), pct: null },
|
|
1720
|
+
{ label: 'I/O Ratio', value: totalOutput > 0 ? (totalInput / totalOutput).toFixed(1) + ' : 1' : '—', pct: null },
|
|
1721
|
+
];
|
|
1722
|
+
overviewGrid.innerHTML = overviewItems.map(it =>
|
|
1723
|
+
`<div class="token-item"><span class="token-label">${it.label}</span><span class="token-value">${it.value}</span></div>`
|
|
1724
|
+
).join('');
|
|
1725
|
+
|
|
1726
|
+
// ── Agent cards ──
|
|
1727
|
+
const agentCardsEl = document.getElementById('token-agent-cards');
|
|
1728
|
+
// Sort by lastActivity descending (active first)
|
|
1729
|
+
const sorted = entries.sort((a, b) => (b[1].lastActivity || 0) - (a[1].lastActivity || 0));
|
|
1730
|
+
agentCardsEl.innerHTML = sorted.map(([key, ctx]) => {
|
|
1731
|
+
const [sid, agentId] = key.split(':');
|
|
1732
|
+
const isMain = agentId === 'main' || !agentId.includes('-');
|
|
1733
|
+
const icon = isMain ? '🗣' : '🤖';
|
|
1734
|
+
const agentName = isMain ? 'Main' : agentId;
|
|
1735
|
+
const active = ctx.lastActivity && (Date.now() - ctx.lastActivity < 180000);
|
|
1736
|
+
const activeDot = active ? '<span class="token-active-dot">🟢</span>' : '<span class="token-active-dot">⚪</span>';
|
|
1737
|
+
|
|
1738
|
+
const pct = ctx.contextWindow > 0 ? Math.round(ctx.inputTokens / ctx.contextWindow * 100) : 0;
|
|
1739
|
+
const pctCls = pct > 80 ? 'danger' : pct > 50 ? 'warn' : '';
|
|
1740
|
+
const barCls = pct > 80 ? 'danger' : pct > 50 ? 'warn' : '';
|
|
1741
|
+
const barWidth = Math.min(pct, 100);
|
|
1742
|
+
|
|
1743
|
+
const ioRatio = ctx.outputTokens > 0 ? (ctx.inputTokens / ctx.outputTokens).toFixed(1) + ' : 1' : '—';
|
|
1744
|
+
|
|
1745
|
+
return `<div class="token-card">
|
|
1746
|
+
<div class="token-card-title">${icon} ${esc(agentName)} ${ctx.model ? '· ' + esc(ctx.model) : ''} ${activeDot}</div>
|
|
1747
|
+
<div class="token-usage-line">
|
|
1748
|
+
<span class="token-pct ${pctCls}">${pct}%</span>
|
|
1749
|
+
<span class="token-ctx-info">${fmtTok(ctx.inputTokens)} / ${fmtTok(ctx.contextWindow)}</span>
|
|
1750
|
+
</div>
|
|
1751
|
+
<div class="token-bar"><div class="token-bar-fill ${barCls}" style="width:${barWidth}%"></div></div>
|
|
1752
|
+
<div class="token-detail-row">
|
|
1753
|
+
<span class="token-kv">Output: <b>${fmtTok(ctx.outputTokens)}</b></span>
|
|
1754
|
+
<span class="token-kv">Cache+: <b>${fmtTok(ctx.cacheCreation)}</b></span>
|
|
1755
|
+
<span class="token-kv">Cache Read: <b>${fmtTok(ctx.cacheRead)}</b></span>
|
|
1756
|
+
<span class="token-kv">I/O: <b>${ioRatio}</b></span>
|
|
1757
|
+
</div>
|
|
1758
|
+
</div>`;
|
|
1759
|
+
}).join('');
|
|
1760
|
+
|
|
1761
|
+
// ── Detail table ──
|
|
1762
|
+
const tbody = document.getElementById('token-table-body');
|
|
1763
|
+
tbody.innerHTML = sorted.map(([key, ctx]) => {
|
|
1764
|
+
const [sid, agentId] = key.split(':');
|
|
1765
|
+
const agentName = (agentId === 'main' || !agentId.includes('-')) ? 'Main' : agentId;
|
|
1766
|
+
const pct = ctx.contextWindow > 0 ? (ctx.inputTokens / ctx.contextWindow * 100).toFixed(1) + '%' : '—';
|
|
1767
|
+
const ioRatio = ctx.outputTokens > 0 ? (ctx.inputTokens / ctx.outputTokens).toFixed(1) : '—';
|
|
1768
|
+
return `<tr>
|
|
1769
|
+
<td>${esc(agentName)}</td>
|
|
1770
|
+
<td>${esc(ctx.model || '—')}</td>
|
|
1771
|
+
<td>${ctx.inputTokens}</td>
|
|
1772
|
+
<td>${ctx.outputTokens}</td>
|
|
1773
|
+
<td>${ctx.cacheCreation}</td>
|
|
1774
|
+
<td>${ctx.cacheRead}</td>
|
|
1775
|
+
<td>${ctx.contextWindow}</td>
|
|
1776
|
+
<td>${pct}</td>
|
|
1777
|
+
<td>${ioRatio}</td>
|
|
1778
|
+
</tr>`;
|
|
1779
|
+
}).join('');
|
|
1780
|
+
}
|
|
1633
1781
|
function toggleAutoScroll() { autoScroll = !autoScroll; if (autoScroll) streamEl.scrollTop = streamEl.scrollHeight; renderAll(); }
|
|
1634
1782
|
function toggleTree() { showTree = !showTree; document.getElementById('tree-panel').classList.toggle('hidden', !showTree); }
|
|
1635
1783
|
function toggleAutoDiscovery() { sendCmd('toggleAutoDiscovery'); }
|