<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LeanCTX — Dashboard</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
<style>
*{margin:0;padding:0;box-sizing:border-box}
:root{
--bg:#06060a;--surface:#0a0a12;--surface-2:#0f0f1a;--surface-3:#161625;
--border:rgba(255,255,255,0.06);--border-light:rgba(255,255,255,0.1);
--text:#e2e2ef;--text-bright:#f5f5ff;--muted:#6b6b88;
--green:#34d399;--green-dim:rgba(52,211,153,0.08);--green-glow:rgba(52,211,153,0.15);
--purple:#818cf8;--purple-dim:rgba(129,140,248,0.08);
--blue:#38bdf8;--blue-dim:rgba(56,189,248,0.08);
--pink:#f472b6;--pink-dim:rgba(244,114,182,0.08);
--yellow:#fbbf24;--yellow-dim:rgba(251,191,36,0.08);
--red:#f87171;--red-dim:rgba(248,113,113,0.08);
--font:'Inter',-apple-system,BlinkMacSystemFont,sans-serif;
--display:'Space Grotesk','Inter',sans-serif;
--mono:'JetBrains Mono','Fira Code',monospace;
--r:16px;--rs:10px;
}
body{background:var(--bg);color:var(--text);font-family:var(--font);min-height:100vh;
font-weight:350;-webkit-font-smoothing:antialiased}
::selection{background:var(--green);color:var(--bg)}
.app{max-width:1400px;margin:0 auto;padding:16px 28px 80px}
header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px;
padding:12px 20px;border-radius:var(--rs);
background:rgba(10,10,18,0.9);backdrop-filter:blur(24px);
border:1px solid var(--border);position:sticky;top:8px;z-index:100}
.logo{display:flex;align-items:baseline;gap:2px}
.logo h1{font-family:var(--display);font-size:18px;font-weight:700;letter-spacing:-0.02em}
.logo h1 span{background:linear-gradient(135deg,var(--green),#6ee7b7);
-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;font-weight:300}
.ver{color:var(--muted);font-size:9px;font-family:var(--mono);margin-left:8px;
background:var(--surface-2);padding:2px 6px;border-radius:4px;border:1px solid var(--border)}
.hdr-r{display:flex;align-items:center;gap:8px}
.hdr-link{color:var(--muted);font-size:10px;text-decoration:none;transition:.2s}
.hdr-link:hover{color:var(--green)}
.fg{display:flex;gap:1px;background:var(--surface);border:1px solid var(--border);border-radius:6px;padding:2px}
.fb{background:transparent;border:none;color:var(--muted);padding:3px 10px;border-radius:4px;
font-size:10px;font-family:var(--font);font-weight:500;cursor:pointer;transition:.2s}
.fb:hover{color:var(--text)}.fb.on{background:var(--surface-2);color:var(--green);border:1px solid var(--border)}
.pulse{display:flex;align-items:center;gap:5px;font-size:10px;color:var(--muted)}
.dot{width:5px;height:5px;border-radius:50%;background:var(--green);animation:p 2s ease infinite}
@keyframes p{0%,100%{opacity:1}50%{opacity:.3}}
.dot.off{background:var(--muted);animation:none}
.btn{background:transparent;border:1px solid var(--border);color:var(--muted);padding:3px 10px;
border-radius:6px;font-size:10px;font-family:var(--font);cursor:pointer;transition:.2s}
.btn:hover{border-color:var(--green);color:var(--green)}
.hero{display:grid;grid-template-columns:1.8fr 1fr 1fr 1fr;gap:10px;margin-bottom:14px}
.hero-main{position:relative;border-radius:var(--r);padding:32px 28px;overflow:hidden;
background:linear-gradient(145deg,rgba(52,211,153,0.06) 0%,rgba(129,140,248,0.03) 100%);
border:1px solid var(--green-glow)}
.hero-main::before{content:'';position:absolute;top:-60%;right:-30%;width:300px;height:300px;
background:radial-gradient(circle,rgba(52,211,153,0.08),transparent 60%);pointer-events:none}
.hero-main .hv{font-family:var(--display);font-size:48px;font-weight:700;letter-spacing:-0.04em;
line-height:1;color:var(--green);margin-bottom:8px;text-shadow:0 0 40px rgba(52,211,153,0.2)}
.hero-main .hl{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.15em;font-weight:600}
.hero-main .hs{font-size:11px;color:var(--muted);margin-top:14px;line-height:1.7}
.hero-main .hs b{color:var(--text);font-weight:500}
.hc{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);
padding:22px 18px;display:flex;flex-direction:column;justify-content:center;transition:.2s}
.hc:hover{border-color:var(--border-light);transform:translateY(-1px)}
.hc .hv{font-family:var(--display);font-size:32px;font-weight:700;letter-spacing:-0.04em;line-height:1;margin-bottom:5px}
.hc .hl{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.18em;font-weight:600}
.hc .hs{font-size:10px;color:var(--muted);margin-top:8px;font-family:var(--mono)}
.row{display:grid;gap:10px;margin-bottom:10px}
.r3{grid-template-columns:1fr 1fr 1fr}
.r21{grid-template-columns:2fr 1fr}
.r12{grid-template-columns:1fr 2fr}
.r11{grid-template-columns:1fr 1fr}
.r1{grid-template-columns:1fr}
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--r);padding:20px;transition:.2s}
.card:hover{border-color:var(--border-light)}
.card h3{font-family:var(--display);font-size:10px;color:var(--muted);margin-bottom:14px;
font-weight:600;text-transform:uppercase;letter-spacing:.18em;display:flex;align-items:center;gap:8px}
.badge{font-size:8px;background:var(--green-dim);color:var(--green);padding:2px 6px;border-radius:4px;
letter-spacing:.05em;font-family:var(--mono);font-weight:600}
.card canvas{max-height:220px}
.cost-row{display:flex;align-items:stretch;gap:0}
.cost-box{flex:1;padding:16px 12px;text-align:center;border-radius:var(--rs)}
.cost-box.bad{background:var(--red-dim);border:1px solid rgba(248,113,113,0.1)}
.cost-box.good{background:var(--green-dim);border:1px solid rgba(52,211,153,0.1)}
.cost-box .amt{font-family:var(--display);font-size:28px;font-weight:700;letter-spacing:-.03em}
.cost-box .lb{font-size:8px;color:var(--muted);text-transform:uppercase;letter-spacing:.12em;font-weight:600;margin-top:3px}
.cost-arrow{display:flex;align-items:center;padding:0 12px;color:var(--green);font-size:18px;opacity:.6}
.cost-detail{display:grid;grid-template-columns:1fr 1fr;gap:6px;margin-top:12px}
.cd-item{background:var(--surface-2);border-radius:8px;padding:10px;text-align:center}
.cd-item .v{font-family:var(--display);font-size:18px;font-weight:700;letter-spacing:-.02em}
.cd-item .l{font-size:8px;color:var(--muted);text-transform:uppercase;letter-spacing:.1em;margin-top:2px}
.src-grid{display:grid;grid-template-columns:1fr 1fr;gap:0;border:1px solid var(--border);border-radius:var(--rs);overflow:hidden}
.src-item{padding:14px 16px;background:var(--surface)}
.src-item:first-child{border-right:1px solid var(--border)}
.src-item h4{font-size:9px;color:var(--muted);text-transform:uppercase;letter-spacing:.12em;
font-weight:600;margin-bottom:8px;display:flex;align-items:center;gap:5px}
.src-item h4 .d{width:7px;height:7px;border-radius:50%}
.sr{display:flex;justify-content:space-between;padding:2px 0;font-size:10px}
.sr .sl{color:var(--muted)}.sr .sv{font-family:var(--mono);font-weight:500}
table{width:100%;border-collapse:collapse}
th{text-align:left;font-size:9px;color:var(--muted);text-transform:uppercase;letter-spacing:.12em;
padding:7px 10px;border-bottom:1px solid var(--border);font-weight:600}
th.r{text-align:right}
td{padding:6px 10px;font-size:11px;border-bottom:1px solid var(--border);font-family:var(--mono)}
td.r{text-align:right}
tr:last-child td{border-bottom:none}
tr:hover td{background:var(--surface-2)}
.tag{display:inline-block;padding:1px 5px;border-radius:4px;font-size:9px;font-weight:600;font-family:var(--mono)}
.tg{background:var(--green-dim);color:var(--green)}
.tp{background:var(--purple-dim);color:var(--purple)}
.tb{background:var(--blue-dim);color:var(--blue)}
.ty{background:var(--yellow-dim);color:var(--yellow)}
.td{background:var(--red-dim);color:var(--red)}
.bar-bg{background:var(--surface-2);border-radius:3px;height:4px;overflow:hidden;margin-top:2px}
.bar-f{height:100%;border-radius:3px;transition:width .6s cubic-bezier(.22,1,.36,1)}
.tbtn{background:transparent;border:1px solid var(--border);color:var(--muted);padding:4px 14px;
border-radius:6px;font-size:10px;font-family:var(--font);cursor:pointer;transition:.2s;margin-top:8px}
.tbtn:hover{border-color:var(--green);color:var(--green)}
.empty{text-align:center;padding:100px 20px}
.empty h2{font-family:var(--display);font-size:20px;font-weight:700;margin-bottom:12px}
.empty p{font-size:13px;color:var(--muted);max-width:440px;margin:0 auto;line-height:1.7}
.empty code{background:var(--surface-3);border:1px solid var(--border);border-radius:5px;padding:.15em .4em;font-family:var(--mono);font-size:.85em}
.loading{text-align:center;padding:120px 20px;color:var(--muted);font-size:13px}
[data-live]{transition:opacity .15s}[data-live].flash{opacity:.6}
.footer{text-align:center;padding:28px 20px;color:var(--muted);font-size:10px;
border-top:1px solid var(--border);margin-top:28px}
.footer a{color:var(--green);text-decoration:none;transition:.2s}.footer a:hover{opacity:.8}
@keyframes fadeUp{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
.hero>*,.card{animation:fadeUp .4s ease both}
.hero>:nth-child(1){animation-delay:.03s}.hero>:nth-child(2){animation-delay:.06s}
.hero>:nth-child(3){animation-delay:.09s}.hero>:nth-child(4){animation-delay:.12s}
@media(max-width:1024px){.hero{grid-template-columns:1fr 1fr}.r21,.r12,.r11,.r3{grid-template-columns:1fr}
.ig{grid-template-columns:repeat(3,1fr)}.sg{grid-template-columns:repeat(2,1fr)}}
@media(max-width:640px){.hero{grid-template-columns:1fr}
header{flex-direction:column;gap:8px;position:static}
.hdr-r{flex-wrap:wrap;justify-content:center}
.ig{grid-template-columns:repeat(2,1fr)}.sg{grid-template-columns:1fr}
.src-grid{grid-template-columns:1fr}.src-item:first-child{border-right:none;border-bottom:1px solid var(--border)}}
</style>
</head>
<body>
<div class="app">
<header>
<div style="display:flex;align-items:center;gap:12px">
<div class="logo"><h1>Lean<span>CTX</span></h1><span class="ver">v2.12.7</span></div>
<a class="hdr-link" href="https://leanctx.com" target="_blank">leanctx.com</a>
</div>
<div class="hdr-r">
<div class="fg" id="rf">
<button class="fb" data-r="7" onclick="setR(7)">7d</button>
<button class="fb" data-r="30" onclick="setR(30)">30d</button>
<button class="fb on" data-r="0" onclick="setR(0)">All</button>
</div>
<div class="pulse"><span class="dot" id="aDot"></span><span id="aLbl">Live</span></div>
<button class="btn" id="aTog" onclick="togA()">Pause</button>
<button class="btn" onclick="loadAll()">Refresh</button>
</div>
</header>
<div id="updateBanner" style="display:none;background:linear-gradient(135deg,#f59e0b22,#f59e0b11);border:1px solid #f59e0b44;border-radius:10px;padding:12px 20px;margin:0 auto 16px;max-width:1200px;text-align:center;font-size:14px;color:#f59e0b">
<strong>⟳ Update available:</strong> <span id="ubCur"></span> → <strong><span id="ubNew"></span></strong>
— run: <code style="background:#f59e0b22;padding:2px 8px;border-radius:4px;font-weight:600">lean-ctx update</code>
</div>
<div id="root"><div class="loading">Loading...</div></div>
<div class="footer">LeanCTX — The Intelligence Layer for AI Coding · <a href="https://leanctx.com" target="_blank">leanctx.com</a></div>
</div>
<script>
const $=id=>document.getElementById(id);
let aOn=true,aI=null,cR=0,raw=null,built=false,ch={},pH='',showAll=false;
function fmt(n){if(n>=1e6)return(n/1e6).toFixed(1)+'M';if(n>=1e3)return(n/1e3).toFixed(1)+'K';return String(n)}
function ff(n){return n.toLocaleString('en-US')}
function pc(a,b){return b>0?Math.round(a/b*100):0}
const CM={i:2.50,o:10.0,v:450,c:120};
function ci(t){return t/1e6*CM.i}
function fu(a){return'$'+a.toFixed(2)}
function gc(inp,out,n){
const iW=inp/1e6*CM.i,iC=out/1e6*CM.i;
const eW=n*CM.v,eC=n*CM.c,oW=eW/1e6*CM.o,oC=eC/1e6*CM.o;
return{iW,iC,oW,oC,tW:iW+oW,tC:iC+oC,sv:iW+oW-iC-oC,os:eW-eC};
}
function isM(n){return n.startsWith('ctx_')}
function sb(n){return isM(n)?'<span class="tag tp">MCP</span>':'<span class="tag tb">Hook</span>'}
function ss(cmds){
let m={c:0,i:0,o:0,s:0},h={c:0,i:0,o:0,s:0};
for(const[n,s]of cmds){const t=isM(n)?m:h;t.c+=s.count;t.i+=s.input_tokens;t.o+=s.output_tokens;t.s+=s.input_tokens-s.output_tokens}
return{m,h};
}
function fd(d,r){return(!r||r===0)?d:d.slice(-r)}
function setR(r){cR=r;document.querySelectorAll('.fb').forEach(b=>b.classList.toggle('on',parseInt(b.dataset.r)===r));if(raw)upd(raw)}
function togA(){aOn=!aOn;$('aTog').textContent=aOn?'Pause':'Resume';$('aDot').classList.toggle('off',!aOn);
$('aLbl').textContent=aOn?'Live':'Paused';if(aOn)startA();else if(aI){clearInterval(aI);aI=null}}
function startA(){if(aI)clearInterval(aI);aI=setInterval(loadAll,3000)}
function lv(id,val){const el=$(id);if(!el)return;const s=String(val);if(el.textContent===s)return;
el.textContent=s;el.classList.add('flash');setTimeout(()=>el.classList.remove('flash'),200)}
function build(){
$('root').innerHTML=`
<div class="hero">
<div class="hero-main">
<div class="hv" data-live id="vSaved"></div>
<div class="hl">Total Tokens Saved</div>
<div class="hs">
<b data-live id="vSavedIn"></b> input tokens compressed<br>
<b data-live id="vSavedOut"></b> output tokens reduced via CEP/TDD
</div>
</div>
<div class="hc">
<div class="hv" data-live id="vCost" style="color:var(--yellow)"></div>
<div class="hl">Cost Saved</div>
<div class="hs" data-live id="vCostSub"></div>
</div>
<div class="hc">
<div class="hv" data-live id="vRate" style="color:var(--purple)"></div>
<div class="hl">Compression</div>
<div class="hs" data-live id="vRateSub"></div>
</div>
<div class="hc">
<div class="hv" data-live id="vCalls" style="color:var(--blue)"></div>
<div class="hl">Total Calls</div>
<div class="hs" data-live id="vCallsSub"></div>
</div>
</div>
<div class="row r21">
<div class="card"><h3>Cumulative Token Savings</h3><canvas id="cCum"></canvas></div>
<div class="card">
<h3>Cost Analysis <span class="badge">$2.50/M in · $10/M out</span></h3>
<div class="cost-row">
<div class="cost-box bad"><div class="amt" data-live id="vCW" style="color:var(--red)"></div><div class="lb">Without</div></div>
<div class="cost-arrow">→</div>
<div class="cost-box good"><div class="amt" data-live id="vCC" style="color:var(--green)"></div><div class="lb">With</div></div>
</div>
<div class="cost-detail">
<div class="cd-item"><div class="v" data-live id="vIS" style="color:var(--purple)"></div><div class="l">Input Saved</div></div>
<div class="cd-item"><div class="v" data-live id="vOS" style="color:var(--pink)"></div><div class="l">Output Saved</div></div>
</div>
<div style="text-align:center;margin-top:8px;font-size:8px;color:var(--muted)">
~<span data-live id="vOT"></span> output tokens reduced via CEP/TDD
</div>
</div>
</div>
<div class="row r3">
<div class="card"><h3>Daily Activity</h3><canvas id="cDay"></canvas></div>
<div class="card"><h3>Savings Rate</h3><canvas id="cRate"></canvas></div>
<div class="card">
<h3>MCP vs Shell Hook</h3>
<canvas id="cPie" style="max-height:140px"></canvas>
<div class="src-grid" style="margin-top:10px">
<div class="src-item">
<h4><span class="d" style="background:var(--purple)"></span>MCP Tools</h4>
<div class="sr"><span class="sl">Calls</span><span class="sv" data-live id="vMC"></span></div>
<div class="sr"><span class="sl">Saved</span><span class="sv" style="color:var(--green)" data-live id="vMS"></span></div>
<div class="sr"><span class="sl">Rate</span><span class="sv" style="color:var(--purple)" data-live id="vMR"></span></div>
</div>
<div class="src-item">
<h4><span class="d" style="background:var(--blue)"></span>Shell Hook</h4>
<div class="sr"><span class="sl">Calls</span><span class="sv" data-live id="vHC"></span></div>
<div class="sr"><span class="sl">Saved</span><span class="sv" style="color:var(--green)" data-live id="vHS"></span></div>
<div class="sr"><span class="sl">Rate</span><span class="sv" style="color:var(--blue)" data-live id="vHR"></span></div>
</div>
</div>
</div>
</div>
<div class="row r1">
<div class="card">
<h3>Command Breakdown</h3>
<div style="overflow-x:auto">
<table>
<thead><tr><th>Source</th><th>Command</th><th class="r">Count</th><th class="r">Input</th>
<th class="r">Output</th><th class="r">Saved</th><th class="r">Rate</th><th style="width:90px"></th></tr></thead>
<tbody id="tC"></tbody>
</table>
</div>
<div style="text-align:center"><button class="tbtn" id="cTog" onclick="togC()"></button></div>
</div>
</div>
`;
built=true;
}
function upd(d){
if(!d.total_commands){if(built){$('root').innerHTML='<div class="empty"><h2>No data yet</h2><p>Start using LeanCTX to see savings.</p></div>';built=false}return}
if(!built)build();
const daily=fd(d.daily||[],cR);
const iSv=d.total_input_tokens-d.total_output_tokens;
const co=gc(d.total_input_tokens,d.total_output_tokens,d.total_commands);
const tot=iSv+co.os;
const rate=pc(iSv,d.total_input_tokens);
const cmds=Object.entries(d.commands||{});
cmds.sort((a,b)=>(b[1].input_tokens-b[1].output_tokens)-(a[1].input_tokens-a[1].output_tokens));
const sp=ss(cmds);
let fu1='—';if(d.first_use)fu1=d.first_use.substring(0,10);
lv('vSaved',fmt(tot));
lv('vSavedIn',ff(iSv));
lv('vSavedOut','~'+fmt(co.os));
lv('vCost',fu(co.sv));
lv('vCostSub','input '+fu(co.iW-co.iC)+' · output '+fu(co.oW-co.oC));
lv('vRate',rate+'%');
lv('vRateSub',fmt(d.total_input_tokens)+' → '+fmt(d.total_output_tokens));
lv('vCalls',ff(d.total_commands));
lv('vCallsSub',daily.length+' days · since '+fu1);
lv('vCW',fu(co.tW));lv('vCC',fu(co.tC));
lv('vIS',fu(co.iW-co.iC));lv('vOS',fu(co.oW-co.oC));
lv('vOT',fmt(co.os));
lv('vMC',ff(sp.m.c));lv('vMS',fmt(sp.m.s));lv('vMR',pc(sp.m.s,sp.m.i)+'%');
lv('vHC',ff(sp.h.c));lv('vHS',fmt(sp.h.s));lv('vHR',pc(sp.h.s,sp.h.i)+'%');
const dh=JSON.stringify(daily.map(x=>x.date+x.commands));
const chh=JSON.stringify(cmds.map(c=>c[0]+c[1].count));
if(dh+chh!==pH){updCh(daily,cmds,sp);renC(cmds);pH=dh+chh}
}
async function load(){try{const r=await fetch('/api/stats');raw=await r.json();upd(raw)}
catch(e){if(!built)$('root').innerHTML='<div class="empty"><h2>Connection Error</h2></div>'}}
function df(){return{responsive:true,maintainAspectRatio:true,animation:{duration:500,easing:'easeOutQuart'},
plugins:{legend:{display:false}},scales:{
x:{ticks:{color:'#6b6b88',font:{size:8,family:'Inter'}},grid:{color:'rgba(255,255,255,0.025)'},border:{display:false}},
y:{ticks:{color:'#6b6b88',font:{size:8,family:'Inter'},callback:v=>fmt(v)},grid:{color:'rgba(255,255,255,0.025)'},border:{display:false}}
}}}
function updCh(daily,cmds,sp){
const lb=daily.map(d=>d.date.substring(5));
const sv=daily.map(d=>d.input_tokens-d.output_tokens);
const ot=daily.map(d=>d.output_tokens);
const rt=daily.map(d=>d.input_tokens>0?Math.round((d.input_tokens-d.output_tokens)/d.input_tokens*100):0);
let cum=[];let s=0;for(const x of sv){s+=x;cum.push(s)}
const sm=daily.length>30;
if(ch.cum){ch.cum.data.labels=lb;ch.cum.data.datasets[0].data=cum;ch.cum.update('none')}
else{ch.cum=new Chart($('cCum'),{type:'line',
data:{labels:lb,datasets:[{data:cum,fill:true,borderColor:'#34d399',backgroundColor:'rgba(52,211,153,.04)',borderWidth:2,pointRadius:sm?0:3,pointBackgroundColor:'#34d399',tension:.4}]},
options:{...df(),plugins:{legend:{display:false},tooltip:{callbacks:{label:c=>`${fmt(c.parsed.y)} tokens saved`}}}}})}
if(ch.day){ch.day.data.labels=lb;ch.day.data.datasets[0].data=sv;ch.day.data.datasets[1].data=ot;ch.day.update('none')}
else{ch.day=new Chart($('cDay'),{type:'bar',
data:{labels:lb,datasets:[
{label:'Saved',data:sv,backgroundColor:'rgba(52,211,153,.4)',borderRadius:3,borderSkipped:false},
{label:'Sent',data:ot,backgroundColor:'rgba(129,140,248,.15)',borderRadius:3,borderSkipped:false}
]},options:{...df(),plugins:{legend:{display:true,position:'bottom',labels:{color:'#6b6b88',font:{size:9,family:'Inter'},usePointStyle:true,pointStyle:'circle',padding:12}}},
scales:{...df().scales,x:{...df().scales.x,stacked:true},y:{...df().scales.y,stacked:true}}}})}
if(ch.rate){ch.rate.data.labels=lb;ch.rate.data.datasets[0].data=rt;ch.rate.update('none')}
else{ch.rate=new Chart($('cRate'),{type:'line',
data:{labels:lb,datasets:[{data:rt,fill:true,borderColor:'#818cf8',backgroundColor:'rgba(129,140,248,.04)',borderWidth:2,pointRadius:sm?0:3,pointBackgroundColor:'#818cf8',tension:.4}]},
options:{...df(),scales:{...df().scales,y:{...df().scales.y,ticks:{...df().scales.y.ticks,callback:v=>v+'%'},suggestedMin:0,suggestedMax:100}},
plugins:{legend:{display:false},tooltip:{callbacks:{label:c=>c.parsed.y+'% savings rate'}}}}})}
const top8=cmds.filter(c=>c[1].input_tokens-c[1].output_tokens>0).slice(0,8);
if(ch.cmd){ch.cmd.data.labels=top8.map(c=>c[0]);ch.cmd.data.datasets[0].data=top8.map(c=>c[1].input_tokens-c[1].output_tokens);ch.cmd.update('none')}
if(ch.pie){ch.pie.data.datasets[0].data=[sp.m.s,sp.h.s];ch.pie.update('none')}
else{ch.pie=new Chart($('cPie'),{type:'doughnut',
data:{labels:['MCP Tools','Shell Hook'],datasets:[{data:[sp.m.s,sp.h.s],backgroundColor:['#818cf8','#38bdf8'],borderWidth:0,hoverOffset:4,borderRadius:3}]},
options:{responsive:true,maintainAspectRatio:true,cutout:'70%',
plugins:{legend:{position:'bottom',labels:{color:'#6b6b88',font:{size:9,family:'Inter'},padding:10,usePointStyle:true,pointStyle:'circle'}},
tooltip:{callbacks:{label:c=>`${c.label}: ${fmt(c.parsed)} tokens`}}}}})}
}
function togC(){showAll=!showAll;if(raw){const c=Object.entries(raw.commands||{});c.sort((a,b)=>(b[1].input_tokens-b[1].output_tokens)-(a[1].input_tokens-a[1].output_tokens));renC(c)}
const b=$('cTog');if(b)b.textContent=showAll?'Show active only':'Show all commands'}
function renC(cmds){
const t=$('tC');if(!t)return;
const f=showAll?cmds:cmds.filter(([,s])=>s.input_tokens-s.output_tokens>0);
const mx=f.length>0?Math.max(...f.map(c=>c[1].input_tokens-c[1].output_tokens),1):1;
t.innerHTML=f.map(([n,s])=>{
const sv=s.input_tokens-s.output_tokens;const r=pc(sv,s.input_tokens);const bw=pc(Math.abs(sv),mx);const ng=sv<0;
return `<tr><td>${sb(n)}</td><td style="color:var(--text-bright)">${n}</td>
<td class="r">${ff(s.count)}</td><td class="r">${fmt(s.input_tokens)}</td><td class="r">${fmt(s.output_tokens)}</td>
<td class="r"><span class="tag ${ng?'td':'tg'}">${ng?'-':''}${fmt(Math.abs(sv))}</span></td>
<td class="r"><span class="tag ${r>=50?'tg':r>=20?'tp':'ty'}">${r}%</span></td>
<td><div class="bar-bg"><div class="bar-f" style="width:${bw}%;background:${isM(n)?'var(--purple)':'var(--green)'}"></div></div></td></tr>`;
}).join('');
const hid=cmds.length-f.length;const b=$('cTog');
if(b){if(hid===0&&!showAll)b.style.display='none';else{b.style.display='';b.textContent=showAll?'Show active only':'Show all ('+hid+' hidden)'}}
}
async function checkVersion(){
try{const r=await fetch('/api/version');const v=await r.json();
if(v.update_available){$('ubCur').textContent='v'+v.current;$('ubNew').textContent='v'+v.latest;$('updateBanner').style.display='block'}}catch(e){}}
async function loadAll(){await load();await checkVersion()}
loadAll();startA();
</script>
</body>
</html>